Repository: calcite
Updated Branches:
  refs/heads/master e9d0ca673 -> feff5964b


[CALCITE-715] Add PERIOD type constructor and period operators (CONTAINS, 
PRECEDES, etc.)


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

Branch: refs/heads/master
Commit: feff5964bf36d2e5aa311fcd7777b159aa4a8a1e
Parents: c94a080
Author: Julian Hyde <[email protected]>
Authored: Wed Apr 19 21:10:00 2017 -0700
Committer: Julian Hyde <[email protected]>
Committed: Wed Apr 26 14:21:11 2017 -0700

----------------------------------------------------------------------
 core/src/main/codegen/templates/Parser.jj       | 170 +++++++++++--------
 .../java/org/apache/calcite/sql/SqlKind.java    |  41 ++++-
 .../calcite/sql/fun/SqlOverlapsOperator.java    | 130 +++++---------
 .../calcite/sql/fun/SqlStdOperatorTable.java    |  20 ++-
 .../apache/calcite/sql/type/OperandTypes.java   |  65 +++++++
 .../sql2rel/StandardConvertletTable.java        |  95 ++++++++---
 .../calcite/sql/parser/SqlParserTest.java       |  77 ++++++---
 .../apache/calcite/sql/test/SqlAdvisorTest.java |   7 +
 .../calcite/sql/test/SqlOperatorBaseTest.java   | 122 ++++++++++++-
 .../calcite/sql/test/SqlPrettyWriterTest.java   |   2 +-
 .../apache/calcite/test/SqlValidatorTest.java   | 153 ++++++++++++++++-
 core/src/test/resources/sql/misc.iq             |  32 +++-
 site/_docs/reference.md                         | 155 ++++++++++++++++-
 13 files changed, 856 insertions(+), 213 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/feff5964/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 a6672db..6b63d28 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -621,13 +621,17 @@ JAVACODE boolean matchesPrefix(int[] seq, int[][] 
prefixes)
  *
  * <p>Postgres syntax for limit:
  *
+ * <blockquote><pre>
  *    [ LIMIT { count | ALL } ]
- *    [ OFFSET start ]
+ *    [ OFFSET start ]</pre>
+ * </blockquote>
  *
  * <p>SQL:2008 syntax for limit:
  *
+ * <blockquote><pre>
  *    [ OFFSET start { ROW | ROWS } ]
- *    [ FETCH { FIRST | NEXT } [ count ] { ROW | ROWS } ONLY ]
+ *    [ FETCH { FIRST | NEXT } [ count ] { ROW | ROWS } ONLY ]</pre>
+ * </blockquote>
  */
 SqlNode OrderedQueryOrExpr(ExprContext exprContext) :
 {
@@ -733,14 +737,18 @@ SqlNode ParenthesizedExpression(ExprContext exprContext) :
  * <p>REVIEW jvs 8-Feb-2004: There's a small hole in this production.  It can 
be
  * used to construct something like
  *
- * <code>WHERE x IN (select count(*) from t where c=d,5)</code>,
+ * <blockquote><pre>
+ * WHERE x IN (select count(*) from t where c=d,5)</pre>
+ * </blockquote>
  *
- * which should be illegal.  The above is interpreted as equivalent to
+ * <p>which should be illegal.  The above is interpreted as equivalent to
  *
- * <code>WHERE x IN ((select count(*) from t where c=d),5)</code>,
+ * <blockquote><pre>
+ * <code>WHERE x IN ((select count(*) from t where c=d),5)</pre>
+ * </blockquote>
  *
- * which is a legal use of a sub-query.  The only way to fix the hole is to be
- * able to remember whether a subexpression was parenthesized or not, which
+ * <p>which is a legal use of a sub-query.  The only way to fix the hole is to
+ * be able to remember whether a subexpression was parenthesized or not, which
  * means preserving parentheses in the SqlNode tree.  This is probably
  * desirable anyway for use in purely syntactic parsing applications (e.g. SQL
  * pretty-printer).  However, if this is done, it's important to also make
@@ -2028,7 +2036,7 @@ SqlNode TableFunctionCall(SqlParserPos pos) :
  * Abstract production:
  *    SqlNode ExtendedTableRef()
  *
- * Allows parser to be extended with new types of table references.  The
+ * <p>Allows parser to be extended with new types of table references.  The
  * default implementation of this production is empty.
  */
 
@@ -3120,14 +3128,13 @@ List<Object> Expression2(ExprContext exprContext) :
  */
 SqlNode Expression3(ExprContext exprContext) :
 {
-    SqlNode e;
-    SqlNodeList list;
-    SqlNodeList list1;
-    SqlNodeList list2;
-    SqlPrefixOperator op;
-    boolean rowSeen = false;
-    SqlParserPos pos;
-    SqlParserPos prefixRowOpPos;
+    final SqlNode e;
+    final SqlNodeList list;
+    final SqlNodeList list1;
+    final SqlNodeList list2;
+    final SqlOperator op;
+    final SqlParserPos pos;
+    SqlParserPos rowPos = null;
 }
 {
     LOOKAHEAD(2)
@@ -3152,25 +3159,22 @@ SqlNode Expression3(ExprContext exprContext) :
         return SqlStdOperatorTable.ROW.createCall(list);
     }
     |
-    {
-        pos = getPos();
-    }
     [
-        <ROW>
-        {
-            pos = getPos(); rowSeen = true;
-        }
+        <ROW> { rowPos = getPos(); }
     ]
     list1 = ParenthesizedQueryOrCommaList(exprContext) {
-        if (rowSeen) {
+        pos = list1.getParserPosition();
+        if (rowPos != null) {
             // interpret as row constructor
-            return SqlStdOperatorTable.ROW.createCall(pos, list1.toArray());
-
+            return SqlStdOperatorTable.ROW.createCall(rowPos.plus(pos),
+                list1.toArray());
         }
     }
     [
+        LOOKAHEAD(2)
+        /* TODO:
         (
-            <OVERLAPS>
+            op = periodOperator()
             list2 = ParenthesizedQueryOrCommaList(exprContext)
             {
                 if (list1.size() != 2 || list2.size() != 2) {
@@ -3182,12 +3186,13 @@ SqlNode Expression3(ExprContext exprContext) :
                 for (SqlNode node : list2) {
                     list1.add(node);
                 }
-                return SqlStdOperatorTable.OVERLAPS.createCall(
+                return op.createCall(
                     list1.getParserPosition().plus(list2.getParserPosition()),
                     list1.toArray());
             }
         )
         |
+        */
         (
             e = IntervalQualifier()
             {
@@ -3217,11 +3222,29 @@ SqlNode Expression3(ExprContext exprContext) :
             return list1.get(0);
         } else {
             // interpret as row constructor
-            return SqlStdOperatorTable.ROW.createCall(pos, list1.toArray());
+            return SqlStdOperatorTable.ROW.createCall(
+                pos.plus(list1.getParserPosition()), list1.toArray());
         }
     }
 }
 
+SqlOperator periodOperator() :
+{
+}
+{
+     <OVERLAPS> { return SqlStdOperatorTable.OVERLAPS; }
+|
+     <IMMEDIATELY> <PRECEDES> { return 
SqlStdOperatorTable.IMMEDIATELY_PRECEDES; }
+|
+     <PRECEDES> { return SqlStdOperatorTable.PRECEDES; }
+|
+     <IMMEDIATELY> <SUCCEEDS> { return 
SqlStdOperatorTable.IMMEDIATELY_SUCCEEDS; }
+|
+     <SUCCEEDS> { return SqlStdOperatorTable.SUCCEEDS; }
+|
+     <EQUALS> { return SqlStdOperatorTable.PERIOD_EQUALS; }
+}
+
 /**
  * Parses a COLLATE clause
  */
@@ -3260,6 +3283,8 @@ SqlNode AtomicRowExpression() :
     |
     e = MapConstructor() { return e; }
     |
+    e = PeriodConstructor() { return e; }
+    |
     // NOTE jvs 18-Jan-2005:  use syntactic lookahead to discriminate
     // compound identifiers from function calls in which the function
     // name is a compound identifier
@@ -3825,7 +3850,7 @@ SqlNode ArrayConstructor() :
     )
 }
 
-/** Parses an MAP constructor */
+/** Parses a MAP constructor */
 SqlNode MapConstructor() :
 {
     SqlNodeList args;
@@ -3860,6 +3885,23 @@ SqlNode MapConstructor() :
     )
 }
 
+/** Parses a PERIOD constructor */
+SqlNode PeriodConstructor() :
+{
+    final SqlNode e0, e1;
+    final SqlParserPos pos;
+}
+{
+    <PERIOD> { pos = getPos(); }
+    <LPAREN>
+    e0 = Expression(ExprContext.ACCEPT_SUB_QUERY)
+    <COMMA>
+    e1 = Expression(ExprContext.ACCEPT_SUB_QUERY)
+    <RPAREN> {
+        return SqlStdOperatorTable.ROW.createCall(pos.plus(getPos()), e0, e1);
+    }
+}
+
 /**
  * Parses an interval literal.
  */
@@ -5387,50 +5429,38 @@ SqlBinaryOperator BinaryRowOperator() :
     SqlBinaryOperator op;
 }
 {
-    <EQ>
-    { return SqlStdOperatorTable.EQUALS; }
-    | <GT>
-    { return SqlStdOperatorTable.GREATER_THAN; }
-    | <LT>
-    { return SqlStdOperatorTable.LESS_THAN; }
-    | <LE>
-    { return SqlStdOperatorTable.LESS_THAN_OR_EQUAL; }
-    | <GE>
-    { return SqlStdOperatorTable.GREATER_THAN_OR_EQUAL; }
-    | <NE>
-    { return SqlStdOperatorTable.NOT_EQUALS; }
-    | <NE2>
-    {
+    // <IN> is handled as a special case
+    <EQ> { return SqlStdOperatorTable.EQUALS; }
+    | <GT> { return SqlStdOperatorTable.GREATER_THAN; }
+    | <LT> { return SqlStdOperatorTable.LESS_THAN; }
+    | <LE> { return SqlStdOperatorTable.LESS_THAN_OR_EQUAL; }
+    | <GE> { return SqlStdOperatorTable.GREATER_THAN_OR_EQUAL; }
+    | <NE> { return SqlStdOperatorTable.NOT_EQUALS; }
+    | <NE2> {
         if (!this.conformance.isBangEqualAllowed()) {
             throw new ParseException(RESOURCE.bangEqualNotAllowed().str());
         }
         return SqlStdOperatorTable.NOT_EQUALS;
     }
-    | <PLUS>
-    { return SqlStdOperatorTable.PLUS; }
-    | <MINUS>
-    { return SqlStdOperatorTable.MINUS; }
-    | <STAR>
-    { return SqlStdOperatorTable.MULTIPLY; }
-    | <SLASH>
-    { return SqlStdOperatorTable.DIVIDE; }
-    | <CONCAT>
-    { return SqlStdOperatorTable.CONCAT; }
-    | <AND>
-    { return SqlStdOperatorTable.AND; }
-    | <OR>
-    { return SqlStdOperatorTable.OR; }
-    | LOOKAHEAD(2) <IS> <DISTINCT> <FROM>
-    { return SqlStdOperatorTable.IS_DISTINCT_FROM; }
-    | <IS> <NOT> <DISTINCT> <FROM>
-    { return SqlStdOperatorTable.IS_NOT_DISTINCT_FROM; }
-    // <IN> is handled as a special case
-    | <MEMBER> <OF>
-    { return SqlStdOperatorTable.MEMBER_OF; }
-    | <SUBMULTISET> <OF>
-    { return SqlStdOperatorTable.SUBMULTISET_OF; }
-    | op = BinaryMultisetOperator()
-    { return op; }
+    | <PLUS> { return SqlStdOperatorTable.PLUS; }
+    | <MINUS> { return SqlStdOperatorTable.MINUS; }
+    | <STAR> { return SqlStdOperatorTable.MULTIPLY; }
+    | <SLASH> { return SqlStdOperatorTable.DIVIDE; }
+    | <CONCAT> { return SqlStdOperatorTable.CONCAT; }
+    | <AND> { return SqlStdOperatorTable.AND; }
+    | <OR> { return SqlStdOperatorTable.OR; }
+    | LOOKAHEAD(2) <IS> <DISTINCT> <FROM> { return 
SqlStdOperatorTable.IS_DISTINCT_FROM; }
+    | <IS> <NOT> <DISTINCT> <FROM> { return 
SqlStdOperatorTable.IS_NOT_DISTINCT_FROM; }
+    | <MEMBER> <OF> { return SqlStdOperatorTable.MEMBER_OF; }
+    | <SUBMULTISET> <OF> { return SqlStdOperatorTable.SUBMULTISET_OF; }
+    | <CONTAINS> { return SqlStdOperatorTable.CONTAINS; }
+    | <OVERLAPS> { return SqlStdOperatorTable.OVERLAPS; }
+    | <EQUALS> { return SqlStdOperatorTable.PERIOD_EQUALS; }
+    | <PRECEDES> { return SqlStdOperatorTable.PRECEDES; }
+    | <SUCCEEDS> { return SqlStdOperatorTable.SUCCEEDS; }
+    | <IMMEDIATELY> <PRECEDES> { return 
SqlStdOperatorTable.IMMEDIATELY_PRECEDES; }
+    | <IMMEDIATELY> <SUCCEEDS> { return 
SqlStdOperatorTable.IMMEDIATELY_SUCCEEDS; }
+    | op = BinaryMultisetOperator() { return op; }
 }
 
 /**
@@ -5707,6 +5737,7 @@ SqlPostfixOperator PostfixRowOperator() :
     | < HOUR: "HOUR" >
     | < IDENTITY: "IDENTITY" >
     | < IMMEDIATE: "IMMEDIATE" >
+    | < IMMEDIATELY: "IMMEDIATELY" >
     | < IMPLEMENTATION: "IMPLEMENTATION" >
     | < IMPORT: "IMPORT" >
     | < IN: "IN" >
@@ -6237,6 +6268,7 @@ String CommonNonReservedKeyWord() :
         | <GRANTED>
         | <HIERARCHY>
         | <IMMEDIATE>
+        | <IMMEDIATELY>
         | <IMPLEMENTATION>
         | <INCLUDING>
         | <INCREMENT>

http://git-wip-us.apache.org/repos/asf/calcite/blob/feff5964/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 07164b3..4147c9a 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
@@ -332,11 +332,41 @@ public enum SqlKind {
   DOT,
 
   /**
-   * The "OVERLAPS" operator.
+   * The "OVERLAPS" operator for periods.
    */
   OVERLAPS,
 
   /**
+   * The "CONTAINS" operator for periods.
+   */
+  CONTAINS,
+
+  /**
+   * The "PRECEDES" operator for periods.
+   */
+  PRECEDES,
+
+  /**
+   * The "IMMEDIATELY PRECEDES" operator for periods.
+   */
+  IMMEDIATELY_PRECEDES("IMMEDIATELY PRECEDES"),
+
+  /**
+   * The "SUCCEEDS" operator for periods.
+   */
+  SUCCEEDS,
+
+  /**
+   * The "IMMEDIATELY SUCCEEDS" operator for periods.
+   */
+  IMMEDIATELY_SUCCEEDS("IMMEDIATELY SUCCEEDS"),
+
+  /**
+   * The "EQUALS" operator for periods.
+   */
+  PERIOD_EQUALS("EQUALS"),
+
+  /**
    * The "LIKE" operator.
    */
   LIKE,
@@ -1084,6 +1114,15 @@ public enum SqlKind {
 
   /** Lower-case name. */
   public final String lowerName = name().toLowerCase(Locale.ROOT);
+  public final String sql;
+
+  SqlKind() {
+    sql = name();
+  }
+
+  SqlKind(String sql) {
+    this.sql = sql;
+  }
 
   /** Returns the kind that corresponds to this operator but in the opposite
    * direction. Or returns this, if this kind is not reversible.

http://git-wip-us.apache.org/repos/asf/calcite/blob/feff5964/core/src/main/java/org/apache/calcite/sql/fun/SqlOverlapsOperator.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/sql/fun/SqlOverlapsOperator.java 
b/core/src/main/java/org/apache/calcite/sql/fun/SqlOverlapsOperator.java
index 941a06e..0ca5e54 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlOverlapsOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlOverlapsOperator.java
@@ -17,20 +17,19 @@
 package org.apache.calcite.sql.fun;
 
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.sql.SqlBinaryOperator;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlCallBinding;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlOperandCountRange;
-import org.apache.calcite.sql.SqlSpecialOperator;
 import org.apache.calcite.sql.SqlUtil;
 import org.apache.calcite.sql.SqlWriter;
 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.SqlOperandCountRanges;
+import org.apache.calcite.sql.type.SqlSingleOperandTypeChecker;
 import org.apache.calcite.sql.type.SqlTypeUtil;
-import org.apache.calcite.sql.validate.SqlValidator;
-import org.apache.calcite.sql.validate.SqlValidatorScope;
 
 import com.google.common.collect.ImmutableList;
 
@@ -38,52 +37,44 @@ import com.google.common.collect.ImmutableList;
  * SqlOverlapsOperator represents the SQL:1999 standard {@code OVERLAPS}
  * function. Determines whether two anchored time intervals overlap.
  */
-public class SqlOverlapsOperator extends SqlSpecialOperator {
-  //~ Static fields/initializers ---------------------------------------------
-
-  private static final SqlWriter.FrameType FRAME_TYPE =
-      SqlWriter.FrameTypeEnum.create("OVERLAPS");
-
+public class SqlOverlapsOperator extends SqlBinaryOperator {
   //~ Constructors -----------------------------------------------------------
 
-  public SqlOverlapsOperator() {
-    super("OVERLAPS",
-        SqlKind.OVERLAPS,
-        30,
-        true,
-        ReturnTypes.BOOLEAN_NULLABLE,
+  SqlOverlapsOperator(SqlKind kind) {
+    super(kind.sql, kind, 30, true, ReturnTypes.BOOLEAN_NULLABLE,
         InferTypes.FIRST_KNOWN,
-        null);
+        OperandTypes.sequence("'<PERIOD> " + kind.sql + " <PERIOD>'",
+            OperandTypes.PERIOD, OperandTypes.PERIOD));
   }
 
   //~ Methods ----------------------------------------------------------------
 
-  public void unparse(
-      SqlWriter writer,
-      SqlCall call,
-      int leftPrec,
+  @Override public void unparse(SqlWriter writer, SqlCall call, int leftPrec,
       int rightPrec) {
     final SqlWriter.Frame frame =
-        writer.startList(FRAME_TYPE, "(", ")");
-    call.operand(0).unparse(writer, leftPrec, rightPrec);
-    writer.sep(",", true);
-    call.operand(1).unparse(writer, leftPrec, rightPrec);
-    writer.sep(")", true);
+        writer.startList(SqlWriter.FrameTypeEnum.SIMPLE);
+    arg(writer, call, leftPrec, rightPrec, 0);
     writer.sep(getName());
-    writer.sep("(", true);
-    call.operand(2).unparse(writer, leftPrec, rightPrec);
-    writer.sep(",", true);
-    call.operand(3).unparse(writer, leftPrec, rightPrec);
+    arg(writer, call, leftPrec, rightPrec, 1);
     writer.endList(frame);
   }
 
-  public SqlOperandCountRange getOperandCountRange() {
-    return SqlOperandCountRanges.of(4);
+  void arg(SqlWriter writer, SqlCall call, int leftPrec, int rightPrec, int i) 
{
+    if (SqlUtil.isCallTo(call.operand(i), SqlStdOperatorTable.ROW)) {
+      SqlCall row = call.operand(i);
+      writer.keyword("PERIOD");
+      writer.sep("(", true);
+      row.operand(0).unparse(writer, leftPrec, rightPrec);
+      writer.sep(",", true);
+      row.operand(1).unparse(writer, leftPrec, rightPrec);
+      writer.sep(")", true);
+    } else {
+      call.operand(i).unparse(writer, leftPrec, rightPrec);
+    }
   }
 
-  public String getSignatureTemplate(int operandsCount) {
-    assert 4 == operandsCount;
-    return "({1}, {2}) {0} ({3}, {4})";
+  public SqlOperandCountRange getOperandCountRange() {
+    return SqlOperandCountRanges.of(2);
   }
 
   public String getAllowedSignatures(String opName) {
@@ -108,69 +99,36 @@ public class SqlOverlapsOperator extends 
SqlSpecialOperator {
     return ret.toString();
   }
 
-  public boolean checkOperandTypes(
-      SqlCallBinding callBinding,
+  public boolean checkOperandTypes(SqlCallBinding callBinding,
       boolean throwOnFailure) {
-    SqlValidator validator = callBinding.getValidator();
-    SqlValidatorScope scope = callBinding.getScope();
-    if (!OperandTypes.DATETIME.checkSingleOperandType(
-        callBinding,
-        callBinding.operand(0),
-        0,
-        throwOnFailure)) {
+    if (!OperandTypes.PERIOD.checkSingleOperandType(callBinding,
+        callBinding.operand(0), 0, throwOnFailure)) {
       return false;
     }
-    if (!OperandTypes.DATETIME.checkSingleOperandType(
-        callBinding,
-        callBinding.operand(2),
-        0,
-        throwOnFailure)) {
-      return false;
+    final SqlSingleOperandTypeChecker rightChecker;
+    switch (kind) {
+    case CONTAINS:
+      rightChecker = OperandTypes.PERIOD_OR_DATETIME;
+      break;
+    default:
+      rightChecker = OperandTypes.PERIOD;
+      break;
     }
-
-    RelDataType t0 = validator.deriveType(scope, callBinding.operand(0));
-    RelDataType t1 = validator.deriveType(scope, callBinding.operand(1));
-    RelDataType t2 = validator.deriveType(scope, callBinding.operand(2));
-    RelDataType t3 = validator.deriveType(scope, callBinding.operand(3));
-
-    // t0 must be comparable with t2
-    if (!SqlTypeUtil.sameNamedType(t0, t2)) {
-      if (throwOnFailure) {
-        throw callBinding.newValidationSignatureError();
-      }
+    if (!rightChecker.checkSingleOperandType(callBinding,
+        callBinding.operand(1), 0, throwOnFailure)) {
       return false;
     }
-
-    if (SqlTypeUtil.isDatetime(t1)) {
-      // if t1 is of DATETIME,
-      // then t1 must be comparable with t0
-      if (!SqlTypeUtil.sameNamedType(t0, t1)) {
+    final RelDataType t0 = callBinding.getOperandType(0);
+    final RelDataType t1 = callBinding.getOperandType(1);
+    if (!SqlTypeUtil.isDatetime(t1)) {
+      final RelDataType t00 = t0.getFieldList().get(0).getType();
+      final RelDataType t10 = t1.getFieldList().get(0).getType();
+      if (!SqlTypeUtil.sameNamedType(t00, t10)) {
         if (throwOnFailure) {
           throw callBinding.newValidationSignatureError();
         }
         return false;
       }
-    } else if (!SqlTypeUtil.isInterval(t1)) {
-      if (throwOnFailure) {
-        throw callBinding.newValidationSignatureError();
-      }
-      return false;
-    }
-
-    if (SqlTypeUtil.isDatetime(t3)) {
-      // if t3 is of DATETIME,
-      // then t3 must be comparable with t2
-      if (!SqlTypeUtil.sameNamedType(t2, t3)) {
-        if (throwOnFailure) {
-          throw callBinding.newValidationSignatureError();
-        }
-        return false;
-      }
-    } else if (!SqlTypeUtil.isInterval(t3)) {
-      if (throwOnFailure) {
-        throw callBinding.newValidationSignatureError();
-      }
-      return false;
     }
     return true;
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/feff5964/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 8d385a6..bd63b91 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
@@ -1048,7 +1048,25 @@ public class SqlStdOperatorTable extends 
ReflectiveSqlOperatorTable {
       new SqlCollectionTableOperator("TABLE", SqlModality.RELATION);
 
   public static final SqlOverlapsOperator OVERLAPS =
-      new SqlOverlapsOperator();
+      new SqlOverlapsOperator(SqlKind.OVERLAPS);
+
+  public static final SqlOverlapsOperator CONTAINS =
+      new SqlOverlapsOperator(SqlKind.CONTAINS);
+
+  public static final SqlOverlapsOperator PRECEDES =
+      new SqlOverlapsOperator(SqlKind.PRECEDES);
+
+  public static final SqlOverlapsOperator IMMEDIATELY_PRECEDES =
+      new SqlOverlapsOperator(SqlKind.IMMEDIATELY_PRECEDES);
+
+  public static final SqlOverlapsOperator SUCCEEDS =
+      new SqlOverlapsOperator(SqlKind.SUCCEEDS);
+
+  public static final SqlOverlapsOperator IMMEDIATELY_SUCCEEDS =
+      new SqlOverlapsOperator(SqlKind.IMMEDIATELY_SUCCEEDS);
+
+  public static final SqlOverlapsOperator PERIOD_EQUALS =
+      new SqlOverlapsOperator(SqlKind.PERIOD_EQUALS);
 
   public static final SqlSpecialOperator VALUES =
       new SqlValuesOperator();

http://git-wip-us.apache.org/repos/asf/calcite/blob/feff5964/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 ddddd08..15d3b70 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
@@ -233,6 +233,12 @@ public abstract class OperandTypes {
   public static final SqlSingleOperandTypeChecker INTERVAL =
       family(SqlTypeFamily.DATETIME_INTERVAL);
 
+  public static final SqlSingleOperandTypeChecker PERIOD =
+      new PeriodOperandTypeChecker();
+
+  public static final SqlSingleOperandTypeChecker PERIOD_OR_DATETIME =
+      or(PERIOD, DATETIME);
+
   public static final FamilyOperandTypeChecker INTERVAL_INTERVAL =
       family(SqlTypeFamily.DATETIME_INTERVAL, SqlTypeFamily.DATETIME_INTERVAL);
 
@@ -621,6 +627,65 @@ public abstract class OperandTypes {
           return Consistency.NONE;
         }
       };
+
+  /** Operand type checker that accepts period types:
+   * PERIOD (DATETIME, DATETIME)
+   * PERIOD (DATETIME, INTERVAL)
+   * [ROW] (DATETIME, DATETIME)
+   * [ROW] (DATETIME, INTERVAL) */
+  private static class PeriodOperandTypeChecker
+      implements SqlSingleOperandTypeChecker {
+    public boolean checkSingleOperandType(SqlCallBinding callBinding,
+        SqlNode node, int iFormalOperand, boolean throwOnFailure) {
+      assert 0 == iFormalOperand;
+      RelDataType type =
+          callBinding.getValidator().deriveType(callBinding.getScope(), node);
+      boolean valid = false;
+      if (type.isStruct() && type.getFieldList().size() == 2) {
+        final RelDataType t0 = type.getFieldList().get(0).getType();
+        final RelDataType t1 = type.getFieldList().get(1).getType();
+        if (SqlTypeUtil.isDatetime(t0)) {
+          if (SqlTypeUtil.isDatetime(t1)) {
+            // t0 must be comparable with t1; (DATE, TIMESTAMP) is not valid
+            if (SqlTypeUtil.sameNamedType(t0, t1)) {
+              valid = true;
+            }
+          } else if (SqlTypeUtil.isInterval(t1)) {
+            valid = true;
+          }
+        }
+      }
+
+      if (!valid && throwOnFailure) {
+        throw callBinding.newValidationSignatureError();
+      }
+      return valid;
+    }
+
+    public boolean checkOperandTypes(SqlCallBinding callBinding,
+        boolean throwOnFailure) {
+      return checkSingleOperandType(callBinding, callBinding.operand(0), 0,
+          throwOnFailure);
+    }
+
+    public SqlOperandCountRange getOperandCountRange() {
+      return SqlOperandCountRanges.of(1);
+    }
+
+    public String getAllowedSignatures(SqlOperator op, String opName) {
+      return SqlUtil.getAliasedSignature(op, opName,
+          ImmutableList.of("PERIOD (DATETIME, INTERVAL)",
+              "PERIOD (DATETIME, DATETIME)"));
+    }
+
+    public boolean isOptional(int i) {
+      return false;
+    }
+
+    public Consistency getConsistency() {
+      return Consistency.NONE;
+    }
+  }
 }
 
 // End OperandTypes.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/feff5964/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java 
b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
index 90f1ffc..3d9975c 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
@@ -32,7 +32,6 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexRangeRef;
 import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.sql.SqlAggFunction;
-import org.apache.calcite.sql.SqlBasicCall;
 import org.apache.calcite.sql.SqlBinaryOperator;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlDataTypeSpec;
@@ -70,6 +69,7 @@ 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.SqlValidator;
+import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableList;
@@ -365,6 +365,10 @@ public class StandardConvertletTable extends 
ReflectiveConvertletTable {
     return rexBuilder.makeCall(SqlStdOperatorTable.OR, a0, a1);
   }
 
+  private RexNode eq(RexBuilder rexBuilder, RexNode a0, RexNode a1) {
+    return rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, a0, a1);
+  }
+
   private RexNode ge(RexBuilder rexBuilder, RexNode a0, RexNode a1) {
     return rexBuilder.makeCall(SqlStdOperatorTable.GREATER_THAN_OR_EQUAL, a0,
         a1);
@@ -1192,35 +1196,76 @@ public class StandardConvertletTable extends 
ReflectiveConvertletTable {
       SqlCall call) {
     // for intervals [t0, t1] overlaps [t2, t3], we can find if the
     // intervals overlaps by: ~(t1 < t2 or t3 < t0)
-    final SqlNode[] operands = ((SqlBasicCall) call).getOperands();
-    assert operands.length == 4;
-    final SqlParserPos pos = call.getParserPosition();
-    final RelDataType t1 = cx.getValidator().getValidatedNodeType(operands[1]);
-    if (SqlTypeUtil.isInterval(t1)) {
-      // make t1 = t0 + t1 when t1 is an interval.
-      operands[1] = plus(pos, operands[0], operands[1]);
-    }
-    final RelDataType t3 = cx.getValidator().getValidatedNodeType(operands[3]);
-    if (SqlTypeUtil.isInterval(t3)) {
-      // make t3 = t2 + t3 when t3 is an interval.
-      operands[3] = plus(pos, operands[2], operands[3]);
-    }
+    assert call.getOperandList().size() == 2;
 
-    final RexNode r0 = cx.convertExpression(operands[0]);
-    final RexNode r1 = cx.convertExpression(operands[1]);
-    final RexNode r2 = cx.convertExpression(operands[2]);
-    final RexNode r3 = cx.convertExpression(operands[3]);
+    final Pair<RexNode, RexNode> left =
+        convertOverlapsOperand(cx, call.getParserPosition(), call.operand(0));
+    final RexNode r0 = left.left;
+    final RexNode r1 = left.right;
+    final Pair<RexNode, RexNode> right =
+        convertOverlapsOperand(cx, call.getParserPosition(), call.operand(1));
+    final RexNode r2 = right.left;
+    final RexNode r3 = right.right;
 
     // Sort end points into start and end, such that (s0 <= e0) and (s1 <= e1).
     final RexBuilder rexBuilder = cx.getRexBuilder();
-    final RexNode s0 = case_(rexBuilder, le(rexBuilder, r0, r1), r0, r1);
-    final RexNode e0 = case_(rexBuilder, le(rexBuilder, r0, r1), r1, r0);
-    final RexNode s1 = case_(rexBuilder, le(rexBuilder, r2, r3), r2, r3);
-    final RexNode e1 = case_(rexBuilder, le(rexBuilder, r2, r3), r3, r2);
+    RexNode leftSwap = le(rexBuilder, r0, r1);
+    final RexNode s0 = case_(rexBuilder, leftSwap, r0, r1);
+    final RexNode e0 = case_(rexBuilder, leftSwap, r1, r0);
+    RexNode rightSwap = le(rexBuilder, r2, r3);
+    final RexNode s1 = case_(rexBuilder, rightSwap, r2, r3);
+    final RexNode e1 = case_(rexBuilder, rightSwap, r3, r2);
     // (e0 >= s1) AND (e1 >= s0)
-    return and(rexBuilder,
-        ge(rexBuilder, e0, s1),
-        ge(rexBuilder, e1, s0));
+    switch (op.kind) {
+    case OVERLAPS:
+      return and(rexBuilder,
+          ge(rexBuilder, e0, s1),
+          ge(rexBuilder, e1, s0));
+    case CONTAINS:
+      return and(rexBuilder,
+          le(rexBuilder, s0, s1),
+          ge(rexBuilder, e0, e1));
+    case PERIOD_EQUALS:
+      return and(rexBuilder,
+          eq(rexBuilder, s0, s1),
+          eq(rexBuilder, e0, e1));
+    case PRECEDES:
+      return le(rexBuilder, e0, s1);
+    case IMMEDIATELY_PRECEDES:
+      return eq(rexBuilder, e0, s1);
+    case SUCCEEDS:
+      return ge(rexBuilder, s0, e1);
+    case IMMEDIATELY_SUCCEEDS:
+      return eq(rexBuilder, s0, e1);
+    default:
+      throw new AssertionError(op);
+    }
+  }
+
+  private Pair<RexNode, RexNode> convertOverlapsOperand(SqlRexContext cx,
+      SqlParserPos pos, SqlNode operand) {
+    final SqlNode a0;
+    final SqlNode a1;
+    switch (operand.getKind()) {
+    case ROW:
+      a0 = ((SqlCall) operand).operand(0);
+      final SqlNode a10 = ((SqlCall) operand).operand(1);
+      final RelDataType t1 = cx.getValidator().getValidatedNodeType(a10);
+      if (SqlTypeUtil.isInterval(t1)) {
+        // make t1 = t0 + t1 when t1 is an interval.
+        a1 = plus(pos, a0, a10);
+      } else {
+        a1 = a10;
+      }
+      break;
+    default:
+      a0 = operand;
+      a1 = operand;
+    }
+
+    final RexNode r0 = cx.convertExpression(a0);
+    final RexNode r1 = cx.convertExpression(a1);
+    return Pair.of(r0, r1);
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/calcite/blob/feff5964/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 6fdb98d..613191d 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
@@ -1011,30 +1011,46 @@ public class SqlParserTest {
             + "FROM `SALES`.`DEPTS`) AS `T`");
   }
 
-  @Test public void testOverlaps() {
-    checkExp(
-        "(x,xx) overlaps (y,yy)",
-        "((`X`, `XX`) OVERLAPS (`Y`, `YY`))");
+  @Test public void testPeriod() {
+    // We don't have a PERIOD constructor currently;
+    // ROW constructor is sufficient for now.
+    checkExp("period (date '1969-01-05', interval '2-3' year to month)",
+        "(ROW(DATE '1969-01-05', INTERVAL '2-3' YEAR TO MONTH))");
+  }
 
-    checkExp(
-        "(x,xx) overlaps (y,yy) or false",
-        "(((`X`, `XX`) OVERLAPS (`Y`, `YY`)) OR FALSE)");
+  @Test public void testOverlaps() {
+    final String[] ops = {
+      "overlaps", "equals", "precedes", "succeeds",
+      "immediately precedes", "immediately succeeds"
+    };
+    final String[] periods = {"period ", ""};
+    for (String period : periods) {
+      for (String op : ops) {
+        checkPeriodPredicate(new Checker(op, period));
+      }
+    }
+  }
 
-    checkExp(
-        "true and not (x,xx) overlaps (y,yy) or false",
-        "((TRUE AND (NOT ((`X`, `XX`) OVERLAPS (`Y`, `YY`)))) OR FALSE)");
+  void checkPeriodPredicate(Checker checker) {
+    checker.checkExp("$p(x,xx) $op $p(y,yy)",
+        "(PERIOD (`X`, `XX`) $op PERIOD (`Y`, `YY`))");
 
-    checkExpFails(
-        "^(x,xx,xxx) overlaps (y,yy)^ or false",
-        "(?s).*Illegal overlaps expression.*");
+    checker.checkExp(
+        "$p(x,xx) $op $p(y,yy) or false",
+        "((PERIOD (`X`, `XX`) $op PERIOD (`Y`, `YY`)) OR FALSE)");
 
-    checkExpFails(
-        "true or ^(x,xx,xxx) overlaps (y,yy,yyy)^ or false",
-        "(?s).*Illegal overlaps expression.*");
+    checker.checkExp(
+        "true and not $p(x,xx) $op $p(y,yy) or false",
+        "((TRUE AND (NOT (PERIOD (`X`, `XX`) $op PERIOD (`Y`, `YY`)))) OR 
FALSE)");
 
-    checkExpFails(
-        "^(x,xx) overlaps (y,yy,yyy)^ or false",
-        "(?s).*Illegal overlaps expression.*");
+    if (checker.period.isEmpty()) {
+      checker.checkExp("$p(x,xx,xxx) $op $p(y,yy) or false",
+          "((PERIOD (`X`, `XX`) $op PERIOD (`Y`, `YY`)) OR FALSE)");
+    } else {
+      // 3-argument rows are valid in the parser, rejected by the validator
+      checker.checkExpFails("$p(x,xx^,^xxx) $op $p(y,yy) or false",
+          "(?s).*Encountered \",\" at .*");
+    }
   }
 
   @Test public void testIsDistinctFrom() {
@@ -7971,6 +7987,29 @@ public class SqlParserTest {
       return this;
     }
   }
+
+  /** Runs tests on period operators such as OVERLAPS, IMMEDIATELY PRECEDES. */
+  private class Checker {
+    final String op;
+    final String period;
+
+    Checker(String op, String period) {
+      this.op = op;
+      this.period = period;
+    }
+
+    public void checkExp(String sql, String expected) {
+      SqlParserTest.this.checkExp(
+          sql.replace("$op", op).replace("$p", period),
+          expected.replace("$op", op.toUpperCase(Locale.ROOT)));
+    }
+
+    public void checkExpFails(String sql, String expected) {
+      SqlParserTest.this.checkExpFails(
+          sql.replace("$op", op).replace("$p", period),
+          expected.replace("$op", op));
+    }
+  }
 }
 
 // End SqlParserTest.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/feff5964/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java 
b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
index 356709b..085de9b 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
@@ -179,6 +179,7 @@ public class SqlAdvisorTest extends SqlValidatorTestCase {
           "KEYWORD(OCTET_LENGTH)",
           "KEYWORD(OVERLAY)",
           "KEYWORD(PERCENT_RANK)",
+          "KEYWORD(PERIOD)",
           "KEYWORD(POSITION)",
           "KEYWORD(POWER)",
           "KEYWORD(PREV)",
@@ -258,6 +259,9 @@ public class SqlAdvisorTest extends SqlValidatorTestCase {
           "KEYWORD(>=)",
           "KEYWORD(AND)",
           "KEYWORD(BETWEEN)",
+          "KEYWORD(CONTAINS)",
+          "KEYWORD(EQUALS)",
+          "KEYWORD(IMMEDIATELY)",
           "KEYWORD(IN)",
           "KEYWORD(IS)",
           "KEYWORD(LIKE)",
@@ -265,8 +269,11 @@ public class SqlAdvisorTest extends SqlValidatorTestCase {
           "KEYWORD(MULTISET)",
           "KEYWORD(NOT)",
           "KEYWORD(OR)",
+          "KEYWORD(OVERLAPS)",
+          "KEYWORD(PRECEDES)",
           "KEYWORD(SIMILAR)",
           "KEYWORD(SUBMULTISET)",
+          "KEYWORD(SUCCEEDS)",
           "KEYWORD([)",
           "KEYWORD(||)");
 

http://git-wip-us.apache.org/repos/asf/calcite/blob/feff5964/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java 
b/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
index ff3c0e2..d3c4920 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
@@ -2353,7 +2353,15 @@ public abstract class SqlOperatorBaseTest {
         "(timestamp '1-2-3 4:5:6', timestamp '1-2-3 4:5:6' ) overlaps 
(cast(null as timestamp), interval '1 2:3:4.5' day to second)");
   }
 
-  @Test public void testOverlapsEtc() {
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-715";>[CALCITE-715]
+   * Add PERIOD type constructor and period operators (CONTAINS, PRECEDES,
+   * etc.)</a>.
+   *
+   * <p>Tests OVERLAP and similar period operators CONTAINS, EQUALS, PRECEDES,
+   * SUCCEEDS, IMMEDIATELY PRECEDES, IMMEDIATELY SUCCEEDS for DATE, TIME and
+   * TIMESTAMP values. */
+  @Test public void testPeriodOperators() {
     String[] times = {
       "TIME '01:00:00'",
       "TIME '02:00:00'",
@@ -2378,6 +2386,7 @@ public abstract class SqlOperatorBaseTest {
   }
 
   private void checkOverlaps(OverlapChecker c) {
+    c.isTrue("($0,$0) OVERLAPS ($0,$0)");
     c.isFalse("($0,$1) OVERLAPS ($2,$3)");
     c.isTrue("($0,$1) OVERLAPS ($1,$2)");
     c.isTrue("($0,$2) OVERLAPS ($1,$3)");
@@ -2387,6 +2396,117 @@ public abstract class SqlOperatorBaseTest {
     c.isTrue("($2,$3) OVERLAPS ($0,$2)");
     c.isTrue("($2,$3) OVERLAPS ($2,$0)");
     c.isTrue("($3,$2) OVERLAPS ($2,$0)");
+    c.isTrue("($0,$2) OVERLAPS ($2,$0)");
+    c.isTrue("($0,$3) OVERLAPS ($1,$3)");
+    c.isTrue("($0,$3) OVERLAPS ($3,$3)");
+
+    c.isTrue("($0,$0) CONTAINS ($0,$0)");
+    c.isFalse("($0,$1) CONTAINS ($2,$3)");
+    c.isFalse("($0,$1) CONTAINS ($1,$2)");
+    c.isFalse("($0,$2) CONTAINS ($1,$3)");
+    c.isFalse("($0,$2) CONTAINS ($3,$1)");
+    c.isFalse("($2,$0) CONTAINS ($3,$1)");
+    c.isFalse("($3,$2) CONTAINS ($1,$0)");
+    c.isFalse("($2,$3) CONTAINS ($0,$2)");
+    c.isFalse("($2,$3) CONTAINS ($2,$0)");
+    c.isFalse("($3,$2) CONTAINS ($2,$0)");
+    c.isTrue("($0,$2) CONTAINS ($2,$0)");
+    c.isTrue("($0,$3) CONTAINS ($1,$3)");
+    c.isTrue("($0,$3) CONTAINS ($3,$3)");
+    c.isTrue("($3,$0) CONTAINS ($3,$3)");
+    c.isTrue("($3,$0) CONTAINS ($0,$0)");
+
+    c.isTrue("($0,$0) CONTAINS $0");
+    c.isTrue("($3,$0) CONTAINS $0");
+    c.isTrue("($3,$0) CONTAINS $1");
+    c.isTrue("($3,$0) CONTAINS $2");
+    c.isTrue("($3,$0) CONTAINS $3");
+    c.isTrue("($0,$3) CONTAINS $0");
+    c.isTrue("($0,$3) CONTAINS $1");
+    c.isTrue("($0,$3) CONTAINS $2");
+    c.isTrue("($0,$3) CONTAINS $3");
+    c.isFalse("($1,$3) CONTAINS $0");
+    c.isFalse("($1,$2) CONTAINS $3");
+
+    c.isTrue("($0,$0) EQUALS ($0,$0)");
+    c.isFalse("($0,$1) EQUALS ($2,$3)");
+    c.isFalse("($0,$1) EQUALS ($1,$2)");
+    c.isFalse("($0,$2) EQUALS ($1,$3)");
+    c.isFalse("($0,$2) EQUALS ($3,$1)");
+    c.isFalse("($2,$0) EQUALS ($3,$1)");
+    c.isFalse("($3,$2) EQUALS ($1,$0)");
+    c.isFalse("($2,$3) EQUALS ($0,$2)");
+    c.isFalse("($2,$3) EQUALS ($2,$0)");
+    c.isFalse("($3,$2) EQUALS ($2,$0)");
+    c.isTrue("($0,$2) EQUALS ($2,$0)");
+    c.isFalse("($0,$3) EQUALS ($1,$3)");
+    c.isFalse("($0,$3) EQUALS ($3,$3)");
+    c.isFalse("($3,$0) EQUALS ($3,$3)");
+    c.isFalse("($3,$0) EQUALS ($0,$0)");
+
+    c.isTrue("($0,$0) PRECEDES ($0,$0)");
+    c.isTrue("($0,$1) PRECEDES ($2,$3)");
+    c.isTrue("($0,$1) PRECEDES ($1,$2)");
+    c.isFalse("($0,$2) PRECEDES ($1,$3)");
+    c.isFalse("($0,$2) PRECEDES ($3,$1)");
+    c.isFalse("($2,$0) PRECEDES ($3,$1)");
+    c.isFalse("($3,$2) PRECEDES ($1,$0)");
+    c.isFalse("($2,$3) PRECEDES ($0,$2)");
+    c.isFalse("($2,$3) PRECEDES ($2,$0)");
+    c.isFalse("($3,$2) PRECEDES ($2,$0)");
+    c.isFalse("($0,$2) PRECEDES ($2,$0)");
+    c.isFalse("($0,$3) PRECEDES ($1,$3)");
+    c.isTrue("($0,$3) PRECEDES ($3,$3)");
+    c.isTrue("($3,$0) PRECEDES ($3,$3)");
+    c.isFalse("($3,$0) PRECEDES ($0,$0)");
+
+    c.isTrue("($0,$0) SUCCEEDS ($0,$0)");
+    c.isFalse("($0,$1) SUCCEEDS ($2,$3)");
+    c.isFalse("($0,$1) SUCCEEDS ($1,$2)");
+    c.isFalse("($0,$2) SUCCEEDS ($1,$3)");
+    c.isFalse("($0,$2) SUCCEEDS ($3,$1)");
+    c.isFalse("($2,$0) SUCCEEDS ($3,$1)");
+    c.isTrue("($3,$2) SUCCEEDS ($1,$0)");
+    c.isTrue("($2,$3) SUCCEEDS ($0,$2)");
+    c.isTrue("($2,$3) SUCCEEDS ($2,$0)");
+    c.isTrue("($3,$2) SUCCEEDS ($2,$0)");
+    c.isFalse("($0,$2) SUCCEEDS ($2,$0)");
+    c.isFalse("($0,$3) SUCCEEDS ($1,$3)");
+    c.isFalse("($0,$3) SUCCEEDS ($3,$3)");
+    c.isFalse("($3,$0) SUCCEEDS ($3,$3)");
+    c.isTrue("($3,$0) SUCCEEDS ($0,$0)");
+
+    c.isTrue("($0,$0) IMMEDIATELY PRECEDES ($0,$0)");
+    c.isFalse("($0,$1) IMMEDIATELY PRECEDES ($2,$3)");
+    c.isTrue("($0,$1) IMMEDIATELY PRECEDES ($1,$2)");
+    c.isFalse("($0,$2) IMMEDIATELY PRECEDES ($1,$3)");
+    c.isFalse("($0,$2) IMMEDIATELY PRECEDES ($3,$1)");
+    c.isFalse("($2,$0) IMMEDIATELY PRECEDES ($3,$1)");
+    c.isFalse("($3,$2) IMMEDIATELY PRECEDES ($1,$0)");
+    c.isFalse("($2,$3) IMMEDIATELY PRECEDES ($0,$2)");
+    c.isFalse("($2,$3) IMMEDIATELY PRECEDES ($2,$0)");
+    c.isFalse("($3,$2) IMMEDIATELY PRECEDES ($2,$0)");
+    c.isFalse("($0,$2) IMMEDIATELY PRECEDES ($2,$0)");
+    c.isFalse("($0,$3) IMMEDIATELY PRECEDES ($1,$3)");
+    c.isTrue("($0,$3) IMMEDIATELY PRECEDES ($3,$3)");
+    c.isTrue("($3,$0) IMMEDIATELY PRECEDES ($3,$3)");
+    c.isFalse("($3,$0) IMMEDIATELY PRECEDES ($0,$0)");
+
+    c.isTrue("($0,$0) IMMEDIATELY SUCCEEDS ($0,$0)");
+    c.isFalse("($0,$1) IMMEDIATELY SUCCEEDS ($2,$3)");
+    c.isFalse("($0,$1) IMMEDIATELY SUCCEEDS ($1,$2)");
+    c.isFalse("($0,$2) IMMEDIATELY SUCCEEDS ($1,$3)");
+    c.isFalse("($0,$2) IMMEDIATELY SUCCEEDS ($3,$1)");
+    c.isFalse("($2,$0) IMMEDIATELY SUCCEEDS ($3,$1)");
+    c.isFalse("($3,$2) IMMEDIATELY SUCCEEDS ($1,$0)");
+    c.isTrue("($2,$3) IMMEDIATELY SUCCEEDS ($0,$2)");
+    c.isTrue("($2,$3) IMMEDIATELY SUCCEEDS ($2,$0)");
+    c.isTrue("($3,$2) IMMEDIATELY SUCCEEDS ($2,$0)");
+    c.isFalse("($0,$2) IMMEDIATELY SUCCEEDS ($2,$0)");
+    c.isFalse("($0,$3) IMMEDIATELY SUCCEEDS ($1,$3)");
+    c.isFalse("($0,$3) IMMEDIATELY SUCCEEDS ($3,$3)");
+    c.isFalse("($3,$0) IMMEDIATELY SUCCEEDS ($3,$3)");
+    c.isTrue("($3,$0) IMMEDIATELY SUCCEEDS ($0,$0)");
   }
 
   @Test public void testLessThanOperator() {

http://git-wip-us.apache.org/repos/asf/calcite/blob/feff5964/core/src/test/java/org/apache/calcite/sql/test/SqlPrettyWriterTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/calcite/sql/test/SqlPrettyWriterTest.java 
b/core/src/test/java/org/apache/calcite/sql/test/SqlPrettyWriterTest.java
index e414b0e..83d5a7e 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlPrettyWriterTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlPrettyWriterTest.java
@@ -275,7 +275,7 @@ public class SqlPrettyWriterTest {
     assertExprPrintsTo(
         true,
         "(x,xx) overlaps (y,yy) or x is not null",
-        "(`X`, `XX`) OVERLAPS (`Y`, `YY`) OR `X` IS NOT NULL");
+        "PERIOD (`X`, `XX`) OVERLAPS PERIOD (`Y`, `YY`) OR `X` IS NOT NULL");
   }
 
   @Test public void testUnion() {

http://git-wip-us.apache.org/repos/asf/calcite/blob/feff5964/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 fe9261d..95fdef0 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -4778,7 +4778,7 @@ public class SqlValidatorTest extends 
SqlValidatorTestCase {
     checkExpFails("false and ^1 in (date '2012-01-02', date '2012-01-04')^",
         ERR_IN_OPERANDS_INCOMPATIBLE);
     checkExpFails(
-        "1 > 5 ^or (1, 2) in (3, 4)^",
+        "1 > 5 or ^(1, 2) in (3, 4)^",
         ERR_IN_OPERANDS_INCOMPATIBLE);
   }
 
@@ -6727,8 +6727,11 @@ public class SqlValidatorTest extends 
SqlValidatorTestCase {
   }
 
   @Test public void testOverlaps() {
-    checkExpType(
-        "(date '1-2-3', date '1-2-3') overlaps (date '1-2-3', date '1-2-3')",
+    checkExpType("(date '1-2-3', date '1-2-3')\n"
+            + " overlaps (date '1-2-3', date '1-2-3')",
+        "BOOLEAN NOT NULL");
+    checkExpType("period (date '1-2-3', date '1-2-3')\n"
+            + " overlaps period (date '1-2-3', date '1-2-3')",
         "BOOLEAN NOT NULL");
     checkExp(
         "(date '1-2-3', date '1-2-3') overlaps (date '1-2-3', interval '1' 
year)");
@@ -6739,13 +6742,143 @@ public class SqlValidatorTest extends 
SqlValidatorTestCase {
 
     checkWholeExpFails(
         "(timestamp '1-2-3 4:5:6', timestamp '1-2-3 4:5:6' ) overlaps (time 
'4:5:6', interval '1 2:3:4.5' day to second)",
-        "(?s).*Cannot apply 'OVERLAPS' to arguments of type '.<TIMESTAMP.0.>, 
<TIMESTAMP.0.>. OVERLAPS .<TIME.0.>, <INTERVAL DAY TO SECOND>.*");
+        "(?s).*Cannot apply 'OVERLAPS' to arguments of type .*");
     checkWholeExpFails(
         "(time '4:5:6', timestamp '1-2-3 4:5:6' ) overlaps (time '4:5:6', 
interval '1 2:3:4.5' day to second)",
-        "(?s).*Cannot apply 'OVERLAPS' to arguments of type '.<TIME.0.>, 
<TIMESTAMP.0.>. OVERLAPS .<TIME.0.>, <INTERVAL DAY TO SECOND>.'.*");
+        "(?s).*Cannot apply 'OVERLAPS' to arguments of type .*");
     checkWholeExpFails(
         "(time '4:5:6', time '4:5:6' ) overlaps (time '4:5:6', date '1-2-3')",
-        "(?s).*Cannot apply 'OVERLAPS' to arguments of type '.<TIME.0.>, 
<TIME.0.>. OVERLAPS .<TIME.0.>, <DATE>.'.*");
+        "(?s).*Cannot apply 'OVERLAPS' to arguments of type .*");
+    checkWholeExpFails("1 overlaps 2",
+        "(?s).*Cannot apply 'OVERLAPS' to arguments of type "
+            + "'<INTEGER> OVERLAPS <INTEGER>'\\. Supported form.*");
+
+    checkExpType("true\n"
+            + "or (date '1-2-3', date '1-2-3')\n"
+            + "   overlaps (date '1-2-3', date '1-2-3')\n"
+            + "or false",
+        "BOOLEAN NOT NULL");
+    // row with 3 arguments as left argument to overlaps
+    checkExpFails("true\n"
+            + "or ^(date '1-2-3', date '1-2-3', date '1-2-3')\n"
+            + "   overlaps (date '1-2-3', date '1-2-3')^\n"
+            + "or false",
+        "(?s).*Cannot apply 'OVERLAPS' to arguments of type .*");
+    // row with 3 arguments as right argument to overlaps
+    checkExpFails("true\n"
+            + "or ^(date '1-2-3', date '1-2-3')\n"
+            + "  overlaps (date '1-2-3', date '1-2-3', date '1-2-3')^\n"
+            + "or false",
+        "(?s).*Cannot apply 'OVERLAPS' to arguments of type .*");
+    checkExpFails("^period (date '1-2-3', date '1-2-3')\n"
+            + "   overlaps (date '1-2-3', date '1-2-3', date '1-2-3')^",
+        "(?s).*Cannot apply 'OVERLAPS' to arguments of type .*");
+    checkExpFails("true\n"
+            + "or ^(1, 2) overlaps (2, 3)^\n"
+            + "or false",
+        "(?s).*Cannot apply 'OVERLAPS' to arguments of type .*");
+
+    // Other operators with similar syntax
+    String[] ops = {
+      "overlaps", "contains", "equals", "precedes", "succeeds",
+      "immediately precedes", "immediately succeeds"
+    };
+    for (String op : ops) {
+      checkExpType("period (date '1-2-3', date '1-2-3')\n"
+              + " " + op + " period (date '1-2-3', date '1-2-3')",
+          "BOOLEAN NOT NULL");
+      checkExpType("(date '1-2-3', date '1-2-3')\n"
+              + " " + op + " (date '1-2-3', date '1-2-3')",
+          "BOOLEAN NOT NULL");
+    }
+  }
+
+  @Test public void testContains() {
+    final String cannotApply =
+        "(?s).*Cannot apply 'CONTAINS' to arguments of type .*";
+
+    checkExpType("(date '1-2-3', date '1-2-3')\n"
+            + " contains (date '1-2-3', date '1-2-3')",
+        "BOOLEAN NOT NULL");
+    checkExpType("period (date '1-2-3', date '1-2-3')\n"
+            + " contains period (date '1-2-3', date '1-2-3')",
+        "BOOLEAN NOT NULL");
+    checkExp("(date '1-2-3', date '1-2-3')\n"
+        + "  contains (date '1-2-3', interval '1' year)");
+    checkExp("(time '1:2:3', interval '1' second)\n"
+        + " contains (time '23:59:59', time '1:2:3')");
+    checkExp("(timestamp '1-2-3 4:5:6', timestamp '1-2-3 4:5:6')\n"
+        + " contains (timestamp '1-2-3 4:5:6', interval '1 2:3:4.5' day to 
second)");
+
+    // period contains point
+    checkExp("(date '1-2-3', date '1-2-3')\n"
+        + "  contains date '1-2-3'");
+    // same, with "period" keyword
+    checkExp("period (date '1-2-3', date '1-2-3')\n"
+        + "  contains date '1-2-3'");
+    // point contains period
+    checkWholeExpFails("date '1-2-3'\n"
+            + "  contains (date '1-2-3', date '1-2-3')",
+        cannotApply);
+    // same, with "period" keyword
+    checkWholeExpFails("date '1-2-3'\n"
+            + "  contains period (date '1-2-3', date '1-2-3')",
+        cannotApply);
+    // point contains point
+    checkWholeExpFails("date '1-2-3' contains date '1-2-3'",
+        cannotApply);
+
+    checkWholeExpFails("(timestamp '1-2-3 4:5:6', timestamp '1-2-3 4:5:6' )\n"
+            + " contains (time '4:5:6', interval '1 2:3:4.5' day to second)",
+        cannotApply);
+    checkWholeExpFails("(time '4:5:6', timestamp '1-2-3 4:5:6' )\n"
+            + " contains (time '4:5:6', interval '1 2:3:4.5' day to second)",
+        cannotApply);
+    checkWholeExpFails("(time '4:5:6', time '4:5:6' )\n"
+            + "  contains (time '4:5:6', date '1-2-3')",
+        cannotApply);
+    checkWholeExpFails("1 contains 2",
+        cannotApply);
+    // row with 3 arguments
+    checkExpFails("true\n"
+            + "or ^(date '1-2-3', date '1-2-3', date '1-2-3')\n"
+            + "  contains (date '1-2-3', date '1-2-3')^\n"
+            + "or false",
+        cannotApply);
+
+    checkExpType("true\n"
+            + "or (date '1-2-3', date '1-2-3')\n"
+            + "  contains (date '1-2-3', date '1-2-3')\n"
+            + "or false",
+        "BOOLEAN NOT NULL");
+    // second argument is a point
+    checkExpType("true\n"
+            + "or (date '1-2-3', date '1-2-3')\n"
+            + "  contains date '1-2-3'\n"
+            + "or false",
+        "BOOLEAN NOT NULL");
+    // first argument may be null, so result may be null
+    checkExpType("true\n"
+            + "or (date '1-2-3',\n"
+            + "     case 1 when 2 then date '1-2-3' else null end)\n"
+            + "  contains date '1-2-3'\n"
+            + "or false",
+        "BOOLEAN");
+    // second argument may be null, so result may be null
+    checkExpType("true\n"
+            + "or (date '1-2-3', date '1-2-3')\n"
+            + "  contains case 1 when 1 then date '1-2-3' else null end\n"
+            + "or false",
+        "BOOLEAN");
+    checkExpFails("true\n"
+            + "or ^period (date '1-2-3', date '1-2-3')\n"
+            + "  contains period (date '1-2-3', time '4:5:6')^\n"
+            + "or false",
+        cannotApply);
+    checkExpFails("true\n"
+            + "or ^(1, 2) contains (2, 3)^\n"
+            + "or false",
+        cannotApply);
   }
 
   @Test public void testExtract() {
@@ -7994,11 +8127,17 @@ public class SqlValidatorTest extends 
SqlValidatorTestCase {
         + "= left\n"
         + "> left\n"
         + ">= left\n"
+        + "CONTAINS left\n"
+        + "EQUALS left\n"
+        + "IMMEDIATELY PRECEDES left\n"
+        + "IMMEDIATELY SUCCEEDS left\n"
         + "IS DISTINCT FROM left\n"
         + "IS NOT DISTINCT FROM left\n"
         + "MEMBER OF left\n"
-        + "OVERLAPS -\n"
+        + "OVERLAPS left\n"
+        + "PRECEDES left\n"
         + "SUBMULTISET OF left\n"
+        + "SUCCEEDS left\n"
         + "\n"
         + "IS A SET post\n"
         + "IS FALSE post\n"

http://git-wip-us.apache.org/repos/asf/calcite/blob/feff5964/core/src/test/resources/sql/misc.iq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/misc.iq 
b/core/src/test/resources/sql/misc.iq
index 956fa10..0ba3335 100644
--- a/core/src/test/resources/sql/misc.iq
+++ b/core/src/test/resources/sql/misc.iq
@@ -620,11 +620,41 @@ select count(*) as c from "everyTypes" where "utilDate" = 
TIMESTAMP '1970-01-01
 
 !ok
 
+# [CALCITE-715] Add PERIOD type constructor and period operators (CONTAINS, 
PRECEDES, etc.)
+select period("sqlDate", INTERVAL '1' YEAR) as p
+from "everyTypes";
++------------+
+| P          |
++------------+
+| {0, 12}    |
+| {null, 12} |
++------------+
+(2 rows)
+
+!ok
+
+!use foodmart
+
+select count(*) as c
+from "customer"
+where period ("birthdate", DATE '1970-02-05') contains DATE '1964-01-01';
+EnumerableAggregate(group=[{}], C=[COUNT()])
+  EnumerableCalc(expr#0..28=[{inputs}], expr#29=[1970-02-05], 
expr#30=[<=($t16, $t29)], expr#31=[CASE($t30, $t16, $t29)], 
expr#32=[1964-01-01], expr#33=[<=($t31, $t32)], expr#34=[CASE($t30, $t29, 
$t16)], expr#35=[>=($t34, $t32)], expr#36=[AND($t33, $t35)], 
proj#0..28=[{exprs}], $condition=[$t36])
+    EnumerableTableScan(table=[[foodmart2, customer]])
+!plan
++------+
+| C    |
++------+
+| 7918 |
++------+
+(1 row)
+
+!ok
+
 # [CALCITE-346] Add commutative join rule
 #
 # 3-way join that does not require bushy join.  Best plan is: sales_fact_1997 
as
 # left-most leaf, then customer (with filter), then product.
-!use foodmart
 select *
 from "sales_fact_1997" as s
   join "customer" as c using ("customer_id")

http://git-wip-us.apache.org/repos/asf/calcite/blob/feff5964/site/_docs/reference.md
----------------------------------------------------------------------
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index c942c14..5e64c70 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -22,6 +22,61 @@ limitations under the License.
 {% endcomment %}
 -->
 
+<style>
+.container {
+  width: 400px;
+  height: 26px;
+}
+.gray {
+  width: 60px;
+  height: 26px;
+  background: gray;
+  float: left;
+}
+.r15 {
+  width: 40px;
+  height: 6px;
+  background: yellow;
+  margin-top: 4px;
+  margin-left: 10px;
+}
+.r12 {
+  width: 10px;
+  height: 6px;
+  background: yellow;
+  margin-top: 4px;
+  margin-left: 10px;
+}
+.r13 {
+  width: 20px;
+  height: 6px;
+  background: yellow;
+  margin-top: 4px;
+  margin-left: 10px;
+}
+.r2 {
+  width: 2px;
+  height: 6px;
+  background: yellow;
+  margin-top: 4px;
+  margin-left: 20px;
+}
+.r24 {
+  width: 20px;
+  height: 6px;
+  background: yellow;
+  margin-top: 4px;
+  margin-left: 20px;
+}
+.r35 {
+  width: 20px;
+  height: 6px;
+  background: yellow;
+  margin-top: 4px;
+  margin-left: 30px;
+}
+</style>
+
 The page describes the SQL dialect recognized by Calcite's default SQL parser.
 
 ## Grammar
@@ -454,6 +509,7 @@ HIERARCHY,
 **HOUR**,
 **IDENTITY**,
 IMMEDIATE,
+IMMEDIATELY,
 IMPLEMENTATION,
 **IMPORT**,
 **IN**,
@@ -910,7 +966,6 @@ name will have been converted to upper case also.
 | TIMESTAMP [ WITHOUT TIME ZONE ] | Date and time | Example: TIMESTAMP 
'1969-07-20 20:17:40'
 | TIMESTAMP WITH TIME ZONE | Date and time with time zone | Example: TIMESTAMP 
'1969-07-20 20:17:40 America/Los Angeles'
 | INTERVAL timeUnit [ TO timeUnit ] | Date time interval | Examples: INTERVAL 
'1:5' YEAR TO MONTH, INTERVAL '45' DAY
-| Anchored interval | Date time interval  | Example: (DATE '1969-07-20', DATE 
'1972-08-29')
 
 Where:
 
@@ -949,7 +1004,7 @@ The operator precedence and associativity, highest to 
lowest.
 | + - (unary plus, minus)                           | right
 | * /                                               | left
 | + -                                               | left
-| BETWEEN, IN, LIKE, SIMILAR                        | -
+| BETWEEN, IN, LIKE, SIMILAR, OVERLAPS, CONTAINS etc. | -
 | < > = <= >= <> !=                                 | left
 | IS NULL, IS FALSE, IS NOT TRUE etc.               | -
 | NOT                                               | right
@@ -1151,6 +1206,102 @@ Not implemented:
 
 See also: UNNEST relational operator converts a collection to a relation.
 
+### Period predicates
+
+<table>
+  <tr>
+    <th>Operator syntax</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td>period1 CONTAINS dateTime</td>
+    <td>
+      <div class="container">
+        <div class="gray"><div class="r15"></div><div class="r2"></div></div>
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>period1 CONTAINS period2</td>
+    <td>
+      <div class="container">
+        <div class="gray"><div class="r15"></div><div class="r24"></div></div>
+        <div class="gray"><div class="r15"></div><div class="r13"></div></div>
+        <div class="gray"><div class="r15"></div><div class="r35"></div></div>
+        <div class="gray"><div class="r15"></div><div class="r15"></div></div>
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>period1 OVERLAPS period2</td>
+    <td>
+      <div class="container">
+        <div class="gray"><div class="r15"></div><div class="r24"></div></div>
+        <div class="gray"><div class="r15"></div><div class="r13"></div></div>
+        <div class="gray"><div class="r15"></div><div class="r35"></div></div>
+        <div class="gray"><div class="r15"></div><div class="r15"></div></div>
+        <div class="gray"><div class="r24"></div><div class="r15"></div></div>
+        <div class="gray"><div class="r13"></div><div class="r15"></div></div>
+        <div class="gray"><div class="r35"></div><div class="r15"></div></div>
+        <div class="gray"><div class="r24"></div><div class="r13"></div></div>
+        <div class="gray"><div class="r13"></div><div class="r24"></div></div>
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>period1 EQUALS period2</td>
+    <td>
+      <div class="container">
+        <div class="gray"><div class="r15"></div><div class="r15"></div></div>
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>period1 PRECEDES period2</td>
+    <td>
+      <div class="container">
+        <div class="gray"><div class="r12"></div><div class="r35"></div></div>
+        <div class="gray"><div class="r13"></div><div class="r35"></div></div>
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>period1 IMMEDIATELY PRECEDES period2</td>
+    <td>
+      <div class="container">
+        <div class="gray"><div class="r13"></div><div class="r35"></div></div>
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>period1 SUCCEEDS period2</td>
+    <td>
+      <div class="container">
+        <div class="gray"><div class="r35"></div><div class="r12"></div></div>
+        <div class="gray"><div class="r35"></div><div class="r13"></div></div>
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td>period1 IMMEDIATELY SUCCEEDS period2</td>
+    <td>
+      <div class="container">
+        <div class="gray"><div class="r35"></div><div class="r13"></div></div>
+      </div>
+    </td>
+  </tr>
+</table>
+
+Where *period1* and *period2* are period expressions:
+
+{% highlight sql %}
+period:
+      (dateTime, dateTime)
+  |   (dateTime, interval)
+  |   PERIOD (dateTime, dateTime)
+  |   PERIOD (dateTime, interval)
+{% endhighlight %}
+
 ### JDBC function escape
 
 #### Numeric

Reply via email to