Skip to content

Commit 34e65a8

Browse files
panbingkuncloud-fan
authored andcommitted
[SPARK-48990][SQL] Unified variable related SQL syntax keywords
### What changes were proposed in this pull request? The pr aims to unified `variable` related `SQL syntax` keywords, enable syntax `DECLARE (OR REPLACE)? ...` and `DROP TEMPORARY ...` to support keyword: `VAR` (not only `VARIABLE`). ### Why are the changes needed? When `setting` variables, we support `(VARIABLE | VAR)`, but when `declaring` and `dropping` variables, we only support the keyword `VARIABLE` (not support the keyword `VAR`) <img width="597" alt="image" src="https://github.com/user-attachments/assets/07084fef-4080-4410-a74c-e6001ae0a8fa"> https://github.com/apache/spark/blob/285489b0225004e918b6e937f7367e492292815e/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4#L68-L72 https://github.com/apache/spark/blob/285489b0225004e918b6e937f7367e492292815e/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4#L218-L220 The syntax seems `a bit weird`, `inconsistent experience` in SQL syntax related to variable usage by end-users, so I propose to `unify` it. ### Does this PR introduce _any_ user-facing change? Yes, enable end-users to use `variable related SQL` with `consistent` keywords. ### How was this patch tested? Updated existed UT. ### Was this patch authored or co-authored using generative AI tooling? No. Closes #47469 from panbingkun/SPARK-48990. Authored-by: panbingkun <panbingkun@baidu.com> Signed-off-by: Wenchen Fan <wenchen@databricks.com>
1 parent d68cde8 commit 34e65a8

File tree

8 files changed

+263
-14
lines changed

8 files changed

+263
-14
lines changed

docs/sql-ref-syntax-ddl-declare-variable.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ column default expressions, and generated column expressions.
3434
### Syntax
3535

3636
```sql
37-
DECLARE [ OR REPLACE ] [ VARIABLE ]
37+
DECLARE [ OR REPLACE ] [ VAR | VARIABLE ]
3838
variable_name [ data_type ] [ { DEFAULT | = } default_expr ]
3939
```
4040

docs/sql-ref-syntax-ddl-drop-variable.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ be thrown if the variable does not exist.
2727
### Syntax
2828

2929
```sql
30-
DROP TEMPORARY VARIABLE [ IF EXISTS ] variable_name
30+
DROP TEMPORARY { VAR | VARIABLE } [ IF EXISTS ] variable_name
3131
```
3232

3333
### Parameters

sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ compoundStatement
6666
;
6767

6868
setStatementWithOptionalVarKeyword
69-
: SET (VARIABLE | VAR)? assignmentList #setVariableWithOptionalKeyword
70-
| SET (VARIABLE | VAR)? LEFT_PAREN multipartIdentifierList RIGHT_PAREN EQ
69+
: SET variable? assignmentList #setVariableWithOptionalKeyword
70+
| SET variable? LEFT_PAREN multipartIdentifierList RIGHT_PAREN EQ
7171
LEFT_PAREN query RIGHT_PAREN #setVariableWithOptionalKeyword
7272
;
7373

@@ -215,9 +215,9 @@ statement
215215
routineCharacteristics
216216
RETURN (query | expression) #createUserDefinedFunction
217217
| DROP TEMPORARY? FUNCTION (IF EXISTS)? identifierReference #dropFunction
218-
| DECLARE (OR REPLACE)? VARIABLE?
218+
| DECLARE (OR REPLACE)? variable?
219219
identifierReference dataType? variableDefaultExpression? #createVariable
220-
| DROP TEMPORARY VARIABLE (IF EXISTS)? identifierReference #dropVariable
220+
| DROP TEMPORARY variable (IF EXISTS)? identifierReference #dropVariable
221221
| EXPLAIN (LOGICAL | FORMATTED | EXTENDED | CODEGEN | COST)?
222222
(statement|setResetStatement) #explain
223223
| SHOW TABLES ((FROM | IN) identifierReference)?
@@ -272,8 +272,8 @@ setResetStatement
272272
| SET TIME ZONE interval #setTimeZone
273273
| SET TIME ZONE timezone #setTimeZone
274274
| SET TIME ZONE .*? #setTimeZone
275-
| SET (VARIABLE | VAR) assignmentList #setVariable
276-
| SET (VARIABLE | VAR) LEFT_PAREN multipartIdentifierList RIGHT_PAREN EQ
275+
| SET variable assignmentList #setVariable
276+
| SET variable LEFT_PAREN multipartIdentifierList RIGHT_PAREN EQ
277277
LEFT_PAREN query RIGHT_PAREN #setVariable
278278
| SET configKey EQ configValue #setQuotedConfiguration
279279
| SET configKey (EQ .*?)? #setConfiguration
@@ -438,6 +438,11 @@ namespaces
438438
| SCHEMAS
439439
;
440440

441+
variable
442+
: VARIABLE
443+
| VAR
444+
;
445+
441446
describeFuncName
442447
: identifierReference
443448
| stringLit

sql/core/src/test/resources/sql-tests/analyzer-results/sql-session-variables.sql.out

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ SetVariable [variablereference(system.session.title='Create Variable - Failure C
150150

151151

152152
-- !query
153-
DECLARE VARIABLE var1 INT
153+
DECLARE VAR var1 INT
154154
-- !query analysis
155155
CreateVariable defaultvalueexpression(null, null), false
156156
+- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.var1
@@ -164,7 +164,7 @@ Project [variablereference(system.session.var1=CAST(NULL AS INT)) AS var1#x]
164164

165165

166166
-- !query
167-
DROP TEMPORARY VARIABLE var1
167+
DROP TEMPORARY VAR var1
168168
-- !query analysis
169169
DropVariable false
170170
+- ResolvedIdentifier org.apache.spark.sql.catalyst.analysis.FakeSystemCatalog$@xxxxxxxx, session.var1

sql/core/src/test/resources/sql-tests/inputs/sql-session-variables.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ DECLARE VARIABLE IF NOT EXISTS var1 INT;
2626
DROP TEMPORARY VARIABLE IF EXISTS var1;
2727

2828
SET VARIABLE title = 'Drop Variable';
29-
DECLARE VARIABLE var1 INT;
29+
DECLARE VAR var1 INT;
3030
SELECT var1;
31-
DROP TEMPORARY VARIABLE var1;
31+
DROP TEMPORARY VAR var1;
3232

3333
-- Variable is gone
3434
SELECT var1;

sql/core/src/test/resources/sql-tests/results/sql-session-variables.sql.out

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ struct<>
168168

169169

170170
-- !query
171-
DECLARE VARIABLE var1 INT
171+
DECLARE VAR var1 INT
172172
-- !query schema
173173
struct<>
174174
-- !query output
@@ -184,7 +184,7 @@ NULL
184184

185185

186186
-- !query
187-
DROP TEMPORARY VARIABLE var1
187+
DROP TEMPORARY VAR var1
188188
-- !query schema
189189
struct<>
190190
-- !query output
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.spark.sql.execution.command
19+
20+
import org.apache.spark.sql.catalyst.analysis.{AnalysisTest, UnresolvedAttribute, UnresolvedFunction, UnresolvedIdentifier, UnresolvedInlineTable}
21+
import org.apache.spark.sql.catalyst.expressions.{Add, Cast, Divide, Literal, ScalarSubquery}
22+
import org.apache.spark.sql.catalyst.parser.CatalystSqlParser.parsePlan
23+
import org.apache.spark.sql.catalyst.parser.ParseException
24+
import org.apache.spark.sql.catalyst.plans.logical.{CreateVariable, DefaultValueExpression, Project, SubqueryAlias}
25+
import org.apache.spark.sql.test.SharedSparkSession
26+
import org.apache.spark.sql.types.{Decimal, DecimalType, DoubleType, IntegerType, MapType, NullType, StringType}
27+
import org.apache.spark.unsafe.types.UTF8String
28+
29+
class DeclareVariableParserSuite extends AnalysisTest with SharedSparkSession {
30+
31+
test("declare variable") {
32+
comparePlans(
33+
parsePlan("DECLARE var1 INT = 1"),
34+
CreateVariable(
35+
UnresolvedIdentifier(Seq("var1")),
36+
DefaultValueExpression(Cast(Literal(1, IntegerType), IntegerType), "1"),
37+
replace = false))
38+
comparePlans(
39+
parsePlan("DECLARE var1 INT"),
40+
CreateVariable(
41+
UnresolvedIdentifier(Seq("var1")),
42+
DefaultValueExpression(Literal(null, IntegerType), "null"),
43+
replace = false))
44+
comparePlans(
45+
parsePlan("DECLARE var1 = 1"),
46+
CreateVariable(
47+
UnresolvedIdentifier(Seq("var1")),
48+
DefaultValueExpression(Literal(1, IntegerType), "1"),
49+
replace = false))
50+
comparePlans(
51+
parsePlan("DECLARE VARIABLE var1 = 1"),
52+
CreateVariable(
53+
UnresolvedIdentifier(Seq("var1")),
54+
DefaultValueExpression(Literal(1, IntegerType), "1"),
55+
replace = false))
56+
comparePlans(
57+
parsePlan("DECLARE VAR var1 = 1"),
58+
CreateVariable(
59+
UnresolvedIdentifier(Seq("var1")),
60+
DefaultValueExpression(Literal(1, IntegerType), "1"),
61+
replace = false))
62+
comparePlans(
63+
parsePlan("DECLARE VARIABLE var1 DEFAULT 1"),
64+
CreateVariable(
65+
UnresolvedIdentifier(Seq("var1")),
66+
DefaultValueExpression(Literal(1, IntegerType), "1"),
67+
replace = false))
68+
comparePlans(
69+
parsePlan("DECLARE VARIABLE var1 INT DEFAULT 1"),
70+
CreateVariable(
71+
UnresolvedIdentifier(Seq("var1")),
72+
DefaultValueExpression(Cast(Literal(1, IntegerType), IntegerType), "1"),
73+
replace = false))
74+
comparePlans(
75+
parsePlan("DECLARE VARIABLE system.session.var1 DEFAULT 1"),
76+
CreateVariable(
77+
UnresolvedIdentifier(Seq("system", "session", "var1")),
78+
DefaultValueExpression(Literal(1, IntegerType), "1"),
79+
replace = false))
80+
comparePlans(
81+
parsePlan("DECLARE VARIABLE session.var1 DEFAULT 1"),
82+
CreateVariable(
83+
UnresolvedIdentifier(Seq("session", "var1")),
84+
DefaultValueExpression(Literal(1, IntegerType), "1"),
85+
replace = false))
86+
comparePlans(
87+
parsePlan("DECLARE VARIABLE var1 STRING DEFAULT CURRENT_DATABASE()"),
88+
CreateVariable(
89+
UnresolvedIdentifier(Seq("var1")),
90+
DefaultValueExpression(
91+
Cast(UnresolvedFunction("CURRENT_DATABASE", Nil, isDistinct = false), StringType),
92+
"CURRENT_DATABASE()"),
93+
replace = false))
94+
comparePlans(
95+
parsePlan("DECLARE VARIABLE var1 INT DEFAULT (SELECT c1 FROM VALUES(1) AS T(c1))"),
96+
CreateVariable(
97+
UnresolvedIdentifier(Seq("var1")),
98+
DefaultValueExpression(
99+
Cast(ScalarSubquery(
100+
Project(UnresolvedAttribute("c1") :: Nil,
101+
SubqueryAlias(Seq("T"),
102+
UnresolvedInlineTable(Seq("c1"), Seq(Literal(1)) :: Nil)))), IntegerType),
103+
"(SELECT c1 FROM VALUES(1) AS T(c1))"),
104+
replace = false))
105+
}
106+
107+
test("declare or replace variable") {
108+
comparePlans(
109+
parsePlan("DECLARE OR REPLACE VARIABLE var1 = 1"),
110+
CreateVariable(
111+
UnresolvedIdentifier(Seq("var1")),
112+
DefaultValueExpression(Literal(1, IntegerType), "1"),
113+
replace = true))
114+
comparePlans(
115+
parsePlan("DECLARE OR REPLACE VARIABLE var1 DOUBLE DEFAULT 1 + RAND(5)"),
116+
CreateVariable(
117+
UnresolvedIdentifier(Seq("var1")),
118+
DefaultValueExpression(
119+
Cast(
120+
Add(Literal(1, IntegerType),
121+
UnresolvedFunction("RAND", Seq(Literal(5, IntegerType)), isDistinct = false)),
122+
DoubleType),
123+
"1 + RAND(5)"),
124+
replace = true))
125+
comparePlans(
126+
parsePlan("DECLARE OR REPLACE VARIABLE var1 DEFAULT NULL"),
127+
CreateVariable(
128+
UnresolvedIdentifier(Seq("var1")),
129+
DefaultValueExpression(Literal(null, NullType), "NULL"),
130+
replace = true))
131+
comparePlans(
132+
parsePlan("DECLARE OR REPLACE VARIABLE INT DEFAULT 5.0"),
133+
CreateVariable(
134+
UnresolvedIdentifier(Seq("INT")),
135+
DefaultValueExpression(Literal(Decimal("5.0"), DecimalType(2, 1)), "5.0"),
136+
replace = true))
137+
comparePlans(
138+
parsePlan("DECLARE OR REPLACE VARIABLE var1 MAP<string, double> " +
139+
"DEFAULT MAP('Hello', 5.1, 'World', -7.1E10)"),
140+
CreateVariable(
141+
UnresolvedIdentifier(Seq("var1")),
142+
DefaultValueExpression(Cast(
143+
UnresolvedFunction("MAP", Seq(
144+
Literal(UTF8String.fromString("Hello"), StringType),
145+
Literal(Decimal("5.1"), DecimalType(2, 1)),
146+
Literal(UTF8String.fromString("World"), StringType),
147+
Literal(-7.1E10, DoubleType)), isDistinct = false),
148+
MapType(StringType, DoubleType)),
149+
"MAP('Hello', 5.1, 'World', -7.1E10)"),
150+
replace = true))
151+
comparePlans(
152+
parsePlan("DECLARE OR REPLACE VARIABLE var1 INT DEFAULT NULL"),
153+
CreateVariable(
154+
UnresolvedIdentifier(Seq("var1")),
155+
DefaultValueExpression(Cast(Literal(null, NullType), IntegerType), "NULL"),
156+
replace = true))
157+
comparePlans(
158+
parsePlan("DECLARE OR REPLACE VARIABLE var1 INT DEFAULT 1 / 0"),
159+
CreateVariable(
160+
UnresolvedIdentifier(Seq("var1")),
161+
DefaultValueExpression(Cast(
162+
Divide(Literal(1, IntegerType), Literal(0, IntegerType)), IntegerType),
163+
"1 / 0"),
164+
replace = true))
165+
}
166+
167+
test("declare variable - not support syntax") {
168+
// IF NOT EXISTS
169+
checkError(
170+
exception = intercept[ParseException] {
171+
parsePlan("DECLARE VARIABLE IF NOT EXISTS var1 INT")
172+
},
173+
errorClass = "PARSE_SYNTAX_ERROR",
174+
parameters = Map("error" -> "'EXISTS'", "hint" -> "")
175+
)
176+
177+
// The datatype or default value of a variable must be specified
178+
val sqlText = "DECLARE VARIABLE var1"
179+
checkError(
180+
exception = intercept[ParseException] {
181+
parsePlan(sqlText)
182+
},
183+
errorClass = "INVALID_SQL_SYNTAX.VARIABLE_TYPE_OR_DEFAULT_REQUIRED",
184+
parameters = Map.empty,
185+
context = ExpectedContext(fragment = sqlText, start = 0, stop = 20)
186+
)
187+
}
188+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.spark.sql.execution.command
19+
20+
import org.apache.spark.sql.catalyst.analysis.{AnalysisTest, UnresolvedIdentifier}
21+
import org.apache.spark.sql.catalyst.parser.CatalystSqlParser.parsePlan
22+
import org.apache.spark.sql.catalyst.parser.ParseException
23+
import org.apache.spark.sql.catalyst.plans.logical.DropVariable
24+
import org.apache.spark.sql.test.SharedSparkSession
25+
26+
class DropVariableParserSuite extends AnalysisTest with SharedSparkSession {
27+
28+
test("drop variable") {
29+
comparePlans(
30+
parsePlan("DROP TEMPORARY VARIABLE var1"),
31+
DropVariable(UnresolvedIdentifier(Seq("var1")), ifExists = false))
32+
comparePlans(
33+
parsePlan("DROP TEMPORARY VAR var1"),
34+
DropVariable(UnresolvedIdentifier(Seq("var1")), ifExists = false))
35+
comparePlans(
36+
parsePlan("DROP TEMPORARY VARIABLE IF EXISTS var1"),
37+
DropVariable(UnresolvedIdentifier(Seq("var1")), ifExists = true))
38+
}
39+
40+
test("drop variable - not support syntax 'DROP VARIABLE|VAR'") {
41+
checkError(
42+
exception = intercept[ParseException] {
43+
parsePlan("DROP VARIABLE var1")
44+
},
45+
errorClass = "PARSE_SYNTAX_ERROR",
46+
parameters = Map("error" -> "'VARIABLE'", "hint" -> "")
47+
)
48+
checkError(
49+
exception = intercept[ParseException] {
50+
parsePlan("DROP VAR var1")
51+
},
52+
errorClass = "PARSE_SYNTAX_ERROR",
53+
parameters = Map("error" -> "'VAR'", "hint" -> "")
54+
)
55+
}
56+
}

0 commit comments

Comments
 (0)