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 2015/01/05 10:10:15 UTC

incubator-calcite git commit: [CALCITE-492] Support UPSERT statement in parser

Repository: incubator-calcite
Updated Branches:
  refs/heads/master 175d0705c -> 2727c5b33


[CALCITE-492] Support UPSERT statement in parser

Add ALTER SESSION/SYSTEM, INSERT, UPSERT, UPDATE, MERGE, DELETE, EXPLAIN to SQL reference.


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

Branch: refs/heads/master
Commit: 2727c5b33a40b9af193a5f7f86fc28227ebe62df
Parents: 175d070
Author: Julian Hyde <jh...@apache.org>
Authored: Mon Jan 5 00:46:27 2015 -0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Mon Jan 5 00:46:27 2015 -0800

----------------------------------------------------------------------
 core/src/main/codegen/templates/Parser.jj       | 18 ++++---
 .../java/org/apache/calcite/sql/SqlInsert.java  | 11 ++++-
 .../apache/calcite/sql/SqlInsertKeyword.java    | 17 +++++--
 .../calcite/sql/parser/SqlParserTest.java       | 27 +++++++++++
 doc/REFERENCE.md                                | 50 +++++++++++++++++++-
 5 files changed, 112 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2727c5b3/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 5f540e7..3f9a608 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -54,6 +54,7 @@ import org.apache.calcite.sql.SqlExplainLevel;
 import org.apache.calcite.sql.SqlFunctionCategory;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlInsert;
+import org.apache.calcite.sql.SqlInsertKeyword;
 import org.apache.calcite.sql.SqlIntervalLiteral;
 import org.apache.calcite.sql.SqlIntervalQualifier;
 import org.apache.calcite.sql.SqlJdbcFunctionCall;
@@ -274,7 +275,7 @@ SqlNode TableOverOpt() :
 /*
  * Parses dialect-specific keywords immediately following the SELECT keyword.
  */
-void SqlSelectKeywords(List<SqlNode> keywords) :
+void SqlSelectKeywords(List<SqlLiteral> keywords) :
 {}
 {
     E()
@@ -283,7 +284,7 @@ void SqlSelectKeywords(List<SqlNode> keywords) :
 /*
  * Parses dialect-specific keywords immediately following the INSERT keyword.
  */
-void SqlInsertKeywords(List keywords) :
+void SqlInsertKeywords(List<SqlLiteral> keywords) :
 {}
 {
     E()
@@ -923,7 +924,7 @@ SqlNode SqlStmtEof() :
  */
 SqlSelect SqlSelect() :
 {
-    List<SqlNode> keywords = new ArrayList<SqlNode>();
+    final List<SqlLiteral> keywords = Lists.newArrayList();
     List<SqlNode> selectList;
     SqlNode fromClause;
     SqlNode where;
@@ -1127,14 +1128,18 @@ SqlNode NamedRoutineCall(
  */
 SqlNode SqlInsert() :
 {
-    List keywords = new ArrayList();
+    final List<SqlLiteral> keywords = Lists.newArrayList();
     SqlIdentifier table;
     SqlNode source;
     SqlNodeList columnList = null;
     SqlParserPos pos;
 }
 {
-    <INSERT>
+    (
+        <INSERT>
+    |
+        <UPSERT> { keywords.add(SqlInsertKeyword.UPSERT.symbol(getPos())); }
+    )
     SqlInsertKeywords(keywords)
     <INTO> table = CompoundIdentifier()
     {
@@ -1333,7 +1338,7 @@ SqlUpdate WhenMatchedClause(SqlNode table, SqlIdentifier alias) :
 SqlInsert WhenNotMatchedClause(SqlNode table) :
 {
     SqlParserPos pos, insertPos;
-    List keywords = new ArrayList();
+    List<SqlLiteral> keywords = Lists.newArrayList();
     SqlNodeList insertColumnList = null;
     SqlNode rowConstructor;
     SqlNode insertValues;
@@ -5007,6 +5012,7 @@ SqlPostfixOperator PostfixRowOperator() :
     | < UNNEST: "UNNEST" >
     | < UPDATE: "UPDATE" >
     | < UPPER: "UPPER" >
+    | < UPSERT: "UPSERT" >
     | < USAGE: "USAGE" >
     | < USER: "USER" >
     | < USER_DEFINED_TYPE_CATALOG: "USER_DEFINED_TYPE_CATALOG" >

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2727c5b3/core/src/main/java/org/apache/calcite/sql/SqlInsert.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlInsert.java b/core/src/main/java/org/apache/calcite/sql/SqlInsert.java
index 43861a9..7b58c8f 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlInsert.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlInsert.java
@@ -65,6 +65,15 @@ public class SqlInsert extends SqlCall {
     return ImmutableNullableList.of(keywords, targetTable, source, columnList);
   }
 
+  /** Returns whether this is an UPSERT statement.
+   *
+   * <p>In SQL, this is represented using the {@code UPSERT} keyword rather than
+   * {@code INSERT}; in the abstract syntax tree, an UPSERT is indicated by the
+   * presence of a {@link SqlInsertKeyword#UPSERT} keyword. */
+  public final boolean isUpsert() {
+    return getModifierNode(SqlInsertKeyword.UPSERT) != null;
+  }
+
   @Override public void setOperand(int i, SqlNode operand) {
     switch (i) {
     case 0:
@@ -122,7 +131,7 @@ public class SqlInsert extends SqlCall {
 
   @Override public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
     writer.startList(SqlWriter.FrameTypeEnum.SELECT);
-    writer.sep("INSERT INTO");
+    writer.sep(isUpsert() ? "UPSERT INTO" : "INSERT INTO");
     final int opLeft = getOperator().getLeftPrec();
     final int opRight = getOperator().getRightPrec();
     targetTable.unparse(writer, opLeft, opRight);

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2727c5b3/core/src/main/java/org/apache/calcite/sql/SqlInsertKeyword.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlInsertKeyword.java b/core/src/main/java/org/apache/calcite/sql/SqlInsertKeyword.java
index 8e93954..e4704c7 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlInsertKeyword.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlInsertKeyword.java
@@ -16,12 +16,23 @@
  */
 package org.apache.calcite.sql;
 
+import org.apache.calcite.sql.parser.SqlParserPos;
+
 /**
- * Defines the keywords which can occur immediately after the "INSERT" keyword.
- * Standard SQL has no such keywords. This enumeration exists only to allow
- * extension projects to define them.
+ * Defines the keywords that can occur immediately after the "INSERT" keyword.
+ *
+ * <p>Standard SQL has no such keywords, but extension projects may define them.
  */
 public enum SqlInsertKeyword implements SqlLiteral.SqlSymbol {
+  UPSERT;
+
+  /**
+   * Creates a parse-tree node representing an occurrence of this keyword
+   * at a particular position in the parsed text.
+   */
+  public SqlLiteral symbol(SqlParserPos pos) {
+    return SqlLiteral.createSymbol(this, pos);
+  }
 }
 
 // End SqlInsertKeyword.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2727c5b3/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 adcaf93..1a66182 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
@@ -2111,6 +2111,26 @@ public class SqlParserTest {
             + "FROM `EMPS2`)");
   }
 
+  @Test public void testUpsertValues() {
+    sql("upsert into emps values (1,'Fredkin')")
+        .ok("UPSERT INTO `EMPS`\n"
+                + "(VALUES (ROW(1, 'Fredkin')))");
+  }
+
+  @Test public void testUpsertSelect() {
+    sql("upsert into emps select * from emp as e")
+        .ok("UPSERT INTO `EMPS`\n"
+                + "(SELECT *\n"
+                + "FROM `EMP` AS `E`)");
+  }
+
+  @Test public void testExplainUpsert() {
+    sql("explain plan for upsert into emps1 values (1, 2)")
+        .ok("EXPLAIN PLAN INCLUDING ATTRIBUTES WITH IMPLEMENTATION FOR\n"
+            + "UPSERT INTO `EMPS1`\n"
+            + "(VALUES (ROW(1, 2)))");
+  }
+
   @Test public void testDelete() {
     check("delete from emps", "DELETE FROM `EMPS`");
   }
@@ -2122,6 +2142,13 @@ public class SqlParserTest {
             + "WHERE (`EMPNO` = 12)");
   }
 
+  @Test public void testUpdate() {
+    sql("update emps set empno = empno + 1, sal = sal - 1 where empno=12")
+        .ok("UPDATE `EMPS` (`EMPNO`, `SAL`) SET `EMPNO` = (`EMPNO` + 1)\n"
+                + ", `SAL` = (`SAL` - 1)\n"
+                + "WHERE (`EMPNO` = 12)");
+  }
+
   @Test public void testMergeSelectSource() {
     check(
         "merge into emps e "

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/2727c5b3/doc/REFERENCE.md
----------------------------------------------------------------------
diff --git a/doc/REFERENCE.md b/doc/REFERENCE.md
index d21ab24..412bec6 100644
--- a/doc/REFERENCE.md
+++ b/doc/REFERENCE.md
@@ -3,6 +3,48 @@
 ## SQL constructs
 
 ```SQL
+statement:
+      setStatement
+  |   explain
+  |   insert
+  |   update
+  |   merge
+  |   delete
+  |   query
+
+setStatement:
+      ALTER ( SYSTEM | SESSION ) SET identifier = expression
+
+explain:
+      EXPLAIN PLAN
+      [ WITH TYPE | WITH IMPLEMENTATION | WITHOUT IMPLEMENTATION ]
+      [ EXCLUDING ATTRIBUTES | INCLUDING [ ALL ] ATTRIBUTES ]
+      FOR ( insert | update | merge | delete | query )
+
+insert:
+      ( INSERT | UPSERT ) INTO tablePrimary
+      [ '(' column [, column ]* ')' ]
+      query
+
+update:
+      UPDATE tablePrimary
+      SET assign [, assign ]*
+      [ WHERE booleanExpression ]
+
+assign:
+      identifier '=' expression
+
+merge:
+      MERGE INTO tablePrimary [ [ AS ] alias ]
+      USING tablePrimary
+      ON booleanExpression
+      [ WHEN MATCHED THEN UPDATE SET assign [, assign ]* ]
+      [ WHEN NOT MATCHED THEN INSERT VALUES '(' value [ , value ]* ')' ]
+
+delete:
+      DELETE FROM tablePrimary [ [ AS ] alias ]
+      [ WHERE booleanExpression ]
+
 query:
       [ WITH withItem [ , withItem ]* query ]
   |   {
@@ -53,10 +95,13 @@ tableReference:
 tablePrimary:
       [ TABLE ] [ [ catalogName . ] schemaName . ] tableName
   |   '(' query ')'
-  |   VALUES expression [, expression ]*
+  |   values
   |   UNNEST '(' expression ')'
   |   '(' TABLE expression ')'
 
+values:
+      VALUES expression [, expression ]*
+
 groupItem:
       expression
   |   '(' ')'
@@ -81,6 +126,9 @@ windowSpec:
       ')'
 ```
 
+In *merge*, at least one of the WHEN MATCHED and WHEN NOT MATCHED clauses must
+be present.
+
 In *orderItem*, if *expression* is a positive integer *n*, it denotes
 the <em>n</em>th item in the SELECT clause.