HIVE-13316: Upgrade to Calcite 1.10 (Jesus Camacho Rodriguez, reviewed by Ashutosh Chauhan)
Project: http://git-wip-us.apache.org/repos/asf/hive/repo Commit: http://git-wip-us.apache.org/repos/asf/hive/commit/b597ab2a Tree: http://git-wip-us.apache.org/repos/asf/hive/tree/b597ab2a Diff: http://git-wip-us.apache.org/repos/asf/hive/diff/b597ab2a Branch: refs/heads/master Commit: b597ab2a07034b9c82e4bb0591123c3a115f27eb Parents: 4b7f373 Author: Jesus Camacho Rodriguez <jcama...@apache.org> Authored: Wed Sep 28 20:23:33 2016 +0100 Committer: Jesus Camacho Rodriguez <jcama...@apache.org> Committed: Tue Oct 18 12:27:41 2016 +0100 ---------------------------------------------------------------------- .../druid/HiveDruidQueryBasedInputFormat.java | 6 +- .../serde/DruidGroupByQueryRecordReader.java | 2 +- .../serde/DruidSelectQueryRecordReader.java | 2 +- .../hadoop/hive/druid/serde/DruidSerDe.java | 2 +- .../serde/DruidTimeseriesQueryRecordReader.java | 2 +- .../druid/serde/DruidTopNQueryRecordReader.java | 2 +- pom.xml | 3 +- ql/pom.xml | 7 +- .../calcite/HiveDefaultRelMetadataProvider.java | 2 +- .../optimizer/calcite/HivePlannerContext.java | 9 +- .../ql/optimizer/calcite/HiveRelBuilder.java | 18 +- .../ql/optimizer/calcite/HiveRelOptUtil.java | 8 +- .../hive/ql/optimizer/calcite/HiveRexUtil.java | 821 -------------- .../optimizer/calcite/HiveTypeSystemImpl.java | 39 +- .../calcite/cost/HiveDefaultCostModel.java | 7 +- .../optimizer/calcite/cost/HiveRelMdCost.java | 10 +- .../calcite/druid/DruidIntervalUtils.java | 466 -------- .../ql/optimizer/calcite/druid/DruidQuery.java | 1053 ------------------ .../optimizer/calcite/druid/DruidQueryType.java | 42 - .../ql/optimizer/calcite/druid/DruidRules.java | 591 ---------- .../ql/optimizer/calcite/druid/DruidSchema.java | 51 - .../ql/optimizer/calcite/druid/DruidTable.java | 121 -- .../optimizer/calcite/druid/HiveDruidConf.java | 33 - .../calcite/reloperators/HiveAggregate.java | 3 +- .../reloperators/HiveDateGranularity.java | 54 - .../calcite/reloperators/HiveExtractDate.java | 50 + .../calcite/reloperators/HiveFloorDate.java | 64 ++ .../rules/HiveAggregateJoinTransposeRule.java | 9 +- .../rules/HiveAggregateProjectMergeRule.java | 3 +- .../rules/HiveFilterProjectTSTransposeRule.java | 16 +- .../rules/HiveFilterProjectTransposeRule.java | 21 +- .../calcite/rules/HivePreFilteringRule.java | 7 +- .../rules/HiveReduceExpressionsRule.java | 914 ++------------- .../HiveReduceExpressionsWithStatsRule.java | 5 +- .../calcite/rules/HiveRelFieldTrimmer.java | 243 +--- .../calcite/stats/HiveRelMdCollation.java | 10 +- .../calcite/stats/HiveRelMdDistribution.java | 10 +- .../calcite/stats/HiveRelMdPredicates.java | 31 +- .../calcite/stats/HiveRelMdSelectivity.java | 28 +- .../optimizer/calcite/stats/HiveRelMdSize.java | 13 +- .../calcite/stats/HiveRelMdUniqueKeys.java | 72 +- .../calcite/translator/ASTBuilder.java | 49 +- .../calcite/translator/ASTConverter.java | 51 +- .../calcite/translator/ExprNodeConverter.java | 49 +- .../translator/PlanModifierForASTConv.java | 5 + .../calcite/translator/RexNodeConverter.java | 61 +- .../translator/SqlFunctionConverter.java | 37 +- .../calcite/translator/TypeConverter.java | 41 +- .../hadoop/hive/ql/parse/CalcitePlanner.java | 40 +- .../hive/ql/parse/TypeCheckProcFactory.java | 2 +- .../optimizer/calcite/TestCBOMaxNumToCNF.java | 5 +- .../calcite/TestCBORuleFiredOnlyOnce.java | 2 +- .../results/clientpositive/druid_basic2.q.out | 48 +- .../clientpositive/druid_intervals.q.out | 40 +- .../clientpositive/druid_timeseries.q.out | 52 +- .../results/clientpositive/druid_topn.q.out | 32 +- .../clientpositive/explain_logical.q.out | 48 +- .../clientpositive/groupby_sort_1_23.q.out | 40 +- .../clientpositive/groupby_sort_skew_1_23.q.out | 40 +- .../results/clientpositive/limit_pushdown.q.out | 12 +- .../clientpositive/limit_pushdown3.q.out | 12 +- .../clientpositive/llap/explainuser_4.q.out | 32 +- .../clientpositive/llap/limit_pushdown.q.out | 9 +- .../results/clientpositive/llap/lineage3.q.out | 2 +- .../llap/table_access_keys_stats.q.out | 6 +- .../llap/tez_dynpart_hashjoin_1.q.out | 42 +- .../llap/tez_vector_dynpart_hashjoin_1.q.out | 42 +- .../offset_limit_ppd_optimizer.q.out | 12 +- .../results/clientpositive/perf/query75.q.out | 12 +- .../spark/groupby_sort_1_23.q.out | 32 +- .../spark/groupby_sort_skew_1_23.q.out | 32 +- .../clientpositive/spark/limit_pushdown.q.out | 9 +- .../spark/table_access_keys_stats.q.out | 6 +- .../clientpositive/tez/explainanalyze_4.q.out | 32 +- .../tez/vectorization_limit.q.out | 9 +- .../clientpositive/vectorization_limit.q.out | 12 +- 76 files changed, 1136 insertions(+), 4669 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/hive/blob/b597ab2a/druid-handler/src/java/org/apache/hadoop/hive/druid/HiveDruidQueryBasedInputFormat.java ---------------------------------------------------------------------- diff --git a/druid-handler/src/java/org/apache/hadoop/hive/druid/HiveDruidQueryBasedInputFormat.java b/druid-handler/src/java/org/apache/hadoop/hive/druid/HiveDruidQueryBasedInputFormat.java index 3df1452..a18e590 100644 --- a/druid-handler/src/java/org/apache/hadoop/hive/druid/HiveDruidQueryBasedInputFormat.java +++ b/druid-handler/src/java/org/apache/hadoop/hive/druid/HiveDruidQueryBasedInputFormat.java @@ -25,6 +25,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.calcite.adapter.druid.DruidDateTimeUtils; +import org.apache.calcite.adapter.druid.DruidTable; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.conf.Configuration; @@ -37,8 +39,6 @@ import org.apache.hadoop.hive.druid.serde.DruidSelectQueryRecordReader; import org.apache.hadoop.hive.druid.serde.DruidTimeseriesQueryRecordReader; import org.apache.hadoop.hive.druid.serde.DruidTopNQueryRecordReader; import org.apache.hadoop.hive.druid.serde.DruidWritable; -import org.apache.hadoop.hive.ql.optimizer.calcite.druid.DruidIntervalUtils; -import org.apache.hadoop.hive.ql.optimizer.calcite.druid.DruidTable; import org.apache.hadoop.hive.shims.ShimLoader; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.mapred.JobConf; @@ -273,7 +273,7 @@ public class HiveDruidQueryBasedInputFormat extends InputFormat<NullWritable, Dr } private static List<List<Interval>> createSplitsIntervals(List<Interval> intervals, int numSplits) { - final long totalTime = DruidIntervalUtils.extractTotalTime(intervals); + final long totalTime = DruidDateTimeUtils.extractTotalTime(intervals); long startTime = intervals.get(0).getStartMillis(); long endTime = startTime; long currTime = 0; http://git-wip-us.apache.org/repos/asf/hive/blob/b597ab2a/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidGroupByQueryRecordReader.java ---------------------------------------------------------------------- diff --git a/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidGroupByQueryRecordReader.java b/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidGroupByQueryRecordReader.java index 226060f..49e096b 100644 --- a/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidGroupByQueryRecordReader.java +++ b/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidGroupByQueryRecordReader.java @@ -21,9 +21,9 @@ import java.io.IOException; import java.io.InputStream; import java.util.List; +import org.apache.calcite.adapter.druid.DruidTable; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hive.druid.DruidStorageHandlerUtils; -import org.apache.hadoop.hive.ql.optimizer.calcite.druid.DruidTable; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.mapreduce.InputSplit; http://git-wip-us.apache.org/repos/asf/hive/blob/b597ab2a/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidSelectQueryRecordReader.java ---------------------------------------------------------------------- diff --git a/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidSelectQueryRecordReader.java b/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidSelectQueryRecordReader.java index 70b493c..fccf7c4 100644 --- a/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidSelectQueryRecordReader.java +++ b/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidSelectQueryRecordReader.java @@ -22,8 +22,8 @@ import java.io.InputStream; import java.util.Iterator; import java.util.List; +import org.apache.calcite.adapter.druid.DruidTable; import org.apache.hadoop.hive.druid.DruidStorageHandlerUtils; -import org.apache.hadoop.hive.ql.optimizer.calcite.druid.DruidTable; import org.apache.hadoop.io.NullWritable; import com.fasterxml.jackson.core.type.TypeReference; http://git-wip-us.apache.org/repos/asf/hive/blob/b597ab2a/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidSerDe.java ---------------------------------------------------------------------- diff --git a/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidSerDe.java b/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidSerDe.java index 8f53d4a..238f7a3 100644 --- a/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidSerDe.java +++ b/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidSerDe.java @@ -25,11 +25,11 @@ import java.util.List; import java.util.Map.Entry; import java.util.Properties; +import org.apache.calcite.adapter.druid.DruidTable; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hive.conf.Constants; import org.apache.hadoop.hive.conf.HiveConf; import org.apache.hadoop.hive.druid.DruidStorageHandlerUtils; -import org.apache.hadoop.hive.ql.optimizer.calcite.druid.DruidTable; import org.apache.hadoop.hive.serde2.AbstractSerDe; import org.apache.hadoop.hive.serde2.SerDeException; import org.apache.hadoop.hive.serde2.SerDeSpec; http://git-wip-us.apache.org/repos/asf/hive/blob/b597ab2a/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidTimeseriesQueryRecordReader.java ---------------------------------------------------------------------- diff --git a/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidTimeseriesQueryRecordReader.java b/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidTimeseriesQueryRecordReader.java index 812ae03..b91178c 100644 --- a/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidTimeseriesQueryRecordReader.java +++ b/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidTimeseriesQueryRecordReader.java @@ -21,8 +21,8 @@ import java.io.IOException; import java.io.InputStream; import java.util.List; +import org.apache.calcite.adapter.druid.DruidTable; import org.apache.hadoop.hive.druid.DruidStorageHandlerUtils; -import org.apache.hadoop.hive.ql.optimizer.calcite.druid.DruidTable; import org.apache.hadoop.io.NullWritable; import com.fasterxml.jackson.core.type.TypeReference; http://git-wip-us.apache.org/repos/asf/hive/blob/b597ab2a/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidTopNQueryRecordReader.java ---------------------------------------------------------------------- diff --git a/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidTopNQueryRecordReader.java b/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidTopNQueryRecordReader.java index 0b87976..0b77a9b 100644 --- a/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidTopNQueryRecordReader.java +++ b/druid-handler/src/java/org/apache/hadoop/hive/druid/serde/DruidTopNQueryRecordReader.java @@ -22,8 +22,8 @@ import java.io.InputStream; import java.util.Iterator; import java.util.List; +import org.apache.calcite.adapter.druid.DruidTable; import org.apache.hadoop.hive.druid.DruidStorageHandlerUtils; -import org.apache.hadoop.hive.ql.optimizer.calcite.druid.DruidTable; import org.apache.hadoop.io.NullWritable; import com.fasterxml.jackson.core.type.TypeReference; http://git-wip-us.apache.org/repos/asf/hive/blob/b597ab2a/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 5d13344..98d2dc2 100644 --- a/pom.xml +++ b/pom.xml @@ -112,9 +112,10 @@ <antlr.version>3.4</antlr.version> <apache-directory-server.version>1.5.6</apache-directory-server.version> <apache-directory-clientapi.version>0.1</apache-directory-clientapi.version> + <avatica.version>1.8.0</avatica.version> <avro.version>1.7.7</avro.version> <bonecp.version>0.8.0.RELEASE</bonecp.version> - <calcite.version>1.6.0</calcite.version> + <calcite.version>1.10.0</calcite.version> <datanucleus-api-jdo.version>4.2.1</datanucleus-api-jdo.version> <datanucleus-core.version>4.1.6</datanucleus-core.version> <datanucleus-rdbms.version>4.1.7</datanucleus-rdbms.version> http://git-wip-us.apache.org/repos/asf/hive/blob/b597ab2a/ql/pom.xml ---------------------------------------------------------------------- diff --git a/ql/pom.xml b/ql/pom.xml index 2a93bb7..489c6f3 100644 --- a/ql/pom.xml +++ b/ql/pom.xml @@ -383,8 +383,13 @@ </dependency> <dependency> <groupId>org.apache.calcite</groupId> - <artifactId>calcite-avatica</artifactId> + <artifactId>calcite-druid</artifactId> <version>${calcite.version}</version> + </dependency> + <dependency> + <groupId>org.apache.calcite.avatica</groupId> + <artifactId>avatica</artifactId> + <version>${avatica.version}</version> <exclusions> <!-- hsqldb interferes with the use of derby as the default db in hive's use of datanucleus. http://git-wip-us.apache.org/repos/asf/hive/blob/b597ab2a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveDefaultRelMetadataProvider.java ---------------------------------------------------------------------- diff --git a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveDefaultRelMetadataProvider.java b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveDefaultRelMetadataProvider.java index c0609d7..75fb916 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveDefaultRelMetadataProvider.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveDefaultRelMetadataProvider.java @@ -77,7 +77,7 @@ public class HiveDefaultRelMetadataProvider { HiveRelMdDistribution.SOURCE, HiveRelMdCollation.SOURCE, HiveRelMdPredicates.SOURCE, - new DefaultRelMetadataProvider())); + DefaultRelMetadataProvider.INSTANCE)); } } http://git-wip-us.apache.org/repos/asf/hive/blob/b597ab2a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HivePlannerContext.java ---------------------------------------------------------------------- diff --git a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HivePlannerContext.java b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HivePlannerContext.java index 890aea1..8beb0dd 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HivePlannerContext.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HivePlannerContext.java @@ -19,19 +19,15 @@ package org.apache.hadoop.hive.ql.optimizer.calcite; import org.apache.calcite.plan.Context; import org.apache.hadoop.hive.ql.optimizer.calcite.cost.HiveAlgorithmsConf; -import org.apache.hadoop.hive.ql.optimizer.calcite.druid.HiveDruidConf; import org.apache.hadoop.hive.ql.optimizer.calcite.rules.HiveRulesRegistry; public class HivePlannerContext implements Context { private HiveAlgorithmsConf algoConfig; - private HiveDruidConf druidConf; private HiveRulesRegistry registry; - public HivePlannerContext(HiveAlgorithmsConf algoConfig, HiveDruidConf druidConf, - HiveRulesRegistry registry) { + public HivePlannerContext(HiveAlgorithmsConf algoConfig, HiveRulesRegistry registry) { this.algoConfig = algoConfig; - this.druidConf = druidConf; this.registry = registry; } @@ -39,9 +35,6 @@ public class HivePlannerContext implements Context { if (clazz.isInstance(algoConfig)) { return clazz.cast(algoConfig); } - if (clazz.isInstance(druidConf)) { - return clazz.cast(druidConf); - } if (clazz.isInstance(registry)) { return clazz.cast(registry); } http://git-wip-us.apache.org/repos/asf/hive/blob/b597ab2a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveRelBuilder.java ---------------------------------------------------------------------- diff --git a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveRelBuilder.java b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveRelBuilder.java index 1c64d64..bc160d8 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveRelBuilder.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveRelBuilder.java @@ -81,7 +81,7 @@ public class HiveRelBuilder extends RelBuilder { @Override public RelBuilder filter(Iterable<? extends RexNode> predicates) { - final RexNode x = HiveRexUtil.simplify(cluster.getRexBuilder(), + final RexNode x = RexUtil.simplify(cluster.getRexBuilder(), RexUtil.composeConjunction(cluster.getRexBuilder(), predicates, false)); if (!x.isAlwaysTrue()) { final RelNode input = build(); @@ -91,4 +91,20 @@ public class HiveRelBuilder extends RelBuilder { return this; } + /** + * Empty relationship can be expressed in many different ways, e.g., + * filter(cond=false), empty LogicalValues(), etc. Calcite default implementation + * uses empty LogicalValues(); however, currently there is not an equivalent to + * this expression in Hive. Thus, we use limit 0, since Hive already includes + * optimizations that will do early pruning of the result tree when it is found, + * e.g., GlobalLimitOptimizer. + */ + @Override + public RelBuilder empty() { + final RelNode input = build(); + final RelNode sort = HiveRelFactories.HIVE_SORT_FACTORY.createSort( + input, RelCollations.of(), null, literal(0)); + return this.push(sort); + } + } http://git-wip-us.apache.org/repos/asf/hive/blob/b597ab2a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveRelOptUtil.java ---------------------------------------------------------------------- diff --git a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveRelOptUtil.java b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveRelOptUtil.java index 4c154d0..50fbb78 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveRelOptUtil.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveRelOptUtil.java @@ -24,7 +24,6 @@ import java.util.List; import org.apache.calcite.plan.RelOptCluster; import org.apache.calcite.plan.RelOptUtil; import org.apache.calcite.rel.RelNode; -import org.apache.calcite.rel.core.RelFactories; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeField; import org.apache.calcite.rex.RexBuilder; @@ -34,6 +33,7 @@ import org.apache.calcite.rex.RexUtil; import org.apache.calcite.sql.SqlKind; import org.apache.calcite.sql.SqlOperator; import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.apache.calcite.tools.RelBuilder; import org.apache.calcite.util.ImmutableBitSet; import org.apache.hadoop.hive.ql.exec.FunctionRegistry; import org.apache.hadoop.hive.ql.optimizer.calcite.translator.TypeConverter; @@ -314,12 +314,12 @@ public class HiveRelOptUtil extends RelOptUtil { * * <p>Optimizes if the fields are the identity projection. * - * @param factory ProjectFactory + * @param relBuilder RelBuilder * @param child Input relational expression * @param posList Source of each projected field * @return Relational expression that projects given fields */ - public static RelNode createProject(final RelFactories.ProjectFactory factory, + public static RelNode createProject(final RelBuilder relBuilder, final RelNode child, final List<Integer> posList) { RelDataType rowType = child.getRowType(); final List<String> fieldNames = rowType.getFieldNames(); @@ -344,7 +344,7 @@ public class HiveRelOptUtil extends RelOptUtil { final int pos = posList.get(index); return fieldNames.get(pos); } - }, true, factory); + }, true, relBuilder); } } http://git-wip-us.apache.org/repos/asf/hive/blob/b597ab2a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveRexUtil.java ---------------------------------------------------------------------- diff --git a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveRexUtil.java b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveRexUtil.java deleted file mode 100644 index 15707c1..0000000 --- a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveRexUtil.java +++ /dev/null @@ -1,821 +0,0 @@ -/* - * 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.hadoop.hive.ql.optimizer.calcite; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.apache.calcite.linq4j.Ord; -import org.apache.calcite.plan.RelOptUtil; -import org.apache.calcite.rex.RexBuilder; -import org.apache.calcite.rex.RexCall; -import org.apache.calcite.rex.RexInputRef; -import org.apache.calcite.rex.RexLiteral; -import org.apache.calcite.rex.RexNode; -import org.apache.calcite.rex.RexShuttle; -import org.apache.calcite.rex.RexUtil; -import org.apache.calcite.sql.SqlKind; -import org.apache.calcite.sql.SqlOperator; -import org.apache.calcite.sql.fun.SqlStdOperatorTable; -import org.apache.calcite.sql.type.SqlTypeName; -import org.apache.calcite.util.ControlFlowException; -import org.apache.calcite.util.Pair; -import org.apache.calcite.util.Util; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import com.google.common.collect.Multimap; - - -public class HiveRexUtil { - - protected static final Logger LOG = LoggerFactory.getLogger(HiveRexUtil.class); - - - /** Converts an expression to conjunctive normal form (CNF). - * - * <p>The following expression is in CNF: - * - * <blockquote>(a OR b) AND (c OR d)</blockquote> - * - * <p>The following expression is not in CNF: - * - * <blockquote>(a AND b) OR c</blockquote> - * - * but can be converted to CNF: - * - * <blockquote>(a OR c) AND (b OR c)</blockquote> - * - * <p>The following expression is not in CNF: - * - * <blockquote>NOT (a OR NOT b)</blockquote> - * - * but can be converted to CNF by applying de Morgan's theorem: - * - * <blockquote>NOT a AND b</blockquote> - * - * <p>Expressions not involving AND, OR or NOT at the top level are in CNF. - */ - public static RexNode toCnf(RexBuilder rexBuilder, RexNode rex) { - return new CnfHelper(rexBuilder).toCnf(rex); - } - - public static RexNode toCnf(RexBuilder rexBuilder, int maxCNFNodeCount, RexNode rex) { - return new CnfHelper(rexBuilder, maxCNFNodeCount).toCnf(rex); - } - - /** Helps {@link org.apache.calcite.rex.RexUtil#toCnf}. */ - private static class CnfHelper { - final RexBuilder rexBuilder; - int currentCount; - final int maxNodeCount; - - private CnfHelper(RexBuilder rexBuilder) { - this(rexBuilder, Integer.MAX_VALUE); - } - - private CnfHelper(RexBuilder rexBuilder, int maxNodeCount) { - this.rexBuilder = rexBuilder; - this.maxNodeCount = maxNodeCount == -1 ? Integer.MAX_VALUE : maxNodeCount; - } - - public RexNode toCnf(RexNode rex) { - try { - this.currentCount = 0; - return toCnf2(rex); - } catch (OverflowError e) { - if (LOG.isDebugEnabled()) { - LOG.debug("Transformation to CNF not carried out as number of resulting nodes " - + "in expression is greater than the max number of nodes allowed"); - } - Util.swallow(e, null); - return rex; - } - } - - private RexNode toCnf2(RexNode rex) { - final List<RexNode> operands; - switch (rex.getKind()) { - case AND: - incrementAndCheck(); - operands = RexUtil.flattenAnd(((RexCall) rex).getOperands()); - final List<RexNode> cnfOperands = Lists.newArrayList(); - for (RexNode node : operands) { - RexNode cnf = toCnf2(node); - switch (cnf.getKind()) { - case AND: - incrementAndCheck(); - cnfOperands.addAll(((RexCall) cnf).getOperands()); - break; - default: - incrementAndCheck(); - cnfOperands.add(cnf); - } - } - return and(cnfOperands); - case OR: - incrementAndCheck(); - operands = RexUtil.flattenOr(((RexCall) rex).getOperands()); - final RexNode head = operands.get(0); - final RexNode headCnf = toCnf2(head); - final List<RexNode> headCnfs = RelOptUtil.conjunctions(headCnf); - final RexNode tail = or(Util.skip(operands)); - final RexNode tailCnf = toCnf2(tail); - final List<RexNode> tailCnfs = RelOptUtil.conjunctions(tailCnf); - final List<RexNode> list = Lists.newArrayList(); - for (RexNode h : headCnfs) { - for (RexNode t : tailCnfs) { - list.add(or(ImmutableList.of(h, t))); - } - } - return and(list); - case NOT: - final RexNode arg = ((RexCall) rex).getOperands().get(0); - switch (arg.getKind()) { - case NOT: - return toCnf2(((RexCall) arg).getOperands().get(0)); - case OR: - operands = ((RexCall) arg).getOperands(); - List<RexNode> transformedDisj = new ArrayList<>(); - for (RexNode input : RexUtil.flattenOr(operands)) { - transformedDisj.add(rexBuilder.makeCall(input.getType(), SqlStdOperatorTable.NOT, - ImmutableList.of(input))); - } - return toCnf2(and(transformedDisj)); - case AND: - operands = ((RexCall) arg).getOperands(); - List<RexNode> transformedConj = new ArrayList<>(); - for (RexNode input : RexUtil.flattenAnd(operands)) { - transformedConj.add(rexBuilder.makeCall(input.getType(), SqlStdOperatorTable.NOT, - ImmutableList.of(input))); - } - return toCnf2(or(transformedConj)); - default: - incrementAndCheck(); - return rex; - } - default: - incrementAndCheck(); - return rex; - } - } - - private RexNode and(Iterable<? extends RexNode> nodes) { - return RexUtil.composeConjunction(rexBuilder, nodes, false); - } - - private RexNode or(Iterable<? extends RexNode> nodes) { - return RexUtil.composeDisjunction(rexBuilder, nodes, false); - } - - private void incrementAndCheck() { - this.currentCount++; - if (this.currentCount > this.maxNodeCount) { - throw OverflowError.INSTANCE; - } - } - - @SuppressWarnings("serial") - private static class OverflowError extends ControlFlowException { - - public static final OverflowError INSTANCE = new OverflowError(); - - private OverflowError() {} - } - } - - - /** - * Simplifies a boolean expression. - * - * <p>In particular:</p> - * <ul> - * <li>{@code simplify(x = 1 AND y = 2 AND NOT x = 1)} - * returns {@code y = 2}</li> - * <li>{@code simplify(x = 1 AND FALSE)} - * returns {@code FALSE}</li> - * </ul> - */ - public static RexNode simplify(RexBuilder rexBuilder, RexNode e) { - return simplify(rexBuilder, e, false); - } - - public static RexNode simplify(RexBuilder rexBuilder, RexNode e, - boolean unknownAsFalse) { - switch (e.getKind()) { - case AND: - return simplifyAnd(rexBuilder, (RexCall) e, unknownAsFalse); - case OR: - return simplifyOr(rexBuilder, (RexCall) e); - case NOT: - return simplifyNot(rexBuilder, (RexCall) e); - case CASE: - return simplifyCase(rexBuilder, (RexCall) e, unknownAsFalse); - case IS_NULL: - return ((RexCall) e).getOperands().get(0).getType().isNullable() - ? e : rexBuilder.makeLiteral(false); - case IS_NOT_NULL: - return ((RexCall) e).getOperands().get(0).getType().isNullable() - ? e : rexBuilder.makeLiteral(true); - default: - return e; - } - } - - private static RexNode simplifyNot(RexBuilder rexBuilder, RexCall call) { - final RexNode a = call.getOperands().get(0); - switch (a.getKind()) { - case NOT: - // NOT NOT x ==> x - return simplify(rexBuilder, ((RexCall) a).getOperands().get(0)); - } - final SqlKind negateKind = a.getKind().negate(); - if (a.getKind() != negateKind) { - return simplify(rexBuilder, - rexBuilder.makeCall(op(negateKind), - ImmutableList.of(((RexCall) a).getOperands().get(0)))); - } - final SqlKind negateKind2 = negate(a.getKind()); - if (a.getKind() != negateKind2) { - return simplify(rexBuilder, - rexBuilder.makeCall(op(negateKind2), ((RexCall) a).getOperands())); - } - if (a.getKind() == SqlKind.AND) { - // NOT distributivity for AND - final List<RexNode> newOperands = new ArrayList<>(); - for (RexNode operand : ((RexCall) a).getOperands()) { - newOperands.add(simplify(rexBuilder, - rexBuilder.makeCall(SqlStdOperatorTable.NOT, operand))); - } - return simplify(rexBuilder, - rexBuilder.makeCall(SqlStdOperatorTable.OR, newOperands)); - } - if (a.getKind() == SqlKind.OR) { - // NOT distributivity for OR - final List<RexNode> newOperands = new ArrayList<>(); - for (RexNode operand : ((RexCall) a).getOperands()) { - newOperands.add(simplify(rexBuilder, - rexBuilder.makeCall(SqlStdOperatorTable.NOT, operand))); - } - return simplify(rexBuilder, - rexBuilder.makeCall(SqlStdOperatorTable.AND, newOperands)); - } - return call; - } - - private static RexNode simplifyCase(RexBuilder rexBuilder, RexCall call, - boolean unknownAsFalse) { - final List<RexNode> operands = call.getOperands(); - final List<RexNode> newOperands = new ArrayList<>(); - final Set<String> values = new HashSet<>(); - for (int i = 0; i < operands.size(); i++) { - RexNode operand = operands.get(i); - if (RexUtil.isCasePredicate(call, i)) { - if (operand.isAlwaysTrue()) { - // Predicate is always TRUE. Make value the ELSE and quit. - newOperands.add(operands.get(i + 1)); - if (unknownAsFalse && RexUtil.isNull(operands.get(i + 1))) { - values.add(rexBuilder.makeLiteral(false).toString()); - } else { - values.add(operands.get(i + 1).toString()); - } - break; - } else if (operand.isAlwaysFalse() || RexUtil.isNull(operand)) { - // Predicate is always FALSE or NULL. Skip predicate and value. - ++i; - continue; - } - } else { - if (unknownAsFalse && RexUtil.isNull(operand)) { - values.add(rexBuilder.makeLiteral(false).toString()); - } else { - values.add(operand.toString()); - } - } - newOperands.add(operand); - } - assert newOperands.size() % 2 == 1; - if (newOperands.size() == 1 || values.size() == 1) { - return rexBuilder.makeCast(call.getType(), newOperands.get(newOperands.size() - 1)); - } - trueFalse: - if (call.getType().getSqlTypeName() == SqlTypeName.BOOLEAN) { - // Optimize CASE where every branch returns constant true or constant - // false. - final List<Pair<RexNode, RexNode>> pairs = - casePairs(rexBuilder, newOperands); - // 1) Possible simplification if unknown is treated as false: - // CASE - // WHEN p1 THEN TRUE - // WHEN p2 THEN TRUE - // ELSE FALSE - // END - // can be rewritten to: (p1 or p2) - if (unknownAsFalse) { - final List<RexNode> terms = new ArrayList<>(); - int pos = 0; - for (; pos < pairs.size(); pos++) { - // True block - Pair<RexNode, RexNode> pair = pairs.get(pos); - if (!pair.getValue().isAlwaysTrue()) { - break; - } - terms.add(pair.getKey()); - } - for (; pos < pairs.size(); pos++) { - // False block - Pair<RexNode, RexNode> pair = pairs.get(pos); - if (!pair.getValue().isAlwaysFalse() && !RexUtil.isNull(pair.getValue())) { - break; - } - } - if (pos == pairs.size()) { - return RexUtil.composeDisjunction(rexBuilder, terms, false); - } - } - // 2) Another simplification - // CASE - // WHEN p1 THEN TRUE - // WHEN p2 THEN FALSE - // WHEN p3 THEN TRUE - // ELSE FALSE - // END - // if p1...pn cannot be nullable - for (Ord<Pair<RexNode, RexNode>> pair : Ord.zip(pairs)) { - if (pair.e.getKey().getType().isNullable()) { - break trueFalse; - } - if (!pair.e.getValue().isAlwaysTrue() - && !pair.e.getValue().isAlwaysFalse() - && (!unknownAsFalse || !RexUtil.isNull(pair.e.getValue()))) { - break trueFalse; - } - } - final List<RexNode> terms = new ArrayList<>(); - final List<RexNode> notTerms = new ArrayList<>(); - for (Ord<Pair<RexNode, RexNode>> pair : Ord.zip(pairs)) { - if (pair.e.getValue().isAlwaysTrue()) { - terms.add(RexUtil.andNot(rexBuilder, pair.e.getKey(), notTerms)); - } else { - notTerms.add(pair.e.getKey()); - } - } - return RexUtil.composeDisjunction(rexBuilder, terms, false); - } - if (newOperands.equals(operands)) { - return call; - } - return call.clone(call.getType(), newOperands); - } - - /** Given "CASE WHEN p1 THEN v1 ... ELSE e END" - * returns [(p1, v1), ..., (true, e)]. */ - private static List<Pair<RexNode, RexNode>> casePairs(RexBuilder rexBuilder, - List<RexNode> operands) { - final ImmutableList.Builder<Pair<RexNode, RexNode>> builder = - ImmutableList.builder(); - for (int i = 0; i < operands.size() - 1; i += 2) { - builder.add(Pair.of(operands.get(i), operands.get(i + 1))); - } - builder.add( - Pair.of((RexNode) rexBuilder.makeLiteral(true), Util.last(operands))); - return builder.build(); - } - - public static RexNode simplifyAnd(RexBuilder rexBuilder, RexCall e, - boolean unknownAsFalse) { - final List<RexNode> terms = new ArrayList<>(); - final List<RexNode> notTerms = new ArrayList<>(); - RelOptUtil.decomposeConjunction(e, terms, notTerms); - if (unknownAsFalse) { - return simplifyAnd2ForUnknownAsFalse(rexBuilder, terms, notTerms); - } - return simplifyAnd2(rexBuilder, terms, notTerms); - } - - public static RexNode simplifyAnd2(RexBuilder rexBuilder, - List<RexNode> terms, List<RexNode> notTerms) { - for (RexNode term : terms) { - if (term.isAlwaysFalse()) { - return rexBuilder.makeLiteral(false); - } - } - if (terms.isEmpty() && notTerms.isEmpty()) { - return rexBuilder.makeLiteral(true); - } - if (terms.size() == 1 && notTerms.isEmpty()) { - // Make sure "x OR y OR x" (a single-term conjunction) gets simplified. - return simplify(rexBuilder, terms.get(0)); - } - // If one of the not-disjunctions is a disjunction that is wholly - // contained in the disjunctions list, the expression is not - // satisfiable. - // - // Example #1. x AND y AND z AND NOT (x AND y) - not satisfiable - // Example #2. x AND y AND NOT (x AND y) - not satisfiable - // Example #3. x AND y AND NOT (x AND y AND z) - may be satisfiable - for (RexNode notDisjunction : notTerms) { - final List<RexNode> terms2 = RelOptUtil.conjunctions(notDisjunction); - if (terms.containsAll(terms2)) { - return rexBuilder.makeLiteral(false); - } - } - // Add the NOT disjunctions back in. - for (RexNode notDisjunction : notTerms) { - terms.add( - simplify(rexBuilder, - rexBuilder.makeCall(SqlStdOperatorTable.NOT, notDisjunction))); - } - return RexUtil.composeConjunction(rexBuilder, terms, false); - } - - /** As {@link #simplifyAnd2(RexBuilder, List, List)} but we assume that if the expression returns - * UNKNOWN it will be interpreted as FALSE. */ - public static RexNode simplifyAnd2ForUnknownAsFalse(RexBuilder rexBuilder, - List<RexNode> terms, List<RexNode> notTerms) { - for (RexNode term : terms) { - if (term.isAlwaysFalse()) { - return rexBuilder.makeLiteral(false); - } - } - if (terms.isEmpty() && notTerms.isEmpty()) { - return rexBuilder.makeLiteral(true); - } - if (terms.size() == 1 && notTerms.isEmpty()) { - // Make sure "x OR y OR x" (a single-term conjunction) gets simplified. - return simplify(rexBuilder, terms.get(0), true); - } - // Try to simplify the expression - final Multimap<String,Pair<String,RexNode>> equalityTerms = ArrayListMultimap.create(); - final Map<String,String> equalityConstantTerms = new HashMap<>(); - final Set<String> negatedTerms = new HashSet<>(); - final Set<String> nullOperands = new HashSet<>(); - final Set<RexNode> notNullOperands = new LinkedHashSet<>(); - final Set<String> comparedOperands = new HashSet<>(); - for (int i = 0; i < terms.size(); i++) { - RexNode term = terms.get(i); - if (!HiveCalciteUtil.isDeterministic(term)) { - continue; - } - // Simplify BOOLEAN expressions if possible - while (term.getKind() == SqlKind.EQUALS) { - RexCall call = (RexCall) term; - if (call.getOperands().get(0).isAlwaysTrue()) { - term = call.getOperands().get(1); - terms.remove(i); - terms.add(i, term); - continue; - } else if (call.getOperands().get(1).isAlwaysTrue()) { - term = call.getOperands().get(0); - terms.remove(i); - terms.add(i, term); - continue; - } - break; - } - switch (term.getKind()) { - case EQUALS: - case NOT_EQUALS: - case LESS_THAN: - case GREATER_THAN: - case LESS_THAN_OR_EQUAL: - case GREATER_THAN_OR_EQUAL: - RexCall call = (RexCall) term; - RexNode left = call.getOperands().get(0); - comparedOperands.add(left.toString()); - RexCall leftCast = null; - // if it is a cast, we include the inner reference - if (left.getKind() == SqlKind.CAST) { - leftCast = (RexCall) left; - comparedOperands.add(leftCast.getOperands().get(0).toString()); - } - RexNode right = call.getOperands().get(1); - comparedOperands.add(right.toString()); - RexCall rightCast = null; - // if it is a cast, we include the inner reference - if (right.getKind() == SqlKind.CAST) { - rightCast = (RexCall) right; - comparedOperands.add(rightCast.getOperands().get(0).toString()); - } - // Check for equality on different constants. If the same ref or CAST(ref) - // is equal to different constants, this condition cannot be satisfied, - // and hence it can be evaluated to FALSE - if (term.getKind() == SqlKind.EQUALS) { - boolean leftRef = left instanceof RexInputRef || - (leftCast != null && leftCast.getOperands().get(0) instanceof RexInputRef); - boolean rightRef = right instanceof RexInputRef || - (rightCast != null && rightCast.getOperands().get(0) instanceof RexInputRef); - if (right instanceof RexLiteral && leftRef) { - final String literal = right.toString(); - final String prevLiteral = equalityConstantTerms.put(left.toString(), literal); - if (prevLiteral != null && !literal.equals(prevLiteral)) { - return rexBuilder.makeLiteral(false); - } - } else if (left instanceof RexLiteral && rightRef) { - final String literal = left.toString(); - final String prevLiteral = equalityConstantTerms.put(right.toString(), literal); - if (prevLiteral != null && !literal.equals(prevLiteral)) { - return rexBuilder.makeLiteral(false); - } - } else if (leftRef && rightRef) { - equalityTerms.put(left.toString(), Pair.of(right.toString(), term)); - } - } - // Assume the expression a > 5 is part of a Filter condition. - // Then we can derive the negated term: a <= 5. - // But as the comparison is string based and thus operands order dependent, - // we should also add the inverted negated term: 5 >= a. - // Observe that for creating the inverted term we invert the list of operands. - RexNode negatedTerm = negate(rexBuilder, call); - if (negatedTerm != null) { - negatedTerms.add(negatedTerm.toString()); - RexNode invertNegatedTerm = invert(rexBuilder, (RexCall) negatedTerm); - if (invertNegatedTerm != null) { - negatedTerms.add(invertNegatedTerm.toString()); - } - } - break; - case IN: - comparedOperands.add(((RexCall) term).operands.get(0).toString()); - break; - case BETWEEN: - comparedOperands.add(((RexCall) term).operands.get(1).toString()); - break; - case IS_NOT_NULL: - notNullOperands.add(((RexCall) term).getOperands().get(0)); - terms.remove(i); - --i; - break; - case IS_NULL: - nullOperands.add(((RexCall) term).getOperands().get(0).toString()); - } - } - // If one column should be null and is in a comparison predicate, - // it is not satisfiable. - // Example. IS NULL(x) AND x < 5 - not satisfiable - if (!Collections.disjoint(nullOperands, comparedOperands)) { - return rexBuilder.makeLiteral(false); - } - // Check for equality of two refs wrt equality with constants - // Example #1. x=5 AND y=5 AND x=y : x=5 AND y=5 - // Example #2. x=5 AND y=6 AND x=y - not satisfiable - for (String ref1 : equalityTerms.keySet()) { - final String literal1 = equalityConstantTerms.get(ref1); - if (literal1 == null) { - continue; - } - Collection<Pair<String, RexNode>> references = equalityTerms.get(ref1); - for (Pair<String,RexNode> ref2 : references) { - final String literal2 = equalityConstantTerms.get(ref2.left); - if (literal2 == null) { - continue; - } - if (!literal1.equals(literal2)) { - // If an expression is equal to two different constants, - // it is not satisfiable - return rexBuilder.makeLiteral(false); - } - // Otherwise we can remove the term, as we already know that - // the expression is equal to two constants - terms.remove(ref2.right); - } - } - // Remove not necessary IS NOT NULL expressions. - // - // Example. IS NOT NULL(x) AND x < 5 : x < 5 - for (RexNode operand : notNullOperands) { - if (!comparedOperands.contains(operand.toString())) { - terms.add( - rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, operand)); - } - } - // If one of the not-disjunctions is a disjunction that is wholly - // contained in the disjunctions list, the expression is not - // satisfiable. - // - // Example #1. x AND y AND z AND NOT (x AND y) - not satisfiable - // Example #2. x AND y AND NOT (x AND y) - not satisfiable - // Example #3. x AND y AND NOT (x AND y AND z) - may be satisfiable - final Set<String> termsSet = new HashSet<String>( - Lists.transform(terms, HiveCalciteUtil.REX_STR_FN)); - for (RexNode notDisjunction : notTerms) { - if (!HiveCalciteUtil.isDeterministic(notDisjunction)) { - continue; - } - final List<String> terms2Set = Lists.transform( - RelOptUtil.conjunctions(notDisjunction), HiveCalciteUtil.REX_STR_FN); - if (termsSet.containsAll(terms2Set)) { - return rexBuilder.makeLiteral(false); - } - } - // Add the NOT disjunctions back in. - for (RexNode notDisjunction : notTerms) { - terms.add( - simplify(rexBuilder, - rexBuilder.makeCall(SqlStdOperatorTable.NOT, notDisjunction), true)); - } - // The negated terms: only deterministic expressions - for (String negatedTerm : negatedTerms) { - if (termsSet.contains(negatedTerm)) { - return rexBuilder.makeLiteral(false); - } - } - return RexUtil.composeConjunction(rexBuilder, terms, false); - } - - /** Simplifies OR(x, x) into x, and similar. */ - public static RexNode simplifyOr(RexBuilder rexBuilder, RexCall call) { - assert call.getKind() == SqlKind.OR; - final List<RexNode> terms = RelOptUtil.disjunctions(call); - for (int i = 0; i < terms.size(); i++) { - final RexNode term = terms.get(i); - switch (term.getKind()) { - case LITERAL: - if (!RexLiteral.isNullLiteral(term)) { - if (RexLiteral.booleanValue(term)) { - return term; // true - } else { - terms.remove(i); - --i; - } - } - } - } - return RexUtil.composeDisjunction(rexBuilder, terms, false); - } - - private static RexCall negate(RexBuilder rexBuilder, RexCall call) { - switch (call.getKind()) { - case EQUALS: - case NOT_EQUALS: - case LESS_THAN: - case GREATER_THAN: - case LESS_THAN_OR_EQUAL: - case GREATER_THAN_OR_EQUAL: - return (RexCall) rexBuilder.makeCall(op(negate(call.getKind())), call.getOperands()); - } - return null; - } - - private static SqlKind negate(SqlKind kind) { - switch (kind) { - case EQUALS: - return SqlKind.NOT_EQUALS; - case NOT_EQUALS: - return SqlKind.EQUALS; - case LESS_THAN: - return SqlKind.GREATER_THAN_OR_EQUAL; - case GREATER_THAN: - return SqlKind.LESS_THAN_OR_EQUAL; - case LESS_THAN_OR_EQUAL: - return SqlKind.GREATER_THAN; - case GREATER_THAN_OR_EQUAL: - return SqlKind.LESS_THAN; - } - return kind; - } - - private static RexCall invert(RexBuilder rexBuilder, RexCall call) { - switch (call.getKind()) { - case EQUALS: - return (RexCall) rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, - Lists.reverse(call.getOperands())); - case NOT_EQUALS: - return (RexCall) rexBuilder.makeCall(SqlStdOperatorTable.NOT_EQUALS, - Lists.reverse(call.getOperands())); - case LESS_THAN: - return (RexCall) rexBuilder.makeCall(SqlStdOperatorTable.GREATER_THAN, - Lists.reverse(call.getOperands())); - case GREATER_THAN: - return (RexCall) rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN, - Lists.reverse(call.getOperands())); - case LESS_THAN_OR_EQUAL: - return (RexCall) rexBuilder.makeCall(SqlStdOperatorTable.GREATER_THAN_OR_EQUAL, - Lists.reverse(call.getOperands())); - case GREATER_THAN_OR_EQUAL: - return (RexCall) rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN_OR_EQUAL, - Lists.reverse(call.getOperands())); - } - return null; - } - - private static SqlOperator op(SqlKind kind) { - switch (kind) { - case IS_FALSE: - return SqlStdOperatorTable.IS_FALSE; - case IS_TRUE: - return SqlStdOperatorTable.IS_TRUE; - case IS_UNKNOWN: - return SqlStdOperatorTable.IS_UNKNOWN; - case IS_NULL: - return SqlStdOperatorTable.IS_NULL; - case IS_NOT_FALSE: - return SqlStdOperatorTable.IS_NOT_FALSE; - case IS_NOT_TRUE: - return SqlStdOperatorTable.IS_NOT_TRUE; - case IS_NOT_NULL: - return SqlStdOperatorTable.IS_NOT_NULL; - case EQUALS: - return SqlStdOperatorTable.EQUALS; - case NOT_EQUALS: - return SqlStdOperatorTable.NOT_EQUALS; - case LESS_THAN: - return SqlStdOperatorTable.LESS_THAN; - case GREATER_THAN: - return SqlStdOperatorTable.GREATER_THAN; - case LESS_THAN_OR_EQUAL: - return SqlStdOperatorTable.LESS_THAN_OR_EQUAL; - case GREATER_THAN_OR_EQUAL: - return SqlStdOperatorTable.GREATER_THAN_OR_EQUAL; - default: - throw new AssertionError(kind); - } - } - - public static SqlKind invert(SqlKind kind) { - switch (kind) { - case EQUALS: - return SqlKind.EQUALS; - case NOT_EQUALS: - return SqlKind.NOT_EQUALS; - case LESS_THAN: - return SqlKind.GREATER_THAN; - case GREATER_THAN: - return SqlKind.LESS_THAN; - case LESS_THAN_OR_EQUAL: - return SqlKind.GREATER_THAN_OR_EQUAL; - case GREATER_THAN_OR_EQUAL: - return SqlKind.LESS_THAN_OR_EQUAL; - } - return null; - } - - public static class ExprSimplifier extends RexShuttle { - private final RexBuilder rexBuilder; - private final boolean unknownAsFalse; - private final Map<RexNode,Boolean> unknownAsFalseMap; - - public ExprSimplifier(RexBuilder rexBuilder, boolean unknownAsFalse) { - this.rexBuilder = rexBuilder; - this.unknownAsFalse = unknownAsFalse; - this.unknownAsFalseMap = new HashMap<>(); - } - - @Override - public RexNode visitCall(RexCall call) { - Boolean unknownAsFalseCall = unknownAsFalse; - if (unknownAsFalseCall) { - switch (call.getKind()) { - case AND: - case CASE: - unknownAsFalseCall = this.unknownAsFalseMap.get(call); - if (unknownAsFalseCall == null) { - // Top operator - unknownAsFalseCall = true; - } - break; - default: - unknownAsFalseCall = false; - } - for (RexNode operand : call.operands) { - this.unknownAsFalseMap.put(operand, unknownAsFalseCall); - } - } - RexNode node = super.visitCall(call); - RexNode simplifiedNode = HiveRexUtil.simplify(rexBuilder, node, unknownAsFalseCall); - if (node == simplifiedNode) { - return node; - } - if (simplifiedNode.getType().equals(call.getType())) { - return simplifiedNode; - } - return rexBuilder.makeCast(call.getType(), simplifiedNode, true); - } - } - -} http://git-wip-us.apache.org/repos/asf/hive/blob/b597ab2a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveTypeSystemImpl.java ---------------------------------------------------------------------- diff --git a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveTypeSystemImpl.java b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveTypeSystemImpl.java index 10fdcc6..279d101 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveTypeSystemImpl.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/HiveTypeSystemImpl.java @@ -36,8 +36,19 @@ public class HiveTypeSystemImpl extends RelDataTypeSystemImpl { switch (typeName) { case DECIMAL: return getMaxNumericScale(); - case INTERVAL_DAY_TIME: + case INTERVAL_YEAR: + case INTERVAL_MONTH: case INTERVAL_YEAR_MONTH: + case INTERVAL_DAY: + case INTERVAL_DAY_HOUR: + case INTERVAL_DAY_MINUTE: + case INTERVAL_DAY_SECOND: + case INTERVAL_HOUR: + case INTERVAL_HOUR_MINUTE: + case INTERVAL_HOUR_SECOND: + case INTERVAL_MINUTE: + case INTERVAL_MINUTE_SECOND: + case INTERVAL_SECOND: return SqlTypeName.MAX_INTERVAL_FRACTIONAL_SECOND_PRECISION; default: return -1; @@ -58,8 +69,19 @@ public class HiveTypeSystemImpl extends RelDataTypeSystemImpl { return getMaxPrecision(typeName); case DECIMAL: return DEFAULT_DECIMAL_PRECISION; - case INTERVAL_DAY_TIME: + case INTERVAL_YEAR: + case INTERVAL_MONTH: case INTERVAL_YEAR_MONTH: + case INTERVAL_DAY: + case INTERVAL_DAY_HOUR: + case INTERVAL_DAY_MINUTE: + case INTERVAL_DAY_SECOND: + case INTERVAL_HOUR: + case INTERVAL_HOUR_MINUTE: + case INTERVAL_HOUR_SECOND: + case INTERVAL_MINUTE: + case INTERVAL_MINUTE_SECOND: + case INTERVAL_SECOND: return SqlTypeName.DEFAULT_INTERVAL_START_PRECISION; default: return -1; @@ -81,8 +103,19 @@ public class HiveTypeSystemImpl extends RelDataTypeSystemImpl { case TIME: case TIMESTAMP: return MAX_TIMESTAMP_PRECISION; - case INTERVAL_DAY_TIME: + case INTERVAL_YEAR: + case INTERVAL_MONTH: case INTERVAL_YEAR_MONTH: + case INTERVAL_DAY: + case INTERVAL_DAY_HOUR: + case INTERVAL_DAY_MINUTE: + case INTERVAL_DAY_SECOND: + case INTERVAL_HOUR: + case INTERVAL_HOUR_MINUTE: + case INTERVAL_HOUR_SECOND: + case INTERVAL_MINUTE: + case INTERVAL_MINUTE_SECOND: + case INTERVAL_SECOND: return SqlTypeName.MAX_INTERVAL_START_PRECISION; default: return -1; http://git-wip-us.apache.org/repos/asf/hive/blob/b597ab2a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/cost/HiveDefaultCostModel.java ---------------------------------------------------------------------- diff --git a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/cost/HiveDefaultCostModel.java b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/cost/HiveDefaultCostModel.java index badb8ca..40f2cef 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/cost/HiveDefaultCostModel.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/cost/HiveDefaultCostModel.java @@ -20,6 +20,7 @@ package org.apache.hadoop.hive.ql.optimizer.calcite.cost; import org.apache.calcite.plan.RelOptCost; import org.apache.calcite.rel.RelCollation; import org.apache.calcite.rel.RelDistribution; +import org.apache.calcite.rel.RelDistributions; import org.apache.calcite.rel.metadata.RelMetadataQuery; import org.apache.hadoop.hive.ql.optimizer.calcite.reloperators.HiveAggregate; import org.apache.hadoop.hive.ql.optimizer.calcite.reloperators.HiveJoin; @@ -92,12 +93,12 @@ public class HiveDefaultCostModel extends HiveCostModel { @Override public ImmutableList<RelCollation> getCollation(HiveJoin join) { - return null; + return ImmutableList.of(); } @Override public RelDistribution getDistribution(HiveJoin join) { - return null; + return RelDistributions.SINGLETON; } @Override @@ -117,7 +118,7 @@ public class HiveDefaultCostModel extends HiveCostModel { @Override public Integer getSplitCount(HiveJoin join) { - return null; + return 1; } } http://git-wip-us.apache.org/repos/asf/hive/blob/b597ab2a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/cost/HiveRelMdCost.java ---------------------------------------------------------------------- diff --git a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/cost/HiveRelMdCost.java b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/cost/HiveRelMdCost.java index ed45ab3..cbea307 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/cost/HiveRelMdCost.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/cost/HiveRelMdCost.java @@ -19,7 +19,10 @@ package org.apache.hadoop.hive.ql.optimizer.calcite.cost; import org.apache.calcite.plan.RelOptCost; import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.metadata.BuiltInMetadata; import org.apache.calcite.rel.metadata.ChainedRelMetadataProvider; +import org.apache.calcite.rel.metadata.MetadataDef; +import org.apache.calcite.rel.metadata.MetadataHandler; import org.apache.calcite.rel.metadata.ReflectiveRelMetadataProvider; import org.apache.calcite.rel.metadata.RelMdPercentageOriginalRows; import org.apache.calcite.rel.metadata.RelMetadataProvider; @@ -34,7 +37,7 @@ import com.google.common.collect.ImmutableList; /** * HiveRelMdCost supplies the implementation of cost model. */ -public class HiveRelMdCost { +public class HiveRelMdCost implements MetadataHandler<BuiltInMetadata.NonCumulativeCost> { private final HiveCostModel hiveCostModel; @@ -50,6 +53,11 @@ public class HiveRelMdCost { RelMdPercentageOriginalRows.SOURCE)); } + @Override + public MetadataDef<BuiltInMetadata.NonCumulativeCost> getDef() { + return BuiltInMetadata.NonCumulativeCost.DEF; + } + public RelOptCost getNonCumulativeCost(HiveAggregate aggregate, RelMetadataQuery mq) { return hiveCostModel.getAggregateCost(aggregate); } http://git-wip-us.apache.org/repos/asf/hive/blob/b597ab2a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/druid/DruidIntervalUtils.java ---------------------------------------------------------------------- diff --git a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/druid/DruidIntervalUtils.java b/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/druid/DruidIntervalUtils.java deleted file mode 100644 index 82ab4d7..0000000 --- a/ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/druid/DruidIntervalUtils.java +++ /dev/null @@ -1,466 +0,0 @@ -/** - * 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.hadoop.hive.ql.optimizer.calcite.druid; - -import java.sql.Timestamp; -import java.text.DateFormat; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.TreeSet; - -import org.apache.calcite.rel.type.RelDataType; -import org.apache.calcite.rex.RexCall; -import org.apache.calcite.rex.RexInputRef; -import org.apache.calcite.rex.RexLiteral; -import org.apache.calcite.rex.RexNode; -import org.apache.calcite.sql.SqlKind; -import org.apache.commons.lang.StringUtils; -import org.joda.time.Interval; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.base.Function; -import com.google.common.collect.BoundType; -import com.google.common.collect.Lists; -import com.google.common.collect.Range; -import com.google.common.collect.Sets; - -/** - * Utilities for generating intervals from RexNode. - * - * Based on Navis logic implemented on Hive data structures. - * See <a href="https://github.com/druid-io/druid/pull/2880">Druid PR-2880</a> - * - */ -@SuppressWarnings({"rawtypes","unchecked"}) -public class DruidIntervalUtils { - - protected static final Logger LOG = LoggerFactory.getLogger(DruidIntervalUtils.class); - - - /** - * Given a list of predicates, it generates the equivalent Interval - * (if possible). It assumes that all the predicates in the input - * reference a single column : the timestamp column. - * - * @param conjs list of conditions to use for the transformation - * @return interval representing the conditions in the input list - */ - public static List<Interval> createInterval(RelDataType type, List<RexNode> conjs) { - List<Range> ranges = new ArrayList<>(); - for (RexNode child : conjs) { - List<Range> extractedRanges = extractRanges(type, child, false); - if (extractedRanges == null || extractedRanges.isEmpty()) { - // We could not extract, we bail out - return null; - } - if (ranges.isEmpty()) { - ranges.addAll(extractedRanges); - continue; - } - List<Range> overlapped = Lists.newArrayList(); - for (Range current : ranges) { - for (Range interval : extractedRanges) { - if (current.isConnected(interval)) { - overlapped.add(current.intersection(interval)); - } - } - } - ranges = overlapped; - } - List<Range> compactRanges = condenseRanges(ranges); - LOG.debug("Inferred ranges on interval : " + compactRanges); - return toInterval(compactRanges); - } - - protected static List<Interval> toInterval(List<Range> ranges) { - List<Interval> intervals = Lists.transform(ranges, new Function<Range, Interval>() { - @Override - public Interval apply(Range range) { - if (!range.hasLowerBound() && !range.hasUpperBound()) { - return DruidTable.DEFAULT_INTERVAL; - } - long start = range.hasLowerBound() ? toLong(range.lowerEndpoint()) : - DruidTable.DEFAULT_INTERVAL.getStartMillis(); - long end = range.hasUpperBound() ? toLong(range.upperEndpoint()) : - DruidTable.DEFAULT_INTERVAL.getEndMillis(); - if (range.hasLowerBound() && range.lowerBoundType() == BoundType.OPEN) { - start++; - } - if (range.hasUpperBound() && range.upperBoundType() == BoundType.CLOSED) { - end++; - } - return new Interval(start, end); - } - }); - LOG.info("Converted time ranges " + ranges + " to interval " + intervals); - return intervals; - } - - protected static List<Range> extractRanges(RelDataType type, RexNode node, - boolean withNot) { - switch (node.getKind()) { - case EQUALS: - case LESS_THAN: - case LESS_THAN_OR_EQUAL: - case GREATER_THAN: - case GREATER_THAN_OR_EQUAL: - case BETWEEN: - case IN: - return leafToRanges(type, (RexCall) node, withNot); - - case NOT: - return extractRanges(type, ((RexCall) node).getOperands().get(0), !withNot); - - case OR: - RexCall call = (RexCall) node; - List<Range> intervals = Lists.newArrayList(); - for (RexNode child : call.getOperands()) { - List<Range> extracted = extractRanges(type, child, withNot); - if (extracted != null) { - intervals.addAll(extracted); - } - } - return intervals; - - default: - return null; - } - } - - protected static List<Range> leafToRanges(RelDataType type, RexCall call, - boolean withNot) { - switch (call.getKind()) { - case EQUALS: - case LESS_THAN: - case LESS_THAN_OR_EQUAL: - case GREATER_THAN: - case GREATER_THAN_OR_EQUAL: - { - RexLiteral literal = null; - if (call.getOperands().get(0) instanceof RexInputRef && - call.getOperands().get(1) instanceof RexLiteral) { - literal = extractLiteral(call.getOperands().get(1)); - } else if (call.getOperands().get(0) instanceof RexInputRef && - call.getOperands().get(1).getKind() == SqlKind.CAST) { - literal = extractLiteral(call.getOperands().get(1)); - } else if (call.getOperands().get(1) instanceof RexInputRef && - call.getOperands().get(0) instanceof RexLiteral) { - literal = extractLiteral(call.getOperands().get(0)); - } else if (call.getOperands().get(1) instanceof RexInputRef && - call.getOperands().get(0).getKind() == SqlKind.CAST) { - literal = extractLiteral(call.getOperands().get(0)); - } - if (literal == null) { - return null; - } - Comparable value = literalToType(literal, type); - if (value == null) { - return null; - } - if (call.getKind() == SqlKind.LESS_THAN) { - return Arrays.<Range> asList(withNot ? Range.atLeast(value) : Range.lessThan(value)); - } else if (call.getKind() == SqlKind.LESS_THAN_OR_EQUAL) { - return Arrays.<Range> asList(withNot ? Range.greaterThan(value) : Range.atMost(value)); - } else if (call.getKind() == SqlKind.GREATER_THAN) { - return Arrays.<Range> asList(withNot ? Range.atMost(value) : Range.greaterThan(value)); - } else if (call.getKind() == SqlKind.GREATER_THAN_OR_EQUAL) { - return Arrays.<Range> asList(withNot ? Range.lessThan(value) : Range.atLeast(value)); - } else { //EQUALS - if (!withNot) { - return Arrays.<Range> asList(Range.closed(value, value)); - } - return Arrays.<Range> asList(Range.lessThan(value), Range.greaterThan(value)); - } - } - case BETWEEN: - { - RexLiteral literal1 = extractLiteral(call.getOperands().get(2)); - if (literal1 == null) { - return null; - } - RexLiteral literal2 = extractLiteral(call.getOperands().get(3)); - if (literal2 == null) { - return null; - } - Comparable value1 = literalToType(literal1, type); - Comparable value2 = literalToType(literal2, type); - if (value1 == null || value2 == null) { - return null; - } - boolean inverted = value1.compareTo(value2) > 0; - if (!withNot) { - return Arrays.<Range> asList( - inverted ? Range.closed(value2, value1) : Range.closed(value1, value2)); - } - return Arrays.<Range> asList(Range.lessThan(inverted ? value2 : value1), - Range.greaterThan(inverted ? value1 : value2)); - } - case IN: - { - List<Range> ranges = Lists.newArrayList(); - for (int i = 1; i < call.getOperands().size(); i++) { - RexLiteral literal = extractLiteral(call.getOperands().get(i)); - if (literal == null) { - return null; - } - Comparable element = literalToType(literal, type); - if (element == null) { - return null; - } - if (withNot) { - ranges.addAll( - Arrays.<Range> asList(Range.lessThan(element), Range.greaterThan(element))); - } else { - ranges.add(Range.closed(element, element)); - } - } - return ranges; - } - default: - return null; - } - } - - @SuppressWarnings("incomplete-switch") - protected static Comparable literalToType(RexLiteral literal, RelDataType type) { - // Extract - Object value = null; - switch (literal.getType().getSqlTypeName()) { - case DATE: - case TIME: - case TIMESTAMP: - case INTERVAL_YEAR_MONTH: - case INTERVAL_DAY_TIME: - value = literal.getValue(); - break; - case TINYINT: - case SMALLINT: - case INTEGER: - case BIGINT: - case DOUBLE: - case DECIMAL: - case FLOAT: - case REAL: - case VARCHAR: - case CHAR: - case BOOLEAN: - value = literal.getValue3(); - } - if (value == null) { - return null; - } - - // Convert - switch (type.getSqlTypeName()) { - case BIGINT: - return toLong(value); - case INTEGER: - return toInt(value); - case FLOAT: - return toFloat(value); - case DOUBLE: - return toDouble(value); - case VARCHAR: - case CHAR: - return String.valueOf(value); - case TIMESTAMP: - return toTimestamp(value); - } - return null; - } - - private static RexLiteral extractLiteral(RexNode node) { - RexNode target = node; - if (node.getKind() == SqlKind.CAST) { - target = ((RexCall)node).getOperands().get(0); - } - if (!(target instanceof RexLiteral)) { - return null; - } - return (RexLiteral) target; - } - - private static Comparable toTimestamp(Object literal) { - if (literal instanceof Timestamp) { - return (Timestamp) literal; - } - if (literal instanceof Date) { - return new Timestamp(((Date) literal).getTime()); - } - if (literal instanceof Number) { - return new Timestamp(((Number) literal).longValue()); - } - if (literal instanceof String) { - String string = (String) literal; - if (StringUtils.isNumeric(string)) { - return new Timestamp(Long.valueOf(string)); - } - try { - return Timestamp.valueOf(string); - } catch (NumberFormatException e) { - // ignore - } - } - return null; - } - - private static Long toLong(Object literal) { - if (literal instanceof Number) { - return ((Number) literal).longValue(); - } - if (literal instanceof Date) { - return ((Date) literal).getTime(); - } - if (literal instanceof Timestamp) { - return ((Timestamp) literal).getTime(); - } - if (literal instanceof String) { - try { - return Long.valueOf((String) literal); - } catch (NumberFormatException e) { - // ignore - } - try { - return DateFormat.getDateInstance().parse((String) literal).getTime(); - } catch (ParseException e) { - // best effort. ignore - } - } - return null; - } - - - private static Integer toInt(Object literal) { - if (literal instanceof Number) { - return ((Number) literal).intValue(); - } - if (literal instanceof String) { - try { - return Integer.valueOf((String) literal); - } catch (NumberFormatException e) { - // ignore - } - } - return null; - } - - private static Float toFloat(Object literal) { - if (literal instanceof Number) { - return ((Number) literal).floatValue(); - } - if (literal instanceof String) { - try { - return Float.valueOf((String) literal); - } catch (NumberFormatException e) { - // ignore - } - } - return null; - } - - private static Double toDouble(Object literal) { - if (literal instanceof Number) { - return ((Number) literal).doubleValue(); - } - if (literal instanceof String) { - try { - return Double.valueOf((String) literal); - } catch (NumberFormatException e) { - // ignore - } - } - return null; - } - - protected static List<Range> condenseRanges(List<Range> ranges) { - if (ranges.size() <= 1) { - return ranges; - } - - Comparator<Range> startThenEnd = new Comparator<Range>() { - @Override - public int compare(Range lhs, Range rhs) { - int compare = 0; - if (lhs.hasLowerBound() && rhs.hasLowerBound()) { - compare = lhs.lowerEndpoint().compareTo(rhs.lowerEndpoint()); - } else if (!lhs.hasLowerBound() && rhs.hasLowerBound()) { - compare = -1; - } else if (lhs.hasLowerBound() && !rhs.hasLowerBound()) { - compare = 1; - } - if (compare != 0) { - return compare; - } - if (lhs.hasUpperBound() && rhs.hasUpperBound()) { - compare = lhs.upperEndpoint().compareTo(rhs.upperEndpoint()); - } else if (!lhs.hasUpperBound() && rhs.hasUpperBound()) { - compare = -1; - } else if (lhs.hasUpperBound() && !rhs.hasUpperBound()) { - compare = 1; - } - return compare; - } - }; - - TreeSet<Range> sortedIntervals = Sets.newTreeSet(startThenEnd); - sortedIntervals.addAll(ranges); - - List<Range> retVal = Lists.newArrayList(); - - Iterator<Range> intervalsIter = sortedIntervals.iterator(); - Range currInterval = intervalsIter.next(); - while (intervalsIter.hasNext()) { - Range next = intervalsIter.next(); - if (currInterval.encloses(next)) { - continue; - } - if (mergeable(currInterval, next)) { - currInterval = currInterval.span(next); - } else { - retVal.add(currInterval); - currInterval = next; - } - } - retVal.add(currInterval); - - return retVal; - } - - protected static boolean mergeable(Range range1, Range range2) { - Comparable x1 = range1.upperEndpoint(); - Comparable x2 = range2.lowerEndpoint(); - int compare = x1.compareTo(x2); - return compare > 0 || (compare == 0 && range1.upperBoundType() == BoundType.CLOSED - && range2.lowerBoundType() == BoundType.CLOSED); - } - - public static long extractTotalTime(List<Interval> intervals) { - long totalTime = 0; - for (Interval interval : intervals) { - totalTime += (interval.getEndMillis() - interval.getStartMillis()); - } - return totalTime; - } - -}