Skip to content

Commit 119c6bc

Browse files
authored
feat: extend PostgreSQL COMMENT ON targets (#2816)
Model the remaining common PostgreSQL COMMENT ON object kinds with the existing CommentTarget: TRIGGER, POLICY and RULE within a table; PROCEDURE, ROUTINE and AGGREGATE signatures through RoutineReference; CAST; and ACCESS METHOD, COLLATION, DATABASE, EVENT TRIGGER, EXTENSION, FOREIGN DATA WRAPPER, FOREIGN TABLE, [PROCEDURAL] LANGUAGE, PUBLICATION, ROLE, SERVER, STATISTICS, SUBSCRIPTION and TABLESPACE. The grammar reads the kind first and lets it select the reference shape. Kind words without a token stay non-reserved. Shared routine signatures now accept ordered-set aggregates without direct arguments, such as mode(ORDER BY anyelement). Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 721474d commit 119c6bc

8 files changed

Lines changed: 523 additions & 62 deletions

File tree

‎src/main/java/net/sf/jsqlparser/parser/feature/Feature.java‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,24 @@ public enum Feature {
660660
/** Additional structured catalog targets of COMMENT ON. */
661661
commentOnIndex, commentOnSchema, commentOnSequence, commentOnDomain, commentOnType, commentOnMaterializedView, commentOnFunction, commentOnConstraint,
662662

663+
/** COMMENT ON TRIGGER, POLICY or RULE within a table. */
664+
commentOnTrigger, commentOnPolicy, commentOnRule,
665+
666+
/** COMMENT ON PROCEDURE, ROUTINE, AGGREGATE or CAST. */
667+
commentOnProcedure, commentOnRoutine, commentOnAggregate, commentOnCast,
668+
669+
/** COMMENT ON COLLATION, FOREIGN TABLE or STATISTICS. */
670+
commentOnCollation, commentOnForeignTable, commentOnStatistics,
671+
672+
/** COMMENT ON DATABASE, ROLE or TABLESPACE. */
673+
commentOnDatabase, commentOnRole, commentOnTablespace,
674+
675+
/** COMMENT ON ACCESS METHOD, EXTENSION, FOREIGN DATA WRAPPER, LANGUAGE or SERVER. */
676+
commentOnAccessMethod, commentOnExtension, commentOnForeignDataWrapper, commentOnLanguage, commentOnServer,
677+
678+
/** COMMENT ON EVENT TRIGGER, PUBLICATION or SUBSCRIPTION. */
679+
commentOnEventTrigger, commentOnPublication, commentOnSubscription,
680+
663681
/**
664682
* SQL "DESCRIBE" statement is allowed
665683
*

‎src/main/java/net/sf/jsqlparser/statement/comment/CommentTarget.java‎

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,43 @@
1313
import java.util.function.Consumer;
1414
import net.sf.jsqlparser.schema.Table;
1515
import net.sf.jsqlparser.statement.RoutineReference;
16+
import net.sf.jsqlparser.statement.create.table.ColDataType;
1617

1718
/** A catalog object addressed by COMMENT, rather than a function invocation or a query. */
1819
public class CommentTarget implements Serializable {
1920
public enum Kind {
20-
INDEX, SCHEMA, SEQUENCE, DOMAIN, TYPE, MATERIALIZED_VIEW, FUNCTION, CONSTRAINT
21+
INDEX, SCHEMA, SEQUENCE, DOMAIN, TYPE, MATERIALIZED_VIEW, FUNCTION, CONSTRAINT,
22+
23+
TRIGGER, POLICY, RULE, PROCEDURE, ROUTINE, AGGREGATE, CAST,
24+
25+
COLLATION, FOREIGN_TABLE, STATISTICS, DATABASE, ROLE, TABLESPACE, ACCESS_METHOD, EXTENSION,
26+
27+
FOREIGN_DATA_WRAPPER, LANGUAGE, SERVER, EVENT_TRIGGER, PUBLICATION, SUBSCRIPTION;
28+
29+
/**
30+
* Whether the target is a routine signature, exposed by {@link CommentTarget#getRoutine()}.
31+
*/
32+
public boolean isRoutine() {
33+
return this == FUNCTION || this == PROCEDURE || this == ROUTINE || this == AGGREGATE;
34+
}
35+
36+
/**
37+
* Whether the target is named within the table or domain of
38+
* {@link CommentTarget#getRelation()}.
39+
*/
40+
public boolean isOwnedByRelation() {
41+
return this == CONSTRAINT || this == TRIGGER || this == POLICY || this == RULE;
42+
}
2143
}
2244

2345
private Kind kind;
2446
private Table name;
2547
private RoutineReference routine;
2648
private Table relation;
2749
private boolean onDomain;
50+
private boolean procedural;
51+
private ColDataType sourceType;
52+
private ColDataType targetType;
2853

2954
public Kind getKind() {
3055
return kind;
@@ -34,7 +59,10 @@ public void setKind(Kind kind) {
3459
this.kind = kind;
3560
}
3661

37-
/** The object's identifier; using Table preserves the individual name components. */
62+
/**
63+
* The object's identifier; using Table preserves the individual name components. Routine and
64+
* cast targets have no name.
65+
*/
3866
public Table getName() {
3967
return name;
4068
}
@@ -51,7 +79,10 @@ public void setRoutine(RoutineReference routine) {
5179
this.routine = routine;
5280
}
5381

54-
/** The table or domain owning a constraint, distinguished by {@link #isOnDomain()}. */
82+
/**
83+
* The table owning a constraint, trigger, policy or rule, or the domain owning a constraint,
84+
* distinguished by {@link #isOnDomain()}.
85+
*/
5586
public Table getRelation() {
5687
return relation;
5788
}
@@ -68,23 +99,62 @@ public void setOnDomain(boolean onDomain) {
6899
this.onDomain = onDomain;
69100
}
70101

71-
/** Returns only an explicitly named table/view, never an index, type, function or domain. */
102+
/** Whether a {@link Kind#LANGUAGE} target was written as PROCEDURAL LANGUAGE. */
103+
public boolean isProcedural() {
104+
return procedural;
105+
}
106+
107+
public void setProcedural(boolean procedural) {
108+
this.procedural = procedural;
109+
}
110+
111+
/** The source type of a {@link Kind#CAST} target. */
112+
public ColDataType getSourceType() {
113+
return sourceType;
114+
}
115+
116+
public void setSourceType(ColDataType sourceType) {
117+
this.sourceType = sourceType;
118+
}
119+
120+
/** The target type of a {@link Kind#CAST} target. */
121+
public ColDataType getTargetType() {
122+
return targetType;
123+
}
124+
125+
public void setTargetType(ColDataType targetType) {
126+
this.targetType = targetType;
127+
}
128+
129+
/**
130+
* Returns only an explicitly named table, foreign table or view, never an index, type,
131+
* function, statistics object or domain.
132+
*/
72133
public Table getReferencedRelation() {
73-
if (kind == Kind.MATERIALIZED_VIEW) {
134+
if (namesRelation()) {
74135
return name;
75136
}
76-
return kind == Kind.CONSTRAINT && !onDomain ? relation : null;
137+
return kind != null && kind.isOwnedByRelation() && !onDomain ? relation : null;
138+
}
139+
140+
private boolean namesRelation() {
141+
return kind == Kind.MATERIALIZED_VIEW || kind == Kind.FOREIGN_TABLE;
77142
}
78143

79144
public StringBuilder appendTo(StringBuilder builder, Consumer<Table> relationWriter) {
145+
if (kind == Kind.LANGUAGE && procedural) {
146+
builder.append("PROCEDURAL ");
147+
}
80148
builder.append(kind.name().replace('_', ' ')).append(' ');
81-
if (kind == Kind.FUNCTION) {
149+
if (kind.isRoutine()) {
82150
builder.append(routine);
83-
} else if (kind == Kind.MATERIALIZED_VIEW) {
151+
} else if (kind == Kind.CAST) {
152+
builder.append('(').append(sourceType).append(" AS ").append(targetType).append(')');
153+
} else if (namesRelation()) {
84154
relationWriter.accept(name);
85155
} else {
86156
builder.append(name);
87-
if (kind == Kind.CONSTRAINT) {
157+
if (kind.isOwnedByRelation()) {
88158
builder.append(" ON ");
89159
if (onDomain) {
90160
builder.append("DOMAIN ").append(relation);

‎src/main/java/net/sf/jsqlparser/util/validation/feature/PostgresqlVersion.java‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,16 @@ public enum PostgresqlVersion implements Version {
9595
Feature.commentOnDomain, Feature.commentOnType,
9696
Feature.commentOnMaterializedView,
9797
Feature.commentOnFunction, Feature.commentOnConstraint,
98+
Feature.commentOnTrigger, Feature.commentOnPolicy, Feature.commentOnRule,
99+
Feature.commentOnProcedure, Feature.commentOnRoutine,
100+
Feature.commentOnAggregate, Feature.commentOnCast,
101+
Feature.commentOnCollation, Feature.commentOnForeignTable,
102+
Feature.commentOnStatistics, Feature.commentOnDatabase, Feature.commentOnRole,
103+
Feature.commentOnTablespace, Feature.commentOnAccessMethod,
104+
Feature.commentOnExtension, Feature.commentOnForeignDataWrapper,
105+
Feature.commentOnLanguage, Feature.commentOnServer,
106+
Feature.commentOnEventTrigger, Feature.commentOnPublication,
107+
Feature.commentOnSubscription,
98108

99109
// https://www.postgresql.org/docs/current/sql-createsequence.html
100110
Feature.createSequence,

‎src/main/java/net/sf/jsqlparser/util/validation/validator/CommentValidator.java‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,48 @@ private static Feature targetFeature(CommentTarget.Kind kind) {
4646
return Feature.commentOnFunction;
4747
case CONSTRAINT:
4848
return Feature.commentOnConstraint;
49+
case TRIGGER:
50+
return Feature.commentOnTrigger;
51+
case POLICY:
52+
return Feature.commentOnPolicy;
53+
case RULE:
54+
return Feature.commentOnRule;
55+
case PROCEDURE:
56+
return Feature.commentOnProcedure;
57+
case ROUTINE:
58+
return Feature.commentOnRoutine;
59+
case AGGREGATE:
60+
return Feature.commentOnAggregate;
61+
case CAST:
62+
return Feature.commentOnCast;
63+
case COLLATION:
64+
return Feature.commentOnCollation;
65+
case FOREIGN_TABLE:
66+
return Feature.commentOnForeignTable;
67+
case STATISTICS:
68+
return Feature.commentOnStatistics;
69+
case DATABASE:
70+
return Feature.commentOnDatabase;
71+
case ROLE:
72+
return Feature.commentOnRole;
73+
case TABLESPACE:
74+
return Feature.commentOnTablespace;
75+
case ACCESS_METHOD:
76+
return Feature.commentOnAccessMethod;
77+
case EXTENSION:
78+
return Feature.commentOnExtension;
79+
case FOREIGN_DATA_WRAPPER:
80+
return Feature.commentOnForeignDataWrapper;
81+
case LANGUAGE:
82+
return Feature.commentOnLanguage;
83+
case SERVER:
84+
return Feature.commentOnServer;
85+
case EVENT_TRIGGER:
86+
return Feature.commentOnEventTrigger;
87+
case PUBLICATION:
88+
return Feature.commentOnPublication;
89+
case SUBSCRIPTION:
90+
return Feature.commentOnSubscription;
4991
default:
5092
throw new IllegalArgumentException("Unknown COMMENT target: " + kind);
5193
}

‎src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt‎

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21663,38 +21663,83 @@ Table CommentObjectName():
2166321663
: new Table(names.getNames()); }
2166421664
}
2166521665

21666+
/** The object kind decides the shape of the reference that follows it. */
2166621667
CommentTarget CommentTarget():
2166721668
{
2166821669
CommentTarget result = new CommentTarget();
21670+
CommentTarget.Kind kind;
2166921671
Table name;
21670-
String constraintName;
21672+
String objectName;
2167121673
RoutineReference routine;
21674+
ColDataType type;
2167221675
}
2167321676
{
21677+
kind=CommentTargetKind(result) { result.setKind(kind); }
2167421678
(
21675-
<K_FUNCTION> routine=RoutineReference(false)
21676-
{ result.setKind(CommentTarget.Kind.FUNCTION); result.setRoutine(routine); }
21679+
LOOKAHEAD({ kind.isRoutine() })
21680+
routine=RoutineReference(kind == CommentTarget.Kind.AGGREGATE) { result.setRoutine(routine); }
2167721681
|
21678-
<K_CONSTRAINT> constraintName=RelObjectName()
21679-
{ result.setKind(CommentTarget.Kind.CONSTRAINT); result.setName(new Table(constraintName, false)); }
21682+
LOOKAHEAD({ kind.isOwnedByRelation() })
21683+
objectName=RelObjectName() { result.setName(new Table(objectName, false)); }
2168021684
<K_ON>
21681-
[ LOOKAHEAD({ getToken(1).kind == K_DOMAIN && !".".equals(getToken(2).image)
21682-
&& getToken(2).kind != K_IS }) <K_DOMAIN> { result.setOnDomain(true); } ]
21685+
[ LOOKAHEAD({ kind == CommentTarget.Kind.CONSTRAINT && getToken(1).kind == K_DOMAIN
21686+
&& !".".equals(getToken(2).image) && getToken(2).kind != K_IS })
21687+
<K_DOMAIN> { result.setOnDomain(true); } ]
2168321688
name=CommentObjectName() { result.setRelation(name); }
2168421689
|
21685-
(
21686-
<K_INDEX> { result.setKind(CommentTarget.Kind.INDEX); }
21687-
| <K_SCHEMA> { result.setKind(CommentTarget.Kind.SCHEMA); }
21688-
| <K_SEQUENCE> { result.setKind(CommentTarget.Kind.SEQUENCE); }
21689-
| <K_MATERIALIZED> <K_VIEW> { result.setKind(CommentTarget.Kind.MATERIALIZED_VIEW); }
21690-
| <K_DOMAIN> { result.setKind(CommentTarget.Kind.DOMAIN); }
21691-
| <K_TYPE> { result.setKind(CommentTarget.Kind.TYPE); }
21692-
)
21690+
LOOKAHEAD({ kind == CommentTarget.Kind.CAST })
21691+
"(" type=ColDataType() { result.setSourceType(type); }
21692+
<K_AS> type=ColDataType() { result.setTargetType(type); } ")"
21693+
|
2169321694
name=CommentObjectName() { result.setName(name); }
2169421695
)
2169521696
{ return result; }
2169621697
}
2169721698

21699+
/** Kind words without a token are matched contextually and remain ordinary identifiers. */
21700+
CommentTarget.Kind CommentTargetKind(CommentTarget target):
21701+
{ CommentTarget.Kind kind; }
21702+
{
21703+
(
21704+
<K_INDEX> { kind = CommentTarget.Kind.INDEX; }
21705+
| <K_SCHEMA> { kind = CommentTarget.Kind.SCHEMA; }
21706+
| <K_SEQUENCE> { kind = CommentTarget.Kind.SEQUENCE; }
21707+
| <K_MATERIALIZED> <K_VIEW> { kind = CommentTarget.Kind.MATERIALIZED_VIEW; }
21708+
| <K_DOMAIN> { kind = CommentTarget.Kind.DOMAIN; }
21709+
| <K_TYPE> { kind = CommentTarget.Kind.TYPE; }
21710+
| <K_FUNCTION> { kind = CommentTarget.Kind.FUNCTION; }
21711+
| <K_CONSTRAINT> { kind = CommentTarget.Kind.CONSTRAINT; }
21712+
| <K_TRIGGER> { kind = CommentTarget.Kind.TRIGGER; }
21713+
| <K_POLICY> { kind = CommentTarget.Kind.POLICY; }
21714+
| LOOKAHEAD({ isKeywordAhead("RULE") }) ContextualKeyword("RULE") { kind = CommentTarget.Kind.RULE; }
21715+
| <K_PROCEDURE> { kind = CommentTarget.Kind.PROCEDURE; }
21716+
| LOOKAHEAD({ isKeywordAhead("ROUTINE") }) ContextualKeyword("ROUTINE") { kind = CommentTarget.Kind.ROUTINE; }
21717+
| <K_AGGREGATE> { kind = CommentTarget.Kind.AGGREGATE; }
21718+
| <K_CAST> { kind = CommentTarget.Kind.CAST; }
21719+
| LOOKAHEAD({ isKeywordAhead("ACCESS") }) ContextualKeyword("ACCESS") ContextualKeyword("METHOD")
21720+
{ kind = CommentTarget.Kind.ACCESS_METHOD; }
21721+
| LOOKAHEAD({ isKeywordAhead("COLLATION") }) ContextualKeyword("COLLATION") { kind = CommentTarget.Kind.COLLATION; }
21722+
| <K_DATABASE> { kind = CommentTarget.Kind.DATABASE; }
21723+
| <K_EVENT> <K_TRIGGER> { kind = CommentTarget.Kind.EVENT_TRIGGER; }
21724+
| LOOKAHEAD({ isKeywordAhead("EXTENSION") }) ContextualKeyword("EXTENSION") { kind = CommentTarget.Kind.EXTENSION; }
21725+
| <K_FOREIGN>
21726+
(
21727+
<K_DATA> ContextualKeyword("WRAPPER") { kind = CommentTarget.Kind.FOREIGN_DATA_WRAPPER; }
21728+
| <K_TABLE> { kind = CommentTarget.Kind.FOREIGN_TABLE; }
21729+
)
21730+
| LOOKAHEAD({ isKeywordAhead("PROCEDURAL") || isKeywordAhead("LANGUAGE") })
21731+
[ LOOKAHEAD({ isKeywordAhead("PROCEDURAL") }) ContextualKeyword("PROCEDURAL") { target.setProcedural(true); } ]
21732+
ContextualKeyword("LANGUAGE") { kind = CommentTarget.Kind.LANGUAGE; }
21733+
| LOOKAHEAD({ isKeywordAhead("PUBLICATION") }) ContextualKeyword("PUBLICATION") { kind = CommentTarget.Kind.PUBLICATION; }
21734+
| LOOKAHEAD({ isKeywordAhead("ROLE") }) ContextualKeyword("ROLE") { kind = CommentTarget.Kind.ROLE; }
21735+
| LOOKAHEAD({ isKeywordAhead("SERVER") }) ContextualKeyword("SERVER") { kind = CommentTarget.Kind.SERVER; }
21736+
| LOOKAHEAD({ isKeywordAhead("STATISTICS") }) ContextualKeyword("STATISTICS") { kind = CommentTarget.Kind.STATISTICS; }
21737+
| LOOKAHEAD({ isKeywordAhead("SUBSCRIPTION") }) ContextualKeyword("SUBSCRIPTION") { kind = CommentTarget.Kind.SUBSCRIPTION; }
21738+
| <K_TABLESPACE> { kind = CommentTarget.Kind.TABLESPACE; }
21739+
)
21740+
{ return kind; }
21741+
}
21742+
2169821743
/** Consumes a keyword that remains an ordinary identifier outside this grammar context. */
2169921744
void ContextualKeyword(String expected):
2170021745
{ Token keyword; }
@@ -23130,7 +23175,9 @@ RoutineReference RoutineReference(boolean aggregate):
2313023175
(
2313123176
"*" { requireTypeDdlSyntax(aggregate, "Only aggregate signatures accept *"); result.setAllArguments(true); }
2313223177
|
23133-
[ argument=RoutineSignatureArgument() { arguments.add(argument); }
23178+
// ORDER is non-reserved, so ORDER BY without direct arguments is not an argument name
23179+
[ LOOKAHEAD(1, { getToken(1).kind != K_ORDER || getToken(2).kind != K_BY })
23180+
argument=RoutineSignatureArgument() { arguments.add(argument); }
2313423181
( "," argument=RoutineSignatureArgument() { arguments.add(argument); } )* ]
2313523182
[ <K_ORDER> <K_BY> { requireTypeDdlSyntax(aggregate, "ORDER BY requires an aggregate signature"); orderBy = new ArrayList<RoutineReference.Argument>(); }
2313623183
argument=RoutineSignatureArgument() { orderBy.add(argument); }

0 commit comments

Comments
 (0)