You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by ja...@apache.org on 2014/01/27 23:15:58 UTC
[42/51] [partial] Initial commit of master branch from github
http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java
new file mode 100644
index 0000000..cd2b0e2
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java
@@ -0,0 +1,1391 @@
+/*
+ * Copyright 2010 The Apache Software Foundation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.phoenix.compile;
+
+import java.math.BigDecimal;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+
+import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
+import org.apache.phoenix.exception.SQLExceptionCode;
+import org.apache.phoenix.exception.SQLExceptionInfo;
+import org.apache.phoenix.expression.AndExpression;
+import org.apache.phoenix.expression.ArrayConstructorExpression;
+import org.apache.phoenix.expression.CaseExpression;
+import org.apache.phoenix.expression.CoerceExpression;
+import org.apache.phoenix.expression.ComparisonExpression;
+import org.apache.phoenix.expression.DateAddExpression;
+import org.apache.phoenix.expression.DateSubtractExpression;
+import org.apache.phoenix.expression.DecimalAddExpression;
+import org.apache.phoenix.expression.DecimalDivideExpression;
+import org.apache.phoenix.expression.DecimalMultiplyExpression;
+import org.apache.phoenix.expression.DecimalSubtractExpression;
+import org.apache.phoenix.expression.DoubleAddExpression;
+import org.apache.phoenix.expression.DoubleDivideExpression;
+import org.apache.phoenix.expression.DoubleMultiplyExpression;
+import org.apache.phoenix.expression.DoubleSubtractExpression;
+import org.apache.phoenix.expression.Expression;
+import org.apache.phoenix.expression.InListExpression;
+import org.apache.phoenix.expression.IsNullExpression;
+import org.apache.phoenix.expression.LikeExpression;
+import org.apache.phoenix.expression.LiteralExpression;
+import org.apache.phoenix.expression.LongAddExpression;
+import org.apache.phoenix.expression.LongDivideExpression;
+import org.apache.phoenix.expression.LongMultiplyExpression;
+import org.apache.phoenix.expression.LongSubtractExpression;
+import org.apache.phoenix.expression.NotExpression;
+import org.apache.phoenix.expression.OrExpression;
+import org.apache.phoenix.expression.RowKeyColumnExpression;
+import org.apache.phoenix.expression.RowValueConstructorExpression;
+import org.apache.phoenix.expression.StringConcatExpression;
+import org.apache.phoenix.expression.TimestampAddExpression;
+import org.apache.phoenix.expression.TimestampSubtractExpression;
+import org.apache.phoenix.parse.AddParseNode;
+import org.apache.phoenix.parse.AndParseNode;
+import org.apache.phoenix.parse.ArithmeticParseNode;
+import org.apache.phoenix.parse.ArrayConstructorNode;
+import org.apache.phoenix.parse.BindParseNode;
+import org.apache.phoenix.parse.CaseParseNode;
+import org.apache.phoenix.parse.CastParseNode;
+import org.apache.phoenix.parse.ColumnParseNode;
+import org.apache.phoenix.parse.ComparisonParseNode;
+import org.apache.phoenix.parse.DivideParseNode;
+import org.apache.phoenix.parse.FunctionParseNode;
+import org.apache.phoenix.parse.FunctionParseNode.BuiltInFunctionInfo;
+import org.apache.phoenix.parse.InListParseNode;
+import org.apache.phoenix.parse.IsNullParseNode;
+import org.apache.phoenix.parse.LikeParseNode;
+import org.apache.phoenix.parse.LiteralParseNode;
+import org.apache.phoenix.parse.MultiplyParseNode;
+import org.apache.phoenix.parse.NotParseNode;
+import org.apache.phoenix.parse.OrParseNode;
+import org.apache.phoenix.parse.ParseNode;
+import org.apache.phoenix.parse.RowValueConstructorParseNode;
+import org.apache.phoenix.parse.SequenceValueParseNode;
+import org.apache.phoenix.parse.StringConcatParseNode;
+import org.apache.phoenix.parse.SubtractParseNode;
+import org.apache.phoenix.parse.UnsupportedAllParseNodeVisitor;
+import org.apache.phoenix.schema.ColumnModifier;
+import org.apache.phoenix.schema.ColumnNotFoundException;
+import org.apache.phoenix.schema.ColumnRef;
+import org.apache.phoenix.schema.DelegateDatum;
+import org.apache.phoenix.schema.PArrayDataType;
+import org.apache.phoenix.schema.PDataType;
+import org.apache.phoenix.schema.PDatum;
+import org.apache.phoenix.schema.PTable;
+import org.apache.phoenix.schema.PTableType;
+import org.apache.phoenix.schema.RowKeyValueAccessor;
+import org.apache.phoenix.schema.TableRef;
+import org.apache.phoenix.schema.TypeMismatchException;
+import org.apache.phoenix.util.ByteUtil;
+import org.apache.phoenix.util.SchemaUtil;
+
+
+public class ExpressionCompiler extends UnsupportedAllParseNodeVisitor<Expression> {
+ private boolean isAggregate;
+ protected ParseNode aggregateFunction;
+ protected final StatementContext context;
+ protected final GroupBy groupBy;
+ private int nodeCount;
+
+ ExpressionCompiler(StatementContext context) {
+ this(context,GroupBy.EMPTY_GROUP_BY);
+ }
+
+ ExpressionCompiler(StatementContext context, GroupBy groupBy) {
+ this.context = context;
+ this.groupBy = groupBy;
+ }
+
+ public boolean isAggregate() {
+ return isAggregate;
+ }
+
+ public boolean isTopLevel() {
+ return nodeCount == 0;
+ }
+
+ public void reset() {
+ this.isAggregate = false;
+ this.nodeCount = 0;
+ }
+
+ @Override
+ public boolean visitEnter(ComparisonParseNode node) {
+ return true;
+ }
+
+ private void addBindParamMetaData(ParseNode parentNode, ParseNode lhsNode, ParseNode rhsNode, Expression lhsExpr, Expression rhsExpr) throws SQLException {
+ if (lhsNode instanceof BindParseNode) {
+ context.getBindManager().addParamMetaData((BindParseNode)lhsNode, rhsExpr);
+ }
+ if (rhsNode instanceof BindParseNode) {
+ context.getBindManager().addParamMetaData((BindParseNode)rhsNode, lhsExpr);
+ }
+ }
+
+ private static void checkComparability(ParseNode node, PDataType lhsDataType, PDataType rhsDataType) throws TypeMismatchException {
+ if(lhsDataType != null && rhsDataType != null && !lhsDataType.isComparableTo(rhsDataType)) {
+ throw TypeMismatchException.newException(lhsDataType, rhsDataType, node.toString());
+ }
+ }
+
+ // TODO: this no longer needs to be recursive, as we flatten out rvc when we normalize the statement
+ private void checkComparability(ParseNode parentNode, ParseNode lhsNode, ParseNode rhsNode, Expression lhsExpr, Expression rhsExpr) throws SQLException {
+ if (lhsNode instanceof RowValueConstructorParseNode && rhsNode instanceof RowValueConstructorParseNode) {
+ int i = 0;
+ for (; i < Math.min(lhsExpr.getChildren().size(),rhsExpr.getChildren().size()); i++) {
+ checkComparability(parentNode, lhsNode.getChildren().get(i), rhsNode.getChildren().get(i), lhsExpr.getChildren().get(i), rhsExpr.getChildren().get(i));
+ }
+ for (; i < lhsExpr.getChildren().size(); i++) {
+ checkComparability(parentNode, lhsNode.getChildren().get(i), null, lhsExpr.getChildren().get(i), null);
+ }
+ for (; i < rhsExpr.getChildren().size(); i++) {
+ checkComparability(parentNode, null, rhsNode.getChildren().get(i), null, rhsExpr.getChildren().get(i));
+ }
+ } else if (lhsExpr instanceof RowValueConstructorExpression) {
+ checkComparability(parentNode, lhsNode.getChildren().get(0), rhsNode, lhsExpr.getChildren().get(0), rhsExpr);
+ for (int i = 1; i < lhsExpr.getChildren().size(); i++) {
+ checkComparability(parentNode, lhsNode.getChildren().get(i), null, lhsExpr.getChildren().get(i), null);
+ }
+ } else if (rhsExpr instanceof RowValueConstructorExpression) {
+ checkComparability(parentNode, lhsNode, rhsNode.getChildren().get(0), lhsExpr, rhsExpr.getChildren().get(0));
+ for (int i = 1; i < rhsExpr.getChildren().size(); i++) {
+ checkComparability(parentNode, null, rhsNode.getChildren().get(i), null, rhsExpr.getChildren().get(i));
+ }
+ } else if (lhsNode == null && rhsNode == null) { // null == null will end up making the query degenerate
+
+ } else if (lhsNode == null) { // AND rhs IS NULL
+ addBindParamMetaData(parentNode, lhsNode, rhsNode, lhsExpr, rhsExpr);
+ } else if (rhsNode == null) { // AND lhs IS NULL
+ addBindParamMetaData(parentNode, lhsNode, rhsNode, lhsExpr, rhsExpr);
+ } else { // AND lhs = rhs
+ checkComparability(parentNode, lhsExpr.getDataType(), rhsExpr.getDataType());
+ addBindParamMetaData(parentNode, lhsNode, rhsNode, lhsExpr, rhsExpr);
+ }
+ }
+
+ @Override
+ public Expression visitLeave(ComparisonParseNode node, List<Expression> children) throws SQLException {
+ ParseNode lhsNode = node.getChildren().get(0);
+ ParseNode rhsNode = node.getChildren().get(1);
+ Expression lhsExpr = children.get(0);
+ Expression rhsExpr = children.get(1);
+ boolean isDeterministic = lhsExpr.isDeterministic() || rhsExpr.isDeterministic();
+
+ PDataType lhsExprDataType = lhsExpr.getDataType();
+ PDataType rhsExprDataType = rhsExpr.getDataType();
+ checkComparability(node, lhsNode, rhsNode, lhsExpr, rhsExpr);
+ // We don't yet support comparison between entire arrays
+ if ( ( (lhsExprDataType != null && lhsExprDataType.isArrayType()) ||
+ (rhsExprDataType != null && rhsExprDataType.isArrayType()) ) &&
+ ( node.getFilterOp() != CompareOp.EQUAL && node.getFilterOp() != CompareOp.NOT_EQUAL ) ) {
+ throw new SQLExceptionInfo.Builder(SQLExceptionCode.NON_EQUALITY_ARRAY_COMPARISON)
+ .setMessage(ComparisonExpression.toString(node.getFilterOp(), children)).build().buildException();
+ }
+
+ if (lhsExpr instanceof RowValueConstructorExpression || rhsExpr instanceof RowValueConstructorExpression) {
+ rhsExpr = RowValueConstructorExpression.coerce(lhsExpr, rhsExpr, node.getFilterOp());
+ // Always wrap both sides in row value constructor, so we don't have to consider comparing
+ // a non rvc with a rvc.
+ if ( ! ( lhsExpr instanceof RowValueConstructorExpression ) ) {
+ lhsExpr = new RowValueConstructorExpression(Collections.singletonList(lhsExpr), lhsExpr.isStateless());
+ }
+ children = Arrays.asList(lhsExpr, rhsExpr);
+ }
+
+ Object lhsValue = null;
+ // Can't use lhsNode.isConstant(), because we have cases in which we don't know
+ // in advance if a function evaluates to null (namely when bind variables are used)
+ // TODO: use lhsExpr.isStateless instead
+ if (lhsExpr instanceof LiteralExpression) {
+ lhsValue = ((LiteralExpression)lhsExpr).getValue();
+ if (lhsValue == null) {
+ return LiteralExpression.newConstant(false, PDataType.BOOLEAN, lhsExpr.isDeterministic());
+ }
+ }
+ Object rhsValue = null;
+ // TODO: use lhsExpr.isStateless instead
+ if (rhsExpr instanceof LiteralExpression) {
+ rhsValue = ((LiteralExpression)rhsExpr).getValue();
+ if (rhsValue == null) {
+ return LiteralExpression.newConstant(false, PDataType.BOOLEAN, rhsExpr.isDeterministic());
+ }
+ }
+ if (lhsValue != null && rhsValue != null) {
+ return LiteralExpression.newConstant(ByteUtil.compare(node.getFilterOp(),lhsExprDataType.compareTo(lhsValue, rhsValue, rhsExprDataType)), isDeterministic);
+ }
+ // Coerce constant to match type of lhs so that we don't need to
+ // convert at filter time. Since we normalize the select statement
+ // to put constants on the LHS, we don't need to check the RHS.
+ if (rhsValue != null) {
+ // Comparing an unsigned int/long against a negative int/long would be an example. We just need to take
+ // into account the comparison operator.
+ if (rhsExprDataType != lhsExprDataType
+ || rhsExpr.getColumnModifier() != lhsExpr.getColumnModifier()
+ || (rhsExpr.getMaxLength() != null && lhsExpr.getMaxLength() != null && rhsExpr.getMaxLength() < lhsExpr.getMaxLength())) {
+ // TODO: if lengths are unequal and fixed width?
+ if (rhsExprDataType.isCoercibleTo(lhsExprDataType, rhsValue)) { // will convert 2.0 -> 2
+ children = Arrays.asList(children.get(0), LiteralExpression.newConstant(rhsValue, lhsExprDataType,
+ lhsExpr.getMaxLength(), null, lhsExpr.getColumnModifier(), isDeterministic));
+ } else if (node.getFilterOp() == CompareOp.EQUAL) {
+ return LiteralExpression.newConstant(false, PDataType.BOOLEAN, true);
+ } else if (node.getFilterOp() == CompareOp.NOT_EQUAL) {
+ return LiteralExpression.newConstant(true, PDataType.BOOLEAN, true);
+ } else { // TODO: generalize this with PDataType.getMinValue(), PDataTypeType.getMaxValue() methods
+ switch(rhsExprDataType) {
+ case DECIMAL:
+ /*
+ * We're comparing an int/long to a constant decimal with a fraction part.
+ * We need the types to match in case this is used to form a key. To form the start/stop key,
+ * we need to adjust the decimal by truncating it or taking its ceiling, depending on the comparison
+ * operator, to get a whole number.
+ */
+ int increment = 0;
+ switch (node.getFilterOp()) {
+ case GREATER_OR_EQUAL:
+ case LESS: // get next whole number
+ increment = 1;
+ default: // Else, we truncate the value
+ BigDecimal bd = (BigDecimal)rhsValue;
+ rhsValue = bd.longValue() + increment;
+ children = Arrays.asList(children.get(0), LiteralExpression.newConstant(rhsValue, lhsExprDataType, lhsExpr.getColumnModifier(), rhsExpr.isDeterministic()));
+ break;
+ }
+ break;
+ case LONG:
+ /*
+ * We are comparing an int, unsigned_int to a long, or an unsigned_long to a negative long.
+ * int has range of -2147483648 to 2147483647, and unsigned_int has a value range of 0 to 4294967295.
+ *
+ * If lhs is int or unsigned_int, since we already determined that we cannot coerce the rhs
+ * to become the lhs, we know the value on the rhs is greater than lhs if it's positive, or smaller than
+ * lhs if it's negative.
+ *
+ * If lhs is an unsigned_long, then we know the rhs is definitely a negative long. rhs in this case
+ * will always be bigger than rhs.
+ */
+ if (lhsExprDataType == PDataType.INTEGER ||
+ lhsExprDataType == PDataType.UNSIGNED_INT) {
+ switch (node.getFilterOp()) {
+ case LESS:
+ case LESS_OR_EQUAL:
+ if ((Long)rhsValue > 0) {
+ return LiteralExpression.newConstant(true, PDataType.BOOLEAN, isDeterministic);
+ } else {
+ return LiteralExpression.newConstant(false, PDataType.BOOLEAN, isDeterministic);
+ }
+ case GREATER:
+ case GREATER_OR_EQUAL:
+ if ((Long)rhsValue > 0) {
+ return LiteralExpression.newConstant(false, PDataType.BOOLEAN, isDeterministic);
+ } else {
+ return LiteralExpression.newConstant(true, PDataType.BOOLEAN, isDeterministic);
+ }
+ default:
+ break;
+ }
+ } else if (lhsExprDataType == PDataType.UNSIGNED_LONG) {
+ switch (node.getFilterOp()) {
+ case LESS:
+ case LESS_OR_EQUAL:
+ return LiteralExpression.newConstant(false, PDataType.BOOLEAN, isDeterministic);
+ case GREATER:
+ case GREATER_OR_EQUAL:
+ return LiteralExpression.newConstant(true, PDataType.BOOLEAN, isDeterministic);
+ default:
+ break;
+ }
+ }
+ children = Arrays.asList(children.get(0), LiteralExpression.newConstant(rhsValue, rhsExprDataType, lhsExpr.getColumnModifier(), isDeterministic));
+ break;
+ }
+ }
+ }
+
+ // Determine if we know the expression must be TRUE or FALSE based on the max size of
+ // a fixed length expression.
+ if (children.get(1).getMaxLength() != null && lhsExpr.getMaxLength() != null && lhsExpr.getMaxLength() < children.get(1).getMaxLength()) {
+ switch (node.getFilterOp()) {
+ case EQUAL:
+ return LiteralExpression.newConstant(false, PDataType.BOOLEAN, isDeterministic);
+ case NOT_EQUAL:
+ return LiteralExpression.newConstant(true, PDataType.BOOLEAN, isDeterministic);
+ default:
+ break;
+ }
+ }
+ }
+ return new ComparisonExpression(node.getFilterOp(), children);
+ }
+
+ @Override
+ public boolean visitEnter(AndParseNode node) throws SQLException {
+ return true;
+ }
+
+ @Override
+ public Expression visitLeave(AndParseNode node, List<Expression> children) throws SQLException {
+ boolean isDeterministic = true;
+ Iterator<Expression> iterator = children.iterator();
+ while (iterator.hasNext()) {
+ Expression child = iterator.next();
+ if (child.getDataType() != PDataType.BOOLEAN) {
+ throw TypeMismatchException.newException(PDataType.BOOLEAN, child.getDataType(), child.toString());
+ }
+ if (LiteralExpression.isFalse(child)) {
+ return child;
+ }
+ if (LiteralExpression.isTrue(child)) {
+ iterator.remove();
+ }
+ isDeterministic &= child.isDeterministic();
+ }
+ if (children.size() == 0) {
+ return LiteralExpression.newConstant(true, isDeterministic);
+ }
+ if (children.size() == 1) {
+ return children.get(0);
+ }
+ return new AndExpression(children);
+ }
+
+ @Override
+ public boolean visitEnter(OrParseNode node) throws SQLException {
+ return true;
+ }
+
+ private Expression orExpression(List<Expression> children) throws SQLException {
+ Iterator<Expression> iterator = children.iterator();
+ boolean isDeterministic = true;
+ while (iterator.hasNext()) {
+ Expression child = iterator.next();
+ if (child.getDataType() != PDataType.BOOLEAN) {
+ throw TypeMismatchException.newException(PDataType.BOOLEAN, child.getDataType(), child.toString());
+ }
+ if (LiteralExpression.isFalse(child)) {
+ iterator.remove();
+ }
+ if (LiteralExpression.isTrue(child)) {
+ return child;
+ }
+ isDeterministic &= child.isDeterministic();
+ }
+ if (children.size() == 0) {
+ return LiteralExpression.newConstant(true, isDeterministic);
+ }
+ if (children.size() == 1) {
+ return children.get(0);
+ }
+ return new OrExpression(children);
+ }
+
+ @Override
+ public Expression visitLeave(OrParseNode node, List<Expression> children) throws SQLException {
+ return orExpression(children);
+ }
+
+ @Override
+ public boolean visitEnter(FunctionParseNode node) throws SQLException {
+ // TODO: Oracle supports nested aggregate function while other DBs don't. Should we?
+ if (node.isAggregate()) {
+ if (aggregateFunction != null) {
+ throw new SQLFeatureNotSupportedException("Nested aggregate functions are not supported");
+ }
+ this.aggregateFunction = node;
+ this.isAggregate = true;
+
+ }
+ return true;
+ }
+
+ private Expression wrapGroupByExpression(Expression expression) {
+ // If we're in an aggregate function, don't wrap a group by expression,
+ // since in that case we're aggregating over the regular/ungrouped
+ // column.
+ if (aggregateFunction == null) {
+ int index = groupBy.getExpressions().indexOf(expression);
+ if (index >= 0) {
+ isAggregate = true;
+ RowKeyValueAccessor accessor = new RowKeyValueAccessor(groupBy.getKeyExpressions(), index);
+ expression = new RowKeyColumnExpression(expression, accessor, groupBy.getKeyExpressions().get(index).getDataType());
+ }
+ }
+ return expression;
+ }
+
+ /**
+ * Add expression to the expression manager, returning the same one if
+ * already used.
+ */
+ protected Expression addExpression(Expression expression) {
+ return context.getExpressionManager().addIfAbsent(expression);
+ }
+
+ @Override
+ /**
+ * @param node a function expression node
+ * @param children the child expression arguments to the function expression node.
+ */
+ public Expression visitLeave(FunctionParseNode node, List<Expression> children) throws SQLException {
+ children = node.validate(children, context);
+ Expression expression = node.create(children, context);
+ ImmutableBytesWritable ptr = context.getTempPtr();
+ if (node.isStateless()) {
+ Object value = null;
+ PDataType type = expression.getDataType();
+ if (expression.evaluate(null, ptr)) {
+ value = type.toObject(ptr);
+ }
+ return LiteralExpression.newConstant(value, type, expression.isDeterministic());
+ }
+ boolean isDeterministic = true;
+ BuiltInFunctionInfo info = node.getInfo();
+ for (int i = 0; i < info.getRequiredArgCount(); i++) {
+ // Optimization to catch cases where a required argument is null resulting in the function
+ // returning null. We have to wait until after we create the function expression so that
+ // we can get the proper type to use.
+ if (node.evalToNullIfParamIsNull(context, i)) {
+ Expression child = children.get(i);
+ isDeterministic &= child.isDeterministic();
+ if (child.isStateless() && (!child.evaluate(null, ptr) || ptr.getLength() == 0)) {
+ return LiteralExpression.newConstant(null, expression.getDataType(), isDeterministic);
+ }
+ }
+ }
+ expression = addExpression(expression);
+ expression = wrapGroupByExpression(expression);
+ if (aggregateFunction == node) {
+ aggregateFunction = null; // Turn back off on the way out
+ }
+ return expression;
+ }
+
+ /**
+ * Called by visitor to resolve a column expression node into a column reference.
+ * Derived classes may use this as a hook to trap all column resolves.
+ * @param node a column expression node
+ * @return a resolved ColumnRef
+ * @throws SQLException if the column expression node does not refer to a known/unambiguous column
+ */
+ protected ColumnRef resolveColumn(ColumnParseNode node) throws SQLException {
+ ColumnRef ref = context.getResolver().resolveColumn(node.getSchemaName(), node.getTableName(), node.getName());
+ PTable table = ref.getTable();
+ int pkPosition = ref.getPKSlotPosition();
+ // Disallow explicit reference to SALT or TENANT_ID columns
+ if (pkPosition >= 0) {
+ boolean isSalted = table.getBucketNum() != null;
+ boolean isMultiTenant = context.getConnection().getTenantId() != null && table.isMultiTenant();
+ int minPosition = (isSalted ? 1 : 0) + (isMultiTenant ? 1 : 0);
+ if (pkPosition < minPosition) {
+ throw new ColumnNotFoundException(table.getSchemaName().getString(), table.getTableName().getString(), null, ref.getColumn().getName().getString());
+ }
+ }
+ return ref;
+ }
+
+ @Override
+ public Expression visit(ColumnParseNode node) throws SQLException {
+ ColumnRef ref = resolveColumn(node);
+ TableRef tableRef = ref.getTableRef();
+ if (tableRef.equals(context.getCurrentTable())
+ && !SchemaUtil.isPKColumn(ref.getColumn())) { // project only kv columns
+ context.getScan().addColumn(ref.getColumn().getFamilyName().getBytes(), ref.getColumn().getName().getBytes());
+ }
+ Expression expression = ref.newColumnExpression();
+ Expression wrappedExpression = wrapGroupByExpression(expression);
+ // If we're in an aggregate expression
+ // and we're not in the context of an aggregate function
+ // and we didn't just wrap our column reference
+ // then we're mixing aggregate and non aggregate expressions in the same expression.
+ // This catches cases like this: SELECT sum(a_integer) + a_integer FROM atable GROUP BY a_string
+ if (isAggregate && aggregateFunction == null && wrappedExpression == expression) {
+ throwNonAggExpressionInAggException(expression.toString());
+ }
+ return wrappedExpression;
+ }
+
+ @Override
+ public Expression visit(BindParseNode node) throws SQLException {
+ Object value = context.getBindManager().getBindValue(node);
+ return LiteralExpression.newConstant(value, true);
+ }
+
+ @Override
+ public Expression visit(LiteralParseNode node) throws SQLException {
+ return LiteralExpression.newConstant(node.getValue(), node.getType(), true);
+ }
+
+ @Override
+ public List<Expression> newElementList(int size) {
+ nodeCount += size;
+ return new ArrayList<Expression>(size);
+ }
+
+ @Override
+ public void addElement(List<Expression> l, Expression element) {
+ nodeCount--;
+ l.add(element);
+ }
+
+ @Override
+ public boolean visitEnter(CaseParseNode node) throws SQLException {
+ return true;
+ }
+
+ private static boolean isDeterministic(List<Expression> l) {
+ for (Expression e : l) {
+ if (!e.isDeterministic()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public Expression visitLeave(CaseParseNode node, List<Expression> l) throws SQLException {
+ final CaseExpression caseExpression = new CaseExpression(l);
+ for (int i = 0; i < node.getChildren().size(); i+=2) {
+ ParseNode childNode = node.getChildren().get(i);
+ if (childNode instanceof BindParseNode) {
+ context.getBindManager().addParamMetaData((BindParseNode)childNode, new DelegateDatum(caseExpression));
+ }
+ }
+ if (node.isStateless()) {
+ ImmutableBytesWritable ptr = context.getTempPtr();
+ int index = caseExpression.evaluateIndexOf(null, ptr);
+ if (index < 0) {
+ return LiteralExpression.newConstant(null, isDeterministic(l));
+ }
+ return caseExpression.getChildren().get(index);
+ }
+ return wrapGroupByExpression(caseExpression);
+ }
+
+ @Override
+ public boolean visitEnter(LikeParseNode node) throws SQLException {
+ return true;
+ }
+
+ @Override
+ public Expression visitLeave(LikeParseNode node, List<Expression> children) throws SQLException {
+ ParseNode lhsNode = node.getChildren().get(0);
+ ParseNode rhsNode = node.getChildren().get(1);
+ Expression lhs = children.get(0);
+ Expression rhs = children.get(1);
+ if ( rhs.getDataType() != null && lhs.getDataType() != null &&
+ !lhs.getDataType().isCoercibleTo(rhs.getDataType()) &&
+ !rhs.getDataType().isCoercibleTo(lhs.getDataType())) {
+ throw TypeMismatchException.newException(lhs.getDataType(), rhs.getDataType(), node.toString());
+ }
+ if (lhsNode instanceof BindParseNode) {
+ context.getBindManager().addParamMetaData((BindParseNode)lhsNode, rhs);
+ }
+ if (rhsNode instanceof BindParseNode) {
+ context.getBindManager().addParamMetaData((BindParseNode)rhsNode, lhs);
+ }
+ if (rhs instanceof LiteralExpression) {
+ String pattern = (String)((LiteralExpression)rhs).getValue();
+ if (pattern == null || pattern.length() == 0) {
+ return LiteralExpression.newConstant(null, rhs.isDeterministic());
+ }
+ // TODO: for pattern of '%' optimize to strlength(lhs) > 0
+ // We can't use lhs IS NOT NULL b/c if lhs is NULL we need
+ // to return NULL.
+ int index = LikeExpression.indexOfWildcard(pattern);
+ // Can't possibly be as long as the constant, then FALSE
+ Integer lhsByteSize = lhs.getByteSize();
+ if (lhsByteSize != null && lhsByteSize < index) {
+ return LiteralExpression.newConstant(false, rhs.isDeterministic());
+ }
+ if (index == -1) {
+ String rhsLiteral = LikeExpression.unescapeLike(pattern);
+ if (lhsByteSize != null && lhsByteSize != rhsLiteral.length()) {
+ return LiteralExpression.newConstant(false, rhs.isDeterministic());
+ }
+ CompareOp op = node.isNegate() ? CompareOp.NOT_EQUAL : CompareOp.EQUAL;
+ if (pattern.equals(rhsLiteral)) {
+ return new ComparisonExpression(op, children);
+ } else {
+ rhs = LiteralExpression.newConstant(rhsLiteral, PDataType.CHAR, rhs.isDeterministic());
+ return new ComparisonExpression(op, Arrays.asList(lhs,rhs));
+ }
+ }
+ }
+ Expression expression = new LikeExpression(children);
+ if (node.isStateless()) {
+ ImmutableBytesWritable ptr = context.getTempPtr();
+ if (!expression.evaluate(null, ptr)) {
+ return LiteralExpression.newConstant(null, expression.isDeterministic());
+ } else {
+ return LiteralExpression.newConstant(Boolean.TRUE.equals(PDataType.BOOLEAN.toObject(ptr)) ^ node.isNegate(), expression.isDeterministic());
+ }
+ }
+ if (node.isNegate()) {
+ expression = new NotExpression(expression);
+ }
+ return expression;
+ }
+
+
+ @Override
+ public boolean visitEnter(NotParseNode node) throws SQLException {
+ return true;
+ }
+
+ @Override
+ public Expression visitLeave(NotParseNode node, List<Expression> children) throws SQLException {
+ ParseNode childNode = node.getChildren().get(0);
+ Expression child = children.get(0);
+ if (!PDataType.BOOLEAN.isCoercibleTo(child.getDataType())) {
+ throw TypeMismatchException.newException(PDataType.BOOLEAN, child.getDataType(), node.toString());
+ }
+ if (childNode instanceof BindParseNode) { // TODO: valid/possibe?
+ context.getBindManager().addParamMetaData((BindParseNode)childNode, child);
+ }
+ ImmutableBytesWritable ptr = context.getTempPtr();
+ // Can't use child.isConstant(), because we have cases in which we don't know
+ // in advance if a function evaluates to null (namely when bind variables are used)
+ // TODO: use child.isStateless
+ if (child.isStateless()) {
+ if (!child.evaluate(null, ptr) || ptr.getLength() == 0) {
+ return LiteralExpression.newConstant(null, PDataType.BOOLEAN, child.isDeterministic());
+ }
+ return LiteralExpression.newConstant(!(Boolean)PDataType.BOOLEAN.toObject(ptr), PDataType.BOOLEAN, child.isDeterministic());
+ }
+ return new NotExpression(child);
+ }
+
+ @Override
+ public boolean visitEnter(CastParseNode node) throws SQLException {
+ return true;
+ }
+
+ @Override
+ public Expression visitLeave(CastParseNode node, List<Expression> children) throws SQLException {
+ ParseNode childNode = node.getChildren().get(0);
+ PDataType targetDataType = node.getDataType();
+ Expression childExpr = children.get(0);
+ PDataType fromDataType = childExpr.getDataType();
+
+ if (childNode instanceof BindParseNode) {
+ context.getBindManager().addParamMetaData((BindParseNode)childNode, childExpr);
+ }
+
+ Expression expr = childExpr;
+ if(fromDataType != null) {
+ /*
+ * IndexStatementRewriter creates a CAST parse node when rewriting the query to use
+ * indexed columns. Without this check present we wrongly and unnecessarily
+ * end up creating a RoundExpression.
+ */
+ if (context.getResolver().getTables().get(0).getTable().getType() != PTableType.INDEX) {
+ expr = CastParseNode.convertToRoundExpressionIfNeeded(fromDataType, targetDataType, children);
+ }
+ }
+ return CoerceExpression.create(expr, targetDataType);
+ }
+
+ @Override
+ public boolean visitEnter(InListParseNode node) throws SQLException {
+ return true;
+ }
+
+ @Override
+ public Expression visitLeave(InListParseNode node, List<Expression> l) throws SQLException {
+ List<Expression> inChildren = l;
+ Expression firstChild = inChildren.get(0);
+ ImmutableBytesWritable ptr = context.getTempPtr();
+ PDataType firstChildType = firstChild.getDataType();
+ ParseNode firstChildNode = node.getChildren().get(0);
+
+ if (firstChildNode instanceof BindParseNode) {
+ PDatum datum = firstChild;
+ if (firstChildType == null) {
+ datum = inferBindDatum(inChildren);
+ }
+ context.getBindManager().addParamMetaData((BindParseNode)firstChildNode, datum);
+ }
+ for (int i = 1; i < l.size(); i++) {
+ ParseNode childNode = node.getChildren().get(i);
+ if (childNode instanceof BindParseNode) {
+ context.getBindManager().addParamMetaData((BindParseNode)childNode, firstChild);
+ }
+ }
+ if (firstChildNode.isStateless() && firstChild.evaluate(null, ptr) && ptr.getLength() == 0) {
+ return LiteralExpression.newConstant(null, PDataType.BOOLEAN, firstChild.isDeterministic());
+ }
+
+ Expression e = InListExpression.create(inChildren, ptr);
+ if (node.isNegate()) {
+ e = new NotExpression(e);
+ }
+ if (node.isStateless()) {
+ if (!e.evaluate(null, ptr) || ptr.getLength() == 0) {
+ return LiteralExpression.newConstant(null, e.getDataType(), e.isDeterministic());
+ }
+ Object value = e.getDataType().toObject(ptr);
+ return LiteralExpression.newConstant(value, e.getDataType(), e.isDeterministic());
+ }
+
+ return e;
+ }
+
+ private static final PDatum DECIMAL_DATUM = new PDatum() {
+ @Override
+ public boolean isNullable() {
+ return true;
+ }
+ @Override
+ public PDataType getDataType() {
+ return PDataType.DECIMAL;
+ }
+ @Override
+ public Integer getByteSize() {
+ return null;
+ }
+ @Override
+ public Integer getMaxLength() {
+ return null;
+ }
+ @Override
+ public Integer getScale() {
+ return PDataType.DEFAULT_SCALE;
+ }
+ @Override
+ public ColumnModifier getColumnModifier() {
+ return null;
+ }
+ };
+
+ private static PDatum inferBindDatum(List<Expression> children) {
+ boolean isChildTypeUnknown = false;
+ PDatum datum = children.get(1);
+ for (int i = 2; i < children.size(); i++) {
+ Expression child = children.get(i);
+ PDataType childType = child.getDataType();
+ if (childType == null) {
+ isChildTypeUnknown = true;
+ } else if (datum.getDataType() == null) {
+ datum = child;
+ isChildTypeUnknown = true;
+ } else if (datum.getDataType() == childType || childType.isCoercibleTo(datum.getDataType())) {
+ continue;
+ } else if (datum.getDataType().isCoercibleTo(childType)) {
+ datum = child;
+ }
+ }
+ // If we found an "unknown" child type and the return type is a number
+ // make the return type be the most general number type of DECIMAL.
+ // TODO: same for TIMESTAMP for DATE/TIME?
+ if (isChildTypeUnknown && datum.getDataType() != null && datum.getDataType().isCoercibleTo(PDataType.DECIMAL)) {
+ return DECIMAL_DATUM;
+ }
+ return datum;
+ }
+
+ @Override
+ public boolean visitEnter(IsNullParseNode node) throws SQLException {
+ return true;
+ }
+
+ @Override
+ public Expression visitLeave(IsNullParseNode node, List<Expression> children) throws SQLException {
+ ParseNode childNode = node.getChildren().get(0);
+ Expression child = children.get(0);
+ if (childNode instanceof BindParseNode) { // TODO: valid/possibe?
+ context.getBindManager().addParamMetaData((BindParseNode)childNode, child);
+ }
+ Boolean value = null;
+ // Can't use child.isConstant(), because we have cases in which we don't know
+ // in advance if a function evaluates to null (namely when bind variables are used)
+ // TODO: review: have ParseNode.getValue()?
+ if (child instanceof LiteralExpression) {
+ value = (Boolean)((LiteralExpression)child).getValue();
+ return node.isNegate() ^ value == null ? LiteralExpression.newConstant(false, PDataType.BOOLEAN, child.isDeterministic()) : LiteralExpression.newConstant(true, PDataType.BOOLEAN, child.isDeterministic());
+ }
+ if (!child.isNullable()) { // If child expression is not nullable, we can rewrite this
+ return node.isNegate() ? LiteralExpression.newConstant(true, PDataType.BOOLEAN, child.isDeterministic()) : LiteralExpression.newConstant(false, PDataType.BOOLEAN, child.isDeterministic());
+ }
+ return new IsNullExpression(child, node.isNegate());
+ }
+
+ private static interface ArithmeticExpressionFactory {
+ Expression create(ArithmeticParseNode node, List<Expression> children) throws SQLException;
+ }
+
+ private static interface ArithmeticExpressionBinder {
+ PDatum getBindMetaData(int i, List<Expression> children, Expression expression);
+ }
+
+ private Expression visitLeave(ArithmeticParseNode node, List<Expression> children, ArithmeticExpressionBinder binder, ArithmeticExpressionFactory factory)
+ throws SQLException {
+
+ boolean isNull = false;
+ for (Expression child : children) {
+ boolean isChildLiteral = (child instanceof LiteralExpression);
+ isNull |= isChildLiteral && ((LiteralExpression)child).getValue() == null;
+ }
+
+ Expression expression = factory.create(node, children);
+
+ for (int i = 0; i < node.getChildren().size(); i++) {
+ ParseNode childNode = node.getChildren().get(i);
+ if (childNode instanceof BindParseNode) {
+ context.getBindManager().addParamMetaData((BindParseNode)childNode, binder == null ? expression : binder.getBindMetaData(i, children, expression));
+ }
+ }
+
+ ImmutableBytesWritable ptr = context.getTempPtr();
+
+ // If all children are literals, just evaluate now
+ if (expression.isStateless()) {
+ if (!expression.evaluate(null,ptr) || ptr.getLength() == 0) {
+ return LiteralExpression.newConstant(null, expression.getDataType(), expression.isDeterministic());
+ }
+ return LiteralExpression.newConstant(expression.getDataType().toObject(ptr), expression.getDataType(), expression.isDeterministic());
+ } else if (isNull) {
+ return LiteralExpression.newConstant(null, expression.getDataType(), expression.isDeterministic());
+ }
+ // Otherwise create and return the expression
+ return wrapGroupByExpression(expression);
+ }
+
+ @Override
+ public boolean visitEnter(SubtractParseNode node) throws SQLException {
+ return true;
+ }
+
+ @Override
+ public Expression visitLeave(SubtractParseNode node,
+ List<Expression> children) throws SQLException {
+ return visitLeave(node, children, new ArithmeticExpressionBinder() {
+ @Override
+ public PDatum getBindMetaData(int i, List<Expression> children,
+ final Expression expression) {
+ final PDataType type;
+ // If we're binding the first parameter and the second parameter
+ // is a date
+ // we know that the first parameter must be a date type too.
+ if (i == 0 && (type = children.get(1).getDataType()) != null
+ && type.isCoercibleTo(PDataType.DATE)) {
+ return new PDatum() {
+ @Override
+ public boolean isNullable() {
+ return expression.isNullable();
+ }
+ @Override
+ public PDataType getDataType() {
+ return type;
+ }
+ @Override
+ public Integer getByteSize() {
+ return type.getByteSize();
+ }
+ @Override
+ public Integer getMaxLength() {
+ return expression.getMaxLength();
+ }
+ @Override
+ public Integer getScale() {
+ return expression.getScale();
+ }
+ @Override
+ public ColumnModifier getColumnModifier() {
+ return expression.getColumnModifier();
+ }
+ };
+ } else if (expression.getDataType() != null
+ && expression.getDataType().isCoercibleTo(
+ PDataType.DATE)) {
+ return new PDatum() { // Same as with addition
+ @Override
+ public boolean isNullable() {
+ return expression.isNullable();
+ }
+ @Override
+ public PDataType getDataType() {
+ return PDataType.DECIMAL;
+ }
+ @Override
+ public Integer getByteSize() {
+ return null;
+ }
+ @Override
+ public Integer getMaxLength() {
+ return expression.getMaxLength();
+ }
+ @Override
+ public Integer getScale() {
+ return expression.getScale();
+ }
+ @Override
+ public ColumnModifier getColumnModifier() {
+ return expression.getColumnModifier();
+ }
+ };
+ }
+ // Otherwise just go with what was calculated for the expression
+ return expression;
+ }
+ }, new ArithmeticExpressionFactory() {
+ @Override
+ public Expression create(ArithmeticParseNode node,
+ List<Expression> children) throws SQLException {
+ int i = 0;
+ PDataType theType = null;
+ Expression e1 = children.get(0);
+ Expression e2 = children.get(1);
+ boolean isDeterministic = e1.isDeterministic() && e2.isDeterministic();
+ PDataType type1 = e1.getDataType();
+ PDataType type2 = e2.getDataType();
+ // TODO: simplify this special case for DATE conversion
+ /**
+ * For date1-date2, we want to coerce to a LONG because this
+ * cannot be compared against another date. It has essentially
+ * become a number. For date1-5, we want to preserve the DATE
+ * type because this can still be compared against another date
+ * and cannot be multiplied or divided. Any other time occurs is
+ * an error. For example, 5-date1 is an error. The nulls occur if
+ * we have bind variables.
+ */
+ boolean isType1Date =
+ type1 != null
+ && type1 != PDataType.TIMESTAMP
+ && type1 != PDataType.UNSIGNED_TIMESTAMP
+ && type1.isCoercibleTo(PDataType.DATE);
+ boolean isType2Date =
+ type2 != null
+ && type2 != PDataType.TIMESTAMP
+ && type2 != PDataType.UNSIGNED_TIMESTAMP
+ && type2.isCoercibleTo(PDataType.DATE);
+ if (isType1Date || isType2Date) {
+ if (isType1Date && isType2Date) {
+ i = 2;
+ theType = PDataType.LONG;
+ } else if (isType1Date && type2 != null
+ && type2.isCoercibleTo(PDataType.DECIMAL)) {
+ i = 2;
+ theType = PDataType.DATE;
+ } else if (type1 == null || type2 == null) {
+ /*
+ * FIXME: Could be either a Date or BigDecimal, but we
+ * don't know if we're comparing to a date or a number
+ * which would be disambiguate it.
+ */
+ i = 2;
+ theType = null;
+ }
+ } else if(type1 == PDataType.TIMESTAMP || type2 == PDataType.TIMESTAMP) {
+ i = 2;
+ theType = PDataType.TIMESTAMP;
+ } else if(type1 == PDataType.UNSIGNED_TIMESTAMP || type2 == PDataType.UNSIGNED_TIMESTAMP) {
+ i = 2;
+ theType = PDataType.UNSIGNED_TIMESTAMP;
+ }
+
+ for (; i < children.size(); i++) {
+ // This logic finds the common type to which all child types are coercible
+ // without losing precision.
+ Expression e = children.get(i);
+ isDeterministic &= e.isDeterministic();
+ PDataType type = e.getDataType();
+ if (type == null) {
+ continue;
+ } else if (type.isCoercibleTo(PDataType.LONG)) {
+ if (theType == null) {
+ theType = PDataType.LONG;
+ }
+ } else if (type == PDataType.DECIMAL) {
+ // Coerce return type to DECIMAL from LONG or DOUBLE if DECIMAL child found,
+ // unless we're doing date arithmetic.
+ if (theType == null
+ || !theType.isCoercibleTo(PDataType.DATE)) {
+ theType = PDataType.DECIMAL;
+ }
+ } else if (type.isCoercibleTo(PDataType.DOUBLE)) {
+ // Coerce return type to DOUBLE from LONG if DOUBLE child found,
+ // unless we're doing date arithmetic or we've found another child of type DECIMAL
+ if (theType == null
+ || (theType != PDataType.DECIMAL && !theType.isCoercibleTo(PDataType.DATE) )) {
+ theType = PDataType.DOUBLE;
+ }
+ } else {
+ throw TypeMismatchException.newException(type, node.toString());
+ }
+ }
+ if (theType == PDataType.DECIMAL) {
+ return new DecimalSubtractExpression(children);
+ } else if (theType == PDataType.LONG) {
+ return new LongSubtractExpression(children);
+ } else if (theType == PDataType.DOUBLE) {
+ return new DoubleSubtractExpression(children);
+ } else if (theType == null) {
+ return LiteralExpression.newConstant(null, theType, isDeterministic);
+ } else if (theType == PDataType.TIMESTAMP || theType == PDataType.UNSIGNED_TIMESTAMP) {
+ return new TimestampSubtractExpression(children);
+ } else if (theType.isCoercibleTo(PDataType.DATE)) {
+ return new DateSubtractExpression(children);
+ } else {
+ throw TypeMismatchException.newException(theType, node.toString());
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean visitEnter(AddParseNode node) throws SQLException {
+ return true;
+ }
+
+ @Override
+ public Expression visitLeave(AddParseNode node, List<Expression> children) throws SQLException {
+ return visitLeave(node, children,
+ new ArithmeticExpressionBinder() {
+ @Override
+ public PDatum getBindMetaData(int i, List<Expression> children, final Expression expression) {
+ PDataType type = expression.getDataType();
+ if (type != null && type.isCoercibleTo(PDataType.DATE)) {
+ return new PDatum() {
+ @Override
+ public boolean isNullable() {
+ return expression.isNullable();
+ }
+ @Override
+ public PDataType getDataType() {
+ return PDataType.DECIMAL;
+ }
+ @Override
+ public Integer getByteSize() {
+ return null;
+ }
+ @Override
+ public Integer getMaxLength() {
+ return expression.getMaxLength();
+ }
+ @Override
+ public Integer getScale() {
+ return expression.getScale();
+ }
+ @Override
+ public ColumnModifier getColumnModifier() {
+ return expression.getColumnModifier();
+ }
+ };
+ }
+ return expression;
+ }
+ },
+ new ArithmeticExpressionFactory() {
+ @Override
+ public Expression create(ArithmeticParseNode node, List<Expression> children) throws SQLException {
+ boolean foundDate = false;
+ boolean isDeterministic = true;
+ PDataType theType = null;
+ for(int i = 0; i < children.size(); i++) {
+ Expression e = children.get(i);
+ isDeterministic &= e.isDeterministic();
+ PDataType type = e.getDataType();
+ if (type == null) {
+ continue;
+ } else if (type.isCoercibleTo(PDataType.TIMESTAMP)) {
+ if (foundDate) {
+ throw TypeMismatchException.newException(type, node.toString());
+ }
+ if (theType == null || (theType != PDataType.TIMESTAMP && theType != PDataType.UNSIGNED_TIMESTAMP)) {
+ theType = type;
+ }
+ foundDate = true;
+ }else if (type == PDataType.DECIMAL) {
+ if (theType == null || !theType.isCoercibleTo(PDataType.TIMESTAMP)) {
+ theType = PDataType.DECIMAL;
+ }
+ } else if (type.isCoercibleTo(PDataType.LONG)) {
+ if (theType == null) {
+ theType = PDataType.LONG;
+ }
+ } else if (type.isCoercibleTo(PDataType.DOUBLE)) {
+ if (theType == null) {
+ theType = PDataType.DOUBLE;
+ }
+ } else {
+ throw TypeMismatchException.newException(type, node.toString());
+ }
+ }
+ if (theType == PDataType.DECIMAL) {
+ return new DecimalAddExpression(children);
+ } else if (theType == PDataType.LONG) {
+ return new LongAddExpression(children);
+ } else if (theType == PDataType.DOUBLE) {
+ return new DoubleAddExpression(children);
+ } else if (theType == null) {
+ return LiteralExpression.newConstant(null, theType, isDeterministic);
+ } else if (theType == PDataType.TIMESTAMP || theType == PDataType.UNSIGNED_TIMESTAMP) {
+ return new TimestampAddExpression(children);
+ } else if (theType.isCoercibleTo(PDataType.DATE)) {
+ return new DateAddExpression(children);
+ } else {
+ throw TypeMismatchException.newException(theType, node.toString());
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean visitEnter(MultiplyParseNode node) throws SQLException {
+ return true;
+ }
+
+ @Override
+ public Expression visitLeave(MultiplyParseNode node, List<Expression> children) throws SQLException {
+ return visitLeave(node, children, null, new ArithmeticExpressionFactory() {
+ @Override
+ public Expression create(ArithmeticParseNode node, List<Expression> children) throws SQLException {
+ PDataType theType = null;
+ boolean isDeterministic = true;
+ for(int i = 0; i < children.size(); i++) {
+ Expression e = children.get(i);
+ isDeterministic &= e.isDeterministic();
+ PDataType type = e.getDataType();
+ if (type == null) {
+ continue;
+ } else if (type == PDataType.DECIMAL) {
+ theType = PDataType.DECIMAL;
+ } else if (type.isCoercibleTo(PDataType.LONG)) {
+ if (theType == null) {
+ theType = PDataType.LONG;
+ }
+ } else if (type.isCoercibleTo(PDataType.DOUBLE)) {
+ if (theType == null) {
+ theType = PDataType.DOUBLE;
+ }
+ } else {
+ throw TypeMismatchException.newException(type, node.toString());
+ }
+ }
+ switch (theType) {
+ case DECIMAL:
+ return new DecimalMultiplyExpression( children);
+ case LONG:
+ return new LongMultiplyExpression( children);
+ case DOUBLE:
+ return new DoubleMultiplyExpression( children);
+ default:
+ return LiteralExpression.newConstant(null, theType, isDeterministic);
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean visitEnter(DivideParseNode node) throws SQLException {
+ return true;
+ }
+
+ @Override
+ public Expression visitLeave(DivideParseNode node, List<Expression> children) throws SQLException {
+ for (int i = 1; i < children.size(); i++) { // Compile time check for divide by zero and null
+ Expression child = children.get(i);
+ if (child.getDataType() != null && child instanceof LiteralExpression) {
+ LiteralExpression literal = (LiteralExpression)child;
+ if (literal.getDataType() == PDataType.DECIMAL) {
+ if (PDataType.DECIMAL.compareTo(literal.getValue(), BigDecimal.ZERO) == 0) {
+ throw new SQLExceptionInfo.Builder(SQLExceptionCode.DIVIDE_BY_ZERO).build().buildException();
+ }
+ } else {
+ if (literal.getDataType().compareTo(literal.getValue(), 0L, PDataType.LONG) == 0) {
+ throw new SQLExceptionInfo.Builder(SQLExceptionCode.DIVIDE_BY_ZERO).build().buildException();
+ }
+ }
+ }
+ }
+ return visitLeave(node, children, null, new ArithmeticExpressionFactory() {
+ @Override
+ public Expression create(ArithmeticParseNode node, List<Expression> children) throws SQLException {
+ PDataType theType = null;
+ boolean isDeterministic = true;
+ for(int i = 0; i < children.size(); i++) {
+ Expression e = children.get(i);
+ isDeterministic &= e.isDeterministic();
+ PDataType type = e.getDataType();
+ if (type == null) {
+ continue;
+ } else if (type == PDataType.DECIMAL) {
+ theType = PDataType.DECIMAL;
+ } else if (type.isCoercibleTo(PDataType.LONG)) {
+ if (theType == null) {
+ theType = PDataType.LONG;
+ }
+ } else if (type.isCoercibleTo(PDataType.DOUBLE)) {
+ if (theType == null) {
+ theType = PDataType.DOUBLE;
+ }
+ } else {
+ throw TypeMismatchException.newException(type, node.toString());
+ }
+ }
+ switch (theType) {
+ case DECIMAL:
+ return new DecimalDivideExpression( children);
+ case LONG:
+ return new LongDivideExpression( children);
+ case DOUBLE:
+ return new DoubleDivideExpression(children);
+ default:
+ return LiteralExpression.newConstant(null, theType, isDeterministic);
+ }
+ }
+ });
+ }
+
+ public static void throwNonAggExpressionInAggException(String nonAggregateExpression) throws SQLException {
+ throw new SQLExceptionInfo.Builder(SQLExceptionCode.AGGREGATE_WITH_NOT_GROUP_BY_COLUMN)
+ .setMessage(nonAggregateExpression).build().buildException();
+ }
+
+ @Override
+ public Expression visitLeave(StringConcatParseNode node, List<Expression> children) throws SQLException {
+ final StringConcatExpression expression=new StringConcatExpression(children);
+ for (int i = 0; i < children.size(); i++) {
+ ParseNode childNode=node.getChildren().get(i);
+ if(childNode instanceof BindParseNode) {
+ context.getBindManager().addParamMetaData((BindParseNode)childNode,expression);
+ }
+ PDataType type=children.get(i).getDataType();
+ if(type==PDataType.VARBINARY){
+ throw new SQLExceptionInfo.Builder(SQLExceptionCode.TYPE_NOT_SUPPORTED_FOR_OPERATOR)
+ .setMessage("Concatenation does not support "+ type +" in expression" + node).build().buildException();
+ }
+ }
+ ImmutableBytesWritable ptr = context.getTempPtr();
+ if (expression.isStateless()) {
+ if (!expression.evaluate(null,ptr) || ptr.getLength() == 0) {
+ return LiteralExpression.newConstant(null, expression.getDataType(), expression.isDeterministic());
+ }
+ return LiteralExpression.newConstant(expression.getDataType().toObject(ptr), expression.getDataType(), expression.isDeterministic());
+ }
+ return wrapGroupByExpression(expression);
+ }
+
+ @Override
+ public boolean visitEnter(StringConcatParseNode node) throws SQLException {
+ return true;
+ }
+
+ @Override
+ public boolean visitEnter(RowValueConstructorParseNode node) throws SQLException {
+ return true;
+ }
+
+ @Override
+ public Expression visitLeave(RowValueConstructorParseNode node, List<Expression> l) throws SQLException {
+ // Don't trim trailing nulls here, as we'd potentially be dropping bind
+ // variables that aren't bound yet.
+ return new RowValueConstructorExpression(l, node.isStateless());
+ }
+
+ @Override
+ public Expression visit(SequenceValueParseNode node)
+ throws SQLException {
+ // NEXT VALUE FOR is only supported in SELECT expressions and UPSERT VALUES
+ throw new SQLExceptionInfo.Builder(SQLExceptionCode.INVALID_USE_OF_NEXT_VALUE_FOR)
+ .setSchemaName(node.getTableName().getSchemaName())
+ .setTableName(node.getTableName().getTableName()).build().buildException();
+ }
+
+ @Override
+ public Expression visitLeave(ArrayConstructorNode node, List<Expression> children) throws SQLException {
+ boolean isChildTypeUnknown = false;
+ Expression arrayElemChild = null;
+ PDataType arrayElemDataType = children.get(0).getDataType();
+ for (int i = 0; i < children.size(); i++) {
+ Expression child = children.get(i);
+ PDataType childType = child.getDataType();
+ if (childType == null) {
+ isChildTypeUnknown = true;
+ } else if (arrayElemDataType == null) {
+ arrayElemDataType = childType;
+ isChildTypeUnknown = true;
+ arrayElemChild = child;
+ } else if (arrayElemDataType == childType || childType.isCoercibleTo(arrayElemDataType)) {
+ continue;
+ } else if (arrayElemDataType.isCoercibleTo(childType)) {
+ arrayElemChild = child;
+ arrayElemDataType = childType;
+ } else {
+ throw new SQLExceptionInfo.Builder(SQLExceptionCode.CANNOT_CONVERT_TYPE)
+ .setMessage(
+ "Case expressions must have common type: " + arrayElemDataType
+ + " cannot be coerced to " + childType).build().buildException();
+ }
+ }
+ // If we found an "unknown" child type and the return type is a number
+ // make the return type be the most general number type of DECIMAL.
+ if (isChildTypeUnknown && arrayElemDataType != null && arrayElemDataType.isCoercibleTo(PDataType.DECIMAL)) {
+ arrayElemDataType = PDataType.DECIMAL;
+ }
+ final PDataType theArrayElemDataType = arrayElemDataType;
+ for (int i = 0; i < node.getChildren().size(); i++) {
+ ParseNode childNode = node.getChildren().get(i);
+ if (childNode instanceof BindParseNode) {
+ context.getBindManager().addParamMetaData((BindParseNode)childNode,
+ arrayElemDataType == arrayElemChild.getDataType() ? arrayElemChild :
+ new DelegateDatum(arrayElemChild) {
+ @Override
+ public PDataType getDataType() {
+ return theArrayElemDataType;
+ }
+ });
+ }
+ }
+ ImmutableBytesWritable ptr = context.getTempPtr();
+ Object[] elements = new Object[children.size()];
+ if (node.isStateless()) {
+ boolean isDeterministic = true;
+ for (int i = 0; i < children.size(); i++) {
+ Expression child = children.get(i);
+ isDeterministic &= child.isDeterministic();
+ child.evaluate(null, ptr);
+ Object value = arrayElemDataType.toObject(ptr, child.getDataType(), child.getColumnModifier());
+ elements[i] = LiteralExpression.newConstant(value, child.getDataType(), child.isDeterministic()).getValue();
+ }
+ Object value = PArrayDataType.instantiatePhoenixArray(arrayElemDataType, elements);
+ return LiteralExpression.newConstant(value,
+ PDataType.fromTypeId(arrayElemDataType.getSqlType() + Types.ARRAY), isDeterministic);
+ }
+
+ ArrayConstructorExpression arrayExpression = new ArrayConstructorExpression(children, arrayElemDataType);
+ return wrapGroupByExpression(arrayExpression);
+ }
+
+ @Override
+ public boolean visitEnter(ArrayConstructorNode node) throws SQLException {
+ return true;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionManager.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionManager.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionManager.java
new file mode 100644
index 0000000..f66e61f
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionManager.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2010 The Apache Software Foundation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.phoenix.compile;
+
+import java.util.Iterator;
+import java.util.Map;
+
+
+import com.google.common.collect.Maps;
+import org.apache.phoenix.expression.Expression;
+
+/**
+ *
+ * Class to manage list of expressions inside of a select statement by
+ * deduping them.
+ *
+ * @author jtaylor
+ * @since 0.1
+ */
+public class ExpressionManager {
+ // Use a Map instead of a Set because we need to get and return
+ // the existing Expression
+ private final Map<Expression, Expression> expressionMap;
+
+ public ExpressionManager() {
+ expressionMap = Maps.newHashMap();
+ }
+
+ /**
+ * Add the expression to the set of known expressions for the select
+ * clause. If the expression is already in the set, then the new one
+ * passed in is ignored.
+ * @param expression the new expression to add
+ * @return the new expression if not already present in the set and
+ * the existing one otherwise.
+ */
+ public Expression addIfAbsent(Expression expression) {
+ Expression existingExpression = expressionMap.get(expression);
+ if (existingExpression == null) {
+ expressionMap.put(expression, expression);
+ return expression;
+ }
+ return existingExpression;
+ }
+
+ public int getExpressionCount() {
+ return expressionMap.size();
+ }
+
+ public Iterator<Expression> getExpressions() {
+ return expressionMap.keySet().iterator();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/50d523f6/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionProjector.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionProjector.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionProjector.java
new file mode 100644
index 0000000..8b30f0f
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionProjector.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2010 The Apache Software Foundation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.phoenix.compile;
+
+
+import java.sql.SQLException;
+
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+
+import org.apache.phoenix.expression.Expression;
+import org.apache.phoenix.schema.PDataType;
+import org.apache.phoenix.schema.tuple.Tuple;
+
+
+
+/**
+ *
+ * Projector for getting value from a select statement for an expression
+ *
+ * @author jtaylor
+ * @since 0.1
+ */
+public class ExpressionProjector implements ColumnProjector {
+ private final String name;
+ private final Expression expression;
+ private final String tableName;
+ private final boolean isCaseSensitive;
+
+ public ExpressionProjector(String name, String tableName, Expression expression, boolean isCaseSensitive) {
+ this.name = name;
+ this.expression = expression;
+ this.tableName = tableName;
+ this.isCaseSensitive = isCaseSensitive;
+ }
+
+ @Override
+ public String getTableName() {
+ return tableName;
+ }
+
+ @Override
+ public Expression getExpression() {
+ return expression;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public final Object getValue(Tuple tuple, PDataType type, ImmutableBytesWritable ptr) throws SQLException {
+ try {
+ Expression expression = getExpression();
+ if (!expression.evaluate(tuple, ptr)) {
+ return null;
+ }
+ if (ptr.getLength() == 0) {
+ return null;
+ }
+ return type.toObject(ptr, expression.getDataType(), expression.getColumnModifier());
+ } catch (RuntimeException e) {
+ // FIXME: Expression.evaluate does not throw SQLException
+ // so this will unwrap throws from that.
+ if (e.getCause() instanceof SQLException) {
+ throw (SQLException) e.getCause();
+ }
+ throw e;
+ }
+ }
+
+ @Override
+ public boolean isCaseSensitive() {
+ return isCaseSensitive;
+ }
+}