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]