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 2017/03/04 21:03:28 UTC
[09/10] calcite git commit: [CALCITE-1641] Add parser and validator
support for MATCH_RECOGNIZE (Zhiqiang-He)
[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 <ab...@qq.com>
Authored: Tue Feb 21 19:44:11 2017 +0800
Committer: Julian Hyde <jh...@apache.org>
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