Skip to content

Commit f787596

Browse files
committed
feat: parse COMMENT ON operators, operator classes, text search objects and large objects
Extend CommentTarget with the PostgreSQL COMMENT ON kinds that still failed: OPERATOR with its operand types (NONE for a missing operand), OPERATOR CLASS and OPERATOR FAMILY with their access method, LARGE OBJECT with its OID, TEXT SEARCH CONFIGURATION, DICTIONARY, PARSER and TEMPLATE, CONVERSION, and TRANSFORM FOR type LANGUAGE lang. COMMENT ON OPERATOR reuses the operator reference shared with exclusion constraints and ALTER EXTENSION. That production now accepts the JSON, pattern, bitwise and geometric operators, and joins operator parts that the lexer splits but that touch each other, such as <<= or -|-. The new kind words stay non-reserved. Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 29dcb3a commit f787596

11 files changed

Lines changed: 490 additions & 18 deletions

File tree

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,15 @@ public enum Feature {
665665
/** COMMENT ON EVENT TRIGGER, PUBLICATION or SUBSCRIPTION. */
666666
commentOnEventTrigger, commentOnPublication, commentOnSubscription,
667667

668+
/** COMMENT ON OPERATOR, OPERATOR CLASS or OPERATOR FAMILY. */
669+
commentOnOperator, commentOnOperatorClass, commentOnOperatorFamily,
670+
671+
/** COMMENT ON CONVERSION, LARGE OBJECT or TRANSFORM. */
672+
commentOnConversion, commentOnLargeObject, commentOnTransform,
673+
674+
/** COMMENT ON TEXT SEARCH CONFIGURATION, DICTIONARY, PARSER or TEMPLATE. */
675+
commentOnTextSearchConfiguration, commentOnTextSearchDictionary, commentOnTextSearchParser, commentOnTextSearchTemplate,
676+
668677
/**
669678
* SQL "DESCRIBE" statement is allowed
670679
*

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

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import net.sf.jsqlparser.schema.Table;
1515
import net.sf.jsqlparser.statement.RoutineReference;
1616
import net.sf.jsqlparser.statement.create.table.ColDataType;
17+
import net.sf.jsqlparser.statement.create.table.ExclusionOperator;
1718

1819
/** A catalog object addressed by COMMENT, rather than a function invocation or a query. */
1920
public class CommentTarget implements Serializable {
@@ -24,7 +25,11 @@ public enum Kind {
2425

2526
COLLATION, FOREIGN_TABLE, STATISTICS, DATABASE, ROLE, TABLESPACE, ACCESS_METHOD, EXTENSION,
2627

27-
FOREIGN_DATA_WRAPPER, LANGUAGE, SERVER, EVENT_TRIGGER, PUBLICATION, SUBSCRIPTION;
28+
FOREIGN_DATA_WRAPPER, LANGUAGE, SERVER, EVENT_TRIGGER, PUBLICATION, SUBSCRIPTION,
29+
30+
OPERATOR, OPERATOR_CLASS, OPERATOR_FAMILY, LARGE_OBJECT, CONVERSION, TRANSFORM,
31+
32+
TEXT_SEARCH_CONFIGURATION, TEXT_SEARCH_DICTIONARY, TEXT_SEARCH_PARSER, TEXT_SEARCH_TEMPLATE;
2833

2934
/**
3035
* Whether the target is a routine signature, exposed by {@link CommentTarget#getRoutine()}.
@@ -40,6 +45,14 @@ public boolean isRoutine() {
4045
public boolean isOwnedByRelation() {
4146
return this == CONSTRAINT || this == TRIGGER || this == POLICY || this == RULE;
4247
}
48+
49+
/**
50+
* Whether the target is named within the index access method of
51+
* {@link CommentTarget#getAccessMethod()}.
52+
*/
53+
public boolean isOwnedByAccessMethod() {
54+
return this == OPERATOR_CLASS || this == OPERATOR_FAMILY;
55+
}
4356
}
4457

4558
private Kind kind;
@@ -50,6 +63,13 @@ public boolean isOwnedByRelation() {
5063
private boolean procedural;
5164
private ColDataType sourceType;
5265
private ColDataType targetType;
66+
private ExclusionOperator operator;
67+
private ColDataType leftOperandType;
68+
private ColDataType rightOperandType;
69+
private String accessMethod;
70+
private Long largeObjectOid;
71+
private ColDataType transformType;
72+
private String language;
5373

5474
public Kind getKind() {
5575
return kind;
@@ -60,8 +80,8 @@ public void setKind(Kind kind) {
6080
}
6181

6282
/**
63-
* The object's identifier; using Table preserves the individual name components. Routine and
64-
* cast targets have no name.
83+
* The object's identifier; using Table preserves the individual name components. Routine, cast,
84+
* operator, large object and transform targets have no name.
6585
*/
6686
public Table getName() {
6787
return name;
@@ -126,6 +146,75 @@ public void setTargetType(ColDataType targetType) {
126146
this.targetType = targetType;
127147
}
128148

149+
/**
150+
* The possibly schema-qualified operator of an {@link Kind#OPERATOR} target, read by the
151+
* operator reference shared with exclusion constraints and ALTER EXTENSION.
152+
*/
153+
public ExclusionOperator getOperator() {
154+
return operator;
155+
}
156+
157+
public void setOperator(ExclusionOperator operator) {
158+
this.operator = operator;
159+
}
160+
161+
/** The left operand type of an {@link Kind#OPERATOR} target; null stands for NONE. */
162+
public ColDataType getLeftOperandType() {
163+
return leftOperandType;
164+
}
165+
166+
public void setLeftOperandType(ColDataType leftOperandType) {
167+
this.leftOperandType = leftOperandType;
168+
}
169+
170+
/** The right operand type of an {@link Kind#OPERATOR} target; null stands for NONE. */
171+
public ColDataType getRightOperandType() {
172+
return rightOperandType;
173+
}
174+
175+
public void setRightOperandType(ColDataType rightOperandType) {
176+
this.rightOperandType = rightOperandType;
177+
}
178+
179+
/**
180+
* The index access method of an {@link Kind#OPERATOR_CLASS} or {@link Kind#OPERATOR_FAMILY}
181+
* target.
182+
*/
183+
public String getAccessMethod() {
184+
return accessMethod;
185+
}
186+
187+
public void setAccessMethod(String accessMethod) {
188+
this.accessMethod = accessMethod;
189+
}
190+
191+
/** The OID of a {@link Kind#LARGE_OBJECT} target. */
192+
public Long getLargeObjectOid() {
193+
return largeObjectOid;
194+
}
195+
196+
public void setLargeObjectOid(Long largeObjectOid) {
197+
this.largeObjectOid = largeObjectOid;
198+
}
199+
200+
/** The data type of a {@link Kind#TRANSFORM} target. */
201+
public ColDataType getTransformType() {
202+
return transformType;
203+
}
204+
205+
public void setTransformType(ColDataType transformType) {
206+
this.transformType = transformType;
207+
}
208+
209+
/** The procedural language of a {@link Kind#TRANSFORM} target. */
210+
public String getLanguage() {
211+
return language;
212+
}
213+
214+
public void setLanguage(String language) {
215+
this.language = language;
216+
}
217+
129218
/**
130219
* Returns only an explicitly named table, foreign table or view, never an index, type,
131220
* function, statistics object or domain.
@@ -150,6 +239,13 @@ public StringBuilder appendTo(StringBuilder builder, Consumer<Table> relationWri
150239
builder.append(routine);
151240
} else if (kind == Kind.CAST) {
152241
builder.append('(').append(sourceType).append(" AS ").append(targetType).append(')');
242+
} else if (kind == Kind.OPERATOR) {
243+
builder.append(operator).append(" (").append(operandType(leftOperandType))
244+
.append(", ").append(operandType(rightOperandType)).append(')');
245+
} else if (kind == Kind.LARGE_OBJECT) {
246+
builder.append(largeObjectOid);
247+
} else if (kind == Kind.TRANSFORM) {
248+
builder.append("FOR ").append(transformType).append(" LANGUAGE ").append(language);
153249
} else if (namesRelation()) {
154250
relationWriter.accept(name);
155251
} else {
@@ -161,11 +257,17 @@ public StringBuilder appendTo(StringBuilder builder, Consumer<Table> relationWri
161257
} else {
162258
relationWriter.accept(relation);
163259
}
260+
} else if (kind.isOwnedByAccessMethod()) {
261+
builder.append(" USING ").append(accessMethod);
164262
}
165263
}
166264
return builder;
167265
}
168266

267+
private static Object operandType(ColDataType type) {
268+
return type == null ? "NONE" : type;
269+
}
270+
169271
@Override
170272
public String toString() {
171273
StringBuilder builder = new StringBuilder();

‎src/main/java/net/sf/jsqlparser/statement/create/table/ExclusionOperator.java‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111

1212
import java.io.Serializable;
1313

14-
/** An exclusion operator, optionally qualified and enclosed in PostgreSQL OPERATOR(...). */
14+
/**
15+
* An exclusion operator, optionally qualified and enclosed in PostgreSQL OPERATOR(...). COMMENT ON
16+
* OPERATOR uses the same reference without the OPERATOR keyword.
17+
*/
1518
public class ExclusionOperator implements Serializable {
1619
private String schemaName;
1720
private String name;

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ public enum PostgresqlVersion implements Version {
105105
Feature.commentOnLanguage, Feature.commentOnServer,
106106
Feature.commentOnEventTrigger, Feature.commentOnPublication,
107107
Feature.commentOnSubscription,
108+
Feature.commentOnOperator, Feature.commentOnOperatorClass,
109+
Feature.commentOnOperatorFamily, Feature.commentOnConversion,
110+
Feature.commentOnLargeObject, Feature.commentOnTransform,
111+
Feature.commentOnTextSearchConfiguration,
112+
Feature.commentOnTextSearchDictionary, Feature.commentOnTextSearchParser,
113+
Feature.commentOnTextSearchTemplate,
108114

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

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,26 @@ private static Feature targetFeature(CommentTarget.Kind kind) {
8888
return Feature.commentOnPublication;
8989
case SUBSCRIPTION:
9090
return Feature.commentOnSubscription;
91+
case OPERATOR:
92+
return Feature.commentOnOperator;
93+
case OPERATOR_CLASS:
94+
return Feature.commentOnOperatorClass;
95+
case OPERATOR_FAMILY:
96+
return Feature.commentOnOperatorFamily;
97+
case CONVERSION:
98+
return Feature.commentOnConversion;
99+
case LARGE_OBJECT:
100+
return Feature.commentOnLargeObject;
101+
case TRANSFORM:
102+
return Feature.commentOnTransform;
103+
case TEXT_SEARCH_CONFIGURATION:
104+
return Feature.commentOnTextSearchConfiguration;
105+
case TEXT_SEARCH_DICTIONARY:
106+
return Feature.commentOnTextSearchDictionary;
107+
case TEXT_SEARCH_PARSER:
108+
return Feature.commentOnTextSearchParser;
109+
case TEXT_SEARCH_TEMPLATE:
110+
return Feature.commentOnTextSearchTemplate;
91111
default:
92112
throw new IllegalArgumentException("Unknown COMMENT target: " + kind);
93113
}

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

Lines changed: 95 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20300,6 +20300,8 @@ CommentTarget CommentTarget():
2030020300
String objectName;
2030120301
RoutineReference routine;
2030220302
ColDataType type;
20303+
ExclusionOperator operator;
20304+
Long oid;
2030320305
}
2030420306
{
2030520307
kind=CommentTargetKind(result) { result.setKind(kind); }
@@ -20319,6 +20321,24 @@ CommentTarget CommentTarget():
2031920321
"(" type=ColDataType() { result.setSourceType(type); }
2032020322
<K_AS> type=ColDataType() { result.setTargetType(type); } ")"
2032120323
|
20324+
LOOKAHEAD({ kind == CommentTarget.Kind.OPERATOR })
20325+
operator=PostgreSqlOperatorReference() { result.setOperator(operator); }
20326+
"(" type=ExtensionOperatorType() { result.setLeftOperandType(type); }
20327+
"," type=ExtensionOperatorType() { result.setRightOperandType(type); } ")"
20328+
{ requireTypeDdlSyntax(result.getLeftOperandType() != null || result.getRightOperandType() != null,
20329+
"An operator signature needs an operand type"); }
20330+
|
20331+
LOOKAHEAD({ kind.isOwnedByAccessMethod() })
20332+
name=CommentObjectName() { result.setName(name); }
20333+
<K_USING> objectName=RelObjectName() { result.setAccessMethod(objectName); }
20334+
|
20335+
LOOKAHEAD({ kind == CommentTarget.Kind.LARGE_OBJECT })
20336+
oid=ObjectIdentifier() { result.setLargeObjectOid(oid); }
20337+
|
20338+
LOOKAHEAD({ kind == CommentTarget.Kind.TRANSFORM })
20339+
<K_FOR> type=ColDataType() { result.setTransformType(type); }
20340+
ContextualKeyword("LANGUAGE") objectName=RelObjectName() { result.setLanguage(objectName); }
20341+
|
2032220342
name=CommentObjectName() { result.setName(name); }
2032320343
)
2032420344
{ return result; }
@@ -20364,10 +20384,44 @@ CommentTarget.Kind CommentTargetKind(CommentTarget target):
2036420384
| LOOKAHEAD({ isKeywordAhead("STATISTICS") }) ContextualKeyword("STATISTICS") { kind = CommentTarget.Kind.STATISTICS; }
2036520385
| LOOKAHEAD({ isKeywordAhead("SUBSCRIPTION") }) ContextualKeyword("SUBSCRIPTION") { kind = CommentTarget.Kind.SUBSCRIPTION; }
2036620386
| <K_TABLESPACE> { kind = CommentTarget.Kind.TABLESPACE; }
20387+
| LOOKAHEAD({ isKeywordAhead("OPERATOR") }) ContextualKeyword("OPERATOR") { kind = CommentTarget.Kind.OPERATOR; }
20388+
// a schema named CLASS or FAMILY may qualify an operator, as in class.+
20389+
[ LOOKAHEAD({ (isKeywordAhead("CLASS") || isKeywordAhead("FAMILY")) && !".".equals(getToken(2).image) })
20390+
( LOOKAHEAD({ isKeywordAhead("CLASS") }) ContextualKeyword("CLASS") { kind = CommentTarget.Kind.OPERATOR_CLASS; }
20391+
| ContextualKeyword("FAMILY") { kind = CommentTarget.Kind.OPERATOR_FAMILY; } ) ]
20392+
| LOOKAHEAD({ isKeywordAhead("LARGE") }) ContextualKeyword("LARGE") ContextualKeyword("OBJECT")
20393+
{ kind = CommentTarget.Kind.LARGE_OBJECT; }
20394+
| LOOKAHEAD({ isKeywordAhead("CONVERSION") }) ContextualKeyword("CONVERSION") { kind = CommentTarget.Kind.CONVERSION; }
20395+
| LOOKAHEAD({ isKeywordAhead("TRANSFORM") }) ContextualKeyword("TRANSFORM") { kind = CommentTarget.Kind.TRANSFORM; }
20396+
| LOOKAHEAD({ isKeywordAhead("TEXT") }) <K_TEXT_LITERAL> <K_SEARCH>
20397+
(
20398+
<K_PARSER> { kind = CommentTarget.Kind.TEXT_SEARCH_PARSER; }
20399+
| LOOKAHEAD({ isKeywordAhead("CONFIGURATION") }) ContextualKeyword("CONFIGURATION")
20400+
{ kind = CommentTarget.Kind.TEXT_SEARCH_CONFIGURATION; }
20401+
| LOOKAHEAD({ isKeywordAhead("DICTIONARY") }) ContextualKeyword("DICTIONARY")
20402+
{ kind = CommentTarget.Kind.TEXT_SEARCH_DICTIONARY; }
20403+
| ContextualKeyword("TEMPLATE") { kind = CommentTarget.Kind.TEXT_SEARCH_TEMPLATE; }
20404+
)
2036720405
)
2036820406
{ return kind; }
2036920407
}
2037020408

20409+
/** An unsigned 32-bit PostgreSQL object identifier, such as the OID of a large object. */
20410+
Long ObjectIdentifier():
20411+
{ Token token; long value; }
20412+
{
20413+
token=<S_LONG>
20414+
{
20415+
try {
20416+
value = Long.parseLong(token.image);
20417+
} catch (NumberFormatException exception) {
20418+
value = -1;
20419+
}
20420+
requireTypeDdlSyntax(value >= 0 && value <= 0xFFFFFFFFL, "OID out of range: " + token.image);
20421+
return value;
20422+
}
20423+
}
20424+
2037120425
/** Consumes a keyword that remains an ordinary identifier outside this grammar context. */
2037220426
void ContextualKeyword(String expected):
2037320427
{ Token keyword; }
@@ -21492,15 +21546,49 @@ String ExtensionOperatorName():
2149221546

2149321547
/** Shared schema-qualified operator spelling used in DDL references. */
2149421548
ExclusionOperator PostgreSqlOperatorReference():
21495-
{ String schema = null; Token token; }
21549+
{ String schema = null; String name; }
2149621550
{
2149721551
[ LOOKAHEAD(RelObjectName() ".") schema=RelObjectName() "." ]
21498-
( token="+" | token="-" | token="*" | token="/" | token="%" | token="="
21499-
| token=<OP_NOTEQUALSSTANDARD> | token=<OP_NOTEQUALSBANG> | token="<" | token=">"
21500-
| token=<OP_MINORTHANEQUALS> | token=<OP_GREATERTHANEQUALS> | token="&&" | token="@>" | token="<@" | token="~"
21501-
| token="~*" | token="!~" | token="!~*" | token="?" | token="?|" | token="?&"
21502-
| token=<OP_CONCAT> )
21503-
{ return new ExclusionOperator().withSchemaName(schema).withName(token.image); }
21552+
name=PostgreSqlOperatorName()
21553+
{ return new ExclusionOperator().withSchemaName(schema).withName(name); }
21554+
}
21555+
21556+
/**
21557+
* The lexer splits some PostgreSQL operators, such as <<= or -|-, into several tokens. Parts that
21558+
* touch each other are joined, because PostgreSQL reads them as one operator.
21559+
*/
21560+
String PostgreSqlOperatorName():
21561+
{ StringBuilder name = new StringBuilder(); Token token; }
21562+
{
21563+
token=PostgreSqlOperatorPart() { name.append(token.image); }
21564+
(
21565+
LOOKAHEAD(1, { getToken(1).specialToken == null && getToken(1).beginLine == getToken(0).endLine
21566+
&& getToken(1).beginColumn == getToken(0).endColumn + 1 })
21567+
token=PostgreSqlOperatorPart() { name.append(token.image); }
21568+
)*
21569+
{ return name.toString(); }
21570+
}
21571+
21572+
Token PostgreSqlOperatorPart():
21573+
{ Token token; }
21574+
{
21575+
(
21576+
token="+" | token="-" | token="*" | token="/" | token="%" | token=<OP_CARET> | token=<OP_POWER>
21577+
| token="<" | token=">" | token="=" | token="==" | token=<OP_MINORTHANEQUALS> | token=<OP_GREATERTHANEQUALS>
21578+
| token=<OP_NOTEQUALSSTANDARD> | token=<OP_NOTEQUALSBANG> | token=<OP_NOTEQUALSHAT> | token="*=" | token="=*"
21579+
| token=<OP_COSINESIMILARITY> | token=<OP_CONCAT> | token=<OP_PIPE> | token="|>" | token="&" | token=<OP_DOUBLEAND>
21580+
| token=<OP_CONTAINS> | token=<OP_CONTAINEDBY> | token=<OP_LSHIFT> | token=<OP_RSHIFT>
21581+
| token="~" | token="~*" | token="!~" | token="!~*" | token="~~" | token="~~*" | token="!~~" | token="!~~*"
21582+
| token="~~~" | token="!" | token=<K_AT_SIGN> | token="@>" | token="<@" | token="@@" | token=<OP_JSON_PATH_EXISTS>
21583+
| token="?" | token="?|" | token="?&" | token="->" | token="->>" | token=<S_HASH_OPERATOR> | token="#>"
21584+
| token="#>>" | token=<OP_JSON_PATH_DELETE> | token="-#" | token="<->" | token="<#>"
21585+
)
21586+
{
21587+
// PostgreSQL reads "< =" as two operators, while the lexer tolerates whitespace inside it
21588+
requireTypeDdlSyntax(!token.image.matches("(?s).*\\s.*"),
21589+
"Unexpected whitespace in operator " + token.image);
21590+
return token;
21591+
}
2150421592
}
2150521593

2150621594
ColDataType ExtensionOperatorType():

0 commit comments

Comments
 (0)