You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2016/09/16 04:48:15 UTC
[1/6] calcite git commit: Fix up previous commit
Repository: calcite
Updated Branches:
refs/heads/master 962eac566 -> 0938c7b6d
Fix up previous commit
Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/baaf2562
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/baaf2562
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/baaf2562
Branch: refs/heads/master
Commit: baaf2562ea16cc7d11964afb04a4d68d2d1ade04
Parents: 962eac5
Author: Julian Hyde <jh...@apache.org>
Authored: Thu Sep 15 15:38:58 2016 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Sep 15 17:02:19 2016 -0700
----------------------------------------------------------------------
.../apache/calcite/jdbc/CalciteMetaImpl.java | 2 +-
.../adapter/druid/DruidDateTimeUtils.java | 33 +++++++--------
.../calcite/adapter/druid/DruidQuery.java | 42 ++++++++++----------
.../calcite/adapter/druid/DruidRules.java | 4 +-
.../calcite/adapter/druid/DruidTable.java | 6 +--
5 files changed, 40 insertions(+), 47 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/calcite/blob/baaf2562/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaImpl.java b/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaImpl.java
index 434b1d4..168f356 100644
--- a/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaImpl.java
+++ b/core/src/main/java/org/apache/calcite/jdbc/CalciteMetaImpl.java
@@ -452,7 +452,7 @@ public class CalciteMetaImpl extends MetaImpl {
private ImmutableList<MetaTypeInfo> getAllDefaultType() {
final ImmutableList.Builder<MetaTypeInfo> allTypeList =
- new ImmutableList.Builder<>();
+ ImmutableList.builder();
final CalciteConnectionImpl conn = (CalciteConnectionImpl) connection;
final RelDataTypeSystem typeSystem = conn.typeFactory.getTypeSystem();
for (SqlTypeName sqlTypeName : SqlTypeName.values()) {
http://git-wip-us.apache.org/repos/asf/calcite/blob/baaf2562/druid/src/main/java/org/apache/calcite/adapter/druid/DruidDateTimeUtils.java
----------------------------------------------------------------------
diff --git a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidDateTimeUtils.java b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidDateTimeUtils.java
index edfaad8..2921b9f 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidDateTimeUtils.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidDateTimeUtils.java
@@ -60,12 +60,9 @@ public class DruidDateTimeUtils {
}
/**
- * 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
+ * Generates a list of {@link Interval}s equivalent to a given
+ * expression. Assumes that all the predicates in the input
+ * reference a single column: the timestamp column.
*/
public static List<Interval> createInterval(RelDataType type, RexNode e) {
final List<Range> ranges = extractRanges(type, e, false);
@@ -90,9 +87,9 @@ public class DruidDateTimeUtils {
return DruidTable.DEFAULT_INTERVAL;
}
long start = range.hasLowerBound() ? toLong(range.lowerEndpoint())
- : DruidTable.DEFAULT_INTERVAL.getStartMillis();
+ : DruidTable.DEFAULT_INTERVAL.getStartMillis();
long end = range.hasUpperBound() ? toLong(range.upperEndpoint())
- : DruidTable.DEFAULT_INTERVAL.getEndMillis();
+ : DruidTable.DEFAULT_INTERVAL.getEndMillis();
if (range.hasLowerBound() && range.lowerBoundType() == BoundType.OPEN) {
start++;
}
@@ -109,7 +106,7 @@ public class DruidDateTimeUtils {
}
protected static List<Range> extractRanges(RelDataType type, RexNode node,
- boolean withNot) {
+ boolean withNot) {
switch (node.getKind()) {
case EQUALS:
case LESS_THAN:
@@ -167,7 +164,7 @@ public class DruidDateTimeUtils {
}
protected static List<Range> leafToRanges(RelDataType type, RexCall call,
- boolean withNot) {
+ boolean withNot) {
switch (call.getKind()) {
case EQUALS:
case LESS_THAN:
@@ -177,16 +174,16 @@ public class DruidDateTimeUtils {
{
RexLiteral literal = null;
if (call.getOperands().get(0) instanceof RexInputRef
- && call.getOperands().get(1) instanceof RexLiteral) {
+ && 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) {
+ && 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) {
+ && 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) {
+ && call.getOperands().get(0).getKind() == SqlKind.CAST) {
literal = extractLiteral(call.getOperands().get(0));
}
if (literal == null) {
@@ -230,10 +227,10 @@ public class DruidDateTimeUtils {
boolean inverted = value1.compareTo(value2) > 0;
if (!withNot) {
return Arrays.<Range>asList(
- inverted ? Range.closed(value2, value1) : Range.closed(value1, value2));
+ inverted ? Range.closed(value2, value1) : Range.closed(value1, value2));
}
return Arrays.<Range>asList(Range.lessThan(inverted ? value2 : value1),
- Range.greaterThan(inverted ? value1 : value2));
+ Range.greaterThan(inverted ? value1 : value2));
}
case IN:
{
@@ -249,7 +246,7 @@ public class DruidDateTimeUtils {
}
if (withNot) {
ranges.addAll(
- Arrays.<Range>asList(Range.lessThan(element), Range.greaterThan(element)));
+ Arrays.<Range>asList(Range.lessThan(element), Range.greaterThan(element)));
} else {
ranges.add(Range.closed(element, element));
}
@@ -433,7 +430,7 @@ public class DruidDateTimeUtils {
}
/**
- * Extract granularity from a call FLOOR(<time> TO <timeunit>).
+ * Extracts granularity from a call {@code FLOOR(<time> TO <timeunit>)}.
* Timeunit specifies the granularity. Returns null if it cannot
* be inferred.
*
http://git-wip-us.apache.org/repos/asf/calcite/blob/baaf2562/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java
----------------------------------------------------------------------
diff --git a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java
index cec8261..bb2a534 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidQuery.java
@@ -97,7 +97,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
* @param traitSet Traits
* @param table Table
* @param druidTable Druid table
- * @param interval Interval for the query
+ * @param intervals Intervals for the query
* @param rels Internal relational expressions
*/
protected DruidQuery(RelOptCluster cluster, RelTraitSet traitSet,
@@ -127,10 +127,10 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
for (RelNode rel : rels) {
b.append(rel instanceof TableScan ? 's'
: rel instanceof Project ? 'p'
- : rel instanceof Filter ? 'f'
- : rel instanceof Aggregate ? 'a'
- : rel instanceof Sort ? 'l'
- : '!');
+ : rel instanceof Filter ? 'f'
+ : rel instanceof Aggregate ? 'a'
+ : rel instanceof Sort ? 'l'
+ : '!');
}
return b.toString();
}
@@ -243,13 +243,13 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
public static DruidQuery extendQuery(DruidQuery query, RelNode r) {
final ImmutableList.Builder<RelNode> builder = ImmutableList.builder();
return DruidQuery.create(query.getCluster(), r.getTraitSet(), query.getTable(),
- query.druidTable, query.intervals, builder.addAll(query.rels).add(r).build());
+ query.druidTable, query.intervals, builder.addAll(query.rels).add(r).build());
}
/** Extends a DruidQuery. */
public static DruidQuery extendQuery(DruidQuery query, List<Interval> intervals) {
return DruidQuery.create(query.getCluster(), query.getTraitSet(), query.getTable(),
- query.druidTable, intervals, query.rels);
+ query.druidTable, intervals, query.rels);
}
@Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) {
@@ -259,8 +259,8 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
@Override public RelDataType deriveRowType() {
return getCluster().getTypeFactory().createStructType(
- Pair.right(Util.last(rels).getRowType().getFieldList()),
- getQuerySpec().fieldNames);
+ Pair.right(Util.last(rels).getRowType().getFieldList()),
+ getQuerySpec().fieldNames);
}
public TableScan getTableScan() {
@@ -296,7 +296,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
} else if (rel instanceof Sort) {
final Sort sort = (Sort) rel;
for (Ord<RelFieldCollation> ord
- : Ord.zip(sort.collation.getFieldCollations())) {
+ : Ord.zip(sort.collation.getFieldCollations())) {
pw.item("sort" + ord.i, ord.e.getFieldIndex());
}
for (Ord<RelFieldCollation> ord
@@ -392,7 +392,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
}
return getQuery(rowType, filter, projects, groupSet, aggCalls, aggNames,
- collationIndexes, collationDirections, fetch);
+ collationIndexes, collationDirections, fetch);
}
public QueryType getQueryType() {
@@ -451,7 +451,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
// Reference, it could be to the timestamp column or any other dimension
final RexInputRef ref = (RexInputRef) project;
final String origin = druidTable.getRowType(getCluster().getTypeFactory())
- .getFieldList().get(ref.getIndex()).getName();
+ .getFieldList().get(ref.getIndex()).getName();
if (origin.equals(druidTable.timestampFieldName)) {
granularity = "none";
builder.add(s);
@@ -465,7 +465,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
// Call, check if we should infer granularity
final RexCall call = (RexCall) project;
final String funcGranularity =
- DruidDateTimeUtils.extractGranularity(call);
+ DruidDateTimeUtils.extractGranularity(call);
if (funcGranularity != null) {
granularity = funcGranularity;
builder.add(s);
@@ -507,12 +507,12 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
boolean sortsMetric = false;
if (collationIndexes != null) {
assert collationDirections != null;
- ImmutableList.Builder<JsonCollation> colBuilder
- = new ImmutableList.Builder<JsonCollation>();
+ ImmutableList.Builder<JsonCollation> colBuilder =
+ ImmutableList.builder();
for (Pair<Integer, Direction> p : Pair.zip(collationIndexes, collationDirections)) {
colBuilder.add(
- new JsonCollation(fieldNames.get(p.left),
- p.right == Direction.DESCENDING ? "descending" : "ascending"));
+ new JsonCollation(fieldNames.get(p.left),
+ p.right == Direction.DESCENDING ? "descending" : "ascending"));
if (p.left >= groupSet.cardinality() && p.right == Direction.DESCENDING) {
// Currently only support for DESC in TopN
sortsMetric = true;
@@ -619,7 +619,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
generator.writeFieldName("pagingSpec");
generator.writeStartObject();
generator.writeNumberField("threshold", fetch != null ? fetch
- : CalciteConnectionProperty.DRUID_FETCH.wrap(new Properties()).getInt());
+ : CalciteConnectionProperty.DRUID_FETCH.wrap(new Properties()).getInt());
generator.writeEndObject();
generator.writeFieldName("context");
@@ -789,7 +789,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
if (druidTable.metricFieldNames.contains(fieldName)) {
metrics.add(fieldName);
} else if (!druidTable.timestampFieldName.equals(fieldName)
- && !DruidTable.DEFAULT_TIMESTAMP_COLUMN.equals(fieldName)) {
+ && !DruidTable.DEFAULT_TIMESTAMP_COLUMN.equals(fieldName)) {
dimensions.add(fieldName);
}
}
@@ -805,7 +805,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
if (druidTable.metricFieldNames.contains(fieldName)) {
metrics.add(fieldName);
} else if (!druidTable.timestampFieldName.equals(fieldName)
- && !DruidTable.DEFAULT_TIMESTAMP_COLUMN.equals(fieldName)) {
+ && !DruidTable.DEFAULT_TIMESTAMP_COLUMN.equals(fieldName)) {
dimensions.add(fieldName);
}
}
@@ -916,7 +916,7 @@ public class DruidQuery extends AbstractRelNode implements BindableRel {
private static boolean containsLimit(QuerySpec querySpec) {
return querySpec.queryString.contains("\"context\":{\""
- + DRUID_QUERY_FETCH + "\":true");
+ + DRUID_QUERY_FETCH + "\":true");
}
private ColumnMetaData.Rep getPrimitive(RelDataTypeField field) {
http://git-wip-us.apache.org/repos/asf/calcite/blob/baaf2562/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
----------------------------------------------------------------------
diff --git a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
index 3d1c23a..70917e8 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidRules.java
@@ -264,9 +264,7 @@ public class DruidRules {
final int index = positions.indexOf(ref.getIndex());
return rexBuilder.makeInputRef(belowTypes.get(index), index);
}
- }
- )
- );
+ }));
}
return Pair.of(aboveNodes, belowNodes);
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/baaf2562/druid/src/main/java/org/apache/calcite/adapter/druid/DruidTable.java
----------------------------------------------------------------------
diff --git a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidTable.java b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidTable.java
index fce83a8..836941b 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidTable.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidTable.java
@@ -50,10 +50,8 @@ import java.util.Set;
public class DruidTable extends AbstractTable implements TranslatableTable {
public static final String DEFAULT_TIMESTAMP_COLUMN = "__time";
- public static final Interval DEFAULT_INTERVAL = new Interval(
- new DateTime("1900-01-01"),
- new DateTime("3000-01-01")
- );
+ public static final Interval DEFAULT_INTERVAL =
+ new Interval(new DateTime("1900-01-01"), new DateTime("3000-01-01"));
final DruidSchema schema;
final String dataSource;
[6/6] calcite git commit: [CALCITE-1372] JDBC adapter generates SQL
with wrong field names
Posted by jh...@apache.org.
[CALCITE-1372] JDBC adapter generates SQL with wrong field names
Clean up RelToSqlConverterTest, using a fluent API to invoke tests.
Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/f9724c27
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/f9724c27
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/f9724c27
Branch: refs/heads/master
Commit: f9724c2739fe482d36cfdd5aa6cad207c7a8fcd4
Parents: baaf256
Author: Julian Hyde <jh...@apache.org>
Authored: Wed Sep 14 18:42:22 2016 +0200
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Sep 15 17:02:42 2016 -0700
----------------------------------------------------------------------
.../calcite/rel/rel2sql/SqlImplementor.java | 42 +-
.../rel/rel2sql/RelToSqlConverterTest.java | 504 ++++++++++---------
.../apache/calcite/test/JdbcAdapterTest.java | 14 +
3 files changed, 303 insertions(+), 257 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/calcite/blob/f9724c27/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
index 3fc030f..826ca6b 100644
--- a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
+++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
@@ -65,6 +65,7 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -74,6 +75,7 @@ import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -239,11 +241,11 @@ public abstract class SqlImplementor {
/**
* Converts a {@link RexNode} condition into a {@link SqlNode}.
*
- * @param node condition Node
- * @param leftContext LeftContext
- * @param rightContext RightContext
- * @param leftFieldCount Number of field on left result
- * @return SqlJoin which represent the condition
+ * @param node Join condition
+ * @param leftContext Left context
+ * @param rightContext Right context
+ * @param leftFieldCount Number of fields on left result
+ * @return SqlNode that represents the condition
*/
public static SqlNode convertConditionToSqlNode(RexNode node,
Context leftContext,
@@ -396,10 +398,10 @@ public abstract class SqlImplementor {
alias3, aliasSet, SqlValidatorUtil.EXPR_SUGGESTER);
if (aliases != null
&& !aliases.isEmpty()
- && !dialect.hasImplicitTableAlias()) {
+ && (!dialect.hasImplicitTableAlias()
+ || aliases.size() > 1)) {
return new Result(node, clauses, alias4, aliases);
}
-
final String alias5;
if (alias2 == null
|| !alias2.equals(alias4)
@@ -415,12 +417,26 @@ public abstract class SqlImplementor {
/** Creates a result based on a join. (Each join could contain one or more
* relational expressions.) */
public Result result(SqlNode join, Result leftResult, Result rightResult) {
- final Map<String, RelDataType> aliases =
- ImmutableMap.<String, RelDataType>builder()
- .putAll(leftResult.aliases)
- .putAll(rightResult.aliases)
- .build();
- return new Result(join, Expressions.list(Clause.FROM), null, aliases);
+ final ImmutableMap.Builder<String, RelDataType> builder =
+ ImmutableMap.builder();
+ collectAliases(builder, join,
+ Iterables.concat(leftResult.aliases.values(),
+ rightResult.aliases.values()).iterator());
+ return new Result(join, Expressions.list(Clause.FROM), null,
+ builder.build());
+ }
+
+ private void collectAliases(ImmutableMap.Builder<String, RelDataType> builder,
+ SqlNode node, Iterator<RelDataType> aliases) {
+ if (node instanceof SqlJoin) {
+ final SqlJoin join = (SqlJoin) node;
+ collectAliases(builder, join.getLeft(), aliases);
+ collectAliases(builder, join.getRight(), aliases);
+ } else {
+ final String alias = SqlValidatorUtil.getAlias(node, -1);
+ assert alias != null;
+ builder.put(alias, aliases.next());
+ }
}
/** Wraps a node in a SELECT statement that has no clauses:
http://git-wip-us.apache.org/repos/asf/calcite/blob/f9724c27/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index 85f4720..1fb9695 100644
--- a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -42,292 +42,250 @@ import static org.junit.Assert.assertTrue;
* Tests for utility {@link RelToSqlConverter}
*/
public class RelToSqlConverterTest {
- private Planner logicalPlanner = getPlanner(null);
-
- private void checkRel2Sql(Planner planner, String query, String expectedQuery,
- SqlDialect dialect) {
- try {
- SqlNode parse = planner.parse(query);
- SqlNode validate = planner.validate(parse);
- RelNode rel = planner.rel(validate).rel;
- final RelToSqlConverter converter =
- new RelToSqlConverter(dialect);
- final SqlNode sqlNode = converter.visitChild(0, rel).asQuery();
- assertThat(Util.toLinux(sqlNode.toSqlString(dialect).getSql()),
- is(expectedQuery));
- } catch (Exception e) {
- assertTrue("Parsing failed throwing error: " + e.getMessage(), false);
- }
- }
-
- private void checkRel2Sql(Planner planner, String query, String expectedQuery) {
- checkRel2Sql(planner, query, expectedQuery, SqlDialect.CALCITE);
+ /** Initiates a test case with a given SQL query. */
+ private Sql sql(String sql) {
+ return new Sql(CalciteAssert.SchemaSpec.JDBC_FOODMART, sql,
+ SqlDialect.CALCITE);
}
- private Planner getPlanner(List<RelTraitDef> traitDefs, Program... programs) {
- return getPlanner(traitDefs, SqlParser.Config.DEFAULT, programs);
- }
-
- private Planner getPlanner(List<RelTraitDef> traitDefs,
- SqlParser.Config parserConfig, Program... programs) {
+ private static Planner getPlanner(List<RelTraitDef> traitDefs,
+ SqlParser.Config parserConfig, CalciteAssert.SchemaSpec schemaSpec,
+ Program... programs) {
final SchemaPlus rootSchema = Frameworks.createRootSchema(true);
final FrameworkConfig config = Frameworks.newConfigBuilder()
.parserConfig(parserConfig)
- .defaultSchema(
- CalciteAssert.addSchema(rootSchema, CalciteAssert.SchemaSpec.JDBC_FOODMART))
+ .defaultSchema(CalciteAssert.addSchema(rootSchema, schemaSpec))
.traitDefs(traitDefs)
.programs(programs)
.build();
return Frameworks.getPlanner(config);
}
- @Test
- public void testSimpleSelectStarFromProductTable() {
+ @Test public void testSimpleSelectStarFromProductTable() {
String query = "select * from \"product\"";
- checkRel2Sql(logicalPlanner, query,
- "SELECT *\nFROM \"foodmart\".\"product\"");
+ sql(query).ok("SELECT *\nFROM \"foodmart\".\"product\"");
}
- @Test
- public void testSimpleSelectQueryFromProductTable() {
+ @Test public void testSimpleSelectQueryFromProductTable() {
String query = "select \"product_id\", \"product_class_id\" from \"product\"";
- checkRel2Sql(logicalPlanner, query,
- "SELECT \"product_id\", \"product_class_id\"\n"
- + "FROM \"foodmart\".\"product\"");
+ final String expected = "SELECT \"product_id\", \"product_class_id\"\n"
+ + "FROM \"foodmart\".\"product\"";
+ sql(query).ok(expected);
}
//TODO: add test for query -> select * from product
- @Test
- public void testSelectQueryWithWhereClauseOfLessThan() {
+ @Test public void testSelectQueryWithWhereClauseOfLessThan() {
String query =
"select \"product_id\", \"shelf_width\" from \"product\" where \"product_id\" < 10";
- checkRel2Sql(logicalPlanner, query,
- "SELECT \"product_id\", \"shelf_width\"\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "WHERE \"product_id\" < 10");
+ final String expected = "SELECT \"product_id\", \"shelf_width\"\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "WHERE \"product_id\" < 10";
+ sql(query).ok(expected);
}
- @Test
- public void testSelectQueryWithWhereClauseOfBasicOperators() {
+ @Test public void testSelectQueryWithWhereClauseOfBasicOperators() {
String query = "select * from \"product\" "
+ "where (\"product_id\" = 10 OR \"product_id\" <= 5) "
+ "AND (80 >= \"shelf_width\" OR \"shelf_width\" > 30)";
- checkRel2Sql(logicalPlanner, query,
- "SELECT *\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "WHERE (\"product_id\" = 10 OR \"product_id\" <= 5) "
- + "AND (80 >= \"shelf_width\" OR \"shelf_width\" > 30)");
+ final String expected = "SELECT *\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "WHERE (\"product_id\" = 10 OR \"product_id\" <= 5) "
+ + "AND (80 >= \"shelf_width\" OR \"shelf_width\" > 30)";
+ sql(query).ok(expected);
}
- @Test
- public void testSelectQueryWithGroupBy() {
+ @Test public void testSelectQueryWithGroupBy() {
String query = "select count(*) from \"product\" group by \"product_class_id\", \"product_id\"";
- checkRel2Sql(logicalPlanner, query,
- "SELECT COUNT(*)\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "GROUP BY \"product_class_id\", \"product_id\"");
+ final String expected = "SELECT COUNT(*)\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "GROUP BY \"product_class_id\", \"product_id\"";
+ sql(query).ok(expected);
}
- @Test
- public void testSelectQueryWithMinAggregateFunction() {
+ @Test public void testSelectQueryWithMinAggregateFunction() {
String query = "select min(\"net_weight\") from \"product\" group by \"product_class_id\" ";
- checkRel2Sql(logicalPlanner, query,
- "SELECT MIN(\"net_weight\")\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "GROUP BY \"product_class_id\"");
+ final String expected = "SELECT MIN(\"net_weight\")\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "GROUP BY \"product_class_id\"";
+ sql(query).ok(expected);
}
- @Test
- public void testSelectQueryWithMinAggregateFunction1() {
+ @Test public void testSelectQueryWithMinAggregateFunction1() {
String query = "select \"product_class_id\", min(\"net_weight\") from"
+ " \"product\" group by \"product_class_id\"";
- checkRel2Sql(logicalPlanner, query,
- "SELECT \"product_class_id\", MIN(\"net_weight\")\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "GROUP BY \"product_class_id\"");
+ final String expected = "SELECT \"product_class_id\", MIN(\"net_weight\")\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "GROUP BY \"product_class_id\"";
+ sql(query).ok(expected);
}
- @Test
- public void testSelectQueryWithSumAggregateFunction() {
+ @Test public void testSelectQueryWithSumAggregateFunction() {
String query =
"select sum(\"net_weight\") from \"product\" group by \"product_class_id\" ";
- checkRel2Sql(logicalPlanner, query,
- "SELECT SUM(\"net_weight\")\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "GROUP BY \"product_class_id\"");
- }
-
- @Test
- public void testSelectQueryWithMultipleAggregateFunction() {
- String query =
- "select sum(\"net_weight\"), min(\"low_fat\"), count(*)"
- + " from \"product\" group by \"product_class_id\" ";
- checkRel2Sql(logicalPlanner, query,
- "SELECT SUM(\"net_weight\"), MIN(\"low_fat\"), COUNT(*)\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "GROUP BY \"product_class_id\"");
- }
-
- @Test
- public void testSelectQueryWithMultipleAggregateFunction1() {
- String query =
- "select \"product_class_id\", sum(\"net_weight\"), min(\"low_fat\"), count(*)"
- + " from \"product\" group by \"product_class_id\" ";
- checkRel2Sql(logicalPlanner, query,
- "SELECT \"product_class_id\", SUM(\"net_weight\"), MIN(\"low_fat\"), COUNT(*)\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "GROUP BY \"product_class_id\"");
- }
-
- @Test
- public void testSelectQueryWithGroupByAndProjectList() {
- String query =
- "select \"product_class_id\", \"product_id\", count(*) from \"product\" group "
- + "by \"product_class_id\", \"product_id\" ";
- checkRel2Sql(logicalPlanner, query,
- "SELECT \"product_class_id\", \"product_id\", COUNT(*)\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "GROUP BY \"product_class_id\", \"product_id\"");
- }
-
- @Test
- public void testSelectQueryWithGroupByAndProjectList1() {
+ final String expected = "SELECT SUM(\"net_weight\")\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "GROUP BY \"product_class_id\"";
+ sql(query).ok(expected);
+ }
+
+ @Test public void testSelectQueryWithMultipleAggregateFunction() {
+ String query = "select sum(\"net_weight\"), min(\"low_fat\"), count(*)"
+ + " from \"product\" group by \"product_class_id\" ";
+ final String expected = "SELECT SUM(\"net_weight\"), MIN(\"low_fat\"),"
+ + " COUNT(*)\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "GROUP BY \"product_class_id\"";
+ sql(query).ok(expected);
+ }
+
+ @Test public void testSelectQueryWithMultipleAggregateFunction1() {
+ String query = "select \"product_class_id\","
+ + " sum(\"net_weight\"), min(\"low_fat\"), count(*)"
+ + " from \"product\" group by \"product_class_id\" ";
+ final String expected = "SELECT \"product_class_id\","
+ + " SUM(\"net_weight\"), MIN(\"low_fat\"), COUNT(*)\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "GROUP BY \"product_class_id\"";
+ sql(query).ok(expected);
+ }
+
+ @Test public void testSelectQueryWithGroupByAndProjectList() {
+ String query = "select \"product_class_id\", \"product_id\", count(*) "
+ + "from \"product\" group by \"product_class_id\", \"product_id\" ";
+ final String expected = "SELECT \"product_class_id\", \"product_id\","
+ + " COUNT(*)\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "GROUP BY \"product_class_id\", \"product_id\"";
+ sql(query).ok(expected);
+ }
+
+ @Test public void testSelectQueryWithGroupByAndProjectList1() {
String query =
"select count(*) from \"product\" group by \"product_class_id\", \"product_id\"";
- checkRel2Sql(logicalPlanner, query,
- "SELECT COUNT(*)\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "GROUP BY \"product_class_id\", \"product_id\"");
+
+ final String expected = "SELECT COUNT(*)\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "GROUP BY \"product_class_id\", \"product_id\"";
+ sql(query).ok(expected);
}
- @Test
- public void testSelectQueryWithGroupByHaving() {
+ @Test public void testSelectQueryWithGroupByHaving() {
String query = "select count(*) from \"product\" group by \"product_class_id\","
+ " \"product_id\" having \"product_id\" > 10";
- checkRel2Sql(logicalPlanner, query,
- "SELECT COUNT(*)\n"
- + "FROM (SELECT \"product_class_id\", \"product_id\", COUNT(*)\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "GROUP BY \"product_class_id\", \"product_id\") AS \"t0\"\n"
- + "WHERE \"product_id\" > 10");
+ final String expected = "SELECT COUNT(*)\n"
+ + "FROM (SELECT \"product_class_id\", \"product_id\", COUNT(*)\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "GROUP BY \"product_class_id\", \"product_id\") AS \"t0\"\n"
+ + "WHERE \"product_id\" > 10";
+ sql(query).ok(expected);
}
- @Test
- public void testSelectQueryWithOrderByClause() {
+ @Test public void testSelectQueryWithOrderByClause() {
String query = "select \"product_id\" from \"product\" order by \"net_weight\"";
- checkRel2Sql(logicalPlanner, query,
- "SELECT \"product_id\", \"net_weight\"\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "ORDER BY \"net_weight\"");
+ final String expected = "SELECT \"product_id\", \"net_weight\"\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "ORDER BY \"net_weight\"";
+ sql(query).ok(expected);
}
- @Test
- public void testSelectQueryWithOrderByClause1() {
+ @Test public void testSelectQueryWithOrderByClause1() {
String query =
"select \"product_id\", \"net_weight\" from \"product\" order by \"net_weight\"";
- checkRel2Sql(logicalPlanner, query,
- "SELECT \"product_id\", \"net_weight\"\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "ORDER BY \"net_weight\"");
+ final String expected = "SELECT \"product_id\", \"net_weight\"\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "ORDER BY \"net_weight\"";
+ sql(query).ok(expected);
}
- @Test
- public void testSelectQueryWithTwoOrderByClause() {
+ @Test public void testSelectQueryWithTwoOrderByClause() {
String query =
"select \"product_id\" from \"product\" order by \"net_weight\", \"gross_weight\"";
- checkRel2Sql(logicalPlanner, query,
- "SELECT \"product_id\", \"net_weight\", \"gross_weight\"\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "ORDER BY \"net_weight\", \"gross_weight\"");
+ final String expected = "SELECT \"product_id\", \"net_weight\","
+ + " \"gross_weight\"\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "ORDER BY \"net_weight\", \"gross_weight\"";
+ sql(query).ok(expected);
}
- @Test
- public void testSelectQueryWithAscDescOrderByClause() {
- String query =
- "select \"product_id\" from \"product\" order by \"net_weight\" asc, "
- + "\"gross_weight\" desc, \"low_fat\"";
- checkRel2Sql(logicalPlanner, query,
- "SELECT \"product_id\", \"net_weight\", \"gross_weight\", \"low_fat\"\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "ORDER BY \"net_weight\", \"gross_weight\" DESC, \"low_fat\"");
+ @Test public void testSelectQueryWithAscDescOrderByClause() {
+ String query = "select \"product_id\" from \"product\" "
+ + "order by \"net_weight\" asc, \"gross_weight\" desc, \"low_fat\"";
+ final String expected = "SELECT"
+ + " \"product_id\", \"net_weight\", \"gross_weight\", \"low_fat\"\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "ORDER BY \"net_weight\", \"gross_weight\" DESC, \"low_fat\"";
+ sql(query).ok(expected);
}
- @Test
- public void testSelectQueryWithLimitClause() {
+ @Test public void testSelectQueryWithLimitClause() {
String query = "select \"product_id\" from \"product\" limit 100 offset 10";
- checkRel2Sql(logicalPlanner, query,
- "SELECT product_id\n"
- + "FROM foodmart.product\n"
- + "LIMIT 100\nOFFSET 10",
- SqlDialect.DatabaseProduct.HIVE.getDialect());
+ final String expected = "SELECT product_id\n"
+ + "FROM foodmart.product\n"
+ + "LIMIT 100\nOFFSET 10";
+ sql(query).dialect(SqlDialect.DatabaseProduct.HIVE.getDialect())
+ .ok(expected);
}
- @Test
- public void testSelectQueryWithLimitClauseWithoutOrder() {
+ @Test public void testSelectQueryWithLimitClauseWithoutOrder() {
String query = "select \"product_id\" from \"product\" limit 100 offset 10";
- checkRel2Sql(logicalPlanner, query,
- "SELECT \"product_id\"\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "OFFSET 10 ROWS\n"
- + "FETCH NEXT 100 ROWS ONLY");
+ final String expected = "SELECT \"product_id\"\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "OFFSET 10 ROWS\n"
+ + "FETCH NEXT 100 ROWS ONLY";
+ sql(query).ok(expected);
}
- @Test
- public void testSelectQueryWithLimitOffsetClause() {
+ @Test public void testSelectQueryWithLimitOffsetClause() {
String query = "select \"product_id\" from \"product\" order by \"net_weight\" asc"
+ " limit 100 offset 10";
- checkRel2Sql(logicalPlanner, query,
- "SELECT \"product_id\", \"net_weight\"\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "ORDER BY \"net_weight\"\n"
- + "OFFSET 10 ROWS\n"
- + "FETCH NEXT 100 ROWS ONLY");
+ final String expected = "SELECT \"product_id\", \"net_weight\"\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "ORDER BY \"net_weight\"\n"
+ + "OFFSET 10 ROWS\n"
+ + "FETCH NEXT 100 ROWS ONLY";
+ sql(query).ok(expected);
}
- @Test
- public void testSelectQueryWithFetchOffsetClause() {
+ @Test public void testSelectQueryWithFetchOffsetClause() {
String query = "select \"product_id\" from \"product\" order by \"product_id\""
+ " offset 10 rows fetch next 100 rows only";
- checkRel2Sql(logicalPlanner, query,
- "SELECT \"product_id\"\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "ORDER BY \"product_id\"\n"
- + "OFFSET 10 ROWS\n"
- + "FETCH NEXT 100 ROWS ONLY");
+ final String expected = "SELECT \"product_id\"\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "ORDER BY \"product_id\"\n"
+ + "OFFSET 10 ROWS\n"
+ + "FETCH NEXT 100 ROWS ONLY";
+ sql(query).ok(expected);
}
- @Test
- public void testSelectQueryComplex() {
+ @Test public void testSelectQueryComplex() {
String query =
"select count(*), \"units_per_case\" from \"product\" where \"cases_per_pallet\" > 100 "
+ "group by \"product_id\", \"units_per_case\" order by \"units_per_case\" desc";
- checkRel2Sql(logicalPlanner, query,
- "SELECT COUNT(*), \"units_per_case\"\n"
- + "FROM \"foodmart\".\"product\"\n"
- + "WHERE \"cases_per_pallet\" > 100\n"
- + "GROUP BY \"product_id\", \"units_per_case\"\n"
- + "ORDER BY \"units_per_case\" DESC");
+ final String expected = "SELECT COUNT(*), \"units_per_case\"\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "WHERE \"cases_per_pallet\" > 100\n"
+ + "GROUP BY \"product_id\", \"units_per_case\"\n"
+ + "ORDER BY \"units_per_case\" DESC";
+ sql(query).ok(expected);
}
- @Test
- public void testSelectQueryWithGroup() {
- String query =
- "select count(*), sum(\"employee_id\") from \"reserve_employee\" "
- + "where \"hire_date\" > '2015-01-01' "
- + "and (\"position_title\" = 'SDE' or \"position_title\" = 'SDM') "
- + "group by \"store_id\", \"position_title\"";
- checkRel2Sql(logicalPlanner, query,
- "SELECT COUNT(*), SUM(\"employee_id\")\n"
- + "FROM \"foodmart\".\"reserve_employee\"\n"
- + "WHERE \"hire_date\" > '2015-01-01' "
- + "AND (\"position_title\" = 'SDE' OR \"position_title\" = 'SDM')\n"
- + "GROUP BY \"store_id\", \"position_title\"");
- }
-
- @Test
- public void testSimpleJoin() {
+ @Test public void testSelectQueryWithGroup() {
+ String query = "select"
+ + " count(*), sum(\"employee_id\") from \"reserve_employee\" "
+ + "where \"hire_date\" > '2015-01-01' "
+ + "and (\"position_title\" = 'SDE' or \"position_title\" = 'SDM') "
+ + "group by \"store_id\", \"position_title\"";
+ final String expected = "SELECT COUNT(*), SUM(\"employee_id\")\n"
+ + "FROM \"foodmart\".\"reserve_employee\"\n"
+ + "WHERE \"hire_date\" > '2015-01-01' "
+ + "AND (\"position_title\" = 'SDE' OR \"position_title\" = 'SDM')\n"
+ + "GROUP BY \"store_id\", \"position_title\"";
+ sql(query).ok(expected);
+ }
+
+ @Test public void testSimpleJoin() {
String query = "select *\n"
+ "from \"sales_fact_1997\" as s\n"
+ " join \"customer\" as c using (\"customer_id\")\n"
@@ -335,28 +293,32 @@ public class RelToSqlConverterTest {
+ " join \"product_class\" as pc using (\"product_class_id\")\n"
+ "where c.\"city\" = 'San Francisco'\n"
+ "and pc.\"product_department\" = 'Snacks'\n";
- checkRel2Sql(logicalPlanner, query,
- "SELECT *\nFROM \"foodmart\".\"sales_fact_1997\"\n"
- + "INNER JOIN \"foodmart\".\"customer\" "
- + "ON \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\"\n"
- + "INNER JOIN \"foodmart\".\"product\" "
- + "ON \"sales_fact_1997\".\"product_id\" = \"product\".\"product_id\"\n"
- + "INNER JOIN \"foodmart\".\"product_class\" "
- + "ON \"product\".\"product_class_id\" = \"product_class\".\"product_class_id\"\n"
- + "WHERE \"customer\".\"city\" = 'San Francisco' AND "
- + "\"product_class\".\"product_department\" = 'Snacks'");
+ final String expected = "SELECT *\nFROM \"foodmart\".\"sales_fact_1997\"\n"
+ + "INNER JOIN \"foodmart\".\"customer\" "
+ + "ON \"sales_fact_1997\".\"customer_id\" = \"customer\""
+ + ".\"customer_id\"\n"
+ + "INNER JOIN \"foodmart\".\"product\" "
+ + "ON \"sales_fact_1997\".\"product_id\" = \"product\".\"product_id\"\n"
+ + "INNER JOIN \"foodmart\".\"product_class\" "
+ + "ON \"product\".\"product_class_id\" = \"product_class\""
+ + ".\"product_class_id\"\n"
+ + "WHERE \"customer\".\"city\" = 'San Francisco' AND "
+ + "\"product_class\".\"product_department\" = 'Snacks'";
+ sql(query).ok(expected);
}
@Test public void testSimpleIn() {
String query = "select * from \"department\" where \"department_id\" in (\n"
+ " select \"department_id\" from \"employee\"\n"
+ " where \"store_id\" < 150)";
- checkRel2Sql(logicalPlanner, query,
- "SELECT \"department\".\"department_id\", \"department\".\"department_description\"\n"
- + "FROM \"foodmart\".\"department\"\nINNER JOIN "
- + "(SELECT \"department_id\"\nFROM \"foodmart\".\"employee\"\n"
- + "WHERE \"store_id\" < 150\nGROUP BY \"department_id\") AS \"t1\" "
- + "ON \"department\".\"department_id\" = \"t1\".\"department_id\"");
+ final String expected = "SELECT "
+ + "\"department\".\"department_id\", \"department\""
+ + ".\"department_description\"\n"
+ + "FROM \"foodmart\".\"department\"\nINNER JOIN "
+ + "(SELECT \"department_id\"\nFROM \"foodmart\".\"employee\"\n"
+ + "WHERE \"store_id\" < 150\nGROUP BY \"department_id\") AS \"t1\" "
+ + "ON \"department\".\"department_id\" = \"t1\".\"department_id\"";
+ sql(query).ok(expected);
}
/** Test case for
@@ -371,8 +333,7 @@ public class RelToSqlConverterTest {
+ "FROM foodmart.employee AS employee\n"
+ "INNER JOIN foodmart.department AS department "
+ "ON employee.department_id = department.department_id";
- checkRel2Sql(logicalPlanner, query, expected,
- DatabaseProduct.DB2.getDialect());
+ sql(query).dialect(DatabaseProduct.DB2.getDialect()).ok(expected);
}
@Test public void testDb2DialectSelfJoinStar() {
@@ -383,9 +344,7 @@ public class RelToSqlConverterTest {
+ "FROM foodmart.employee AS employee\n"
+ "INNER JOIN foodmart.employee AS employee0 "
+ "ON employee.department_id = employee0.department_id";
- checkRel2Sql(logicalPlanner, query,
- expected,
- DatabaseProduct.DB2.getDialect());
+ sql(query).dialect(DatabaseProduct.DB2.getDialect()).ok(expected);
}
@Test public void testDb2DialectJoin() {
@@ -397,8 +356,7 @@ public class RelToSqlConverterTest {
+ "FROM foodmart.employee AS employee\n"
+ "INNER JOIN foodmart.department AS department "
+ "ON employee.department_id = department.department_id";
- checkRel2Sql(logicalPlanner, query, expected,
- DatabaseProduct.DB2.getDialect());
+ sql(query).dialect(DatabaseProduct.DB2.getDialect()).ok(expected);
}
@Test public void testDb2DialectSelfJoin() {
@@ -410,8 +368,7 @@ public class RelToSqlConverterTest {
+ "FROM foodmart.employee AS employee\n"
+ "INNER JOIN foodmart.employee AS employee0 "
+ "ON employee.department_id = employee0.department_id";
- checkRel2Sql(logicalPlanner, query, expected,
- DatabaseProduct.DB2.getDialect());
+ sql(query).dialect(DatabaseProduct.DB2.getDialect()).ok(expected);
}
@Test public void testDb2DialectWhere() {
@@ -420,8 +377,7 @@ public class RelToSqlConverterTest {
final String expected = "SELECT employee.employee_id\n"
+ "FROM foodmart.employee AS employee\n"
+ "WHERE employee.department_id < 1000";
- checkRel2Sql(logicalPlanner, query, expected,
- DatabaseProduct.DB2.getDialect());
+ sql(query).dialect(DatabaseProduct.DB2.getDialect()).ok(expected);
}
@Test public void testDb2DialectJoinWhere() {
@@ -435,8 +391,7 @@ public class RelToSqlConverterTest {
+ "INNER JOIN foodmart.department AS department "
+ "ON employee.department_id = department.department_id\n"
+ "WHERE employee.employee_id < 1000";
- checkRel2Sql(logicalPlanner, query, expected,
- DatabaseProduct.DB2.getDialect());
+ sql(query).dialect(DatabaseProduct.DB2.getDialect()).ok(expected);
}
@Test public void testDb2DialectSelfJoinWhere() {
@@ -450,8 +405,7 @@ public class RelToSqlConverterTest {
+ "INNER JOIN foodmart.employee AS employee0 "
+ "ON employee.department_id = employee0.department_id\n"
+ "WHERE employee0.employee_id < 2000";
- checkRel2Sql(logicalPlanner, query, expected,
- DatabaseProduct.DB2.getDialect());
+ sql(query).dialect(DatabaseProduct.DB2.getDialect()).ok(expected);
}
@Test public void testDb2DialectCast() {
@@ -460,8 +414,7 @@ public class RelToSqlConverterTest {
final String expected = "SELECT reserve_employee.hire_date, "
+ "CAST(reserve_employee.hire_date AS VARCHAR(10))\n"
+ "FROM foodmart.reserve_employee AS reserve_employee";
- checkRel2Sql(logicalPlanner, query, expected,
- DatabaseProduct.DB2.getDialect());
+ sql(query).dialect(DatabaseProduct.DB2.getDialect()).ok(expected);
}
@Test public void testDb2DialectSelectQueryWithGroupByHaving() {
@@ -474,8 +427,7 @@ public class RelToSqlConverterTest {
+ "FROM foodmart.product AS product\n"
+ "GROUP BY product.product_class_id, product.product_id) AS t0\n"
+ "WHERE t0.product_id > 10";
- checkRel2Sql(logicalPlanner, query, expected,
- DatabaseProduct.DB2.getDialect());
+ sql(query).dialect(DatabaseProduct.DB2.getDialect()).ok(expected);
}
@@ -489,8 +441,7 @@ public class RelToSqlConverterTest {
+ "WHERE product.cases_per_pallet > 100\n"
+ "GROUP BY product.product_id, product.units_per_case\n"
+ "ORDER BY product.units_per_case DESC";
- checkRel2Sql(logicalPlanner, query, expected,
- DatabaseProduct.DB2.getDialect());
+ sql(query).dialect(DatabaseProduct.DB2.getDialect()).ok(expected);
}
@Test public void testDb2DialectSelectQueryWithGroup() {
@@ -506,10 +457,75 @@ public class RelToSqlConverterTest {
+ "AND (reserve_employee.position_title = 'SDE' OR "
+ "reserve_employee.position_title = 'SDM')\n"
+ "GROUP BY reserve_employee.store_id, reserve_employee.position_title";
- checkRel2Sql(logicalPlanner, query, expected,
- DatabaseProduct.DB2.getDialect());
+ sql(query).dialect(DatabaseProduct.DB2.getDialect()).ok(expected);
}
+ /** Test case for
+ * <a href="https://issues.apache.org/jira/browse/CALCITE-1372">[CALCITE-1372]
+ * JDBC adapter generates SQL with wrong field names</a>. */
+ @Test public void testJoinPlan2() {
+ final String sql = "SELECT v1.deptno, v2.deptno\n"
+ + "FROM dept v1 LEFT JOIN emp v2 ON v1.deptno = v2.deptno\n"
+ + "WHERE v2.job LIKE 'PRESIDENT'";
+ final String expected = "SELECT \"DEPT\".\"DEPTNO\","
+ + " \"EMP\".\"DEPTNO\" AS \"DEPTNO0\"\n"
+ + "FROM \"JDBC_SCOTT\".\"DEPT\"\n"
+ + "LEFT JOIN \"JDBC_SCOTT\".\"EMP\""
+ + " ON \"DEPT\".\"DEPTNO\" = \"EMP\".\"DEPTNO\"\n"
+ + "WHERE \"EMP\".\"JOB\" LIKE 'PRESIDENT'";
+ final String expected2 = "SELECT DEPT.DEPTNO, EMP.DEPTNO AS DEPTNO0\n"
+ + "FROM JDBC_SCOTT.DEPT AS DEPT\n"
+ + "LEFT JOIN JDBC_SCOTT.EMP AS EMP ON DEPT.DEPTNO = EMP.DEPTNO\n"
+ + "WHERE EMP.JOB LIKE 'PRESIDENT'";
+ sql(sql)
+ .schema(CalciteAssert.SchemaSpec.JDBC_SCOTT)
+ .ok(expected)
+ .dialect(DatabaseProduct.DB2.getDialect())
+ .ok(expected2);
+ }
+
+ /** Fluid interface to run tests. */
+ private static class Sql {
+ private CalciteAssert.SchemaSpec schemaSpec;
+ private final String sql;
+ private final SqlDialect dialect;
+
+ Sql(CalciteAssert.SchemaSpec schemaSpec, String sql, SqlDialect dialect) {
+ this.schemaSpec = schemaSpec;
+ this.sql = sql;
+ this.dialect = dialect;
+ }
+
+ Sql dialect(SqlDialect dialect) {
+ return new Sql(schemaSpec, sql, dialect);
+ }
+
+ Sql planner(Planner planner) {
+ return new Sql(schemaSpec, sql, dialect);
+ }
+
+ Sql ok(String expectedQuery) {
+ final Planner planner =
+ getPlanner(null, SqlParser.Config.DEFAULT, schemaSpec);
+ try {
+ SqlNode parse = planner.parse(sql);
+ SqlNode validate = planner.validate(parse);
+ RelNode rel = planner.rel(validate).rel;
+ final RelToSqlConverter converter =
+ new RelToSqlConverter(dialect);
+ final SqlNode sqlNode = converter.visitChild(0, rel).asQuery();
+ assertThat(Util.toLinux(sqlNode.toSqlString(dialect).getSql()),
+ is(expectedQuery));
+ } catch (Exception e) {
+ assertTrue("Parsing failed throwing error: " + e.getMessage(), false);
+ }
+ return this;
+ }
+
+ public Sql schema(CalciteAssert.SchemaSpec schemaSpec) {
+ return new Sql(schemaSpec, sql, dialect);
+ }
+ }
}
// End RelToSqlConverterTest.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/f9724c27/core/src/test/java/org/apache/calcite/test/JdbcAdapterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcAdapterTest.java b/core/src/test/java/org/apache/calcite/test/JdbcAdapterTest.java
index 97da6c3..4b65017 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcAdapterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcAdapterTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.calcite.test;
+import org.apache.calcite.config.Lex;
import org.apache.calcite.jdbc.CalciteConnection;
import com.google.common.base.Function;
@@ -319,6 +320,19 @@ public class JdbcAdapterTest {
}
/** Test case for
+ * <a href="https://issues.apache.org/jira/browse/CALCITE-1372">[CALCITE-1372]
+ * JDBC adapter generates SQL with wrong field names</a>. */
+ @Test public void testJoinPlan2() {
+ final String sql = "SELECT v1.deptno, v2.deptno\n"
+ + "FROM Scott.dept v1 LEFT JOIN Scott.emp v2 ON v1.deptno = v2.deptno\n"
+ + "WHERE v2.job LIKE 'PRESIDENT'";
+ CalciteAssert.model(JdbcTest.SCOTT_MODEL)
+ .with(Lex.MYSQL)
+ .query(sql).runs()
+ .returnsCount(1);
+ }
+
+ /** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-657">[CALCITE-657]
* NullPointerException when executing JdbcAggregate implement method</a>. */
@Test public void testJdbcAggregate() throws Exception {
[3/6] calcite git commit: [CALCITE-1208] Improve two-level column
structure handling
Posted by jh...@apache.org.
[CALCITE-1208] Improve two-level column structure handling
Modify RelDataType interface, and RelDataTypeImpl.getField().
Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/a859f93f
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/a859f93f
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/a859f93f
Branch: refs/heads/master
Commit: a859f93ff2fc800e3e4a481274e3daf19f72e8b2
Parents: 339ac13
Author: maryannxue <ma...@gmail.com>
Authored: Wed Apr 27 22:41:19 2016 -0400
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Sep 15 17:02:42 2016 -0700
----------------------------------------------------------------------
.../apache/calcite/rel/type/RelDataType.java | 13 ++
.../calcite/rel/type/RelDataTypeImpl.java | 43 +++++-
.../apache/calcite/test/MockCatalogReader.java | 131 +++++++++++++++++++
.../calcite/test/SqlToRelConverterTest.java | 4 +
.../apache/calcite/test/SqlValidatorTest.java | 101 ++++++++++++++
5 files changed, 288 insertions(+), 4 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/calcite/blob/a859f93f/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
index 9bd73f2..442538e 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
@@ -36,6 +36,12 @@ public interface RelDataType /*extends Type*/ {
int SCALE_NOT_SPECIFIED = Integer.MIN_VALUE;
int PRECISION_NOT_SPECIFIED = -1;
+ enum StructKind {
+ FULLY_QUALIFIED,
+ PEEK_FIELDS,
+ PEEK_FIELDS_DEFAULT,
+ }
+
//~ Methods ----------------------------------------------------------------
/**
@@ -77,6 +83,13 @@ public interface RelDataType /*extends Type*/ {
int getFieldCount();
/**
+ * Gets the StructKind of a structured type.
+ *
+ * @return the StructKind that determines how its fields are resolved.
+ */
+ StructKind getStructKind();
+
+ /**
* Looks up a field by name.
*
* <p>NOTE: Be careful choosing the value of {@code caseSensitive}:</p>
http://git-wip-us.apache.org/repos/asf/calcite/blob/a859f93f/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java
index 3134b15..0fc14f3 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java
@@ -84,6 +84,36 @@ public abstract class RelDataTypeImpl
return field;
}
}
+ for (RelDataTypeField field : fieldList) {
+ RelDataType dataType = field.getType();
+ if (dataType.isStruct()
+ && dataType.getStructKind() == StructKind.PEEK_FIELDS_DEFAULT) {
+ for (RelDataTypeField subField : dataType.getFieldList()) {
+ if (Util.matches(caseSensitive, subField.getName(), fieldName)) {
+ return subField;
+ }
+ }
+ }
+ }
+ RelDataTypeField matched = null;
+ for (RelDataTypeField field : fieldList) {
+ RelDataType dataType = field.getType();
+ if (dataType.isStruct()
+ && dataType.getStructKind() == StructKind.PEEK_FIELDS) {
+ for (RelDataTypeField subField : dataType.getFieldList()) {
+ if (Util.matches(caseSensitive, subField.getName(), fieldName)) {
+ if (matched == null) {
+ matched = subField;
+ } else {
+ // TODO throw columnAmbiguous
+ }
+ }
+ }
+ }
+ }
+ if (matched != null) {
+ return matched;
+ }
if (elideRecord) {
final List<Slot> slots = Lists.newArrayList();
getFieldRecurse(slots, this, 0, fieldName, caseSensitive);
@@ -119,13 +149,13 @@ public abstract class RelDataTypeImpl
return null;
}
- private static void getFieldRecurse(List<Slot> slots, RelDataTypeImpl type,
+ private static void getFieldRecurse(List<Slot> slots, RelDataType type,
int depth, String fieldName, boolean caseSensitive) {
while (slots.size() <= depth) {
slots.add(new Slot());
}
final Slot slot = slots.get(depth);
- for (RelDataTypeField field : type.fieldList) {
+ for (RelDataTypeField field : type.getFieldList()) {
if (Util.matches(caseSensitive, field.getName(), fieldName)) {
slot.count++;
slot.field = field;
@@ -133,9 +163,9 @@ public abstract class RelDataTypeImpl
}
// No point looking to depth + 1 if there is a hit at depth.
if (slot.count == 0) {
- for (RelDataTypeField field : type.fieldList) {
+ for (RelDataTypeField field : type.getFieldList()) {
if (field.getType().isStruct()) {
- getFieldRecurse(slots, (RelDataTypeImpl) field.getType(), depth + 1,
+ getFieldRecurse(slots, field.getType(), depth + 1,
fieldName, caseSensitive);
}
}
@@ -158,6 +188,11 @@ public abstract class RelDataTypeImpl
return fieldList.size();
}
+ public StructKind getStructKind() {
+ assert isStruct() : this;
+ return StructKind.FULLY_QUALIFIED;
+ }
+
// implement RelDataType
public RelDataType getComponentType() {
// this is not a collection type
http://git-wip-us.apache.org/repos/asf/calcite/blob/a859f93f/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java b/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
index 039a655..d61b440 100644
--- a/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
+++ b/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
@@ -34,10 +34,14 @@ import org.apache.calcite.rel.logical.LogicalProject;
import org.apache.calcite.rel.logical.LogicalTableScan;
import org.apache.calcite.rel.type.DynamicRecordTypeImpl;
import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataType.StructKind;
import org.apache.calcite.rel.type.RelDataTypeComparability;
import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rel.type.RelDataTypeFactory.FieldInfoBuilder;
+import org.apache.calcite.rel.type.RelDataTypeFamily;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rel.type.RelDataTypeFieldImpl;
+import org.apache.calcite.rel.type.RelDataTypePrecedenceList;
import org.apache.calcite.rel.type.RelRecordType;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexInputRef;
@@ -50,8 +54,10 @@ import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.schema.Schemas;
import org.apache.calcite.schema.Table;
import org.apache.calcite.sql.SqlAccessType;
+import org.apache.calcite.sql.SqlCollation;
import org.apache.calcite.sql.SqlFunctionCategory;
import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlIntervalQualifier;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlSyntax;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
@@ -79,6 +85,7 @@ import com.google.common.collect.Sets;
import java.lang.reflect.Type;
import java.math.BigDecimal;
+import java.nio.charset.Charset;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
@@ -419,6 +426,32 @@ public class MockCatalogReader implements Prepare.CatalogReader {
emp20View.addColumn("SLACKER", booleanType);
registerTable(emp20View);
+ MockSchema structTypeSchema = new MockSchema("STRUCT");
+ registerSchema(structTypeSchema);
+ MockTable structTypeTable = MockTable.create(
+ this, structTypeSchema, "T", false, 100);
+ structTypeTable.addColumn("K0", varchar20Type);
+ structTypeTable.addColumn("C1", varchar20Type);
+ FieldInfoBuilder builder = typeFactory.builder();
+ builder.add("C0", intType);
+ builder.add("C1", intType);
+ RelDataType f0Type = new DelegateStructType(
+ typeFactory.createStructType(builder), StructKind.PEEK_FIELDS_DEFAULT);
+ structTypeTable.addColumn("F0", f0Type);
+ builder = typeFactory.builder();
+ builder.add("C0", intTypeNull);
+ builder.add("C2", intType);
+ builder.add("A0", intType);
+ RelDataType f1Type = new DelegateStructType(
+ typeFactory.createStructType(builder), StructKind.PEEK_FIELDS);
+ structTypeTable.addColumn("F1", f1Type);
+ builder = typeFactory.builder();
+ builder.add("C3", intType);
+ builder.add("A0", booleanType);
+ RelDataType f2Type = new DelegateStructType(
+ typeFactory.createStructType(builder), StructKind.PEEK_FIELDS);
+ structTypeTable.addColumn("F2", f2Type);
+ registerTable(structTypeTable);
return this;
}
@@ -766,7 +799,105 @@ public class MockCatalogReader implements Prepare.CatalogReader {
}
}
+ private static class DelegateStructType implements RelDataType {
+ private RelDataType delegate;
+ private StructKind structKind;
+ DelegateStructType(RelDataType delegate, StructKind structKind) {
+ assert delegate.isStruct();
+ this.delegate = delegate;
+ this.structKind = structKind;
+ }
+
+ @Override public boolean isStruct() {
+ return delegate.isStruct();
+ }
+
+ @Override public boolean isDynamicStruct() {
+ return delegate.isDynamicStruct();
+ }
+
+ @Override public List<RelDataTypeField> getFieldList() {
+ return delegate.getFieldList();
+ }
+
+ @Override public List<String> getFieldNames() {
+ return delegate.getFieldNames();
+ }
+
+ @Override public int getFieldCount() {
+ return delegate.getFieldCount();
+ }
+
+ @Override public StructKind getStructKind() {
+ return structKind;
+ }
+
+ @Override public RelDataTypeField getField(String fieldName,
+ boolean caseSensitive, boolean elideRecord) {
+ return delegate.getField(fieldName, caseSensitive, elideRecord);
+ }
+
+ @Override public boolean isNullable() {
+ return delegate.isNullable();
+ }
+
+ @Override public RelDataType getComponentType() {
+ return delegate.getComponentType();
+ }
+
+ @Override public RelDataType getKeyType() {
+ return delegate.getKeyType();
+ }
+
+ @Override public RelDataType getValueType() {
+ return delegate.getValueType();
+ }
+
+ @Override public Charset getCharset() {
+ return delegate.getCharset();
+ }
+
+ @Override public SqlCollation getCollation() {
+ return delegate.getCollation();
+ }
+
+ @Override public SqlIntervalQualifier getIntervalQualifier() {
+ return delegate.getIntervalQualifier();
+ }
+
+ @Override public int getPrecision() {
+ return delegate.getPrecision();
+ }
+
+ @Override public int getScale() {
+ return delegate.getScale();
+ }
+
+ @Override public SqlTypeName getSqlTypeName() {
+ return delegate.getSqlTypeName();
+ }
+
+ @Override public SqlIdentifier getSqlIdentifier() {
+ return delegate.getSqlIdentifier();
+ }
+
+ @Override public String getFullTypeString() {
+ return delegate.getFullTypeString();
+ }
+
+ @Override public RelDataTypeFamily getFamily() {
+ return delegate.getFamily();
+ }
+
+ @Override public RelDataTypePrecedenceList getPrecedenceList() {
+ return delegate.getPrecedenceList();
+ }
+
+ @Override public RelDataTypeComparability getComparability() {
+ return delegate.getComparability();
+ }
+ }
}
// End MockCatalogReader.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/a859f93f/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
index 0ab5260..7ec5087 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -1720,6 +1720,10 @@ public class SqlToRelConverterTest extends SqlToRelTestBase {
sql(sql).decorrelate(true).ok();
}
+ @Test public void testStructType() {
+ sql("select * from struct.t").convertsTo("");
+ }
+
/**
* Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1150">[CALCITE-1150]
http://git-wip-us.apache.org/repos/asf/calcite/blob/a859f93f/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 7fff2d9..c48b528 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -8137,6 +8137,107 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
// 12345678901234567890123456789012345678901234567890123456789012345
// check("SELECT count(0) FROM emp GROUP BY ()");
}
+
+ @Test public void testStructType() {
+ // Table STRUCT.T is defined as:
+ // (K0 VARCHAR(20) NOT NULL, C1 VARCHAR(20) NOT NULL,
+ // RecordType(C0 INTEGER NOT NULL, C1 INTEGER NOT NULL) F0,
+ // RecordType(C0 INTEGER, C2 INTEGER NOT NULL, A0 INTEGER NOT NULL) F1,
+ // RecordType(C3 INTEGER NOT NULL, A0 BOOLEAN NOT NULL) F2)
+ // , where F0 has a default struct priority.
+
+ check("select * from struct.t");
+
+ // Resolve K0 as top-level column K0.
+ checkResultType("select k0 from struct.t",
+ "RecordType(VARCHAR(20) NOT NULL K0) NOT NULL");
+
+ // Resolve C2 as secondary-level column F1.C2.
+ checkResultType("select c2 from struct.t",
+ "RecordType(INTEGER NOT NULL C2) NOT NULL");
+
+ // Resolve F1.C2 as fully qualified column F1.C2.
+ checkResultType("select c2 from struct.t",
+ "RecordType(INTEGER NOT NULL C2) NOT NULL");
+
+ // Resolve C1 as top-level column C1 as opposed to F0.C1.
+ checkResultType("select c1 from struct.t",
+ "RecordType(VARCHAR(20) NOT NULL C1) NOT NULL");
+
+ // Resolve C0 as secondary-level column F0.C0 as opposed to F1.C0, since F0 has the
+ // default priority.
+ checkResultType("select c0 from struct.t",
+ "RecordType(INTEGER NOT NULL C0) NOT NULL");
+
+ // Resolve F1.C0 as fully qualified column F1.C0.
+ checkResultType("select f1.c0 from struct.t",
+ "RecordType(INTEGER C0) NOT NULL");
+
+ // Fail ambiguous column reference A0, since F1.A0 and F2.A0 both exist with the
+ // same resolving priority.
+ checkFails("select ^a0^ from struct.t",
+ "Column 'A0' is ambiguous");
+
+ // Resolve F2.A0 as fully qualified column F2.A0.
+ checkResultType("select f2.a0 from struct.t",
+ "RecordType(BOOLEAN NOT NULL C0) NOT NULL");
+
+ // Resolve T0.K0 as top-level column K0, since T0 is recognized as the table alias.
+ checkResultType("select t0.k0 from struct.t t0",
+ "RecordType(VARCHAR(20) NOT NULL K0) NOT NULL");
+
+ // Resolve T0.C2 as secondary-level column F1.C2, since T0 is recognized as the
+ // table alias here.
+ checkResultType("select t0.c2 from struct.t t0",
+ "RecordType(INTEGER NOT NULL C2) NOT NULL");
+
+ // Resolve F0.C2 as secondary-level column F1.C2, since F0 is recognized as the
+ // table alias here.
+ checkResultType("select f0.c2 from struct.t f0",
+ "RecordType(INTEGER NOT NULL C2) NOT NULL");
+
+ // Resolve F0.C1 as top-level column C1 as opposed to F0.C1, since F0 is recognized
+ // as the table alias here.
+ checkResultType("select f0.c1 from struct.t f0",
+ "RecordType(VARCHAR(20) NOT NULL C1) NOT NULL");
+
+ // Resolve T.C1 as top-level column C1 as opposed to F0.C1, since T is recognized as
+ // the table name.
+ checkResultType("select t.c1 from struct.t f0",
+ "RecordType(VARCHAR(20) NOT NULL C1) NOT NULL");
+
+ // Resolve STRUCT.T.C1 as top-level column C1 as opposed to F0.C1, since STRUCT.T is
+ // recognized as the schema and table name.
+ checkResultType("select struct.t.c1 from struct.t f0",
+ "RecordType(VARCHAR(20) NOT NULL C1) NOT NULL");
+
+ // Resolve F0.F0.C1 as secondary-level column F0.C1, since the first F0 is recognized
+ // as the table alias here.
+ checkResultType("select f0.f0.c1 from struct.t f0",
+ "RecordType(INTEGER NOT NULL C1) NOT NULL");
+
+ // Resolve T.F0.C1 as secondary-level column F0.C1, since T is recognized as the table
+ // name.
+ checkResultType("select t.f0.c1 from struct.t f0",
+ "RecordType(INTEGER NOT NULL C1) NOT NULL");
+
+ // Resolve STRUCT.T.F0.C1 as secondary-level column F0.C1, since STRUCT.T is
+ // recognized as the schema and table name.
+ checkResultType("select struct.t.f0.c1 from struct.t f0",
+ "RecordType(INTEGER NOT NULL C1) NOT NULL");
+
+ // Resolve struct type F1 with wildcard.
+ checkResultType("select f1.* from struct.t",
+ "RecordType(INTEGER NOT NULL C0, INTEGER NOT NULL C2) NOT NULL");
+
+ // Fail non-existent column B0.
+ checkFails("select ^b0^ from struct.t",
+ "Column 'B0' not found in any table");
+
+ // Fail struct type with no wildcard.
+ checkFails("select ^f1^ from struct.t",
+ "Unknown identifier 'F1'");
+ }
}
// End SqlValidatorTest.java
[5/6] calcite git commit: Complete [CALCITE-1208] by giving "resolve"
a call-back, so it can make multiple matches
Posted by jh...@apache.org.
Complete [CALCITE-1208] by giving "resolve" a call-back, so it can make multiple matches
Change parser to make 'SELECT t.*.c' illegal.
Address Jinfeng's review comments.
Add a test for converting a query with UNION in FROM clause.
Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/0938c7b6
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/0938c7b6
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/0938c7b6
Branch: refs/heads/master
Commit: 0938c7b6d767e3242874d87a30d9112512d9243a
Parents: a859f93
Author: Julian Hyde <jh...@apache.org>
Authored: Fri May 6 11:57:25 2016 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Sep 15 17:02:42 2016 -0700
----------------------------------------------------------------------
core/src/main/codegen/templates/Parser.jj | 25 +-
.../calcite/prepare/CalciteCatalogReader.java | 15 +-
.../AggregateExpandDistinctAggregatesRule.java | 2 +-
.../calcite/rel/type/DynamicRecordTypeImpl.java | 41 ++-
.../apache/calcite/rel/type/RelDataType.java | 14 +-
.../calcite/rel/type/RelDataTypeFactory.java | 36 ++-
.../rel/type/RelDataTypeFactoryImpl.java | 61 +++-
.../calcite/rel/type/RelDataTypeHolder.java | 27 +-
.../calcite/rel/type/RelDataTypeImpl.java | 58 +---
.../apache/calcite/rel/type/RelRecordType.java | 36 ++-
.../org/apache/calcite/rel/type/StructKind.java | 83 ++++++
.../apache/calcite/runtime/CalciteResource.java | 3 +
.../org/apache/calcite/runtime/FlatLists.java | 41 +++
.../org/apache/calcite/sql/SqlIdentifier.java | 16 +
.../calcite/sql/validate/CatalogScope.java | 10 -
.../calcite/sql/validate/DelegatingScope.java | 265 +++++++++++++----
.../apache/calcite/sql/validate/EmptyScope.java | 6 +-
.../apache/calcite/sql/validate/ListScope.java | 136 +++++----
.../calcite/sql/validate/ParameterScope.java | 9 -
.../calcite/sql/validate/SqlQualified.java | 5 +-
.../sql/validate/SqlValidatorCatalogReader.java | 8 -
.../calcite/sql/validate/SqlValidatorImpl.java | 74 +++--
.../calcite/sql/validate/SqlValidatorScope.java | 120 +++++++-
.../calcite/sql/validate/SqlValidatorUtil.java | 48 ++-
.../apache/calcite/sql/validate/WithScope.java | 15 +-
.../calcite/sql2rel/SqlToRelConverter.java | 61 ++--
.../apache/calcite/util/ImmutableIntList.java | 6 +-
.../main/java/org/apache/calcite/util/Pair.java | 5 +
.../calcite/runtime/CalciteResource.properties | 1 +
.../calcite/sql/parser/SqlParserTest.java | 15 +
.../apache/calcite/sql/test/SqlAdvisorTest.java | 2 +
.../apache/calcite/test/MockCatalogReader.java | 162 ++++++----
.../calcite/test/SqlToRelConverterTest.java | 10 +-
.../apache/calcite/test/SqlValidatorTest.java | 294 +++++++++++++------
.../calcite/test/SqlToRelConverterTest.xml | 31 ++
.../adapter/splunk/SplunkPushDownRule.java | 13 +-
36 files changed, 1165 insertions(+), 589 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/codegen/templates/Parser.jj
----------------------------------------------------------------------
diff --git a/core/src/main/codegen/templates/Parser.jj b/core/src/main/codegen/templates/Parser.jj
index 43d6cd5..2d97cf3 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -3686,20 +3686,19 @@ SqlIdentifier CompoundIdentifier() :
}
(
<DOT>
- (
- p = Identifier() {
- star = false;
- list.add(p);
- posList.add(getPos());
- }
- |
- <STAR> {
- star = true;
- list.add("*");
- posList.add(getPos());
- }
- )
+ p = Identifier() {
+ list.add(p);
+ posList.add(getPos());
+ }
) *
+ (
+ <DOT>
+ <STAR> {
+ star = true;
+ list.add("");
+ posList.add(getPos());
+ }
+ )?
{
SqlParserPos pos = SqlParserPos.sum(posList);
if (star) {
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java b/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
index 9d8f3ef..0e14c83 100644
--- a/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
+++ b/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
@@ -74,7 +74,6 @@ public class CalciteCatalogReader implements Prepare.CatalogReader {
final CalciteSchema rootSchema;
final JavaTypeFactory typeFactory;
private final List<String> defaultSchema;
- private final boolean elideRecord = true;
private final boolean caseSensitive;
public CalciteCatalogReader(
@@ -203,27 +202,17 @@ public class CalciteCatalogReader implements Prepare.CatalogReader {
}
public RelDataTypeField field(RelDataType rowType, String alias) {
- return SqlValidatorUtil.lookupField(caseSensitive, elideRecord, rowType,
- alias);
- }
-
- public int fieldOrdinal(RelDataType rowType, String alias) {
- RelDataTypeField field = field(rowType, alias);
- return field != null ? field.getIndex() : -1;
+ return SqlValidatorUtil.lookupField(caseSensitive, rowType, alias);
}
public boolean matches(String string, String name) {
return Util.matches(caseSensitive, string, name);
}
- public int match(List<String> strings, String name) {
- return Util.findMatch(strings, name, caseSensitive);
- }
-
public RelDataType createTypeFromProjection(final RelDataType type,
final List<String> columnNameList) {
return SqlValidatorUtil.createTypeFromProjection(type, columnNameList,
- typeFactory, caseSensitive, elideRecord);
+ typeFactory, caseSensitive);
}
public void lookupOperatorOverloads(final SqlIdentifier opName,
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java
index a5f27f7..de12e42 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateExpandDistinctAggregatesRule.java
@@ -571,7 +571,7 @@ public final class AggregateExpandDistinctAggregatesRule extends RelOptRule {
List<Integer> argList) {
ImmutableIntList list = ImmutableIntList.of();
for (int arg : argList) {
- list = list.add(remap(groupSet, arg));
+ list = list.append(remap(groupSet, arg));
}
return list;
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/type/DynamicRecordTypeImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/DynamicRecordTypeImpl.java b/core/src/main/java/org/apache/calcite/rel/type/DynamicRecordTypeImpl.java
index 21206ac..82b6714 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/DynamicRecordTypeImpl.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/DynamicRecordTypeImpl.java
@@ -24,35 +24,34 @@ import java.util.Collections;
import java.util.List;
/**
- * Implementation of RelDataType for dynamic table. It's used in
- * Sql validation phase, where field list is mutable for getField() call.
+ * Implementation of {@link RelDataType} for a dynamic table.
*
- * <p>After Sql validation, a normal RelDataTypeImpl with immutable field list
- * would take place of DynamicRecordTypeImpl instance for dynamic table. </p>
+ * <p>It's used during SQL validation, where the field list is mutable for
+ * the getField() call. After SQL validation, a normal {@link RelDataTypeImpl}
+ * with an immutable field list takes the place of the DynamicRecordTypeImpl
+ * instance.
*/
public class DynamicRecordTypeImpl extends DynamicRecordType {
-
- private final RelDataTypeFactory typeFactory;
private final RelDataTypeHolder holder;
+ /** Creates a DynamicRecordTypeImpl. */
public DynamicRecordTypeImpl(RelDataTypeFactory typeFactory) {
- this.typeFactory = typeFactory;
- this.holder = new RelDataTypeHolder();
- this.holder.setRelDataTypeFactory(typeFactory);
+ this.holder = new RelDataTypeHolder(typeFactory);
computeDigest();
}
- public List<RelDataTypeField> getFieldList() {
- return holder.getFieldList(typeFactory);
+ @Override public List<RelDataTypeField> getFieldList() {
+ return holder.getFieldList();
}
- public int getFieldCount() {
+ @Override public int getFieldCount() {
return holder.getFieldCount();
}
- public RelDataTypeField getField(String fieldName, boolean caseSensitive, boolean elideRecord) {
- Pair<RelDataTypeField, Boolean> pair = holder.getFieldOrInsert(typeFactory, fieldName,
- caseSensitive);
+ @Override public RelDataTypeField getField(String fieldName,
+ boolean caseSensitive, boolean elideRecord) {
+ final Pair<RelDataTypeField, Boolean> pair =
+ holder.getFieldOrInsert(fieldName, caseSensitive);
// If a new field is added, we should re-compute the digest.
if (pair.right) {
computeDigest();
@@ -61,27 +60,27 @@ public class DynamicRecordTypeImpl extends DynamicRecordType {
return pair.left;
}
- public List<String> getFieldNames() {
+ @Override public List<String> getFieldNames() {
return holder.getFieldNames();
}
- public SqlTypeName getSqlTypeName() {
+ @Override public SqlTypeName getSqlTypeName() {
return SqlTypeName.ROW;
}
- public RelDataTypePrecedenceList getPrecedenceList() {
+ @Override public RelDataTypePrecedenceList getPrecedenceList() {
return new SqlTypeExplicitPrecedenceList(Collections.<SqlTypeName>emptyList());
}
protected void generateTypeString(StringBuilder sb, boolean withDetail) {
- sb.append("(DynamicRecordRow" + getFieldNames() + ")");
+ sb.append("(DynamicRecordRow").append(getFieldNames()).append(")");
}
- public boolean isStruct() {
+ @Override public boolean isStruct() {
return true;
}
- public RelDataTypeFamily getFamily() {
+ @Override public RelDataTypeFamily getFamily() {
return getSqlTypeName().getFamily();
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
index 442538e..7b18d67 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
@@ -36,12 +36,6 @@ public interface RelDataType /*extends Type*/ {
int SCALE_NOT_SPECIFIED = Integer.MIN_VALUE;
int PRECISION_NOT_SPECIFIED = -1;
- enum StructKind {
- FULLY_QUALIFIED,
- PEEK_FIELDS,
- PEEK_FIELDS_DEFAULT,
- }
-
//~ Methods ----------------------------------------------------------------
/**
@@ -83,9 +77,10 @@ public interface RelDataType /*extends Type*/ {
int getFieldCount();
/**
- * Gets the StructKind of a structured type.
+ * Returns the rule for resolving the fields of a structured type,
+ * or {@link StructKind#NONE} if this is not a structured type.
*
- * @return the StructKind that determines how its fields are resolved.
+ * @return the StructKind that determines how this type's fields are resolved
*/
StructKind getStructKind();
@@ -242,9 +237,10 @@ public interface RelDataType /*extends Type*/ {
RelDataTypeComparability getComparability();
/**
- *@return whether it has dynamic structure (for "schema-on-read" table)
+ * @return whether it has dynamic structure (for "schema-on-read" table)
*/
boolean isDynamicStruct();
+
}
// End RelDataType.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java
index 7c3e88c..1b3a1be 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java
@@ -50,7 +50,7 @@ public interface RelDataTypeFactory {
RelDataTypeSystem getTypeSystem();
/**
- * Creates a type which corresponds to a Java class.
+ * Creates a type that corresponds to a Java class.
*
* @param clazz the Java class used to define the type
* @return canonical Java type descriptor
@@ -66,28 +66,37 @@ public interface RelDataTypeFactory {
RelDataType createJoinType(RelDataType... types);
/**
- * Creates a type which represents a structured collection of fields, given
+ * Creates a type that represents a structured collection of fields, given
* lists of the names and types of the fields.
*
+ * @param kind Name resolution policy
* @param typeList types of the fields
* @param fieldNameList names of the fields
* @return canonical struct type descriptor
*/
+ RelDataType createStructType(StructKind kind,
+ List<RelDataType> typeList,
+ List<String> fieldNameList);
+
+ /** Creates a type that represents a structured collection of fields.
+ * Shorthand for <code>createStructType(StructKind.FULLY_QUALIFIED, typeList,
+ * fieldNameList)</code>. */
RelDataType createStructType(
List<RelDataType> typeList,
List<String> fieldNameList);
/**
- * Creates a type which represents a structured collection of fields,
+ * Creates a type that represents a structured collection of fields,
* obtaining the field information via a callback.
*
* @param fieldInfo callback for field information
* @return canonical struct type descriptor
*/
+ @Deprecated // to be removed before 2.0
RelDataType createStructType(FieldInfo fieldInfo);
/**
- * Creates a type which represents a structured collection of fieldList,
+ * Creates a type that represents a structured collection of fieldList,
* obtaining the field information from a list of (name, type) pairs.
*
* @param fieldList List of (name, type) pairs
@@ -140,7 +149,7 @@ public interface RelDataTypeFactory {
RelDataType copyType(RelDataType type);
/**
- * Creates a type which is the same as another type but with possibly
+ * Creates a type that is the same as another type but with possibly
* different nullability. The output type may be identical to the input
* type. For type systems without a concept of nullability, the return value
* is always the same as the input.
@@ -156,7 +165,7 @@ public interface RelDataTypeFactory {
boolean nullable);
/**
- * Creates a Type which is the same as another type but with possibly
+ * Creates a type that is the same as another type but with possibly
* different charset or collation. For types without a concept of charset or
* collation this function must throw an error.
*
@@ -179,7 +188,7 @@ public interface RelDataTypeFactory {
/**
* Returns the most general of a set of types (that is, one type to which
* they can all be cast), or null if conversion is not possible. The result
- * may be a new type which is less restrictive than any of the input types,
+ * may be a new type that is less restrictive than any of the input types,
* e.g. <code>leastRestrictive(INT, NUMERIC(3, 2))</code> could be
* {@code NUMERIC(12, 2)}.
*
@@ -288,8 +297,9 @@ public interface RelDataTypeFactory {
//~ Inner Interfaces -------------------------------------------------------
/**
- * Callback which provides enough information to create fields.
+ * Callback that provides enough information to create fields.
*/
+ @Deprecated // to be removed before 2.0
interface FieldInfo {
/**
* Returns the number of fields.
@@ -319,10 +329,11 @@ public interface RelDataTypeFactory {
* Implementation of {@link FieldInfo} that provides a fluid API to build
* a list of fields.
*/
+ @SuppressWarnings("deprecation")
class FieldInfoBuilder implements FieldInfo {
private final List<String> names = new ArrayList<>();
private final List<RelDataType> types = new ArrayList<>();
-
+ private StructKind kind = StructKind.FULLY_QUALIFIED;
private final RelDataTypeFactory typeFactory;
/**
@@ -418,6 +429,11 @@ public interface RelDataTypeFactory {
return this;
}
+ public FieldInfoBuilder kind(StructKind kind) {
+ this.kind = kind;
+ return this;
+ }
+
/**
* Makes sure that field names are unique.
*/
@@ -435,7 +451,7 @@ public interface RelDataTypeFactory {
* Creates a struct type with the current contents of this builder.
*/
public RelDataType build() {
- return typeFactory.createStructType(types, names);
+ return typeFactory.createStructType(kind, types, names);
}
}
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java
index eaff896..774a728 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java
@@ -22,7 +22,6 @@ import org.apache.calcite.sql.type.JavaToSqlTypeConversionRules;
import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.type.SqlTypeUtil;
-import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
import com.google.common.base.Preconditions;
@@ -41,6 +40,8 @@ import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import javax.annotation.Nonnull;
/**
* Abstract base for implementations of {@link RelDataTypeFactory}.
@@ -56,21 +57,20 @@ public abstract class RelDataTypeFactoryImpl implements RelDataTypeFactory {
.softValues()
.build(
new CacheLoader<Object, RelDataType>() {
- @Override public RelDataType load(Object key) {
- if (key instanceof RelDataType) {
- return (RelDataType) key;
+ @Override public RelDataType load(@Nonnull Object k) {
+ if (k instanceof RelDataType) {
+ return (RelDataType) k;
}
@SuppressWarnings("unchecked")
- final Pair<List<String>, List<RelDataType>> pair =
- (Pair<List<String>, List<RelDataType>>) key;
+ final Key key = (Key) k;
final ImmutableList.Builder<RelDataTypeField> list =
ImmutableList.builder();
- for (int i = 0; i < pair.left.size(); i++) {
+ for (int i = 0; i < key.names.size(); i++) {
list.add(
new RelDataTypeFieldImpl(
- pair.left.get(i), i, pair.right.get(i)));
+ key.names.get(i), i, key.types.get(i)));
}
- return new RelRecordType(list.build());
+ return new RelRecordType(key.kind, list.build());
}
});
@@ -132,18 +132,24 @@ public abstract class RelDataTypeFactoryImpl implements RelDataTypeFactory {
new RelCrossType(flattenedTypes, getFieldList(flattenedTypes)));
}
- // implement RelDataTypeFactory
public RelDataType createStructType(
final List<RelDataType> typeList,
final List<String> fieldNameList) {
+ return createStructType(StructKind.FULLY_QUALIFIED, typeList,
+ fieldNameList);
+ }
+
+ public RelDataType createStructType(StructKind kind,
+ final List<RelDataType> typeList,
+ final List<String> fieldNameList) {
assert typeList.size() == fieldNameList.size();
- return canonize(fieldNameList, typeList);
+ return canonize(kind, fieldNameList, typeList);
}
// implement RelDataTypeFactory
public RelDataType createStructType(
final RelDataTypeFactory.FieldInfo fieldInfo) {
- return canonize(
+ return canonize(StructKind.FULLY_QUALIFIED,
new AbstractList<String>() {
@Override public String get(int index) {
return fieldInfo.getFieldName(index);
@@ -346,16 +352,16 @@ public abstract class RelDataTypeFactoryImpl implements RelDataTypeFactory {
* key is more expensive, because it must be immutable and not hold
* references into other data structures.</p>
*/
- protected RelDataType canonize(
+ protected RelDataType canonize(final StructKind kind,
final List<String> names,
final List<RelDataType> types) {
- final RelDataType type = CACHE.getIfPresent(Pair.of(names, types));
+ final RelDataType type = CACHE.getIfPresent(new Key(kind, names, types));
if (type != null) {
return type;
}
final ImmutableList<String> names2 = ImmutableList.copyOf(names);
final ImmutableList<RelDataType> types2 = ImmutableList.copyOf(types);
- return CACHE.getUnchecked(Pair.of(names2, types2));
+ return CACHE.getUnchecked(new Key(kind, names2, types2));
}
/**
@@ -646,6 +652,31 @@ public abstract class RelDataTypeFactoryImpl implements RelDataTypeFactory {
return typeName;
}
}
+
+ /** Key to the data type cache. */
+ private static class Key {
+ private final StructKind kind;
+ private final List<String> names;
+ private final List<RelDataType> types;
+
+ Key(StructKind kind, List<String> names, List<RelDataType> types) {
+ this.kind = kind;
+ this.names = names;
+ this.types = types;
+ }
+
+ @Override public int hashCode() {
+ return Objects.hash(kind, names, types);
+ }
+
+ @Override public boolean equals(Object obj) {
+ return obj == this
+ || obj instanceof Key
+ && kind == ((Key) obj).kind
+ && names.equals(((Key) obj).names)
+ && types.equals(((Key) obj).types);
+ }
+ }
}
// End RelDataTypeFactoryImpl.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeHolder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeHolder.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeHolder.java
index 414045d..1a777ff 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeHolder.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeHolder.java
@@ -26,12 +26,15 @@ import java.util.List;
/**
* Holding the expandable list of fields for dynamic table.
*/
-public class RelDataTypeHolder {
- List<RelDataTypeField> fields = new ArrayList<>();
+class RelDataTypeHolder {
+ private final List<RelDataTypeField> fields = new ArrayList<>();
+ private final RelDataTypeFactory typeFactory;
- private RelDataTypeFactory typeFactory;
+ RelDataTypeHolder(RelDataTypeFactory typeFactory) {
+ this.typeFactory = typeFactory;
+ }
- public List<RelDataTypeField> getFieldList(RelDataTypeFactory typeFactory) {
+ public List<RelDataTypeField> getFieldList() {
return fields;
}
@@ -43,15 +46,12 @@ public class RelDataTypeHolder {
* Get field if exists, otherwise inserts a new field. The new field by default will have "any"
* type, except for the dynamic star field.
*
- * @param typeFactory RelDataTypeFactory
* @param fieldName Request field name
* @param caseSensitive Case Sensitive
* @return A pair of RelDataTypeField and Boolean. Boolean indicates whether a new field is added
* to this holder.
*/
- public Pair<RelDataTypeField, Boolean> getFieldOrInsert(RelDataTypeFactory typeFactory,
- String fieldName, boolean caseSensitive) {
-
+ Pair<RelDataTypeField, Boolean> getFieldOrInsert(String fieldName, boolean caseSensitive) {
// First check if this field name exists in our field list
for (RelDataTypeField f : fields) {
if (Util.matches(caseSensitive, f.getName(), fieldName)) {
@@ -75,16 +75,7 @@ public class RelDataTypeHolder {
}
public List<String> getFieldNames() {
- List<String> fieldNames = new ArrayList<>();
- for (RelDataTypeField f : fields) {
- fieldNames.add(f.getName());
- }
-
- return fieldNames;
- }
-
- public void setRelDataTypeFactory(RelDataTypeFactory typeFactory) {
- this.typeFactory = typeFactory;
+ return Pair.left(fields);
}
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java
index 0fc14f3..a559686 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java
@@ -84,36 +84,6 @@ public abstract class RelDataTypeImpl
return field;
}
}
- for (RelDataTypeField field : fieldList) {
- RelDataType dataType = field.getType();
- if (dataType.isStruct()
- && dataType.getStructKind() == StructKind.PEEK_FIELDS_DEFAULT) {
- for (RelDataTypeField subField : dataType.getFieldList()) {
- if (Util.matches(caseSensitive, subField.getName(), fieldName)) {
- return subField;
- }
- }
- }
- }
- RelDataTypeField matched = null;
- for (RelDataTypeField field : fieldList) {
- RelDataType dataType = field.getType();
- if (dataType.isStruct()
- && dataType.getStructKind() == StructKind.PEEK_FIELDS) {
- for (RelDataTypeField subField : dataType.getFieldList()) {
- if (Util.matches(caseSensitive, subField.getName(), fieldName)) {
- if (matched == null) {
- matched = subField;
- } else {
- // TODO throw columnAmbiguous
- }
- }
- }
- }
- }
- if (matched != null) {
- return matched;
- }
if (elideRecord) {
final List<Slot> slots = Lists.newArrayList();
getFieldRecurse(slots, this, 0, fieldName, caseSensitive);
@@ -172,7 +142,6 @@ public abstract class RelDataTypeImpl
}
}
- // implement RelDataType
public List<RelDataTypeField> getFieldList() {
assert isStruct();
return fieldList;
@@ -182,18 +151,15 @@ public abstract class RelDataTypeImpl
return Pair.left(fieldList);
}
- // implement RelDataType
public int getFieldCount() {
assert isStruct() : this;
return fieldList.size();
}
public StructKind getStructKind() {
- assert isStruct() : this;
- return StructKind.FULLY_QUALIFIED;
+ return isStruct() ? StructKind.FULLY_QUALIFIED : StructKind.NONE;
}
- // implement RelDataType
public RelDataType getComponentType() {
// this is not a collection type
return null;
@@ -209,13 +175,11 @@ public abstract class RelDataTypeImpl
return null;
}
- // implement RelDataType
public boolean isStruct() {
return fieldList != null;
}
- // implement RelDataType
- public boolean equals(Object obj) {
+ @Override public boolean equals(Object obj) {
if (obj instanceof RelDataTypeImpl) {
final RelDataTypeImpl that = (RelDataTypeImpl) obj;
return this.digest.equals(that.digest);
@@ -223,52 +187,42 @@ public abstract class RelDataTypeImpl
return false;
}
- // implement RelDataType
- public int hashCode() {
+ @Override public int hashCode() {
return digest.hashCode();
}
- // implement RelDataType
public String getFullTypeString() {
return digest;
}
- // implement RelDataType
public boolean isNullable() {
return false;
}
- // implement RelDataType
public Charset getCharset() {
return null;
}
- // implement RelDataType
public SqlCollation getCollation() {
return null;
}
- // implement RelDataType
public SqlIntervalQualifier getIntervalQualifier() {
return null;
}
- // implement RelDataType
public int getPrecision() {
return PRECISION_NOT_SPECIFIED;
}
- // implement RelDataType
public int getScale() {
return SCALE_NOT_SPECIFIED;
}
- // implement RelDataType
public SqlTypeName getSqlTypeName() {
return null;
}
- // implement RelDataType
public SqlIdentifier getSqlIdentifier() {
SqlTypeName typeName = getSqlTypeName();
if (typeName == null) {
@@ -279,7 +233,6 @@ public abstract class RelDataTypeImpl
SqlParserPos.ZERO);
}
- // implement RelDataType
public RelDataTypeFamily getFamily() {
// by default, put each type into its own family
return this;
@@ -310,14 +263,12 @@ public abstract class RelDataTypeImpl
digest = sb.toString();
}
- // implement RelDataType
- public String toString() {
+ @Override public String toString() {
StringBuilder sb = new StringBuilder();
generateTypeString(sb, false);
return sb.toString();
}
- // implement RelDataType
public RelDataTypePrecedenceList getPrecedenceList() {
// by default, make each type have a precedence list containing
// only other types in the same family
@@ -336,7 +287,6 @@ public abstract class RelDataTypeImpl
};
}
- // implement RelDataType
public RelDataTypeComparability getComparability() {
return RelDataTypeComparability.ALL;
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java b/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java
index 8d09cf4..7ae4dda 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java
@@ -19,6 +19,8 @@ package org.apache.calcite.rel.type;
import org.apache.calcite.linq4j.Ord;
import org.apache.calcite.sql.type.SqlTypeName;
+import com.google.common.base.Preconditions;
+
import java.io.Serializable;
import java.util.List;
@@ -26,37 +28,55 @@ import java.util.List;
* RelRecordType represents a structured type having named fields.
*/
public class RelRecordType extends RelDataTypeImpl implements Serializable {
+ /** Name resolution policy; usually {@link StructKind#FULLY_QUALIFIED}. */
+ private final StructKind kind;
+
//~ Constructors -----------------------------------------------------------
/**
* Creates a <code>RecordType</code>. This should only be called from a
* factory method.
*/
- public RelRecordType(List<RelDataTypeField> fields) {
+ public RelRecordType(StructKind kind, List<RelDataTypeField> fields) {
super(fields);
+ this.kind = Preconditions.checkNotNull(kind);
computeDigest();
}
+ public RelRecordType(List<RelDataTypeField> fields) {
+ this(StructKind.FULLY_QUALIFIED, fields);
+ }
+
//~ Methods ----------------------------------------------------------------
- // implement RelDataType
- public SqlTypeName getSqlTypeName() {
+ @Override public SqlTypeName getSqlTypeName() {
return SqlTypeName.ROW;
}
- // implement RelDataType
- public boolean isNullable() {
+ @Override public boolean isNullable() {
return false;
}
- // implement RelDataType
- public int getPrecision() {
+ @Override public int getPrecision() {
// REVIEW: angel 18-Aug-2005 Put in fake implementation for precision
return 0;
}
+ @Override public StructKind getStructKind() {
+ return kind;
+ }
+
protected void generateTypeString(StringBuilder sb, boolean withDetail) {
- sb.append("RecordType(");
+ sb.append("RecordType");
+ switch (kind) {
+ case PEEK_FIELDS:
+ sb.append(":peek");
+ break;
+ case PEEK_FIELDS_DEFAULT:
+ sb.append(":peek_default");
+ break;
+ }
+ sb.append("(");
for (Ord<RelDataTypeField> ord : Ord.zip(fieldList)) {
if (ord.i > 0) {
sb.append(", ");
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/rel/type/StructKind.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/type/StructKind.java b/core/src/main/java/org/apache/calcite/rel/type/StructKind.java
new file mode 100644
index 0000000..b10f816
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/type/StructKind.java
@@ -0,0 +1,83 @@
+/*
+ * 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.calcite.rel.type;
+
+/**
+ * Describes a policy for resolving fields in record types.
+ *
+ * <p>The usual value is {@link #FULLY_QUALIFIED}.
+ *
+ * <p>A field whose record type is labeled {@link #PEEK_FIELDS} can be omitted.
+ * In Phoenix, column families are represented by fields like this.
+ * {@link #PEEK_FIELDS_DEFAULT} is similar, but represents the default column
+ * family, so it will win in the event of a tie.
+ *
+ * <p>SQL usually disallows a record type. For instance,
+ *
+ * <blockquote>SELECT address.zip FROM Emp AS e</blockquote>
+ *
+ * is disallowed because {@code address} "looks like" a table alias. You'd have
+ * to write
+ *
+ * <blockquote>SELECT e.address.zip FROM Emp AS e</blockquote>
+ *
+ * <p>But if a table has one or more columns that are record-typed and are
+ * labeled {@link #PEEK_FIELDS} or {@link #PEEK_FIELDS_DEFAULT} we suspend that
+ * rule and would allow {@code address.zip}.
+ *
+ * <p>If there are multiple matches, we choose the one that is:
+ * <ol>
+ * <li>Shorter. If you write {@code zipcode}, {@code address.zipcode} will
+ * be preferred over {@code product.supplier.zipcode}.
+ * <li>Uses as little skipping as possible. A match that is fully-qualified
+ * will beat one that uses {@code PEEK_FIELDS_DEFAULT} at some point, which
+ * will beat one that uses {@code PEEK_FIELDS} at some point.
+ * </ol>
+ */
+public enum StructKind {
+ /** This is not a structured type. */
+ NONE,
+
+ /** This is a traditional structured type, where each field must be
+ * referenced explicitly.
+ *
+ * <p>Also, when referencing a struct column, you
+ * need to qualify it with the table alias, per standard SQL. For instance,
+ * {@code SELECT c.address.zipcode FROM customer AS c}
+ * is valid but
+ * {@code SELECT address.zipcode FROM customer}
+ * it not valid.
+ */
+ FULLY_QUALIFIED,
+
+ /** As {@link #PEEK_FIELDS}, but takes priority if another struct-typed
+ * field also has a field of the name being sought.
+ *
+ * <p>In Phoenix, only one of a table's columns is labeled
+ * {@code PEEK_FIELDS_DEFAULT} - the default column family - but in principle
+ * there could be more than one. */
+ PEEK_FIELDS_DEFAULT,
+
+ /** If a field has this type, you can see its fields without qualifying them
+ * with the name of this field.
+ *
+ * <p>For example, if {@code address} is labeled {@code PEEK_FIELDS}, you
+ * could write {@code zipcode} as shorthand for {@code address.zipcode}. */
+ PEEK_FIELDS,
+}
+
+// End StructKind.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
index b936859..f261659 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -607,6 +607,9 @@ public interface CalciteResource {
@BaseMessage("Table ''{0}'' not found")
ExInst<CalciteException> tableNotFound(String tableName);
+ @BaseMessage("Not a record type. The ''*'' operator requires a record")
+ ExInst<SqlValidatorException> starRequiresRecordType();
+
@BaseMessage("FILTER expression must be of type BOOLEAN")
ExInst<CalciteException> filterMustBeBoolean();
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/runtime/FlatLists.java b/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
index eba51b7..4ab4ef2 100644
--- a/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
+++ b/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
@@ -18,7 +18,10 @@ package org.apache.calcite.runtime;
import org.apache.calcite.util.ImmutableNullableList;
+import com.google.common.collect.ImmutableList;
+
import java.util.AbstractList;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
@@ -238,6 +241,17 @@ public class FlatLists {
}
}
+ /** Returns a list that consists of a given list plus an element. */
+ public static <E> List<E> append(List<E> list, E e) {
+ if (list instanceof AbstractFlatList) {
+ //noinspection unchecked
+ return ((AbstractFlatList) list).append(e);
+ }
+ final List<E> newList = new ArrayList<>(list);
+ newList.add(e);
+ return FlatLists.of(newList);
+ }
+
/** Base class for flat lists. */
public abstract static class AbstractFlatList<T>
extends AbstractImmutableList<T> implements RandomAccess {
@@ -246,6 +260,9 @@ public class FlatLists {
return Arrays.asList((T[]) toArray());
}
+ /** Returns a list that consists of a this list's elements plus a given
+ * element. */
+ public abstract List<T> append(T e);
}
/**
@@ -348,6 +365,10 @@ public class FlatLists {
public int compareTo(List o) {
return ComparableListImpl.compare((List) this, o);
}
+
+ public List<T> append(T e) {
+ return new Flat2List<T>(t0, e);
+ }
}
/**
@@ -469,6 +490,10 @@ public class FlatLists {
public int compareTo(List o) {
return ComparableListImpl.compare((List) this, o);
}
+
+ public List<T> append(T e) {
+ return new Flat3List<T>(t0, t1, e);
+ }
}
/**
@@ -609,6 +634,10 @@ public class FlatLists {
public int compareTo(List o) {
return ComparableListImpl.compare((List) this, o);
}
+
+ public List<T> append(T e) {
+ return new Flat4List<T>(t0, t1, t2, e);
+ }
}
/**
@@ -768,6 +797,10 @@ public class FlatLists {
public int compareTo(List o) {
return ComparableListImpl.compare((List) this, o);
}
+
+ public List<T> append(T e) {
+ return new Flat5List<T>(t0, t1, t2, t3, e);
+ }
}
/**
@@ -946,6 +979,10 @@ public class FlatLists {
public int compareTo(List o) {
return ComparableListImpl.compare((List) this, o);
}
+
+ public List<T> append(T e) {
+ return new Flat6List<T>(t0, t1, t2, t3, t4, e);
+ }
}
/**
@@ -1144,6 +1181,10 @@ public class FlatLists {
public int compareTo(List o) {
return ComparableListImpl.compare((List) this, o);
}
+
+ public List<T> append(T e) {
+ return ImmutableList.of(t0, t1, t2, t3, t5, e);
+ }
}
/** Empty list that implements the {@link Comparable} interface. */
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java b/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java
index d9b44ba..d070150 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java
@@ -30,6 +30,7 @@ import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -173,6 +174,21 @@ public class SqlIdentifier extends SqlNode {
}
}
+ /** Returns an identifier that is the same as this except with a component
+ * added at a given position. Does not modify this identifier. */
+ public SqlIdentifier add(int i, String name, SqlParserPos pos) {
+ final List<String> names2 = new ArrayList<>(names);
+ names2.add(i, name);
+ final List<SqlParserPos> pos2;
+ if (componentPositions == null) {
+ pos2 = null;
+ } else {
+ pos2 = new ArrayList<>(componentPositions);
+ pos2.add(i, pos);
+ }
+ return new SqlIdentifier(names2, collation, pos, pos2);
+ }
+
/**
* Returns the position of the <code>i</code>th component of a compound
* identifier, or the position of the whole identifier if that information
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/sql/validate/CatalogScope.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/CatalogScope.java b/core/src/main/java/org/apache/calcite/sql/validate/CatalogScope.java
index c2ef43e..1420123 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/CatalogScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/CatalogScope.java
@@ -73,16 +73,6 @@ class CatalogScope extends DelegatingScope {
throw new UnsupportedOperationException();
}
- public SqlValidatorNamespace resolve(List<String> names,
- SqlValidatorScope[] ancestorOut, int[] offsetOut) {
- final ImmutableList<String> nameList =
- ImmutableList.<String>builder().addAll(this.names).addAll(names)
- .build();
- if (schemaNames.contains(nameList)) {
- return new SchemaNamespace(validator, nameList);
- }
- return null;
- }
}
// End CatalogScope.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java b/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java
index d48bb50..491ea1d 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/DelegatingScope.java
@@ -19,6 +19,7 @@ package org.apache.calcite.sql.validate;
import org.apache.calcite.rel.type.DynamicRecordType;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rel.type.StructKind;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlNode;
@@ -29,9 +30,13 @@ import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.util.Pair;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
+import java.util.Map;
import static org.apache.calcite.util.Static.RESOURCE;
@@ -74,11 +79,42 @@ public abstract class DelegatingScope implements SqlValidatorScope {
throw new UnsupportedOperationException();
}
- public SqlValidatorNamespace resolve(
- List<String> names,
- SqlValidatorScope[] ancestorOut,
- int[] offsetOut) {
- return parent.resolve(names, ancestorOut, offsetOut);
+ public void resolve(List<String> names, boolean deep, Resolved resolved) {
+ parent.resolve(names, deep, resolved);
+ }
+
+ /** If a record type allows implicit references to fields, recursively looks
+ * into the fields. Otherwise returns immediately. */
+ void resolveInNamespace(SqlValidatorNamespace ns, List<String> names,
+ Path path, Resolved resolved) {
+ if (names.isEmpty()) {
+ resolved.found(ns, this, path);
+ return;
+ }
+ final RelDataType rowType = ns.getRowType();
+ if (rowType.isStruct()) {
+ final String name = names.get(0);
+ final RelDataTypeField field0 =
+ validator.catalogReader.field(rowType, name);
+ if (field0 != null) {
+ final SqlValidatorNamespace ns2 = ns.lookupChild(field0.getName());
+ final Step path2 = path.add(rowType, field0.getIndex(),
+ StructKind.FULLY_QUALIFIED);
+ resolveInNamespace(ns2, names.subList(1, names.size()), path2,
+ resolved);
+ } else {
+ for (RelDataTypeField field : rowType.getFieldList()) {
+ switch (field.getType().getStructKind()) {
+ case PEEK_FIELDS:
+ case PEEK_FIELDS_DEFAULT:
+ final Step path2 = path.add(rowType, field.getIndex(),
+ field.getType().getStructKind());
+ final SqlValidatorNamespace ns2 = ns.lookupChild(field.getName());
+ resolveInNamespace(ns2, names, path2, resolved);
+ }
+ }
+ }
+ }
}
protected void addColumnNames(
@@ -113,6 +149,11 @@ public abstract class DelegatingScope implements SqlValidatorScope {
return parent.findQualifyingTableName(columnName, ctx);
}
+ protected Map<String, SqlValidatorNamespace>
+ findQualifyingTables(String columnName) {
+ return ImmutableMap.of();
+ }
+
public RelDataType resolveColumn(String name, SqlNode ctx) {
return parent.resolveColumn(name, ctx);
}
@@ -148,6 +189,7 @@ public abstract class DelegatingScope implements SqlValidatorScope {
return SqlQualified.create(this, 1, null, identifier);
}
+ final SqlIdentifier previous = identifier;
String columnName;
switch (identifier.names.size()) {
case 1: {
@@ -159,63 +201,167 @@ public abstract class DelegatingScope implements SqlValidatorScope {
final RelDataTypeField field =
validator.catalogReader.field(namespace.getRowType(), columnName);
+ if (field != null) {
+ if (hasAmbiguousUnresolvedStar(namespace.getRowType(), field,
+ columnName)) {
+ throw validator.newValidationError(identifier,
+ RESOURCE.columnAmbiguous(columnName));
+ }
- checkAmbiguousUnresolvedStar(namespace.getRowType(), field, identifier, columnName);
-
+ columnName = field.getName(); // use resolved field name
+ }
// todo: do implicit collation here
final SqlParserPos pos = identifier.getParserPosition();
- SqlIdentifier expanded =
- new SqlIdentifier(
- ImmutableList.of(tableName, field.getName()), // use resolved field name
- null,
- pos,
- ImmutableList.of(SqlParserPos.ZERO, pos));
- validator.setOriginal(expanded, identifier);
- return SqlQualified.create(this, 1, namespace, expanded);
+ identifier =
+ new SqlIdentifier(ImmutableList.of(tableName, columnName), null,
+ pos, ImmutableList.of(SqlParserPos.ZERO, pos));
}
-
+ // fall through
default: {
SqlValidatorNamespace fromNs = null;
+ Path fromPath = null;
+ final ResolvedImpl resolved = new ResolvedImpl();
final int size = identifier.names.size();
int i = size - 1;
for (; i > 0; i--) {
final SqlIdentifier prefix = identifier.getComponent(0, i);
- fromNs = resolve(prefix.names, null, null);
- if (fromNs != null) {
- if (fromNs.getEnclosingNode() != null) {
- String alias =
- SqlValidatorUtil.getAlias(fromNs.getEnclosingNode(), -1);
- if (alias != null
- && !alias.equals(identifier.names.get(i - 1))) {
- identifier = identifier.setName(i - 1, alias);
- }
- }
+ resolved.clear();
+ resolve(prefix.names, false, resolved);
+ if (resolved.count() == 1) {
+ final Resolve resolve = resolved.only();
+ fromNs = resolve.namespace;
+ fromPath = resolve.path;
break;
}
}
if (fromNs == null || fromNs instanceof SchemaNamespace) {
- final SqlIdentifier prefix1 = identifier.skipLast(1);
- throw validator.newValidationError(prefix1,
- RESOURCE.tableNameNotFound(prefix1.toString()));
+ // Look for a column not qualified by a table alias.
+ columnName = identifier.names.get(0);
+ final Map<String, SqlValidatorNamespace> map =
+ findQualifyingTables(columnName);
+ switch (map.size()) {
+ default:
+ final SqlIdentifier prefix1 = identifier.skipLast(1);
+ throw validator.newValidationError(prefix1,
+ RESOURCE.tableNameNotFound(prefix1.toString()));
+ case 1:
+ fromPath = resolved.emptyPath();
+ fromNs = map.entrySet().iterator().next().getValue();
+ }
+
+ // Throw an error if the table was not found.
+ // If one or more of the child namespaces allows peeking
+ // (e.g. if they are Phoenix column families) then we relax the SQL
+ // standard requirement that record fields are qualified by table alias.
+ if (!hasLiberalChild()) {
+ final SqlIdentifier prefix1 = identifier.skipLast(1);
+ throw validator.newValidationError(prefix1,
+ RESOURCE.tableNameNotFound(prefix1.toString()));
+ }
+ }
+
+ // If a table alias is part of the identifier, make sure that the table
+ // alias uses the same case as it was defined. For example, in
+ //
+ // SELECT e.empno FROM Emp as E
+ //
+ // change "e.empno" to "E.empno".
+ if (fromNs.getEnclosingNode() != null) {
+ String alias =
+ SqlValidatorUtil.getAlias(fromNs.getEnclosingNode(), -1);
+ if (alias != null
+ && i > 0
+ && !alias.equals(identifier.names.get(i - 1))) {
+ identifier = identifier.setName(i - 1, alias);
+ }
}
RelDataType fromRowType = fromNs.getRowType();
- for (int j = i; j < size; j++) {
- final SqlIdentifier last = identifier.getComponent(j);
- columnName = last.getSimple();
- final RelDataTypeField field =
- validator.catalogReader.field(fromRowType, columnName);
- if (field == null) {
- throw validator.newValidationError(last,
- RESOURCE.columnNotFoundInTable(columnName,
- identifier.getComponent(0, j).toString()));
+ if (fromPath.stepCount() > 1) {
+ for (Step p : fromPath.steps()) {
+ fromRowType = fromRowType.getFieldList().get(p.i).getType();
}
+ ++i;
+ }
+ final SqlIdentifier suffix = identifier.getComponent(i, size);
+ resolved.clear();
+ resolveInNamespace(fromNs, suffix.names, resolved.emptyPath(), resolved);
+ final Path path;
+ switch (resolved.count()) {
+ case 0:
+ // Find the shortest suffix that also fails. Suppose we cannot resolve
+ // "a.b.c"; we find we cannot resolve "a.b" but can resolve "a". So,
+ // the error will be "Column 'a.b' not found".
+ int k = size - 1;
+ for (; k > i; --k) {
+ SqlIdentifier suffix2 = identifier.getComponent(i, k);
+ resolved.clear();
+ resolveInNamespace(fromNs, suffix2.names, resolved.emptyPath(),
+ resolved);
+ if (resolved.count() > 0) {
+ break;
+ }
+ }
+ final SqlIdentifier prefix = identifier.getComponent(0, i);
+ final SqlIdentifier suffix3 = identifier.getComponent(i, k + 1);
+ throw validator.newValidationError(suffix3,
+ RESOURCE.columnNotFoundInTable(suffix3.toString(), prefix.toString()));
+ case 1:
+ path = resolved.only().path;
+ break;
+ default:
+ final Comparator<Resolve> c =
+ new Comparator<Resolve>() {
+ public int compare(Resolve o1, Resolve o2) {
+ // Name resolution that uses fewer implicit steps wins.
+ int c = Integer.compare(worstKind(o1.path), worstKind(o2.path));
+ if (c != 0) {
+ return c;
+ }
+ // Shorter path wins
+ return Integer.compare(o1.path.stepCount(), o2.path.stepCount());
+ }
+
+ private int worstKind(Path path) {
+ int kind = -1;
+ for (Step step : path.steps()) {
+ kind = Math.max(kind, step.kind.ordinal());
+ }
+ return kind;
+ }
+ };
+ Collections.sort(resolved.resolves, c);
+ if (c.compare(resolved.resolves.get(0), resolved.resolves.get(1)) == 0) {
+ throw validator.newValidationError(suffix,
+ RESOURCE.columnAmbiguous(suffix.toString()));
+ }
+ path = resolved.resolves.get(0).path;
+ }
- checkAmbiguousUnresolvedStar(fromRowType, field, identifier, columnName);
-
- // normalize case to match definition, in a copy of the identifier
- identifier = identifier.setName(j, field.getName());
- fromRowType = field.getType();
+ // Normalize case to match definition, make elided fields explicit,
+ // and check that references to dynamic stars ("**") are unambiguous.
+ int k = i;
+ for (Step step : path.steps()) {
+ final RelDataTypeField field0 =
+ step.rowType.getFieldList().get(step.i);
+ final String fieldName = field0.getName();
+ switch (step.kind) {
+ case PEEK_FIELDS:
+ case PEEK_FIELDS_DEFAULT:
+ identifier = identifier.add(k, fieldName, SqlParserPos.ZERO);
+ break;
+ default:
+ final String name = identifier.names.get(k);
+ if (!fieldName.equals(name)) {
+ identifier = identifier.setName(k, fieldName);
+ }
+ if (hasAmbiguousUnresolvedStar(step.rowType, field0, name)) {
+ throw validator.newValidationError(identifier,
+ RESOURCE.columnAmbiguous(name));
+ }
+ }
+ ++k;
}
+
if (i > 1) {
// Simplify overqualified identifiers.
// For example, schema.emp.deptno becomes emp.deptno.
@@ -226,11 +372,19 @@ public abstract class DelegatingScope implements SqlValidatorScope {
// SELECT schema.emp.deptno FROM schema.emp AS e
identifier = identifier.getComponent(i - 1, identifier.names.size());
}
+
+ if (!previous.equals(identifier)) {
+ validator.setOriginal(identifier, previous);
+ }
return SqlQualified.create(this, i, fromNs, identifier);
}
}
}
+ protected boolean hasLiberalChild() {
+ return false;
+ }
+
public void validateExpr(SqlNode expr) {
// Do not delegate to parent. An expression valid in this scope may not
// be valid in the parent scope.
@@ -248,27 +402,22 @@ public abstract class DelegatingScope implements SqlValidatorScope {
return parent.getOrderList();
}
- private void checkAmbiguousUnresolvedStar(RelDataType fromRowType, RelDataTypeField field,
- SqlIdentifier identifier, String columnName) {
-
- if (field != null
- && field.isDynamicStar()
+ /** Returns whether {@code rowType} contains more than one star column.
+ * Having more than one star columns implies ambiguous column. */
+ private boolean hasAmbiguousUnresolvedStar(RelDataType rowType,
+ RelDataTypeField field, String columnName) {
+ if (field.isDynamicStar()
&& !DynamicRecordType.isDynamicStarColName(columnName)) {
- // Make sure fromRowType only contains one star column.
- // Having more than one star columns implies ambiguous column.
int count = 0;
- for (RelDataTypeField possibleStar : fromRowType.getFieldList()) {
+ for (RelDataTypeField possibleStar : rowType.getFieldList()) {
if (possibleStar.isDynamicStar()) {
- count++;
+ if (++count > 1) {
+ return true;
+ }
}
}
-
- if (count > 1) {
- throw validator.newValidationError(identifier,
- RESOURCE.columnAmbiguous(columnName));
- }
}
-
+ return false;
}
/**
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java b/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java
index 9da3bff..e7f0ff8 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/EmptyScope.java
@@ -64,11 +64,7 @@ class EmptyScope implements SqlValidatorScope {
throw new UnsupportedOperationException();
}
- public SqlValidatorNamespace resolve(
- List<String> names,
- SqlValidatorScope[] ancestorOut,
- int[] offsetOut) {
- return null;
+ public void resolve(List<String> names, boolean deep, Resolved resolved) {
}
public SqlValidatorNamespace getTableNamespace(List<String> names) {
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/sql/validate/ListScope.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/ListScope.java b/core/src/main/java/org/apache/calcite/sql/validate/ListScope.java
index 8567cc5..c4d292f 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/ListScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/ListScope.java
@@ -16,15 +16,21 @@
*/
package org.apache.calcite.sql.validate;
+import org.apache.calcite.linq4j.Ord;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rel.type.StructKind;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
+import com.google.common.collect.ImmutableList;
+
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import static org.apache.calcite.util.Static.RESOURCE;
@@ -39,7 +45,7 @@ public abstract class ListScope extends DelegatingScope {
* List of child {@link SqlValidatorNamespace} objects and their names.
*/
protected final List<Pair<String, SqlValidatorNamespace>> children =
- new ArrayList<Pair<String, SqlValidatorNamespace>>();
+ new ArrayList<>();
//~ Constructors -----------------------------------------------------------
@@ -63,57 +69,30 @@ public abstract class ListScope extends DelegatingScope {
return Pair.right(children);
}
- protected SqlValidatorNamespace getChild(String alias) {
- if (alias == null) {
- if (children.size() != 1) {
- throw Util.newInternal(
- "no alias specified, but more than one table in from list");
- }
- return children.get(0).right;
- } else {
- final int i = validator.catalogReader.match(Pair.left(children), alias);
- if (i >= 0) {
- return children.get(i).right;
- }
- return null;
- }
- }
-
- /** Returns a child namespace that matches a fully-qualified list of names.
- * This will be a schema-qualified table, for example
- *
- * <blockquote><pre>SELECT sales.emp.empno FROM sales.emp</pre></blockquote>
- */
- protected SqlValidatorNamespace getChild(List<String> names) {
- int i = findChild(names);
- return i < 0 ? null : children.get(i).right;
- }
-
- protected int findChild(List<String> names) {
- for (int i = 0; i < children.size(); i++) {
- Pair<String, SqlValidatorNamespace> child = children.get(i);
- if (child.left != null) {
- if (validator.catalogReader.matches(child.left, Util.last(names))) {
- if (names.size() == 1) {
- return i;
- }
- } else {
+ private int findChild(List<String> names) {
+ for (Ord<Pair<String, SqlValidatorNamespace>> child : Ord.zip(children)) {
+ String lastName = Util.last(names);
+ if (child.e.left != null) {
+ if (!validator.catalogReader.matches(child.e.left, lastName)) {
// Alias does not match last segment. Don't consider the
// fully-qualified name. E.g.
// SELECT sales.emp.name FROM sales.emp AS otherAlias
continue;
}
+ if (names.size() == 1) {
+ return child.i;
+ }
}
// Look up the 2 tables independently, in case one is qualified with
// catalog & schema and the other is not.
- final SqlValidatorTable table = child.right.getTable();
+ final SqlValidatorTable table = child.e.right.getTable();
if (table != null) {
final SqlValidatorTable table2 =
validator.catalogReader.getTable(names);
if (table2 != null
&& table.getQualifiedName().equals(table2.getQualifiedName())) {
- return i;
+ return child.i;
}
}
}
@@ -134,47 +113,84 @@ public abstract class ListScope extends DelegatingScope {
parent.findAliases(result);
}
- public Pair<String, SqlValidatorNamespace>
+ @Override public Pair<String, SqlValidatorNamespace>
findQualifyingTableName(final String columnName, SqlNode ctx) {
- int count = 0;
- Pair<String, SqlValidatorNamespace> tableName = null;
- for (Pair<String, SqlValidatorNamespace> child : children) {
- final RelDataType rowType = child.right.getRowType();
- if (validator.catalogReader.field(rowType, columnName) != null) {
- tableName = child;
- count++;
- }
- }
- switch (count) {
+ final Map<String, SqlValidatorNamespace> map =
+ findQualifyingTables(columnName);
+ switch (map.size()) {
case 0:
return parent.findQualifyingTableName(columnName, ctx);
case 1:
- return tableName;
+ return Pair.of(map.entrySet().iterator().next());
default:
throw validator.newValidationError(ctx,
RESOURCE.columnAmbiguous(columnName));
}
}
- public SqlValidatorNamespace resolve(
- List<String> names,
- SqlValidatorScope[] ancestorOut,
- int[] offsetOut) {
+ @Override public Map<String, SqlValidatorNamespace>
+ findQualifyingTables(String columnName) {
+ final Map<String, SqlValidatorNamespace> map = new HashMap<>();
+ for (Pair<String, SqlValidatorNamespace> child : children) {
+ final ResolvedImpl resolved = new ResolvedImpl();
+ resolve(ImmutableList.of(child.left, columnName), true, resolved);
+ if (resolved.count() > 0) {
+ map.put(child.getKey(), child.getValue());
+ }
+ }
+ return map;
+ }
+
+ @Override protected boolean hasLiberalChild() {
+ for (Pair<String, SqlValidatorNamespace> child : children) {
+ final RelDataType rowType = child.right.getRowType();
+ switch (rowType.getStructKind()) {
+ case PEEK_FIELDS:
+ case PEEK_FIELDS_DEFAULT:
+ return true;
+ }
+ for (RelDataTypeField field : rowType.getFieldList()) {
+ switch (field.getType().getStructKind()) {
+ case PEEK_FIELDS:
+ case PEEK_FIELDS_DEFAULT:
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override public void resolve(List<String> names, boolean deep,
+ Resolved resolved) {
// First resolve by looking through the child namespaces.
final int i = findChild(names);
if (i >= 0) {
- if (ancestorOut != null) {
- ancestorOut[0] = this;
+ final Step path =
+ resolved.emptyPath().add(null, i, StructKind.FULLY_QUALIFIED);
+ resolved.found(children.get(i).right, this, path);
+ return;
+ }
+
+ // Recursively look deeper into the record-valued fields of the namespace,
+ // if it allows skipping fields.
+ if (deep) {
+ for (Ord<Pair<String, SqlValidatorNamespace>> child : Ord.zip(children)) {
+ // If identifier starts with table alias, remove the alias.
+ final List<String> names2 =
+ validator.catalogReader.matches(child.e.left, names.get(0))
+ ? names.subList(1, names.size())
+ : names;
+ resolveInNamespace(child.e.right, names2, resolved.emptyPath(),
+ resolved);
}
- if (offsetOut != null) {
- offsetOut[0] = i;
+ if (resolved.count() > 0) {
+ return;
}
- return children.get(i).right;
}
// Then call the base class method, which will delegate to the
// parent scope.
- return parent.resolve(names, ancestorOut, offsetOut);
+ super.resolve(names, deep, resolved);
}
public RelDataType resolveColumn(String columnName, SqlNode ctx) {
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/sql/validate/ParameterScope.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/ParameterScope.java b/core/src/main/java/org/apache/calcite/sql/validate/ParameterScope.java
index f1a4e91..c9a0484 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/ParameterScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/ParameterScope.java
@@ -20,7 +20,6 @@ import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlIdentifier;
-import java.util.List;
import java.util.Map;
/**
@@ -57,14 +56,6 @@ public class ParameterScope extends EmptyScope {
return this;
}
- public SqlValidatorNamespace resolve(
- List<String> names,
- SqlValidatorScope[] ancestorOut,
- int[] offsetOut) {
- assert names.size() == 1;
- final RelDataType type = nameToTypeMap.get(names.get(0));
- return new ParameterNamespace(validator, type);
- }
}
// End ParameterScope.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/sql/validate/SqlQualified.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlQualified.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlQualified.java
index 067c324..55c1d01 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlQualified.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlQualified.java
@@ -61,8 +61,11 @@ public class SqlQualified {
return identifier.names;
}
final ImmutableList.Builder<String> builder = ImmutableList.builder();
+ final SqlValidatorScope.ResolvedImpl resolved =
+ new SqlValidatorScope.ResolvedImpl();
+ scope.resolve(Util.skipLast(identifier.names), false, resolved);
SqlValidatorNamespace namespace =
- scope.resolve(Util.skipLast(identifier.names), null, null);
+ resolved.count() == 1 ? resolved.only().namespace : null;
builder.add(identifier.names.get(0));
for (String name : Util.skip(identifier.names)) {
if (namespace != null) {
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java
index b157c71..b43f85c 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorCatalogReader.java
@@ -80,16 +80,8 @@ public interface SqlValidatorCatalogReader {
*/
RelDataTypeField field(RelDataType rowType, String alias);
- /**
- * Finds the ordinal of a field with a given name, using the case-sensitivity
- * of the current session.
- */
- int fieldOrdinal(RelDataType rowType, String alias);
-
boolean matches(String string, String name);
- int match(List<String> strings, String name);
-
RelDataType createTypeFromProjection(RelDataType type,
List<String> columnNameList);
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
index 44377d1..f4bbb79 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
@@ -445,7 +445,6 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
switch (identifier.names.size()) {
case 1:
for (Pair<String, SqlValidatorNamespace> p : scope.children) {
-
if (p.right.getRowType().isDynamicStruct()) {
// don't expand star if the underneath table is dynamic.
// Treat this star as a special field in validation/conversion and
@@ -482,33 +481,22 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
includeSystemVars);
}
}
-
}
return true;
+
default:
final SqlIdentifier prefixId = identifier.skipLast(1);
- final SqlValidatorNamespace fromNs;
- if (prefixId.names.size() == 1) {
- String tableName = prefixId.names.get(0);
- final SqlValidatorNamespace childNs = scope.getChild(tableName);
- if (childNs == null) {
- // e.g. "select r.* from e"
- throw newValidationError(identifier.getComponent(0),
- RESOURCE.unknownIdentifier(tableName));
- }
- final SqlNode from = childNs.getNode();
- fromNs = getNamespace(from);
- } else {
- fromNs = scope.getChild(prefixId.names);
- if (fromNs == null) {
- // e.g. "select s.t.* from e"
- throw newValidationError(prefixId,
- RESOURCE.unknownIdentifier(prefixId.toString()));
- }
- }
- assert fromNs != null;
+ final SqlValidatorScope.ResolvedImpl resolved =
+ new SqlValidatorScope.ResolvedImpl();
+ scope.resolve(prefixId.names, true, resolved);
+ if (resolved.count() == 0) {
+ // e.g. "select s.t.* from e"
+ // or "select r.* from e"
+ throw newValidationError(prefixId,
+ RESOURCE.unknownIdentifier(prefixId.toString()));
+ }
+ final SqlValidatorNamespace fromNs = resolved.only().namespace;
final RelDataType rowType = fromNs.getRowType();
-
if (rowType.isDynamicStruct()) {
// don't expand star if the underneath table is dynamic.
addToSelectList(
@@ -518,7 +506,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
prefixId.plus(DynamicRecordType.DYNAMIC_STAR_PREFIX, startPosition),
scope,
includeSystemVars);
- } else {
+ } else if (rowType.isStruct()) {
for (RelDataTypeField field : rowType.getFieldList()) {
String columnName = field.getName();
@@ -531,8 +519,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
scope,
includeSystemVars);
}
+ } else {
+ throw newValidationError(prefixId, RESOURCE.starRequiresRecordType());
}
-
return true;
}
}
@@ -701,7 +690,12 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
SqlValidatorNamespace ns = null;
for (String name : subNames) {
if (ns == null) {
- ns = scope.resolve(ImmutableList.of(name), null, null);
+ final SqlValidatorScope.ResolvedImpl resolved =
+ new SqlValidatorScope.ResolvedImpl();
+ scope.resolve(ImmutableList.of(name), false, resolved);
+ if (resolved.count() == 1) {
+ ns = resolved.only().namespace;
+ }
} else {
ns = ns.lookupChild(name);
}
@@ -932,10 +926,11 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
final SqlValidatorScope parentScope =
((DelegatingScope) scope).getParent();
if (id.isSimple()) {
- SqlValidatorNamespace ns =
- parentScope.resolve(id.names, null, null);
- if (ns != null) {
- return ns;
+ final SqlValidatorScope.ResolvedImpl resolved =
+ new SqlValidatorScope.ResolvedImpl();
+ parentScope.resolve(id.names, false, resolved);
+ if (resolved.count() == 1) {
+ return resolved.only().namespace;
}
}
}
@@ -4439,7 +4434,6 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
}
// Resolve the longest prefix of id that we can
- SqlValidatorNamespace resolvedNs;
int i;
for (i = id.names.size() - 1; i > 0; i--) {
// REVIEW jvs 9-June-2005: The name resolution rules used
@@ -4450,10 +4444,16 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
// we could do a better job if they were looked up via
// resolveColumn.
- resolvedNs = scope.resolve(id.names.subList(0, i), null, null);
- if (resolvedNs != null) {
+ final SqlValidatorScope.ResolvedImpl resolved =
+ new SqlValidatorScope.ResolvedImpl();
+ scope.resolve(id.names.subList(0, i), false, resolved);
+ if (resolved.count() == 1) {
// There's a namespace with the name we seek.
- type = resolvedNs.getRowType();
+ final SqlValidatorScope.Resolve resolve = resolved.only();
+ type = resolve.namespace.getRowType();
+ for (SqlValidatorScope.Step p : Util.skip(resolve.path.steps())) {
+ type = type.getFieldList().get(p.i).getType();
+ }
break;
}
}
@@ -4525,9 +4525,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
private static class Expander extends SqlScopedShuttle {
private final SqlValidatorImpl validator;
- public Expander(
- SqlValidatorImpl validator,
- SqlValidatorScope scope) {
+ Expander(SqlValidatorImpl validator, SqlValidatorScope scope) {
super(scope);
this.validator = validator;
}
@@ -4549,7 +4547,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
// SqlIdentifier "col_name" would be resolved to a dynamic star field in dynTable's rowType.
// Expand such SqlIdentifier to ITEM operator.
if (DynamicRecordType.isDynamicStarColName(Util.last(fqId.names))
- && !DynamicRecordType.isDynamicStarColName(Util.last(id.names))) {
+ && !DynamicRecordType.isDynamicStarColName(Util.last(id.names))) {
SqlNode[] inputs = new SqlNode[2];
inputs[0] = fqId;
inputs[1] = SqlLiteral.createCharString(
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorScope.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorScope.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorScope.java
index ee72ffb..c30b9ff 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorScope.java
@@ -17,6 +17,7 @@
package org.apache.calcite.sql.validate;
import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.StructKind;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlNode;
@@ -24,6 +25,11 @@ import org.apache.calcite.sql.SqlNodeList;
import org.apache.calcite.sql.SqlWindow;
import org.apache.calcite.util.Pair;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -53,14 +59,11 @@ public interface SqlValidatorScope {
/**
* Looks up a node with a given name. Returns null if none is found.
*
- * @param names Name of node to find
- * @param ancestorOut If not null, writes the ancestor scope here
- * @param offsetOut If not null, writes the offset within the ancestor here
+ * @param names Name of node to find, maybe partially or fully qualified
+ * @param deep Whether to look more than one level deep
+ * @param resolved Callback wherein to write the match(es) we find
*/
- SqlValidatorNamespace resolve(
- List<String> names,
- SqlValidatorScope[] ancestorOut,
- int[] offsetOut);
+ void resolve(List<String> names, boolean deep, Resolved resolved);
/**
* Finds the table alias which is implicitly qualifying an unqualified
@@ -172,6 +175,109 @@ public interface SqlValidatorScope {
/** Converts the type of an expression to nullable, if the context
* warrants it. */
RelDataType nullifyType(SqlNode node, RelDataType type);
+
+ /** Callback from
+ * {@link SqlValidatorScope#resolve(List, boolean, Resolved)}. */
+ interface Resolved {
+ void found(SqlValidatorNamespace namespace, SqlValidatorScope scope,
+ Path path);
+ int count();
+ Path emptyPath();
+ }
+
+ /** A sequence of steps by which an identifier was resolved. */
+ abstract class Path {
+ /** Creates a path which consists of this path plus one additional step. */
+ Step add(RelDataType rowType, int i, StructKind kind) {
+ return new Step(this, rowType, i, kind);
+ }
+
+ /** Number of steps in this path. */
+ public int stepCount() {
+ return 0;
+ }
+
+ /** Returns the steps in this path. */
+ public List<Step> steps() {
+ ImmutableList.Builder<Step> paths = new ImmutableList.Builder<>();
+ build(paths);
+ return paths.build();
+ }
+
+ protected void build(ImmutableList.Builder<Step> paths) {
+ }
+ }
+
+ /** A path that has no steps. */
+ class EmptyPath extends Path {
+ }
+
+ /** A step in resolving an identifier. */
+ class Step extends Path {
+ final Path parent;
+ final RelDataType rowType;
+ public final int i;
+ final StructKind kind;
+
+ Step(Path parent, RelDataType rowType, int i, StructKind kind) {
+ this.parent = Preconditions.checkNotNull(parent);
+ this.rowType = rowType; // may be null
+ this.i = i;
+ this.kind = Preconditions.checkNotNull(kind);
+ }
+
+ @Override public int stepCount() {
+ return 1 + parent.stepCount();
+ }
+
+ protected void build(ImmutableList.Builder<Step> paths) {
+ parent.build(paths);
+ paths.add(this);
+ }
+ }
+
+ /** Default implementation of
+ * {@link org.apache.calcite.sql.validate.SqlValidatorScope.Resolved}. */
+ class ResolvedImpl implements Resolved {
+ final List<Resolve> resolves = new ArrayList<>();
+ private final EmptyPath emptyPath = new EmptyPath();
+
+ public void found(SqlValidatorNamespace namespace, SqlValidatorScope scope, Path path) {
+ resolves.add(new Resolve(namespace, scope, path));
+ }
+
+ public int count() {
+ return resolves.size();
+ }
+
+ public Path emptyPath() {
+ return emptyPath;
+ }
+
+ public Resolve only() {
+ return Iterables.getOnlyElement(resolves);
+ }
+
+ /** Resets all state. */
+ public void clear() {
+ resolves.clear();
+ }
+
+ }
+
+ /** A match found when looking up a name. */
+ class Resolve {
+ public final SqlValidatorNamespace namespace;
+ public final SqlValidatorScope scope;
+ public final Path path;
+
+ Resolve(SqlValidatorNamespace namespace, SqlValidatorScope scope,
+ Path path) {
+ this.namespace = Preconditions.checkNotNull(namespace);
+ this.scope = scope;
+ this.path = path;
+ }
+ }
}
// End SqlValidatorScope.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
index 249f678..249f5f5 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
@@ -109,14 +109,13 @@ public class SqlValidatorUtil {
* Looks up a field with a given name, returning null if not found.
*
* @param caseSensitive Whether match is case-sensitive
- * @param elideRecord Whether to find fields nested within records
* @param rowType Row type
* @param columnName Field name
* @return Field, or null if not found
*/
public static RelDataTypeField lookupField(boolean caseSensitive,
- boolean elideRecord, final RelDataType rowType, String columnName) {
- return rowType.getField(columnName, caseSensitive, elideRecord);
+ final RelDataType rowType, String columnName) {
+ return rowType.getField(columnName, caseSensitive, false);
}
public static void checkCharsetAndCollateConsistentIfCharType(
@@ -445,16 +444,15 @@ public class SqlValidatorUtil {
SqlValidatorScope scope,
List<String> names) {
assert names.size() > 0;
- SqlValidatorNamespace namespace = null;
- for (int i = 0; i < names.size(); i++) {
- String name = names.get(i);
- if (i == 0) {
- namespace = scope.resolve(ImmutableList.of(name), null, null);
- } else {
- namespace = namespace.lookupChild(name);
- }
+ final SqlValidatorScope.ResolvedImpl resolved =
+ new SqlValidatorScope.ResolvedImpl();
+ scope.resolve(ImmutableList.of(names.get(0)), false, resolved);
+ assert resolved.count() == 1;
+ SqlValidatorNamespace namespace = resolved.only().namespace;
+ for (String name : Util.skip(names)) {
+ namespace = namespace.lookupChild(name);
+ assert namespace != null;
}
- assert namespace != null;
return namespace;
}
@@ -524,14 +522,14 @@ public class SqlValidatorUtil {
public static RelDataType createTypeFromProjection(RelDataType type,
List<String> columnNameList, RelDataTypeFactory typeFactory,
- boolean caseSensitive, boolean elideRecord) {
+ boolean caseSensitive) {
// If the names in columnNameList and type have case-sensitive differences,
// the resulting type will use those from type. These are presumably more
// canonical.
final List<RelDataTypeField> fields =
new ArrayList<>(columnNameList.size());
for (String name : columnNameList) {
- RelDataTypeField field = type.getField(name, caseSensitive, elideRecord);
+ RelDataTypeField field = type.getField(name, caseSensitive, false);
fields.add(type.getFieldList().get(field.getIndex()));
}
return typeFactory.createStructType(fields);
@@ -657,26 +655,24 @@ public class SqlValidatorUtil {
String originalRelName = expr.names.get(0);
String originalFieldName = expr.names.get(1);
- int[] nsIndexes = {-1};
- final SqlValidatorScope[] ancestorScopes = {null};
- SqlValidatorNamespace foundNs =
- scope.resolve(
- ImmutableList.of(originalRelName),
- ancestorScopes,
- nsIndexes);
+ final SqlValidatorScope.ResolvedImpl resolved =
+ new SqlValidatorScope.ResolvedImpl();
+ scope.resolve(ImmutableList.of(originalRelName), false, resolved);
- assert foundNs != null;
- assert nsIndexes.length == 1;
- int childNamespaceIndex = nsIndexes[0];
+ assert resolved.count() == 1;
+ final SqlValidatorScope.Resolve resolve = resolved.only();
+ final SqlValidatorNamespace foundNs = resolve.namespace;
+ final int childNamespaceIndex = resolve.path.steps().get(0).i;
int namespaceOffset = 0;
if (childNamespaceIndex > 0) {
// If not the first child, need to figure out the width of
// output types from all the preceding namespaces
- assert ancestorScopes[0] instanceof ListScope;
+ final SqlValidatorScope ancestorScope = resolve.scope;
+ assert ancestorScope instanceof ListScope;
List<SqlValidatorNamespace> children =
- ((ListScope) ancestorScopes[0]).getChildren();
+ ((ListScope) ancestorScope).getChildren();
for (int j = 0; j < childNamespaceIndex; j++) {
namespaceOffset +=
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/sql/validate/WithScope.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/WithScope.java b/core/src/main/java/org/apache/calcite/sql/validate/WithScope.java
index 9141f83..42c8b99 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/WithScope.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/WithScope.java
@@ -16,6 +16,7 @@
*/
package org.apache.calcite.sql.validate;
+import org.apache.calcite.rel.type.StructKind;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlWithItem;
@@ -53,14 +54,16 @@ class WithScope extends ListScope {
return super.getTableNamespace(names);
}
- @Override public SqlValidatorNamespace resolve(List<String> names,
- SqlValidatorScope[] ancestorOut,
- int[] offsetOut) {
+ @Override public void resolve(List<String> names, boolean deep, Resolved resolved) {
if (names.size() == 1
- && names.get(0).equals(withItem.name.getSimple())) {
- return validator.getNamespace(withItem);
+ && names.equals(withItem.name.names)) {
+ final SqlValidatorNamespace ns = validator.getNamespace(withItem);
+ final Step path = resolved.emptyPath()
+ .add(ns.getRowType(), 0, StructKind.FULLY_QUALIFIED);
+ resolved.found(ns, null, path);
+ return;
}
- return super.resolve(names, ancestorOut, offsetOut);
+ super.resolve(names, deep, resolved);
}
}
[4/6] calcite git commit: Complete [CALCITE-1208] by giving "resolve"
a call-back, so it can make multiple matches
Posted by jh...@apache.org.
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index 0efdaf2..d380c1b 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -2203,20 +2203,15 @@ public class SqlToRelConverter {
String originalRelName = lookup.getOriginalRelName();
String originalFieldName = fieldAccess.getField().getName();
- int[] nsIndexes = {-1};
- final SqlValidatorScope[] ancestorScopes = {null};
- SqlValidatorNamespace foundNs =
- lookup.bb.scope.resolve(
- ImmutableList.of(originalRelName),
- ancestorScopes,
- nsIndexes);
-
- assert foundNs != null;
- assert nsIndexes.length == 1;
-
- int childNamespaceIndex = nsIndexes[0];
-
- SqlValidatorScope ancestorScope = ancestorScopes[0];
+ SqlValidatorScope.ResolvedImpl resolved =
+ new SqlValidatorScope.ResolvedImpl();
+ lookup.bb.scope.resolve(ImmutableList.of(originalRelName), false,
+ resolved);
+ assert resolved.count() == 1;
+ final SqlValidatorScope.Resolve resolve = resolved.only();
+ final SqlValidatorNamespace foundNs = resolve.namespace;
+ final int childNamespaceIndex = resolve.path.steps().get(0).i;
+ final SqlValidatorScope ancestorScope = resolve.scope;
boolean correlInCurrentScope = ancestorScope == bb.scope;
if (!correlInCurrentScope) {
@@ -2309,18 +2304,12 @@ public class SqlToRelConverter {
DeferredLookup lookup = mapCorrelToDeferred.get(correlName);
String originalRelName = lookup.getOriginalRelName();
- int[] nsIndexes = {-1};
- final SqlValidatorScope[] ancestorScopes = {null};
- SqlValidatorNamespace foundNs =
- lookup.bb.scope.resolve(
- ImmutableList.of(originalRelName),
- ancestorScopes,
- nsIndexes);
-
- assert foundNs != null;
- assert nsIndexes.length == 1;
+ final SqlValidatorScope.ResolvedImpl resolved =
+ new SqlValidatorScope.ResolvedImpl();
+ lookup.bb.scope.resolve(ImmutableList.of(originalRelName), false,
+ resolved);
- SqlValidatorScope ancestorScope = ancestorScopes[0];
+ SqlValidatorScope ancestorScope = resolved.only().scope;
// If the correlated reference is in a scope that's "above" the
// subquery, then this is a correlated subquery.
@@ -3900,24 +3889,24 @@ public class SqlToRelConverter {
}
return Pair.of(node, null);
}
- int[] offsets = {-1};
- final SqlValidatorScope[] ancestorScopes = {null};
- SqlValidatorNamespace foundNs =
- scope.resolve(qualified.prefix(), ancestorScopes, offsets);
- if (foundNs == null) {
+ final SqlValidatorScope.ResolvedImpl resolved =
+ new SqlValidatorScope.ResolvedImpl();
+ scope.resolve(qualified.prefix(), false, resolved);
+ if (!(resolved.count() == 1)) {
return null;
}
+ final SqlValidatorScope.Resolve resolve = resolved.only();
+ final SqlValidatorNamespace foundNs = resolve.namespace;
// Found in current query's from list. Find which from item.
// We assume that the order of the from clause items has been
// preserved.
- SqlValidatorScope ancestorScope = ancestorScopes[0];
+ final SqlValidatorScope ancestorScope = resolve.scope;
boolean isParent = ancestorScope != scope;
if ((inputs != null) && !isParent) {
- int offset = offsets[0];
final LookupContext rels =
new LookupContext(this, inputs, systemFieldList.size());
- final RexNode node = lookup(offset, rels);
+ final RexNode node = lookup(resolve.path.steps().get(0).i, rels);
if (node == null) {
return null;
} else {
@@ -3932,20 +3921,20 @@ public class SqlToRelConverter {
new DeferredLookup(this, qualified.identifier.names.get(0));
final CorrelationId correlId = cluster.createCorrel();
mapCorrelToDeferred.put(correlId, lookup);
- if (offsets[0] < 0) {
+ if (resolve.path.steps().get(0).i < 0) {
return Pair.of(rexBuilder.makeCorrel(foundNs.getRowType(), correlId),
null);
} else {
final RelDataTypeFactory.FieldInfoBuilder builder =
typeFactory.builder();
- final ListScope ancestorScope1 = (ListScope) ancestorScopes[0];
+ final ListScope ancestorScope1 = (ListScope) resolve.scope;
final ImmutableMap.Builder<String, Integer> fields =
ImmutableMap.builder();
int i = 0;
int offset = 0;
for (SqlValidatorNamespace c : ancestorScope1.getChildren()) {
builder.addAll(c.getRowType().getFieldList());
- if (i == offsets[0]) {
+ if (i == resolve.path.steps().get(0).i) {
for (RelDataTypeField field : c.getRowType().getFieldList()) {
fields.put(field.getName(), field.getIndex() + offset);
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/util/ImmutableIntList.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/ImmutableIntList.java b/core/src/main/java/org/apache/calcite/util/ImmutableIntList.java
index 78b3dbe..90cbdb0 100644
--- a/core/src/main/java/org/apache/calcite/util/ImmutableIntList.java
+++ b/core/src/main/java/org/apache/calcite/util/ImmutableIntList.java
@@ -223,8 +223,12 @@ public class ImmutableIntList extends FlatLists.AbstractFlatList<Integer> {
return -1;
}
+ @Override public ImmutableIntList append(Integer e) {
+ return append((int) e);
+ }
+
/** Returns a copy of this list with one element added. */
- public ImmutableIntList add(int element) {
+ public ImmutableIntList append(int element) {
if (ints.length == 0) {
return of(element);
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/java/org/apache/calcite/util/Pair.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/Pair.java b/core/src/main/java/org/apache/calcite/util/Pair.java
index a3d509a..bb2521e 100644
--- a/core/src/main/java/org/apache/calcite/util/Pair.java
+++ b/core/src/main/java/org/apache/calcite/util/Pair.java
@@ -73,6 +73,11 @@ public class Pair<T1, T2>
return new Pair<>(left, right);
}
+ /** Creates a {@code Pair} from a {@link java.util.Map.Entry}. */
+ public static <K, V> Pair<K, V> of(Map.Entry<K, V> entry) {
+ return of(entry.getKey(), entry.getValue());
+ }
+
//~ Methods ----------------------------------------------------------------
public boolean equals(Object obj) {
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
----------------------------------------------------------------------
diff --git a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
index bab7a4a..729fa13 100644
--- a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++ b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -198,6 +198,7 @@ ModifiableViewMustBeBasedOnSingleTable=Modifiable view must be based on a single
MoreThanOneMappedColumn=View is not modifiable. More than one expression maps to column ''{0}'' of base table ''{1}''
NoValueSuppliedForViewColumn=View is not modifiable. No value is supplied for NOT NULL column ''{0}'' of base table ''{1}''
TableNotFound=Table ''{0}'' not found
+StarRequiresRecordType=Not a record type. The ''*'' operator requires a record
FilterMustBeBoolean=FILTER expression must be of type BOOLEAN
CannotStreamResultsForNonStreamingInputs=Cannot stream results of a query with no streaming inputs: ''{0}''. At least one input should be convertible to a stream
SelectMissingFrom=SELECT must have a FROM clause
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
index 7a3ea1a..a65f797 100644
--- a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -2126,6 +2126,11 @@ public class SqlParserTest {
+ "FROM `EMP`");
}
+ @Test public void testTableStarColumnFails() {
+ sql("select emp.*^.^xx from emp")
+ .fails("(?s).*Encountered \".\" .*");
+ }
+
@Test public void testNotExists() {
check(
"select * from dept where not not exists (select * from emp) and true",
@@ -2639,6 +2644,16 @@ public class SqlParserTest {
+ "FROM `EMP`");
}
+ @Test public void testCompoundStar() {
+ final String sql = "select sales.emp.address.zipcode,\n"
+ + " sales.emp.address.*\n"
+ + "from sales.emp";
+ final String expected = "SELECT `SALES`.`EMP`.`ADDRESS`.`ZIPCODE`,"
+ + " `SALES`.`EMP`.`ADDRESS`.*\n"
+ + "FROM `SALES`.`EMP`";
+ sql(sql).ok(expected);
+ }
+
@Test public void testSelectDistinct() {
check(
"select distinct foo from bar",
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
index 2a3c02f..03200f0 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
@@ -81,7 +81,9 @@ public class SqlAdvisorTest extends SqlValidatorTestCase {
private static final List<String> SCHEMAS =
Arrays.asList(
"CATALOG(CATALOG)",
+ "SCHEMA(CATALOG.DYNAMIC)",
"SCHEMA(CATALOG.SALES)",
+ "SCHEMA(CATALOG.STRUCT)",
"SCHEMA(CATALOG.CUSTOMER)");
private static final List<String> AB_TABLES =
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java b/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
index d61b440..23719d4 100644
--- a/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
+++ b/core/src/test/java/org/apache/calcite/test/MockCatalogReader.java
@@ -34,15 +34,14 @@ import org.apache.calcite.rel.logical.LogicalProject;
import org.apache.calcite.rel.logical.LogicalTableScan;
import org.apache.calcite.rel.type.DynamicRecordTypeImpl;
import org.apache.calcite.rel.type.RelDataType;
-import org.apache.calcite.rel.type.RelDataType.StructKind;
import org.apache.calcite.rel.type.RelDataTypeComparability;
import org.apache.calcite.rel.type.RelDataTypeFactory;
-import org.apache.calcite.rel.type.RelDataTypeFactory.FieldInfoBuilder;
import org.apache.calcite.rel.type.RelDataTypeFamily;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rel.type.RelDataTypeFieldImpl;
import org.apache.calcite.rel.type.RelDataTypePrecedenceList;
import org.apache.calcite.rel.type.RelRecordType;
+import org.apache.calcite.rel.type.StructKind;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexNode;
@@ -115,7 +114,6 @@ public class MockCatalogReader implements Prepare.CatalogReader {
protected final RelDataTypeFactory typeFactory;
private final boolean caseSensitive;
- private final boolean elideRecord = true;
private final Map<List<String>, MockTable> tables;
protected final Map<String, MockSchema> schemas;
private RelDataType addressType;
@@ -165,7 +163,16 @@ public class MockCatalogReader implements Prepare.CatalogReader {
final RelDataType booleanType =
typeFactory.createSqlType(SqlTypeName.BOOLEAN);
final RelDataType rectilinearCoordType =
- typeFactory.builder().add("X", intType).add("Y", intType).build();
+ typeFactory.builder()
+ .add("X", intType)
+ .add("Y", intType)
+ .build();
+ final RelDataType rectilinearPeekCoordType =
+ typeFactory.builder()
+ .add("X", intType)
+ .add("Y", intType)
+ .kind(StructKind.PEEK_FIELDS)
+ .build();
final RelDataType empRecordType =
typeFactory.builder()
.add("EMPNO", intType)
@@ -183,8 +190,8 @@ public class MockCatalogReader implements Prepare.CatalogReader {
Arrays.asList(
new RelDataTypeFieldImpl("STREET", 0, varchar20Type),
new RelDataTypeFieldImpl("CITY", 1, varchar20Type),
- new RelDataTypeFieldImpl("ZIP", 1, intType),
- new RelDataTypeFieldImpl("STATE", 1, varchar20Type)),
+ new RelDataTypeFieldImpl("ZIP", 2, intType),
+ new RelDataTypeFieldImpl("STATE", 3, varchar20Type)),
RelDataTypeComparability.NONE);
// Register "SALES" schema.
@@ -235,7 +242,8 @@ public class MockCatalogReader implements Prepare.CatalogReader {
registerTable(deptNestedTable);
// Register "BONUS" table.
- MockTable bonusTable = MockTable.create(this, salesSchema, "BONUS", false, 0);
+ MockTable bonusTable =
+ MockTable.create(this, salesSchema, "BONUS", false, 0);
bonusTable.addColumn("ENAME", varchar20Type);
bonusTable.addColumn("JOB", varchar10Type);
bonusTable.addColumn("SAL", intType);
@@ -243,7 +251,8 @@ public class MockCatalogReader implements Prepare.CatalogReader {
registerTable(bonusTable);
// Register "SALGRADE" table.
- MockTable salgradeTable = MockTable.create(this, salesSchema, "SALGRADE", false, 5);
+ MockTable salgradeTable =
+ MockTable.create(this, salesSchema, "SALGRADE", false, 5);
salgradeTable.addColumn("GRADE", intType);
salgradeTable.addColumn("LOSAL", intType);
salgradeTable.addColumn("HISAL", intType);
@@ -257,6 +266,20 @@ public class MockCatalogReader implements Prepare.CatalogReader {
contactAddressTable.addColumn("MAILING_ADDRESS", addressType);
registerTable(contactAddressTable);
+ // Register "DYNAMIC" schema.
+ MockSchema dynamicSchema = new MockSchema("DYNAMIC");
+ registerSchema(dynamicSchema);
+
+ MockTable nationTable =
+ new MockDynamicTable(this, dynamicSchema.getCatalogName(),
+ dynamicSchema.getName(), "NATION", false, 100);
+ registerTable(nationTable);
+
+ MockTable customerTable =
+ new MockDynamicTable(this, dynamicSchema.getCatalogName(),
+ dynamicSchema.getName(), "CUSTOMER", false, 100);
+ registerTable(customerTable);
+
// Register "CUSTOMER" schema.
MockSchema customerSchema = new MockSchema("CUSTOMER");
registerSchema(customerSchema);
@@ -271,6 +294,16 @@ public class MockCatalogReader implements Prepare.CatalogReader {
contactTable.addColumn("COORD", rectilinearCoordType);
registerTable(contactTable);
+ // Register "CONTACT_PEEK" table. The
+ MockTable contactPeekTable =
+ MockTable.create(this, customerSchema, "CONTACT_PEEK", false, 1000);
+ contactPeekTable.addColumn("CONTACTNO", intType);
+ contactPeekTable.addColumn("FNAME", varchar10Type);
+ contactPeekTable.addColumn("LNAME", varchar10Type);
+ contactPeekTable.addColumn("EMAIL", varchar20Type);
+ contactPeekTable.addColumn("COORD", rectilinearPeekCoordType);
+ registerTable(contactPeekTable);
+
// Register "ACCOUNT" table.
MockTable accountTable = MockTable.create(this, customerSchema, "ACCOUNT",
false, 457);
@@ -428,28 +461,28 @@ public class MockCatalogReader implements Prepare.CatalogReader {
MockSchema structTypeSchema = new MockSchema("STRUCT");
registerSchema(structTypeSchema);
- MockTable structTypeTable = MockTable.create(
- this, structTypeSchema, "T", false, 100);
+ MockTable structTypeTable = MockTable.create(this, structTypeSchema, "T",
+ false, 100);
structTypeTable.addColumn("K0", varchar20Type);
structTypeTable.addColumn("C1", varchar20Type);
- FieldInfoBuilder builder = typeFactory.builder();
- builder.add("C0", intType);
- builder.add("C1", intType);
- RelDataType f0Type = new DelegateStructType(
- typeFactory.createStructType(builder), StructKind.PEEK_FIELDS_DEFAULT);
+ final RelDataType f0Type = typeFactory.builder()
+ .add("C0", intType)
+ .add("C1", intType)
+ .kind(StructKind.PEEK_FIELDS_DEFAULT)
+ .build();
structTypeTable.addColumn("F0", f0Type);
- builder = typeFactory.builder();
- builder.add("C0", intTypeNull);
- builder.add("C2", intType);
- builder.add("A0", intType);
- RelDataType f1Type = new DelegateStructType(
- typeFactory.createStructType(builder), StructKind.PEEK_FIELDS);
+ final RelDataType f1Type = typeFactory.builder()
+ .add("C0", intTypeNull)
+ .add("C2", intType)
+ .add("A0", intType)
+ .kind(StructKind.PEEK_FIELDS)
+ .build();
structTypeTable.addColumn("F1", f1Type);
- builder = typeFactory.builder();
- builder.add("C3", intType);
- builder.add("A0", booleanType);
- RelDataType f2Type = new DelegateStructType(
- typeFactory.createStructType(builder), StructKind.PEEK_FIELDS);
+ final RelDataType f2Type = typeFactory.builder()
+ .add("C3", intType)
+ .add("A0", booleanType)
+ .kind(StructKind.PEEK_FIELDS)
+ .build();
structTypeTable.addColumn("F2", f2Type);
registerTable(structTypeTable);
return this;
@@ -562,27 +595,17 @@ public class MockCatalogReader implements Prepare.CatalogReader {
}
public RelDataTypeField field(RelDataType rowType, String alias) {
- return SqlValidatorUtil.lookupField(caseSensitive, elideRecord, rowType,
- alias);
- }
-
- public int fieldOrdinal(RelDataType rowType, String alias) {
- final RelDataTypeField field = field(rowType, alias);
- return field != null ? field.getIndex() : -1;
+ return SqlValidatorUtil.lookupField(caseSensitive, rowType, alias);
}
public boolean matches(String string, String name) {
return Util.matches(caseSensitive, string, name);
}
- public int match(List<String> strings, String name) {
- return Util.findMatch(strings, name, caseSensitive);
- }
-
public RelDataType createTypeFromProjection(final RelDataType type,
final List<String> columnNameList) {
return SqlValidatorUtil.createTypeFromProjection(type, columnNameList,
- typeFactory, caseSensitive, elideRecord);
+ typeFactory, caseSensitive);
}
private static List<RelCollation> deduceMonotonicity(
@@ -646,6 +669,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
private List<RelCollation> collationList;
protected final List<String> names;
private final Set<String> monotonicColumnSet = Sets.newHashSet();
+ private StructKind kind = StructKind.FULLY_QUALIFIED;
public MockTable(MockCatalogReader catalogReader, String catalogName,
String schemaName, String name, boolean stream, double rowCount) {
@@ -732,7 +756,8 @@ public class MockCatalogReader implements Prepare.CatalogReader {
}
public void onRegister(RelDataTypeFactory typeFactory) {
- rowType = typeFactory.createStructType(columnList);
+ rowType = typeFactory.createStructType(kind, Pair.right(columnList),
+ Pair.left(columnList));
collationList = deduceMonotonicity(this);
}
@@ -771,6 +796,14 @@ public class MockCatalogReader implements Prepare.CatalogReader {
table.onRegister(catalogReader.typeFactory);
return table;
}
+
+ public void setKind(StructKind kind) {
+ this.kind = kind;
+ }
+
+ public StructKind getKind() {
+ return kind;
+ }
}
/**
@@ -778,13 +811,13 @@ public class MockCatalogReader implements Prepare.CatalogReader {
* {@link org.apache.calcite.prepare.Prepare.PreparingTable} with dynamic record type.
*/
public static class MockDynamicTable extends MockTable {
- public MockDynamicTable(MockCatalogReader catalogReader, String catalogName,
+ MockDynamicTable(MockCatalogReader catalogReader, String catalogName,
String schemaName, String name, boolean stream, double rowCount) {
super(catalogReader, catalogName, schemaName, name, stream, rowCount);
}
public void onRegister(RelDataTypeFactory typeFactory) {
- rowType = new DynamicRecordTypeImpl(typeFactory);
+ rowType = new DynamicRecordTypeImpl(typeFactory);
}
/**
@@ -799,6 +832,7 @@ public class MockCatalogReader implements Prepare.CatalogReader {
}
}
+ /** Struct type based on another struct type. */
private static class DelegateStructType implements RelDataType {
private RelDataType delegate;
private StructKind structKind;
@@ -809,92 +843,92 @@ public class MockCatalogReader implements Prepare.CatalogReader {
this.structKind = structKind;
}
- @Override public boolean isStruct() {
+ public boolean isStruct() {
return delegate.isStruct();
}
- @Override public boolean isDynamicStruct() {
+ public boolean isDynamicStruct() {
return delegate.isDynamicStruct();
}
- @Override public List<RelDataTypeField> getFieldList() {
+ public List<RelDataTypeField> getFieldList() {
return delegate.getFieldList();
}
- @Override public List<String> getFieldNames() {
+ public List<String> getFieldNames() {
return delegate.getFieldNames();
}
- @Override public int getFieldCount() {
+ public int getFieldCount() {
return delegate.getFieldCount();
}
- @Override public StructKind getStructKind() {
+ public StructKind getStructKind() {
return structKind;
}
- @Override public RelDataTypeField getField(String fieldName,
- boolean caseSensitive, boolean elideRecord) {
+ public RelDataTypeField getField(String fieldName, boolean caseSensitive,
+ boolean elideRecord) {
return delegate.getField(fieldName, caseSensitive, elideRecord);
}
- @Override public boolean isNullable() {
+ public boolean isNullable() {
return delegate.isNullable();
}
- @Override public RelDataType getComponentType() {
+ public RelDataType getComponentType() {
return delegate.getComponentType();
}
- @Override public RelDataType getKeyType() {
+ public RelDataType getKeyType() {
return delegate.getKeyType();
}
- @Override public RelDataType getValueType() {
+ public RelDataType getValueType() {
return delegate.getValueType();
}
- @Override public Charset getCharset() {
+ public Charset getCharset() {
return delegate.getCharset();
}
- @Override public SqlCollation getCollation() {
+ public SqlCollation getCollation() {
return delegate.getCollation();
}
- @Override public SqlIntervalQualifier getIntervalQualifier() {
+ public SqlIntervalQualifier getIntervalQualifier() {
return delegate.getIntervalQualifier();
}
- @Override public int getPrecision() {
+ public int getPrecision() {
return delegate.getPrecision();
}
- @Override public int getScale() {
+ public int getScale() {
return delegate.getScale();
}
- @Override public SqlTypeName getSqlTypeName() {
+ public SqlTypeName getSqlTypeName() {
return delegate.getSqlTypeName();
}
- @Override public SqlIdentifier getSqlIdentifier() {
+ public SqlIdentifier getSqlIdentifier() {
return delegate.getSqlIdentifier();
}
- @Override public String getFullTypeString() {
+ public String getFullTypeString() {
return delegate.getFullTypeString();
}
- @Override public RelDataTypeFamily getFamily() {
+ public RelDataTypeFamily getFamily() {
return delegate.getFamily();
}
- @Override public RelDataTypePrecedenceList getPrecedenceList() {
+ public RelDataTypePrecedenceList getPrecedenceList() {
return delegate.getPrecedenceList();
}
- @Override public RelDataTypeComparability getComparability() {
+ public RelDataTypeComparability getComparability() {
return delegate.getComparability();
}
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
index 7ec5087..9953355 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -1721,7 +1721,7 @@ public class SqlToRelConverterTest extends SqlToRelTestBase {
}
@Test public void testStructType() {
- sql("select * from struct.t").convertsTo("");
+ sql("select * from struct.t").convertsTo("${plan}");
}
/**
@@ -1908,6 +1908,14 @@ public class SqlToRelConverterTest extends SqlToRelTestBase {
});
}
+ @Test public void testUnionInFrom() {
+ final String sql = "select x0, x1 from (\n"
+ + " select 'a' as x0, 'a' as x1, 'a' as x2 from emp\n"
+ + " union all\n"
+ + " select 'bb' as x0, 'bb' as x1, 'bb' as x2 from dept)";
+ sql(sql).ok();
+ }
+
/**
* Visitor that checks that every {@link RelNode} in a tree is valid.
*
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index c48b528..e1afc68 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -4615,9 +4615,10 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
check("select emp.* from emp");
// Error message could be better (EMPNO does exist, but it's a column).
- checkFails(
- "select ^empno^ . * from emp",
- "Unknown identifier 'EMPNO'");
+ sql("select ^empno^ . * from emp")
+ .fails("Not a record type. The '\\*' operator requires a record");
+ sql("select ^emp.empno^ . * from emp")
+ .fails("Not a record type. The '\\*' operator requires a record");
}
/**
@@ -4636,10 +4637,10 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
}
@Test public void testNonLocalStar() {
- // MySQL allows this but we can't, currently
+ // MySQL allows this, and now so do we
sql("select * from emp e where exists (\n"
- + " select ^e^.* from dept where dept.deptno = e.deptno)")
- .fails("Unknown identifier 'E'");
+ + " select e.* from dept where dept.deptno = e.deptno)")
+ .type(EMP_RECORD_TYPE);
}
/**
@@ -4656,10 +4657,9 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
}
@Test public void testStarDotIdFails() {
- // Parser allows a star inside (not at end of) compound identifier, but
- // validator does not
+ // Fails in parser
sql("select emp.^*^.foo from emp")
- .fails("Column '\\*' not found in table 'EMP'");
+ .fails("(?s).*Encountered \".\" at .*");
// Parser does not allow star dot identifier.
sql("select ^*^.foo from emp")
.fails("(?s).*Encountered \".\" at .*");
@@ -6950,6 +6950,21 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
"DISTINCT/ALL not allowed with COALESCE function");
}
+ @Test public void testColumnNotFound() {
+ sql("select ^b0^ from sales.emp")
+ .fails("Column 'B0' not found in any table");
+ }
+
+ @Test public void testColumnNotFound2() {
+ sql("select ^b0^ from sales.emp, sales.dept")
+ .fails("Column 'B0' not found in any table");
+ }
+
+ @Test public void testColumnNotFound3() {
+ sql("select e.^b0^ from sales.emp as e")
+ .fails("Column 'B0' not found in table 'E'");
+ }
+
@Test public void testSelectDistinct() {
check("SELECT DISTINCT deptno FROM emp");
check("SELECT DISTINCT deptno, sal FROM emp");
@@ -7151,14 +7166,28 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
* <a href="https://issues.apache.org/jira/browse/CALCITE-497">[CALCITE-497]
* Support optional qualifier for column name references</a>. */
@Test public void testRecordTypeElided() {
- checkResultType(
- "SELECT contact.x, contact.coord.y FROM customer.contact",
- "RecordType(INTEGER NOT NULL X, INTEGER NOT NULL Y) NOT NULL");
+ sql("SELECT contact.^x^, contact.coord.y FROM customer.contact")
+ .fails("Column 'X' not found in table 'CONTACT'");
+
+ // Fully qualified works.
+ sql("SELECT contact.coord.x, contact.coord.y FROM customer.contact")
+ .type("RecordType(INTEGER NOT NULL X, INTEGER NOT NULL Y) NOT NULL");
+
+ // Because the type of CONTACT_PEEK.COORD is marked "peek", the validator
+ // can see through it.
+ sql("SELECT c.x, c.coord.y FROM customer.contact_peek as c")
+ .type("RecordType(INTEGER NOT NULL X, INTEGER NOT NULL Y) NOT NULL");
+ sql("SELECT c.coord.x, c.coord.y FROM customer.contact_peek as c")
+ .type("RecordType(INTEGER NOT NULL X, INTEGER NOT NULL Y) NOT NULL");
+ sql("SELECT x, c.coord.y FROM customer.contact_peek as c")
+ .type("RecordType(INTEGER NOT NULL X, INTEGER NOT NULL Y) NOT NULL");
// Qualifying with schema is OK.
- checkResultType(
- "SELECT customer.contact.x, customer.contact.email, contact.coord.y FROM customer.contact",
- "RecordType(INTEGER NOT NULL X, VARCHAR(20) NOT NULL EMAIL, INTEGER NOT NULL Y) NOT NULL");
+ final String sql = "SELECT customer.contact_peek.x,\n"
+ + " customer.contact_peek.email, contact_peek.coord.y\n"
+ + "FROM customer.contact_peek";
+ sql(sql).type("RecordType(INTEGER NOT NULL X, VARCHAR(20) NOT NULL EMAIL,"
+ + " INTEGER NOT NULL Y) NOT NULL");
}
@Test public void testSample() {
@@ -8131,112 +8160,195 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
.fails(cannotStreamResultsForNonStreamingInputs("PRODUCTS, SUPPLIERS"));
}
- @Test public void testNew() {
+ @Test public void testDummy() {
// (To debug individual statements, paste them into this method.)
- // 1 2 3 4 5 6
- // 12345678901234567890123456789012345678901234567890123456789012345
- // check("SELECT count(0) FROM emp GROUP BY ()");
}
@Test public void testStructType() {
- // Table STRUCT.T is defined as:
- // (K0 VARCHAR(20) NOT NULL, C1 VARCHAR(20) NOT NULL,
- // RecordType(C0 INTEGER NOT NULL, C1 INTEGER NOT NULL) F0,
- // RecordType(C0 INTEGER, C2 INTEGER NOT NULL, A0 INTEGER NOT NULL) F1,
- // RecordType(C3 INTEGER NOT NULL, A0 BOOLEAN NOT NULL) F2)
- // , where F0 has a default struct priority.
+ // Table STRUCT.T is defined as: (
+ // K0 VARCHAR(20) NOT NULL,
+ // C1 VARCHAR(20) NOT NULL,
+ // RecordType:PEEK_FIELDS_DEFAULT(
+ // C0 INTEGER NOT NULL,
+ // C1 INTEGER NOT NULL) F0,
+ // RecordType:PEEK_FIELDS(
+ // C0 INTEGER,
+ // C2 INTEGER NOT NULL,
+ // A0 INTEGER NOT NULL) F1,
+ // RecordType:PEEK_FIELDS(
+ // C3 INTEGER NOT NULL,
+ // A0 BOOLEAN NOT NULL) F2)
+ //
+ // The labels 'PEEK_FIELDS_DEFAULT' and 'PEEK_FIELDS' mean that F0, F1 and
+ // F2 can all be transparent. F0 has default struct priority; F1 and F2 have
+ // lower priority.
- check("select * from struct.t");
+ sql("select * from struct.t").ok();
// Resolve K0 as top-level column K0.
- checkResultType("select k0 from struct.t",
- "RecordType(VARCHAR(20) NOT NULL K0) NOT NULL");
+ sql("select k0 from struct.t")
+ .type("RecordType(VARCHAR(20) NOT NULL K0) NOT NULL");
// Resolve C2 as secondary-level column F1.C2.
- checkResultType("select c2 from struct.t",
- "RecordType(INTEGER NOT NULL C2) NOT NULL");
+ sql("select c2 from struct.t")
+ .type("RecordType(INTEGER NOT NULL C2) NOT NULL");
// Resolve F1.C2 as fully qualified column F1.C2.
- checkResultType("select c2 from struct.t",
- "RecordType(INTEGER NOT NULL C2) NOT NULL");
+ sql("select f1.c2 from struct.t")
+ .type("RecordType(INTEGER NOT NULL C2) NOT NULL");
// Resolve C1 as top-level column C1 as opposed to F0.C1.
- checkResultType("select c1 from struct.t",
- "RecordType(VARCHAR(20) NOT NULL C1) NOT NULL");
+ sql("select c1 from struct.t")
+ .type("RecordType(VARCHAR(20) NOT NULL C1) NOT NULL");
- // Resolve C0 as secondary-level column F0.C0 as opposed to F1.C0, since F0 has the
- // default priority.
- checkResultType("select c0 from struct.t",
- "RecordType(INTEGER NOT NULL C0) NOT NULL");
+ // Resolve C0 as secondary-level column F0.C0 as opposed to F1.C0, since F0
+ // has the default priority.
+ sql("select c0 from struct.t")
+ .type("RecordType(INTEGER NOT NULL C0) NOT NULL");
- // Resolve F1.C0 as fully qualified column F1.C0.
- checkResultType("select f1.c0 from struct.t",
- "RecordType(INTEGER C0) NOT NULL");
+ // Resolve F1.C0 as fully qualified column F1.C0 (as evidenced by "INTEGER"
+ // rather than "INTEGER NOT NULL")
+ sql("select f1.c0 from struct.t")
+ .type("RecordType(INTEGER C0) NOT NULL");
- // Fail ambiguous column reference A0, since F1.A0 and F2.A0 both exist with the
- // same resolving priority.
- checkFails("select ^a0^ from struct.t",
- "Column 'A0' is ambiguous");
+ // Fail ambiguous column reference A0, since F1.A0 and F2.A0 both exist with
+ // the same resolving priority.
+ sql("select ^a0^ from struct.t")
+ .fails("Column 'A0' is ambiguous");
// Resolve F2.A0 as fully qualified column F2.A0.
- checkResultType("select f2.a0 from struct.t",
- "RecordType(BOOLEAN NOT NULL C0) NOT NULL");
+ sql("select f2.a0 from struct.t")
+ .type("RecordType(BOOLEAN NOT NULL A0) NOT NULL");
- // Resolve T0.K0 as top-level column K0, since T0 is recognized as the table alias.
- checkResultType("select t0.k0 from struct.t t0",
- "RecordType(VARCHAR(20) NOT NULL K0) NOT NULL");
+ // Resolve T0.K0 as top-level column K0, since T0 is recognized as the table
+ // alias.
+ sql("select t0.k0 from struct.t t0")
+ .type("RecordType(VARCHAR(20) NOT NULL K0) NOT NULL");
+
+ // Resolve T0.C2 as secondary-level column F1.C2, since T0 is recognized as
+ // the table alias here.
+ sql("select t0.c2 from struct.t t0")
+ .type("RecordType(INTEGER NOT NULL C2) NOT NULL");
+
+ // Resolve F0.C2 as secondary-level column F1.C2, since F0 is recognized as
+ // the table alias here.
+ sql("select f0.c2 from struct.t f0")
+ .type("RecordType(INTEGER NOT NULL C2) NOT NULL");
+
+ // Resolve F0.C1 as top-level column C1 as opposed to F0.C1, since F0 is
+ // recognized as the table alias here.
+ sql("select f0.c1 from struct.t f0")
+ .type("RecordType(VARCHAR(20) NOT NULL C1) NOT NULL");
+
+ // Resolve C1 as inner INTEGER column not top-level VARCHAR column.
+ sql("select f0.f0.c1 from struct.t f0")
+ .type("RecordType(INTEGER NOT NULL C1) NOT NULL");
+
+ // Resolve T.C1 as top-level column C1 as opposed to F0.C1, since T is
+ // recognized as the table name.
+ sql("select t.c1 from struct.t")
+ .type("RecordType(VARCHAR(20) NOT NULL C1) NOT NULL");
+
+ // Alias "f0" obscures table name "t"
+ sql("select ^t^.c1 from struct.t f0")
+ .fails("Table 'T' not found");
+
+ // Resolve STRUCT.T.C1 as top-level column C1 as opposed to F0.C1, since
+ // STRUCT.T is recognized as the schema and table name.
+ sql("select struct.t.c1 from struct.t")
+ .type("RecordType(VARCHAR(20) NOT NULL C1) NOT NULL");
+
+ // Table alias "f0" obscures table name "struct.t"
+ sql("select ^struct.t^.c1 from struct.t f0")
+ .fails("Table 'STRUCT.T' not found");
+
+ // Resolve F0.F0.C1 as secondary-level column F0.C1, since the first F0 is
+ // recognized as the table alias here.
+ sql("select f0.f0.c1 from struct.t f0")
+ .type("RecordType(INTEGER NOT NULL C1) NOT NULL");
+
+ // Resolve T.F0.C1 as secondary-level column F0.C1, since T is recognized as
+ // the table name.
+ sql("select t.f0.c1 from struct.t")
+ .type("RecordType(INTEGER NOT NULL C1) NOT NULL");
- // Resolve T0.C2 as secondary-level column F1.C2, since T0 is recognized as the
- // table alias here.
- checkResultType("select t0.c2 from struct.t t0",
- "RecordType(INTEGER NOT NULL C2) NOT NULL");
+ // Table alias obscures
+ sql("select ^t.f0^.c1 from struct.t f0")
+ .fails("Table 'T.F0' not found");
- // Resolve F0.C2 as secondary-level column F1.C2, since F0 is recognized as the
- // table alias here.
- checkResultType("select f0.c2 from struct.t f0",
- "RecordType(INTEGER NOT NULL C2) NOT NULL");
+ // Resolve STRUCT.T.F0.C1 as secondary-level column F0.C1, since STRUCT.T is
+ // recognized as the schema and table name.
+ sql("select struct.t.f0.c1 from struct.t")
+ .type("RecordType(INTEGER NOT NULL C1) NOT NULL");
- // Resolve F0.C1 as top-level column C1 as opposed to F0.C1, since F0 is recognized
- // as the table alias here.
- checkResultType("select f0.c1 from struct.t f0",
- "RecordType(VARCHAR(20) NOT NULL C1) NOT NULL");
+ // Table alias "f0" obscures table name "struct.t"
+ sql("select ^struct.t.f0^.c1 from struct.t f0")
+ .fails("Table 'STRUCT.T.F0' not found");
- // Resolve T.C1 as top-level column C1 as opposed to F0.C1, since T is recognized as
- // the table name.
- checkResultType("select t.c1 from struct.t f0",
- "RecordType(VARCHAR(20) NOT NULL C1) NOT NULL");
+ // Resolve struct type F1 with wildcard.
+ sql("select f1.* from struct.t")
+ .type("RecordType(INTEGER C0, INTEGER NOT NULL C2,"
+ + " INTEGER NOT NULL A0) NOT NULL");
- // Resolve STRUCT.T.C1 as top-level column C1 as opposed to F0.C1, since STRUCT.T is
- // recognized as the schema and table name.
- checkResultType("select struct.t.c1 from struct.t f0",
- "RecordType(VARCHAR(20) NOT NULL C1) NOT NULL");
+ // Fail non-existent column B0.
+ sql("select ^b0^ from struct.t")
+ .fails("Column 'B0' not found in any table");
- // Resolve F0.F0.C1 as secondary-level column F0.C1, since the first F0 is recognized
- // as the table alias here.
- checkResultType("select f0.f0.c1 from struct.t f0",
- "RecordType(INTEGER NOT NULL C1) NOT NULL");
+ // It's OK to reference a record type.
+ //
+ // This is admittedly a bit strange for Phoenix users. We model a column
+ // family as a column whose type is a record, but Phoenix users would
+ // rarely if ever want to use a column family as a record.
+ sql("select f1 from struct.t")
+ .type("RecordType(RecordType:peek(INTEGER C0, INTEGER NOT NULL C2,"
+ + " INTEGER NOT NULL A0) NOT NULL F1) NOT NULL");
+
+ // If we fail to find a column, give an error based on the shortest prefix
+ // that fails.
+ sql("select t.^f0.notFound^.a.b.c.d from struct.t")
+ .fails("Column 'F0\\.NOTFOUND' not found in table 'T'");
+ sql("select t.^f0.notFound^ from struct.t")
+ .fails("Column 'F0\\.NOTFOUND' not found in table 'T'");
+ sql("select t.^f0.c1.notFound^ from struct.t")
+ .fails("Column 'F0\\.C1\\.NOTFOUND' not found in table 'T'");
+ }
- // Resolve T.F0.C1 as secondary-level column F0.C1, since T is recognized as the table
- // name.
- checkResultType("select t.f0.c1 from struct.t f0",
- "RecordType(INTEGER NOT NULL C1) NOT NULL");
+ /** Test case for
+ * <a href="https://issues.apache.org/jira/browse/CALCITE-1150">[CALCITE-1150]
+ * Dynamic Table / Dynamic Star support</a>. */
+ @Test public void testAmbiguousDynamicStar() throws Exception {
+ final String sql = "select ^n_nation^\n"
+ + "from (select * from \"DYNAMIC\".NATION),\n"
+ + " (select * from \"DYNAMIC\".CUSTOMER)";
+ sql(sql).fails("Column 'N_NATION' is ambiguous");
+ }
- // Resolve STRUCT.T.F0.C1 as secondary-level column F0.C1, since STRUCT.T is
- // recognized as the schema and table name.
- checkResultType("select struct.t.f0.c1 from struct.t f0",
- "RecordType(INTEGER NOT NULL C1) NOT NULL");
+ @Test public void testAmbiguousDynamicStar2() throws Exception {
+ final String sql = "select ^n_nation^\n"
+ + "from (select * from \"DYNAMIC\".NATION, \"DYNAMIC\".CUSTOMER)";
+ sql(sql).fails("Column 'N_NATION' is ambiguous");
+ }
- // Resolve struct type F1 with wildcard.
- checkResultType("select f1.* from struct.t",
- "RecordType(INTEGER NOT NULL C0, INTEGER NOT NULL C2) NOT NULL");
+ @Test public void testAmbiguousDynamicStar3() throws Exception {
+ final String sql = "select ^nc.n_nation^\n"
+ + "from (select * from \"DYNAMIC\".NATION, \"DYNAMIC\".CUSTOMER) as nc";
+ sql(sql).fails("Column 'N_NATION' is ambiguous");
+ }
- // Fail non-existent column B0.
- checkFails("select ^b0^ from struct.t",
- "Column 'B0' not found in any table");
+ @Test public void testAmbiguousDynamicStar4() throws Exception {
+ final String sql = "select n.n_nation\n"
+ + "from (select * from \"DYNAMIC\".NATION) as n,\n"
+ + " (select * from \"DYNAMIC\".CUSTOMER)";
+ sql(sql).type("RecordType(ANY N_NATION) NOT NULL");
+ }
- // Fail struct type with no wildcard.
- checkFails("select ^f1^ from struct.t",
- "Unknown identifier 'F1'");
+ /** When resolve column reference, regular field has higher priority than
+ * dynamic star columns. */
+ @Test public void testDynamicStar2() throws Exception {
+ final String sql = "select newid from (\n"
+ + " select *, NATION.N_NATION + 100 as newid\n"
+ + " from \"DYNAMIC\".NATION, \"DYNAMIC\".CUSTOMER)";
+ sql(sql).type("RecordType(ANY NEWID) NOT NULL");
}
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
index 6713fd4..3c29e67 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -2469,6 +2469,19 @@ LogicalTableModify(table=[[CATALOG, SALES, EMP]], operation=[INSERT], updateColu
]]>
</Resource>
</TestCase>
+ <TestCase name="testStructType">
+ <Resource name="sql">
+ <![CDATA[select * from struct.t]]>
+ </Resource>
+ <Resource name="plan">
+ <![CDATA[
+LogicalProject(K0=[$0], C1=[$1], F0=[$2], F1=[$3], F2=[$4])
+ LogicalProject(K0=[$0], C1=[$1], F0=[$2], F03=[$3], F1=[$4], F15=[$5], F16=[$6], F2=[$7], F28=[$8])
+ LogicalProject(K0=[$0], C1=[$1], C0=[$2.C0], C13=[$2.C1], C04=[$3.C0], C2=[$3.C2], A0=[$3.A0], C3=[$4.C3], A08=[$4.A0])
+ LogicalTableScan(table=[[CATALOG, STRUCT, T]])
+]]>
+ </Resource>
+ </TestCase>
<TestCase name="testWindowAggWithGroupBy">
<Resource name="sql">
<![CDATA[select min(deptno), rank() over (order by empno),
@@ -3073,6 +3086,24 @@ LogicalAggregate(group=[{}], EXPR$0=[MIN($0)])
]]>
</Resource>
</TestCase>
+ <TestCase name="testUnionInFrom">
+ <Resource name="sql">
+ <![CDATA[select x0, x1 from (
+ select 'a' as x0, 'a' as x1, 'a' as x2 from emp
+ union all
+ select 'bb' as x0, 'bb' as x1, 'bb' as x2 from dept)]]>
+ </Resource>
+ <Resource name="plan">
+ <![CDATA[
+LogicalProject(X0=[$0], X1=[$1])
+ LogicalUnion(all=[true])
+ LogicalProject(X0=['a'], X1=['a'], X2=['a'])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalProject(X0=['bb'], X1=['bb'], X2=['bb'])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+]]>
+ </Resource>
+ </TestCase>
<TestCase name="testWhereInCorrelated">
<Resource name="sql">
<![CDATA[select empno from emp as e
http://git-wip-us.apache.org/repos/asf/calcite/blob/0938c7b6/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java
----------------------------------------------------------------------
diff --git a/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java b/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java
index 31e73d8..fdf5b9d 100644
--- a/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java
+++ b/splunk/src/main/java/org/apache/calcite/adapter/splunk/SplunkPushDownRule.java
@@ -17,6 +17,7 @@
package org.apache.calcite.adapter.splunk;
import org.apache.calcite.adapter.splunk.util.StringUtils;
+import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelOptRuleOperand;
@@ -25,7 +26,6 @@ import org.apache.calcite.rel.logical.LogicalFilter;
import org.apache.calcite.rel.logical.LogicalProject;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeField;
-import org.apache.calcite.rel.type.RelRecordType;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexLiteral;
@@ -189,6 +189,7 @@ public class SplunkPushDownRule
LogicalProject bottomProj,
RelDataType topRow,
RelDataType bottomRow) {
+ final RelOptCluster cluster = splunkRel.getCluster();
StringBuilder updateSearchStr = new StringBuilder(splunkRel.search);
if (!toAppend.isEmpty()) {
@@ -237,7 +238,7 @@ public class SplunkPushDownRule
if (!bottomFields.get(rif.getIndex()).getName()
.equals(topFields.get(i).getName())) {
renames.add(
- new Pair<String, String>(
+ Pair.of(
bottomFields.get(rif.getIndex()).getName(),
topFields.get(i).getName()));
field = topFields.get(i);
@@ -254,12 +255,13 @@ public class SplunkPushDownRule
}
}
- RelDataType resultType = new RelRecordType(newFields);
+ RelDataType resultType =
+ cluster.getTypeFactory().createStructType(newFields);
String searchWithFilter = updateSearchStr.toString();
RelNode rel =
new SplunkTableScan(
- splunkRel.getCluster(),
+ cluster,
splunkRel.getTable(),
splunkRel.splunkTable,
searchWithFilter,
@@ -267,8 +269,7 @@ public class SplunkPushDownRule
splunkRel.latest,
resultType.getFieldNames());
- LOGGER.debug(
- "end of appendSearchString fieldNames: {}",
+ LOGGER.debug("end of appendSearchString fieldNames: {}",
rel.getRowType().getFieldNames());
return rel;
}
[2/6] calcite git commit: [CALCITE-1095] NOT precedence
Posted by jh...@apache.org.
[CALCITE-1095] NOT precedence
Previously prefix operators such as NOT were not handled in the same
way as infix and postfix operators. With a new precedence climbing
parser, we can handle all operators uniformly.
Document precedence of all operators, adjusting some to match the SQL
standard; for example, LIKE now has slightly higher precedence than =,
and NOT has lower precedence than =.
Add a test to ensure that operators' real rankings match the documentation.
Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/339ac13e
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/339ac13e
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/339ac13e
Branch: refs/heads/master
Commit: 339ac13eb23b5aac6cadc16c42e36deebbcd1e39
Parents: f9724c2
Author: Julian Hyde <jh...@apache.org>
Authored: Thu Feb 18 11:14:52 2016 -0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Thu Sep 15 17:02:42 2016 -0700
----------------------------------------------------------------------
core/src/main/codegen/templates/Parser.jj | 69 +--
.../apache/calcite/sql/SqlPrefixOperator.java | 2 +-
.../apache/calcite/sql/SqlSpecialOperator.java | 40 +-
.../calcite/sql/fun/SqlBetweenOperator.java | 42 +-
.../apache/calcite/sql/fun/SqlInOperator.java | 2 +-
.../apache/calcite/sql/fun/SqlItemOperator.java | 19 +-
.../apache/calcite/sql/fun/SqlLikeOperator.java | 22 +-
.../calcite/sql/fun/SqlStdOperatorTable.java | 26 +-
.../calcite/sql/parser/SqlParserUtil.java | 387 ++++++++--------
.../calcite/util/PrecedenceClimbingParser.java | 447 +++++++++++++++++++
.../main/java/org/apache/calcite/util/Util.java | 2 +-
.../calcite/sql/parser/SqlParserTest.java | 104 +++--
.../apache/calcite/sql/test/SqlTesterImpl.java | 26 +-
.../org/apache/calcite/test/CalciteSuite.java | 2 +
.../apache/calcite/test/SqlValidatorTest.java | 181 ++++++++
.../util/PrecedenceClimbingParserTest.java | 181 ++++++++
core/src/test/resources/sql/operator.iq | 69 +++
site/_docs/reference.md | 18 +
18 files changed, 1310 insertions(+), 329 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/codegen/templates/Parser.jj
----------------------------------------------------------------------
diff --git a/core/src/main/codegen/templates/Parser.jj b/core/src/main/codegen/templates/Parser.jj
index 50f762c..43d6cd5 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -2556,13 +2556,30 @@ SqlNode Expression(ExprContext exprContext) :
// TODO jvs 15-Nov-2003: ANY/ALL
+void Expression2b(ExprContext exprContext, List<Object> list) :
+{
+ SqlNode e;
+ SqlOperator op;
+}
+{
+ (
+ op = PrefixRowOperator() {
+ checkNonQueryExpression(exprContext);
+ list.add(new SqlParserUtil.ToTreeListItem(op, getPos()));
+ }
+ )*
+ e = Expression3(exprContext) {
+ list.add(e);
+ }
+}
+
/**
* Parses a binary row expression, or a parenthesized expression of any
* kind.
*
* <p>The result is as a flat list of operators and operands. The top-level
* call to get an expression should call {@link #Expression}, but lower-level
- * calls should call this, to give the parser the opos to associate
+ * calls should call this, to give the parser the opportunity to associate
* operator calls.
*
* <p>For example 'a = b like c = d' should come out '((a = b) like c) = d'
@@ -2572,17 +2589,15 @@ SqlNode Expression(ExprContext exprContext) :
*/
List<Object> Expression2(ExprContext exprContext) :
{
- List<Object> list, list2;
+ final List<Object> list = new ArrayList();
+ List<Object> list2;
SqlNodeList nodeList;
SqlNode e;
SqlOperator op;
- SqlParserPos pos;
+ SqlParserPos pos = getPos();
}
{
- e = Expression3(exprContext)
- {
- list = startList(e);
- }
+ Expression2b(exprContext, list)
(
(
LOOKAHEAD(2)
@@ -2599,7 +2614,7 @@ List<Object> Expression2(ExprContext exprContext) :
op = SqlStdOperatorTable.NOT_IN;
pos = getPos();
}
- |
+ |
<IN>
{
op = SqlStdOperatorTable.IN;
@@ -2622,7 +2637,7 @@ List<Object> Expression2(ExprContext exprContext) :
list.add(nodeList);
}
}
- |
+ |
LOOKAHEAD(2)
{
checkNonQueryExpression(exprContext);
@@ -2635,10 +2650,10 @@ List<Object> Expression2(ExprContext exprContext) :
}
[
<SYMMETRIC> { op = SqlStdOperatorTable.SYMMETRIC_NOT_BETWEEN; }
- |
+ |
<ASYMMETRIC>
]
- |
+ |
<BETWEEN>
{
op = SqlStdOperatorTable.BETWEEN;
@@ -2646,7 +2661,7 @@ List<Object> Expression2(ExprContext exprContext) :
}
[
<SYMMETRIC> { op = SqlStdOperatorTable.SYMMETRIC_BETWEEN; }
- |
+ |
<ASYMMETRIC>
]
)
@@ -2655,7 +2670,7 @@ List<Object> Expression2(ExprContext exprContext) :
list.add(new SqlParserUtil.ToTreeListItem(op, pos));
list.add(e);
}
- |
+ |
{
checkNonQueryExpression(exprContext);
pos = getPos();
@@ -2664,12 +2679,12 @@ List<Object> Expression2(ExprContext exprContext) :
<NOT>
(
<LIKE> { op = SqlStdOperatorTable.NOT_LIKE; }
- |
+ |
<SIMILAR> <TO> { op = SqlStdOperatorTable.NOT_SIMILAR_TO; }
)
- |
+ |
<LIKE> { op = SqlStdOperatorTable.LIKE; }
- |
+ |
<SIMILAR> <TO> { op = SqlStdOperatorTable.SIMILAR_TO; }
)
list2 = Expression2(ExprContext.ACCEPT_SUBQUERY)
@@ -2688,17 +2703,14 @@ List<Object> Expression2(ExprContext exprContext) :
list.add(e);
}
]
- |
+ |
LOOKAHEAD(3) op = BinaryRowOperator()
{
checkNonQueryExpression(exprContext);
- }
- e = Expression3(ExprContext.ACCEPT_SUBQUERY)
- {
list.add(new SqlParserUtil.ToTreeListItem(op, getPos()));
- list.add(e);
}
- |
+ Expression2b(ExprContext.ACCEPT_SUBQUERY, list)
+ |
<LBRACKET>
e = Expression(ExprContext.ACCEPT_SUBQUERY)
<RBRACKET>
@@ -2708,7 +2720,7 @@ List<Object> Expression2(ExprContext exprContext) :
SqlStdOperatorTable.ITEM, getPos()));
list.add(e);
}
- |
+ |
{
checkNonQueryExpression(exprContext);
}
@@ -2766,17 +2778,6 @@ SqlNode Expression3(ExprContext exprContext) :
return SqlStdOperatorTable.ROW.createCall(list);
}
|
- op = PrefixRowOperator()
- {
- prefixRowOpPos = getPos();
- checkNonQueryExpression(exprContext);
- }
- e = Expression3(ExprContext.ACCEPT_SUBQUERY)
- {
- SqlParserPos callPos = prefixRowOpPos.plus(e.getParserPosition());
- return op.createCall(callPos, e);
- }
- |
{
pos = getPos();
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/sql/SqlPrefixOperator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlPrefixOperator.java b/core/src/main/java/org/apache/calcite/sql/SqlPrefixOperator.java
index 11eeb26..af44dd2 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlPrefixOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlPrefixOperator.java
@@ -42,7 +42,7 @@ public class SqlPrefixOperator extends SqlOperator {
super(
name,
kind,
- leftPrec(0, true),
+ leftPrec(prec, true),
rightPrec(prec, true),
returnTypeInference,
operandTypeInference,
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/sql/SqlSpecialOperator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlSpecialOperator.java b/core/src/main/java/org/apache/calcite/sql/SqlSpecialOperator.java
index 85da998..83049ac 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlSpecialOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlSpecialOperator.java
@@ -16,12 +16,14 @@
*/
package org.apache.calcite.sql;
+import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlOperandTypeChecker;
import org.apache.calcite.sql.type.SqlOperandTypeInference;
import org.apache.calcite.sql.type.SqlReturnTypeInference;
+import org.apache.calcite.util.PrecedenceClimbingParser;
import org.apache.calcite.util.Util;
-import java.util.List;
+import com.google.common.base.Predicate;
/**
* Generic operator for nodes with special syntax.
@@ -81,11 +83,43 @@ public class SqlSpecialOperator extends SqlOperator {
* {@link SqlNode}
* @return ordinal of the node which replaced the expression
*/
- public int reduceExpr(
+ public ReduceResult reduceExpr(
int ordinal,
- List<Object> list) {
+ TokenSequence list) {
throw Util.needToImplement(this);
}
+
+ /** List of tokens: the input to a parser. Every token is either an operator
+ * ({@link SqlOperator}) or an expression ({@link SqlNode}), and every token
+ * has a position. */
+ public interface TokenSequence {
+ int size();
+ SqlOperator op(int i);
+ SqlParserPos pos(int i);
+ boolean isOp(int i);
+ SqlNode node(int i);
+ void replaceSublist(int start, int end, SqlNode e);
+
+ /** Creates a parser whose token sequence is a copy of a subset of this
+ * token sequence. */
+ PrecedenceClimbingParser parser(int start,
+ Predicate<PrecedenceClimbingParser.Token> predicate);
+ }
+
+ /** Result of applying
+ * {@link org.apache.calcite.util.PrecedenceClimbingParser.Special#apply}.
+ * Tells the caller which range of tokens to replace, and with what. */
+ public class ReduceResult {
+ public final int startOrdinal;
+ public final int endOrdinal;
+ public final SqlNode node;
+
+ public ReduceResult(int startOrdinal, int endOrdinal, SqlNode node) {
+ this.startOrdinal = startOrdinal;
+ this.endOrdinal = endOrdinal;
+ this.node = node;
+ }
+ }
}
// End SqlSpecialOperator.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/sql/fun/SqlBetweenOperator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlBetweenOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlBetweenOperator.java
index 86660a2..bf23c3f 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlBetweenOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlBetweenOperator.java
@@ -109,7 +109,7 @@ public class SqlBetweenOperator extends SqlInfixOperator {
//~ Constructors -----------------------------------------------------------
public SqlBetweenOperator(Flag flag, boolean negated) {
- super(negated ? NOT_BETWEEN_NAMES : BETWEEN_NAMES, SqlKind.BETWEEN, 30,
+ super(negated ? NOT_BETWEEN_NAMES : BETWEEN_NAMES, SqlKind.BETWEEN, 32,
null, InferTypes.FIRST_KNOWN, OTC_CUSTOM);
this.flag = flag;
this.negated = negated;
@@ -191,12 +191,8 @@ public class SqlBetweenOperator extends SqlInfixOperator {
writer.endList(frame);
}
- public int reduceExpr(
- int opOrdinal,
- List<Object> list) {
- final SqlParserUtil.ToTreeListItem betweenNode =
- (SqlParserUtil.ToTreeListItem) list.get(opOrdinal);
- SqlOperator op = betweenNode.getOperator();
+ public ReduceResult reduceExpr(int opOrdinal, TokenSequence list) {
+ SqlOperator op = list.op(opOrdinal);
assert op == this;
// Break the expression up into expressions. For example, a simple
@@ -208,26 +204,18 @@ public class SqlBetweenOperator extends SqlInfixOperator {
// |_____| |_____| |_____|
// exp0 exp1 exp2
// Create the expression between 'BETWEEN' and 'AND'.
- final SqlParserPos pos =
- ((SqlNode) list.get(opOrdinal + 1)).getParserPosition();
SqlNode exp1 =
SqlParserUtil.toTreeEx(list, opOrdinal + 1, 0, SqlKind.AND);
if ((opOrdinal + 2) >= list.size()) {
- SqlParserPos lastPos =
- ((SqlNode) list.get(list.size() - 1)).getParserPosition();
+ SqlParserPos lastPos = list.pos(list.size() - 1);
final int line = lastPos.getEndLineNum();
final int col = lastPos.getEndColumnNum() + 1;
SqlParserPos errPos = new SqlParserPos(line, col, line, col);
throw SqlUtil.newContextException(errPos, RESOURCE.betweenWithoutAnd());
}
- final Object o = list.get(opOrdinal + 2);
- if (!(o instanceof SqlParserUtil.ToTreeListItem)) {
- SqlParserPos errPos = ((SqlNode) o).getParserPosition();
- throw SqlUtil.newContextException(errPos, RESOURCE.betweenWithoutAnd());
- }
- if (((SqlParserUtil.ToTreeListItem) o).getOperator().getKind()
- != SqlKind.AND) {
- SqlParserPos errPos = ((SqlParserUtil.ToTreeListItem) o).getPos();
+ if (!list.isOp(opOrdinal + 2)
+ || list.op(opOrdinal + 2).getKind() != SqlKind.AND) {
+ SqlParserPos errPos = list.pos(opOrdinal + 2);
throw SqlUtil.newContextException(errPos, RESOURCE.betweenWithoutAnd());
}
@@ -240,30 +228,22 @@ public class SqlBetweenOperator extends SqlInfixOperator {
// (a BETWEEN b AND c + d) OR e
// because OR has lower precedence than BETWEEN.
SqlNode exp2 =
- SqlParserUtil.toTreeEx(
- list,
+ SqlParserUtil.toTreeEx(list,
opOrdinal + 3,
getRightPrec(),
SqlKind.OTHER);
// Create the call.
- SqlNode exp0 = (SqlNode) list.get(opOrdinal - 1);
+ SqlNode exp0 = list.node(opOrdinal - 1);
SqlCall newExp =
createCall(
- betweenNode.getPos(),
+ list.pos(opOrdinal),
exp0,
exp1,
exp2);
// Replace all of the matched nodes with the single reduced node.
- SqlParserUtil.replaceSublist(
- list,
- opOrdinal - 1,
- opOrdinal + 4,
- newExp);
-
- // Return the ordinal of the new current node.
- return opOrdinal - 1;
+ return new ReduceResult(opOrdinal - 1, opOrdinal + 4, newExp);
}
//~ Inner Classes ----------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/sql/fun/SqlInOperator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlInOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlInOperator.java
index a20cea3..716902b 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlInOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlInOperator.java
@@ -66,7 +66,7 @@ public class SqlInOperator extends SqlBinaryOperator {
super(
isNotIn ? "NOT IN" : "IN",
SqlKind.IN,
- 30,
+ 32,
true,
ReturnTypes.BOOLEAN_NULLABLE,
InferTypes.FIRST_KNOWN,
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java
index 6b07a35..7cd0e1e 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlItemOperator.java
@@ -26,15 +26,12 @@ import org.apache.calcite.sql.SqlOperandCountRange;
import org.apache.calcite.sql.SqlOperatorBinding;
import org.apache.calcite.sql.SqlSpecialOperator;
import org.apache.calcite.sql.SqlWriter;
-import org.apache.calcite.sql.parser.SqlParserUtil;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.SqlOperandCountRanges;
import org.apache.calcite.sql.type.SqlSingleOperandTypeChecker;
import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlTypeName;
-import java.util.List;
-
/**
* The item operator {@code [ ... ]}, used to access a given element of an
* array or map. For example, {@code myArray[3]} or {@code "myMap['foo']"}.
@@ -51,22 +48,18 @@ class SqlItemOperator extends SqlSpecialOperator {
super("ITEM", SqlKind.OTHER_FUNCTION, 100, true, null, null, null);
}
- @Override public int reduceExpr(int ordinal, List<Object> list) {
- SqlNode left = (SqlNode) list.get(ordinal - 1);
- SqlNode right = (SqlNode) list.get(ordinal + 1);
- final SqlParserUtil.ToTreeListItem treeListItem =
- (SqlParserUtil.ToTreeListItem) list.get(ordinal);
- SqlParserUtil.replaceSublist(
- list,
- ordinal - 1,
+ @Override public ReduceResult reduceExpr(int ordinal,
+ TokenSequence list) {
+ SqlNode left = list.node(ordinal - 1);
+ SqlNode right = list.node(ordinal + 1);
+ return new ReduceResult(ordinal - 1,
ordinal + 2,
createCall(
left.getParserPosition()
.plus(right.getParserPosition())
- .plus(treeListItem.getPos()),
+ .plus(list.pos(ordinal)),
left,
right));
- return ordinal - 1;
}
@Override public void unparse(
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/sql/fun/SqlLikeOperator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlLikeOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlLikeOperator.java
index 1de1407..9a0187c 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLikeOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLikeOperator.java
@@ -33,8 +33,6 @@ import org.apache.calcite.sql.type.SqlOperandCountRanges;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.calcite.util.Util;
-import java.util.List;
-
/**
* An operator describing the <code>LIKE</code> and <code>SIMILAR</code>
* operators.
@@ -76,7 +74,7 @@ public class SqlLikeOperator extends SqlSpecialOperator {
super(
name,
kind,
- 30,
+ 32,
false,
ReturnTypes.BOOLEAN_NULLABLE,
InferTypes.FIRST_KNOWN,
@@ -148,16 +146,15 @@ public class SqlLikeOperator extends SqlSpecialOperator {
writer.endList(frame);
}
- public int reduceExpr(
+ public ReduceResult reduceExpr(
final int opOrdinal,
- List<Object> list) {
+ TokenSequence list) {
// Example:
// a LIKE b || c ESCAPE d || e AND f
// | | | | | |
// exp0 exp1 exp2
- SqlNode exp0 = (SqlNode) list.get(opOrdinal - 1);
- SqlOperator op =
- ((SqlParserUtil.ToTreeListItem) list.get(opOrdinal)).getOperator();
+ SqlNode exp0 = list.node(opOrdinal - 1);
+ SqlOperator op = list.op(opOrdinal);
assert op instanceof SqlLikeOperator;
SqlNode exp1 =
SqlParserUtil.toTreeEx(
@@ -167,10 +164,8 @@ public class SqlLikeOperator extends SqlSpecialOperator {
SqlKind.ESCAPE);
SqlNode exp2 = null;
if ((opOrdinal + 2) < list.size()) {
- final Object o = list.get(opOrdinal + 2);
- if (o instanceof SqlParserUtil.ToTreeListItem) {
- final SqlOperator op2 =
- ((SqlParserUtil.ToTreeListItem) o).getOperator();
+ if (list.isOp(opOrdinal + 2)) {
+ final SqlOperator op2 = list.op(opOrdinal + 2);
if (op2.getKind() == SqlKind.ESCAPE) {
exp2 =
SqlParserUtil.toTreeEx(
@@ -191,8 +186,7 @@ public class SqlLikeOperator extends SqlSpecialOperator {
end = opOrdinal + 2;
}
SqlCall call = createCall(SqlParserPos.ZERO, operands);
- SqlParserUtil.replaceSublist(list, opOrdinal - 1, end, call);
- return opOrdinal - 1;
+ return new ReduceResult(opOrdinal - 1, end, call);
}
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
index e94748f..d0e60d2 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
@@ -142,7 +142,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlBinaryOperator(
"AND",
SqlKind.AND,
- 28,
+ 24,
true,
ReturnTypes.BOOLEAN_NULLABLE_OPTIMIZED,
InferTypes.BOOLEAN,
@@ -431,7 +431,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlBinaryOperator(
"OR",
SqlKind.OR,
- 26,
+ 22,
true,
ReturnTypes.BOOLEAN_NULLABLE_OPTIMIZED,
InferTypes.BOOLEAN,
@@ -553,7 +553,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS NOT NULL",
SqlKind.IS_NOT_NULL,
- 30,
+ 28,
ReturnTypes.BOOLEAN_NOT_NULL,
InferTypes.VARCHAR_1024,
OperandTypes.ANY);
@@ -562,7 +562,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS NULL",
SqlKind.IS_NULL,
- 30,
+ 28,
ReturnTypes.BOOLEAN_NOT_NULL,
InferTypes.VARCHAR_1024,
OperandTypes.ANY);
@@ -571,7 +571,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS NOT TRUE",
SqlKind.IS_NOT_TRUE,
- 30,
+ 28,
ReturnTypes.BOOLEAN_NOT_NULL,
InferTypes.BOOLEAN,
OperandTypes.BOOLEAN);
@@ -580,7 +580,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS TRUE",
SqlKind.IS_TRUE,
- 30,
+ 28,
ReturnTypes.BOOLEAN_NOT_NULL,
InferTypes.BOOLEAN,
OperandTypes.BOOLEAN);
@@ -589,7 +589,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS NOT FALSE",
SqlKind.IS_NOT_FALSE,
- 30,
+ 28,
ReturnTypes.BOOLEAN_NOT_NULL,
InferTypes.BOOLEAN,
OperandTypes.BOOLEAN);
@@ -598,7 +598,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS FALSE",
SqlKind.IS_FALSE,
- 30,
+ 28,
ReturnTypes.BOOLEAN_NOT_NULL,
InferTypes.BOOLEAN,
OperandTypes.BOOLEAN);
@@ -607,7 +607,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS NOT UNKNOWN",
SqlKind.IS_NOT_NULL,
- 30,
+ 28,
ReturnTypes.BOOLEAN_NOT_NULL,
InferTypes.BOOLEAN,
OperandTypes.BOOLEAN);
@@ -616,7 +616,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS UNKNOWN",
SqlKind.IS_NULL,
- 30,
+ 28,
ReturnTypes.BOOLEAN_NOT_NULL,
InferTypes.BOOLEAN,
OperandTypes.BOOLEAN);
@@ -625,7 +625,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPostfixOperator(
"IS A SET",
SqlKind.OTHER,
- 30,
+ 28,
ReturnTypes.BOOLEAN,
null,
OperandTypes.MULTISET);
@@ -657,7 +657,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlPrefixOperator(
"NOT",
SqlKind.NOT,
- 30,
+ 26,
ReturnTypes.ARG0,
InferTypes.BOOLEAN,
OperandTypes.BOOLEAN);
@@ -1054,7 +1054,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
* SIMILAR TO expression.
*/
public static final SqlSpecialOperator ESCAPE =
- new SqlSpecialOperator("Escape", SqlKind.ESCAPE, 30);
+ new SqlSpecialOperator("ESCAPE", SqlKind.ESCAPE, 0);
public static final SqlCaseOperator CASE = SqlCaseOperator.INSTANCE;
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java b/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java
index 03b0ac3..3c605a5 100644
--- a/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java
+++ b/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java
@@ -20,25 +20,32 @@ import org.apache.calcite.avatica.util.Casing;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.runtime.CalciteContextException;
import org.apache.calcite.sql.SqlBinaryOperator;
-import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlIntervalLiteral;
import org.apache.calcite.sql.SqlIntervalQualifier;
import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.SqlNumericLiteral;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlPostfixOperator;
+import org.apache.calcite.sql.SqlPrefixOperator;
import org.apache.calcite.sql.SqlSpecialOperator;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.util.PrecedenceClimbingParser;
import org.apache.calcite.util.SaffronProperties;
import org.apache.calcite.util.Util;
import org.apache.calcite.util.trace.CalciteTrace;
+import com.google.common.base.Predicate;
+
import org.slf4j.Logger;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
@@ -99,6 +106,7 @@ public final class SqlParserUtil {
/**
* @deprecated this method is not localized for Farrago standards
*/
+ @Deprecated // to be removed before 2.0
public static java.sql.Date parseDate(String s) {
return java.sql.Date.valueOf(s);
}
@@ -106,6 +114,7 @@ public final class SqlParserUtil {
/**
* @deprecated Does not parse SQL:99 milliseconds
*/
+ @Deprecated // to be removed before 2.0
public static java.sql.Time parseTime(String s) {
return java.sql.Time.valueOf(s);
}
@@ -113,6 +122,7 @@ public final class SqlParserUtil {
/**
* @deprecated this method is not localized for Farrago standards
*/
+ @Deprecated // to be removed before 2.0
public static java.sql.Timestamp parseTimestamp(String s) {
return java.sql.Timestamp.valueOf(s);
}
@@ -227,6 +237,7 @@ public final class SqlParserUtil {
* Parses a Binary string. SQL:99 defines a binary string as a hexstring
* with EVEN nbr of hex digits.
*/
+ @Deprecated // to be removed before 2.0
public static byte[] parseBinaryString(String s) {
s = s.replaceAll(" ", "");
s = s.replaceAll("\n", "");
@@ -491,6 +502,7 @@ public final class SqlParserUtil {
return new ParsedCollation(charset, locale, strength);
}
+ @Deprecated // to be removed before 2.0
public static String[] toStringArray(List<String> list) {
return list.toArray(new String[list.size()]);
}
@@ -503,6 +515,7 @@ public final class SqlParserUtil {
return list.toArray();
}
+ @Deprecated // to be removed before 2.0
public static String rightTrim(
String s,
char c) {
@@ -541,8 +554,14 @@ public final class SqlParserUtil {
* taking operator precedence and associativity into account.
*/
public static SqlNode toTree(List<Object> list) {
+ if (list.size() == 1
+ && list.get(0) instanceof SqlNode) {
+ // Short-cut for the simple common case
+ return (SqlNode) list.get(0);
+ }
LOGGER.trace("Attempting to reduce {}", list);
- final SqlNode node = toTreeEx(list, 0, 0, SqlKind.OTHER);
+ final OldTokenSequenceImpl tokenSequence = new OldTokenSequenceImpl(list);
+ final SqlNode node = toTreeEx(tokenSequence, 0, 0, SqlKind.OTHER);
LOGGER.debug("Reduced {}", node);
return node;
}
@@ -563,182 +582,58 @@ public final class SqlParserUtil {
* we encounter a token of this kind.
* @return the root node of the tree which the list condenses into
*/
- public static SqlNode toTreeEx(
- List<Object> list,
- int start,
- int minPrec,
- SqlKind stopperKind) {
-// Make several passes over the list, and each pass, coalesce the
-// expressions with the highest precedence.
- outer:
- while (true) {
- final int count = list.size();
- if (count <= (start + 1)) {
- break;
- }
- int i = start + 1;
- while (i < count) {
- SqlOperator previous;
- SqlOperator current = ((ToTreeListItem) list.get(i)).op;
- SqlParserPos currentPos = ((ToTreeListItem) list.get(i)).pos;
- if ((stopperKind != SqlKind.OTHER)
- && (current.getKind() == stopperKind)) {
- break outer;
- }
- SqlOperator next;
- int previousRight;
- int left = current.getLeftPrec();
- int right = current.getRightPrec();
- if (left < minPrec) {
- break outer;
- }
- int nextLeft;
- if (current instanceof SqlBinaryOperator) {
- if (i == (start + 1)) {
- previous = null;
- previousRight = 0;
- } else {
- previous = ((ToTreeListItem) list.get(i - 2)).op;
- previousRight = previous.getRightPrec();
- }
- if (i == (count - 2)) {
- next = null;
- nextLeft = 0;
- } else {
- next = ((ToTreeListItem) list.get(i + 2)).op;
- nextLeft = next.getLeftPrec();
- if ((next.getKind() == stopperKind)
- && (stopperKind != SqlKind.OTHER)) {
- // Suppose we're looking at 'AND' in
- // a BETWEEN b OR c AND d
- //
- // Because 'AND' is our stopper token, we still
- // want to reduce 'b OR c', even though 'AND' has
- // higher precedence than 'OR'.
- nextLeft = 0;
+ public static SqlNode toTreeEx(SqlSpecialOperator.TokenSequence list,
+ int start, final int minPrec, final SqlKind stopperKind) {
+ final Predicate<PrecedenceClimbingParser.Token> predicate =
+ new Predicate<PrecedenceClimbingParser.Token>() {
+ public boolean apply(PrecedenceClimbingParser.Token t) {
+ if (t instanceof PrecedenceClimbingParser.Op) {
+ final SqlOperator op = ((ToTreeListItem) t.o).op;
+ return stopperKind != SqlKind.OTHER
+ && op.kind == stopperKind
+ || minPrec > 0
+ && op.getLeftPrec() < minPrec;
+ } else {
+ return false;
}
}
- if ((previousRight < left) && (right >= nextLeft)) {
- // For example,
- // i: 0 1 2 3 4 5 6 7 8
- // list: a + b * c * d + e
- // prec: 0 1 2 3 4 3 4 1 2 0
- //
- // At i == 3, we have the first '*' operator, and its
- // surrounding precedences obey the relation 2 < 3 and
- // 4 >= 3, so we can reduce (b * c) to a single node.
- SqlNode leftExp = (SqlNode) list.get(i - 1);
-
- // For example,
- // i: 0 1 2 3 4 5 6 7 8
- // list: a + b * c * d + e
- // prec: 0 1 2 3 4 3 4 1 2 0
- //
- // At i == 3, we have the first '*' operator, and its
- // surrounding precedences obey the relation 2 < 3 and
- // 4 >= 3, so we can reduce (b * c) to a single node.
- SqlNode rightExp = (SqlNode) list.get(i + 1);
- SqlParserPos callPos =
- currentPos.plusAll(
- new SqlNode[]{leftExp, rightExp});
- final SqlCall newExp =
- current.createCall(callPos, leftExp, rightExp);
- LOGGER.debug("Reduced infix: {}", newExp);
-
- // Replace elements {i - 1, i, i + 1} with the new
- // expression.
- replaceSublist(list, i - 1, i + 2, newExp);
- break;
- }
- i += 2;
- } else if (current instanceof SqlPostfixOperator) {
- if (i == (start + 1)) {
- previous = null;
- previousRight = 0;
- } else {
- previous = ((ToTreeListItem) list.get(i - 2)).op;
- previousRight = previous.getRightPrec();
- }
- if (previousRight < left) {
- // For example,
- // i: 0 1 2 3 4 5 6 7 8
- // list: a + b * c ! + d
- // prec: 0 1 2 3 4 3 0 2
- //
- // At i == 3, we have the postfix '!' operator. Its
- // high precedence determines that it binds with 'b *
- // c'. The precedence of the following '+' operator is
- // irrelevant.
- SqlNode leftExp = (SqlNode) list.get(i - 1);
-
- SqlParserPos callPos =
- currentPos.plusAll(new SqlNode[]{leftExp});
- final SqlCall newExp =
- current.createCall(callPos, leftExp);
- LOGGER.debug("Reduced postfix: {}", newExp);
-
- // Replace elements {i - 1, i} with the new expression.
- list.remove(i);
- list.set(i - 1, newExp);
- break;
- }
- ++i;
-
- //
- } else if (current instanceof SqlSpecialOperator) {
- SqlSpecialOperator specOp = (SqlSpecialOperator) current;
-
- // We decide to reduce a special operator only on the basis
- // of what's to the left of it. The operator then decides
- // how far to the right to chew off.
- if (i == (start + 1)) {
- previous = null;
- previousRight = 0;
- } else {
- previous = ((ToTreeListItem) list.get(i - 2)).op;
- previousRight = previous.getRightPrec();
- }
- int nextOrdinal = i + 2;
- if (i == (count - 2)) {
- next = null;
- nextLeft = 0;
- } else {
- // find next op
- next = null;
- nextLeft = 0;
- for (; nextOrdinal < count; nextOrdinal++) {
- Object listItem = list.get(nextOrdinal);
- if (listItem instanceof ToTreeListItem) {
- next = ((ToTreeListItem) listItem).op;
- nextLeft = next.getLeftPrec();
- if ((stopperKind != SqlKind.OTHER)
- && (next.getKind() == stopperKind)) {
- break outer;
- } else {
- break;
- }
- }
- }
- }
- if (nextLeft < minPrec) {
- break outer;
- }
- if ((previousRight < left) && (right >= nextLeft)) {
- i = specOp.reduceExpr(i, list);
- LOGGER.debug("Reduced special op: {}", list.get(i));
- break;
- }
- i = nextOrdinal;
- } else {
- throw Util.newInternal("Unexpected operator type: " + current);
- }
- }
+ };
+ PrecedenceClimbingParser parser = list.parser(start, predicate);
+ final int beforeSize = parser.all().size();
+ parser.partialParse();
+ final int afterSize = parser.all().size();
+ final SqlNode node = convert(parser.all().get(0));
+ list.replaceSublist(start, start + beforeSize - afterSize + 1, node);
+ return node;
+ }
- // Require the list shrinks each time around -- otherwise we will
- // never terminate.
- assert list.size() < count;
+ private static SqlNode convert(PrecedenceClimbingParser.Token token) {
+ switch (token.type) {
+ case ATOM:
+ return (SqlNode) token.o;
+ case CALL:
+ final PrecedenceClimbingParser.Call call =
+ (PrecedenceClimbingParser.Call) token;
+ final List<SqlNode> list = new ArrayList<>();
+ for (PrecedenceClimbingParser.Token arg : call.args) {
+ list.add(convert(arg));
+ }
+ final ToTreeListItem item = (ToTreeListItem) call.op.o;
+ if (item.op == SqlStdOperatorTable.UNARY_MINUS
+ && list.size() == 1
+ && list.get(0) instanceof SqlNumericLiteral) {
+ return SqlLiteral.createNegative((SqlNumericLiteral) list.get(0),
+ item.pos.plusAll(list));
+ }
+ if (item.op == SqlStdOperatorTable.UNARY_PLUS
+ && list.size() == 1
+ && list.get(0) instanceof SqlNumericLiteral) {
+ return list.get(0);
+ }
+ return item.op.createCall(item.pos.plusAll(list), list);
+ default:
+ throw new AssertionError(token);
}
- return (SqlNode) list.get(start);
}
/**
@@ -810,6 +705,10 @@ public final class SqlParserUtil {
this.pos = pos;
}
+ public String toString() {
+ return op.toString();
+ }
+
public SqlOperator getOperator() {
return op;
}
@@ -834,6 +733,146 @@ public final class SqlParserUtil {
this.pos = pos;
}
}
+
+ /** Implementation of {@link SqlSpecialOperator.TokenSequence} based on an
+ * existing parser. */
+ private static class TokenSequenceImpl
+ implements SqlSpecialOperator.TokenSequence {
+ final List<PrecedenceClimbingParser.Token> list;
+ final PrecedenceClimbingParser parser;
+
+ private TokenSequenceImpl(PrecedenceClimbingParser parser) {
+ this.parser = parser;
+ this.list = parser.all();
+ }
+
+ public PrecedenceClimbingParser parser(int start, Predicate
+ <PrecedenceClimbingParser.Token> predicate) {
+ return parser.copy(start, predicate);
+ }
+
+ public int size() {
+ return list.size();
+ }
+
+ public SqlOperator op(int i) {
+ return ((ToTreeListItem) list.get(i).o).getOperator();
+ }
+
+ private static SqlParserPos pos(PrecedenceClimbingParser.Token token) {
+ switch (token.type) {
+ case ATOM:
+ return ((SqlNode) token.o).getParserPosition();
+ case CALL:
+ final PrecedenceClimbingParser.Call call =
+ (PrecedenceClimbingParser.Call) token;
+ SqlParserPos pos = ((ToTreeListItem) call.op.o).pos;
+ for (PrecedenceClimbingParser.Token arg : call.args) {
+ pos = pos.plus(pos(arg));
+ }
+ return pos;
+ default:
+ return ((ToTreeListItem) token.o).getPos();
+ }
+ }
+
+ public SqlParserPos pos(int i) {
+ return pos(list.get(i));
+ }
+
+ public boolean isOp(int i) {
+ return list.get(i).o instanceof ToTreeListItem;
+ }
+
+ public SqlNode node(int i) {
+ return convert(list.get(i));
+ }
+
+ public void replaceSublist(int start, int end, SqlNode e) {
+ SqlParserUtil.replaceSublist(list, start, end, parser.atom(e));
+ }
+ }
+
+ /** Implementation of {@link SqlSpecialOperator.TokenSequence}. */
+ private static class OldTokenSequenceImpl
+ implements SqlSpecialOperator.TokenSequence {
+ final List<Object> list;
+
+ private OldTokenSequenceImpl(List<Object> list) {
+ this.list = list;
+ }
+
+ @Override public PrecedenceClimbingParser parser(int start,
+ Predicate<PrecedenceClimbingParser.Token> predicate) {
+ final PrecedenceClimbingParser.Builder builder =
+ new PrecedenceClimbingParser.Builder();
+ for (Object o : Util.skip(list, start)) {
+ if (o instanceof ToTreeListItem) {
+ final ToTreeListItem item = (ToTreeListItem) o;
+ final SqlOperator op = item.getOperator();
+ if (op instanceof SqlPrefixOperator) {
+ builder.prefix(item, op.getLeftPrec());
+ } else if (op instanceof SqlPostfixOperator) {
+ builder.postfix(item, op.getRightPrec());
+ } else if (op instanceof SqlBinaryOperator) {
+ builder.infix(item, op.getLeftPrec(),
+ op.getLeftPrec() < op.getRightPrec());
+ } else if (op instanceof SqlSpecialOperator) {
+ builder.special(item, op.getLeftPrec(), op.getRightPrec(),
+ new PrecedenceClimbingParser.Special() {
+ public PrecedenceClimbingParser.Result apply(
+ PrecedenceClimbingParser parser,
+ PrecedenceClimbingParser.SpecialOp op) {
+ final List<PrecedenceClimbingParser.Token> tokens =
+ parser.all();
+ final SqlSpecialOperator op1 =
+ (SqlSpecialOperator) ((ToTreeListItem) op.o).op;
+ SqlSpecialOperator.ReduceResult r =
+ op1.reduceExpr(tokens.indexOf(op),
+ new TokenSequenceImpl(parser));
+ return new PrecedenceClimbingParser.Result(
+ tokens.get(r.startOrdinal),
+ tokens.get(r.endOrdinal - 1),
+ parser.atom(r.node));
+ }
+ });
+ } else {
+ throw new AssertionError();
+ }
+ } else {
+ builder.atom(o);
+ }
+ }
+ return builder.build();
+ }
+
+ public int size() {
+ return list.size();
+ }
+
+ public SqlOperator op(int i) {
+ return ((ToTreeListItem) list.get(i)).op;
+ }
+
+ public SqlParserPos pos(int i) {
+ final Object o = list.get(i);
+ return o instanceof ToTreeListItem
+ ? ((ToTreeListItem) o).pos
+ : ((SqlNode) o).getParserPosition();
+ }
+
+ public boolean isOp(int i) {
+ return list.get(i) instanceof ToTreeListItem;
+ }
+
+ public SqlNode node(int i) {
+ return (SqlNode) list.get(i);
+ }
+
+ public void replaceSublist(int start, int end, SqlNode e) {
+ SqlParserUtil.replaceSublist(list, start, end, e);
+ }
+ }
}
// End SqlParserUtil.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/util/PrecedenceClimbingParser.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/PrecedenceClimbingParser.java b/core/src/main/java/org/apache/calcite/util/PrecedenceClimbingParser.java
new file mode 100644
index 0000000..68b8be5
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/util/PrecedenceClimbingParser.java
@@ -0,0 +1,447 @@
+/*
+ * 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.calcite.util;
+
+import org.apache.calcite.linq4j.Ord;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Parser that takes a collection of tokens (atoms and operators)
+ * and groups them together according to the operators' precedence
+ * and associativity.
+ */
+public class PrecedenceClimbingParser {
+ private Token first;
+ private Token last;
+
+ private PrecedenceClimbingParser(List<Token> tokens) {
+ Token p = null;
+ for (Token token : tokens) {
+ if (p != null) {
+ p.next = token;
+ } else {
+ first = token;
+ }
+ token.previous = p;
+ token.next = null;
+ p = token;
+ }
+ last = p;
+ }
+
+ public Token atom(Object o) {
+ return new Token(Type.ATOM, o, -1, -1);
+ }
+
+ public Call call(Op op, ImmutableList<Token> args) {
+ return new Call(op, args);
+ }
+
+ public Op infix(Object o, int precedence, boolean left) {
+ return new Op(Type.INFIX, o, precedence * 2 + (left ? 0 : 1),
+ precedence * 2 + (left ? 1 : 0));
+ }
+
+ public Op prefix(Object o, int precedence) {
+ return new Op(Type.PREFIX, o, -1, precedence * 2);
+ }
+
+ public Op postfix(Object o, int precedence) {
+ return new Op(Type.POSTFIX, o, precedence * 2, -1);
+ }
+
+ public SpecialOp special(Object o, int leftPrec, int rightPrec,
+ Special special) {
+ return new SpecialOp(o, leftPrec * 2, rightPrec * 2, special);
+ }
+
+ public Token parse() {
+ partialParse();
+ if (first != last) {
+ throw new AssertionError("could not find next operator to reduce: "
+ + this);
+ }
+ return first;
+ }
+
+ public void partialParse() {
+ for (;;) {
+ Op op = highest();
+ if (op == null) {
+ return;
+ }
+ final Token t;
+ switch (op.type) {
+ case POSTFIX:
+ t = call(op, ImmutableList.of(op.previous));
+ replace(t, op.previous.previous, op.next);
+ break;
+ case PREFIX:
+ t = call(op, ImmutableList.of(op.next));
+ replace(t, op.previous, op.next.next);
+ break;
+ case INFIX:
+ t = call(op, ImmutableList.of(op.previous, op.next));
+ replace(t, op.previous.previous, op.next.next);
+ break;
+ case SPECIAL:
+ Result r = ((SpecialOp) op).special.apply(this, (SpecialOp) op);
+ Preconditions.checkNotNull(r);
+ replace(r.replacement, r.first.previous, r.last.next);
+ break;
+ default:
+ throw new AssertionError();
+ }
+ // debug: System.out.println(this);
+ }
+ }
+
+ @Override public String toString() {
+ return Util.commaList(all());
+ }
+
+ /** Returns a list of all tokens. */
+ public List<Token> all() {
+ return new TokenList();
+ }
+
+ private void replace(Token t, Token previous, Token next) {
+ t.previous = previous;
+ t.next = next;
+ if (previous == null) {
+ first = t;
+ } else {
+ previous.next = t;
+ }
+ if (next == null) {
+ last = t;
+ } else {
+ next.previous = t;
+ }
+ }
+
+ private Op highest() {
+ int p = -1;
+ Op highest = null;
+ for (Token t = first; t != null; t = t.next) {
+ if ((t.left > p || t.right > p)
+ && (t.left < 0 || t.left >= prevRight(t.previous))
+ && (t.right < 0 || t.right >= nextLeft(t.next))) {
+ p = Math.max(t.left, t.right);
+ highest = (Op) t;
+ }
+ }
+ return highest;
+ }
+
+ /** Returns the right precedence of the preceding operator token. */
+ private int prevRight(Token token) {
+ for (; token != null; token = token.previous) {
+ if (token.type == Type.POSTFIX) {
+ return Integer.MAX_VALUE;
+ }
+ if (token.right >= 0) {
+ return token.right;
+ }
+ }
+ return -1;
+ }
+
+ /** Returns the left precedence of the following operator token. */
+ private int nextLeft(Token token) {
+ for (; token != null; token = token.next) {
+ if (token.type == Type.PREFIX) {
+ return Integer.MAX_VALUE;
+ }
+ if (token.left >= 0) {
+ return token.left;
+ }
+ }
+ return -1;
+ }
+
+ public String print(Token token) {
+ return token.toString();
+ }
+
+ public PrecedenceClimbingParser copy(int start, Predicate<Token> predicate) {
+ final List<Token> tokens = new ArrayList<>();
+ for (Token token : Util.skip(all(), start)) {
+ if (predicate.apply(token)) {
+ break;
+ }
+ tokens.add(token.copy());
+ }
+ return new PrecedenceClimbingParser(tokens);
+ }
+
+ /** Token type. */
+ public enum Type {
+ ATOM,
+ CALL,
+ PREFIX,
+ INFIX,
+ POSTFIX,
+ SPECIAL
+ }
+
+ /** A token: either an atom, a call to an operator with arguments,
+ * or an unmatched operator. */
+ public static class Token {
+ Token previous;
+ Token next;
+ public final Type type;
+ public final Object o;
+ final int left;
+ final int right;
+
+ Token(Type type, Object o, int left, int right) {
+ this.type = type;
+ this.o = o;
+ this.left = left;
+ this.right = right;
+ }
+
+ @Override public String toString() {
+ return o.toString();
+ }
+
+ protected StringBuilder print(StringBuilder b) {
+ return b.append(o);
+ }
+
+ public Token copy() {
+ return new Token(type, o, left, right);
+ }
+ }
+
+ /** An operator token. */
+ public static class Op extends Token {
+ Op(Type type, Object o, int left, int right) {
+ super(type, o, left, right);
+ }
+
+ @Override public Token copy() {
+ return new Op(type, o, left, right);
+ }
+ }
+
+ /** An token corresponding to a special operator. */
+ public static class SpecialOp extends Op {
+ public final Special special;
+
+ SpecialOp(Object o, int left, int right, Special special) {
+ super(Type.SPECIAL, o, left, right);
+ this.special = special;
+ }
+
+ @Override public Token copy() {
+ return new SpecialOp(o, left, right, special);
+ }
+ }
+
+
+ /** A token that is a call to an operator with arguments. */
+ public static class Call extends Token {
+ public final Op op;
+ public final ImmutableList<Token> args;
+
+ Call(Op op, ImmutableList<Token> args) {
+ super(Type.CALL, null, -1, -1);
+ this.op = op;
+ this.args = args;
+ }
+
+ @Override public Token copy() {
+ return new Call(op, args);
+ }
+
+ @Override public String toString() {
+ return print(new StringBuilder()).toString();
+ }
+
+ protected StringBuilder print(StringBuilder b) {
+ switch (op.type) {
+ case PREFIX:
+ b.append('(');
+ printOp(b, false, true);
+ args.get(0).print(b);
+ return b.append(')');
+ case POSTFIX:
+ b.append('(');
+ args.get(0).print(b);
+ return printOp(b, true, false).append(')');
+ case INFIX:
+ b.append('(');
+ args.get(0).print(b);
+ printOp(b, true, true);
+ args.get(1).print(b);
+ return b.append(')');
+ case SPECIAL:
+ printOp(b, false, false)
+ .append('(');
+ for (Ord<Token> arg : Ord.zip(args)) {
+ if (arg.i > 0) {
+ b.append(", ");
+ }
+ arg.e.print(b);
+ }
+ return b.append(')');
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ private StringBuilder printOp(StringBuilder b, boolean leftSpace,
+ boolean rightSpace) {
+ String s = op.o.toString();
+ if (leftSpace) {
+ b.append(' ');
+ }
+ b.append(s);
+ if (rightSpace) {
+ b.append(' ');
+ }
+ return b;
+ }
+ }
+
+ /** Callback defining the behavior of a special function. */
+ public interface Special {
+ /** Given an occurrence of this operator, identifies the range of tokens to
+ * be collapsed into a call of this operator, and the arguments to that
+ * call. */
+ Result apply(PrecedenceClimbingParser parser, SpecialOp op);
+ }
+
+ /** Result of a call to {@link Special#apply}. */
+ public static class Result {
+ final Token first;
+ final Token last;
+ final Token replacement;
+
+ public Result(Token first, Token last, Token replacement) {
+ this.first = first;
+ this.last = last;
+ this.replacement = replacement;
+ }
+ }
+
+ /** Fluent helper to build a parser containing a list of tokens. */
+ public static class Builder {
+ final List<Token> tokens = new ArrayList<>();
+ private final PrecedenceClimbingParser dummy =
+ new PrecedenceClimbingParser(ImmutableList.<Token>of());
+
+ private Builder add(Token t) {
+ tokens.add(t);
+ return this;
+ }
+
+ public Builder atom(Object o) {
+ return add(dummy.atom(o));
+ }
+
+ public Builder call(Op op, Token arg0, Token arg1) {
+ return add(dummy.call(op, ImmutableList.of(arg0, arg1)));
+ }
+
+ public Builder infix(Object o, int precedence, boolean left) {
+ return add(dummy.infix(o, precedence, left));
+ }
+
+ public Builder prefix(Object o, int precedence) {
+ return add(dummy.prefix(o, precedence));
+ }
+
+ public Builder postfix(Object o, int precedence) {
+ return add(dummy.postfix(o, precedence));
+ }
+
+ public Builder special(Object o, int leftPrec, int rightPrec,
+ Special special) {
+ return add(dummy.special(o, leftPrec, rightPrec, special));
+ }
+
+ public PrecedenceClimbingParser build() {
+ return new PrecedenceClimbingParser(tokens);
+ }
+ }
+
+ /** List view onto the tokens in a parser. The view is semi-mutable; it
+ * supports {@link List#remove(int)} but not {@link List#set} or
+ * {@link List#add}. */
+ private class TokenList extends AbstractList<Token> {
+ @Override public Token get(int index) {
+ for (Token t = first; t != null; t = t.next) {
+ if (index-- == 0) {
+ return t;
+ }
+ }
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override public int size() {
+ int n = 0;
+ for (Token t = first; t != null; t = t.next) {
+ ++n;
+ }
+ return n;
+ }
+
+ @Override public Token remove(int index) {
+ Token t = get(index);
+ if (t.previous == null) {
+ first = t.next;
+ } else {
+ t.previous.next = t.next;
+ }
+ if (t.next == null) {
+ last = t.previous;
+ } else {
+ t.next.previous = t.previous;
+ }
+ return t;
+ }
+
+ @Override public Token set(int index, Token element) {
+ final Token t = get(index);
+ element.previous = t.previous;
+ if (t.previous == null) {
+ first = element;
+ } else {
+ t.previous.next = element;
+ }
+ element.next = t.next;
+ if (t.next == null) {
+ last = element;
+ } else {
+ t.next.previous = element;
+ }
+ return t;
+ }
+ }
+}
+
+// End PrecedenceClimbingParser.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/main/java/org/apache/calcite/util/Util.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/Util.java b/core/src/main/java/org/apache/calcite/util/Util.java
index 0eee37b..c01c772 100644
--- a/core/src/main/java/org/apache/calcite/util/Util.java
+++ b/core/src/main/java/org/apache/calcite/util/Util.java
@@ -2044,7 +2044,7 @@ public class Util {
/** Returns all but the first {@code n} elements of a list. */
public static <E> List<E> skip(List<E> list, int fromIndex) {
- return list.subList(fromIndex, list.size());
+ return fromIndex == 0 ? list : list.subList(fromIndex, list.size());
}
public static List<Integer> range(final int end) {
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
index 590f0ee..7a3ea1a 100644
--- a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -744,6 +744,56 @@ public class SqlParserTest {
+ "WHERE (1 AND TRUE)");
}
+ @Test public void testLessThanAssociativity() {
+ checkExp("NOT a = b", "(NOT (`A` = `B`))");
+
+ // comparison operators are left-associative
+ checkExp("x < y < z", "((`X` < `Y`) < `Z`)");
+ checkExp("x < y <= z = a", "(((`X` < `Y`) <= `Z`) = `A`)");
+ checkExp("a = x < y <= z = a", "((((`A` = `X`) < `Y`) <= `Z`) = `A`)");
+
+ // IS NULL has lower precedence than comparison
+ checkExp("a = x is null", "((`A` = `X`) IS NULL)");
+ checkExp("a = x is not null", "((`A` = `X`) IS NOT NULL)");
+
+ // BETWEEN, IN, LIKE have higher precedence than comparison
+ checkExp("a = x between y = b and z = c",
+ "((`A` = (`X` BETWEEN ASYMMETRIC (`Y` = `B`) AND `Z`)) = `C`)");
+ checkExp("a = x like y = b",
+ "((`A` = (`X` LIKE `Y`)) = `B`)");
+ checkExp("a = x not like y = b",
+ "((`A` = (`X` NOT LIKE `Y`)) = `B`)");
+ checkExp("a = x similar to y = b",
+ "((`A` = (`X` SIMILAR TO `Y`)) = `B`)");
+ checkExp("a = x not similar to y = b",
+ "((`A` = (`X` NOT SIMILAR TO `Y`)) = `B`)");
+ checkExp("a = x not in (y, z) = b",
+ "((`A` = (`X` NOT IN (`Y`, `Z`))) = `B`)");
+
+ // LIKE has higher precedence than IS NULL
+ checkExp("a like b is null", "((`A` LIKE `B`) IS NULL)");
+ checkExp("a not like b is not null",
+ "((`A` NOT LIKE `B`) IS NOT NULL)");
+
+ // = has higher precedence than NOT
+ checkExp("NOT a = b", "(NOT (`A` = `B`))");
+ checkExp("NOT a = NOT b", "(NOT (`A` = (NOT `B`)))");
+
+ // IS NULL has higher precedence than NOT
+ checkExp("NOT a IS NULL", "(NOT (`A` IS NULL))");
+ checkExp("NOT a = b IS NOT NULL", "(NOT ((`A` = `B`) IS NOT NULL))");
+
+ // NOT has higher precedence than AND, which has higher precedence than OR
+ checkExp("NOT a AND NOT b", "((NOT `A`) AND (NOT `B`))");
+ checkExp("NOT a OR NOT b", "((NOT `A`) OR (NOT `B`))");
+ checkExp("NOT a = b AND NOT c = d OR NOT e = f",
+ "(((NOT (`A` = `B`)) AND (NOT (`C` = `D`))) OR (NOT (`E` = `F`)))");
+ checkExp("NOT a = b OR NOT c = d AND NOT e = f",
+ "((NOT (`A` = `B`)) OR ((NOT (`C` = `D`)) AND (NOT (`E` = `F`))))");
+ checkExp("NOT NOT a = b OR NOT NOT c = d",
+ "((NOT (NOT (`A` = `B`))) OR (NOT (NOT (`C` = `D`))))");
+ }
+
@Test public void testIsBooleans() {
String[] inOuts = {"NULL", "TRUE", "FALSE", "UNKNOWN"};
@@ -752,7 +802,7 @@ public class SqlParserTest {
"select * from t where nOt fAlSe Is " + inOut,
"SELECT *\n"
+ "FROM `T`\n"
- + "WHERE ((NOT FALSE) IS " + inOut + ")");
+ + "WHERE (NOT (FALSE IS " + inOut + "))");
check(
"select * from t where c1=1.1 IS NOT " + inOut,
@@ -771,7 +821,7 @@ public class SqlParserTest {
check("select 1 from t where not true is unknown",
"SELECT 1\n"
+ "FROM `T`\n"
- + "WHERE ((NOT TRUE) IS UNKNOWN)");
+ + "WHERE (NOT (TRUE IS UNKNOWN))");
check(
"select * from t where x is unknown is not unknown is false is not false"
@@ -781,11 +831,16 @@ public class SqlParserTest {
+ "WHERE ((((((((`X` IS UNKNOWN) IS NOT UNKNOWN) IS FALSE) IS NOT FALSE) IS TRUE) IS NOT TRUE) IS NULL) IS NOT NULL)");
// combine IS postfix operators with infix (AND) and prefix (NOT) ops
- check(
- "select * from t where x is unknown is false and x is unknown is true or not y is unknown is not null",
- "SELECT *\n"
- + "FROM `T`\n"
- + "WHERE ((((`X` IS UNKNOWN) IS FALSE) AND ((`X` IS UNKNOWN) IS TRUE)) OR (((NOT `Y`) IS UNKNOWN) IS NOT NULL))");
+ final String sql = "select * from t "
+ + "where x is unknown is false "
+ + "and x is unknown is true "
+ + "or not y is unknown is not null";
+ final String expected = "SELECT *\n"
+ + "FROM `T`\n"
+ + "WHERE ((((`X` IS UNKNOWN) IS FALSE)"
+ + " AND ((`X` IS UNKNOWN) IS TRUE))"
+ + " OR (NOT ((`Y` IS UNKNOWN) IS NOT NULL)))";
+ check(sql, expected);
}
@Test public void testEqualNotEqual() {
@@ -871,10 +926,10 @@ public class SqlParserTest {
"values a between b and c + 2 or d and e",
"VALUES (ROW(((`A` BETWEEN ASYMMETRIC `B` AND (`C` + 2)) OR (`D` AND `E`))))");
- // '=' and BETWEEN have same precedence, and are left-assoc
+ // '=' has slightly lower precedence than BETWEEN; both are left-assoc
check(
"values x = a between b and c = d = e",
- "VALUES (ROW(((((`X` = `A`) BETWEEN ASYMMETRIC `B` AND `C`) = `D`) = `E`)))");
+ "VALUES (ROW((((`X` = (`A` BETWEEN ASYMMETRIC `B` AND `C`)) = `D`) = `E`)))");
// AND doesn't match BETWEEN if it's between parentheses!
check(
@@ -1147,7 +1202,7 @@ public class SqlParserTest {
// LIKE has same precedence as '='; LIKE is right-assoc, '=' is left
check(
"values a = b like c = d",
- "VALUES (ROW(((`A` = `B`) LIKE (`C` = `D`))))");
+ "VALUES (ROW(((`A` = (`B` LIKE `C`)) = `D`)))");
// Nested LIKE
check(
@@ -1228,13 +1283,13 @@ public class SqlParserTest {
@Test public void testFoo() {
}
- @Test public void testArthimeticOperators() {
+ @Test public void testArithmeticOperators() {
checkExp("1-2+3*4/5/6-7", "(((1 - 2) + (((3 * 4) / 5) / 6)) - 7)");
checkExp("power(2,3)", "POWER(2, 3)");
checkExp("aBs(-2.3e-2)", "ABS(-2.3E-2)");
checkExp("MOD(5 ,\t\f\r\n2)", "MOD(5, 2)");
checkExp("ln(5.43 )", "LN(5.43)");
- checkExp("log10(- -.2 )", "LOG10((- -0.2))");
+ checkExp("log10(- -.2 )", "LOG10(0.2)");
}
@Test public void testExists() {
@@ -2297,14 +2352,14 @@ public class SqlParserTest {
check(
"values (- -1\n"
+ ")",
- "VALUES (ROW((- -1)))");
+ "VALUES (ROW(1))");
check(
"values (--1+\n"
+ "2)",
"VALUES (ROW(2))");
- // end of multiline commment without start
+ // end of multiline comment without start
if (Bug.FRG73_FIXED) {
checkFails("values (1 */ 2)", "xx");
}
@@ -2380,7 +2435,7 @@ public class SqlParserTest {
checkExp("1", "1");
checkExp("+1.", "1");
checkExp("-1", "-1");
- checkExp("- -1", "(- -1)");
+ checkExp("- -1", "1");
checkExp("1.0", "1.0");
checkExp("-3.2", "-3.2");
checkExp("1.", "1");
@@ -2437,7 +2492,11 @@ public class SqlParserTest {
}
@Test public void testPrecedence2() {
- checkExp("- - 1", "(- -1)"); // two prefices
+ checkExp("- - 1", "1"); // special case for unary minus
+ }
+
+ @Test public void testPrecedence2b() {
+ checkExp("not not 1", "(NOT (NOT 1))"); // two prefixes
}
@Test public void testPrecedence3() {
@@ -2821,7 +2880,7 @@ public class SqlParserTest {
"select ^cursor^(select * from emps) from emps",
"CURSOR expression encountered in illegal context");
checkFails(
- "call p(^cursor^(select * from emps))",
+ "call list(^cursor^(select * from emps))",
"CURSOR expression encountered in illegal context");
checkFails(
"select f(^cursor^(select * from emps)) from emps",
@@ -6887,11 +6946,7 @@ public class SqlParserTest {
try {
sqlNode = parseStmt(sql);
} catch (SqlParseException e) {
- e.printStackTrace();
- String message = "Received error while parsing SQL '" + sql
- + "'; error is:\n"
- + e.toString();
- throw new AssertionError(message);
+ throw new RuntimeException("Error while parsing SQL: " + sql, e);
}
return sqlNode;
}
@@ -6912,10 +6967,7 @@ public class SqlParserTest {
try {
sqlNode = parseExpression(sql);
} catch (SqlParseException e) {
- String message = "Received error while parsing SQL '" + sql
- + "'; error is:\n"
- + e.toString();
- throw new RuntimeException(message, e);
+ throw new RuntimeException("Error while parsing expression: " + sql, e);
}
return sqlNode;
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/test/java/org/apache/calcite/sql/test/SqlTesterImpl.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlTesterImpl.java b/core/src/test/java/org/apache/calcite/sql/test/SqlTesterImpl.java
index f9f1bd1..3c55d6d 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlTesterImpl.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlTesterImpl.java
@@ -113,23 +113,16 @@ public class SqlTesterImpl implements SqlTester {
} catch (SqlParseException e) {
String errMessage = e.getMessage();
if (expectedMsgPattern == null) {
- e.printStackTrace();
- throw new AssertionError(
- "Error while parsing query [" + sap.sql + "]");
- } else if (
- (null == errMessage)
- || !errMessage.matches(expectedMsgPattern)) {
- e.printStackTrace();
- throw new AssertionError(
- "Error did not match expected ["
- + expectedMsgPattern + "] while parsing query ["
- + sap.sql + "]");
+ throw new RuntimeException("Error while parsing query:" + sap.sql, e);
+ } else if (errMessage == null
+ || !errMessage.matches(expectedMsgPattern)) {
+ throw new RuntimeException("Error did not match expected ["
+ + expectedMsgPattern + "] while parsing query ["
+ + sap.sql + "]", e);
}
return;
} catch (Throwable e) {
- e.printStackTrace();
- throw new AssertionError(
- "Error while parsing query [" + sap.sql + "]");
+ throw new RuntimeException("Error while parsing query: " + sap.sql, e);
}
Throwable thrown = null;
@@ -163,11 +156,8 @@ public class SqlTesterImpl implements SqlTester {
SqlNode sqlNode;
try {
sqlNode = parseQuery(sql);
- } catch (SqlParseException e) {
- throw new RuntimeException("Error while parsing query [" + sql + "]", e);
} catch (Throwable e) {
- e.printStackTrace();
- throw new AssertionError("Error while parsing query [" + sql + "]");
+ throw new RuntimeException("Error while parsing query: " + sql, e);
}
return validator.validate(sqlNode);
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/CalciteSuite.java b/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
index 7211e05..7152553 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteSuite.java
@@ -53,6 +53,7 @@ import org.apache.calcite.util.ChunkListTest;
import org.apache.calcite.util.ImmutableBitSetTest;
import org.apache.calcite.util.PartiallyOrderedSetTest;
import org.apache.calcite.util.PermutationTestCase;
+import org.apache.calcite.util.PrecedenceClimbingParserTest;
import org.apache.calcite.util.ReflectVisitorTest;
import org.apache.calcite.util.UtilTest;
import org.apache.calcite.util.graph.DirectedGraphTest;
@@ -81,6 +82,7 @@ import org.junit.runners.Suite;
RelOptUtilTest.class,
RelCollationTest.class,
UtilTest.class,
+ PrecedenceClimbingParserTest.class,
MappingTest.class,
CalciteResourceTest.class,
FilteratorTest.class,
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 5d2ef4a..7fff2d9 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -23,6 +23,7 @@ import org.apache.calcite.config.Lex;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.sql.SqlCollation;
import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlSpecialOperator;
import org.apache.calcite.sql.fun.OracleSqlOperatorTable;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.test.SqlTester;
@@ -37,6 +38,7 @@ import org.apache.calcite.util.ImmutableBitSet;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Ordering;
import org.junit.BeforeClass;
import org.junit.Ignore;
@@ -46,6 +48,7 @@ import org.slf4j.LoggerFactory;
import java.nio.charset.Charset;
import java.util.Arrays;
+import java.util.Comparator;
import java.util.List;
import java.util.Locale;
@@ -7623,6 +7626,184 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
}
}
+ private static int prec(SqlOperator op) {
+ return Math.max(op.getLeftPrec(), op.getRightPrec());
+ }
+
+ /** Tests that operators, sorted by precedence, are in a sane order. Each
+ * operator has a {@link SqlOperator#getLeftPrec() left} and
+ * {@link SqlOperator#getRightPrec()} right} precedence, but we would like
+ * the order to remain the same even if we tweak particular operators'
+ * precedences. If you need to update the expected output, you might also
+ * need to change
+ * <a href="http://calcite.apache.org/docs/reference.html#operator-precedence">
+ * the documentation</a>. */
+ @Test public void testOperatorsSortedByPrecedence() {
+ final StringBuilder b = new StringBuilder();
+ final Comparator<SqlOperator> comparator =
+ new Comparator<SqlOperator>() {
+ public int compare(SqlOperator o1, SqlOperator o2) {
+ int c = Integer.compare(prec(o1), prec(o2));
+ if (c != 0) {
+ return -c;
+ }
+ c = o1.getName().compareTo(o2.getName());
+ if (c != 0) {
+ return c;
+ }
+ return o1.getSyntax().compareTo(o2.getSyntax());
+ }
+ };
+ final List<SqlOperator> operators =
+ SqlStdOperatorTable.instance().getOperatorList();
+ int p = -1;
+ for (SqlOperator op : Ordering.from(comparator).sortedCopy(operators)) {
+ final String type;
+ switch (op.getSyntax()) {
+ case FUNCTION:
+ case FUNCTION_ID:
+ case FUNCTION_STAR:
+ case INTERNAL:
+ continue;
+ case PREFIX:
+ type = "pre";
+ break;
+ case POSTFIX:
+ type = "post";
+ break;
+ case BINARY:
+ if (op.getLeftPrec() < op.getRightPrec()) {
+ type = "left";
+ } else {
+ type = "right";
+ }
+ break;
+ default:
+ if (op instanceof SqlSpecialOperator) {
+ type = "-";
+ } else {
+ continue;
+ }
+ }
+ if (prec(op) != p) {
+ b.append('\n');
+ p = prec(op);
+ }
+ b.append(op.getName())
+ .append(' ')
+ .append(type)
+ .append('\n');
+ }
+ final String expected = "\n"
+ + "ARRAY -\n"
+ + "ARRAY -\n"
+ + "COLUMN_LIST -\n"
+ + "CURSOR -\n"
+ + "LATERAL -\n"
+ + "MAP -\n"
+ + "MAP -\n"
+ + "MULTISET -\n"
+ + "MULTISET -\n"
+ + "ROW -\n"
+ + "TABLE -\n"
+ + "UNNEST -\n"
+ + "\n"
+ + "CURRENT_VALUE -\n"
+ + "DEFAULT -\n"
+ + "ITEM -\n"
+ + "NEXT_VALUE -\n"
+ + "\n"
+ + "$LiteralChain -\n"
+ + "+ pre\n"
+ + "- pre\n"
+ + ". left\n"
+ + "\n"
+ + "* left\n"
+ + "/ left\n"
+ + "/INT left\n"
+ + "|| left\n"
+ + "\n"
+ + "+ left\n"
+ + "- left\n"
+ + "- -\n"
+ + "DATETIME_PLUS -\n"
+ + "EXISTS pre\n"
+ + "\n"
+ + "BETWEEN ASYMMETRIC -\n"
+ + "BETWEEN SYMMETRIC -\n"
+ + "IN left\n"
+ + "LIKE -\n"
+ + "NOT BETWEEN ASYMMETRIC -\n"
+ + "NOT BETWEEN SYMMETRIC -\n"
+ + "NOT IN left\n"
+ + "NOT LIKE -\n"
+ + "NOT SIMILAR TO -\n"
+ + "SIMILAR TO -\n"
+ + "\n"
+ + "$IS_DIFFERENT_FROM left\n"
+ + "< left\n"
+ + "<= left\n"
+ + "<> left\n"
+ + "= left\n"
+ + "> left\n"
+ + ">= left\n"
+ + "IS DISTINCT FROM left\n"
+ + "IS NOT DISTINCT FROM left\n"
+ + "MEMBER OF left\n"
+ + "OVERLAPS -\n"
+ + "SUBMULTISET OF left\n"
+ + "\n"
+ + "IS A SET post\n"
+ + "IS FALSE post\n"
+ + "IS NOT FALSE post\n"
+ + "IS NOT NULL post\n"
+ + "IS NOT TRUE post\n"
+ + "IS NOT UNKNOWN post\n"
+ + "IS NULL post\n"
+ + "IS TRUE post\n"
+ + "IS UNKNOWN post\n"
+ + "\n"
+ + "NOT pre\n"
+ + "\n"
+ + "AND left\n"
+ + "\n"
+ + "OR left\n"
+ + "\n"
+ + "=> -\n"
+ + "AS -\n"
+ + "DESC post\n"
+ + "OVER left\n"
+ + "TABLESAMPLE -\n"
+ + "\n"
+ + "INTERSECT left\n"
+ + "INTERSECT ALL left\n"
+ + "MULTISET INTERSECT left\n"
+ + "MULTISET INTERSECT ALL left\n"
+ + "NULLS FIRST post\n"
+ + "NULLS LAST post\n"
+ + "\n"
+ + "EXCEPT left\n"
+ + "EXCEPT ALL left\n"
+ + "MULTISET EXCEPT left\n"
+ + "MULTISET EXCEPT ALL left\n"
+ + "MULTISET UNION left\n"
+ + "MULTISET UNION ALL left\n"
+ + "UNION left\n"
+ + "UNION ALL left\n"
+ + "\n"
+ + "$throw -\n"
+ + "EXTRACT_DATE -\n"
+ + "FILTER left\n"
+ + "Reinterpret -\n"
+ + "TABLE pre\n"
+ + "VALUES -\n"
+ + "\n"
+ + "CALL pre\n"
+ + "ESCAPE -\n"
+ + "NEW pre\n";
+ assertThat(b.toString(), is(expected));
+ }
+
/** Tests that it is an error to insert into the same column twice, even using
* case-insensitive matching. */
@Test public void testCaseInsensitiveInsert() {
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/test/java/org/apache/calcite/util/PrecedenceClimbingParserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/util/PrecedenceClimbingParserTest.java b/core/src/test/java/org/apache/calcite/util/PrecedenceClimbingParserTest.java
new file mode 100644
index 0000000..3fbeac4
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/util/PrecedenceClimbingParserTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.calcite.util;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit test for {@link PrecedenceClimbingParser}.
+ */
+public class PrecedenceClimbingParserTest {
+ @Test public void testBasic() {
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .atom("a")
+ .infix("+", 1, true)
+ .prefix("-", 3)
+ .atom("b")
+ .infix("*", 2, true)
+ .atom("c")
+ .postfix("!", 4)
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token), is("(a + ((- b) * (c !)))"));
+ }
+
+ @Test public void testRepeatedPrefixPostfix() {
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .prefix("+", 3)
+ .prefix("-", 3)
+ .prefix("+", 3)
+ .prefix("+", 3)
+ .atom("a")
+ .postfix("!", 4)
+ .infix("+", 1, true)
+ .prefix("-", 3)
+ .prefix("-", 3)
+ .atom("b")
+ .postfix("!", 4)
+ .postfix("!", 4)
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token),
+ is("((+ (- (+ (+ (a !))))) + (- (- ((b !) !))))"));
+ }
+
+ @Test public void testAtom() {
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .atom("a")
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token), is("a"));
+ }
+
+ @Test public void testOnlyPrefix() {
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .prefix("-", 3)
+ .prefix("-", 3)
+ .atom(1)
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token), is("(- (- 1))"));
+ }
+
+ @Test public void testOnlyPostfix() {
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .atom(1)
+ .postfix("!", 33333)
+ .postfix("!", 33333)
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token), is("((1 !) !)"));
+ }
+
+ @Test public void testLeftAssociative() {
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .atom("a")
+ .infix("*", 2, true)
+ .atom("b")
+ .infix("+", 1, true)
+ .atom("c")
+ .infix("+", 1, true)
+ .atom("d")
+ .infix("+", 1, true)
+ .atom("e")
+ .infix("*", 2, true)
+ .atom("f")
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token), is("((((a * b) + c) + d) + (e * f))"));
+ }
+
+ @Test public void testRightAssociative() {
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .atom("a")
+ .infix("^", 3, false)
+ .atom("b")
+ .infix("^", 3, false)
+ .atom("c")
+ .infix("^", 3, false)
+ .atom("d")
+ .infix("+", 1, true)
+ .atom("e")
+ .infix("*", 2, true)
+ .atom("f")
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token), is("((a ^ (b ^ (c ^ d))) + (e * f))"));
+ }
+
+ @Test public void testSpecial() {
+ // price > 5 and price between 1 + 2 and 3 * 4 and price is null
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .atom("price")
+ .infix(">", 4, true)
+ .atom("5")
+ .infix("and", 2, true)
+ .atom("price")
+ .special("between", 3, 3,
+ new PrecedenceClimbingParser.Special() {
+ public PrecedenceClimbingParser.Result apply(
+ PrecedenceClimbingParser parser,
+ PrecedenceClimbingParser.SpecialOp op) {
+ return new PrecedenceClimbingParser.Result(op.previous,
+ op.next.next.next,
+ parser.call(op,
+ ImmutableList.of(op.previous, op.next,
+ op.next.next.next)));
+ }
+ })
+ .atom("1")
+ .infix("+", 5, true)
+ .atom("2")
+ .infix("and", 2, true)
+ .atom("3")
+ .infix("*", 6, true)
+ .atom("4")
+ .infix("and", 2, true)
+ .atom("price")
+ .postfix("is null", 4)
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token),
+ is("(((price > 5) and between(price, (1 + 2), (3 * 4)))"
+ + " and (price is null))"));
+ }
+
+ @Test public void testEqualPrecedence() {
+ // LIKE has same precedence as '='; LIKE is right-assoc, '=' is left
+ final PrecedenceClimbingParser p = new PrecedenceClimbingParser.Builder()
+ .atom("a")
+ .infix("=", 3, true)
+ .atom("b")
+ .infix("like", 3, false)
+ .atom("c")
+ .infix("=", 3, true)
+ .atom("d")
+ .build();
+ final PrecedenceClimbingParser.Token token = p.parse();
+ assertThat(p.print(token), is("(((a = b) like c) = d)"));
+ }
+}
+
+// End PrecedenceClimbingParserTest.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/core/src/test/resources/sql/operator.iq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/operator.iq b/core/src/test/resources/sql/operator.iq
new file mode 100644
index 0000000..626d31d
--- /dev/null
+++ b/core/src/test/resources/sql/operator.iq
@@ -0,0 +1,69 @@
+# operator.iq - expressions involving operators
+#
+# 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.
+#
+!use scott
+!set outputformat mysql
+
+# [CALCITE-1095] NOT precedence
+select * from "scott".emp where not sal > 1300;
++-------+--------+----------+------+------------+---------+---------+--------+
+| EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO |
++-------+--------+----------+------+------------+---------+---------+--------+
+| 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | | 20 |
+| 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 |
+| 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 |
+| 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | | 20 |
+| 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | | 30 |
+| 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | | 10 |
++-------+--------+----------+------+------------+---------+---------+--------+
+(6 rows)
+
+!ok
+
+select count(*) as c from "scott".emp where not ename = 'SMITH';
++----+
+| C |
++----+
+| 13 |
++----+
+(1 row)
+
+!ok
+
+select count(*) as c from "scott".emp where not not ename = 'SMITH';
++---+
+| C |
++---+
+| 1 |
++---+
+(1 row)
+
+!ok
+
+select * from "scott".emp where not sal > 1300 and not sal < 1200;
++-------+--------+----------+------+------------+---------+---------+--------+
+| EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO |
++-------+--------+----------+------+------------+---------+---------+--------+
+| 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 |
+| 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 |
+| 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | | 10 |
++-------+--------+----------+------+------------+---------+---------+--------+
+(3 rows)
+
+!ok
+
+# End operator.iq
http://git-wip-us.apache.org/repos/asf/calcite/blob/339ac13e/site/_docs/reference.md
----------------------------------------------------------------------
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 8f9fd16..428ef83 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -831,6 +831,24 @@ Note:
## Operators and functions
+### Operator precedence
+
+The operator precedence and associativity, highest to lowest.
+
+| Operator | Associativity
+|:------------------------------------------------- |:-------------
+| . | left
+| [ ] (array element) | left
+| + - (unary plus, minus) | right
+| * / | left
+| + - | left
+| BETWEEN, IN, LIKE, SIMILAR | -
+| < > = <= >= <> | left
+| IS NULL, IS FALSE, IS NOT TRUE etc. | -
+| NOT | right
+| AND | left
+| OR | left
+
### Comparison operators
| Operator syntax | Description