Repository: phoenix Updated Branches: refs/heads/calcite 171cc9249 -> f9ddb988c
PHOENIX-1829 Extend support for equi-join conditions Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/f9ddb988 Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/f9ddb988 Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/f9ddb988 Branch: refs/heads/calcite Commit: f9ddb988cd7dca05a4d4aaa75264b505770f3061 Parents: 171cc92 Author: maryannxue <wei....@intel.com> Authored: Fri Jun 12 23:29:15 2015 -0400 Committer: maryannxue <wei....@intel.com> Committed: Fri Jun 12 23:29:15 2015 -0400 ---------------------------------------------------------------------- .../org/apache/phoenix/calcite/CalciteTest.java | 15 + .../apache/phoenix/calcite/BuiltInMethod.java | 3 + .../apache/phoenix/calcite/CalciteUtils.java | 353 ++++++++++++++++++- .../apache/phoenix/calcite/PhoenixTable.java | 1 + .../rel/PhoenixToEnumerableConverter.java | 1 + 5 files changed, 369 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/phoenix/blob/f9ddb988/phoenix-core/src/it/java/org/apache/phoenix/calcite/CalciteTest.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/it/java/org/apache/phoenix/calcite/CalciteTest.java b/phoenix-core/src/it/java/org/apache/phoenix/calcite/CalciteTest.java index 7ce4f49..ebd9dfb 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/calcite/CalciteTest.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/calcite/CalciteTest.java @@ -311,6 +311,21 @@ public class CalciteTest extends BaseClientManagedTimeIT { .resultIs(new Object[][] { {"0000000005", "T5", 500, 8, 15, "0000000005", "Item T5", "0000000005", "S5", "888-888-5555", "505 YYY Street", "10005"}}) .close(); + + start().sql("SELECT \"order_id\", i.name, i.price, discount2, quantity FROM " + JOIN_ORDER_TABLE_FULL_NAME + " o INNER JOIN " + + JOIN_ITEM_TABLE_FULL_NAME + " i ON o.\"item_id\" = i.\"item_id\" AND o.price = (i.price * (100 - discount2)) / 100.0 WHERE quantity < 5000") + .explainIs("PhoenixToEnumerableConverter\n" + + " PhoenixToClientConverter\n" + + " PhoenixPostJoinProject(order_id=[$5], NAME=[$1], PRICE=[$2], DISCOUNT2=[$3], QUANTITY=[$7])\n" + + " PhoenixServerJoin(condition=[AND(=($6, $0), =($8, $4))], joinType=[inner])\n" + + " PhoenixServerProject(item_id=[$0], NAME=[$1], PRICE=[$2], DISCOUNT2=[$4], $f7=[/(*($2, -(100, $4)), 100.0)])\n" + + " PhoenixTableScan(table=[[phoenix, Join, ItemTable]])\n" + + " PhoenixToClientConverter\n" + + " PhoenixServerProject(order_id=[$0], item_id=[$2], QUANTITY=[$4], $f7=[CAST($3):DECIMAL(17, 6) NOT NULL])\n" + + " PhoenixTableScan(table=[[phoenix, Join, OrderTable]], filter=[<($4, 5000)])\n") + .resultIs(new Object[][] { + {"000000000000004", "T6", 600, 15, 4000}}) + .close(); } @Test public void testRightOuterJoin() throws Exception { http://git-wip-us.apache.org/repos/asf/phoenix/blob/f9ddb988/phoenix-core/src/main/java/org/apache/phoenix/calcite/BuiltInMethod.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/calcite/BuiltInMethod.java b/phoenix-core/src/main/java/org/apache/phoenix/calcite/BuiltInMethod.java index 192b421..3c0feeb 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/calcite/BuiltInMethod.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/calcite/BuiltInMethod.java @@ -17,6 +17,7 @@ public enum BuiltInMethod { TO_ENUMERABLE(CalciteRuntime.class, "toEnumerable", QueryPlan.class); public final Method method; + @SuppressWarnings("rawtypes") public final Constructor constructor; public static final ImmutableMap<Method, BuiltInMethod> MAP; @@ -32,11 +33,13 @@ public enum BuiltInMethod { MAP = builder.build(); } + @SuppressWarnings("rawtypes") BuiltInMethod(Class clazz, String methodName, Class... argumentTypes) { this.method = Types.lookupMethod(clazz, methodName, argumentTypes); this.constructor = null; } + @SuppressWarnings("rawtypes") BuiltInMethod(Class clazz, Class... argumentTypes) { this.method = null; this.constructor = Types.lookupConstructor(clazz, argumentTypes); http://git-wip-us.apache.org/repos/asf/phoenix/blob/f9ddb988/phoenix-core/src/main/java/org/apache/phoenix/calcite/CalciteUtils.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/calcite/CalciteUtils.java b/phoenix-core/src/main/java/org/apache/phoenix/calcite/CalciteUtils.java index 93be79d..6e1f07d 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/calcite/CalciteUtils.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/calcite/CalciteUtils.java @@ -1,6 +1,7 @@ package org.apache.phoenix.calcite; import java.sql.SQLException; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -12,21 +13,51 @@ import org.apache.calcite.rex.RexNode; import org.apache.calcite.sql.SqlAggFunction; import org.apache.calcite.sql.SqlFunction; import org.apache.calcite.sql.SqlKind; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.calcite.util.NlsString; import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.phoenix.calcite.rel.PhoenixRel.Implementor; import org.apache.phoenix.expression.AndExpression; +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.Determinism; +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.ExpressionType; 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.OrExpression; +import org.apache.phoenix.expression.TimestampAddExpression; +import org.apache.phoenix.expression.TimestampSubtractExpression; import org.apache.phoenix.expression.function.AggregateFunction; import org.apache.phoenix.expression.function.CountAggregateFunction; import org.apache.phoenix.expression.function.FunctionExpression; import org.apache.phoenix.expression.function.MaxAggregateFunction; import org.apache.phoenix.expression.function.MinAggregateFunction; +import org.apache.phoenix.expression.function.RoundDecimalExpression; +import org.apache.phoenix.expression.function.RoundTimestampExpression; +import org.apache.phoenix.schema.SortOrder; +import org.apache.phoenix.schema.TypeMismatchException; +import org.apache.phoenix.schema.types.PDataType; +import org.apache.phoenix.schema.types.PDate; +import org.apache.phoenix.schema.types.PDecimal; +import org.apache.phoenix.schema.types.PDouble; +import org.apache.phoenix.schema.types.PLong; +import org.apache.phoenix.schema.types.PTimestamp; +import org.apache.phoenix.schema.types.PUnsignedTimestamp; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -42,6 +73,11 @@ public class CalciteUtils { public static String createTempAlias() { return "$" + tempAliasCounter.incrementAndGet(); } + + @SuppressWarnings("rawtypes") + public static PDataType sqlTypeNameToPDataType(SqlTypeName sqlTypeName) { + return PDataType.fromTypeId(sqlTypeName.getJdbcOrdinal()); + } private static final Map<SqlKind, ExpressionFactory> EXPRESSION_MAP = Maps .newHashMapWithExpectedSize(ExpressionType.values().length); @@ -152,12 +188,287 @@ public class CalciteUtils { } }); + EXPRESSION_MAP.put(SqlKind.PLUS, new ExpressionFactory() { + + @SuppressWarnings("rawtypes") + @Override + public Expression newExpression(RexNode node, Implementor implementor) { + try { + List<Expression> children = convertChildren((RexCall) node, implementor); + Expression expr = null; + boolean foundDate = false; + Determinism determinism = Determinism.ALWAYS; + PDataType theType = null; + for(int i = 0; i < children.size(); i++) { + Expression e = children.get(i); + determinism = determinism.combine(e.getDeterminism()); + PDataType type = e.getDataType(); + if (type == null) { + continue; + } else if (type.isCoercibleTo(PTimestamp.INSTANCE)) { + if (foundDate) { + throw TypeMismatchException.newException(type, node.toString()); + } + if (theType == null || (theType != PTimestamp.INSTANCE && theType != PUnsignedTimestamp.INSTANCE)) { + theType = type; + } + foundDate = true; + }else if (type == PDecimal.INSTANCE) { + if (theType == null || !theType.isCoercibleTo(PTimestamp.INSTANCE)) { + theType = PDecimal.INSTANCE; + } + } else if (type.isCoercibleTo(PLong.INSTANCE)) { + if (theType == null) { + theType = PLong.INSTANCE; + } + } else if (type.isCoercibleTo(PDouble.INSTANCE)) { + if (theType == null) { + theType = PDouble.INSTANCE; + } + } else { + throw TypeMismatchException.newException(type, node.toString()); + } + } + if (theType == PDecimal.INSTANCE) { + expr = new DecimalAddExpression(children); + } else if (theType == PLong.INSTANCE) { + expr = new LongAddExpression(children); + } else if (theType == PDouble.INSTANCE) { + expr = new DoubleAddExpression(children); + } else if (theType == null) { + expr = LiteralExpression.newConstant(null, theType, determinism); + } else if (theType == PTimestamp.INSTANCE || theType == PUnsignedTimestamp.INSTANCE) { + expr = new TimestampAddExpression(children); + } else if (theType.isCoercibleTo(PDate.INSTANCE)) { + expr = new DateAddExpression(children); + } else { + throw TypeMismatchException.newException(theType, node.toString()); + } + + PDataType targetType = sqlTypeNameToPDataType(node.getType().getSqlTypeName()); + return cast(targetType, expr); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + }); + EXPRESSION_MAP.put(SqlKind.MINUS, new ExpressionFactory() { + + @SuppressWarnings("rawtypes") + @Override + public Expression newExpression(RexNode node, Implementor implementor) { + try { + List<Expression> children = convertChildren((RexCall) node, implementor); + Expression expr = null; + int i = 0; + PDataType theType = null; + Expression e1 = children.get(0); + Expression e2 = children.get(1); + Determinism determinism = e1.getDeterminism().combine(e2.getDeterminism()); + 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 != PTimestamp.INSTANCE + && type1 != PUnsignedTimestamp.INSTANCE + && type1.isCoercibleTo(PDate.INSTANCE); + boolean isType2Date = + type2 != null + && type2 != PTimestamp.INSTANCE + && type2 != PUnsignedTimestamp.INSTANCE + && type2.isCoercibleTo(PDate.INSTANCE); + if (isType1Date || isType2Date) { + if (isType1Date && isType2Date) { + i = 2; + theType = PDecimal.INSTANCE; + } else if (isType1Date && type2 != null + && type2.isCoercibleTo(PDecimal.INSTANCE)) { + i = 2; + theType = PDate.INSTANCE; + } 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 == PTimestamp.INSTANCE || type2 == PTimestamp.INSTANCE) { + i = 2; + theType = PTimestamp.INSTANCE; + } else if(type1 == PUnsignedTimestamp.INSTANCE || type2 == PUnsignedTimestamp.INSTANCE) { + i = 2; + theType = PUnsignedTimestamp.INSTANCE; + } + + 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); + determinism = determinism.combine(e.getDeterminism()); + PDataType type = e.getDataType(); + if (type == null) { + continue; + } else if (type.isCoercibleTo(PLong.INSTANCE)) { + if (theType == null) { + theType = PLong.INSTANCE; + } + } else if (type == PDecimal.INSTANCE) { + // Coerce return type to DECIMAL from LONG or DOUBLE if DECIMAL child found, + // unless we're doing date arithmetic. + if (theType == null + || !theType.isCoercibleTo(PDate.INSTANCE)) { + theType = PDecimal.INSTANCE; + } + } else if (type.isCoercibleTo(PDouble.INSTANCE)) { + // 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 != PDecimal.INSTANCE && !theType.isCoercibleTo(PDate.INSTANCE) )) { + theType = PDouble.INSTANCE; + } + } else { + throw TypeMismatchException.newException(type, node.toString()); + } + } + if (theType == PDecimal.INSTANCE) { + expr = new DecimalSubtractExpression(children); + } else if (theType == PLong.INSTANCE) { + expr = new LongSubtractExpression(children); + } else if (theType == PDouble.INSTANCE) { + expr = new DoubleSubtractExpression(children); + } else if (theType == null) { + expr = LiteralExpression.newConstant(null, theType, determinism); + } else if (theType == PTimestamp.INSTANCE || theType == PUnsignedTimestamp.INSTANCE) { + expr = new TimestampSubtractExpression(children); + } else if (theType.isCoercibleTo(PDate.INSTANCE)) { + expr = new DateSubtractExpression(children); + } else { + throw TypeMismatchException.newException(theType, node.toString()); + } + PDataType targetType = sqlTypeNameToPDataType(node.getType().getSqlTypeName()); + return cast(targetType, expr); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + }); + EXPRESSION_MAP.put(SqlKind.TIMES, new ExpressionFactory() { + + @SuppressWarnings("rawtypes") + @Override + public Expression newExpression(RexNode node, Implementor implementor) { + try { + List<Expression> children = convertChildren((RexCall) node, implementor); + Expression expr = null; + PDataType theType = null; + Determinism determinism = Determinism.ALWAYS; + for(int i = 0; i < children.size(); i++) { + Expression e = children.get(i); + determinism = determinism.combine(e.getDeterminism()); + PDataType type = e.getDataType(); + if (type == null) { + continue; + } else if (type == PDecimal.INSTANCE) { + theType = PDecimal.INSTANCE; + } else if (type.isCoercibleTo(PLong.INSTANCE)) { + if (theType == null) { + theType = PLong.INSTANCE; + } + } else if (type.isCoercibleTo(PDouble.INSTANCE)) { + if (theType == null) { + theType = PDouble.INSTANCE; + } + } else { + throw TypeMismatchException.newException(type, node.toString()); + } + } + if (theType == PDecimal.INSTANCE) { + expr = new DecimalMultiplyExpression(children); + } else if (theType == PLong.INSTANCE) { + expr = new LongMultiplyExpression(children); + } else if (theType == PDouble.INSTANCE) { + expr = new DoubleMultiplyExpression(children); + } else { + expr = LiteralExpression.newConstant(null, theType, determinism); + } + PDataType targetType = sqlTypeNameToPDataType(node.getType().getSqlTypeName()); + return cast(targetType, expr); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + }); + EXPRESSION_MAP.put(SqlKind.DIVIDE, new ExpressionFactory() { + + @SuppressWarnings("rawtypes") + @Override + public Expression newExpression(RexNode node, Implementor implementor) { + try { + List<Expression> children = convertChildren((RexCall) node, implementor); + Expression expr = null; + PDataType theType = null; + Determinism determinism = Determinism.ALWAYS; + for(int i = 0; i < children.size(); i++) { + Expression e = children.get(i); + determinism = determinism.combine(e.getDeterminism()); + PDataType type = e.getDataType(); + if (type == null) { + continue; + } else if (type == PDecimal.INSTANCE) { + theType = PDecimal.INSTANCE; + } else if (type.isCoercibleTo(PLong.INSTANCE)) { + if (theType == null) { + theType = PLong.INSTANCE; + } + } else if (type.isCoercibleTo(PDouble.INSTANCE)) { + if (theType == null) { + theType = PDouble.INSTANCE; + } + } else { + throw TypeMismatchException.newException(type, node.toString()); + } + } + if (theType == PDecimal.INSTANCE) { + expr = new DecimalDivideExpression( children); + } else if (theType == PLong.INSTANCE) { + expr = new LongDivideExpression( children); + } else if (theType == PDouble.INSTANCE) { + expr = new DoubleDivideExpression(children); + } else { + expr = LiteralExpression.newConstant(null, theType, determinism); + } + PDataType targetType = sqlTypeNameToPDataType(node.getType().getSqlTypeName()); + return cast(targetType, expr); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + }); EXPRESSION_MAP.put(SqlKind.LITERAL, new ExpressionFactory() { @Override public Expression newExpression(RexNode node, Implementor implementor) { RexLiteral lit = (RexLiteral) node; - Object o = lit.getValue2(); + Object o = lit.getValue(); + if (o instanceof NlsString) { + o = ((NlsString) o).getValue(); + } return LiteralExpression.newConstant(o); } @@ -174,11 +485,17 @@ public class CalciteUtils { }); EXPRESSION_MAP.put(SqlKind.CAST, new ExpressionFactory() { + @SuppressWarnings("rawtypes") @Override public Expression newExpression(RexNode node, - Implementor implementor) { - // TODO replace with real implementation - return toExpression(((RexCall) node).getOperands().get(0), implementor); + Implementor implementor) { + List<Expression> children = convertChildren((RexCall) node, implementor); + PDataType targetType = sqlTypeNameToPDataType(node.getType().getSqlTypeName()); + try { + return cast(targetType, children.get(0)); + } catch (SQLException e) { + throw new RuntimeException(e); + } } }); @@ -237,6 +554,34 @@ public class CalciteUtils { } return children; } + + @SuppressWarnings("rawtypes") + private static Expression cast(PDataType targetDataType, Expression childExpr) throws SQLException { + PDataType fromDataType = childExpr.getDataType(); + + Expression expr = childExpr; + if(fromDataType != null) { + expr = convertToRoundExpressionIfNeeded(fromDataType, targetDataType, childExpr); + } + return CoerceExpression.create(expr, targetDataType, SortOrder.getDefault(), expr.getMaxLength()); + } + + @SuppressWarnings("rawtypes") + private static Expression convertToRoundExpressionIfNeeded(PDataType fromDataType, PDataType targetDataType, Expression expr) throws SQLException { + if(fromDataType == targetDataType) { + return expr; + } else if((fromDataType == PDecimal.INSTANCE || fromDataType == PTimestamp.INSTANCE || fromDataType == PUnsignedTimestamp.INSTANCE) && targetDataType.isCoercibleTo( + PLong.INSTANCE)) { + return RoundDecimalExpression.create(Arrays.asList(expr)); + } else if((fromDataType == PDecimal.INSTANCE || fromDataType == PTimestamp.INSTANCE || fromDataType == PUnsignedTimestamp.INSTANCE) && targetDataType.isCoercibleTo( + PDate.INSTANCE)) { + return RoundTimestampExpression.create(Arrays.asList(expr)); + } else if(fromDataType.isCastableTo(targetDataType)) { + return expr; + } else { + throw TypeMismatchException.newException(fromDataType, targetDataType, expr.toString()); + } + } public static boolean isExpressionSupported(RexNode node) { try { http://git-wip-us.apache.org/repos/asf/phoenix/blob/f9ddb988/phoenix-core/src/main/java/org/apache/phoenix/calcite/PhoenixTable.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/calcite/PhoenixTable.java b/phoenix-core/src/main/java/org/apache/phoenix/calcite/PhoenixTable.java index 19d05da..99e829d 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/calcite/PhoenixTable.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/calcite/PhoenixTable.java @@ -62,6 +62,7 @@ public class PhoenixTable extends AbstractTable implements TranslatableTable { return pTable; } + @SuppressWarnings("rawtypes") @Override public RelDataType getRowType(RelDataTypeFactory typeFactory) { final RelDataTypeFactory.FieldInfoBuilder builder = typeFactory.builder(); http://git-wip-us.apache.org/repos/asf/phoenix/blob/f9ddb988/phoenix-core/src/main/java/org/apache/phoenix/calcite/rel/PhoenixToEnumerableConverter.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/calcite/rel/PhoenixToEnumerableConverter.java b/phoenix-core/src/main/java/org/apache/phoenix/calcite/rel/PhoenixToEnumerableConverter.java index 058922c..771cad9 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/calcite/rel/PhoenixToEnumerableConverter.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/calcite/rel/PhoenixToEnumerableConverter.java @@ -99,6 +99,7 @@ public class PhoenixToEnumerableConverter extends ConverterImpl implements Enume }; } + @SuppressWarnings({ "rawtypes", "unchecked" }) static Expression stash(EnumerableRelImplementor implementor, Object o, Class clazz) { ParameterExpression x = (ParameterExpression) implementor.stash(o, clazz); MethodCallExpression e =