diff --git a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/stats/FilterSelectivityEstimator.java b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/stats/FilterSelectivityEstimator.java index b18c525c8849..257a1e1747ec 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/stats/FilterSelectivityEstimator.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/stats/FilterSelectivityEstimator.java @@ -18,18 +18,24 @@ package org.apache.hadoop.hive.ql.optimizer.calcite.stats; import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collections; import java.util.GregorianCalendar; import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.Set; +import com.google.common.collect.BoundType; +import com.google.common.collect.Range; import org.apache.calcite.plan.RelOptUtil; import org.apache.calcite.plan.RelOptUtil.InputReferencedVisitor; import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.core.Filter; import org.apache.calcite.rel.core.Project; import org.apache.calcite.rel.metadata.RelMetadataQuery; +import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rex.RexBuilder; import org.apache.calcite.rex.RexCall; import org.apache.calcite.rex.RexInputRef; @@ -184,91 +190,309 @@ public Double visitCall(RexCall call) { return selectivity; } - private double computeRangePredicateSelectivity(RexCall call, SqlKind op) { - final boolean isLiteralLeft = call.getOperands().get(0).getKind().equals(SqlKind.LITERAL); - final boolean isLiteralRight = call.getOperands().get(1).getKind().equals(SqlKind.LITERAL); - final boolean isInputRefLeft = call.getOperands().get(0).getKind().equals(SqlKind.INPUT_REF); - final boolean isInputRefRight = call.getOperands().get(1).getKind().equals(SqlKind.INPUT_REF); + /** + * Return whether the expression is a removable cast based on stats and type bounds. + * + *
+ * In Hive, if a value cannot be represented by the cast, the result of the cast is NULL, + * and therefore cannot fulfill the predicate. So the possible range of the values + * is limited by the range of possible values of the type. + *
+ * + * @param exp the expression to check + * @param tableScan the table that provides the statistics + * @return true if the expression is a removable cast, false otherwise + */ + private boolean isRemovableCast(RexNode exp, HiveTableScan tableScan) { + if(SqlKind.CAST != exp.getKind()) { + return false; + } + RexCall cast = (RexCall) exp; + RexNode op0 = cast.getOperands().getFirst(); + if (!(op0 instanceof RexInputRef)) { + return false; + } + int index = ((RexInputRef) op0).getIndex(); + final List+ * Special care is taken to support the cast to DECIMAL(precision, scale): + * The cast to DECIMAL rounds the value the same way as {@link RoundingMode#HALF_UP}. + * The boundaries are adjusted accordingly. + *
+ * + * @param predicateRange boundaries of the range predicate + * @param type the DECIMAL type + * @param typeRange the boundaries of the type range + * @return the adjusted boundary + */ + private static Range+ * The rawSelectivity does not account for null values. We multiply with the number of non-null values (getN) + * and we divide by the total number (non-null + null values) to get the overall selectivity. + *
+ * Example: consider a filter "col < 3", and the following table rows: + *
+ * _____ + * | col | + * |_____| + * |1 | + * |null | + * |null | + * |3 | + * |4 | + * ------- + *+ * kll.getN() would be 3, rawSelectivity 1/3, scan.getTable().getRowCount() 5 + * so the final result would be 3 * 1/3 / 5 = 1/5, as expected. + */ + private static double scaleSelectivityToNullableValues(KllFloatsSketch kll, double rawSelectivity, + HiveTableScan scan) { + if (scan.getTable() == null) { + return rawSelectivity; + } + return kll.getN() * rawSelectivity / scan.getTable().getRowCount(); } private Double computeBetweenPredicateSelectivity(RexCall call) { - final boolean hasLiteralBool = call.getOperands().get(0).getKind().equals(SqlKind.LITERAL); - final boolean hasInputRef = call.getOperands().get(1).getKind().equals(SqlKind.INPUT_REF); - final boolean hasLiteralLeft = call.getOperands().get(2).getKind().equals(SqlKind.LITERAL); - final boolean hasLiteralRight = call.getOperands().get(3).getKind().equals(SqlKind.LITERAL); + if (!(childRel instanceof HiveTableScan)) { + return computeFunctionSelectivity(call); + } - if (childRel instanceof HiveTableScan && hasLiteralBool && hasInputRef && hasLiteralLeft && hasLiteralRight) { - final HiveTableScan t = (HiveTableScan) childRel; - final int inputRefIndex = ((RexInputRef) call.getOperands().get(1)).getIndex(); - final List
+ * See {@link org.apache.hadoop.hive.ql.udf.generic.GenericUDFToUnixTimeStamp#evaluate(GenericUDF.DeferredObject[])}. + */ + private static final float[] VALUES_TIME = { + timestamp("2020-11-01"), timestamp("2020-11-02"), timestamp("2020-11-03"), timestamp("2020-11-04"), + timestamp("2020-11-05T11:23:45Z"), timestamp("2020-11-06"), timestamp("2020-11-07") }; + private static final KllFloatsSketch KLL = StatisticsTestUtils.createKll(VALUES); - private static final float DELTA = Float.MIN_VALUE; + private static final KllFloatsSketch KLL2 = StatisticsTestUtils.createKll(VALUES2); + private static final KllFloatsSketch KLL_TIME = StatisticsTestUtils.createKll(VALUES_TIME); + private static final float DELTA = 1e-7f; private static final RexBuilder REX_BUILDER = new RexBuilder(new JavaTypeFactoryImpl(new HiveTypeSystemImpl())); private static final RelDataTypeFactory TYPE_FACTORY = REX_BUILDER.getTypeFactory(); + private static RelOptCluster relOptCluster; private static RexNode intMinus1; private static RexNode int0; @@ -85,7 +130,6 @@ public class TestFilterSelectivityEstimator { private static RexNode inputRef0; private static RexNode boolFalse; private static RexNode boolTrue; - private static ColStatistics stats; @Mock private RelOptSchema schemaMock; @@ -94,12 +138,14 @@ public class TestFilterSelectivityEstimator { @Mock private RelMetadataQuery mq; - private HiveTableScan tableScan; + private ColStatistics stats; private RelNode scan; + private RexNode currentInputRef; + private int currentValuesSize; @BeforeClass public static void beforeClass() { - RelDataType integerType = TYPE_FACTORY.createSqlType(SqlTypeName.INTEGER); + RelDataType integerType = TYPE_FACTORY.createSqlType(INTEGER); intMinus1 = REX_BUILDER.makeLiteral(-1, integerType, true); int0 = REX_BUILDER.makeLiteral(0, integerType, true); int1 = REX_BUILDER.makeLiteral(1, integerType, true); @@ -113,25 +159,54 @@ public static void beforeClass() { int11 = REX_BUILDER.makeLiteral(11, integerType, true); boolFalse = REX_BUILDER.makeLiteral(false, TYPE_FACTORY.createSqlType(SqlTypeName.BOOLEAN), true); boolTrue = REX_BUILDER.makeLiteral(true, TYPE_FACTORY.createSqlType(SqlTypeName.BOOLEAN), true); - tableType = TYPE_FACTORY.createStructType(ImmutableList.of(integerType), ImmutableList.of("f1")); + RelDataTypeFactory.Builder b = new RelDataTypeFactory.Builder(TYPE_FACTORY); + b.add("f_numeric", decimalType(38, 25)); + b.add("f_timestamp", SqlTypeName.TIMESTAMP); + b.add("f_date", SqlTypeName.DATE).build(); + tableType = b.build(); RelOptPlanner planner = CalcitePlanner.createPlanner(new HiveConf()); relOptCluster = RelOptCluster.create(planner, REX_BUILDER); + } - stats = new ColStatistics(); - stats.setHistogram(KLL.toByteArray()); + private static ColStatistics.Range rangeOf(float[] values) { + float min = Float.MAX_VALUE, max = -Float.MAX_VALUE; + for (float v : values) { + min = Math.min(min, v); + max = Math.max(max, v); + } + return new ColStatistics.Range(min, max); } @Before public void before() { + currentValuesSize = VALUES.length; doReturn(tableType).when(tableMock).getRowType(); - doReturn((double) VALUES.length).when(tableMock).getRowCount(); + when(tableMock.getRowCount()).thenAnswer(a -> (double) currentValuesSize); RelBuilder relBuilder = HiveRelFactories.HIVE_BUILDER.create(relOptCluster, schemaMock); - tableScan = new HiveTableScan(relOptCluster, relOptCluster.traitSetOf(HiveRelNode.CONVENTION), - tableMock, "table", null, false, false); + HiveTableScan tableScan = + new HiveTableScan(relOptCluster, relOptCluster.traitSetOf(HiveRelNode.CONVENTION), tableMock, "table", null, + false, false); scan = relBuilder.push(tableScan).build(); inputRef0 = REX_BUILDER.makeInputRef(scan, 0); + currentInputRef = inputRef0; + + stats = new ColStatistics(); + stats.setHistogram(KLL.toByteArray()); + stats.setRange(rangeOf(VALUES)); + } + + /** + * Note: call this method only at the beginning of a test method. + */ + private void useFieldWithValues(String fieldname, float[] values, KllFloatsSketch sketch) { + currentValuesSize = values.length; + stats.setHistogram(sketch.toByteArray()); + stats.setRange(rangeOf(values)); + int fieldIndex = scan.getRowType().getFieldNames().indexOf(fieldname); + currentInputRef = REX_BUILDER.makeInputRef(scan, fieldIndex); + doReturn(Collections.singletonList(stats)).when(tableMock).getColStat(Collections.singletonList(fieldIndex)); } @Test @@ -420,7 +495,7 @@ public void testComputeRangePredicateSelectivityBetweenLeftLowerThanRight() { @Test public void testComputeRangePredicateSelectivityBetweenLeftEqualsRight() { - doReturn(Collections.singletonList(stats)).when(tableMock).getColStat(Collections.singletonList(0)); + verify(tableMock, never()).getColStat(any()); doReturn(10.0).when(mq).getDistinctRowCount(scan, ImmutableBitSet.of(0), REX_BUILDER.makeLiteral(true)); RexNode filter = REX_BUILDER.makeCall(HiveBetween.INSTANCE, boolFalse, inputRef0, int3, int3); FilterSelectivityEstimator estimator = new FilterSelectivityEstimator(scan, mq); @@ -454,7 +529,7 @@ public void testComputeRangePredicateSelectivityNotBetweenRightLowerThanLeft() { @Test public void testComputeRangePredicateSelectivityNotBetweenLeftEqualsRight() { - doReturn(Collections.singletonList(stats)).when(tableMock).getColStat(Collections.singletonList(0)); + verify(tableMock, never()).getColStat(any()); RexNode filter = REX_BUILDER.makeCall(HiveBetween.INSTANCE, boolTrue, inputRef0, int3, int3); FilterSelectivityEstimator estimator = new FilterSelectivityEstimator(scan, mq); Assert.assertEquals(1, estimator.estimateSelectivity(filter), DELTA); @@ -511,6 +586,271 @@ public void testComputeRangePredicateSelectivityNotBetweenWithNULLS() { doReturn(Collections.singletonList(stats)).when(tableMock).getColStat(Collections.singletonList(0)); RexNode filter = REX_BUILDER.makeCall(HiveBetween.INSTANCE, boolTrue, inputRef0, int1, int3); FilterSelectivityEstimator estimator = new FilterSelectivityEstimator(scan, mq); - Assert.assertEquals(0.55, estimator.estimateSelectivity(filter), DELTA); + // only the values 4, 5, 6, 7 fulfill the condition NOT BETWEEN 1 AND 3 + // (the NULL values do not fulfill the condition) + Assert.assertEquals(0.2, estimator.estimateSelectivity(filter), DELTA); + } + + @Test + public void testRangePredicateWithCast() { + useFieldWithValues("f_numeric", VALUES, KLL); + checkSelectivity(3 / 13.f, ge(cast("f_numeric", TINYINT), int5)); + checkSelectivity(10 / 13.f, lt(cast("f_numeric", TINYINT), int5)); + checkSelectivity(2 / 13.f, gt(cast("f_numeric", TINYINT), int5)); + checkSelectivity(11 / 13.f, le(cast("f_numeric", TINYINT), int5)); + + checkSelectivity(12 / 13f, ge(cast("f_numeric", TINYINT), int2)); + checkSelectivity(1 / 13f, lt(cast("f_numeric", TINYINT), int2)); + checkSelectivity(5 / 13f, gt(cast("f_numeric", TINYINT), int2)); + checkSelectivity(8 / 13f, le(cast("f_numeric", TINYINT), int2)); + + // check some types + checkSelectivity(3 / 13.f, ge(cast("f_numeric", INTEGER), int5)); + checkSelectivity(3 / 13.f, ge(cast("f_numeric", SMALLINT), int5)); + checkSelectivity(3 / 13.f, ge(cast("f_numeric", BIGINT), int5)); + checkSelectivity(3 / 13.f, ge(cast("f_numeric", FLOAT), int5)); + checkSelectivity(3 / 13.f, ge(cast("f_numeric", DOUBLE), int5)); + } + + @Test + public void testRangePredicateWithCast2() { + useFieldWithValues("f_numeric", VALUES2, KLL2); + RelDataType decimal3s1 = decimalType(3, 1); + checkSelectivity(4 / 28.f, ge(cast("f_numeric", decimal3s1), literalFloat(1))); + + // values from -99.94999 to 99.94999 (both inclusive) + checkSelectivity(7 / 28.f, lt(cast("f_numeric", decimal3s1), literalFloat(100))); + checkSelectivity(7 / 28.f, le(cast("f_numeric", decimal3s1), literalFloat(100))); + checkSelectivity(0 / 28.f, gt(cast("f_numeric", decimal3s1), literalFloat(100))); + checkSelectivity(0 / 28.f, ge(cast("f_numeric", decimal3s1), literalFloat(100))); + + RelDataType decimal4s1 = decimalType(4, 1); + checkSelectivity(10 / 28.f, lt(cast("f_numeric", decimal4s1), literalFloat(100))); + checkSelectivity(20 / 28.f, le(cast("f_numeric", decimal4s1), literalFloat(100))); + checkSelectivity(3 / 28.f, gt(cast("f_numeric", decimal4s1), literalFloat(100))); + checkSelectivity(13 / 28.f, ge(cast("f_numeric", decimal4s1), literalFloat(100))); + + RelDataType decimal2s1 = decimalType(2, 1); + checkSelectivity(2 / 28.f, lt(cast("f_numeric", decimal2s1), literalFloat(100))); + checkSelectivity(2 / 28.f, le(cast("f_numeric", decimal2s1), literalFloat(100))); + checkSelectivity(0 / 28.f, gt(cast("f_numeric", decimal2s1), literalFloat(100))); + checkSelectivity(0 / 28.f, ge(cast("f_numeric", decimal2s1), literalFloat(100))); + + // expected: 100_000f + RelDataType decimal7s1 = decimalType(7, 1); + checkSelectivity(1 / 28.f, gt(cast("f_numeric", decimal7s1), literalFloat(10000))); + + // expected: 10_000f, 100_000f, because CAST(1_000_000 AS DECIMAL(7,1)) = NULL, and similar for even larger values + checkSelectivity(2 / 28.f, ge(cast("f_numeric", decimal7s1), literalFloat(9999))); + checkSelectivity(2 / 28.f, ge(cast("f_numeric", decimal7s1), literalFloat(10000))); + + // expected: 100_000f + checkSelectivity(1 / 28.f, gt(cast("f_numeric", decimal7s1), literalFloat(10000))); + checkSelectivity(1 / 28.f, gt(cast("f_numeric", decimal7s1), literalFloat(10001))); + + // expected 1f, 10f, 99.94998f, 99.94999f + checkSelectivity(4 / 28.f, ge(cast("f_numeric", decimal3s1), literalFloat(1))); + checkSelectivity(3 / 28.f, gt(cast("f_numeric", decimal3s1), literalFloat(1))); + // expected -99.94999f, -99.94998f, 0f, 1f + checkSelectivity(4 / 28.f, le(cast("f_numeric", decimal3s1), literalFloat(1))); + checkSelectivity(3 / 28.f, lt(cast("f_numeric", decimal3s1), literalFloat(1))); + + // the cast would apply a modulo operation to the values outside the range of the cast + // so instead a default selectivity should be returned + checkSelectivity(1 / 3.f, lt(cast("f_numeric", TINYINT), literalFloat(100))); + checkSelectivity(1 / 3.f, lt(cast("f_numeric", TINYINT), literalFloat(100))); + } + + private void checkTimeFieldOnMidnightTimestamps(RexNode field) { + // note: use only values from VALUES_TIME that specify a date without hh:mm:ss! + checkSelectivity(7 / 7.f, ge(field, literalTimestamp("2020-11-01"))); + checkSelectivity(5 / 7.f, ge(field, literalTimestamp("2020-11-03"))); + checkSelectivity(1 / 7.f, ge(field, literalTimestamp("2020-11-07"))); + + checkSelectivity(6 / 7.f, gt(field, literalTimestamp("2020-11-01"))); + checkSelectivity(4 / 7.f, gt(field, literalTimestamp("2020-11-03"))); + checkSelectivity(0 / 7.f, gt(field, literalTimestamp("2020-11-07"))); + + checkSelectivity(1 / 7.f, le(field, literalTimestamp("2020-11-01"))); + checkSelectivity(3 / 7.f, le(field, literalTimestamp("2020-11-03"))); + checkSelectivity(7 / 7.f, le(field, literalTimestamp("2020-11-07"))); + + checkSelectivity(0 / 7.f, lt(field, literalTimestamp("2020-11-01"))); + checkSelectivity(2 / 7.f, lt(field, literalTimestamp("2020-11-03"))); + checkSelectivity(6 / 7.f, lt(field, literalTimestamp("2020-11-07"))); + } + + private void checkTimeFieldOnIntraDayTimestamps(RexNode field) { + checkSelectivity(3 / 7.f, ge(field, literalTimestamp("2020-11-05T11:23:45Z"))); + checkSelectivity(2 / 7.f, gt(field, literalTimestamp("2020-11-05T11:23:45Z"))); + checkSelectivity(5 / 7.f, le(field, literalTimestamp("2020-11-05T11:23:45Z"))); + checkSelectivity(4 / 7.f, lt(field, literalTimestamp("2020-11-05T11:23:45Z"))); + } + + @Test + public void testRangePredicateOnTimestamp() { + useFieldWithValues("f_timestamp", VALUES_TIME, KLL_TIME); + checkTimeFieldOnMidnightTimestamps(currentInputRef); + checkTimeFieldOnIntraDayTimestamps(currentInputRef); + } + + @Test + public void testRangePredicateOnTimestampWithCast() { + useFieldWithValues("f_timestamp", VALUES_TIME, KLL_TIME); + RexNode expr1 = cast("f_timestamp", SqlTypeName.DATE); + checkTimeFieldOnMidnightTimestamps(expr1); + checkTimeFieldOnIntraDayTimestamps(expr1); + + RexNode expr2 = cast("f_timestamp", SqlTypeName.TIMESTAMP); + checkTimeFieldOnMidnightTimestamps(expr2); + checkTimeFieldOnIntraDayTimestamps(expr2); + } + + @Test + public void testRangePredicateOnDate() { + useFieldWithValues("f_date", VALUES_TIME, KLL_TIME); + checkTimeFieldOnMidnightTimestamps(currentInputRef); + + // it does not make sense to compare with "2020-11-05T11:23:45Z", + // as that value would not be stored as-is in a date column, but as "2020-11-05" instead + } + + @Test + public void testRangePredicateOnDateWithCast() { + useFieldWithValues("f_date", VALUES_TIME, KLL_TIME); + checkTimeFieldOnMidnightTimestamps(cast("f_date", SqlTypeName.DATE)); + checkTimeFieldOnMidnightTimestamps(cast("f_date", SqlTypeName.TIMESTAMP)); + + // it does not make sense to compare with "2020-11-05T11:23:45Z", + // as that value would not be stored as-is in a date column, but as "2020-11-05" instead + } + + @Test + public void testBetweenWithCastDecimal2s1() { + useFieldWithValues("f_numeric", VALUES2, KLL2); + float total = VALUES2.length; + float universe = 2; // the number of values that "survive" the cast + RexNode cast = REX_BUILDER.makeCast(decimalType(2, 1), inputRef0); + checkBetweenSelectivity(0, universe, total, cast, 100f, 1000f); + checkBetweenSelectivity(1, universe, total, cast, 1f, 100f); + checkBetweenSelectivity(0, universe, total, cast, 100f, 0f); + } + + @Test + public void testBetweenWithCastDecimal3s1() { + useFieldWithValues("f_numeric", VALUES2, KLL2); + float total = VALUES2.length; + float universe = 7; // the number of values that "survive" the cast + RexNode cast = REX_BUILDER.makeCast(decimalType(3, 1), inputRef0); + checkBetweenSelectivity(0, universe, total, cast, 100f, 1000f); + checkBetweenSelectivity(4, universe, total, cast, 1f, 100f); + checkBetweenSelectivity(0, universe, total, cast, 100f, 0f); + } + + @Test + public void testBetweenWithCastDecimal4s1() { + useFieldWithValues("f_numeric", VALUES2, KLL2); + float total = VALUES2.length; + float universe = 23; // the number of values that "survive" the cast + RexNode cast = REX_BUILDER.makeCast(decimalType(4, 1), inputRef0); + // the values between -999.94999... and 999.94999... (both inclusive) pass through the cast + // the values between 99.95 and 100 are rounded up to 100, so they fulfill the BETWEEN + checkBetweenSelectivity(13, universe, total, cast, 100, 1000); + checkBetweenSelectivity(14, universe, total, cast, 1f, 100f); + checkBetweenSelectivity(0, universe, total, cast, 100f, 0f); + } + + @Test + public void testBetweenWithCastDecimal7s1() { + useFieldWithValues("f_numeric", VALUES2, KLL2); + float total = VALUES2.length; + float universe = 26; // the number of values that "survive" the cast + RexNode cast = REX_BUILDER.makeCast(decimalType(7, 1), inputRef0); + checkBetweenSelectivity(14, universe, total, cast, 100, 1000); + checkBetweenSelectivity(14, universe, total, cast, 1f, 100f); + checkBetweenSelectivity(0, universe, total, cast, 100f, 0f); + } + + private void checkSelectivity(float expectedSelectivity, RexNode filter) { + FilterSelectivityEstimator estimator = new FilterSelectivityEstimator(scan, mq); + Assert.assertEquals(filter.toString(), expectedSelectivity, estimator.estimateSelectivity(filter), DELTA); + + // convert "col OP value" to "value INVERSE_OP col", and check it + RexNode inverted = RexUtil.invert(REX_BUILDER, (RexCall) filter); + if (inverted != null) { + Assert.assertEquals(filter.toString(), expectedSelectivity, estimator.estimateSelectivity(inverted), DELTA); + } + } + + private void checkBetweenSelectivity(float expectedEntries, float universe, float total, RexNode value, float lower, + float upper) { + RexNode betweenFilter = + REX_BUILDER.makeCall(HiveBetween.INSTANCE, boolFalse, value, literalFloat(lower), literalFloat(upper)); + FilterSelectivityEstimator estimator = new FilterSelectivityEstimator(scan, mq); + String between = "BETWEEN " + lower + " AND " + upper; + float expectedSelectivity = expectedEntries / total; + String message = between + ": calcite filter " + betweenFilter.toString(); + Assert.assertEquals(message, expectedSelectivity, estimator.estimateSelectivity(betweenFilter), DELTA); + + // invert the filter to a NOT BETWEEN + RexNode invBetween = + REX_BUILDER.makeCall(HiveBetween.INSTANCE, boolTrue, value, literalFloat(lower), literalFloat(upper)); + String invMessage = "NOT " + between + ": calcite filter " + invBetween.toString(); + float invExpectedSelectivity = (universe - expectedEntries) / total; + Assert.assertEquals(invMessage, invExpectedSelectivity, estimator.estimateSelectivity(invBetween), DELTA); + } + + private RexNode cast(String fieldname, SqlTypeName typeName) { + return cast(fieldname, type(typeName)); + } + + private RexNode cast(String fieldname, RelDataType type) { + int fieldIndex = scan.getRowType().getFieldNames().indexOf(fieldname); + RexNode column = REX_BUILDER.makeInputRef(scan, fieldIndex); + return REX_BUILDER.makeCast(type, column); + } + + private RexNode ge(RexNode expr, RexNode value) { + return REX_BUILDER.makeCall(SqlStdOperatorTable.GREATER_THAN_OR_EQUAL, expr, value); + } + + private RexNode gt(RexNode expr, RexNode value) { + return REX_BUILDER.makeCall(SqlStdOperatorTable.GREATER_THAN, expr, value); + } + + private RexNode le(RexNode expr, RexNode value) { + return REX_BUILDER.makeCall(SqlStdOperatorTable.LESS_THAN_OR_EQUAL, expr, value); + } + + private RexNode lt(RexNode expr, RexNode value) { + return REX_BUILDER.makeCall(SqlStdOperatorTable.LESS_THAN, expr, value); + } + + private static RelDataType type(SqlTypeName typeName) { + return REX_BUILDER.getTypeFactory().createSqlType(typeName); + } + + private static RelDataType decimalType(int precision, int scale) { + return REX_BUILDER.getTypeFactory().createSqlType(SqlTypeName.DECIMAL, precision, scale); + } + + private static RexLiteral literalTimestamp(String timestamp) { + return REX_BUILDER.makeLiteral(timestampMillis(timestamp), + REX_BUILDER.getTypeFactory().createSqlType(SqlTypeName.TIMESTAMP)); + } + + private RexNode literalFloat(float f) { + return REX_BUILDER.makeLiteral(f, type(SqlTypeName.FLOAT)); + } + + private static long timestampMillis(String timestamp) { + if (!timestamp.contains(":")) { + return LocalDate.parse(timestamp).toEpochSecond(LocalTime.MIDNIGHT, ZoneOffset.UTC) * 1000; + } + return Instant.parse(timestamp).toEpochMilli(); + } + + private static long timestamp(String timestamp) { + return timestampMillis(timestamp) / 1000; } }