Skip to content

Commit bdeb626

Browse files
yaooqinncloud-fan
authored andcommitted
[SPARK-32272][SQL] Add SQL standard command SET TIME ZONE
### What changes were proposed in this pull request? This PR adds the SQL standard command - `SET TIME ZONE` to the current default time zone displacement for the current SQL-session, which is the same as the existing `set spark.sql.session.timeZone=xxx'. All in all, this PR adds syntax as following, ``` SET TIME ZONE LOCAL; SET TIME ZONE 'valid time zone'; -- zone offset or region SET TIME ZONE INTERVAL XXXX; -- xxx must in [-18, + 18] hours, * this range is bigger than ansi [-14, + 14] ``` ### Why are the changes needed? ANSI compliance and supply pure SQL users a way to retrieve all supported TimeZones ### Does this PR introduce _any_ user-facing change? yes, add new syntax. ### How was this patch tested? add unit tests. and locally verified reference doc ![image](https://user-images.githubusercontent.com/8326978/87510244-c8dc3680-c6a5-11ea-954c-b098be84afee.png) Closes apache#29064 from yaooqinn/SPARK-32272. Authored-by: Kent Yao <[email protected]> Signed-off-by: Wenchen Fan <[email protected]>
1 parent db47c6e commit bdeb626

File tree

11 files changed

+308
-6
lines changed

11 files changed

+308
-6
lines changed

docs/_data/menu-sql.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@
249249
url: sql-ref-syntax-aux-conf-mgmt-set.html
250250
- text: RESET
251251
url: sql-ref-syntax-aux-conf-mgmt-reset.html
252+
- text: SET TIME ZONE
253+
url: sql-ref-syntax-aux-conf-mgmt-set-timezone.html
252254
- text: RESOURCE MANAGEMENT
253255
url: sql-ref-syntax-aux-resource-mgmt.html
254256
subitems:

docs/sql-ref-ansi-compliance.md

+2
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ Below is a list of all the keywords in Spark SQL.
355355
|TEMPORARY|non-reserved|non-reserved|non-reserved|
356356
|TERMINATED|non-reserved|non-reserved|non-reserved|
357357
|THEN|reserved|non-reserved|reserved|
358+
|TIME|reserved|non-reserved|reserved|
358359
|TO|reserved|non-reserved|reserved|
359360
|TOUCH|non-reserved|non-reserved|non-reserved|
360361
|TRAILING|reserved|non-reserved|reserved|
@@ -385,3 +386,4 @@ Below is a list of all the keywords in Spark SQL.
385386
|WINDOW|non-reserved|non-reserved|reserved|
386387
|WITH|reserved|non-reserved|reserved|
387388
|YEAR|reserved|non-reserved|reserved|
389+
|ZONE|non-reserved|non-reserved|non-reserved|
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
layout: global
3+
title: SET TIME ZONE
4+
displayTitle: SET TIME ZONE
5+
license: |
6+
Licensed to the Apache Software Foundation (ASF) under one or more
7+
contributor license agreements. See the NOTICE file distributed with
8+
this work for additional information regarding copyright ownership.
9+
The ASF licenses this file to You under the Apache License, Version 2.0
10+
(the "License"); you may not use this file except in compliance with
11+
the License. You may obtain a copy of the License at
12+
13+
http://www.apache.org/licenses/LICENSE-2.0
14+
15+
Unless required by applicable law or agreed to in writing, software
16+
distributed under the License is distributed on an "AS IS" BASIS,
17+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
See the License for the specific language governing permissions and
19+
limitations under the License.
20+
---
21+
22+
### Description
23+
24+
The SET TIME ZONE command sets the time zone of the current session.
25+
26+
### Syntax
27+
28+
```sql
29+
SET TIME ZONE LOCAL
30+
SET TIME ZONE 'timezone_value'
31+
SET TIME ZONE INTERVAL interval_literal
32+
```
33+
34+
### Parameters
35+
36+
* **LOCAL**
37+
38+
Set the time zone to the one specified in the java `user.timezone` property, or to the environment variable `TZ` if `user.timezone` is undefined, or to the system time zone if both of them are undefined.
39+
40+
* **timezone_value**
41+
42+
The ID of session local timezone in the format of either region-based zone IDs or zone offsets. Region IDs must have the form 'area/city', such as 'America/Los_Angeles'. Zone offsets must be in the format '`(+|-)HH`', '`(+|-)HH:mm`' or '`(+|-)HH:mm:ss`', e.g '-08', '+01:00' or '-13:33:33'. Also, 'UTC' and 'Z' are supported as aliases of '+00:00'. Other short names are not recommended to use because they can be ambiguous.
43+
44+
* **interval_literal**
45+
46+
The [interval literal](sql-ref-literals.html#interval-literal) represents the difference between the session time zone to the 'UTC'. It must be in the range of [-18, 18] hours and max to second precision, e.g. `INTERVAL 2 HOURS 30 MINITUES` or `INTERVAL '15:40:32' HOUR TO SECOND`.
47+
48+
### Examples
49+
50+
```sql
51+
-- Set time zone to the system default.
52+
SET TIME ZONE LOCAL;
53+
54+
-- Set time zone to the region-based zone ID.
55+
SET TIME ZONE 'America/Los_Angeles';
56+
57+
-- Set time zone to the Zone offset.
58+
SET TIME ZONE '+08:00';
59+
60+
-- Set time zone with intervals.
61+
SET TIME ZONE INTERVAL 1 HOUR 30 MINUTES;
62+
SET TIME ZONE INTERVAL '08:30:00' HOUR TO SECOND;
63+
```
64+
65+
### Related Statements
66+
67+
* [SET](sql-ref-syntax-aux-conf-mgmt-set.html)

docs/sql-ref-syntax-aux-conf-mgmt.md

+1
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ license: |
2121

2222
* [SET](sql-ref-syntax-aux-conf-mgmt-set.html)
2323
* [RESET](sql-ref-syntax-aux-conf-mgmt-reset.html)
24+
* [SET TIME ZONE](sql-ref-syntax-aux-conf-mgmt-set-timezone.html)

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

+8
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,9 @@ statement
240240
| MSCK REPAIR TABLE multipartIdentifier #repairTable
241241
| op=(ADD | LIST) identifier (STRING | .*?) #manageResource
242242
| SET ROLE .*? #failNativeCommand
243+
| SET TIME ZONE interval #setTimeZone
244+
| SET TIME ZONE timezone=(STRING | LOCAL) #setTimeZone
245+
| SET TIME ZONE .*? #setTimeZone
243246
| SET .*? #setConfiguration
244247
| RESET #resetConfiguration
245248
| unsupportedHiveNativeCommands .*? #failNativeCommand
@@ -1190,6 +1193,7 @@ ansiNonReserved
11901193
| VIEW
11911194
| VIEWS
11921195
| WINDOW
1196+
| ZONE
11931197
//--ANSI-NON-RESERVED-END
11941198
;
11951199

@@ -1431,6 +1435,7 @@ nonReserved
14311435
| TEMPORARY
14321436
| TERMINATED
14331437
| THEN
1438+
| TIME
14341439
| TO
14351440
| TOUCH
14361441
| TRAILING
@@ -1459,6 +1464,7 @@ nonReserved
14591464
| WINDOW
14601465
| WITH
14611466
| YEAR
1467+
| ZONE
14621468
;
14631469

14641470
// NOTE: If you add a new token in the list below, you should update the list of keywords
@@ -1691,6 +1697,7 @@ TBLPROPERTIES: 'TBLPROPERTIES';
16911697
TEMPORARY: 'TEMPORARY' | 'TEMP';
16921698
TERMINATED: 'TERMINATED';
16931699
THEN: 'THEN';
1700+
TIME: 'TIME';
16941701
TO: 'TO';
16951702
TOUCH: 'TOUCH';
16961703
TRAILING: 'TRAILING';
@@ -1721,6 +1728,7 @@ WHERE: 'WHERE';
17211728
WINDOW: 'WINDOW';
17221729
WITH: 'WITH';
17231730
YEAR: 'YEAR';
1731+
ZONE: 'ZONE';
17241732
//--SPARK-KEYWORD-LIST-END
17251733
//============================
17261734
// End of the keywords list

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala

+9-2
Original file line numberDiff line numberDiff line change
@@ -2090,14 +2090,21 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging
20902090
* - from-to unit, for instance: interval '1-2' year to month.
20912091
*/
20922092
override def visitInterval(ctx: IntervalContext): Literal = withOrigin(ctx) {
2093+
Literal(parseIntervalLiteral(ctx), CalendarIntervalType)
2094+
}
2095+
2096+
/**
2097+
* Create a [[CalendarInterval]] object
2098+
*/
2099+
protected def parseIntervalLiteral(ctx: IntervalContext): CalendarInterval = withOrigin(ctx) {
20932100
if (ctx.errorCapturingMultiUnitsInterval != null) {
20942101
val innerCtx = ctx.errorCapturingMultiUnitsInterval
20952102
if (innerCtx.unitToUnitInterval != null) {
20962103
throw new ParseException(
20972104
"Can only have a single from-to unit in the interval literal syntax",
20982105
innerCtx.unitToUnitInterval)
20992106
}
2100-
Literal(visitMultiUnitsInterval(innerCtx.multiUnitsInterval), CalendarIntervalType)
2107+
visitMultiUnitsInterval(innerCtx.multiUnitsInterval)
21012108
} else if (ctx.errorCapturingUnitToUnitInterval != null) {
21022109
val innerCtx = ctx.errorCapturingUnitToUnitInterval
21032110
if (innerCtx.error1 != null || innerCtx.error2 != null) {
@@ -2106,7 +2113,7 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging
21062113
"Can only have a single from-to unit in the interval literal syntax",
21072114
errorCtx)
21082115
}
2109-
Literal(visitUnitToUnitInterval(innerCtx.body), CalendarIntervalType)
2116+
visitUnitToUnitInterval(innerCtx.body)
21102117
} else {
21112118
throw new ParseException("at least one time unit should be given for interval literal", ctx)
21122119
}

sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -1723,9 +1723,9 @@ object SQLConf {
17231723
val SESSION_LOCAL_TIMEZONE = buildConf("spark.sql.session.timeZone")
17241724
.doc("The ID of session local timezone in the format of either region-based zone IDs or " +
17251725
"zone offsets. Region IDs must have the form 'area/city', such as 'America/Los_Angeles'. " +
1726-
"Zone offsets must be in the format '(+|-)HH:mm', for example '-08:00' or '+01:00'. " +
1727-
"Also 'UTC' and 'Z' are supported as aliases of '+00:00'. Other short names are not " +
1728-
"recommended to use because they can be ambiguous.")
1726+
"Zone offsets must be in the format '(+|-)HH', '(+|-)HH:mm' or '(+|-)HH:mm:ss', e.g '-08', " +
1727+
"'+01:00' or '-13:33:33'. Also 'UTC' and 'Z' are supported as aliases of '+00:00'. Other " +
1728+
"short names are not recommended to use because they can be ambiguous.")
17291729
.version("2.2.0")
17301730
.stringConf
17311731
.checkValue(isValidTimezone, s"Cannot resolve the given timezone with" +

sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala

+38-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717

1818
package org.apache.spark.sql.execution
1919

20-
import java.util.Locale
20+
import java.time.ZoneOffset
21+
import java.util.{Locale, TimeZone}
2122
import javax.ws.rs.core.UriBuilder
2223

2324
import scala.collection.JavaConverters._
@@ -32,6 +33,7 @@ import org.apache.spark.sql.catalyst.expressions.Expression
3233
import org.apache.spark.sql.catalyst.parser._
3334
import org.apache.spark.sql.catalyst.parser.SqlBaseParser._
3435
import org.apache.spark.sql.catalyst.plans.logical._
36+
import org.apache.spark.sql.catalyst.util.DateTimeConstants
3537
import org.apache.spark.sql.execution.command._
3638
import org.apache.spark.sql.execution.datasources._
3739
import org.apache.spark.sql.internal.{HiveSerDe, SQLConf, VariableSubstitution}
@@ -90,6 +92,41 @@ class SparkSqlAstBuilder(conf: SQLConf) extends AstBuilder(conf) {
9092
ResetCommand
9193
}
9294

95+
/**
96+
* Create a [[SetCommand]] logical plan to set [[SQLConf.SESSION_LOCAL_TIMEZONE]]
97+
* Example SQL :
98+
* {{{
99+
* SET TIME ZONE LOCAL;
100+
* SET TIME ZONE 'Asia/Shanghai';
101+
* SET TIME ZONE INTERVAL 10 HOURS;
102+
* }}}
103+
*/
104+
override def visitSetTimeZone(ctx: SetTimeZoneContext): LogicalPlan = withOrigin(ctx) {
105+
val key = SQLConf.SESSION_LOCAL_TIMEZONE.key
106+
if (ctx.interval != null) {
107+
val interval = parseIntervalLiteral(ctx.interval)
108+
if (interval.months != 0 || interval.days != 0 ||
109+
math.abs(interval.microseconds) > 18 * DateTimeConstants.MICROS_PER_HOUR ||
110+
interval.microseconds % DateTimeConstants.MICROS_PER_SECOND != 0) {
111+
throw new ParseException("The interval value must be in the range of [-18, +18] hours" +
112+
" with second precision",
113+
ctx.interval())
114+
} else {
115+
val seconds = (interval.microseconds / DateTimeConstants.MICROS_PER_SECOND).toInt
116+
SetCommand(Some(key -> Some(ZoneOffset.ofTotalSeconds(seconds).toString)))
117+
}
118+
} else if (ctx.timezone != null) {
119+
ctx.timezone.getType match {
120+
case SqlBaseParser.LOCAL =>
121+
SetCommand(Some(key -> Some(TimeZone.getDefault.getID)))
122+
case _ =>
123+
SetCommand(Some(key -> Some(string(ctx.STRING))))
124+
}
125+
} else {
126+
throw new ParseException("Invalid time zone displacement value", ctx)
127+
}
128+
}
129+
93130
/**
94131
* Create a [[RefreshResource]] logical plan.
95132
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- valid time zones
2+
SET TIME ZONE 'Asia/Hong_Kong';
3+
SET TIME ZONE 'GMT+1';
4+
SET TIME ZONE INTERVAL 10 HOURS;
5+
SET TIME ZONE INTERVAL '15:40:32' HOUR TO SECOND;
6+
SET TIME ZONE LOCAL;
7+
8+
-- invalid time zone
9+
SET TIME ZONE;
10+
SET TIME ZONE 'invalid/zone';
11+
SET TIME ZONE INTERVAL 3 DAYS;
12+
SET TIME ZONE INTERVAL 24 HOURS;
13+
SET TIME ZONE INTERVAL '19:40:32' HOUR TO SECOND;
14+
SET TIME ZONE INTERVAL 10 HOURS 'GMT+1';
15+
SET TIME ZONE INTERVAL 10 HOURS 1 MILLISECOND;

0 commit comments

Comments
 (0)