Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,12 @@ tableIdentifier
;

functionIdentifier
: (db=identifier '.')? function=identifier
: (db=identifier '.')? function=functionName
;

functionName
: identifier
| {ansi}? (CURRENT_DATE | CURRENT_TIMESTAMP)
;

namedExpression
Expand Down Expand Up @@ -616,9 +621,9 @@ primaryExpression
| qualifiedName '.' ASTERISK #star
| '(' namedExpression (',' namedExpression)+ ')' #rowConstructor
| '(' query ')' #subqueryExpression
| qualifiedName '(' (setQuantifier? argument+=expression (',' argument+=expression)*)? ')'
(OVER windowSpec)? #functionCall
| qualifiedName '(' trimOption=(BOTH | LEADING | TRAILING) argument+=expression
| functionIdentifier '(' (setQuantifier? argument+=expression
(',' argument+=expression)*)? ')' (OVER windowSpec)? #functionCall
| functionIdentifier '(' trimOption=(BOTH | LEADING | TRAILING) argument+=expression
FROM argument+=expression ')' #functionCall
| IDENTIFIER '->' expression #lambda
| '(' IDENTIFIER (',' IDENTIFIER)+ ')' '->' expression #lambda
Expand Down Expand Up @@ -736,7 +741,6 @@ qualifiedName

identifier
: strictIdentifier
| {ansi}? ansiReserved

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only code change I expect. What's the rationale of the other changes? I think select current_date() should not be supported in ansi mode.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or, we can find some more examples to advocate it. AFAIK presto supports select current_date() although it's not SQL standard. Do you know of any other systems that support it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it seems current_timestamp() is not a topic of the ANSI standard. So, I'll drop it in this pr.

But, some databases support current_timestamp() and this is implementation-specific.
For example, postgresql/oracle support current_timestamp(precision) as follows;

postgres=# select CURRENT_TIMESTAMP;
              now              
-------------------------------
 2019-03-12 20:22:52.065108+09
(1 row)

postgres=# select CURRENT_TIMESTAMP(1);
       timestamptz        
--------------------------
 2019-03-12 20:22:56.2+09
(1 row)

postgres=# select CURRENT_TIMESTAMP();
ERROR:  syntax error at or near ")" at character 26
STATEMENT:  select CURRENT_TIMESTAMP();
ERROR:  syntax error at or near ")"
LINE 1: select CURRENT_TIMESTAMP();
                                 ^

So, it might be worth supporting this function for better portability even when ansi mode enabled (this is future work though...).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for the ANSI starndard, we need to support these functions below for datetime, too;

postgres=# select CURRENT_TIME;
       timetz       
--------------------
 20:33:08.179954+09
(1 row)

postgres=# select LOCALTIME;
      time       
-----------------
 20:33:54.281054
(1 row)

postgres=# select LOCALTIMESTAMP;
         timestamp         
---------------------------
 2019-03-12 20:33:57.85737
(1 row)

I'll file a jira later.

| {!ansi}? defaultReserved
;

Expand All @@ -761,89 +765,6 @@ number
| MINUS? BIGDECIMAL_LITERAL #bigDecimalLiteral
;

// NOTE: You must follow a rule below when you add a new ANTLR token in this file:
// - All the ANTLR tokens = UNION(`ansiReserved`, `ansiNonReserved`) = UNION(`defaultReserved`, `nonReserved`)
//
// Let's say you add a new token `NEWTOKEN` and this is not reserved regardless of a `spark.sql.parser.ansi.enabled`
// value. In this case, you must add a token `NEWTOKEN` in both `ansiNonReserved` and `nonReserved`.
//
// It is recommended to list them in alphabetical order.

// The list of the reserved keywords when `spark.sql.parser.ansi.enabled` is true. Currently, we only reserve
// the ANSI keywords that almost all the ANSI SQL standards (SQL-92, SQL-99, SQL-2003, SQL-2008, SQL-2011,
// and SQL-2016) and PostgreSQL reserve.
ansiReserved
: ALL
| AND
| ANTI
| ANY
| AS
| AUTHORIZATION
| BOTH
| CASE
| CAST
| CHECK
| COLLATE
| COLUMN
| CONSTRAINT
| CREATE
| CROSS
| CURRENT_DATE
| CURRENT_TIME
| CURRENT_TIMESTAMP
| CURRENT_USER
| DISTINCT
| ELSE
| END
| EXCEPT
| FALSE
| FETCH
| FOR
| FOREIGN
| FROM
| FULL
| GRANT
| GROUP
| HAVING
| IN
| INNER
| INTERSECT
| INTO
| IS
| JOIN
| LEADING
| LEFT
| NATURAL
| NOT
| NULL
| ON
| ONLY
| OR
| ORDER
| OUTER
| OVERLAPS
| PRIMARY
| REFERENCES
| RIGHT
| SELECT
| SEMI
| SESSION_USER
| SETMINUS
| SOME
| TABLE
| THEN
| TO
| TRAILING
| UNION
| UNIQUE
| USER
| USING
| WHEN
| WHERE
| WITH
;


// The list of the non-reserved keywords when `spark.sql.parser.ansi.enabled` is true.
ansiNonReserved
: ADD
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1301,8 +1301,8 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging
ctx: FunctionCallContext): FunctionIdentifier = {
val opt = ctx.trimOption
if (opt != null) {
if (ctx.qualifiedName.getText.toLowerCase(Locale.ROOT) != "trim") {
throw new ParseException(s"The specified function ${ctx.qualifiedName.getText} " +
if (ctx.functionIdentifier.getText.toLowerCase(Locale.ROOT) != "trim") {
throw new ParseException(s"The specified function ${ctx.functionIdentifier.getText} " +
s"doesn't support with option ${opt.getText}.", ctx)
}
opt.getType match {
Expand All @@ -1317,7 +1317,7 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging
}
}
// Create the function call.
val name = ctx.qualifiedName.getText
val name = ctx.functionIdentifier.getText
val isDistinct = Option(ctx.setQuantifier()).exists(_.DISTINCT != null)
val arguments = ctx.argument.asScala.map(expression) match {
case Seq(UnresolvedStar(None))
Expand All @@ -1327,7 +1327,7 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging
case expressions =>
expressions
}
val funcId = replaceFunctions(visitFunctionName(ctx.qualifiedName), ctx)
val funcId = replaceFunctions(visitFunctionName(ctx.functionIdentifier), ctx)
val function = UnresolvedFunction(funcId, arguments, isDistinct)


Expand All @@ -1348,10 +1348,19 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging
ctx.identifier().asScala.map(_.getText) match {
case Seq(db, fn) => FunctionIdentifier(fn, Option(db))
case Seq(fn) => FunctionIdentifier(fn, None)
case other => throw new ParseException(s"Unsupported function name '${ctx.getText}'", ctx)
case _ => throw new ParseException(s"Unsupported function name '${ctx.getText}'", ctx)
}
}

private def visitFunctionName(ctx: FunctionIdentifierContext): FunctionIdentifier = {
val dbOption = if (ctx.db != null) {
Some(ctx.db.getText)
} else {
None
}
FunctionIdentifier(ctx.functionName.getText, dbOption)
}

/**
* Create an [[LambdaFunction]].
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ package org.apache.spark.sql.catalyst.parser

import org.apache.spark.SparkFunSuite
import org.apache.spark.sql.catalyst.TableIdentifier
import org.apache.spark.sql.catalyst.plans.SQLHelper
import org.apache.spark.sql.internal.SQLConf

class TableIdentifierParserSuite extends SparkFunSuite {
class TableIdentifierParserSuite extends SparkFunSuite with SQLHelper {
import CatalystSqlParser._

// Add "$elem$", "$value$" & "$key$"
Expand Down Expand Up @@ -281,6 +283,87 @@ class TableIdentifierParserSuite extends SparkFunSuite {
"where",
"with")

// All the keywords in `docs/sql-reserved-and-non-reserved-key-words.md` are listed below:
val allCandidateKeywords = Set("abs", "absolute", "acos", "action", "add", "after",
"all", "allocate", "alter", "analyze", "and", "anti", "any", "archive", "are", "array",
"array_agg", "array_max_cardinality", "as", "asc", "asensitive", "asin", "assertion",
"asymmetric", "at", "atan", "atomic", "authorization", "avg", "before", "begin", "begin_frame",
"begin_partition", "between", "bigint", "binary", "bit", "bit_length", "blob", "boolean",
"both", "breadth", "bucket", "buckets", "by", "cache", "call", "called", "cardinality",
"cascade", "cascaded", "case", "cast", "catalog", "ceil", "ceiling", "change", "char",
"char_length", "character", "character_length", "check", "classifier", "clear", "clob", "close",
"cluster", "clustered", "coalesce", "codegen", "collate", "collation", "collect", "collection",
"column", "columns", "comment", "commit", "compact", "compactions", "compute", "concatenate",
"condition", "connect", "connection", "constraint", "constraints", "constructor", "contains",
"continue", "convert", "copy", "corr", "corresponding", "cos", "cosh", "cost", "count",
"covar_pop", "covar_samp", "create", "cross", "cube", "cume_dist", "current", "current_catalog",
"current_date", "current_default_transform_group", "current_path", "current_role",
"current_row", "current_schema", "current_time", "current_timestamp",
"current_transform_group_for_type", "current_user", "cursor", "cycle", "data", "database",
"databases", "date", "day", "dbproperties", "deallocate", "dec", "decfloat", "decimal",
"declare", "default", "deferrable", "deferred", "define", "defined", "delete", "delimited",
"dense_rank", "depth", "deref", "desc", "describe", "descriptor", "deterministic", "dfs",
"diagnostics", "directories", "directory", "disconnect", "distinct", "distribute", "div", "do",
"domain", "double", "drop", "dynamic", "each", "element", "else", "elseif", "empty", "end",
"end_frame", "end_partition", "equals", "escape", "escaped", "every", "except", "exception",
"exchange", "exec", "execute", "exists", "exit", "exp", "explain", "export", "extended",
"external", "extract", "false", "fetch", "fields", "fileformat", "filter", "first",
"first_value", "float", "following", "for", "foreign", "format", "formatted", "found",
"frame_row", "free", "from", "full", "function", "functions", "fusion", "general", "get",
"global", "go", "goto", "grant", "group", "grouping", "groups", "handler", "having", "hold",
"hour", "identity", "if", "ignore", "immediate", "import", "in", "index", "indexes",
"indicator", "initial", "initially", "inner", "inout", "inpath", "input", "inputformat",
"insensitive", "insert", "int", "integer", "intersect", "intersection", "interval", "into",
"is", "isolation", "items", "iterate", "join", "json_array", "json_arrayagg", "json_exists",
"json_object", "json_objectagg", "json_query", "json_table", "json_table_primitive",
"json_value", "key", "keys", "lag", "language", "large", "last", "last_value", "lateral",
"lazy", "lead", "leading", "leave", "left", "level", "like", "like_regex", "limit", "lines",
"list", "listagg", "ln", "load", "local", "localtime", "localtimestamp", "location",
"locator", "lock", "locks", "log", "log10", "logical", "loop", "lower", "macro", "map",
"match", "match_number", "match_recognize", "matches", "max", "member", "merge", "method",
"min", "minus", "minute", "mod", "modifies", "module", "month", "msck", "multiset", "names",
"national", "natural", "nchar", "nclob", "new", "next", "no", "none", "normalize",
"not", "nth_value", "ntile", "null", "nullif", "nulls", "numeric", "object",
"occurrences_regex", "octet_length", "of", "offset", "old", "omit", "on", "one", "only", "open",
"option", "options", "or", "order", "ordinality", "out", "outer", "output", "outputformat",
"over", "overlaps", "overlay", "overwrite", "pad", "parameter", "partial", "partition",
"partitioned", "partitions", "path", "pattern", "per", "percent", "percent_rank",
"percentile_cont", "percentile_disc", "percentlit", "period", "pivot", "portion", "power",
"precedes", "preceding", "precision", "prepare", "preserve", "primary", "principals", "prior",
"privileges", "procedure", "ptf", "public", "purge", "range", "rank", "read", "reads", "real",
"recordreader", "recordwriter", "recover", "recursive", "reduce", "ref", "references",
"referencing", "refresh", "regr_avgx", "regr_avgy", "regr_count", "regr_intercept", "regr_r2",
"regr_slope", "regr_sxx", "regr_sxy", "regr_syy", "relative", "release", "rename", "repair",
"repeat", "replace", "reset", "resignal", "restrict", "result", "return", "returns", "revoke",
"right", "rlike", "role", "roles", "rollback", "rollup", "routine", "row", "row_number", "rows",
"running", "savepoint", "schema", "scope", "scroll", "search", "second", "section", "seek",
"select", "semi", "sensitive", "separated", "serde", "serdeproperties", "session",
"session_user", "set", "sets", "show", "signal", "similar", "sin", "sinh", "size",
"skewed", "skip", "smallint", "some", "sort", "sorted", "space", "specific", "specifictype",
"sql", "sqlcode", "sqlerror", "sqlexception", "sqlstate", "sqlwarning", "sqrt", "start",
"state", "static", "statistics", "stddev_pop", "stddev_samp", "stored", "stratify", "struct",
"submultiset", "subset", "substring", "substring_regex", "succeeds", "sum", "symmetric",
"system", "system_time", "system_user", "table", "tables", "tablesample", "tan", "tanh",
"tblproperties", "temporary", "terminated", "then", "time", "timestamp", "timezone_hour",
"timezone_minute", "to", "touch", "trailing", "transaction", "transactions", "transform",
"translate", "translate_regex", "translation", "treat", "trigger", "trim", "trim_array", "true",
"truncate", "uescape", "unarchive", "unbounded", "uncache", "under", "undo", "union", "unique",
"unknown", "unlock", "unnest", "unset", "until", "update", "upper", "usage", "use", "user",
"using", "value", "value_of", "values", "var_pop", "var_samp", "varbinary", "varchar",
"varying", "versioning", "view", "when", "whenever", "where", "while", "width_bucket", "window",
"with", "within", "without", "work", "write", "year", "zone")

val reservedKeywordsInAnsiMode = Set("all", "and", "anti", "any", "as", "authorization", "both",
"case", "cast", "check", "collate", "column", "constraint", "create", "cross", "current_date",
"current_time", "current_timestamp", "current_user", "distinct", "else", "end", "except",
"false", "fetch", "for", "foreign", "from", "full", "grant", "group", "having", "in", "inner",
"intersect", "into", "join", "is", "leading", "left", "natural", "not", "null", "on", "only",
"or", "order", "outer", "overlaps", "primary", "references", "right", "select", "semi",
"session_user", "minus", "some", "table", "then", "to", "trailing", "union", "unique", "user",
"using", "when", "where", "with")

val nonReservedKeywordsInAnsiMode = allCandidateKeywords -- reservedKeywordsInAnsiMode

test("table identifier") {
// Regular names.
assert(TableIdentifier("q") === parseTableIdentifier("q"))
Expand All @@ -300,6 +383,23 @@ class TableIdentifierParserSuite extends SparkFunSuite {
assert(TableIdentifier("x.y.z", None) === parseTableIdentifier("`x.y.z`"))
}

test("table identifier - reserved/non-reserved keywords if ANSI mode enabled") {
withSQLConf(SQLConf.ANSI_SQL_PARSER.key -> "true") {
reservedKeywordsInAnsiMode.foreach { keyword =>
val errMsg = intercept[ParseException] {
parseTableIdentifier(keyword)
}.getMessage
assert(errMsg.contains("no viable alternative at input"))
assert(TableIdentifier(keyword) === parseTableIdentifier(s"`$keyword`"))
assert(TableIdentifier(keyword, Option("db")) === parseTableIdentifier(s"db.`$keyword`"))
}
nonReservedKeywordsInAnsiMode.foreach { keyword =>
assert(TableIdentifier(keyword) === parseTableIdentifier(s"$keyword"))
assert(TableIdentifier(keyword, Option("db")) === parseTableIdentifier(s"db.$keyword"))
}
}
}

test("table identifier - strict keywords") {
// SQL Keywords.
hiveStrictNonReservedKeyword.foreach { keyword =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit

import org.apache.spark.sql.catalyst.util.DateTimeUtils
import org.apache.spark.sql.functions._
import org.apache.spark.sql.internal.SQLConf
import org.apache.spark.sql.test.SharedSQLContext
import org.apache.spark.unsafe.types.CalendarInterval

Expand Down Expand Up @@ -57,6 +58,26 @@ class DateFunctionsSuite extends QueryTest with SharedSQLContext {
checkAnswer(sql("""SELECT CURRENT_TIMESTAMP() = NOW()"""), Row(true))
}

test("SPARK-26976 current_date and current_timestamp should work even if ANSI mode enabled") {
withSQLConf(SQLConf.ANSI_SQL_PARSER.key -> "true") {
// current_date
Seq("CURRENT_DATE", "CURRENT_DATE()").foreach { funcName =>
val before = DateTimeUtils.millisToDays(System.currentTimeMillis())
val got = DateTimeUtils.fromJavaDate(sql(s"SELECT $funcName").collect().head.getDate(0))
val after = DateTimeUtils.millisToDays(System.currentTimeMillis())
assert(got >= before && got <= after)
}

// current_timestamp
Seq("CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP()").foreach { funcName =>
val before = System.currentTimeMillis
val got = sql(s"SELECT $funcName").collect().head.getTimestamp(0).getTime
val after = System.currentTimeMillis
assert(got >= before && got <= after)
}
}
}

val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
val sdfDate = new SimpleDateFormat("yyyy-MM-dd", Locale.US)
val d = new Date(sdf.parse("2015-04-08 13:10:15").getTime)
Expand Down