View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *    http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.hadoop.hbase.spark;
19  
20  import com.google.protobuf.ByteString;
21  import com.google.protobuf.InvalidProtocolBufferException;
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.apache.hadoop.hbase.Cell;
25  import org.apache.hadoop.hbase.exceptions.DeserializationException;
26  import org.apache.hadoop.hbase.filter.FilterBase;
27  import org.apache.hadoop.hbase.spark.protobuf.generated.FilterProtos;
28  import org.apache.hadoop.hbase.util.ByteStringer;
29  import org.apache.hadoop.hbase.util.Bytes;
30  import scala.collection.mutable.MutableList;
31  
32  import java.io.IOException;
33  import java.util.HashMap;
34  import java.util.List;
35  import java.util.Map;
36  
37  /**
38   * This filter will push down all qualifier logic given to us
39   * by SparkSQL so that we have make the filters at the region server level
40   * and avoid sending the data back to the client to be filtered.
41   */
42  public class SparkSQLPushDownFilter extends FilterBase{
43    protected static final Log log = LogFactory.getLog(SparkSQLPushDownFilter.class);
44  
45    //The following values are populated with protobuffer
46    DynamicLogicExpression dynamicLogicExpression;
47    byte[][] valueFromQueryArray;
48    HashMap<ByteArrayComparable, HashMap<ByteArrayComparable, String>>
49            currentCellToColumnIndexMap;
50  
51    //The following values are transient
52    HashMap<String, ByteArrayComparable> columnToCurrentRowValueMap = null;
53  
54    static final byte[] rowKeyFamily = new byte[0];
55    static final byte[] rowKeyQualifier = Bytes.toBytes("key");
56  
57    public SparkSQLPushDownFilter(DynamicLogicExpression dynamicLogicExpression,
58                                  byte[][] valueFromQueryArray,
59                                  HashMap<ByteArrayComparable,
60                                          HashMap<ByteArrayComparable, String>>
61                                          currentCellToColumnIndexMap) {
62      this.dynamicLogicExpression = dynamicLogicExpression;
63      this.valueFromQueryArray = valueFromQueryArray;
64      this.currentCellToColumnIndexMap = currentCellToColumnIndexMap;
65    }
66  
67    public SparkSQLPushDownFilter(DynamicLogicExpression dynamicLogicExpression,
68                                  byte[][] valueFromQueryArray,
69                                  MutableList<SchemaQualifierDefinition> columnDefinitions) {
70      this.dynamicLogicExpression = dynamicLogicExpression;
71      this.valueFromQueryArray = valueFromQueryArray;
72  
73      //generate family qualifier to index mapping
74      this.currentCellToColumnIndexMap =
75              new HashMap<>();
76  
77      for (int i = 0; i < columnDefinitions.size(); i++) {
78        SchemaQualifierDefinition definition = columnDefinitions.get(i).get();
79  
80        ByteArrayComparable familyByteComparable =
81                new ByteArrayComparable(definition.columnFamilyBytes(),
82                        0, definition.columnFamilyBytes().length);
83  
84        HashMap<ByteArrayComparable, String> qualifierIndexMap =
85                currentCellToColumnIndexMap.get(familyByteComparable);
86  
87        if (qualifierIndexMap == null) {
88          qualifierIndexMap = new HashMap<>();
89          currentCellToColumnIndexMap.put(familyByteComparable, qualifierIndexMap);
90        }
91        ByteArrayComparable qualifierByteComparable =
92                new ByteArrayComparable(definition.qualifierBytes(), 0,
93                        definition.qualifierBytes().length);
94  
95        qualifierIndexMap.put(qualifierByteComparable, definition.columnName());
96      }
97    }
98  
99    @Override
100   public ReturnCode filterKeyValue(Cell c) throws IOException {
101 
102     //If the map RowValueMap is empty then we need to populate
103     // the row key
104     if (columnToCurrentRowValueMap == null) {
105       columnToCurrentRowValueMap = new HashMap<>();
106       HashMap<ByteArrayComparable, String> qualifierColumnMap =
107               currentCellToColumnIndexMap.get(
108                       new ByteArrayComparable(rowKeyFamily, 0, rowKeyFamily.length));
109 
110       if (qualifierColumnMap != null) {
111         String rowKeyColumnName =
112                 qualifierColumnMap.get(
113                         new ByteArrayComparable(rowKeyQualifier, 0,
114                                 rowKeyQualifier.length));
115         //Make sure that the rowKey is part of the where clause
116         if (rowKeyColumnName != null) {
117           columnToCurrentRowValueMap.put(rowKeyColumnName,
118                   new ByteArrayComparable(c.getRowArray(),
119                           c.getRowOffset(), c.getRowLength()));
120         }
121       }
122     }
123 
124     //Always populate the column value into the RowValueMap
125     ByteArrayComparable currentFamilyByteComparable =
126             new ByteArrayComparable(c.getFamilyArray(),
127             c.getFamilyOffset(),
128             c.getFamilyLength());
129 
130     HashMap<ByteArrayComparable, String> qualifierColumnMap =
131             currentCellToColumnIndexMap.get(
132                     currentFamilyByteComparable);
133 
134     if (qualifierColumnMap != null) {
135 
136       String columnName =
137               qualifierColumnMap.get(
138                       new ByteArrayComparable(c.getQualifierArray(),
139                               c.getQualifierOffset(),
140                               c.getQualifierLength()));
141 
142       if (columnName != null) {
143         columnToCurrentRowValueMap.put(columnName,
144                 new ByteArrayComparable(c.getValueArray(),
145                         c.getValueOffset(), c.getValueLength()));
146       }
147     }
148 
149     return ReturnCode.INCLUDE;
150   }
151 
152 
153   @Override
154   public boolean filterRow() throws IOException {
155 
156     try {
157       boolean result =
158               dynamicLogicExpression.execute(columnToCurrentRowValueMap,
159                       valueFromQueryArray);
160       columnToCurrentRowValueMap = null;
161       return !result;
162     } catch (Throwable e) {
163       log.error("Error running dynamic logic on row", e);
164     }
165     return false;
166   }
167 
168 
169   /**
170    * @param pbBytes A pb serialized instance
171    * @return An instance of SparkSQLPushDownFilter
172    * @throws org.apache.hadoop.hbase.exceptions.DeserializationException
173    */
174   @SuppressWarnings("unused")
175   public static SparkSQLPushDownFilter parseFrom(final byte[] pbBytes)
176           throws DeserializationException {
177 
178     FilterProtos.SQLPredicatePushDownFilter proto;
179     try {
180       proto = FilterProtos.SQLPredicatePushDownFilter.parseFrom(pbBytes);
181     } catch (InvalidProtocolBufferException e) {
182       throw new DeserializationException(e);
183     }
184 
185     //Load DynamicLogicExpression
186     DynamicLogicExpression dynamicLogicExpression =
187             DynamicLogicExpressionBuilder.build(proto.getDynamicLogicExpression());
188 
189     //Load valuesFromQuery
190     final List<ByteString> valueFromQueryArrayList = proto.getValueFromQueryArrayList();
191     byte[][] valueFromQueryArray = new byte[valueFromQueryArrayList.size()][];
192     for (int i = 0; i < valueFromQueryArrayList.size(); i++) {
193       valueFromQueryArray[i] = valueFromQueryArrayList.get(i).toByteArray();
194     }
195 
196     //Load mapping from HBase family/qualifier to Spark SQL columnName
197     HashMap<ByteArrayComparable, HashMap<ByteArrayComparable, String>>
198             currentCellToColumnIndexMap = new HashMap<>();
199 
200     for (FilterProtos.SQLPredicatePushDownCellToColumnMapping
201             sqlPredicatePushDownCellToColumnMapping :
202             proto.getCellToColumnMappingList()) {
203 
204       byte[] familyArray =
205               sqlPredicatePushDownCellToColumnMapping.getColumnFamily().toByteArray();
206       ByteArrayComparable familyByteComparable =
207               new ByteArrayComparable(familyArray, 0, familyArray.length);
208       HashMap<ByteArrayComparable, String> qualifierMap =
209               currentCellToColumnIndexMap.get(familyByteComparable);
210 
211       if (qualifierMap == null) {
212         qualifierMap = new HashMap<>();
213         currentCellToColumnIndexMap.put(familyByteComparable, qualifierMap);
214       }
215       byte[] qualifierArray =
216               sqlPredicatePushDownCellToColumnMapping.getQualifier().toByteArray();
217 
218       ByteArrayComparable qualifierByteComparable =
219               new ByteArrayComparable(qualifierArray, 0 ,qualifierArray.length);
220 
221       qualifierMap.put(qualifierByteComparable,
222               sqlPredicatePushDownCellToColumnMapping.getColumnName());
223     }
224 
225     return new SparkSQLPushDownFilter(dynamicLogicExpression,
226             valueFromQueryArray, currentCellToColumnIndexMap);
227   }
228 
229   /**
230    * @return The filter serialized using pb
231    */
232   public byte[] toByteArray() {
233 
234     FilterProtos.SQLPredicatePushDownFilter.Builder builder =
235             FilterProtos.SQLPredicatePushDownFilter.newBuilder();
236 
237     FilterProtos.SQLPredicatePushDownCellToColumnMapping.Builder columnMappingBuilder =
238             FilterProtos.SQLPredicatePushDownCellToColumnMapping.newBuilder();
239 
240     builder.setDynamicLogicExpression(dynamicLogicExpression.toExpressionString());
241     for (byte[] valueFromQuery: valueFromQueryArray) {
242       builder.addValueFromQueryArray(ByteStringer.wrap(valueFromQuery));
243     }
244 
245     for (Map.Entry<ByteArrayComparable, HashMap<ByteArrayComparable, String>>
246             familyEntry : currentCellToColumnIndexMap.entrySet()) {
247       for (Map.Entry<ByteArrayComparable, String> qualifierEntry :
248               familyEntry.getValue().entrySet()) {
249         columnMappingBuilder.setColumnFamily(
250                 ByteStringer.wrap(familyEntry.getKey().bytes()));
251         columnMappingBuilder.setQualifier(
252                 ByteStringer.wrap(qualifierEntry.getKey().bytes()));
253         columnMappingBuilder.setColumnName(qualifierEntry.getValue());
254         builder.addCellToColumnMapping(columnMappingBuilder.build());
255       }
256     }
257 
258     return builder.build().toByteArray();
259   }
260 }