[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

Reply via email to