This is an automated email from the ASF dual-hosted git repository.

maxgekk pushed a commit to branch branch-4.0
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.0 by this push:
     new 2c523d33aa7b [SPARK-50663][SQL] Fix grammar for IF ELSE statement 
(ELSE IF -> ELSEIF)
2c523d33aa7b is described below

commit 2c523d33aa7bbf77fb99ebd7b87df0886f988809
Author: Milan Dankovic <[email protected]>
AuthorDate: Wed Jan 29 18:07:03 2025 +0100

    [SPARK-50663][SQL] Fix grammar for IF ELSE statement (ELSE IF -> ELSEIF)
    
    ### What changes were proposed in this pull request?
    In this PR we are fixing grammar for `else if` block. Instead of current 
`ELSE IF` parser rule, new rule is `ELSEIF` according to SQL Standard and [Ref 
Spec](https://docs.google.com/document/d/1uFv2VoqDoOH2k6HfgdkBYxp-Ou7Qi721eexNW6IYWwk/edit?tab=t.0#heading=h.4cz970y1mk93)
    
    ### Why are the changes needed?
    This changes are needed to follow grammar of the SQL Standard.
    
    ### Does this PR introduce _any_ user-facing change?
    No.
    
    ### How was this patch tested?
    Updated tests containing `IF ELSE` statement and all other existing tests 
ensuring things are working as before.
    New parser test to test behavior of `ELSEIF` and nested `IF` statements 
inside `ELSE` block.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    No.
    
    Closes #49694 from miland-db/milan-dankovic_data/fix-scripting-elseif.
    
    Authored-by: Milan Dankovic <[email protected]>
    Signed-off-by: Max Gekk <[email protected]>
    (cherry picked from commit 5b796c396625af46559ed6ec80b13e03638adb0b)
    Signed-off-by: Max Gekk <[email protected]>
---
 docs/sql-ref-ansi-compliance.md                    |  1 +
 .../spark/sql/catalyst/parser/SqlBaseLexer.g4      |  1 +
 .../spark/sql/catalyst/parser/SqlBaseParser.g4     |  4 +-
 .../plans/logical/SqlScriptingLogicalPlans.scala   |  4 +-
 .../catalyst/parser/SqlScriptingParserSuite.scala  | 91 ++++++++++++++++++++--
 .../sql/scripting/SqlScriptingExecutionNode.scala  |  6 +-
 .../sql-tests/results/keywords-enforced.sql.out    |  1 +
 .../resources/sql-tests/results/keywords.sql.out   |  1 +
 .../sql-tests/results/nonansi/keywords.sql.out     |  1 +
 .../sql/scripting/SqlScriptingExecutionSuite.scala | 12 +--
 .../scripting/SqlScriptingInterpreterSuite.scala   | 12 +--
 .../ThriftServerWithSparkContextSuite.scala        |  2 +-
 12 files changed, 112 insertions(+), 24 deletions(-)

diff --git a/docs/sql-ref-ansi-compliance.md b/docs/sql-ref-ansi-compliance.md
index 45b8e9a4dcea..7c7e2a690957 100644
--- a/docs/sql-ref-ansi-compliance.md
+++ b/docs/sql-ref-ansi-compliance.md
@@ -503,6 +503,7 @@ Below is a list of all the keywords in Spark SQL.
 |DOUBLE|non-reserved|non-reserved|reserved|
 |DROP|non-reserved|non-reserved|reserved|
 |ELSE|reserved|non-reserved|reserved|
+|ELSEIF|non-reserved|non-reserved|non-reserved|
 |END|reserved|non-reserved|reserved|
 |ESCAPE|reserved|non-reserved|reserved|
 |ESCAPED|non-reserved|non-reserved|non-reserved|
diff --git 
a/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseLexer.g4 
b/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseLexer.g4
index dafeed48aef1..360854d81e38 100644
--- 
a/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseLexer.g4
+++ 
b/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseLexer.g4
@@ -218,6 +218,7 @@ DO: 'DO';
 DOUBLE: 'DOUBLE';
 DROP: 'DROP';
 ELSE: 'ELSE';
+ELSEIF: 'ELSEIF';
 END: 'END';
 ESCAPE: 'ESCAPE';
 ESCAPED: 'ESCAPED';
diff --git 
a/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4 
b/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4
index 8e6edac2108a..9b438a667a3e 100644
--- 
a/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4
+++ 
b/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4
@@ -85,7 +85,7 @@ whileStatement
 
 ifElseStatement
     : IF booleanExpression THEN conditionalBodies+=compoundBody
-        (ELSE IF booleanExpression THEN conditionalBodies+=compoundBody)*
+        (ELSEIF booleanExpression THEN conditionalBodies+=compoundBody)*
         (ELSE elseBody=compoundBody)? END IF
     ;
 
@@ -1642,6 +1642,7 @@ ansiNonReserved
     | DO
     | DOUBLE
     | DROP
+    | ELSEIF
     | ESCAPED
     | EVOLUTION
     | EXCHANGE
@@ -1987,6 +1988,7 @@ nonReserved
     | DOUBLE
     | DROP
     | ELSE
+    | ELSEIF
     | END
     | ESCAPE
     | ESCAPED
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/SqlScriptingLogicalPlans.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/SqlScriptingLogicalPlans.scala
index ad00a5216b4c..b3bd86149ba9 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/SqlScriptingLogicalPlans.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/SqlScriptingLogicalPlans.scala
@@ -81,9 +81,9 @@ case class CompoundBody(
 /**
  * Logical operator for IF ELSE statement.
  * @param conditions Collection of conditions. First condition corresponds to 
IF clause,
- *                   while others (if any) correspond to following ELSE IF 
clauses.
+ *                   while others (if any) correspond to following ELSEIF 
clauses.
  * @param conditionalBodies Collection of bodies that have a corresponding 
condition,
- *                          in IF or ELSE IF branches.
+ *                          in IF or ELSEIF branches.
  * @param elseBody Body that is executed if none of the conditions are met,
  *                          i.e. ELSE branch.
  */
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/SqlScriptingParserSuite.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/SqlScriptingParserSuite.scala
index e129c6dbba05..40ba7809e5ce 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/SqlScriptingParserSuite.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/SqlScriptingParserSuite.scala
@@ -501,12 +501,12 @@ class SqlScriptingParserSuite extends SparkFunSuite with 
SQLHelper {
       .getText == "SELECT 2")
   }
 
-  test("if else if") {
+  test("if elseif") {
     val sqlScriptText =
       """BEGIN
         |IF 1 = 1 THEN
         |  SELECT 1;
-        |ELSE IF 2 = 2 THEN
+        |ELSEIF 2 = 2 THEN
         |  SELECT 2;
         |ELSE
         |  SELECT 3;
@@ -541,14 +541,14 @@ class SqlScriptingParserSuite extends SparkFunSuite with 
SQLHelper {
       .getText == "SELECT 3")
   }
 
-  test("if multi else if") {
+  test("if multi elseif") {
     val sqlScriptText =
       """BEGIN
         |IF 1 = 1 THEN
         |  SELECT 1;
-        |ELSE IF 2 = 2 THEN
+        |ELSEIF 2 = 2 THEN
         |  SELECT 2;
-        |ELSE IF 3 = 3 THEN
+        |ELSEIF 3 = 3 THEN
         |  SELECT 3;
         |END IF;
         |END
@@ -584,6 +584,87 @@ class SqlScriptingParserSuite extends SparkFunSuite with 
SQLHelper {
       .getText == "SELECT 3")
   }
 
+  test("if - multi elseif - else nested") {
+    val sqlScriptText =
+      """BEGIN
+        |IF 1 = 1 THEN
+        |  SELECT 1;
+        |ELSEIF 2 = 2 THEN
+        |  SELECT 2;
+        |ELSE
+        |  IF 3 = 3 THEN
+        |    SELECT 3;
+        |  ELSEIF 4 = 4 THEN
+        |    SELECT 4;
+        |  ELSE
+        |    IF 5 = 5 THEN
+        |      SELECT 5;
+        |    ELSE
+        |      SELECT 6;
+        |    END IF;
+        |  END IF;
+        |END IF;
+        |END
+      """.stripMargin
+    val tree = parsePlan(sqlScriptText).asInstanceOf[CompoundBody]
+    assert(tree.collection.length == 1)
+    assert(tree.collection.head.isInstanceOf[IfElseStatement])
+
+    val ifStmt = tree.collection.head.asInstanceOf[IfElseStatement]
+    assert(ifStmt.conditions.length == 2)
+    assert(ifStmt.conditionalBodies.length == 2)
+    assert(ifStmt.elseBody.nonEmpty)
+
+    assert(ifStmt.conditions.head.isInstanceOf[SingleStatement])
+    assert(ifStmt.conditions.head.getText == "1 = 1")
+
+    
assert(ifStmt.conditionalBodies.head.collection.head.isInstanceOf[SingleStatement])
+    
assert(ifStmt.conditionalBodies.head.collection.head.asInstanceOf[SingleStatement]
+      .getText == "SELECT 1")
+
+    assert(ifStmt.conditions(1).isInstanceOf[SingleStatement])
+    assert(ifStmt.conditions(1).getText == "2 = 2")
+
+    
assert(ifStmt.conditionalBodies(1).collection.head.isInstanceOf[SingleStatement])
+    
assert(ifStmt.conditionalBodies(1).collection.head.asInstanceOf[SingleStatement]
+      .getText == "SELECT 2")
+
+    assert(ifStmt.elseBody.get.collection.head.isInstanceOf[IfElseStatement])
+    val nestedIf_1 = 
ifStmt.elseBody.get.collection.head.asInstanceOf[IfElseStatement]
+
+    assert(nestedIf_1.conditions.length == 2)
+    assert(nestedIf_1.conditionalBodies.length == 2)
+    assert(nestedIf_1.elseBody.nonEmpty)
+
+
+    assert(nestedIf_1.conditions.head.isInstanceOf[SingleStatement])
+    assert(nestedIf_1.conditions.head.getText == "3 = 3")
+
+    
assert(nestedIf_1.conditionalBodies.head.collection.head.isInstanceOf[SingleStatement])
+    
assert(nestedIf_1.conditionalBodies.head.collection.head.asInstanceOf[SingleStatement]
+      .getText == "SELECT 3")
+
+    assert(nestedIf_1.conditions(1).isInstanceOf[SingleStatement])
+    assert(nestedIf_1.conditions(1).getText == "4 = 4")
+
+    
assert(nestedIf_1.conditionalBodies(1).collection.head.isInstanceOf[SingleStatement])
+    
assert(nestedIf_1.conditionalBodies(1).collection.head.asInstanceOf[SingleStatement]
+      .getText == "SELECT 4")
+
+    
assert(nestedIf_1.elseBody.get.collection.head.isInstanceOf[IfElseStatement])
+    val nestedIf_2 = 
nestedIf_1.elseBody.get.collection.head.asInstanceOf[IfElseStatement]
+
+    assert(nestedIf_2.conditions.length == 1)
+    assert(nestedIf_2.conditionalBodies.length == 1)
+    assert(nestedIf_2.elseBody.nonEmpty)
+
+    
assert(nestedIf_2.conditionalBodies.head.collection.head.asInstanceOf[SingleStatement]
+      .getText == "SELECT 5")
+
+    
assert(nestedIf_2.elseBody.get.collection.head.asInstanceOf[SingleStatement]
+      .getText == "SELECT 6")
+  }
+
   test("if nested") {
     val sqlScriptText =
       """
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/scripting/SqlScriptingExecutionNode.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/scripting/SqlScriptingExecutionNode.scala
index d11bf14be654..e153d38a00cf 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/scripting/SqlScriptingExecutionNode.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/scripting/SqlScriptingExecutionNode.scala
@@ -344,9 +344,9 @@ class CompoundBodyExec(
 /**
  * Executable node for IfElseStatement.
  * @param conditions Collection of executable conditions. First condition 
corresponds to IF clause,
- *                   while others (if any) correspond to following ELSE IF 
clauses.
+ *                   while others (if any) correspond to following ELSEIF 
clauses.
  * @param conditionalBodies Collection of executable bodies that have a 
corresponding condition,
-*                 in IF or ELSE IF branches.
+*                 in IF or ELSEIF branches.
  * @param elseBody Body that is executed if none of the conditions are met,
  *                          i.e. ELSE branch.
  * @param session Spark session that SQL script is executed within.
@@ -380,7 +380,7 @@ class IfElseStatementExec(
           } else {
             clauseIdx += 1
             if (clauseIdx < conditionsCount) {
-              // There are ELSE IF clauses remaining.
+              // There are ELSEIF clauses remaining.
               state = IfElseState.Condition
               curr = Some(conditions(clauseIdx))
             } else if (elseBody.isDefined) {
diff --git 
a/sql/core/src/test/resources/sql-tests/results/keywords-enforced.sql.out 
b/sql/core/src/test/resources/sql-tests/results/keywords-enforced.sql.out
index 521b0afe1926..84a35c270b69 100644
--- a/sql/core/src/test/resources/sql-tests/results/keywords-enforced.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/keywords-enforced.sql.out
@@ -104,6 +104,7 @@ DO  false
 DOUBLE false
 DROP   false
 ELSE   true
+ELSEIF false
 END    true
 ESCAPE true
 ESCAPED        false
diff --git a/sql/core/src/test/resources/sql-tests/results/keywords.sql.out 
b/sql/core/src/test/resources/sql-tests/results/keywords.sql.out
index 4d702588ad2b..49ea8bba3e17 100644
--- a/sql/core/src/test/resources/sql-tests/results/keywords.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/keywords.sql.out
@@ -104,6 +104,7 @@ DO  false
 DOUBLE false
 DROP   false
 ELSE   false
+ELSEIF false
 END    false
 ESCAPE false
 ESCAPED        false
diff --git 
a/sql/core/src/test/resources/sql-tests/results/nonansi/keywords.sql.out 
b/sql/core/src/test/resources/sql-tests/results/nonansi/keywords.sql.out
index 4d702588ad2b..49ea8bba3e17 100644
--- a/sql/core/src/test/resources/sql-tests/results/nonansi/keywords.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/nonansi/keywords.sql.out
@@ -104,6 +104,7 @@ DO  false
 DOUBLE false
 DROP   false
 ELSE   false
+ELSEIF false
 END    false
 ESCAPE false
 ESCAPED        false
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/scripting/SqlScriptingExecutionSuite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/scripting/SqlScriptingExecutionSuite.scala
index 5b5285ea1327..503d38d61c7a 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/scripting/SqlScriptingExecutionSuite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/scripting/SqlScriptingExecutionSuite.scala
@@ -218,14 +218,14 @@ class SqlScriptingExecutionSuite extends QueryTest with 
SharedSparkSession {
     verifySqlScriptResult(commands, expected)
   }
 
-  test("if else if going in else if") {
+  test("if elseif going in elseif") {
     val commands =
       """
         |BEGIN
         |  IF 1=2
         |  THEN
         |    SELECT 42;
-        |  ELSE IF 1=1
+        |  ELSEIF 1=1
         |  THEN
         |    SELECT 43;
         |  ELSE
@@ -253,14 +253,14 @@ class SqlScriptingExecutionSuite extends QueryTest with 
SharedSparkSession {
     verifySqlScriptResult(commands, expected)
   }
 
-  test("if else if going in else") {
+  test("if elseif going in else") {
     val commands =
       """
         |BEGIN
         |  IF 1=2
         |  THEN
         |    SELECT 42;
-        |  ELSE IF 1=3
+        |  ELSEIF 1=3
         |  THEN
         |    SELECT 43;
         |  ELSE
@@ -292,7 +292,7 @@ class SqlScriptingExecutionSuite extends QueryTest with 
SharedSparkSession {
     }
   }
 
-  test("if else if with count") {
+  test("if elseif with count") {
     withTable("t") {
       val commands =
         """
@@ -302,7 +302,7 @@ class SqlScriptingExecutionSuite extends QueryTest with 
SharedSparkSession {
           |  INSERT INTO t VALUES (1, 'a', 1.0);
           |  IF (SELECT COUNT(*) > 2 FROM t) THEN
           |    SELECT 42;
-          |  ELSE IF (SELECT COUNT(*) > 1 FROM t) THEN
+          |  ELSEIF (SELECT COUNT(*) > 1 FROM t) THEN
           |    SELECT 43;
           |  ELSE
           |    SELECT 44;
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/scripting/SqlScriptingInterpreterSuite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/scripting/SqlScriptingInterpreterSuite.scala
index 601548a2e6bd..5f149d15c6e8 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/scripting/SqlScriptingInterpreterSuite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/scripting/SqlScriptingInterpreterSuite.scala
@@ -370,14 +370,14 @@ class SqlScriptingInterpreterSuite extends QueryTest with 
SharedSparkSession {
     verifySqlScriptResult(commands, expected)
   }
 
-  test("if else if going in else if") {
+  test("if elseif going in elseif") {
     val commands =
       """
         |BEGIN
         |  IF 1=2
         |  THEN
         |    SELECT 42;
-        |  ELSE IF 1=1
+        |  ELSEIF 1=1
         |  THEN
         |    SELECT 43;
         |  ELSE
@@ -407,14 +407,14 @@ class SqlScriptingInterpreterSuite extends QueryTest with 
SharedSparkSession {
     verifySqlScriptResult(commands, expected)
   }
 
-  test("if else if going in else") {
+  test("if elseif going in else") {
     val commands =
       """
         |BEGIN
         |  IF 1=2
         |  THEN
         |    SELECT 42;
-        |  ELSE IF 1=3
+        |  ELSEIF 1=3
         |  THEN
         |    SELECT 43;
         |  ELSE
@@ -448,7 +448,7 @@ class SqlScriptingInterpreterSuite extends QueryTest with 
SharedSparkSession {
     }
   }
 
-  test("if else if with count") {
+  test("if elseif with count") {
     withTable("t") {
       val commands =
         """
@@ -458,7 +458,7 @@ class SqlScriptingInterpreterSuite extends QueryTest with 
SharedSparkSession {
           |  INSERT INTO t VALUES (1, 'a', 1.0);
           |  IF (SELECT COUNT(*) > 2 FROM t) THEN
           |    SELECT 42;
-          |  ELSE IF (SELECT COUNT(*) > 1 FROM t) THEN
+          |  ELSEIF (SELECT COUNT(*) > 1 FROM t) THEN
           |    SELECT 43;
           |  ELSE
           |    SELECT 44;
diff --git 
a/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftServerWithSparkContextSuite.scala
 
b/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftServerWithSparkContextSuite.scala
index 254eda69e86e..ec65886fb2c9 100644
--- 
a/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftServerWithSparkContextSuite.scala
+++ 
b/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftServerWithSparkContextSuite.scala
@@ -214,7 +214,7 @@ trait ThriftServerWithSparkContextSuite extends 
SharedThriftServer {
       val sessionHandle = client.openSession(user, "")
       val infoValue = client.getInfo(sessionHandle, 
GetInfoType.CLI_ODBC_KEYWORDS)
       // scalastyle:off line.size.limit
-      assert(infoValue.getStringValue == 
"ADD,AFTER,AGGREGATE,ALL,ALTER,ALWAYS,ANALYZE,AND,ANTI,ANY,ANY_VALUE,ARCHIVE,ARRAY,AS,ASC,AT,AUTHORIZATION,BEGIN,BETWEEN,BIGINT,BINARY,BINDING,BOOLEAN,BOTH,BUCKET,BUCKETS,BY,BYTE,CACHE,CALL,CALLED,CASCADE,CASE,CAST,CATALOG,CATALOGS,CHANGE,CHAR,CHARACTER,CHECK,CLEAR,CLUSTER,CLUSTERED,CODEGEN,COLLATE,COLLATION,COLLECTION,COLUMN,COLUMNS,COMMENT,COMMIT,COMPACT,COMPACTIONS,COMPENSATION,COMPUTE,CONCATENATE,CONSTRAINT,CONTAINS,COST,CREATE,CROSS,CUBE,CURR
 [...]
+      assert(infoValue.getStringValue == 
"ADD,AFTER,AGGREGATE,ALL,ALTER,ALWAYS,ANALYZE,AND,ANTI,ANY,ANY_VALUE,ARCHIVE,ARRAY,AS,ASC,AT,AUTHORIZATION,BEGIN,BETWEEN,BIGINT,BINARY,BINDING,BOOLEAN,BOTH,BUCKET,BUCKETS,BY,BYTE,CACHE,CALL,CALLED,CASCADE,CASE,CAST,CATALOG,CATALOGS,CHANGE,CHAR,CHARACTER,CHECK,CLEAR,CLUSTER,CLUSTERED,CODEGEN,COLLATE,COLLATION,COLLECTION,COLUMN,COLUMNS,COMMENT,COMMIT,COMPACT,COMPACTIONS,COMPENSATION,COMPUTE,CONCATENATE,CONSTRAINT,CONTAINS,COST,CREATE,CROSS,CUBE,CURR
 [...]
       // scalastyle:on line.size.limit
     }
   }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to