From 14a87faa213ec72180154d64353f19b1b72da524 Mon Sep 17 00:00:00 2001 From: minleejae Date: Sat, 12 Sep 2026 12:39:30 +0900 Subject: [PATCH] refactor: share PostgreSQL routine argument grammar --- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 31 ++++---- .../create/PostgreSqlRoutineArgumentTest.java | 72 +++++++++++++++++++ 2 files changed, 88 insertions(+), 15 deletions(-) create mode 100644 src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlRoutineArgumentTest.java diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 52afbaddf..298f7c026 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -16382,19 +16382,8 @@ PrivilegeTarget.Kind AccessObjectKind(boolean plural): } RoutineReference.Argument AccessRoutineArgument(): -{ RoutineReference.Argument result = new RoutineReference.Argument(); String name; Token mode; ColDataType type; } -{ - [ - LOOKAHEAD({ isAccessKeywordAhead("IN") || isAccessKeywordAhead("OUT") || isAccessKeywordAhead("INOUT") || isAccessKeywordAhead("VARIADIC") }) - ( { result.setMode(RoutineReference.Argument.Mode.IN); } - | mode= { result.setMode(accessEnum(RoutineReference.Argument.Mode.class, mode.image)); } ) - ] - ( - LOOKAHEAD(ColDataType() ("," | ")")) type=ColDataType() - | name=RelObjectName() { result.setName(name); } type=ColDataType() - ) - { result.setDataType(type); return result; } -} +{ RoutineReference.Argument argument; } +{ argument=RoutineArgument(false) { return argument; } } RoutineReference AccessRoutine(): { RoutineReference result = new RoutineReference(); String name; RoutineReference.Argument argument; List arguments; } @@ -17171,18 +17160,30 @@ CreateExtension CreateExtension(): } RoutineReference.Argument RoutineSignatureArgument(): +{ RoutineReference.Argument argument; } +{ argument=RoutineArgument(true) { return argument; } } + +/** Common argument AST; only extension aggregate signatures may end a type at ORDER BY. */ +RoutineReference.Argument RoutineArgument(boolean allowOrderBy): { RoutineReference.Argument result = new RoutineReference.Argument(); String name; Token mode; ColDataType type; } { [ LOOKAHEAD({ isKeywordAhead("IN") || isKeywordAhead("OUT") || isKeywordAhead("INOUT") || isKeywordAhead("VARIADIC") }) ( { result.setMode(RoutineReference.Argument.Mode.IN); } - | mode= { result.setMode(typeDdlEnum(RoutineReference.Argument.Mode.class, mode.image)); } ) + | mode= { result.setMode(allowOrderBy + ? typeDdlEnum(RoutineReference.Argument.Mode.class, mode.image) + : accessEnum(RoutineReference.Argument.Mode.class, mode.image)); } ) ] ( LOOKAHEAD(ColDataType() ("," | ")" | )) type=ColDataType() | name=RelObjectName() { result.setName(name); } type=ColDataType() ) - { result.setDataType(type); return result; } + { + requireAccessSyntax(allowOrderBy || getToken(1).kind != K_ORDER, + "ORDER BY requires an aggregate signature"); + result.setDataType(type); + return result; + } } RoutineReference RoutineReference(boolean aggregate): diff --git a/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlRoutineArgumentTest.java b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlRoutineArgumentTest.java new file mode 100644 index 000000000..64dc8078f --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlRoutineArgumentTest.java @@ -0,0 +1,72 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.alter.AlterExtension; +import net.sf.jsqlparser.statement.grant.Grant; +import net.sf.jsqlparser.statement.RoutineReference; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PostgreSqlRoutineArgumentTest { + @ParameterizedTest + @ValueSource(strings = {"integer", "text[]", "IN value integer", "OUT result text", + "INOUT state app.custom_type", "VARIADIC items text[]", + "\"argument name\" double precision"}) + void privilegeAndExtensionArgumentsHaveEquivalentAst(String argument) + throws JSQLParserException { + Grant grant = (Grant) CCJSqlParserUtil.parse( + "GRANT EXECUTE ON FUNCTION app.f(" + argument + ") TO reader"); + AlterExtension extension = (AlterExtension) CCJSqlParserUtil.parse( + "ALTER EXTENSION example ADD FUNCTION app.f(" + argument + ")"); + RoutineReference.Argument access = + grant.getTarget().getRoutines().get(0).getArguments().get(0); + RoutineReference.Argument member = extension.getMember().getRoutine().getArguments().get(0); + assertEquals(access.getMode(), member.getMode()); + assertEquals(access.getName(), member.getName()); + assertEquals(access.getDataType().toString(), member.getDataType().toString()); + assertRoundTrip(grant); + assertRoundTrip(extension); + } + + @Test + void aggregateKeepsOrderByArgumentBoundary() throws JSQLParserException { + AlterExtension extension = (AlterExtension) CCJSqlParserUtil.parse( + "ALTER EXTENSION example ADD AGGREGATE app.percentile(double precision ORDER BY integer)"); + RoutineReference routine = extension.getMember().getRoutine(); + assertEquals(1, routine.getArguments().size()); + assertEquals(1, routine.getOrderByArguments().size()); + assertEquals("double precision", routine.getArguments().get(0).getDataType().toString()); + assertRoundTrip(extension); + } + + @ParameterizedTest + @ValueSource(strings = { + "GRANT EXECUTE ON FUNCTION app.f(integer ORDER BY integer) TO reader", + "GRANT EXECUTE ON FUNCTION app.f(*) TO reader", + "ALTER EXTENSION example ADD FUNCTION app.f(integer ORDER BY integer)", + "ALTER EXTENSION example ADD FUNCTION app.f(*)", + "GRANT EXECUTE ON FUNCTION app.f(IN) TO reader" + }) + void aggregateSyntaxDoesNotLeakIntoOtherRoutineContexts(String sql) { + assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql)); + } + + private static void assertRoundTrip(Statement statement) throws JSQLParserException { + assertEquals(statement.toString(), CCJSqlParserUtil.parse(statement.toString()).toString()); + } +}