[CALCITE-1641] Add parser and validator support for MATCH_RECOGNIZE (Zhiqiang-He)
Close apache/calcite#378 Project: http://git-wip-us.apache.org/repos/asf/calcite/repo Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/4e103825 Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/4e103825 Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/4e103825 Branch: refs/heads/master Commit: 4e1038255163792b8055d1b0c3f312bda872252d Parents: 255cd96 Author: Zhiqiang-He <[email protected]> Authored: Tue Feb 21 19:44:11 2017 +0800 Committer: Julian Hyde <[email protected]> Committed: Sat Mar 4 13:02:44 2017 -0800 ---------------------------------------------------------------------- core/src/main/codegen/templates/Parser.jj | 367 +++++++++++++++- .../calcite/prepare/CalciteMaterializer.java | 6 +- .../calcite/rel/RelHomogeneousShuttle.java | 5 + .../java/org/apache/calcite/rel/RelShuttle.java | 3 + .../org/apache/calcite/rel/RelShuttleImpl.java | 5 + .../java/org/apache/calcite/rel/core/Match.java | 274 ++++++++++++ .../apache/calcite/rel/core/RelFactories.java | 30 ++ .../calcite/rel/logical/LogicalMatch.java | 77 ++++ .../calcite/rel/rel2sql/RelToSqlConverter.java | 33 ++ .../calcite/rel/rel2sql/SqlImplementor.java | 26 ++ .../org/apache/calcite/rex/LogicVisitor.java | 4 + .../org/apache/calcite/rex/RexBiVisitor.java | 2 + .../apache/calcite/rex/RexPatternFieldRef.java | 59 +++ .../java/org/apache/calcite/rex/RexShuttle.java | 4 + .../java/org/apache/calcite/rex/RexUtil.java | 4 + .../java/org/apache/calcite/rex/RexVisitor.java | 2 + .../org/apache/calcite/rex/RexVisitorImpl.java | 4 + .../apache/calcite/runtime/CalciteResource.java | 21 + .../apache/calcite/sql/SqlFunctionCategory.java | 3 +- .../java/org/apache/calcite/sql/SqlKind.java | 55 +++ .../apache/calcite/sql/SqlMatchRecognize.java | 216 +++++++++ .../java/org/apache/calcite/sql/SqlSelect.java | 10 +- .../calcite/sql/fun/SqlStdOperatorTable.java | 158 +++++++ .../apache/calcite/sql/type/OperandTypes.java | 3 +- .../calcite/sql/validate/DelegatingScope.java | 3 +- .../sql/validate/MatchRecognizeNamespace.java | 47 ++ .../sql/validate/MatchRecognizeScope.java | 94 ++++ .../calcite/sql/validate/SqlValidator.java | 16 + .../calcite/sql/validate/SqlValidatorImpl.java | 440 ++++++++++++++++++- .../sql2rel/RelStructuredTypeFlattener.java | 5 + .../calcite/sql2rel/SqlToRelConverter.java | 85 ++++ .../sql2rel/StandardConvertletTable.java | 9 +- .../calcite/runtime/CalciteResource.properties | 7 + .../prepare/LookupOperatorOverloadsTest.java | 3 +- .../rel/rel2sql/RelToSqlConverterTest.java | 350 +++++++++++++++ .../calcite/sql/parser/SqlParserTest.java | 292 ++++++++++++ .../apache/calcite/sql/test/SqlAdvisorTest.java | 3 + .../apache/calcite/test/SqlValidatorTest.java | 105 +++++ site/_docs/reference.md | 118 ++++- 39 files changed, 2913 insertions(+), 35 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/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 b070530..6371c24 100644 --- a/core/src/main/codegen/templates/Parser.jj +++ b/core/src/main/codegen/templates/Parser.jj @@ -67,6 +67,7 @@ import org.apache.calcite.sql.SqlJdbcFunctionCall; import org.apache.calcite.sql.SqlJoin; import org.apache.calcite.sql.SqlKind; import org.apache.calcite.sql.SqlLiteral; +import org.apache.calcite.sql.SqlMatchRecognize; import org.apache.calcite.sql.SqlMerge; import org.apache.calcite.sql.SqlNode; import org.apache.calcite.sql.SqlNodeList; @@ -226,9 +227,9 @@ public class ${parser.class} extends SqlAbstractParserImpl PARSER_END(${parser.class}) -/***************************************** - * Utility Codes for Semantical Analysis * - *****************************************/ +/*************************************** + * Utility Codes for Semantic Analysis * + ***************************************/ /* For Debug */ JAVACODE @@ -321,10 +322,6 @@ SqlNode FloorCeilOptions(SqlParserPos pos, boolean floorFlag) : } } - - -// End Parser.jj - /* // This file contains the heart of a parser for SQL SELECT statements. // code can be shared between various parsers (for example, a DDL parser and a @@ -1823,6 +1820,14 @@ SqlNode TableRef2(boolean lateral) : pos, tableRef, over); } } + [ + over = MatchRecognizeOpt(tableRef) + { + if (over != null) { + tableRef = over; + } + } + ] | [ <LATERAL> { lateral = true; } ] tableRef = ParenthesizedExpression(ExprContext.ACCEPT_QUERY) @@ -1838,6 +1843,14 @@ SqlNode TableRef2(boolean lateral) : getPos(), tableRef); } } + ( + [ over = MatchRecognizeOpt(tableRef) ] + { + if (over != null) { + tableRef = over; + } + } + ) | <UNNEST> { pos = getPos(); } args = ParenthesizedQueryOrCommaList(ExprContext.ACCEPT_SUB_QUERY) @@ -2462,6 +2475,236 @@ SqlNode OrderItem() : } } +/** + * Parses a MATCH_RECOGNIZE clause following a table expression. + */ +SqlMatchRecognize MatchRecognizeOpt(SqlNode tableRef) : +{ + final SqlParserPos startPos; + SqlParserPos pos; + SqlNode pattern; + SqlNodeList patternDefList; + SqlLiteral isStrictStarts = SqlLiteral.createBoolean(false, getPos()); + SqlLiteral isStrictEnds = SqlLiteral.createBoolean(false, getPos()); +} +{ + <MATCH_RECOGNIZE> { startPos = getPos(); } <LPAREN> + <PATTERN> + <LPAREN> + ( + <CARET> { isStrictStarts = SqlLiteral.createBoolean(true, getPos()); } + | + { isStrictStarts = SqlLiteral.createBoolean(false, getPos()); } + ) + pattern = PatternExpression() + ( + <DOLLAR> { isStrictEnds = SqlLiteral.createBoolean(true, getPos()); } + | + { isStrictEnds = SqlLiteral.createBoolean(false, getPos()); } + ) + <RPAREN> + <DEFINE> { pos = getPos(); } + patternDefList = PatternDefinitionCommaList(pos) + <RPAREN> { + return new SqlMatchRecognize(startPos.plus(getPos()), tableRef, + pattern, isStrictStarts, isStrictEnds, patternDefList); + } +} + +SqlNode PatternExpression() : +{ + SqlNode left; + SqlNode right; +} +{ + left = PatternTerm() + ( + <VERTICAL_BAR> + right = PatternTerm() { + left = SqlStdOperatorTable.PATTERN_ALTER.createCall( + left.getParserPosition().plus(getPos()), left, right); + } + )* + { + return left; + } +} + +SqlNode PatternTerm() : +{ + SqlNode left; + SqlNode right; +} +{ + left = PatternFactor() + ( + right = PatternFactor() { + left = SqlStdOperatorTable.PATTERN_CONCAT.createCall( + left.getParserPosition().plus(getPos()), left, right); + } + )* + { + return left; + } +} + +SqlNode PatternFactor() : +{ + SqlNode e; + SqlNode extra; + SqlLiteral startNum = null; + SqlLiteral endNum = null; + SqlLiteral reluctant = SqlLiteral.createBoolean(false, SqlParserPos.ZERO); +} +{ + e = PatternPrimary() + [ + ( + <STAR> { + startNum = SqlLiteral.createExactNumeric("0", SqlParserPos.ZERO); + endNum = SqlLiteral.createExactNumeric("-1", SqlParserPos.ZERO); + } + | + <PLUS> { + startNum = SqlLiteral.createExactNumeric("1", SqlParserPos.ZERO); + endNum = SqlLiteral.createExactNumeric("-1", SqlParserPos.ZERO); + } + | + <HOOK> { + startNum = SqlLiteral.createExactNumeric("0", SqlParserPos.ZERO); + endNum = SqlLiteral.createExactNumeric("1", SqlParserPos.ZERO); + } + | + <LBRACE> + ( + startNum = UnsignedNumericLiteral() { endNum = startNum; } + [ + <COMMA> { + endNum = SqlLiteral.createExactNumeric("-1", SqlParserPos.ZERO); + } + [ + endNum = UnsignedNumericLiteral() + ] + ] + <RBRACE> + | + { + startNum = SqlLiteral.createExactNumeric("-1", SqlParserPos.ZERO); + } + <COMMA> + endNum = UnsignedNumericLiteral() + <RBRACE> + | + <MINUS> extra = PatternExpression() <MINUS> <RBRACE> { + extra = SqlStdOperatorTable.PATTERN_EXCLUDE.createCall( + extra.getParserPosition().plus(getPos()), extra); + e = SqlStdOperatorTable.PATTERN_CONCAT.createCall( + e.getParserPosition().plus(getPos()), e, extra); + return e; + } + ) + ) + [ + <HOOK> + { + if (startNum.intValue(true) != endNum.intValue(true)) { + reluctant = SqlLiteral.createBoolean(true, SqlParserPos.ZERO); + } + } + ] + ] + { + if (startNum == null) { + return e; + } else { + return SqlStdOperatorTable.PATTERN_QUANTIFIER.createCall( + e.getParserPosition().plus(getPos()), + e, startNum, endNum, reluctant); + } + } +} + +SqlNode PatternPrimary() : +{ + SqlParserPos pos; + SqlNode e; + List<SqlNode> eList; +} +{ + ( + e = SimpleIdentifier() + | + <LPAREN> e = PatternExpression() <RPAREN> + | + <LBRACE> { pos = getPos(); } + <MINUS> e = PatternExpression() + <MINUS> <RBRACE> { + e = SqlStdOperatorTable.PATTERN_EXCLUDE.createCall( + pos.plus(getPos()), e); + } + | + ( + <PERMUTE> { pos = getPos(); } + <LPAREN> + e = PatternExpression() { + eList = new ArrayList<SqlNode>(); + eList.add(e); + } + ( + <COMMA> + e = PatternExpression() + { + eList.add(e); + } + )* + <RPAREN> { + e = SqlStdOperatorTable.PATTERN_PERMUTE.createCall( + pos.plus(getPos()), eList); + } + ) + ) + { + return e; + } +} + +SqlNodeList PatternDefinitionCommaList(SqlParserPos pos) : +{ + SqlNode e; + final List<SqlNode> eList = new ArrayList<SqlNode>(); +} +{ + e = PatternDefinition() { + if (pos == null) { + pos = e.getParserPosition(); + } + eList.add(e); + } + ( + <COMMA> + e = PatternDefinition() { + eList.add(e); + } + )* + { + return new SqlNodeList(eList, pos.plus(getPos())); + } +} + +SqlNode PatternDefinition() : +{ + SqlNode var; + SqlNode e; +} +{ + var = SimpleIdentifier() + <AS> + e = Expression(ExprContext.ACCEPT_SUB_QUERY) { + return SqlStdOperatorTable.PATTERN_DEFINE_AS.createCall( + var.getParserPosition().plus(getPos()), e, var); + } +} + // ---------------------------------------------------------------------------- // Expressions @@ -4473,6 +4716,8 @@ SqlNode BuiltinFunctionCall() : node = TimestampDiffFunctionCall() { return node; } | node = ExtendedBuiltinFunctionCall() { return node; } + | + node = MatchRecognizeFunctionCall() { return node; } } /** @@ -4533,6 +4778,103 @@ SqlCall TimestampDiffFunctionCall() : } } +SqlCall MatchRecognizeFunctionCall() : +{ + final SqlCall func; +} +{ + ( + func = MatchRecognizeNavigationLogical() + | + func = MatchRecognizeNavigationPhysical() + | + func = MatchRecognizeCallWithModifier() + ) + { return func; } +} + +SqlCall MatchRecognizeCallWithModifier() : +{ + final SqlParserPos pos; + final SqlOperator runningOp; + final SqlNode func; +} +{ + ( + <RUNNING> { runningOp = SqlStdOperatorTable.RUNNING; } + | + <FINAL> { runningOp = SqlStdOperatorTable.FINAL; } + ) + { pos = getPos(); } + func = NamedFunctionCall() { + return runningOp.createCall(pos.plus(getPos()), func); + } +} + +SqlCall MatchRecognizeNavigationLogical() : +{ + SqlParserPos pos; + SqlCall func; + final SqlOperator funcOp; + final SqlOperator runningOp; + SqlNode arg0; + SqlNode arg1 = SqlLiteral.createExactNumeric("0", SqlParserPos.ZERO); +} +{ + ( + <RUNNING> { runningOp = SqlStdOperatorTable.RUNNING; pos = getPos(); } + | + <FINAL> { runningOp = SqlStdOperatorTable.FINAL; pos = getPos(); } + | + { runningOp = null; pos = null; } + ) + ( + <FIRST> { funcOp = SqlStdOperatorTable.FIRST; } + | + <LAST> { funcOp = SqlStdOperatorTable.LAST; } + ) + { + if (pos == null) { + pos = getPos(); + } + } + <LPAREN> + arg0 = Expression(ExprContext.ACCEPT_SUB_QUERY) + [ <COMMA> arg1 = NumericLiteral() ] + <RPAREN> + { + func = funcOp.createCall(pos.plus(getPos()), arg0, arg1); + if (runningOp != null) { + return runningOp.createCall(pos.plus(getPos()), func); + } else { + return func; + } + } +} + +SqlCall MatchRecognizeNavigationPhysical() : +{ + SqlParserPos pos; + SqlCall func; + SqlOperator funcOp; + SqlNode arg0; + SqlNode arg1 = SqlLiteral.createExactNumeric("1", SqlParserPos.ZERO); +} +{ + ( + <PREV> { funcOp = SqlStdOperatorTable.PREV; } + | + <NEXT> { funcOp = SqlStdOperatorTable.NEXT; } + ) + { pos = getPos(); } + <LPAREN> + arg0 = Expression(ExprContext.ACCEPT_SUB_QUERY) + [ <COMMA> arg1 = NumericLiteral() ] + <RPAREN> { + return funcOp.createCall(pos.plus(getPos()), arg0, arg1); + } +} + /** * Parses a call to a named function (could be a builtin with regular * syntax, or else a UDF). @@ -5181,6 +5523,7 @@ SqlPostfixOperator PostfixRowOperator() : | < DEFAULTS: "DEFAULTS" > | < DEFERRABLE: "DEFERRABLE" > | < DEFERRED: "DEFERRED" > + | < DEFINE: "DEFINE" > | < DEFINED: "DEFINED" > | < DEFINER: "DEFINER" > | < DEGREE: "DEGREE" > @@ -5317,6 +5660,7 @@ SqlPostfixOperator PostfixRowOperator() : | < MAP: "MAP" > | < MATCH: "MATCH" > | < MATCHED: "MATCHED" > + | < MATCH_RECOGNIZE: "MATCH_RECOGNIZE"> | < MAX: "MAX" > | < MAXVALUE: "MAXVALUE" > | < MEMBER: "MEMBER" > @@ -5393,6 +5737,8 @@ SqlPostfixOperator PostfixRowOperator() : | < PASCAL: "PASCAL" > | < PASSTHROUGH: "PASSTHROUGH" > | < PATH: "PATH" > + | < PATTERN: "PATTERN" > + | < PERMUTE: "PERMUTE" > | < PERCENT_RANK: "PERCENT_RANK" > | < PERCENTILE_CONT: "PERCENTILE_CONT" > | < PERCENTILE_DISC: "PERCENTILE_DISC" > @@ -5405,6 +5751,7 @@ SqlPostfixOperator PostfixRowOperator() : | < PRECISION: "PRECISION" > | < PREPARE: "PREPARE" > | < PRESERVE: "PRESERVE" > + | < PREV: "PREV" > | < PRIMARY: "PRIMARY" > | < PRIOR: "PRIOR" > | < PRIVILEGES: "PRIVILEGES" > @@ -5456,6 +5803,7 @@ SqlPostfixOperator PostfixRowOperator() : | < ROW_COUNT: "ROW_COUNT" > | < ROW_NUMBER: "ROW_NUMBER" > | < ROWS: "ROWS" > + | < RUNNING: "RUNNING" > | < SAVEPOINT: "SAVEPOINT" > | < SCALE: "SCALE" > | < SCHEMA: "SCHEMA" > @@ -6054,6 +6402,9 @@ String CommonNonReservedKeyWord() : | < DOUBLE_PERIOD: ".." > | < QUOTE: "'" > | < DOUBLE_QUOTE: "\"" > + | < VERTICAL_BAR: "|" > + | < CARET: "^" > + | < DOLLAR: "$" > } @@ -6255,3 +6606,5 @@ void UnusedExtension() : LOOKAHEAD({false}) <ZONE> ) } + +// End Parser.jj http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/prepare/CalciteMaterializer.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/prepare/CalciteMaterializer.java b/core/src/main/java/org/apache/calcite/prepare/CalciteMaterializer.java index 3cb8557..3360627 100644 --- a/core/src/main/java/org/apache/calcite/prepare/CalciteMaterializer.java +++ b/core/src/main/java/org/apache/calcite/prepare/CalciteMaterializer.java @@ -34,6 +34,7 @@ import org.apache.calcite.rel.logical.LogicalExchange; import org.apache.calcite.rel.logical.LogicalFilter; import org.apache.calcite.rel.logical.LogicalIntersect; import org.apache.calcite.rel.logical.LogicalJoin; +import org.apache.calcite.rel.logical.LogicalMatch; import org.apache.calcite.rel.logical.LogicalMinus; import org.apache.calcite.rel.logical.LogicalProject; import org.apache.calcite.rel.logical.LogicalSort; @@ -144,7 +145,7 @@ class CalciteMaterializer extends CalcitePrepareImpl.CalcitePreparingStmt { } /** Implementation of {@link RelShuttle} that returns each relational - * expression unchanged. It does not visit children. */ + * expression unchanged. It does not visit inputs. */ static class RelNullShuttle implements RelShuttle { public RelNode visit(TableScan scan) { return scan; @@ -179,6 +180,9 @@ class CalciteMaterializer extends CalcitePrepareImpl.CalcitePreparingStmt { public RelNode visit(LogicalAggregate aggregate) { return aggregate; } + public RelNode visit(LogicalMatch match) { + return match; + } public RelNode visit(LogicalSort sort) { return sort; } http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/rel/RelHomogeneousShuttle.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rel/RelHomogeneousShuttle.java b/core/src/main/java/org/apache/calcite/rel/RelHomogeneousShuttle.java index ce45689..cd7b85b 100644 --- a/core/src/main/java/org/apache/calcite/rel/RelHomogeneousShuttle.java +++ b/core/src/main/java/org/apache/calcite/rel/RelHomogeneousShuttle.java @@ -24,6 +24,7 @@ import org.apache.calcite.rel.logical.LogicalExchange; import org.apache.calcite.rel.logical.LogicalFilter; import org.apache.calcite.rel.logical.LogicalIntersect; import org.apache.calcite.rel.logical.LogicalJoin; +import org.apache.calcite.rel.logical.LogicalMatch; import org.apache.calcite.rel.logical.LogicalMinus; import org.apache.calcite.rel.logical.LogicalProject; import org.apache.calcite.rel.logical.LogicalSort; @@ -39,6 +40,10 @@ public class RelHomogeneousShuttle extends RelShuttleImpl { return visit((RelNode) aggregate); } + @Override public RelNode visit(LogicalMatch match) { + return visit((RelNode) match); + } + @Override public RelNode visit(TableScan scan) { return visit((RelNode) scan); } http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/rel/RelShuttle.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rel/RelShuttle.java b/core/src/main/java/org/apache/calcite/rel/RelShuttle.java index 444506c..9e25c71 100644 --- a/core/src/main/java/org/apache/calcite/rel/RelShuttle.java +++ b/core/src/main/java/org/apache/calcite/rel/RelShuttle.java @@ -24,6 +24,7 @@ import org.apache.calcite.rel.logical.LogicalExchange; import org.apache.calcite.rel.logical.LogicalFilter; import org.apache.calcite.rel.logical.LogicalIntersect; import org.apache.calcite.rel.logical.LogicalJoin; +import org.apache.calcite.rel.logical.LogicalMatch; import org.apache.calcite.rel.logical.LogicalMinus; import org.apache.calcite.rel.logical.LogicalProject; import org.apache.calcite.rel.logical.LogicalSort; @@ -56,6 +57,8 @@ public interface RelShuttle { RelNode visit(LogicalAggregate aggregate); + RelNode visit(LogicalMatch match); + RelNode visit(LogicalSort sort); RelNode visit(LogicalExchange exchange); http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/rel/RelShuttleImpl.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rel/RelShuttleImpl.java b/core/src/main/java/org/apache/calcite/rel/RelShuttleImpl.java index a28f2b5..d822efb 100644 --- a/core/src/main/java/org/apache/calcite/rel/RelShuttleImpl.java +++ b/core/src/main/java/org/apache/calcite/rel/RelShuttleImpl.java @@ -25,6 +25,7 @@ import org.apache.calcite.rel.logical.LogicalExchange; import org.apache.calcite.rel.logical.LogicalFilter; import org.apache.calcite.rel.logical.LogicalIntersect; import org.apache.calcite.rel.logical.LogicalJoin; +import org.apache.calcite.rel.logical.LogicalMatch; import org.apache.calcite.rel.logical.LogicalMinus; import org.apache.calcite.rel.logical.LogicalProject; import org.apache.calcite.rel.logical.LogicalSort; @@ -74,6 +75,10 @@ public class RelShuttleImpl implements RelShuttle { return visitChild(aggregate, 0, aggregate.getInput()); } + public RelNode visit(LogicalMatch match) { + return visitChild(match, 0, match.getInput()); + } + public RelNode visit(TableScan scan) { return scan; } http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/rel/core/Match.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rel/core/Match.java b/core/src/main/java/org/apache/calcite/rel/core/Match.java new file mode 100644 index 0000000..4e81fd6 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/rel/core/Match.java @@ -0,0 +1,274 @@ +/* + * 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.core; + +import org.apache.calcite.linq4j.Ord; +import org.apache.calcite.plan.RelOptCluster; +import org.apache.calcite.plan.RelTraitSet; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.RelWriter; +import org.apache.calcite.rel.SingleRel; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeField; +import org.apache.calcite.rex.RexCall; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.rex.RexPatternFieldRef; +import org.apache.calcite.rex.RexVisitorImpl; +import org.apache.calcite.sql.SqlAggFunction; +import org.apache.calcite.sql.fun.SqlCountAggFunction; +import org.apache.calcite.sql.fun.SqlMinMaxAggFunction; +import org.apache.calcite.sql.fun.SqlSumAggFunction; +import org.apache.calcite.sql.fun.SqlSumEmptyIsZeroAggFunction; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedSet; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * Relational expression that represent a MATCH_RECOGNIZE node. + * + * <p>Each output row has the columns defined in the measure statements. + */ +public abstract class Match extends SingleRel { + //~ Instance fields --------------------------------------------- + protected final ImmutableMap<String, RexNode> measures; + protected final RexNode pattern; + protected final boolean strictStart; + protected final boolean strictEnd; + protected final ImmutableMap<String, RexNode> patternDefinitions; + protected final Set<RexMRAggCall> aggregateCalls; + + //~ Constructors ----------------------------------------------- + + /** + * Creates a Match. + * + * @param cluster Cluster + * @param traitSet Trait set + * @param input Input relational expression + * @param pattern Regular expression that defines pattern variables + * @param strictStart Whether it is a strict start pattern + * @param strictEnd Whether it is a strict end pattern + * @param patternDefinitions Pattern definitions + * @param rowType Row type + */ + protected Match(RelOptCluster cluster, RelTraitSet traitSet, + RelNode input, RexNode pattern, boolean strictStart, boolean strictEnd, + Map<String, RexNode> patternDefinitions, RelDataType rowType) { + super(cluster, traitSet, input); + this.pattern = Preconditions.checkNotNull(pattern); + Preconditions.checkArgument(patternDefinitions.size() > 0); + this.strictStart = strictStart; + this.strictEnd = strictEnd; + this.patternDefinitions = ImmutableMap.copyOf(patternDefinitions); + this.rowType = rowType; + this.measures = ImmutableMap.of(); + + final AggregateFinder aggregateFinder = new AggregateFinder(); + for (RexNode rex : this.patternDefinitions.values()) { + if (rex instanceof RexCall) { + aggregateFinder.go((RexCall) rex); + } + } + aggregateCalls = ImmutableSortedSet.copyOf(aggregateFinder.aggregateCalls); + } + + //~ Methods -------------------------------------------------- + + public Set<RexMRAggCall> getAggregateCalls() { + return aggregateCalls; + } + + public ImmutableMap<String, RexNode> getMeasures() { + return measures; + } + + public RexNode getPattern() { + return pattern; + } + + public boolean isStrictStart() { + return strictStart; + } + + public boolean isStrictEnd() { + return strictEnd; + } + + public ImmutableMap<String, RexNode> getPatternDefinitions() { + return patternDefinitions; + } + + public abstract Match copy(RelNode input, RexNode pattern, + boolean strictStart, boolean strictEnd, + Map<String, RexNode> patternDefinitions, RelDataType rowType); + + @Override public RelNode copy( + RelTraitSet traitSet, + List<RelNode> inputs) { + if (getInputs().equals(inputs) + && traitSet == getTraitSet()) { + return this; + } + + return copy( + inputs.get(0), + pattern, strictStart, strictEnd, + patternDefinitions, + rowType); + } + + @Override public RelWriter explainTerms(RelWriter pw) { + super.explainTerms(pw); + if (pw.nest()) { + pw.item("fields", rowType.getFieldNames()); + pw.item("exprs", getMeasures().values().asList()); + } else { + for (Ord<RelDataTypeField> field : Ord.zip(rowType.getFieldList())) { + String fieldName = field.e.getName(); + if (fieldName == null) { + fieldName = "Field#" + field.i; + } + pw.item(fieldName, getMeasures().get(field.i)); + } + } + return pw; + } + + + /** + * Find aggregate functions in operands. + */ + private static class AggregateFinder extends RexVisitorImpl { + final SortedSet<RexMRAggCall> aggregateCalls = new TreeSet<>(); + + AggregateFinder() { + super(true); + } + + @Override public Object visitCall(RexCall call) { + SqlAggFunction aggFunction = null; + switch (call.getKind()) { + case SUM: + aggFunction = new SqlSumAggFunction(call.getType()); + break; + case SUM0: + aggFunction = new SqlSumEmptyIsZeroAggFunction(); + break; + case MAX: + case MIN: + aggFunction = new SqlMinMaxAggFunction(call.getKind()); + break; + case COUNT: + aggFunction = new SqlCountAggFunction(); + break; + default: + for (RexNode rex : call.getOperands()) { + rex.accept(this); + } + } + if (aggFunction != null) { + RexMRAggCall aggCall = new RexMRAggCall(aggFunction, + call.getType(), call.getOperands(), aggregateCalls.size()); + aggregateCalls.add(aggCall); + Set<String> pv = new PatternVarFinder().go(call.getOperands()); + } + return null; + } + + public void go(RexCall call) { + call.accept(this); + } + } + + /** + * Visits the operands of an aggregate call to retrieve relevant pattern + * variables. + */ + private static class PatternVarFinder extends RexVisitorImpl { + final Set<String> patternVars = new HashSet<>(); + + PatternVarFinder() { + super(true); + } + + @Override public Object visitPatternFieldRef(RexPatternFieldRef fieldRef) { + patternVars.add(fieldRef.getAlpha()); + return null; + } + + @Override public Object visitCall(RexCall call) { + for (RexNode node : call.getOperands()) { + node.accept(this); + } + return null; + } + + public Set<String> go(RexNode rex) { + rex.accept(this); + return patternVars; + } + + public Set<String> go(List<RexNode> rexNodeList) { + for (RexNode rex : rexNodeList) { + rex.accept(this); + } + return patternVars; + } + } + + /** + * Aggregate calls in match recognize. + */ + public static class RexMRAggCall extends RexCall implements Comparable<RexMRAggCall> { + public final int ordinal; + public RexMRAggCall( + SqlAggFunction aggFun, + RelDataType type, + List<RexNode> operands, + int ordinal) { + super(type, aggFun, operands); + this.ordinal = ordinal; + digest = computeDigest(); + } + + public String computeDigest() { + return super.computeDigest(false); + } + + @Override public int compareTo(RexMRAggCall o) { + if (o.computeDigest() == null) { + return 0; + } + + if (computeDigest() == null) { + return 1; + } + + return o.computeDigest().compareTo(computeDigest()); + } + } +} + +// End Match.java http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java b/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java index 0d439c0..7dad9a8 100644 --- a/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java +++ b/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java @@ -27,6 +27,7 @@ import org.apache.calcite.rel.logical.LogicalCorrelate; import org.apache.calcite.rel.logical.LogicalFilter; import org.apache.calcite.rel.logical.LogicalIntersect; import org.apache.calcite.rel.logical.LogicalJoin; +import org.apache.calcite.rel.logical.LogicalMatch; import org.apache.calcite.rel.logical.LogicalMinus; import org.apache.calcite.rel.logical.LogicalProject; import org.apache.calcite.rel.logical.LogicalSort; @@ -46,6 +47,7 @@ import org.apache.calcite.util.ImmutableBitSet; import com.google.common.collect.ImmutableList; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -73,6 +75,9 @@ public class RelFactories { public static final AggregateFactory DEFAULT_AGGREGATE_FACTORY = new AggregateFactoryImpl(); + public static final MatchFactory DEFAULT_MATCH_FACTORY = + new MatchFactoryImpl(); + public static final SetOpFactory DEFAULT_SET_OP_FACTORY = new SetOpFactoryImpl(); @@ -92,6 +97,7 @@ public class RelFactories { DEFAULT_SEMI_JOIN_FACTORY, DEFAULT_SORT_FACTORY, DEFAULT_AGGREGATE_FACTORY, + DEFAULT_MATCH_FACTORY, DEFAULT_SET_OP_FACTORY, DEFAULT_VALUES_FACTORY, DEFAULT_TABLE_SCAN_FACTORY)); @@ -381,6 +387,30 @@ public class RelFactories { return LogicalTableScan.create(cluster, table); } } + + /** + * Can create a {@link Match} of + * the appropriate type for a rule's calling convention. + */ + public interface MatchFactory { + /** Creates a {@link Match}. */ + RelNode createMatchRecognize(RelNode input, RexNode pattern, + boolean strictStarts, boolean strictEnds, + Map<String, RexNode> patternDefinitions, RelDataType rowType); + } + + /** + * Implementation of {@link MatchFactory} + * that returns a {@link LogicalMatch}. + */ + private static class MatchFactoryImpl implements MatchFactory { + public RelNode createMatchRecognize(RelNode input, RexNode pattern, + boolean strictStarts, boolean strictEnds, + Map<String, RexNode> patternDefinitions, RelDataType rowType) { + return LogicalMatch.create(input, pattern, strictStarts, strictEnds, + patternDefinitions, rowType); + } + } } // End RelFactories.java http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/rel/logical/LogicalMatch.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rel/logical/LogicalMatch.java b/core/src/main/java/org/apache/calcite/rel/logical/LogicalMatch.java new file mode 100644 index 0000000..46d7755 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalMatch.java @@ -0,0 +1,77 @@ +/* + * 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.logical; + +import org.apache.calcite.plan.Convention; +import org.apache.calcite.plan.RelOptCluster; +import org.apache.calcite.plan.RelTraitSet; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.core.Match; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rex.RexNode; + +import java.util.Map; + +/** + * Sub-class of {@link Match} + * not targeted at any particular engine or calling convention. + */ +public class LogicalMatch extends Match { + + /** + * Creates a LogicalMatch. + * + * @param cluster cluster + * @param traitSet Trait set + * @param input Input relational expression + * @param pattern Regular Expression defining pattern variables + * @param strictStart Whether it is a strict start pattern + * @param strictEnd Whether it is a strict end pattern + * @param patternDefinitions Pattern definitions + * @param rowType Row type + */ + public LogicalMatch(RelOptCluster cluster, RelTraitSet traitSet, + RelNode input, RexNode pattern, boolean strictStart, boolean strictEnd, + Map<String, RexNode> patternDefinitions, RelDataType rowType) { + super(cluster, traitSet, input, pattern, strictStart, strictEnd, + patternDefinitions, rowType); + } + + /** + * Creates a LogicalMatch. + */ + public static LogicalMatch create(RelNode input, RexNode pattern, + boolean strictStart, boolean strictEnd, + Map<String, RexNode> patternDefinitions, RelDataType rowType) { + final RelOptCluster cluster = input.getCluster(); + final RelTraitSet traitSet = cluster.traitSetOf(Convention.NONE); + return new LogicalMatch(cluster, traitSet, input, pattern, + strictStart, strictEnd, patternDefinitions, rowType); + } + + //~ Methods ------------------------------------------------------ + + @Override public Match copy(RelNode input, RexNode pattern, + boolean strictStart, boolean strictEnd, + Map<String, RexNode> patternDefinitions, RelDataType rowType) { + final RelTraitSet traitSet = getCluster().traitSetOf(Convention.NONE); + return new LogicalMatch(getCluster(), traitSet, + input, pattern, strictStart, strictEnd, patternDefinitions, rowType); + } +} + +// End LogicalMatch.java http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java index 96cfac7..eefe8b1 100644 --- a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java +++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java @@ -26,6 +26,7 @@ import org.apache.calcite.rel.core.Filter; import org.apache.calcite.rel.core.Intersect; import org.apache.calcite.rel.core.Join; import org.apache.calcite.rel.core.JoinRelType; +import org.apache.calcite.rel.core.Match; import org.apache.calcite.rel.core.Minus; import org.apache.calcite.rel.core.Project; import org.apache.calcite.rel.core.Sort; @@ -46,6 +47,7 @@ import org.apache.calcite.sql.SqlIdentifier; import org.apache.calcite.sql.SqlInsert; import org.apache.calcite.sql.SqlJoin; import org.apache.calcite.sql.SqlLiteral; +import org.apache.calcite.sql.SqlMatchRecognize; import org.apache.calcite.sql.SqlNode; import org.apache.calcite.sql.SqlNodeList; import org.apache.calcite.sql.SqlSelect; @@ -355,6 +357,37 @@ public class RelToSqlConverter extends SqlImplementor }), POS); } + /** + * @see #dispatch + */ + public Result visit(Match e) { + final RelNode input = e.getInput(); + final Result x = visitChild(0, input); + final Context context = matchRecognizeContext(x.qualifiedContext()); + + SqlNode tableRef = x.asQueryOrValues(); + + RexNode rexPattern = e.getPattern(); + final SqlNode pattern = context.toSql(null, rexPattern); + final SqlLiteral isStrictStarts = SqlLiteral.createBoolean(e.isStrictStart(), POS); + final SqlLiteral isStrictEnds = SqlLiteral.createBoolean(e.isStrictEnd(), POS); + + List<SqlNode> list = Lists.newArrayList(); + for (Map.Entry<String, RexNode> entry : e.getPatternDefinitions().entrySet()) { + String alias = entry.getKey(); + SqlNode sqlNode = context.toSql(null, entry.getValue()); + sqlNode = SqlStdOperatorTable.PATTERN_DEFINE_AS.createCall(POS, + sqlNode, new SqlIdentifier(alias, POS)); + list.add(sqlNode); + } + + final SqlNodeList patternDefList = new SqlNodeList(list, POS); + + final SqlNode matchRecognize = new SqlMatchRecognize(POS, tableRef, + pattern, isStrictStarts, isStrictEnds, patternDefList); + return result(matchRecognize, Expressions.list(Clause.FROM), e, null); + } + @Override public void addSelect(List<SqlNode> selectList, SqlNode node, RelDataType rowType) { String name = rowType.getFieldNames().get(selectList.size()); http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/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 3c92f29..e9c148f 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 @@ -46,6 +46,7 @@ import org.apache.calcite.sql.SqlIdentifier; import org.apache.calcite.sql.SqlJoin; import org.apache.calcite.sql.SqlKind; import org.apache.calcite.sql.SqlLiteral; +import org.apache.calcite.sql.SqlMatchRecognize; import org.apache.calcite.sql.SqlNode; import org.apache.calcite.sql.SqlNodeList; import org.apache.calcite.sql.SqlOperator; @@ -61,6 +62,7 @@ import org.apache.calcite.sql.type.BasicSqlType; import org.apache.calcite.sql.type.InferTypes; import org.apache.calcite.sql.type.OperandTypes; import org.apache.calcite.sql.type.ReturnTypes; +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.sql.validate.SqlValidatorUtil; @@ -471,6 +473,7 @@ public abstract class SqlImplementor { SqlSelect wrapSelect(SqlNode node) { assert node instanceof SqlJoin || node instanceof SqlIdentifier + || node instanceof SqlMatchRecognize || node instanceof SqlCall && (((SqlCall) node).getOperator() instanceof SqlSetOperator || ((SqlCall) node).getOperator() == SqlStdOperatorTable.AS) @@ -812,6 +815,29 @@ public abstract class SqlImplementor { return new JoinContext(leftContext, rightContext); } + public Context matchRecognizeContext(Context context) { + return new MatchRecognizeContext(((AliasContext) context).aliases); + } + + /** + * Context for translating MATCH_RECOGNIZE clause + */ + public class MatchRecognizeContext extends AliasContext { + protected MatchRecognizeContext(Map<String, RelDataType> aliases) { + super(aliases, false); + } + + @Override public SqlNode toSql(RexProgram program, RexNode rex) { + if (rex.getKind() == SqlKind.LITERAL) { + final RexLiteral literal = (RexLiteral) rex; + if (literal.getTypeName().getFamily() == SqlTypeFamily.CHARACTER) { + return new SqlIdentifier(RexLiteral.stringValue(literal), POS); + } + } + return super.toSql(program, rex); + } + } + /** Implementation of Context that precedes field references with their * "table alias" based on the current sub-query's FROM clause. */ public class AliasContext extends Context { http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/rex/LogicVisitor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rex/LogicVisitor.java b/core/src/main/java/org/apache/calcite/rex/LogicVisitor.java index 721603e..a16a834 100644 --- a/core/src/main/java/org/apache/calcite/rex/LogicVisitor.java +++ b/core/src/main/java/org/apache/calcite/rex/LogicVisitor.java @@ -162,6 +162,10 @@ public class LogicVisitor implements RexBiVisitor<Logic, Logic> { } return end(subQuery, arg); } + + @Override public Logic visitPatternFieldRef(RexPatternFieldRef ref, Logic arg) { + return end(ref, arg); + } } // End LogicVisitor.java http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/rex/RexBiVisitor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rex/RexBiVisitor.java b/core/src/main/java/org/apache/calcite/rex/RexBiVisitor.java index 8f7e0d3..8e0b014 100644 --- a/core/src/main/java/org/apache/calcite/rex/RexBiVisitor.java +++ b/core/src/main/java/org/apache/calcite/rex/RexBiVisitor.java @@ -47,6 +47,8 @@ public interface RexBiVisitor<R, P> { R visitFieldAccess(RexFieldAccess fieldAccess, P arg); R visitSubQuery(RexSubQuery subQuery, P arg); + + R visitPatternFieldRef(RexPatternFieldRef ref, P arg); } // End RexBiVisitor.java http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/rex/RexPatternFieldRef.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rex/RexPatternFieldRef.java b/core/src/main/java/org/apache/calcite/rex/RexPatternFieldRef.java new file mode 100644 index 0000000..3d9ff42 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/rex/RexPatternFieldRef.java @@ -0,0 +1,59 @@ +/* + * 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.rex; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.SqlKind; + +/** + * Variable which references a field of an input relational expression + */ +public class RexPatternFieldRef extends RexInputRef { + private final String alpha; + + public RexPatternFieldRef(String alpha, int index, RelDataType type) { + super(index, type); + this.alpha = alpha; + digest = alpha + ".$" + index; + } + + public String getAlpha() { + return alpha; + } + + public static RexPatternFieldRef of(String alpha, int index, RelDataType type) { + return new RexPatternFieldRef(alpha, index, type); + } + + public static RexPatternFieldRef of(String alpha, RexInputRef ref) { + return new RexPatternFieldRef(alpha, ref.getIndex(), ref.getType()); + } + + @Override public <R> R accept(RexVisitor<R> visitor) { + return visitor.visitPatternFieldRef(this); + } + + @Override public <R, P> R accept(RexBiVisitor<R, P> visitor, P arg) { + return visitor.visitPatternFieldRef(this, arg); + } + + @Override public SqlKind getKind() { + return SqlKind.PATTERN_INPUT_REF; + } +} + +// End RexPatternFieldRef.java http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/rex/RexShuttle.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rex/RexShuttle.java b/core/src/main/java/org/apache/calcite/rex/RexShuttle.java index f0e5820..36b9e40 100644 --- a/core/src/main/java/org/apache/calcite/rex/RexShuttle.java +++ b/core/src/main/java/org/apache/calcite/rex/RexShuttle.java @@ -88,6 +88,10 @@ public class RexShuttle implements RexVisitor<RexNode> { } } + @Override public RexNode visitPatternFieldRef(RexPatternFieldRef fieldRef) { + return fieldRef; + } + public RexNode visitCall(final RexCall call) { boolean[] update = {false}; List<RexNode> clonedOperands = visitList(call.operands, update); http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/rex/RexUtil.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rex/RexUtil.java b/core/src/main/java/org/apache/calcite/rex/RexUtil.java index fd60098..2352809 100644 --- a/core/src/main/java/org/apache/calcite/rex/RexUtil.java +++ b/core/src/main/java/org/apache/calcite/rex/RexUtil.java @@ -495,6 +495,10 @@ public class RexUtil { return false; } + @Override public Boolean visitPatternFieldRef(RexPatternFieldRef fieldRef) { + return false; + } + public Boolean visitCorrelVariable(RexCorrelVariable correlVariable) { // Correlating variables change when there is an internal restart. // Not good enough for our purposes. http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/rex/RexVisitor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rex/RexVisitor.java b/core/src/main/java/org/apache/calcite/rex/RexVisitor.java index 4c95647..bf6dd99 100644 --- a/core/src/main/java/org/apache/calcite/rex/RexVisitor.java +++ b/core/src/main/java/org/apache/calcite/rex/RexVisitor.java @@ -47,6 +47,8 @@ public interface RexVisitor<R> { R visitFieldAccess(RexFieldAccess fieldAccess); R visitSubQuery(RexSubQuery subQuery); + + R visitPatternFieldRef(RexPatternFieldRef fieldRef); } // End RexVisitor.java http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/rex/RexVisitorImpl.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rex/RexVisitorImpl.java b/core/src/main/java/org/apache/calcite/rex/RexVisitorImpl.java index ec31f26..4710f98 100644 --- a/core/src/main/java/org/apache/calcite/rex/RexVisitorImpl.java +++ b/core/src/main/java/org/apache/calcite/rex/RexVisitorImpl.java @@ -110,6 +110,10 @@ public class RexVisitorImpl<R> implements RexVisitor<R> { return r; } + @Override public R visitPatternFieldRef(RexPatternFieldRef fieldRef) { + return null; + } + /** * <p>Visits an array of expressions, returning the logical 'and' of their * results. http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/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 4c9fa57..685344d 100644 --- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java +++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java @@ -662,6 +662,27 @@ public interface CalciteResource { @BaseMessage("Call to auxiliary group function ''{0}'' must have matching call to group function ''{1}'' in GROUP BY clause") ExInst<SqlValidatorException> auxiliaryWithoutMatchingGroupCall(String func1, String func2); + + @BaseMessage("Pattern variable ''{0}'' has already been defined") + ExInst<SqlValidatorException> PatternVarAlreadyDefined(String varName); + + @BaseMessage("Cannot use PREV/NEXT in MEASURE ''{0}''") + ExInst<SqlValidatorException> PatternPrevFunctionInMeasure(String call); + + @BaseMessage("Cannot nest PREV/NEXT under LAST/FIRST ''{0}''") + ExInst<SqlValidatorException> PatternPrevFunctionOrder(String call); + + @BaseMessage("Cannot use aggregation in navigation ''{0}''") + ExInst<SqlValidatorException> PatternAggregationInNavigation(String call); + + @BaseMessage("Invalid number of parameters to COUNT method") + ExInst<SqlValidatorException> PatternCountFunctionArg(); + + @BaseMessage("Cannot use RUNNING/FINAL in DEFINE ''{0}''") + ExInst<SqlValidatorException> PatternRunningFunctionInDefine(String call); + + @BaseMessage("Multiple pattern variables in ''{0}''") + ExInst<SqlValidatorException> PatternFunctionVariableCheck(String call); } // End CalciteResource.java http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/sql/SqlFunctionCategory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/SqlFunctionCategory.java b/core/src/main/java/org/apache/calcite/sql/SqlFunctionCategory.java index 02cd381..288adfc 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlFunctionCategory.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlFunctionCategory.java @@ -46,7 +46,8 @@ public enum SqlFunctionCategory { USER_DEFINED, TABLE_FUNCTION), USER_DEFINED_TABLE_SPECIFIC_FUNCTION("TABLE_UDF_SPECIFIC", "User-defined table function with SPECIFIC name", USER_DEFINED, - TABLE_FUNCTION, SPECIFIC); + TABLE_FUNCTION, SPECIFIC), + MATCH_RECOGNIZE("MATCH_RECOGNIZE", "MATCH_RECOGNIZE function", TABLE_FUNCTION); private final EnumSet<Property> properties; http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/sql/SqlKind.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/SqlKind.java b/core/src/main/java/org/apache/calcite/sql/SqlKind.java index 8dbb55ef..454e07f 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java @@ -227,6 +227,10 @@ public enum SqlKind { */ TABLESAMPLE, + /** + * MATCH_RECOGNIZE clause + */ + MATCH_RECOGNIZE, // binary operators /** @@ -253,6 +257,16 @@ public enum SqlKind { */ MINUS, + /** + * the alternation operator in a pattern expression within a match_recognize clause + */ + PATTERN_ALTER, + + /** + * the concatenation operator in a pattern expression within a match_recognize clause + */ + PATTERN_CONCAT, + // comparison operators /** @@ -435,6 +449,26 @@ public enum SqlKind { */ NEW_SPECIFICATION, + + /** + * Special functions in MATCH_RECOGNIZE. + */ + FINAL, + + RUNNING, + + PREV, + + NEXT, + + FIRST, + + LAST, + + CLASSIFIER, + + MATCH_NUMBER, + // postfix operators /** @@ -515,6 +549,12 @@ public enum SqlKind { INPUT_REF, /** + * Reference to an input field, with pattern var as modifier + * + * <p>(Only used at the RexNode level.)</p> + */ + PATTERN_INPUT_REF, + /** * Reference to a sub-expression computed within the current relational * operator. * @@ -529,6 +569,11 @@ public enum SqlKind { */ CORREL_VARIABLE, + /** + * the repetition quantifier of a pattern factor in a match_recognize clause. + */ + PATTERN_QUANTIFIER, + // functions /** @@ -692,6 +737,16 @@ public enum SqlKind { /** The {@code GROUP_ID()} function. */ GROUP_ID, + /** + * the internal permute function in match_recognize cluse + */ + PATTERN_PERMUTE, + + /** + * the special patterns to exclude enclosing pattern from output in match_recognize clause + */ + PATTERN_EXCLUDED, + // Aggregate functions /** The {@code COUNT} aggregate function. */ http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/sql/SqlMatchRecognize.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/SqlMatchRecognize.java b/core/src/main/java/org/apache/calcite/sql/SqlMatchRecognize.java new file mode 100644 index 0000000..23350fb --- /dev/null +++ b/core/src/main/java/org/apache/calcite/sql/SqlMatchRecognize.java @@ -0,0 +1,216 @@ +/* + * 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.sql; + +import org.apache.calcite.sql.parser.SqlParserPos; +import org.apache.calcite.sql.util.SqlBasicVisitor; +import org.apache.calcite.sql.util.SqlVisitor; +import org.apache.calcite.sql.validate.SqlValidator; +import org.apache.calcite.sql.validate.SqlValidatorScope; +import org.apache.calcite.util.ImmutableNullableList; + +import java.util.List; + +/** + * SqlNode for Match_recognize clause + */ +public class SqlMatchRecognize extends SqlCall { + public static final int OPERAND_TABLE_REF = 0; + public static final int OPERAND_PATTERN = 1; + public static final int OPERAND_STRICT_START = 2; + public static final int OPERAND_STRICT_END = 3; + public static final int OPERAND_PATTERN_DEFINES = 4; + + //~ Instance fields ------------------------------------------- + + private SqlNode tableRef; + private SqlNode pattern; + private SqlLiteral strictStart; + private SqlLiteral strictEnd; + private SqlNodeList patternDefList; + + /** Creates a SqlMatchRecognize. */ + public SqlMatchRecognize(SqlParserPos pos, SqlNode tableRef, SqlNode pattern, + SqlLiteral strictStart, SqlLiteral strictEnd, SqlNodeList patternDefList) { + super(pos); + this.tableRef = tableRef; + this.pattern = pattern; + this.strictStart = strictStart; + this.strictEnd = strictEnd; + this.patternDefList = patternDefList; + + assert tableRef != null; + assert pattern != null; + assert patternDefList != null && patternDefList.size() > 0; + } + + // ~ Methods + + @Override public SqlOperator getOperator() { + return SqlMatchRecognizeOperator.INSTANCE; + } + + @Override public SqlKind getKind() { + return SqlKind.MATCH_RECOGNIZE; + } + + @Override public List<SqlNode> getOperandList() { + return ImmutableNullableList.of(tableRef, pattern, strictStart, strictEnd, + patternDefList); + } + + @Override public void unparse(SqlWriter writer, int leftPrec, + int rightPrec) { + getOperator().unparse(writer, this, 0, 0); + } + + @Override public void validate(SqlValidator validator, SqlValidatorScope scope) { + validator.validateMatchRecognize(this); + } + + @Override public void setOperand(int i, SqlNode operand) { + switch (i) { + case OPERAND_TABLE_REF: + tableRef = operand; + break; + case OPERAND_PATTERN: + pattern = operand; + break; + case OPERAND_STRICT_START: + strictStart = (SqlLiteral) operand; + break; + case OPERAND_STRICT_END: + strictEnd = (SqlLiteral) operand; + break; + case OPERAND_PATTERN_DEFINES: + patternDefList = (SqlNodeList) operand; + break; + default: + throw new AssertionError(i); + } + } + + public SqlNode getTableRef() { + return tableRef; + } + + public SqlNode getPattern() { + return pattern; + } + + public SqlLiteral getStrictStart() { + return strictStart; + } + + public SqlLiteral getStrictEnd() { + return strictEnd; + } + + public SqlNodeList getPatternDefList() { + return patternDefList; + } + + /** + * An operator describing a MATCH_RECOGNIZE specification. + */ + public static class SqlMatchRecognizeOperator extends SqlOperator { + public static final SqlMatchRecognizeOperator INSTANCE = + new SqlMatchRecognizeOperator(); + + private SqlMatchRecognizeOperator() { + super("MATCH_RECOGNIZE", SqlKind.MATCH_RECOGNIZE, 2, true, null, null, null); + } + + @Override public SqlSyntax getSyntax() { + return SqlSyntax.SPECIAL; + } + + @Override public SqlCall createCall( + SqlLiteral functionQualifier, + SqlParserPos pos, + SqlNode... operands) { + assert functionQualifier == null; + assert operands.length == 5; + + return new SqlMatchRecognize(pos, operands[0], operands[1], + (SqlLiteral) operands[2], (SqlLiteral) operands[3], + (SqlNodeList) operands[4]); + } + + @Override public <R> void acceptCall( + SqlVisitor<R> visitor, + SqlCall call, + boolean onlyExpressions, + SqlBasicVisitor.ArgHandler<R> argHandler) { + if (onlyExpressions) { + List<SqlNode> operands = call.getOperandList(); + for (int i = 0; i < operands.size(); i++) { + SqlNode operand = operands.get(i); + if (operand == null) { + continue; + } + argHandler.visitChild(visitor, call, i, operand); + } + } else { + super.acceptCall(visitor, call, onlyExpressions, argHandler); + } + } + + @Override public void validateCall( + SqlCall call, + SqlValidator validator, + SqlValidatorScope scope, + SqlValidatorScope operandScope) { + validator.validateMatchRecognize(call); + } + + @Override public void unparse( + SqlWriter writer, + SqlCall call, + int leftPrec, + int rightPrec) { + final SqlMatchRecognize pattern = (SqlMatchRecognize) call; + + pattern.tableRef.unparse(writer, 0, 0); + final SqlWriter.Frame mrFrame = writer.startFunCall("MATCH_RECOGNIZE"); + + writer.newlineAndIndent(); + writer.sep("PATTERN"); + + SqlWriter.Frame patternFrame = writer.startList("(", ")"); + if (pattern.strictStart.booleanValue()) { + writer.sep("^"); + } + pattern.pattern.unparse(writer, 0, 0); + if (pattern.strictEnd.booleanValue()) { + writer.sep("$"); + } + writer.endList(patternFrame); + + writer.newlineAndIndent(); + writer.sep("DEFINE"); + + SqlWriter.Frame patternDefFrame = writer.startList("", ""); + pattern.patternDefList.unparse(writer, 0, 0); + writer.endList(patternDefFrame); + + writer.endList(mrFrame); + } + } +} + +// End SqlMatchRecognize.java http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/sql/SqlSelect.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/SqlSelect.java b/core/src/main/java/org/apache/calcite/sql/SqlSelect.java index bb847bc..ea89b37 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlSelect.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlSelect.java @@ -46,7 +46,7 @@ public class SqlSelect extends SqlCall { SqlNodeList orderBy; SqlNode offset; SqlNode fetch; - + SqlMatchRecognize matchRecognize; //~ Constructors ----------------------------------------------------------- public SqlSelect(SqlParserPos pos, @@ -204,6 +204,14 @@ public class SqlSelect extends SqlCall { this.fetch = fetch; } + public SqlMatchRecognize getMatchRecognize() { + return matchRecognize; + } + + public void setMatchRecognize(SqlMatchRecognize matchRecognize) { + this.matchRecognize = matchRecognize; + } + public void validate(SqlValidator validator, SqlValidatorScope scope) { validator.validateQuery(this, scope, validator.getUnknownType()); } http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/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 764a04f..f0e7868 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 @@ -30,6 +30,8 @@ import org.apache.calcite.sql.SqlInternalOperator; import org.apache.calcite.sql.SqlKind; import org.apache.calcite.sql.SqlLateralOperator; import org.apache.calcite.sql.SqlLiteral; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlNumericLiteral; import org.apache.calcite.sql.SqlOperandCountRange; import org.apache.calcite.sql.SqlOperator; import org.apache.calcite.sql.SqlOperatorBinding; @@ -55,6 +57,7 @@ import org.apache.calcite.sql.type.SqlTypeName; import org.apache.calcite.sql.util.ReflectiveSqlOperatorTable; import org.apache.calcite.sql.validate.SqlModality; import org.apache.calcite.util.Litmus; +import org.apache.calcite.util.Util; /** * Implementation of {@link org.apache.calcite.sql.SqlOperatorTable} containing @@ -1396,6 +1399,36 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable { new SqlBaseContextVariable("PI", ReturnTypes.DOUBLE, SqlFunctionCategory.NUMERIC); + /** {@code FINAL} function to be used within {@code MATCH_RECOGNIZE}. */ + public static final SqlFunction FINAL = + new SqlFunction("FINAL", SqlKind.FINAL, ReturnTypes.ARG0_NULLABLE, null, + OperandTypes.ANY, SqlFunctionCategory.MATCH_RECOGNIZE); + + /** {@code RUNNING} function to be used within {@code MATCH_RECOGNIZE}. */ + public static final SqlFunction RUNNING = + new SqlFunction("RUNNING", SqlKind.RUNNING, ReturnTypes.ARG0_NULLABLE, + null, OperandTypes.ANY, SqlFunctionCategory.MATCH_RECOGNIZE); + + /** {@code FIRST} function to be used within {@code MATCH_RECOGNIZE}. */ + public static final SqlFunction FIRST = + new SqlFunction("FIRST", SqlKind.FIRST, ReturnTypes.ARG0_NULLABLE, + null, OperandTypes.ANY_NUMERIC, SqlFunctionCategory.MATCH_RECOGNIZE); + + /** {@code LAST} function to be used within {@code MATCH_RECOGNIZE}. */ + public static final SqlFunction LAST = + new SqlFunction("LAST", SqlKind.LAST, ReturnTypes.ARG0_NULLABLE, + null, OperandTypes.ANY_NUMERIC, SqlFunctionCategory.MATCH_RECOGNIZE); + + /** {@code PREV} function to be used within {@code MATCH_RECOGNIZE}. */ + public static final SqlFunction PREV = + new SqlFunction("PREV", SqlKind.PREV, ReturnTypes.ARG0_NULLABLE, + null, OperandTypes.ANY_NUMERIC, SqlFunctionCategory.MATCH_RECOGNIZE); + + /** {@code NEXT} function to be used within {@code MATCH_RECOGNIZE}. */ + public static final SqlFunction NEXT = + new SqlFunction("NEXT", SqlKind.NEXT, ReturnTypes.ARG0_NULLABLE, null, + OperandTypes.ANY_NUMERIC, SqlFunctionCategory.MATCH_RECOGNIZE); + public static final SqlFunction NULLIF = new SqlNullifFunction(); /** @@ -1900,6 +1933,131 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable { public static final SqlFunction SESSION_END = SESSION.auxiliary(SqlKind.SESSION_END); + /** {@code |} operator to create alternate patterns + * within {@code MATCH_RECOGNIZE}. + * + * <p>If {@code p1} and {@code p2} are patterns then {@code p1 | p2} is a + * pattern that matches {@code p1} or {@code p2}. */ + public static final SqlBinaryOperator PATTERN_ALTER = + new SqlBinaryOperator("|", SqlKind.PATTERN_ALTER, 70, true, null, null, null); + + /** Operator to concatenate patterns within {@code MATCH_RECOGNIZE}. + * + * <p>If {@code p1} and {@code p2} are patterns then {@code p1 p2} is a + * pattern that matches {@code p1} followed by {@code p2}. */ + public static final SqlBinaryOperator PATTERN_CONCAT = + new SqlBinaryOperator("", SqlKind.PATTERN_CONCAT, 80, true, null, null, null); + + /** Operator to quantify patterns within {@code MATCH_RECOGNIZE}. + * + * <p>If {@code p} is a pattern then {@code p{3, 5}} is a + * pattern that matches between 3 and 5 occurrences of {@code p}. */ + public static final SqlSpecialOperator PATTERN_QUANTIFIER = + new SqlSpecialOperator("PATTERN_QUANTIFIER", SqlKind.PATTERN_QUANTIFIER, + 90) { + @Override public void unparse(SqlWriter writer, SqlCall call, + int leftPrec, int rightPrec) { + call.operand(0).unparse(writer, this.getLeftPrec(), this.getRightPrec()); + int startNum = ((SqlNumericLiteral) call.operand(1)).intValue(true); + SqlNumericLiteral endRepNum = call.operand(2); + boolean isReluctant = ((SqlLiteral) call.operand(3)).booleanValue(); + int endNum = endRepNum.intValue(true); + if (startNum == endNum) { + writer.keyword("{ " + startNum + " }"); + } else { + if (endNum == -1) { + if (startNum == 0) { + writer.keyword("*"); + } else if (startNum == 1) { + writer.keyword("+"); + } else { + writer.keyword("{ " + startNum + ", }"); + } + } else { + if (startNum == 0 && endNum == 1) { + writer.keyword("?"); + } else if (startNum == -1) { + writer.keyword("{ , " + endNum + " }"); + } else { + writer.keyword("{ " + startNum + ", " + endNum + " }"); + } + } + if (isReluctant) { + writer.keyword("?"); + } + } + } + }; + + /** {@code PERMUTE} operator to combine patterns within + * {@code MATCH_RECOGNIZE}. + * + * <p>If {@code p1} and {@code p2} are patterns then {@code PERMUTE (p1, p2)} + * is a pattern that matches all permutations of {@code p1} and + * {@code p2}. */ + public static final SqlSpecialOperator PATTERN_PERMUTE = + new SqlSpecialOperator("PATTERN_PERMUTE", SqlKind.PATTERN_PERMUTE, 100) { + @Override public void unparse(SqlWriter writer, SqlCall call, + int leftPrec, int rightPrec) { + writer.keyword("PERMUTE"); + SqlWriter.Frame frame = writer.startList("(", ")"); + for (int i = 0; i < call.getOperandList().size(); i++) { + SqlNode pattern = call.getOperandList().get(i); + pattern.unparse(writer, 0, 0); + if (i != call.getOperandList().size() - 1) { + writer.print(","); + } + } + writer.endList(frame); + } + }; + + /** {@code EXCLUDE} operator within {@code MATCH_RECOGNIZE}. + * + * <p>If {@code p} is a pattern then {@code {- p -} }} is a + * pattern that excludes {@code p} from the output. */ + public static final SqlSpecialOperator PATTERN_EXCLUDE = + new SqlSpecialOperator("PATTERN_EXCLUDE", SqlKind.PATTERN_EXCLUDED, + 100) { + @Override public void unparse(SqlWriter writer, SqlCall call, + int leftPrec, int rightPrec) { + SqlWriter.Frame frame = writer.startList("{-", "-}"); + SqlNode node = call.getOperandList().get(0); + node.unparse(writer, 0, 0); + writer.endList(frame); + } + }; + + /** {@code AS} function that defines a measure inside {@code MATCH_RECOGNIZE}. + * The order of operands will change when the expression is unparsed. */ + public static final SqlSpecialOperator PATTERN_DEFINE_AS = + new SqlAsOperator("PATTERN_DEFINE_AS", SqlKind.AS, 20, true, + ReturnTypes.ARG0, InferTypes.RETURN_TYPE, OperandTypes.ANY_ANY) { + @Override public void unparse(SqlWriter writer, SqlCall call, + int leftPrec, int rightPrec) { + assert call.operandCount() >= 2; + final SqlWriter.Frame frame = + writer.startList( + SqlWriter.FrameTypeEnum.SIMPLE); + call.operand(1).unparse(writer, leftPrec, getLeftPrec()); + final boolean needsSpace = true; + writer.setNeedWhitespace(needsSpace); + writer.sep("AS"); + writer.setNeedWhitespace(needsSpace); + call.operand(0).unparse(writer, getRightPrec(), rightPrec); + if (call.operandCount() > 2) { + final SqlWriter.Frame frame1 = + writer.startList(SqlWriter.FrameTypeEnum.SIMPLE, "(", ")"); + for (SqlNode operand : Util.skip(call.getOperandList(), 2)) { + writer.sep(",", false); + operand.unparse(writer, 0, 0); + } + writer.endList(frame1); + } + writer.endList(frame); + } + }; + //~ Methods ---------------------------------------------------------------- /** http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java index e27e9db..ddddd08 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java +++ b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java @@ -407,7 +407,8 @@ public abstract class OperandTypes { public static final SqlSingleOperandTypeChecker ANY_ANY = family(SqlTypeFamily.ANY, SqlTypeFamily.ANY); - + public static final SqlSingleOperandTypeChecker ANY_NUMERIC = + family(SqlTypeFamily.ANY, SqlTypeFamily.NUMERIC); /** * Parameter type-checking strategy type must a nullable time interval, * nullable time interval http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/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 02823be..0ec59a7 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 @@ -385,7 +385,8 @@ public abstract class DelegatingScope implements SqlValidatorScope { // SELECT e.empno FROM Emp as E // // change "e.empno" to "E.empno". - if (fromNs.getEnclosingNode() != null) { + if (fromNs.getEnclosingNode() != null + && !(this instanceof MatchRecognizeScope)) { String alias = SqlValidatorUtil.getAlias(fromNs.getEnclosingNode(), -1); if (alias != null http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/sql/validate/MatchRecognizeNamespace.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/validate/MatchRecognizeNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/MatchRecognizeNamespace.java new file mode 100644 index 0000000..5b9a3e3 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/sql/validate/MatchRecognizeNamespace.java @@ -0,0 +1,47 @@ +/* + * 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.sql.validate; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.SqlMatchRecognize; +import org.apache.calcite.sql.SqlNode; + +/** + * Namespace for a {@code MATCH_RECOGNIZE} clause. + */ +public class MatchRecognizeNamespace extends AbstractNamespace { + private final SqlMatchRecognize matchRecognize; + + /** Creates a MatchRecognizeNamespace. */ + protected MatchRecognizeNamespace(SqlValidatorImpl validator, + SqlMatchRecognize matchRecognize, + SqlNode enclosingNode) { + super(validator, enclosingNode); + this.matchRecognize = matchRecognize; + } + + @Override public RelDataType validateImpl(RelDataType targetRowType) { + validator.validateMatchRecognize(matchRecognize); + return rowType; + } + + @Override public SqlMatchRecognize getNode() { + return matchRecognize; + } +} + +// End MatchRecognizeNamespace.java http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/sql/validate/MatchRecognizeScope.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/validate/MatchRecognizeScope.java b/core/src/main/java/org/apache/calcite/sql/validate/MatchRecognizeScope.java new file mode 100644 index 0000000..5050057 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/sql/validate/MatchRecognizeScope.java @@ -0,0 +1,94 @@ +/* + * 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.sql.validate; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.StructKind; +import org.apache.calcite.sql.SqlMatchRecognize; +import org.apache.calcite.sql.SqlNode; + +import com.google.common.collect.Sets; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Scope for expressions in a {@code MATCH_RECOGNIZE} clause. + * + * <p>Defines variables and uses them as prefix of columns reference. + */ +public class MatchRecognizeScope extends ListScope { + private static final String STAR = "*"; + + //~ Instance fields --------------------------------------------- + private final SqlMatchRecognize matchRecognize; + private Set<String> patternVars; + + /** Creates a MatchRecognizeScope. */ + public MatchRecognizeScope(SqlValidatorScope parent, + SqlMatchRecognize matchRecognize) { + super(parent); + this.matchRecognize = matchRecognize; + patternVars = Sets.newHashSet(STAR); + } + + @Override public SqlNode getNode() { + return matchRecognize; + } + + public SqlMatchRecognize getMatchRecognize() { + return matchRecognize; + } + + public Set<String> getPatternVars() { + return patternVars; + } + + public void addPatternVar(String str) { + patternVars.add(str); + } + + @Override public Map<String, ScopeChild> + findQualifyingTables(String columnName, SqlNameMatcher nameMatcher) { + final Map<String, ScopeChild> map = new HashMap<>(); + for (ScopeChild child : children) { + final RelDataType rowType = child.namespace.getRowType(); + if (nameMatcher.field(rowType, columnName) != null) { + map.put(STAR, child); + } + } + return map; + } + + @Override public void resolve(List<String> names, SqlNameMatcher nameMatcher, + boolean deep, Resolved resolved) { + if (patternVars.contains(names.get(0))) { + final Step path = new EmptyPath().plus(null, 0, null, StructKind.FULLY_QUALIFIED); + final ScopeChild child = children.get(0); + resolved.found(child.namespace, child.nullable, this, path, names); + if (resolved.count() > 0) { + return; + } + } + super.resolve(names, nameMatcher, deep, resolved); + } + +} + +// End MatchRecognizeScope.java http://git-wip-us.apache.org/repos/asf/calcite/blob/4e103825/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java index 215b96c..a4b4297 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java @@ -32,6 +32,7 @@ import org.apache.calcite.sql.SqlIdentifier; import org.apache.calcite.sql.SqlInsert; import org.apache.calcite.sql.SqlIntervalQualifier; import org.apache.calcite.sql.SqlLiteral; +import org.apache.calcite.sql.SqlMatchRecognize; import org.apache.calcite.sql.SqlMerge; import org.apache.calcite.sql.SqlNode; import org.apache.calcite.sql.SqlNodeList; @@ -274,6 +275,13 @@ public interface SqlValidator { SqlCall call); /** + * Validates a MATCH_RECOGNIZE clause. + * + * @param pattern MATCH_RECOGNIZE clause + */ + void validateMatchRecognize(SqlCall pattern); + + /** * Validates a call to an operator. * * @param call Operator call @@ -554,6 +562,14 @@ public interface SqlValidator { SqlValidatorScope getOrderScope(SqlSelect select); /** + * Returns a scope match recognize clause. + * + * @param node Match recognize + * @return naming scope for Match recognize clause + */ + SqlValidatorScope getMatchRecognizeScope(SqlMatchRecognize node); + + /** * Declares a SELECT expression as a cursor. * * @param select select expression associated with the cursor
