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/04/25 03:53:44 UTC

[3/3] calcite git commit: [CALCITE-1643] AFTER MATCH sub-clause of MATCH_RECOGNIZE clause (Zhiqiang-He)

[CALCITE-1643] AFTER MATCH sub-clause of MATCH_RECOGNIZE clause (Zhiqiang-He)

Close apache/calcite#429


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/c850e227
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/c850e227
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/c850e227

Branch: refs/heads/master
Commit: c850e227db583a697c8cc46585b33274b63ed2d0
Parents: d97c14c
Author: Zhiqiang-He <ab...@qq.com>
Authored: Fri Apr 14 20:58:21 2017 +0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Mon Apr 24 18:26:09 2017 -0700

----------------------------------------------------------------------
 core/src/main/codegen/templates/Parser.jj       |  38 ++++-
 .../java/org/apache/calcite/rel/core/Match.java |  15 +-
 .../apache/calcite/rel/core/RelFactories.java   |   6 +-
 .../calcite/rel/logical/LogicalMatch.java       |  13 +-
 .../calcite/rel/rel2sql/RelToSqlConverter.java  |  14 +-
 .../java/org/apache/calcite/sql/SqlKind.java    |  14 +-
 .../apache/calcite/sql/SqlMatchRecognize.java   |  58 +++++++-
 .../calcite/sql2rel/SqlToRelConverter.java      |  39 ++++--
 .../rel/rel2sql/RelToSqlConverterTest.java      | 139 +++++++++++++++++++
 .../calcite/sql/parser/SqlParserTest.java       | 105 ++++++++++++++
 .../apache/calcite/test/SqlValidatorTest.java   |  63 ++++++---
 site/_docs/reference.md                         |   1 +
 12 files changed, 458 insertions(+), 47 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/c850e227/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 64d5936..a6672db 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -2497,6 +2497,9 @@ SqlMatchRecognize MatchRecognizeOpt(SqlNode tableRef) :
     SqlNodeList measureList = SqlNodeList.EMPTY;
     SqlNode pattern;
     SqlNodeList patternDefList;
+    final SqlNode after;
+    final SqlParserPos pos;
+    final SqlNode var;
     SqlLiteral isStrictStarts = SqlLiteral.createBoolean(false, getPos());
     SqlLiteral isStrictEnds = SqlLiteral.createBoolean(false, getPos());
 }
@@ -2506,6 +2509,36 @@ SqlMatchRecognize MatchRecognizeOpt(SqlNode tableRef) :
         <MEASURES>
         measureList = MeasureColumnCommaList(getPos())
     ]
+    (
+        <AFTER> { pos = getPos(); } <MATCH> <SKIP_>
+        (
+            <TO>
+            (
+                LOOKAHEAD(2)
+                <NEXT> <ROW> {
+                    after = SqlMatchRecognize.AfterOption.SKIP_TO_NEXT_ROW
+                        .symbol(pos.plus(getPos()));
+                }
+            |
+                <FIRST> var = SimpleIdentifier() {
+                    after = SqlMatchRecognize.SKIP_TO_FIRST.createCall(
+                        pos.plus(var.getParserPosition()), var);
+                }
+            |
+                [ <LAST> ] var = SimpleIdentifier() {
+                    after = SqlMatchRecognize.SKIP_TO_LAST.createCall(
+                        pos.plus(var.getParserPosition()), var);
+                }
+            )
+        |
+            <PAST> <LAST> <ROW> {
+                 after = SqlMatchRecognize.AfterOption.SKIP_PAST_LAST_ROW
+                     .symbol(pos.plus(getPos()));
+            }
+        )
+    |
+        { after = null; }
+    )
     <PATTERN>
     <LPAREN>
     (
@@ -2524,7 +2557,8 @@ SqlMatchRecognize MatchRecognizeOpt(SqlNode tableRef) :
     patternDefList = PatternDefinitionCommaList(getPos())
     <RPAREN> {
         return new SqlMatchRecognize(startPos.plus(getPos()), tableRef,
-            pattern, isStrictStarts, isStrictEnds, patternDefList, measureList);
+            pattern, isStrictStarts, isStrictEnds, patternDefList, measureList,
+            after);
     }
 }
 
@@ -5814,6 +5848,7 @@ SqlPostfixOperator PostfixRowOperator() :
     | < PARTITION: "PARTITION" >
     | < PASCAL: "PASCAL" >
     | < PASSTHROUGH: "PASSTHROUGH" >
+    | < PAST: "PAST" >
     | < PATH: "PATH" >
     | < PATTERN: "PATTERN" >
     | < PER: "PER" >
@@ -6261,6 +6296,7 @@ String CommonNonReservedKeyWord() :
         | <PARTIAL>
         | <PASCAL>
         | <PASSTHROUGH>
+        | <PAST>
         | <PATH>
         | <PLACING>
         | <PLAN>

http://git-wip-us.apache.org/repos/asf/calcite/blob/c850e227/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
index 84ef106..ae5215d 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Match.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Match.java
@@ -59,6 +59,7 @@ public abstract class Match extends SingleRel {
   protected final RexNode pattern;
   protected final boolean strictStart;
   protected final boolean strictEnd;
+  protected final RexNode after;
   protected final ImmutableMap<String, RexNode> patternDefinitions;
   protected final Set<RexMRAggCall> aggregateCalls;
   protected final Map<String, SortedSet<RexMRAggCall>> aggregateCallsPreVar;
@@ -76,20 +77,22 @@ public abstract class Match extends SingleRel {
    * @param strictEnd Whether it is a strict end pattern
    * @param patternDefinitions Pattern definitions
    * @param measures Measure definitions
+   * @param after After match definitions
    * @param rowType Row type
    */
   protected Match(RelOptCluster cluster, RelTraitSet traitSet,
       RelNode input, RexNode pattern, boolean strictStart, boolean strictEnd,
       Map<String, RexNode> patternDefinitions, Map<String, RexNode> measures,
-      RelDataType rowType) {
+      RexNode after, 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.rowType = Preconditions.checkNotNull(rowType);
     this.measures = ImmutableMap.copyOf(measures);
+    this.after = Preconditions.checkNotNull(after);
 
     final AggregateFinder aggregateFinder = new AggregateFinder();
     for (RexNode rex : this.patternDefinitions.values()) {
@@ -126,6 +129,10 @@ public abstract class Match extends SingleRel {
     return measures;
   }
 
+  public RexNode getAfter() {
+    return after;
+  }
+
   public RexNode getPattern() {
     return pattern;
   }
@@ -145,7 +152,7 @@ public abstract class Match extends SingleRel {
   public abstract Match copy(RelNode input, RexNode pattern,
       boolean strictStart, boolean strictEnd,
       Map<String, RexNode> patternDefinitions, Map<String, RexNode> measures,
-      RelDataType rowType);
+      RexNode after, RelDataType rowType);
 
   @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) {
     if (getInputs().equals(inputs)
@@ -154,7 +161,7 @@ public abstract class Match extends SingleRel {
     }
 
     return copy(inputs.get(0), pattern, strictStart, strictEnd,
-        patternDefinitions, measures, rowType);
+        patternDefinitions, measures, after, rowType);
   }
 
   @Override public RelWriter explainTerms(RelWriter pw) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/c850e227/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 10f9a10..5404666 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
@@ -397,7 +397,7 @@ public class RelFactories {
     RelNode createMatchRecognize(RelNode input, RexNode pattern,
         boolean strictStart, boolean strictEnd,
         Map<String, RexNode> patternDefinitions, Map<String, RexNode> measures,
-        RelDataType rowType);
+        RexNode after, RelDataType rowType);
   }
 
   /**
@@ -408,9 +408,9 @@ public class RelFactories {
     public RelNode createMatchRecognize(RelNode input, RexNode pattern,
         boolean strictStart, boolean strictEnd,
         Map<String, RexNode> patternDefinitions, Map<String, RexNode> measures,
-        RelDataType rowType) {
+        RexNode after, RelDataType rowType) {
       return LogicalMatch.create(input, pattern, strictStart, strictEnd,
-          patternDefinitions, measures, rowType);
+          patternDefinitions, measures, after, rowType);
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/calcite/blob/c850e227/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
index 524e8a5..62a9814 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalMatch.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalMatch.java
@@ -43,14 +43,15 @@ public class LogicalMatch extends Match {
    * @param strictEnd Whether it is a strict end pattern
    * @param patternDefinitions Pattern definitions
    * @param measures Measure definitions
+   * @param after After match definitions
    * @param rowType Row type
    */
   public LogicalMatch(RelOptCluster cluster, RelTraitSet traitSet,
       RelNode input, RexNode pattern, boolean strictStart, boolean strictEnd,
       Map<String, RexNode> patternDefinitions, Map<String, RexNode> measures,
-      RelDataType rowType) {
+      RexNode after, RelDataType rowType) {
     super(cluster, traitSet, input, pattern, strictStart, strictEnd,
-        patternDefinitions, measures, rowType);
+        patternDefinitions, measures, after, rowType);
   }
 
   /**
@@ -59,11 +60,11 @@ public class LogicalMatch extends Match {
   public static LogicalMatch create(RelNode input, RexNode pattern,
       boolean strictStart, boolean strictEnd,
       Map<String, RexNode> patternDefinitions, Map<String, RexNode> measures,
-      RelDataType rowType) {
+      RexNode after, RelDataType rowType) {
     final RelOptCluster cluster = input.getCluster();
     final RelTraitSet traitSet = cluster.traitSetOf(Convention.NONE);
     return new LogicalMatch(cluster, traitSet, input, pattern,
-        strictStart, strictEnd, patternDefinitions, measures, rowType);
+        strictStart, strictEnd, patternDefinitions, measures, after, rowType);
   }
 
   //~ Methods ------------------------------------------------------
@@ -71,11 +72,11 @@ public class LogicalMatch extends Match {
   @Override public Match copy(RelNode input, RexNode pattern,
       boolean strictStart, boolean strictEnd,
       Map<String, RexNode> patternDefinitions, Map<String, RexNode> measures,
-      RelDataType rowType) {
+      RexNode after, RelDataType rowType) {
     final RelTraitSet traitSet = getCluster().traitSetOf(Convention.NONE);
     return new LogicalMatch(getCluster(), traitSet,
         input, pattern, strictStart, strictEnd, patternDefinitions, measures,
-        rowType);
+        after, rowType);
   }
 }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/c850e227/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 ecb75a0..cec0e03 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
@@ -35,6 +35,7 @@ import org.apache.calcite.rel.core.TableScan;
 import org.apache.calcite.rel.core.Union;
 import org.apache.calcite.rel.core.Values;
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexCall;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexLocalRef;
 import org.apache.calcite.rex.RexNode;
@@ -378,6 +379,17 @@ public class RelToSqlConverter extends SqlImplementor
 
     SqlNode tableRef = x.asQueryOrValues();
 
+    final SqlNode after;
+    if (e.getAfter() instanceof RexLiteral) {
+      SqlMatchRecognize.AfterOption value = (SqlMatchRecognize.AfterOption)
+          ((RexLiteral) e.getAfter()).getValue2();
+      after = SqlLiteral.createSymbol(value, POS);
+    } else {
+      RexCall call = (RexCall) e.getAfter();
+      String operand = RexLiteral.stringValue(call.getOperands().get(0));
+      after = call.getOperator().createCall(POS, new SqlIdentifier(operand, POS));
+    }
+
     RexNode rexPattern = e.getPattern();
     final SqlNode pattern = context.toSql(null, rexPattern);
     final SqlLiteral strictStart = SqlLiteral.createBoolean(e.isStrictStart(), POS);
@@ -398,7 +410,7 @@ public class RelToSqlConverter extends SqlImplementor
     }
 
     final SqlNode matchRecognize = new SqlMatchRecognize(POS, tableRef,
-      pattern, strictStart, strictEnd, patternDefList, measureList);
+        pattern, strictStart, strictEnd, patternDefList, measureList, after);
     return result(matchRecognize, Expressions.list(Clause.FROM), e, null);
   }
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/c850e227/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 8f49591..7a33772 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
@@ -469,6 +469,18 @@ public enum SqlKind {
 
   MATCH_NUMBER,
 
+  /**
+   * The "SKIP TO FIRST" qualifier of restarting point in a MATCH_RECOGNIZE
+   * clause.
+   */
+  SKIP_TO_FIRST,
+
+  /**
+   * The "SKIP TO LAST" qualifier of restarting point in a MATCH_RECOGNIZE
+   * clause.
+   */
+  SKIP_TO_LAST,
+
   // postfix operators
 
   /**
@@ -1026,7 +1038,7 @@ public enum SqlKind {
                   TIMESTAMP_ADD, TIMESTAMP_DIFF, EXTRACT,
                   LITERAL_CHAIN, JDBC_FN, PRECEDING, FOLLOWING, ORDER_BY,
                   NULLS_FIRST, NULLS_LAST, COLLECTION_TABLE, TABLESAMPLE,
-                  VALUES, WITH, WITH_ITEM),
+                  VALUES, WITH, WITH_ITEM, SKIP_TO_FIRST, SKIP_TO_LAST),
               AGGREGATE, DML, DDL));
 
   /**

http://git-wip-us.apache.org/repos/asf/calcite/blob/c850e227/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
index 300e886..7fdc765 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlMatchRecognize.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlMatchRecognize.java
@@ -29,7 +29,7 @@ import java.util.List;
 import javax.annotation.Nonnull;
 
 /**
- * SqlNode for Match_recognize clause
+ * SqlNode for MATCH_RECOGNIZE clause.
  */
 public class SqlMatchRecognize extends SqlCall {
   public static final int OPERAND_TABLE_REF = 0;
@@ -38,6 +38,15 @@ public class SqlMatchRecognize extends SqlCall {
   public static final int OPERAND_STRICT_END = 3;
   public static final int OPERAND_PATTERN_DEFINES = 4;
   public static final int OPERAND_MEASURES = 5;
+  public static final int OPERAND_AFTER = 6;
+
+  public static final SqlPrefixOperator SKIP_TO_FIRST =
+      new SqlPrefixOperator("SKIP TO FIRST", SqlKind.SKIP_TO_FIRST, 20, null,
+          null, null);
+
+  public static final SqlPrefixOperator SKIP_TO_LAST =
+      new SqlPrefixOperator("SKIP TO LAST", SqlKind.SKIP_TO_LAST, 20, null,
+          null, null);
 
   //~ Instance fields -------------------------------------------
 
@@ -47,11 +56,12 @@ public class SqlMatchRecognize extends SqlCall {
   private SqlLiteral strictEnd;
   private SqlNodeList patternDefList;
   private SqlNodeList measureList;
+  private SqlNode after;
 
   /** Creates a SqlMatchRecognize. */
   public SqlMatchRecognize(SqlParserPos pos, SqlNode tableRef, SqlNode pattern,
       SqlLiteral strictStart, SqlLiteral strictEnd, SqlNodeList patternDefList,
-      SqlNodeList measureList) {
+      SqlNodeList measureList, SqlNode after) {
     super(pos);
     this.tableRef = Preconditions.checkNotNull(tableRef);
     this.pattern = Preconditions.checkNotNull(pattern);
@@ -60,6 +70,7 @@ public class SqlMatchRecognize extends SqlCall {
     this.patternDefList = Preconditions.checkNotNull(patternDefList);
     Preconditions.checkArgument(patternDefList.size() > 0);
     this.measureList = Preconditions.checkNotNull(measureList);
+    this.after = after;
   }
 
   // ~ Methods
@@ -74,7 +85,7 @@ public class SqlMatchRecognize extends SqlCall {
 
   @Override public List<SqlNode> getOperandList() {
     return ImmutableNullableList.of(tableRef, pattern, strictStart, strictEnd,
-        patternDefList, measureList);
+        patternDefList, measureList, after);
   }
 
   @Override public void unparse(SqlWriter writer, int leftPrec,
@@ -107,6 +118,9 @@ public class SqlMatchRecognize extends SqlCall {
     case OPERAND_MEASURES:
       measureList = Preconditions.checkNotNull((SqlNodeList) operand);
       break;
+    case OPERAND_AFTER:
+      after = operand;
+      break;
     default:
       throw new AssertionError(i);
     }
@@ -136,6 +150,36 @@ public class SqlMatchRecognize extends SqlCall {
     return measureList;
   }
 
+  public SqlNode getAfter() {
+    return after;
+  }
+
+  /**
+   * Options for {@code AFTER MATCH} clause.
+   */
+  public enum AfterOption {
+    SKIP_TO_NEXT_ROW("SKIP TO NEXT ROW"),
+    SKIP_PAST_LAST_ROW("SKIP PAST LAST ROW");
+
+    private final String sql;
+
+    AfterOption(String sql) {
+      this.sql = sql;
+    }
+
+    @Override public String toString() {
+      return sql;
+    }
+
+    /**
+     * Creates a parse-tree node representing an occurrence of this symbol
+     * at a particular position in the parsed text.
+     */
+    public SqlLiteral symbol(SqlParserPos pos) {
+      return SqlLiteral.createSymbol(this, pos);
+    }
+  }
+
   /**
    * An operator describing a MATCH_RECOGNIZE specification.
    */
@@ -160,7 +204,7 @@ public class SqlMatchRecognize extends SqlCall {
 
       return new SqlMatchRecognize(pos, operands[0], operands[1],
           (SqlLiteral) operands[2], (SqlLiteral) operands[3],
-          (SqlNodeList) operands[4], (SqlNodeList) operands[5]);
+          (SqlNodeList) operands[4], (SqlNodeList) operands[5], operands[6]);
     }
 
     @Override public <R> void acceptCall(
@@ -208,6 +252,12 @@ public class SqlMatchRecognize extends SqlCall {
         writer.endList(measureFrame);
       }
 
+      if (pattern.after != null) {
+        writer.newlineAndIndent();
+        writer.sep("AFTER MATCH");
+        pattern.after.unparse(writer, 0, 0);
+      }
+
       writer.newlineAndIndent();
       writer.sep("PATTERN");
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/c850e227/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index 573214e..abf3fca 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -2082,12 +2082,12 @@ public class SqlToRelConverter {
     final SqlValidatorNamespace ns = validator.getNamespace(matchRecognize);
     final SqlValidatorScope scope = validator.getMatchRecognizeScope(matchRecognize);
 
-    final Blackboard mrBlackBoard = createBlackboard(scope, null, false);
+    final Blackboard matchBb = createBlackboard(scope, null, false);
     final RelDataType rowType = ns.getRowType();
     // convert inner query, could be a table name or a derived table
     SqlNode expr = matchRecognize.getTableRef();
-    convertFrom(mrBlackBoard, expr);
-    final RelNode input = mrBlackBoard.root;
+    convertFrom(matchBb, expr);
+    final RelNode input = matchBb.root;
 
     // convert pattern
     final Set<String> patternVarsSet = new HashSet<>();
@@ -2120,7 +2120,29 @@ public class SqlToRelConverter {
       };
     final RexNode patternNode = pattern.accept(patternVarVisitor);
 
-    mrBlackBoard.setPatternVarRef(true);
+    SqlNode afterMatch = matchRecognize.getAfter();
+    if (afterMatch == null) {
+      afterMatch =
+          SqlMatchRecognize.AfterOption.SKIP_TO_NEXT_ROW.symbol(SqlParserPos.ZERO);
+    }
+
+    final RexNode after;
+    if (afterMatch instanceof SqlCall) {
+      List<SqlNode> operands = ((SqlCall) afterMatch).getOperandList();
+      SqlOperator operator = ((SqlCall) afterMatch).getOperator();
+      assert operands.size() == 1;
+      SqlIdentifier id = (SqlIdentifier) operands.get(0);
+      assert patternVarsSet.contains(id.getSimple())
+          : id.getSimple() + " not defined in pattern";
+      RexNode rex = rexBuilder.makeLiteral(id.getSimple());
+      after =
+          rexBuilder.makeCall(validator.getUnknownType(), operator,
+              ImmutableList.of(rex));
+    } else {
+      after = matchBb.convertExpression(afterMatch);
+    }
+
+    matchBb.setPatternVarRef(true);
 
     // convert measures
     final ImmutableMap.Builder<String, RexNode> measureNodes =
@@ -2128,7 +2150,7 @@ public class SqlToRelConverter {
     for (SqlNode measure : matchRecognize.getMeasureList()) {
       List<SqlNode> operands = ((SqlCall) measure).getOperandList();
       String alias = ((SqlIdentifier) operands.get(1)).getSimple();
-      RexNode rex = mrBlackBoard.convertExpression(operands.get(0));
+      RexNode rex = matchBb.convertExpression(operands.get(0));
       measureNodes.put(alias, rex);
     }
 
@@ -2138,11 +2160,11 @@ public class SqlToRelConverter {
     for (SqlNode def : matchRecognize.getPatternDefList()) {
       List<SqlNode> operands = ((SqlCall) def).getOperandList();
       String alias = ((SqlIdentifier) operands.get(1)).getSimple();
-      RexNode rex = mrBlackBoard.convertExpression(operands.get(0));
+      RexNode rex = matchBb.convertExpression(operands.get(0));
       definitionNodes.put(alias, rex);
     }
 
-    mrBlackBoard.setPatternVarRef(false);
+    matchBb.setPatternVarRef(false);
 
     final RelFactories.MatchFactory factory =
         RelFactories.DEFAULT_MATCH_FACTORY;
@@ -2150,8 +2172,7 @@ public class SqlToRelConverter {
         factory.createMatchRecognize(input, patternNode,
             matchRecognize.getStrictStart().booleanValue(),
             matchRecognize.getStrictEnd().booleanValue(),
-            definitionNodes.build(),
-            measureNodes.build(),
+            definitionNodes.build(), measureNodes.build(), after,
             rowType);
     bb.setRoot(rel, false);
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/c850e227/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index 15c8063..0357c6b 100644
--- a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -627,6 +627,7 @@ public class RelToSqlConverterTest {
     String expected = "SELECT *\n"
         + "FROM (SELECT *\n"
         + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -648,6 +649,7 @@ public class RelToSqlConverterTest {
     final String expected = "SELECT *\n"
         + "FROM (SELECT *\n"
         + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" + $)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -669,6 +671,7 @@ public class RelToSqlConverterTest {
     final String expected = "SELECT *\n"
         + "FROM (SELECT *\n"
         + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (^ \"STRT\" \"DOWN\" + \"UP\" +)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -690,6 +693,7 @@ public class RelToSqlConverterTest {
     final String expected = "SELECT *\n"
         + "FROM (SELECT *\n"
         + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (^ \"STRT\" \"DOWN\" + \"UP\" + $)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -711,6 +715,7 @@ public class RelToSqlConverterTest {
     final String expected = "SELECT *\n"
         + "FROM (SELECT *\n"
         + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" \"DOWN\" * \"UP\" ?)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -732,6 +737,7 @@ public class RelToSqlConverterTest {
     final String expected = "SELECT *\n"
         + "FROM (SELECT *\n"
         + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" {- \"DOWN\" -} \"UP\" ?)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -754,6 +760,7 @@ public class RelToSqlConverterTest {
     final String expected = "SELECT *\n"
         + "FROM (SELECT *\n"
         + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" \"DOWN\" { 2 } \"UP\" { 3, })\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -775,6 +782,7 @@ public class RelToSqlConverterTest {
     final String expected = "SELECT *\n"
         + "FROM (SELECT *\n"
         + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" \"DOWN\" { , 2 } \"UP\" { 3, 5 })\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -796,6 +804,7 @@ public class RelToSqlConverterTest {
     final String expected = "SELECT *\n"
         + "FROM (SELECT *\n"
         + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" {- \"DOWN\" + -} {- \"UP\" * -})\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -818,6 +827,7 @@ public class RelToSqlConverterTest {
     final String expected = "SELECT *\n"
         + "FROM (SELECT *\n"
         + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN "
         + "(\"A\" \"B\" \"C\" | \"A\" \"C\" \"B\" | \"B\" \"A\" \"C\" "
         + "| \"B\" \"C\" \"A\" | \"C\" \"A\" \"B\" | \"C\" \"B\" \"A\")\n"
@@ -840,6 +850,7 @@ public class RelToSqlConverterTest {
     final String expected = "SELECT *\n"
         + "FROM (SELECT *\n"
         + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -861,6 +872,7 @@ public class RelToSqlConverterTest {
     final String expected = "SELECT *\n"
         + "FROM (SELECT *\n"
         + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -900,6 +912,7 @@ public class RelToSqlConverterTest {
         + "WHERE \"customer\".\"city\" = 'San Francisco' "
         + "AND \"product_class\".\"product_department\" = 'Snacks') "
         + "MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -922,6 +935,7 @@ public class RelToSqlConverterTest {
     final String expected = "SELECT *\n"
         + "FROM (SELECT *\n"
         + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0)"
@@ -943,6 +957,7 @@ public class RelToSqlConverterTest {
     final String expected = "SELECT *\n"
         + "FROM (SELECT *\n"
         + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) "
@@ -964,6 +979,7 @@ public class RelToSqlConverterTest {
     final String expected = "SELECT *\n"
         + "FROM (SELECT *\n"
         + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -986,6 +1002,7 @@ public class RelToSqlConverterTest {
     final String expected = "SELECT *\n"
         + "FROM (SELECT *\n"
         + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -1016,6 +1033,7 @@ public class RelToSqlConverterTest {
         + "FINAL \"STRT\".\"net_weight\" AS \"START_NW\", "
         + "FINAL LAST(\"DOWN\".\"net_weight\", 0) AS \"BOTTOM_NW\", "
         + "FINAL LAST(\"UP\".\"net_weight\", 0) AS \"END_NW\"\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -1046,6 +1064,7 @@ public class RelToSqlConverterTest {
         + "FINAL \"STRT\".\"net_weight\" AS \"START_NW\", "
         + "FINAL LAST(\"DOWN\".\"net_weight\", 0) AS \"BOTTOM_NW\", "
         + "FINAL LAST(\"UP\".\"net_weight\", 0) AS \"END_NW\"\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -1076,6 +1095,7 @@ public class RelToSqlConverterTest {
         + "FINAL \"STRT\".\"net_weight\" AS \"START_NW\", "
         + "FINAL (RUNNING LAST(\"DOWN\".\"net_weight\", 0)) AS \"BOTTOM_NW\", "
         + "FINAL LAST(\"UP\".\"net_weight\", 0) AS \"END_NW\"\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -1106,6 +1126,7 @@ public class RelToSqlConverterTest {
         + "FINAL COUNT(\"UP\".\"net_weight\") AS \"UP_CNT\", "
         + "FINAL COUNT(\"*\".\"net_weight\") AS \"DOWN_CNT\", "
         + "FINAL (RUNNING COUNT(\"*\".\"net_weight\")) AS \"RUNNING_CNT\"\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -1138,6 +1159,7 @@ public class RelToSqlConverterTest {
         + "FINAL LAST(\"UP\".\"net_weight\", 0) AS \"UP_CNT\", "
         + "FINAL (SUM(\"DOWN\".\"net_weight\") / COUNT(\"DOWN\".\"net_weight\")) "
         + "AS \"DOWN_CNT\"\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
         + "DEFINE "
         + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) < "
@@ -1168,6 +1190,7 @@ public class RelToSqlConverterTest {
         + "FINAL FIRST(\"STRT\".\"net_weight\", 0) AS \"START_NW\", "
         + "FINAL LAST(\"DOWN\".\"net_weight\", 0) AS \"UP_CNT\", "
         + "FINAL SUM(\"DOWN\".\"net_weight\") AS \"DOWN_CNT\"\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN "
         + "(\"STRT\" \"DOWN\" + \"UP\" +)\n"
         + "DEFINE "
@@ -1199,6 +1222,7 @@ public class RelToSqlConverterTest {
         + "FINAL FIRST(\"STRT\".\"net_weight\", 0) AS \"START_NW\", "
         + "FINAL LAST(\"DOWN\".\"net_weight\", 0) AS \"UP_CNT\", "
         + "FINAL SUM(\"DOWN\".\"net_weight\") AS \"DOWN_CNT\"\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
         + "PATTERN "
         + "(\"STRT\" \"DOWN\" + \"UP\" +)\n"
         + "DEFINE "
@@ -1210,6 +1234,121 @@ public class RelToSqlConverterTest {
     sql(sql).ok(expected);
   }
 
+  @Test public void testMatchRecognizePatternSkip1() {
+    final String sql = "select *\n"
+        + "  from \"product\" match_recognize\n"
+        + "  (\n"
+        + "    after match skip to next row\n"
+        + "    pattern (strt down+ up+)\n"
+        + "    define\n"
+        + "      down as down.\"net_weight\" < PREV(down.\"net_weight\"),\n"
+        + "      up as up.\"net_weight\" > NEXT(up.\"net_weight\")\n"
+        + "  ) mr";
+    final String expected = "SELECT *\n"
+        + "FROM (SELECT *\n"
+        + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
+        + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
+        + "DEFINE "
+        + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0)"
+        + " < PREV(\"DOWN\".\"net_weight\", 1), "
+        + "\"UP\" AS PREV(\"UP\".\"net_weight\", 0)"
+        + " > NEXT(PREV(\"UP\".\"net_weight\", 0), 1))";
+    sql(sql).ok(expected);
+  }
+
+  @Test public void testMatchRecognizePatternSkip2() {
+    final String sql = "select *\n"
+        + "  from \"product\" match_recognize\n"
+        + "  (\n"
+        + "    after match skip past last row\n"
+        + "    pattern (strt down+ up+)\n"
+        + "    define\n"
+        + "      down as down.\"net_weight\" < PREV(down.\"net_weight\"),\n"
+        + "      up as up.\"net_weight\" > NEXT(up.\"net_weight\")\n"
+        + "  ) mr";
+    final String expected = "SELECT *\n"
+        + "FROM (SELECT *\n"
+        + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP PAST LAST ROW\n"
+        + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
+        + "DEFINE "
+        + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0)"
+        + " < PREV(\"DOWN\".\"net_weight\", 1), "
+        + "\"UP\" AS PREV(\"UP\".\"net_weight\", 0)"
+        + " > NEXT(PREV(\"UP\".\"net_weight\", 0), 1))";
+    sql(sql).ok(expected);
+  }
+
+  @Test public void testMatchRecognizePatternSkip3() {
+    final String sql = "select *\n"
+        + "  from \"product\" match_recognize\n"
+        + "  (\n"
+        + "    after match skip to FIRST down\n"
+        + "    pattern (strt down+ up+)\n"
+        + "    define\n"
+        + "      down as down.\"net_weight\" < PREV(down.\"net_weight\"),\n"
+        + "      up as up.\"net_weight\" > NEXT(up.\"net_weight\")\n"
+        + "  ) mr";
+    final String expected = "SELECT *\n"
+        + "FROM (SELECT *\n"
+        + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO FIRST \"DOWN\"\n"
+        + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
+        + "DEFINE "
+        + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0)"
+        + " < PREV(\"DOWN\".\"net_weight\", 1), "
+        + "\"UP\" AS PREV(\"UP\".\"net_weight\", 0)"
+        + " > NEXT(PREV(\"UP\".\"net_weight\", 0), 1))";
+    sql(sql).ok(expected);
+  }
+
+  @Test public void testMatchRecognizePatternSkip4() {
+    final String sql = "select *\n"
+        + "  from \"product\" match_recognize\n"
+        + "  (\n"
+        + "    after match skip to last down\n"
+        + "    pattern (strt down+ up+)\n"
+        + "    define\n"
+        + "      down as down.\"net_weight\" < PREV(down.\"net_weight\"),\n"
+        + "      up as up.\"net_weight\" > NEXT(up.\"net_weight\")\n"
+        + "  ) mr";
+    final String expected = "SELECT *\n"
+        + "FROM (SELECT *\n"
+        + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO LAST \"DOWN\"\n"
+        + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
+        + "DEFINE "
+        + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0)"
+        + " < PREV(\"DOWN\".\"net_weight\", 1), "
+        + "\"UP\" AS PREV(\"UP\".\"net_weight\", 0)"
+        + " > NEXT(PREV(\"UP\".\"net_weight\", 0), 1))";
+    sql(sql).ok(expected);
+  }
+
+  @Test public void testMatchRecognizePatternSkip5() {
+    final String sql = "select *\n"
+        + "  from \"product\" match_recognize\n"
+        + "  (\n"
+        + "    after match skip to down\n"
+        + "    pattern (strt down+ up+)\n"
+        + "    define\n"
+        + "      down as down.\"net_weight\" < PREV(down.\"net_weight\"),\n"
+        + "      up as up.\"net_weight\" > NEXT(up.\"net_weight\")\n"
+        + "  ) mr";
+    final String expected = "SELECT *\n"
+        + "FROM (SELECT *\n"
+        + "FROM \"foodmart\".\"product\") MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO LAST \"DOWN\"\n"
+        + "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
+        + "DEFINE "
+        + "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0)"
+        + " < PREV(\"DOWN\".\"net_weight\", 1), "
+        + "\"UP\" AS PREV(\"UP\".\"net_weight\", 0)"
+        + " > NEXT(PREV(\"UP\".\"net_weight\", 0), 1))";
+    sql(sql).ok(expected);
+  }
+
   /** Fluid interface to run tests. */
   private static class Sql {
     private CalciteAssert.SchemaSpec schemaSpec;

http://git-wip-us.apache.org/repos/asf/calcite/blob/c850e227/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
index 37c6c3a..05089bf 100644
--- a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -7636,6 +7636,111 @@ public class SqlParserTest {
     sql(sql).ok(expected);
   }
 
+  @Test public void testMatchRecognizePatternSkip1() {
+    final String sql = "select *\n"
+        + "  from t match_recognize\n"
+        + "  (\n"
+        + "     after match skip to next row\n"
+        + "    pattern (strt down+ up+)\n"
+        + "    define\n"
+        + "      down as down.price < PREV(down.price),\n"
+        + "      up as up.price > prev(up.price)\n"
+        + "  ) mr";
+    final String expected = "SELECT *\n"
+        + "FROM `T` MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO NEXT ROW\n"
+        + "PATTERN (((`STRT` (`DOWN` +)) (`UP` +)))\n"
+        + "DEFINE "
+        + "`DOWN` AS (`DOWN`.`PRICE` < PREV(`DOWN`.`PRICE`, 1)), "
+        + "`UP` AS (`UP`.`PRICE` > PREV(`UP`.`PRICE`, 1))"
+        + ") AS `MR`";
+    sql(sql).ok(expected);
+  }
+
+  @Test public void testMatchRecognizePatternSkip2() {
+    final String sql = "select *\n"
+        + "  from t match_recognize\n"
+        + "  (\n"
+        + "     after match skip past last row\n"
+        + "    pattern (strt down+ up+)\n"
+        + "    define\n"
+        + "      down as down.price < PREV(down.price),\n"
+        + "      up as up.price > prev(up.price)\n"
+        + "  ) mr";
+    final String expected = "SELECT *\n"
+        + "FROM `T` MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP PAST LAST ROW\n"
+        + "PATTERN (((`STRT` (`DOWN` +)) (`UP` +)))\n"
+        + "DEFINE "
+        + "`DOWN` AS (`DOWN`.`PRICE` < PREV(`DOWN`.`PRICE`, 1)), "
+        + "`UP` AS (`UP`.`PRICE` > PREV(`UP`.`PRICE`, 1))"
+        + ") AS `MR`";
+    sql(sql).ok(expected);
+  }
+
+  @Test public void testMatchRecognizePatternSkip3() {
+    final String sql = "select *\n"
+        + "  from t match_recognize\n"
+        + "  (\n"
+        + "     after match skip to FIRST down\n"
+        + "    pattern (strt down+ up+)\n"
+        + "    define\n"
+        + "      down as down.price < PREV(down.price),\n"
+        + "      up as up.price > prev(up.price)\n"
+        + "  ) mr";
+    final String expected = "SELECT *\n"
+        + "FROM `T` MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO FIRST `DOWN`\n"
+        + "PATTERN (((`STRT` (`DOWN` +)) (`UP` +)))\n"
+        + "DEFINE "
+        + "`DOWN` AS (`DOWN`.`PRICE` < PREV(`DOWN`.`PRICE`, 1)), "
+        + "`UP` AS (`UP`.`PRICE` > PREV(`UP`.`PRICE`, 1))"
+        + ") AS `MR`";
+    sql(sql).ok(expected);
+  }
+
+  @Test public void testMatchRecognizePatternSkip4() {
+    final String sql = "select *\n"
+        + "  from t match_recognize\n"
+        + "  (\n"
+        + "     after match skip to LAST down\n"
+        + "    pattern (strt down+ up+)\n"
+        + "    define\n"
+        + "      down as down.price < PREV(down.price),\n"
+        + "      up as up.price > prev(up.price)\n"
+        + "  ) mr";
+    final String expected = "SELECT *\n"
+        + "FROM `T` MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO LAST `DOWN`\n"
+        + "PATTERN (((`STRT` (`DOWN` +)) (`UP` +)))\n"
+        + "DEFINE "
+        + "`DOWN` AS (`DOWN`.`PRICE` < PREV(`DOWN`.`PRICE`, 1)), "
+        + "`UP` AS (`UP`.`PRICE` > PREV(`UP`.`PRICE`, 1))"
+        + ") AS `MR`";
+    sql(sql).ok(expected);
+  }
+
+  @Test public void testMatchRecognizePatternSkip5() {
+    final String sql = "select *\n"
+        + "  from t match_recognize\n"
+        + "  (\n"
+        + "     after match skip to down\n"
+        + "    pattern (strt down+ up+)\n"
+        + "    define\n"
+        + "      down as down.price < PREV(down.price),\n"
+        + "      up as up.price > prev(up.price)\n"
+        + "  ) mr";
+    final String expected = "SELECT *\n"
+        + "FROM `T` MATCH_RECOGNIZE(\n"
+        + "AFTER MATCH SKIP TO LAST `DOWN`\n"
+        + "PATTERN (((`STRT` (`DOWN` +)) (`UP` +)))\n"
+        + "DEFINE "
+        + "`DOWN` AS (`DOWN`.`PRICE` < PREV(`DOWN`.`PRICE`, 1)), "
+        + "`UP` AS (`UP`.`PRICE` > PREV(`UP`.`PRICE`, 1))"
+        + ") AS `MR`";
+    sql(sql).ok(expected);
+  }
+
   //~ Inner Interfaces -------------------------------------------------------
 
   /**

http://git-wip-us.apache.org/repos/asf/calcite/blob/c850e227/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 94551f9..fe9261d 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -9225,11 +9225,10 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
   }
 
   @Test public void testMatchRecognizeDefines4() throws Exception {
-    final String sql = "select * \n"
-        + "  from emp match_recognize \n"
-        + "  (\n"
+    final String sql = "select *\n"
+        + "  from emp match_recognize (\n"
         + "    pattern (strt down+ up+)\n"
-        + "    define \n"
+        + "    define\n"
         + "      down as down.sal < PREV(down.sal),\n"
         + "      up as up.sal > FIRST(^PREV(up.sal)^)\n"
         + "  ) mr";
@@ -9238,11 +9237,10 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
   }
 
   @Test public void testMatchRecognizeDefines5() throws Exception {
-    final String sql = "select * \n"
-        + "  from emp match_recognize \n"
-        + "  (\n"
+    final String sql = "select *\n"
+        + "  from emp match_recognize (\n"
         + "    pattern (strt down+ up+)\n"
-        + "    define \n"
+        + "    define\n"
         + "      down as down.sal < PREV(down.sal),\n"
         + "      up as up.sal > FIRST(^FIRST(up.sal)^)\n"
         + "  ) mr";
@@ -9251,11 +9249,10 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
   }
 
   @Test public void testMatchRecognizeDefines6() throws Exception {
-    final String sql = "select * \n"
-        + "  from emp match_recognize \n"
-        + "  (\n"
+    final String sql = "select *\n"
+        + "  from emp match_recognize (\n"
         + "    pattern (strt down+ up+)\n"
-        + "    define \n"
+        + "    define\n"
         + "      down as down.sal < PREV(down.sal),\n"
         + "      up as up.sal > ^COUNT(down.sal, up.sal)^\n"
         + "  ) mr";
@@ -9653,12 +9650,10 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
 
   @Test public void testMatchRecognizeMeasures1() throws Exception {
     final String sql = "select *\n"
-        + "  from emp match_recognize\n"
-        + "  (\n"
-        + "   measures "
-        + "   STRT.sal as start_sal,"
-        + "   ^LAST(null)^ as bottom_sal,"
-        + "   LAST(up.ts) as end_sal"
+        + "  from emp match_recognize (\n"
+        + "    measures STRT.sal as start_sal,"
+        + "      ^LAST(null)^ as bottom_sal,"
+        + "      LAST(up.ts) as end_sal"
         + "    pattern (strt down+ up+)\n"
         + "    define\n"
         + "      down as down.sal < PREV(down.sal),\n"
@@ -9668,6 +9663,38 @@ public class SqlValidatorTest extends SqlValidatorTestCase {
       .fails("Null parameters in 'LAST\\(NULL, 0\\)'");
   }
 
+  @Test public void testMatchRecognizeSkipTo1() throws Exception {
+    final String sql = "select *\n"
+        + "  from emp match_recognize (\n"
+        + "    after match skip to ^null^\n"
+        + "    measures\n"
+        + "      STRT.sal as start_sal,\n"
+        + "      LAST(up.ts) as end_sal\n"
+        + "    pattern (strt down+ up+)\n"
+        + "    define\n"
+        + "      down as down.sal < PREV(down.sal),\n"
+        + "      up as up.sal > prev(up.sal)\n"
+        + "  ) mr";
+    sql(sql)
+        .fails("(?s).*Encountered \"to null\" at .*");
+  }
+
+  @Test public void testMatchRecognizeSkipTo2() throws Exception {
+    final String sql = "select *\n"
+        + "  from emp match_recognize (\n"
+        + "    after match skip to ^no_exists^\n"
+        + "    measures\n"
+        + "      STRT.sal as start_sal,"
+        + "      LAST(up.ts) as end_sal"
+        + "    pattern (strt down+ up+)\n"
+        + "    define\n"
+        + "      down as down.sal < PREV(down.sal),\n"
+        + "      up as up.sal > prev(up.sal)\n"
+        + "  ) mr";
+    sql(sql)
+        .fails("(?s).*Encountered \"measures\" at .*");
+  }
+
 }
 
 // End SqlValidatorTest.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/c850e227/site/_docs/reference.md
----------------------------------------------------------------------
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index d4d64f2..c942c14 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -596,6 +596,7 @@ PARTIAL,
 **PARTITION**,
 PASCAL,
 PASSTHROUGH,
+PAST,
 PATH,
 **PATTERN**,
 **PER**,