diff --git a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt index 9a21e6d890..31b588c079 100644 --- a/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt +++ b/partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt @@ -198,19 +198,29 @@ internal class PartiQLPigVisitor( override fun visitByIdent(ctx: PartiQLParser.ByIdentContext) = visitSymbolPrimitive(ctx.symbolPrimitive()) - override fun visitSymbolPrimitive(ctx: PartiQLParser.SymbolPrimitiveContext) = PartiqlAst.build { - val metas = ctx.ident.getSourceMetaContainer() - when (ctx.ident.type) { - PartiQLParser.IDENTIFIER_QUOTED -> id( - ctx.IDENTIFIER_QUOTED().getStringValue(), - caseSensitive(), - unqualified(), - metas - ) - - PartiQLParser.IDENTIFIER -> id(ctx.IDENTIFIER().getStringValue(), caseInsensitive(), unqualified(), metas) + private fun visitSymbolPrimitive(ctx: PartiQLParser.SymbolPrimitiveContext): PartiqlAst.Expr.Id = + when (ctx) { + is PartiQLParser.IdentifierQuotedContext -> visitIdentifierQuoted(ctx) + is PartiQLParser.IdentifierUnquotedContext -> visitIdentifierUnquoted(ctx) else -> throw ParserException("Invalid symbol reference.", ErrorCode.PARSE_INVALID_QUERY) } + + override fun visitIdentifierQuoted(ctx: PartiQLParser.IdentifierQuotedContext): PartiqlAst.Expr.Id = PartiqlAst.build { + id( + ctx.IDENTIFIER_QUOTED().getStringValue(), + caseSensitive(), + unqualified(), + ctx.IDENTIFIER_QUOTED().getSourceMetaContainer() + ) + } + + override fun visitIdentifierUnquoted(ctx: PartiQLParser.IdentifierUnquotedContext): PartiqlAst.Expr.Id = PartiqlAst.build { + id( + ctx.text, + caseInsensitive(), + unqualified(), + ctx.IDENTIFIER().getSourceMetaContainer() + ) } /** @@ -660,9 +670,9 @@ internal class PartiQLPigVisitor( override fun visitExcludeExprTupleAttr(ctx: PartiQLParser.ExcludeExprTupleAttrContext) = PartiqlAst.build { val attr = ctx.symbolPrimitive().getString() - val caseSensitivity = when (ctx.symbolPrimitive().ident.type) { - PartiQLParser.IDENTIFIER_QUOTED -> caseSensitive() - PartiQLParser.IDENTIFIER -> caseInsensitive() + val caseSensitivity = when (ctx.symbolPrimitive()) { + is PartiQLParser.IdentifierQuotedContext -> caseSensitive() + is PartiQLParser.IdentifierUnquotedContext -> caseInsensitive() else -> throw ParserException("Invalid symbol reference.", ErrorCode.PARSE_INVALID_QUERY) } excludeTupleAttr(identifier(attr, caseSensitivity)) @@ -1192,7 +1202,7 @@ internal class PartiQLPigVisitor( override fun visitVariableKeyword(ctx: PartiQLParser.VariableKeywordContext): PartiqlAst.PartiqlAstNode = PartiqlAst.build { - val keyword = ctx.nonReservedKeywords().start.text + val keyword = ctx.nonReserved().start.text val metas = ctx.start.getSourceMetaContainer() val qualifier = ctx.qualifier?.let { localsFirst() } ?: unqualified() id(keyword, caseInsensitive(), qualifier, metas) @@ -1316,16 +1326,11 @@ internal class PartiQLPigVisitor( } override fun visitFunctionCall(ctx: PartiQLParser.FunctionCallContext) = PartiqlAst.build { - val name = when (val nameCtx = ctx.functionName()) { - is PartiQLParser.FunctionNameReservedContext -> { - if (nameCtx.qualifier.isNotEmpty()) error("Legacy AST does not support qualified function names") - nameCtx.name.text.lowercase() - } - is PartiQLParser.FunctionNameSymbolContext -> { - if (nameCtx.qualifier.isNotEmpty()) error("Legacy AST does not support qualified function names") - nameCtx.name.getString().lowercase() - } - else -> error("Expected context FunctionNameReserved or FunctionNameSymbol") + val nameCtx = ctx.qualifiedName() + val name = if (nameCtx.qualifier.isNotEmpty()) { + error("Legacy AST does not support qualified function names") + } else { + nameCtx.name.getString().lowercase() } val args = ctx.expr().map { visitExpr(it) } val metas = ctx.start.getSourceMetaContainer() @@ -1819,9 +1824,9 @@ internal class PartiQLPigVisitor( } } - private fun PartiQLParser.SymbolPrimitiveContext.getSourceMetaContainer() = when (this.ident.type) { - PartiQLParser.IDENTIFIER -> this.IDENTIFIER().getSourceMetaContainer() - PartiQLParser.IDENTIFIER_QUOTED -> this.IDENTIFIER_QUOTED().getSourceMetaContainer() + private fun PartiQLParser.SymbolPrimitiveContext.getSourceMetaContainer() = when (this) { + is PartiQLParser.IdentifierQuotedContext -> this.IDENTIFIER_QUOTED().getSourceMetaContainer() + is PartiQLParser.IdentifierUnquotedContext -> this.start.getSourceMetaContainer() else -> throw ParserException( "Unable to get identifier's source meta-container.", ErrorCode.PARSE_INVALID_QUERY @@ -2125,25 +2130,23 @@ internal class PartiQLPigVisitor( } private fun PartiQLParser.SymbolPrimitiveContext.getString(): String { - return when { - this.IDENTIFIER_QUOTED() != null -> this.IDENTIFIER_QUOTED().getStringValue() - this.IDENTIFIER() != null -> this.IDENTIFIER().text + return when (this) { + is PartiQLParser.IdentifierQuotedContext -> this.IDENTIFIER_QUOTED().getStringValue() + is PartiQLParser.IdentifierUnquotedContext -> this.text else -> throw ParserException("Unable to get symbol's text.", ErrorCode.PARSE_INVALID_QUERY) } } private fun getSymbolPathExpr(ctx: PartiQLParser.SymbolPrimitiveContext) = PartiqlAst.build { - when { - ctx.IDENTIFIER_QUOTED() != null -> pathExpr( + when (ctx) { + is PartiQLParser.IdentifierQuotedContext -> pathExpr( lit(ionString(ctx.IDENTIFIER_QUOTED().getStringValue())), caseSensitive(), - metas = ctx.IDENTIFIER_QUOTED().getSourceMetaContainer() + metas = ctx.getSourceMetaContainer() ) - - ctx.IDENTIFIER() != null -> pathExpr( - lit(ionString(ctx.IDENTIFIER().text)), caseInsensitive(), - metas = ctx.IDENTIFIER().getSourceMetaContainer() + is PartiQLParser.IdentifierUnquotedContext -> pathExpr( + lit(ionString(ctx.text)), caseInsensitive(), + metas = ctx.getSourceMetaContainer() ) - else -> throw ParserException("Unable to get symbol's text.", ErrorCode.PARSE_INVALID_QUERY) } } diff --git a/partiql-lang/src/test/kotlin/org/partiql/lang/errors/ParserErrorsTest.kt b/partiql-lang/src/test/kotlin/org/partiql/lang/errors/ParserErrorsTest.kt index be09bae355..5736186dd2 100644 --- a/partiql-lang/src/test/kotlin/org/partiql/lang/errors/ParserErrorsTest.kt +++ b/partiql-lang/src/test/kotlin/org/partiql/lang/errors/ParserErrorsTest.kt @@ -15,6 +15,7 @@ package org.partiql.lang.errors import com.amazon.ion.Timestamp +import org.junit.Ignore import org.junit.Test import org.partiql.errors.ErrorCode import org.partiql.errors.Property @@ -655,6 +656,7 @@ class ParserErrorsTest : PartiQLParserTestBase() { } @Test + @Ignore("No longer a parser error; evaluation error") fun callTrimNoArgs() { checkInputThrowingParserException( "trim()", @@ -683,6 +685,7 @@ class ParserErrorsTest : PartiQLParserTestBase() { } @Test + @Ignore("No longer a parser error; evaluation error") fun callTrimZeroArguments() { checkInputThrowingParserException( "trim()", @@ -795,6 +798,7 @@ class ParserErrorsTest : PartiQLParserTestBase() { } @Test + @Ignore("No longer a parser error; evaluation error") fun aggregateWithNoArgs() { checkInputThrowingParserException( "SUM()", @@ -809,6 +813,7 @@ class ParserErrorsTest : PartiQLParserTestBase() { } @Test + @Ignore("No longer a parser error; evaluation error") fun aggregateWithTooManyArgs() { checkInputThrowingParserException( "SUM(a, b)", @@ -1672,6 +1677,7 @@ class ParserErrorsTest : PartiQLParserTestBase() { } @Test + @Ignore("No longer a parser error; evaluation error") fun callExtractMissingFromWithComma() { checkInputThrowingParserException( "extract(year, b)", @@ -1717,6 +1723,7 @@ class ParserErrorsTest : PartiQLParserTestBase() { } @Test + @Ignore("No longer a parser error; evaluation error") fun callExtractOnlySecondArgument() { checkInputThrowingParserException( "extract(b)", @@ -1746,6 +1753,7 @@ class ParserErrorsTest : PartiQLParserTestBase() { } @Test + @Ignore("No longer a parser error; evaluation error") fun callExtractOnlyDateTimePart() { checkInputThrowingParserException( "extract(year)", diff --git a/partiql-parser/src/main/antlr/PartiQL.g4 b/partiql-parser/src/main/antlr/PartiQL.g4 index e08b512716..0267c8983b 100644 --- a/partiql-parser/src/main/antlr/PartiQL.g4 +++ b/partiql-parser/src/main/antlr/PartiQL.g4 @@ -40,7 +40,9 @@ byIdent : BY symbolPrimitive; symbolPrimitive - : ident=( IDENTIFIER | IDENTIFIER_QUOTED ) + : IDENTIFIER # IdentifierUnquoted + | IDENTIFIER_QUOTED # IdentifierQuoted + | nonReserved # IdentifierUnquoted ; /** @@ -653,8 +655,8 @@ exprPrimary | dateFunction # ExprPrimaryBase | aggregate # ExprPrimaryBase | trimFunction # ExprPrimaryBase - | functionCall # ExprPrimaryBase | nullIf # ExprPrimaryBase + | functionCall # ExprPrimaryBase | exprPrimary pathStep+ # ExprPrimaryPath | exprGraphMatchMany # ExprPrimaryBase | caseExpr # ExprPrimaryBase @@ -761,13 +763,7 @@ dateFunction // SQL-99 10.4 — ::= functionCall - : functionName PAREN_LEFT ( expr ( COMMA expr )* )? PAREN_RIGHT - ; - -// SQL-99 10.4 — ::= [ ] -functionName - : (qualifier+=symbolPrimitive PERIOD)* name=( CHAR_LENGTH | CHARACTER_LENGTH | OCTET_LENGTH | BIT_LENGTH | UPPER | LOWER | SIZE | EXISTS | COUNT | MOD ) # FunctionNameReserved - | (qualifier+=symbolPrimitive PERIOD)* name=symbolPrimitive # FunctionNameSymbol + : qualifiedName PAREN_LEFT ( expr ( COMMA expr )* )? PAREN_RIGHT ; pathStep @@ -789,11 +785,52 @@ parameter varRefExpr : qualifier=AT_SIGN? ident=(IDENTIFIER|IDENTIFIER_QUOTED) # VariableIdentifier - | qualifier=AT_SIGN? key=nonReservedKeywords # VariableKeyword - ; - -nonReservedKeywords - : EXCLUDED + | qualifier=AT_SIGN? key=nonReserved # VariableKeyword + ; + +nonReserved + : /* From SQL99 https://ronsavage.github.io/SQL/sql-99.bnf.html#non-reserved%20word */ + ABS | ADA | ADMIN | ASENSITIVE | ASSIGNMENT | ASYMMETRIC | ATOMIC + | ATTRIBUTE | AVG + | BIT_LENGTH + | C | CALLED | CARDINALITY | CATALOG_NAME | CHAIN | CHAR_LENGTH + | CHARACTERISTICS | CHARACTER_LENGTH | CHARACTER_SET_CATALOG + | CHARACTER_SET_NAME | CHARACTER_SET_SCHEMA | CHECKED | CLASS_ORIGIN + | COALESCE | COBOL | COLLATION_CATALOG | COLLATION_NAME | COLLATION_SCHEMA + | COLUMN_NAME | COMMAND_FUNCTION | COMMAND_FUNCTION_CODE | COMMITTED + | CONDITION_IDENTIFIER | CONDITION_NUMBER | CONNECTION_NAME + | CONSTRAINT_CATALOG | CONSTRAINT_NAME | CONSTRAINT_SCHEMA | CONTAINS + | CONVERT | COUNT | CURSOR_NAME + | DATETIME_INTERVAL_CODE | DATETIME_INTERVAL_PRECISION | DEFINED + | DEFINER | DEGREE | DERIVED | DISPATCH + | EVERY | EXTRACT + | FINAL | FORTRAN + | G | GENERATED | GRANTED + | HIERARCHY + | IMPLEMENTATION | INSENSITIVE | INSTANCE | INSTANTIABLE | INVOKER + | K | KEY_MEMBER | KEY_TYPE + | LENGTH | LOWER + | M | MAX | MIN | MESSAGE_LENGTH | MESSAGE_OCTET_LENGTH | MESSAGE_TEXT + | MOD | MORE | MUMPS + | NAME | NULLABLE | NUMBER | NULLIF + | OCTET_LENGTH | ORDERING | OPTIONS | OVERLAY | OVERRIDING + | PASCAL | PARAMETER_MODE | PARAMETER_NAME + | PARAMETER_ORDINAL_POSITION | PARAMETER_SPECIFIC_CATALOG + | PARAMETER_SPECIFIC_NAME | PARAMETER_SPECIFIC_SCHEMA | PLI | POSITION + | REPEATABLE | RETURNED_CARDINALITY | RETURNED_LENGTH + | RETURNED_OCTET_LENGTH | RETURNED_SQLSTATE | ROUTINE_CATALOG + | ROUTINE_NAME | ROUTINE_SCHEMA | ROW_COUNT + | SCALE | SCHEMA_NAME | SCOPE | SECURITY | SELF | SENSITIVE | SERIALIZABLE + | SERVER_NAME | SIMPLE | SOURCE | SPECIFIC_NAME | STATEMENT | STRUCTURE + | STYLE | SUBCLASS_ORIGIN | SUBSTRING | SUM | SYMMETRIC | SYSTEM + | TABLE_NAME | TOP_LEVEL_COUNT | TRANSACTIONS_COMMITTED + | TRANSACTIONS_ROLLED_BACK | TRANSACTION_ACTIVE | TRANSFORM + | TRANSFORMS | TRANSLATE | TRIGGER_CATALOG | TRIGGER_SCHEMA + | TRIGGER_NAME | TRIM | TYPE + | UNCOMMITTED | UNNAMED | UPPER + /* PartiQL */ + | EXCLUDED | EXISTS + | SIZE ; /** diff --git a/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt b/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt index 0f633cf8ae..27a1b39da4 100644 --- a/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt +++ b/partiql-parser/src/main/kotlin/org/partiql/parser/internal/PartiQLParserDefault.kt @@ -563,18 +563,25 @@ internal class PartiQLParserDefault : PartiQLParser { override fun visitByIdent(ctx: GeneratedParser.ByIdentContext) = visitSymbolPrimitive(ctx.symbolPrimitive()) - override fun visitSymbolPrimitive(ctx: GeneratedParser.SymbolPrimitiveContext) = translate(ctx) { - when (ctx.ident.type) { - GeneratedParser.IDENTIFIER_QUOTED -> identifierSymbol( - ctx.IDENTIFIER_QUOTED().getStringValue(), - Identifier.CaseSensitivity.SENSITIVE, - ) - GeneratedParser.IDENTIFIER -> identifierSymbol( - ctx.IDENTIFIER().getStringValue(), - Identifier.CaseSensitivity.INSENSITIVE, - ) + private fun visitSymbolPrimitive(ctx: GeneratedParser.SymbolPrimitiveContext): Identifier.Symbol = + when (ctx) { + is GeneratedParser.IdentifierQuotedContext -> visitIdentifierQuoted(ctx) + is GeneratedParser.IdentifierUnquotedContext -> visitIdentifierUnquoted(ctx) else -> throw error(ctx, "Invalid symbol reference.") } + + override fun visitIdentifierQuoted(ctx: GeneratedParser.IdentifierQuotedContext): Identifier.Symbol = translate(ctx) { + identifierSymbol( + ctx.IDENTIFIER_QUOTED().getStringValue(), + Identifier.CaseSensitivity.SENSITIVE + ) + } + + override fun visitIdentifierUnquoted(ctx: GeneratedParser.IdentifierUnquotedContext): Identifier.Symbol = translate(ctx) { + identifierSymbol( + ctx.text, + Identifier.CaseSensitivity.INSENSITIVE + ) } override fun visitQualifiedName(ctx: GeneratedParser.QualifiedNameContext) = translate(ctx) { @@ -657,7 +664,7 @@ internal class PartiQLParserDefault : PartiQLParser { } override fun visitColumnDeclaration(ctx: GeneratedParser.ColumnDeclarationContext) = translate(ctx) { - val name = visitAs (ctx.columnName().symbolPrimitive()) + val name = visitSymbolPrimitive(ctx.columnName().symbolPrimitive()) val type = (visit(ctx.type()) as Type).also { isValidTypeDeclarationOrThrow(it, ctx.type()) } @@ -727,7 +734,7 @@ internal class PartiQLParserDefault : PartiQLParser { is GeneratedParser.UniqueContext -> false else -> throw error(ctx, "Expect UNIQUE or PRIMARY KEY") } - val columns = ctx.columnName().map { visitAs (it.symbolPrimitive()) } + val columns = ctx.columnName().map { visitSymbolPrimitive(it.symbolPrimitive()) } constraintDefinitionUnique(columns, isPrimaryKey) } @@ -741,7 +748,7 @@ internal class PartiQLParserDefault : PartiQLParser { ctx.partitionBy().accept(this) as PartitionBy override fun visitPartitionColList(ctx: GeneratedParser.PartitionColListContext) = translate(ctx) { - partitionByAttrList(ctx.columnName().map { visitAs (it.symbolPrimitive()) }) + partitionByAttrList(ctx.columnName().map { visitSymbolPrimitive(it.symbolPrimitive()) }) } /** @@ -1850,10 +1857,21 @@ internal class PartiQLParserDefault : PartiQLParser { override fun visitFunctionCall(ctx: GeneratedParser.FunctionCallContext) = translate(ctx) { val args = visitOrEmpty(ctx.expr()) - when (val funcName = ctx.functionName()) { - is GeneratedParser.FunctionNameReservedContext -> { - when (funcName.name.type) { + when (val funcName = ctx.qualifiedName()) { + is GeneratedParser.QualifiedNameContext -> { + when (funcName.name.start.type) { GeneratedParser.MOD -> exprBinary(Expr.Binary.Op.MODULO, args[0], args[1]) + GeneratedParser.CHARACTER_LENGTH, GeneratedParser.CHAR_LENGTH -> { + val path = ctx.qualifiedName().qualifier.map { visitSymbolPrimitive(it) } + val name = identifierSymbol("char_length", Identifier.CaseSensitivity.INSENSITIVE) + if (path.isEmpty()) { + exprCall(name, args) + } else { + val root = path.first() + val steps = path.drop(1) + listOf(name) + exprCall(identifierQualified(root, steps), args) + } + } else -> visitNonReservedFunctionCall(ctx, args) } } @@ -1861,39 +1879,10 @@ internal class PartiQLParserDefault : PartiQLParser { } } private fun visitNonReservedFunctionCall(ctx: GeneratedParser.FunctionCallContext, args: List): Expr.Call { - val function = visit(ctx.functionName()) as Identifier + val function = visitQualifiedName(ctx.qualifiedName()) return exprCall(function, args) } - override fun visitFunctionNameReserved(ctx: GeneratedParser.FunctionNameReservedContext): Identifier { - val path = ctx.qualifier.map { visitSymbolPrimitive(it) } - val name = when (ctx.name.type) { - GeneratedParser.CHARACTER_LENGTH, GeneratedParser.CHAR_LENGTH -> - identifierSymbol("char_length", Identifier.CaseSensitivity.INSENSITIVE) - else -> - identifierSymbol(ctx.name.text, Identifier.CaseSensitivity.INSENSITIVE) - } - return if (path.isEmpty()) { - name - } else { - val root = path.first() - val steps = path.drop(1) + listOf(name) - identifierQualified(root, steps) - } - } - - override fun visitFunctionNameSymbol(ctx: GeneratedParser.FunctionNameSymbolContext): Identifier { - val path = ctx.qualifier.map { visitSymbolPrimitive(it) } - val name = visitSymbolPrimitive(ctx.name) - return if (path.isEmpty()) { - name - } else { - val root = path.first() - val steps = path.drop(1) + listOf(name) - identifierQualified(root, steps) - } - } - /** * * FUNCTIONS WITH SPECIAL FORMS @@ -2278,7 +2267,7 @@ internal class PartiQLParserDefault : PartiQLParser { override fun visitTypeStruct(ctx: GeneratedParser.TypeStructContext) = translate(ctx) { val fields = ctx.structField().map { structFieldCtx -> - val name = visitAs (structFieldCtx.columnName()) + val name = visitSymbolPrimitive(structFieldCtx.columnName().symbolPrimitive()) val type = visitAs(structFieldCtx.type()) .also { isValidTypeDeclarationOrThrow(it, structFieldCtx.type()) } @@ -2313,9 +2302,9 @@ internal class PartiQLParserDefault : PartiQLParser { /** * Visiting a symbol to get a string, skip the wrapping, unwrapping, and location tracking. */ - private fun symbolToString(ctx: GeneratedParser.SymbolPrimitiveContext) = when (ctx.ident.type) { - GeneratedParser.IDENTIFIER_QUOTED -> ctx.IDENTIFIER_QUOTED().getStringValue() - GeneratedParser.IDENTIFIER -> ctx.IDENTIFIER().getStringValue() + private fun symbolToString(ctx: GeneratedParser.SymbolPrimitiveContext) = when (ctx) { + is GeneratedParser.IdentifierQuotedContext -> ctx.IDENTIFIER_QUOTED().getStringValue() + is GeneratedParser.IdentifierUnquotedContext -> ctx.text else -> throw error(ctx, "Invalid symbol reference.") }