diff --git a/docs/sql-ref-ansi-compliance.md b/docs/sql-ref-ansi-compliance.md index 40d7d7dd4003..920b3392854c 100644 --- a/docs/sql-ref-ansi-compliance.md +++ b/docs/sql-ref-ansi-compliance.md @@ -426,6 +426,7 @@ Below is a list of all the keywords in Spark SQL. |BY|non-reserved|non-reserved|reserved| |BYTE|non-reserved|non-reserved|non-reserved| |CACHE|non-reserved|non-reserved|non-reserved| +|CALLED|non-reserved|non-reserved|non-reserved| |CASCADE|non-reserved|non-reserved|non-reserved| |CASE|reserved|non-reserved|reserved| |CAST|reserved|non-reserved|reserved| @@ -452,6 +453,7 @@ Below is a list of all the keywords in Spark SQL. |COMPUTE|non-reserved|non-reserved|non-reserved| |CONCATENATE|non-reserved|non-reserved|non-reserved| |CONSTRAINT|reserved|non-reserved|reserved| +|CONTAINS|non-reserved|non-reserved|non-reserved| |COST|non-reserved|non-reserved|non-reserved| |CREATE|reserved|non-reserved|reserved| |CROSS|reserved|strict-non-reserved|reserved| @@ -478,10 +480,12 @@ Below is a list of all the keywords in Spark SQL. |DECLARE|non-reserved|non-reserved|non-reserved| |DEFAULT|non-reserved|non-reserved|non-reserved| |DEFINED|non-reserved|non-reserved|non-reserved| +|DEFINER|non-reserved|non-reserved|non-reserved| |DELETE|non-reserved|non-reserved|reserved| |DELIMITED|non-reserved|non-reserved|non-reserved| |DESC|non-reserved|non-reserved|non-reserved| |DESCRIBE|non-reserved|non-reserved|reserved| +|DETERMINISTIC|non-reserved|non-reserved|reserved| |DFS|non-reserved|non-reserved|non-reserved| |DIRECTORIES|non-reserved|non-reserved|non-reserved| |DIRECTORY|non-reserved|non-reserved|non-reserved| @@ -540,6 +544,7 @@ Below is a list of all the keywords in Spark SQL. |INDEXES|non-reserved|non-reserved|non-reserved| |INNER|reserved|strict-non-reserved|reserved| |INPATH|non-reserved|non-reserved|non-reserved| +|INPUT|non-reserved|non-reserved|non-reserved| |INPUTFORMAT|non-reserved|non-reserved|non-reserved| |INSERT|non-reserved|non-reserved|reserved| |INT|non-reserved|non-reserved|reserved| @@ -547,10 +552,12 @@ Below is a list of all the keywords in Spark SQL. |INTERSECT|reserved|strict-non-reserved|reserved| |INTERVAL|non-reserved|non-reserved|reserved| |INTO|reserved|non-reserved|reserved| +|INVOKER|non-reserved|non-reserved|non-reserved| |IS|reserved|non-reserved|reserved| |ITEMS|non-reserved|non-reserved|non-reserved| |JOIN|reserved|strict-non-reserved|reserved| |KEYS|non-reserved|non-reserved|non-reserved| +|LANGUAGE|non-reserved|non-reserved|reserved| |LAST|non-reserved|non-reserved|non-reserved| |LATERAL|reserved|strict-non-reserved|reserved| |LAZY|non-reserved|non-reserved|non-reserved| @@ -579,6 +586,7 @@ Below is a list of all the keywords in Spark SQL. |MINUTE|non-reserved|non-reserved|non-reserved| |MINUTES|non-reserved|non-reserved|non-reserved| |MINUS|non-reserved|strict-non-reserved|non-reserved| +|MODIFIES|non-reserved|non-reserved|non-reserved| |MONTH|non-reserved|non-reserved|non-reserved| |MONTHS|non-reserved|non-reserved|non-reserved| |MSCK|non-reserved|non-reserved|non-reserved| @@ -623,6 +631,7 @@ Below is a list of all the keywords in Spark SQL. |QUARTER|non-reserved|non-reserved|non-reserved| |QUERY|non-reserved|non-reserved|non-reserved| |RANGE|non-reserved|non-reserved|reserved| +|READS|non-reserved|non-reserved|non-reserved| |REAL|non-reserved|non-reserved|reserved| |RECORDREADER|non-reserved|non-reserved|non-reserved| |RECORDWRITER|non-reserved|non-reserved|non-reserved| @@ -638,6 +647,8 @@ Below is a list of all the keywords in Spark SQL. |RESET|non-reserved|non-reserved|non-reserved| |RESPECT|non-reserved|non-reserved|non-reserved| |RESTRICT|non-reserved|non-reserved|non-reserved| +|RETURN|non-reserved|non-reserved|reserved| +|RETURNS|non-reserved|non-reserved|reserved| |REVOKE|non-reserved|non-reserved|reserved| |RIGHT|reserved|strict-non-reserved|reserved| |RLIKE|non-reserved|non-reserved|non-reserved| @@ -651,6 +662,7 @@ Below is a list of all the keywords in Spark SQL. |SCHEMAS|non-reserved|non-reserved|non-reserved| |SECOND|non-reserved|non-reserved|non-reserved| |SECONDS|non-reserved|non-reserved|non-reserved| +|SECURITY|non-reserved|non-reserved|non-reserved| |SELECT|reserved|non-reserved|reserved| |SEMI|non-reserved|strict-non-reserved|non-reserved| |SEPARATED|non-reserved|non-reserved|non-reserved| @@ -668,6 +680,8 @@ Below is a list of all the keywords in Spark SQL. |SORT|non-reserved|non-reserved|non-reserved| |SORTED|non-reserved|non-reserved|non-reserved| |SOURCE|non-reserved|non-reserved|non-reserved| +|SPECIFIC|non-reserved|non-reserved|reserved| +|SQL|reserved|non-reserved|reserved| |START|non-reserved|non-reserved|reserved| |STATISTICS|non-reserved|non-reserved|non-reserved| |STORED|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 5753b153de30..85a4633e8050 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 @@ -146,6 +146,7 @@ BUCKETS: 'BUCKETS'; BY: 'BY'; BYTE: 'BYTE'; CACHE: 'CACHE'; +CALLED: 'CALLED'; CASCADE: 'CASCADE'; CASE: 'CASE'; CAST: 'CAST'; @@ -172,6 +173,7 @@ COMPENSATION: 'COMPENSATION'; COMPUTE: 'COMPUTE'; CONCATENATE: 'CONCATENATE'; CONSTRAINT: 'CONSTRAINT'; +CONTAINS: 'CONTAINS'; COST: 'COST'; CREATE: 'CREATE'; CROSS: 'CROSS'; @@ -198,10 +200,12 @@ DECIMAL: 'DECIMAL'; DECLARE: 'DECLARE'; DEFAULT: 'DEFAULT'; DEFINED: 'DEFINED'; +DEFINER: 'DEFINER'; DELETE: 'DELETE'; DELIMITED: 'DELIMITED'; DESC: 'DESC'; DESCRIBE: 'DESCRIBE'; +DETERMINISTIC: 'DETERMINISTIC'; DFS: 'DFS'; DIRECTORIES: 'DIRECTORIES'; DIRECTORY: 'DIRECTORY'; @@ -260,6 +264,7 @@ INDEX: 'INDEX'; INDEXES: 'INDEXES'; INNER: 'INNER'; INPATH: 'INPATH'; +INPUT: 'INPUT'; INPUTFORMAT: 'INPUTFORMAT'; INSERT: 'INSERT'; INTERSECT: 'INTERSECT'; @@ -267,10 +272,12 @@ INTERVAL: 'INTERVAL'; INT: 'INT'; INTEGER: 'INTEGER'; INTO: 'INTO'; +INVOKER: 'INVOKER'; IS: 'IS'; ITEMS: 'ITEMS'; JOIN: 'JOIN'; KEYS: 'KEYS'; +LANGUAGE: 'LANGUAGE'; LAST: 'LAST'; LATERAL: 'LATERAL'; LAZY: 'LAZY'; @@ -298,6 +305,7 @@ MILLISECOND: 'MILLISECOND'; MILLISECONDS: 'MILLISECONDS'; MINUTE: 'MINUTE'; MINUTES: 'MINUTES'; +MODIFIES: 'MODIFIES'; MONTH: 'MONTH'; MONTHS: 'MONTHS'; MSCK: 'MSCK'; @@ -342,6 +350,7 @@ PURGE: 'PURGE'; QUARTER: 'QUARTER'; QUERY: 'QUERY'; RANGE: 'RANGE'; +READS: 'READS'; REAL: 'REAL'; RECORDREADER: 'RECORDREADER'; RECORDWRITER: 'RECORDWRITER'; @@ -356,6 +365,8 @@ REPLACE: 'REPLACE'; RESET: 'RESET'; RESPECT: 'RESPECT'; RESTRICT: 'RESTRICT'; +RETURN: 'RETURN'; +RETURNS: 'RETURNS'; REVOKE: 'REVOKE'; RIGHT: 'RIGHT'; RLIKE: 'RLIKE' | 'REGEXP'; @@ -369,6 +380,7 @@ SECOND: 'SECOND'; SECONDS: 'SECONDS'; SCHEMA: 'SCHEMA'; SCHEMAS: 'SCHEMAS'; +SECURITY: 'SECURITY'; SELECT: 'SELECT'; SEMI: 'SEMI'; SEPARATED: 'SEPARATED'; @@ -387,6 +399,8 @@ SOME: 'SOME'; SORT: 'SORT'; SORTED: 'SORTED'; SOURCE: 'SOURCE'; +SPECIFIC: 'SPECIFIC'; +SQL: 'SQL'; START: 'START'; STATISTICS: 'STATISTICS'; STORED: 'STORED'; 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 37671fc735c3..7501283a4ac3 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 @@ -190,6 +190,11 @@ statement | CREATE (OR REPLACE)? TEMPORARY? FUNCTION (IF errorCapturingNot EXISTS)? identifierReference AS className=stringLit (USING resource (COMMA resource)*)? #createFunction + | CREATE (OR REPLACE)? TEMPORARY? FUNCTION (IF errorCapturingNot EXISTS)? + identifierReference LEFT_PAREN parameters=colDefinitionList? RIGHT_PAREN + (RETURNS (dataType | TABLE LEFT_PAREN returnParams=colTypeList RIGHT_PAREN))? + routineCharacteristics + RETURN (query | expression) #createUserDefinedFunction | DROP TEMPORARY? FUNCTION (IF EXISTS)? identifierReference #dropFunction | DECLARE (OR REPLACE)? VARIABLE? identifierReference dataType? variableDefaultExpression? #createVariable @@ -1216,6 +1221,14 @@ createOrReplaceTableColType : colName=errorCapturingIdentifier dataType colDefinitionOption* ; +colDefinitionList + : colDefinition (COMMA colDefinition)* + ; + +colDefinition + : colName=errorCapturingIdentifier dataType colDefinitionOption* + ; + colDefinitionOption : errorCapturingNot NULL | defaultExpression @@ -1235,6 +1248,46 @@ complexColType : errorCapturingIdentifier COLON? dataType (errorCapturingNot NULL)? commentSpec? ; +routineCharacteristics + : (routineLanguage + | specificName + | deterministic + | sqlDataAccess + | nullCall + | commentSpec + | rightsClause)* + ; + +routineLanguage + : LANGUAGE (SQL | IDENTIFIER) + ; + +specificName + : SPECIFIC specific=errorCapturingIdentifier + ; + +deterministic + : DETERMINISTIC + | errorCapturingNot DETERMINISTIC + ; + +sqlDataAccess + : access=NO SQL + | access=CONTAINS SQL + | access=READS SQL DATA + | access=MODIFIES SQL DATA + ; + +nullCall + : RETURNS NULL ON NULL INPUT + | CALLED ON NULL INPUT + ; + +rightsClause + : SQL SECURITY INVOKER + | SQL SECURITY DEFINER + ; + whenClause : WHEN condition=expression THEN result=expression ; @@ -1394,6 +1447,7 @@ ansiNonReserved | BY | BYTE | CACHE + | CALLED | CASCADE | CATALOG | CATALOGS @@ -1413,6 +1467,7 @@ ansiNonReserved | COMPENSATION | COMPUTE | CONCATENATE + | CONTAINS | COST | CUBE | CURRENT @@ -1433,10 +1488,12 @@ ansiNonReserved | DECLARE | DEFAULT | DEFINED + | DEFINER | DELETE | DELIMITED | DESC | DESCRIBE + | DETERMINISTIC | DFS | DIRECTORIES | DIRECTORY @@ -1477,13 +1534,16 @@ ansiNonReserved | INDEX | INDEXES | INPATH + | INPUT | INPUTFORMAT | INSERT | INT | INTEGER | INTERVAL + | INVOKER | ITEMS | KEYS + | LANGUAGE | LAST | LAZY | LIKE @@ -1508,6 +1568,7 @@ ansiNonReserved | MILLISECONDS | MINUTE | MINUTES + | MODIFIES | MONTH | MONTHS | MSCK @@ -1541,6 +1602,7 @@ ansiNonReserved | QUARTER | QUERY | RANGE + | READS | REAL | RECORDREADER | RECORDWRITER @@ -1554,6 +1616,8 @@ ansiNonReserved | RESET | RESPECT | RESTRICT + | RETURN + | RETURNS | REVOKE | RLIKE | ROLE @@ -1566,6 +1630,7 @@ ansiNonReserved | SCHEMAS | SECOND | SECONDS + | SECURITY | SEMI | SEPARATED | SERDE @@ -1581,6 +1646,7 @@ ansiNonReserved | SORT | SORTED | SOURCE + | SPECIFIC | START | STATISTICS | STORED @@ -1698,6 +1764,7 @@ nonReserved | BY | BYTE | CACHE + | CALLED | CASCADE | CASE | CAST @@ -1724,6 +1791,7 @@ nonReserved | COMPUTE | CONCATENATE | CONSTRAINT + | CONTAINS | COST | CREATE | CUBE @@ -1749,10 +1817,12 @@ nonReserved | DECLARE | DEFAULT | DEFINED + | DEFINER | DELETE | DELIMITED | DESC | DESCRIBE + | DETERMINISTIC | DFS | DIRECTORIES | DIRECTORY @@ -1808,15 +1878,18 @@ nonReserved | INDEX | INDEXES | INPATH + | INPUT | INPUTFORMAT | INSERT | INT | INTEGER | INTERVAL | INTO + | INVOKER | IS | ITEMS | KEYS + | LANGUAGE | LAST | LAZY | LEADING @@ -1843,6 +1916,7 @@ nonReserved | MILLISECONDS | MINUTE | MINUTES + | MODIFIES | MONTH | MONTHS | MSCK @@ -1885,6 +1959,7 @@ nonReserved | QUARTER | QUERY | RANGE + | READS | REAL | RECORDREADER | RECORDWRITER @@ -1899,6 +1974,8 @@ nonReserved | RESET | RESPECT | RESTRICT + | RETURN + | RETURNS | REVOKE | RLIKE | ROLE @@ -1911,6 +1988,7 @@ nonReserved | SCHEMAS | SECOND | SECONDS + | SECURITY | SELECT | SEPARATED | SERDE @@ -1927,6 +2005,8 @@ nonReserved | SORT | SORTED | SOURCE + | SPECIFIC + | SQL | START | STATISTICS | STORED diff --git a/sql/api/src/main/scala/org/apache/spark/sql/errors/QueryParsingErrors.scala b/sql/api/src/main/scala/org/apache/spark/sql/errors/QueryParsingErrors.scala index 816fa546a138..e7ae9f2bfb7b 100644 --- a/sql/api/src/main/scala/org/apache/spark/sql/errors/QueryParsingErrors.scala +++ b/sql/api/src/main/scala/org/apache/spark/sql/errors/QueryParsingErrors.scala @@ -574,19 +574,19 @@ private[sql] object QueryParsingErrors extends DataTypeErrorsBase { ctx) } - def createFuncWithBothIfNotExistsAndReplaceError(ctx: CreateFunctionContext): Throwable = { + def createFuncWithBothIfNotExistsAndReplaceError(ctx: ParserRuleContext): Throwable = { new ParseException( errorClass = "INVALID_SQL_SYNTAX.CREATE_ROUTINE_WITH_IF_NOT_EXISTS_AND_REPLACE", ctx) } - def defineTempFuncWithIfNotExistsError(ctx: CreateFunctionContext): Throwable = { + def defineTempFuncWithIfNotExistsError(ctx: ParserRuleContext): Throwable = { new ParseException( errorClass = "INVALID_SQL_SYNTAX.CREATE_TEMP_FUNC_WITH_IF_NOT_EXISTS", ctx) } - def unsupportedFunctionNameError(funcName: Seq[String], ctx: CreateFunctionContext): Throwable = { + def unsupportedFunctionNameError(funcName: Seq[String], ctx: ParserRuleContext): Throwable = { new ParseException( errorClass = "INVALID_SQL_SYNTAX.MULTI_PART_NAME", messageParameters = Map( @@ -597,7 +597,7 @@ private[sql] object QueryParsingErrors extends DataTypeErrorsBase { def specifyingDBInCreateTempFuncError( databaseName: String, - ctx: CreateFunctionContext): Throwable = { + ctx: ParserRuleContext): Throwable = { new ParseException( errorClass = "INVALID_SQL_SYNTAX.CREATE_TEMP_FUNC_WITH_DATABASE", messageParameters = Map("database" -> toSQLId(databaseName)), diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/RoutineLanguage.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/RoutineLanguage.scala new file mode 100644 index 000000000000..fc02bf0c606d --- /dev/null +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/RoutineLanguage.scala @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.spark.sql.catalyst.catalog + +/** + * Supported routine languages for UDFs created via SQL. + */ +sealed trait RoutineLanguage { + def name: String +} + +case object LanguageSQL extends RoutineLanguage { + override def name: String = "SQL" +} diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/UserDefinedFunctionErrors.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/UserDefinedFunctionErrors.scala new file mode 100644 index 000000000000..a5381669caea --- /dev/null +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/UserDefinedFunctionErrors.scala @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.spark.sql.catalyst.catalog + +import org.apache.spark.SparkException +import org.apache.spark.sql.errors.QueryErrorsBase + +/** + * Errors during registering and executing [[UserDefinedFunction]]s. + */ +object UserDefinedFunctionErrors extends QueryErrorsBase { + def unsupportedUserDefinedFunction(language: RoutineLanguage): Throwable = { + unsupportedUserDefinedFunction(language.name) + } + + def unsupportedUserDefinedFunction(language: String): Throwable = { + SparkException.internalError(s"Unsupported user defined function type: $language") + } +} diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala index 2caa8c074f64..a5b9aebefc43 100644 --- a/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala +++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala @@ -653,6 +653,167 @@ class SparkSqlAstBuilder extends AstBuilder { }) } + /** + * Create a [[CreateUserDefinedFunctionCommand]]. + * + * For example: + * {{{ + * CREATE [OR REPLACE] [TEMPORARY] FUNCTION [IF NOT EXISTS] [db_name.]function_name + * ([param_name param_type [COMMENT param_comment], ...]) + * RETURNS {ret_type | TABLE (ret_name ret_type [COMMENT ret_comment], ...])} + * [routine_characteristics] + * RETURN {expression | TABLE ( query )}; + * }}} + */ + override def visitCreateUserDefinedFunction(ctx: CreateUserDefinedFunctionContext): LogicalPlan = + withOrigin(ctx) { + assert(ctx.expression != null || ctx.query != null) + + if (ctx.EXISTS != null && ctx.REPLACE != null) { + throw QueryParsingErrors.createFuncWithBothIfNotExistsAndReplaceError(ctx) + } + + val inputParamText = Option(ctx.parameters).map(source) + val returnTypeText: String = + if (ctx.RETURNS != null && + (Option(ctx.dataType).nonEmpty || Option(ctx.returnParams).nonEmpty)) { + source(Option(ctx.dataType).getOrElse(ctx.returnParams)) + } else { + "" + } + val exprText = Option(ctx.expression()).map(source) + val queryText = Option(ctx.query()).map(source) + + val (containsSQL, deterministic, comment, optionalLanguage) = + visitRoutineCharacteristics(ctx.routineCharacteristics()) + val language: RoutineLanguage = optionalLanguage.getOrElse(LanguageSQL) + val isTableFunc = ctx.TABLE() != null || returnTypeText.equalsIgnoreCase("table") + + withIdentClause(ctx.identifierReference(), functionIdentifier => { + if (ctx.TEMPORARY == null) { + // TODO: support creating persistent UDFs. + operationNotAllowed(s"creating persistent SQL functions is not supported", ctx) + } else { + // Disallow to define a temporary function with `IF NOT EXISTS` + if (ctx.EXISTS != null) { + throw QueryParsingErrors.defineTempFuncWithIfNotExistsError(ctx) + } + + if (functionIdentifier.length > 2) { + throw QueryParsingErrors.unsupportedFunctionNameError(functionIdentifier, ctx) + } else if (functionIdentifier.length == 2) { + // Temporary function names should not contain database prefix like "database.function" + throw QueryParsingErrors.specifyingDBInCreateTempFuncError(functionIdentifier.head, ctx) + } + + CreateUserDefinedFunctionCommand( + functionIdentifier.asFunctionIdentifier, + inputParamText, + returnTypeText, + exprText, + queryText, + comment, + deterministic, + containsSQL, + language, + isTableFunc, + isTemp = true, + ctx.EXISTS != null, + ctx.REPLACE != null + ) + } + }) + } + + /** + * SQL function routine characteristics. + * Currently only deterministic clause and comment clause are used. + * + * routine language: [LANGUAGE SQL | IDENTIFIER] + * specific name: [SPECIFIC specific_name] + * routine data access: [NO SQL | CONTAINS SQL | READS SQL DATA | MODIFIES SQL DATA] + * routine null call: [RETURNS NULL ON NULL INPUT | CALLED ON NULL INPUT] + * routine determinism: [DETERMINISTIC | NOT DETERMINISTIC] + * comment: [COMMENT function_comment] + * rights: [SQL SECURITY INVOKER | SQL SECURITY DEFINER] + */ + override def visitRoutineCharacteristics(ctx: RoutineCharacteristicsContext) + : (Option[Boolean], Option[Boolean], Option[String], Option[RoutineLanguage]) = + withOrigin(ctx) { + checkDuplicateClauses(ctx.routineLanguage(), "LANGUAGE", ctx) + checkDuplicateClauses(ctx.specificName(), "SPECIFIC", ctx) + checkDuplicateClauses(ctx.sqlDataAccess(), "SQL DATA ACCESS", ctx) + checkDuplicateClauses(ctx.nullCall(), "NULL CALL", ctx) + checkDuplicateClauses(ctx.deterministic(), "DETERMINISTIC", ctx) + checkDuplicateClauses(ctx.commentSpec(), "COMMENT", ctx) + checkDuplicateClauses(ctx.rightsClause(), "SQL SECURITY RIGHTS", ctx) + + val language: Option[RoutineLanguage] = ctx + .routineLanguage() + .asScala + .headOption + .map(x => { + if (x.SQL() != null) { + LanguageSQL + } else { + val name: String = x.IDENTIFIER().getText() + operationNotAllowed(s"Unsupported language for user defined functions: $name", x) + } + }) + + val deterministic = ctx.deterministic().asScala.headOption.map(visitDeterminism) + val comment = visitCommentSpecList(ctx.commentSpec()) + + ctx.specificName().asScala.headOption.foreach(checkSpecificName) + ctx.nullCall().asScala.headOption.foreach(checkNullCall) + ctx.rightsClause().asScala.headOption.foreach(checkRightsClause) + val containsSQL: Option[Boolean] = + ctx.sqlDataAccess().asScala.headOption.map(visitDataAccess) + (containsSQL, deterministic, comment, language) + } + + /** + * Check if the function has a SPECIFIC name, + * which is a way to provide an alternative name for the function. + * This check applies for all user defined functions. + * Use functionName to specify the function that is currently checked. + */ + private def checkSpecificName(ctx: SpecificNameContext): Unit = + withOrigin(ctx) { + operationNotAllowed(s"SQL function with SPECIFIC name is not supported", ctx) + } + + private def checkNullCall(ctx: NullCallContext): Unit = withOrigin(ctx) { + if (ctx.RETURNS() != null) { + operationNotAllowed("SQL function with RETURNS NULL ON NULL INPUT is not supported", ctx) + } + } + + /** + * Check SQL function data access clause. Currently only READS SQL DATA and CONTAINS SQL + * are supported. Return true if the data access routine is CONTAINS SQL. + */ + private def visitDataAccess(ctx: SqlDataAccessContext): Boolean = withOrigin(ctx) { + if (ctx.NO() != null) { + operationNotAllowed("SQL function with NO SQL is not supported", ctx) + } + if (ctx.MODIFIES() != null) { + operationNotAllowed("SQL function with MODIFIES SQL DATA is not supported", ctx) + } + return ctx.READS() == null + } + + private def checkRightsClause(ctx: RightsClauseContext): Unit = withOrigin(ctx) { + if (ctx.INVOKER() != null) { + operationNotAllowed("SQL function with SQL SECURITY INVOKER is not supported", ctx) + } + } + + private def visitDeterminism(ctx: DeterministicContext): Boolean = withOrigin(ctx) { + blockBang(ctx.errorCapturingNot()) + ctx.errorCapturingNot() == null + } + /** * Create a DROP FUNCTION statement. * diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/command/CreateSQLFunctionCommand.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/command/CreateSQLFunctionCommand.scala new file mode 100644 index 000000000000..d9ec807c2dc8 --- /dev/null +++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/command/CreateSQLFunctionCommand.scala @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.spark.sql.execution.command + +import org.apache.spark.sql.{Row, SparkSession} +import org.apache.spark.sql.catalyst.FunctionIdentifier + +/** + * The DDL command that creates a SQL function. + * For example: + * {{{ + * CREATE [OR REPLACE] [TEMPORARY] FUNCTION [IF NOT EXISTS] [db_name.]function_name + * ([param_name param_type [COMMENT param_comment], ...]) + * RETURNS {ret_type | TABLE (ret_name ret_type [COMMENT ret_comment], ...])} + * [function_properties] function_body; + * + * function_properties: + * [NOT] DETERMINISTIC | COMMENT function_comment | [ CONTAINS SQL | READS SQL DATA ] + * + * function_body: + * RETURN {expression | TABLE ( query )} + * }}} + */ +case class CreateSQLFunctionCommand( + name: FunctionIdentifier, + inputParamText: Option[String], + returnTypeText: String, + exprText: Option[String], + queryText: Option[String], + comment: Option[String], + isDeterministic: Option[Boolean], + containsSQL: Option[Boolean], + isTableFunc: Boolean, + isTemp: Boolean, + ignoreIfExists: Boolean, + replace: Boolean) + extends CreateUserDefinedFunctionCommand { + + override def run(sparkSession: SparkSession): Seq[Row] = { + // TODO: Implement this. + Seq.empty + } +} diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/command/CreateUserDefinedFunctionCommand.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/command/CreateUserDefinedFunctionCommand.scala new file mode 100644 index 000000000000..bebb0f5cf6c3 --- /dev/null +++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/command/CreateUserDefinedFunctionCommand.scala @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.spark.sql.execution.command + +import org.apache.spark.sql.catalyst.FunctionIdentifier +import org.apache.spark.sql.catalyst.catalog.{LanguageSQL, RoutineLanguage, UserDefinedFunctionErrors} +import org.apache.spark.sql.catalyst.plans.logical.IgnoreCachedData + +/** + * The base class for CreateUserDefinedFunctionCommand + */ +abstract class CreateUserDefinedFunctionCommand + extends LeafRunnableCommand with IgnoreCachedData + + +object CreateUserDefinedFunctionCommand { + + /** + * This factory methods serves as a central place to verify required inputs and + * returns the CREATE command for the parsed user defined function. + */ + // scalastyle:off argcount + def apply( + name: FunctionIdentifier, + inputParamText: Option[String], + returnTypeText: String, + exprText: Option[String], + queryText: Option[String], + comment: Option[String], + isDeterministic: Option[Boolean], + containsSQL: Option[Boolean], + language: RoutineLanguage, + isTableFunc: Boolean, + isTemp: Boolean, + ignoreIfExists: Boolean, + replace: Boolean + ): CreateUserDefinedFunctionCommand = { + // scalastyle:on argcount + + assert(language != null) + + language match { + case LanguageSQL => + CreateSQLFunctionCommand( + name, + inputParamText, + returnTypeText, + exprText, + queryText, + comment, + isDeterministic, + containsSQL, + isTableFunc, + isTemp, + ignoreIfExists, + replace) + + case other => + throw UserDefinedFunctionErrors.unsupportedUserDefinedFunction(other) + } + } +} diff --git a/sql/core/src/test/resources/sql-tests/results/ansi/keywords.sql.out b/sql/core/src/test/resources/sql-tests/results/ansi/keywords.sql.out index 4e928562aa5d..cabbfa520d77 100644 --- a/sql/core/src/test/resources/sql-tests/results/ansi/keywords.sql.out +++ b/sql/core/src/test/resources/sql-tests/results/ansi/keywords.sql.out @@ -32,6 +32,7 @@ BUCKETS false BY false BYTE false CACHE false +CALLED false CASCADE false CASE true CAST true @@ -58,6 +59,7 @@ COMPENSATION false COMPUTE false CONCATENATE false CONSTRAINT true +CONTAINS false COST false CREATE true CROSS true @@ -84,10 +86,12 @@ DECIMAL false DECLARE false DEFAULT false DEFINED false +DEFINER false DELETE false DELIMITED false DESC false DESCRIBE false +DETERMINISTIC false DFS false DIRECTORIES false DIRECTORY false @@ -147,6 +151,7 @@ INDEX false INDEXES false INNER true INPATH false +INPUT false INPUTFORMAT false INSERT false INT false @@ -154,10 +159,12 @@ INTEGER false INTERSECT true INTERVAL false INTO true +INVOKER false IS true ITEMS false JOIN true KEYS false +LANGUAGE false LAST false LATERAL true LAZY false @@ -185,6 +192,7 @@ MILLISECONDS false MINUS false MINUTE false MINUTES false +MODIFIES false MONTH false MONTHS false MSCK false @@ -229,6 +237,7 @@ PURGE false QUARTER false QUERY false RANGE false +READS false REAL false RECORDREADER false RECORDWRITER false @@ -243,6 +252,8 @@ REPLACE false RESET false RESPECT false RESTRICT false +RETURN false +RETURNS false REVOKE false RIGHT true ROLE false @@ -255,6 +266,7 @@ SCHEMA false SCHEMAS false SECOND false SECONDS false +SECURITY false SELECT true SEMI false SEPARATED false @@ -272,6 +284,8 @@ SOME true SORT false SORTED false SOURCE false +SPECIFIC false +SQL true START false STATISTICS false STORED false @@ -409,6 +423,7 @@ RIGHT SELECT SESSION_USER SOME +SQL TABLE THEN TIME 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 e036c6620776..e304509aa6d7 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 @@ -32,6 +32,7 @@ BUCKETS false BY false BYTE false CACHE false +CALLED false CASCADE false CASE false CAST false @@ -58,6 +59,7 @@ COMPENSATION false COMPUTE false CONCATENATE false CONSTRAINT false +CONTAINS false COST false CREATE false CROSS false @@ -84,10 +86,12 @@ DECIMAL false DECLARE false DEFAULT false DEFINED false +DEFINER false DELETE false DELIMITED false DESC false DESCRIBE false +DETERMINISTIC false DFS false DIRECTORIES false DIRECTORY false @@ -147,6 +151,7 @@ INDEX false INDEXES false INNER false INPATH false +INPUT false INPUTFORMAT false INSERT false INT false @@ -154,10 +159,12 @@ INTEGER false INTERSECT false INTERVAL false INTO false +INVOKER false IS false ITEMS false JOIN false KEYS false +LANGUAGE false LAST false LATERAL false LAZY false @@ -185,6 +192,7 @@ MILLISECONDS false MINUS false MINUTE false MINUTES false +MODIFIES false MONTH false MONTHS false MSCK false @@ -229,6 +237,7 @@ PURGE false QUARTER false QUERY false RANGE false +READS false REAL false RECORDREADER false RECORDWRITER false @@ -243,6 +252,8 @@ REPLACE false RESET false RESPECT false RESTRICT false +RETURN false +RETURNS false REVOKE false RIGHT false ROLE false @@ -255,6 +266,7 @@ SCHEMA false SCHEMAS false SECOND false SECONDS false +SECURITY false SELECT false SEMI false SEPARATED false @@ -272,6 +284,8 @@ SOME false SORT false SORTED false SOURCE false +SPECIFIC false +SQL false START false STATISTICS false STORED false diff --git a/sql/core/src/test/scala/org/apache/spark/sql/execution/command/DDLParserSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/execution/command/DDLParserSuite.scala index 6b66f521e930..a4ee33ff1060 100644 --- a/sql/core/src/test/scala/org/apache/spark/sql/execution/command/DDLParserSuite.scala +++ b/sql/core/src/test/scala/org/apache/spark/sql/execution/command/DDLParserSuite.scala @@ -18,6 +18,7 @@ package org.apache.spark.sql.execution.command import org.apache.spark.SparkThrowable +import org.apache.spark.sql.catalyst.FunctionIdentifier import org.apache.spark.sql.catalyst.analysis.{AnalysisTest, GlobalTempView, LocalTempView, SchemaCompensation, UnresolvedAttribute, UnresolvedFunctionName, UnresolvedIdentifier} import org.apache.spark.sql.catalyst.catalog.{ArchiveResource, FileResource, FunctionResource, JarResource} import org.apache.spark.sql.catalyst.dsl.expressions._ @@ -36,6 +37,9 @@ class DDLParserSuite extends AnalysisTest with SharedSparkSession { super.parseException(parser.parsePlan)(sqlText) } + private def intercept(sqlCommand: String, messages: String*): Unit = + interceptParseException(parser.parsePlan)(sqlCommand, messages: _*)() + private def compareTransformQuery(sql: String, expected: LogicalPlan): Unit = { val plan = parser.parsePlan(sql).asInstanceOf[ScriptTransformation].copy(ioschema = null) comparePlans(plan, expected, checkAnalysis = false) @@ -841,4 +845,44 @@ class DDLParserSuite extends AnalysisTest with SharedSparkSession { parser.parsePlan("SHOW CATALOGS LIKE 'defau*'"), ShowCatalogsCommand(Some("defau*"))) } + + test("Create SQL functions") { + comparePlans( + parser.parsePlan("CREATE TEMP FUNCTION foo() RETURNS INT RETURN 1"), + CreateSQLFunctionCommand( + FunctionIdentifier("foo"), + inputParamText = None, + returnTypeText = "INT", + exprText = Some("1"), + queryText = None, + comment = None, + isDeterministic = None, + containsSQL = None, + isTableFunc = false, + isTemp = true, + ignoreIfExists = false, + replace = false)) + intercept("CREATE FUNCTION foo() RETURNS INT RETURN 1", + "Operation not allowed: creating persistent SQL functions is not supported") + } + + test("create SQL functions with unsupported routine characteristics") { + intercept("CREATE FUNCTION foo() RETURNS INT LANGUAGE blah RETURN 1", + "Operation not allowed: Unsupported language for user defined functions: blah") + + intercept("CREATE FUNCTION foo() RETURNS INT SPECIFIC foo1 RETURN 1", + "Operation not allowed: SQL function with SPECIFIC name is not supported") + + intercept("CREATE FUNCTION foo() RETURNS INT NO SQL RETURN 1", + "Operation not allowed: SQL function with NO SQL is not supported") + + intercept("CREATE FUNCTION foo() RETURNS INT NO SQL CONTAINS SQL RETURN 1", + "Found duplicate clauses: SQL DATA ACCESS") + + intercept("CREATE FUNCTION foo() RETURNS INT RETURNS NULL ON NULL INPUT RETURN 1", + "Operation not allowed: SQL function with RETURNS NULL ON NULL INPUT is not supported") + + intercept("CREATE FUNCTION foo() RETURNS INT SQL SECURITY INVOKER RETURN 1", + "Operation not allowed: SQL function with SQL SECURITY INVOKER is not supported") + } } 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 43d3532ab78c..e757487915bb 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,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,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,COST,CREATE,CROSS,CUBE,CURRENT,CURRENT_DATE,CURRENT_TIME,CURRENT_TIMESTAMP,CURRENT_USER,DATA,DATABASE,DATABASES,DATE,DATEADD,DATEDIFF,DATE_ADD,DATE_DIFF,DAY,DAYOFYEAR,DAYS,DBPROPERTIES,DEC,DECIMAL,DECLARE,DEFAULT,DEFINED,DELETE,DELIMITED,DESC,DESCRIBE,DFS,DIRECTORIES,DIRECTORY,DISTINCT,DISTRIBUTE,DIV,DOUBLE,DROP,ELSE,END,ESCAPE,ESCAPED,EVOLUTION,EXCEPT,EXCHANGE,EXCLUDE,EXECUTE,EXISTS,EXPLAIN,EXPORT,EXTENDED,EXTERNAL,EXTRACT,FALSE,FETCH,FIELDS,FILEFORMAT,FILTER,FIRST,FLOAT,FOLLOWING,FOR,FOREIGN,FORMAT,FORMATTED,FROM,FULL,FUNCTION,FUNCTIONS,GENERATED,GLOBAL,GRANT,GROUP,GROUPING,HAVING,HOUR,HOURS,IDENTIFIER,IF,IGNORE,ILIKE,IMMEDIATE,IMPORT,IN,INCLUDE,INDEX,INDEXES,INNER,INPATH,INPUTFORMAT,INSERT,INT,INTEGER,INTERSECT,INTERVAL,INTO,IS,ITEMS,JOIN,KEYS,LAST,LATERAL,LAZY,LEADING,LEFT,LIKE,LIMIT,LINES,LIST,LOAD,LOCAL,LOCATION,LOCK,LOCKS,LOGICAL,LONG,MACRO,MAP,MATCHED,MERGE,MICROSECOND,MICROSECONDS,MILLISECOND,MILLISECONDS,MINUS,MINUTE,MINUTES,MONTH,MONTHS,MSCK,NAME,NAMESPACE,NAMESPACES,NANOSECOND,NANOSECONDS,NATURAL,NO,NOT,NULL,NULLS,NUMERIC,OF,OFFSET,ON,ONLY,OPTION,OPTIONS,OR,ORDER,OUT,OUTER,OUTPUTFORMAT,OVER,OVERLAPS,OVERLAY,OVERWRITE,PARTITION,PARTITIONED,PARTITIONS,PERCENT,PIVOT,PLACING,POSITION,PRECEDING,PRIMARY,PRINCIPALS,PROPERTIES,PURGE,QUARTER,QUERY,RANGE,REAL,RECORDREADER,RECORDWRITER,RECOVER,REDUCE,REFERENCES,REFRESH,RENAME,REPAIR,REPEATABLE,REPLACE,RESET,RESPECT,RESTRICT,REVOKE,RIGHT,ROLE,ROLES,ROLLBACK,ROLLUP,ROW,ROWS,SCHEMA,SCHEMAS,SECOND,SECONDS,SELECT,SEMI,SEPARATED,SERDE,SERDEPROPERTIES,SESSION_USER,SET,SETS,SHORT,SHOW,SINGLE,SKEWED,SMALLINT,SOME,SORT,SORTED,SOURCE,START,STATISTICS,STORED,STRATIFY,STRING,STRUCT,SUBSTR,SUBSTRING,SYNC,SYSTEM_TIME,SYSTEM_VERSION,TABLE,TABLES,TABLESAMPLE,TARGET,TBLPROPERTIES,TERMINATED,THEN,TIME,TIMEDIFF,TIMESTAMP,TIMESTAMPADD,TIMESTAMPDIFF,TIMESTAMP_LTZ,TIMESTAMP_NTZ,TINYINT,TO,TOUCH,TRAILING,TRANSACTION,TRANSACTIONS,TRANSFORM,TRIM,TRUE,TRUNCATE,TRY_CAST,TYPE,UNARCHIVE,UNBOUNDED,UNCACHE,UNION,UNIQUE,UNKNOWN,UNLOCK,UNPIVOT,UNSET,UPDATE,USE,USER,USING,VALUES,VAR,VARCHAR,VARIABLE,VARIANT,VERSION,VIEW,VIEWS,VOID,WEEK,WEEKS,WHEN,WHERE,WINDOW,WITH,WITHIN,X,YEAR,YEARS,ZONE") + assert(infoValue.getStringValue == "ADD,AFTER,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,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,CURRENT,CURRENT_DATE,CURRENT_TIME,CURRENT_TIMESTAMP,CURRENT_USER,DATA,DATABASE,DATABASES,DATE,DATEADD,DATEDIFF,DATE_ADD,DATE_DIFF,DAY,DAYOFYEAR,DAYS,DBPROPERTIES,DEC,DECIMAL,DECLARE,DEFAULT,DEFINED,DEFINER,DELETE,DELIMITED,DESC,DESCRIBE,DETERMINISTIC,DFS,DIRECTORIES,DIRECTORY,DISTINCT,DISTRIBUTE,DIV,DOUBLE,DROP,ELSE,END,ESCAPE,ESCAPED,EVOLUTION,EXCEPT,EXCHANGE,EXCLUDE,EXECUTE,EXISTS,EXPLAIN,EXPORT,EXTENDED,EXTERNAL,EXTRACT,FALSE,FETCH,FIELDS,FILEFORMAT,FILTER,FIRST,FLOAT,FOLLOWING,FOR,FOREIGN,FORMAT,FORMATTED,FROM,FULL,FUNCTION,FUNCTIONS,GENERATED,GLOBAL,GRANT,GROUP,GROUPING,HAVING,HOUR,HOURS,IDENTIFIER,IF,IGNORE,ILIKE,IMMEDIATE,IMPORT,IN,INCLUDE,INDEX,INDEXES,INNER,INPATH,INPUT,INPUTFORMAT,INSERT,INT,INTEGER,INTERSECT,INTERVAL,INTO,INVOKER,IS,ITEMS,JOIN,KEYS,LANGUAGE,LAST,LATERAL,LAZY,LEADING,LEFT,LIKE,LIMIT,LINES,LIST,LOAD,LOCAL,LOCATION,LOCK,LOCKS,LOGICAL,LONG,MACRO,MAP,MATCHED,MERGE,MICROSECOND,MICROSECONDS,MILLISECOND,MILLISECONDS,MINUS,MINUTE,MINUTES,MODIFIES,MONTH,MONTHS,MSCK,NAME,NAMESPACE,NAMESPACES,NANOSECOND,NANOSECONDS,NATURAL,NO,NOT,NULL,NULLS,NUMERIC,OF,OFFSET,ON,ONLY,OPTION,OPTIONS,OR,ORDER,OUT,OUTER,OUTPUTFORMAT,OVER,OVERLAPS,OVERLAY,OVERWRITE,PARTITION,PARTITIONED,PARTITIONS,PERCENT,PIVOT,PLACING,POSITION,PRECEDING,PRIMARY,PRINCIPALS,PROPERTIES,PURGE,QUARTER,QUERY,RANGE,READS,REAL,RECORDREADER,RECORDWRITER,RECOVER,REDUCE,REFERENCES,REFRESH,RENAME,REPAIR,REPEATABLE,REPLACE,RESET,RESPECT,RESTRICT,RETURN,RETURNS,REVOKE,RIGHT,ROLE,ROLES,ROLLBACK,ROLLUP,ROW,ROWS,SCHEMA,SCHEMAS,SECOND,SECONDS,SECURITY,SELECT,SEMI,SEPARATED,SERDE,SERDEPROPERTIES,SESSION_USER,SET,SETS,SHORT,SHOW,SINGLE,SKEWED,SMALLINT,SOME,SORT,SORTED,SOURCE,SPECIFIC,SQL,START,STATISTICS,STORED,STRATIFY,STRING,STRUCT,SUBSTR,SUBSTRING,SYNC,SYSTEM_TIME,SYSTEM_VERSION,TABLE,TABLES,TABLESAMPLE,TARGET,TBLPROPERTIES,TERMINATED,THEN,TIME,TIMEDIFF,TIMESTAMP,TIMESTAMPADD,TIMESTAMPDIFF,TIMESTAMP_LTZ,TIMESTAMP_NTZ,TINYINT,TO,TOUCH,TRAILING,TRANSACTION,TRANSACTIONS,TRANSFORM,TRIM,TRUE,TRUNCATE,TRY_CAST,TYPE,UNARCHIVE,UNBOUNDED,UNCACHE,UNION,UNIQUE,UNKNOWN,UNLOCK,UNPIVOT,UNSET,UPDATE,USE,USER,USING,VALUES,VAR,VARCHAR,VARIABLE,VARIANT,VERSION,VIEW,VIEWS,VOID,WEEK,WEEKS,WHEN,WHERE,WINDOW,WITH,WITHIN,X,YEAR,YEARS,ZONE") // scalastyle:on line.size.limit } }