View Javadoc

1   /*
2    * Copyright (c) 2011.  The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package org.apache.hadoop.hbase.hbql.statement;
22  
23  import org.apache.expreval.expr.TypeSupport;
24  import org.apache.expreval.expr.function.DelegateFunction;
25  import org.apache.expreval.expr.literal.DefaultKeyword;
26  import org.apache.expreval.expr.literal.NullLiteral;
27  import org.apache.expreval.expr.node.GenericValue;
28  import org.apache.expreval.expr.var.DelegateColumn;
29  import org.apache.hadoop.hbase.hbql.client.ExecutionResults;
30  import org.apache.hadoop.hbase.hbql.client.HBatch;
31  import org.apache.hadoop.hbase.hbql.client.HBqlException;
32  import org.apache.hadoop.hbase.hbql.client.HRecord;
33  import org.apache.hadoop.hbase.hbql.impl.HConnectionImpl;
34  import org.apache.hadoop.hbase.hbql.impl.InvalidTypeException;
35  import org.apache.hadoop.hbase.hbql.mapping.ColumnAttrib;
36  import org.apache.hadoop.hbase.hbql.statement.args.InsertValueSource;
37  import org.apache.hadoop.hbase.hbql.statement.select.SelectExpressionContext;
38  import org.apache.hadoop.hbase.hbql.util.Lists;
39  
40  import java.util.List;
41  
42  public class InsertStatement extends StatementWithParameters implements ConnectionStatement {
43  
44      private final List<SelectExpressionContext> columnList = Lists.newArrayList();
45      private final InsertValueSource insertValuesSource;
46  
47      private HConnectionImpl connection          = null;
48      private boolean         validated           = false;
49      private HRecord         record              = null;
50      private String          invalidInsertColumn = null;
51  
52      public InsertStatement(final StatementPredicate predicate,
53                             final String mappingName,
54                             final List<GenericValue> columnList,
55                             final InsertValueSource insertValuesSource) {
56          super(predicate, mappingName);
57  
58          for (final GenericValue val : columnList) {
59              // See if a group of columns are indicated with family(col1, col2), which looks like a function call
60              if (val instanceof DelegateFunction) {
61                  final DelegateFunction function = (DelegateFunction)val;
62                  final String familyName = function.getFunctionName();
63                  for (final GenericValue columnarg : function.getGenericValueList()) {
64                      if (columnarg instanceof DelegateColumn) {
65                          final String columnName = ((DelegateColumn)columnarg).getVariableName();
66                          final DelegateColumn col = new DelegateColumn(familyName + ":" + columnName);
67                          this.getInsertColumnList().add(SelectExpressionContext.newExpression(col, null));
68                      }
69                      else {
70                          // Throw exception in validate()
71                          if (invalidInsertColumn == null)
72                              invalidInsertColumn = columnarg.asString();
73                      }
74                  }
75              }
76              else {
77                  this.getInsertColumnList().add(SelectExpressionContext.newExpression(val, null));
78              }
79          }
80  
81          this.insertValuesSource = insertValuesSource;
82          this.getInsertValuesSource().setInsertStatement(this);
83      }
84  
85      private boolean isValidated() {
86          return this.validated;
87      }
88  
89      private HRecord getHRecord() {
90          return this.record;
91      }
92  
93      public HConnectionImpl getConnection() {
94          return this.connection;
95      }
96  
97      private List<SelectExpressionContext> getInsertColumnList() {
98          return this.columnList;
99      }
100 
101     private InsertValueSource getInsertValuesSource() {
102         return this.insertValuesSource;
103     }
104 
105     public void validate(final HConnectionImpl conn) throws HBqlException {
106 
107         if (this.isValidated())
108             return;
109         else
110             this.validated = true;
111 
112         if (this.invalidInsertColumn != null)
113             throw new InvalidTypeException(this.invalidInsertColumn + " is not a column reference in " + this.asString());
114 
115         this.connection = conn;
116         this.getMappingContext().validateMappingName(this.getConnection());
117         this.record = this.getConnection().getMapping(this.getMappingContext().getMappingName()).newHRecord();
118 
119         for (final SelectExpressionContext element : this.getInsertColumnList()) {
120 
121             element.validate(this.getMappingContext(), this.getConnection());
122 
123             if (!element.isADelegateColumnReference())
124                 throw new InvalidTypeException(element.asString() + " is not a column reference in " + this.asString());
125         }
126 
127         if (!this.hasAKeyValue())
128             throw new InvalidTypeException("Missing a key value in attribute list in " + this.asString());
129 
130         this.getInsertValuesSource().validate();
131 
132         this.collectParameters();
133     }
134 
135     public void validateTypes() throws HBqlException {
136 
137         final List<Class<? extends GenericValue>> columnsTypeList = this.getColumnsTypeList();
138         final List<Class<? extends GenericValue>> valuesTypeList = this.getInsertValuesSource().getValuesTypeList();
139 
140         if (columnsTypeList.size() != valuesTypeList.size())
141             throw new HBqlException("Number of columns not equal to number of values in " + this.asString());
142 
143         for (int i = 0; i < columnsTypeList.size(); i++) {
144 
145             final Class<? extends GenericValue> type1 = columnsTypeList.get(i);
146             final Class<? extends GenericValue> type2 = valuesTypeList.get(i);
147 
148             // Skip Default values
149             if (type2 == DefaultKeyword.class) {
150                 final String name = this.getInsertColumnList().get(i).asString();
151                 final ColumnAttrib attrib = this.getMappingContext().getMapping().getAttribByVariableName(name);
152                 if (!attrib.hasDefaultArg())
153                     throw new HBqlException("No DEFAULT value specified for " + attrib.getNameToUseInExceptions()
154                                             + " in " + this.asString());
155                 continue;
156             }
157 
158             if (type2.equals(NullLiteral.class)) {
159                 if (!TypeSupport.allowsNullValues(type1))
160                     throw new InvalidTypeException("Argument " + i
161                                                    + " type " + type1.getSimpleName()
162                                                    + " cannot be assigned a NULL value"
163                                                    + " in " + this.asString());
164                 continue;
165             }
166 
167             if (!TypeSupport.isParentClass(type1, type2))
168                 throw new InvalidTypeException("Type mismatch with argument " + i
169                                                + " expecting " + type1.getSimpleName()
170                                                + " but found " + type2.getSimpleName()
171                                                + " in " + this.asString());
172         }
173     }
174 
175     private List<Class<? extends GenericValue>> getColumnsTypeList() throws HBqlException {
176         final List<Class<? extends GenericValue>> typeList = Lists.newArrayList();
177         for (final SelectExpressionContext element : this.getInsertColumnList()) {
178             final Class<? extends GenericValue> type = element.getExpressionType();
179             typeList.add(type);
180         }
181         return typeList;
182     }
183 
184     private boolean hasAKeyValue() {
185         for (final SelectExpressionContext element : this.getInsertColumnList()) {
186             if (element.isAKeyValue())
187                 return true;
188         }
189         return false;
190     }
191 
192     private void collectParameters() {
193         this.getNamedParameters().addParameters(this.getInsertValuesSource().getParameterList());
194     }
195 
196     public void resetParameters() {
197         this.getInsertValuesSource().reset();
198         this.getHRecord().reset();
199     }
200 
201     public int setStatementParameter(final String name, final Object val) throws HBqlException {
202         final int cnt = this.getInsertValuesSource().setInsertSourceParameter(name, val);
203         if (cnt == 0)
204             throw new HBqlException("Parameter name " + name + " does not exist in " + this.asString());
205         return cnt;
206     }
207 
208     protected ExecutionResults execute(final HConnectionImpl conn) throws HBqlException {
209 
210         this.validate(conn);
211 
212         this.validateTypes();
213 
214         int cnt = 0;
215 
216         this.getInsertValuesSource().execute();
217 
218         while (this.getInsertValuesSource().hasValues()) {
219 
220             final HBatch<HRecord> batch = conn.newHBatch();
221 
222             for (int i = 0; i < this.getInsertColumnList().size(); i++) {
223                 final String name = this.getInsertColumnList().get(i).asString();
224                 final Object val;
225                 if (this.getInsertValuesSource().isDefaultValue(i)) {
226                     final ColumnAttrib attrib = this.getMappingContext().getMapping().getAttribByVariableName(name);
227                     val = attrib.getDefaultValue();
228                 }
229                 else {
230                     val = this.getInsertValuesSource().getValue(conn, i);
231                 }
232                 this.getHRecord().setCurrentValue(name, val);
233             }
234 
235             batch.insert(this.getHRecord());
236 
237             batch.apply();
238             cnt++;
239         }
240 
241         final ExecutionResults results = new ExecutionResults(cnt + " record" + ((cnt > 1) ? "s" : "") + " inserted");
242         results.setCount(cnt);
243         return results;
244     }
245 
246     public String asString() {
247 
248         final StringBuilder sbuf = new StringBuilder();
249 
250         sbuf.append("INSERT INTO ");
251         sbuf.append(this.getMappingContext().getMappingName());
252         sbuf.append(" (");
253 
254         boolean firstTime = true;
255         for (final SelectExpressionContext val : this.getInsertColumnList()) {
256             if (!firstTime)
257                 sbuf.append(", ");
258             firstTime = false;
259 
260             sbuf.append(val.asString());
261         }
262 
263         sbuf.append(") ");
264         sbuf.append(this.getInsertValuesSource().asString());
265         return sbuf.toString();
266     }
267 
268     public static String usage() {
269         return "INSERT INTO [MAPPING] mapping_name (column_name_list) insert_values [IF bool_expr]";
270     }
271 }