Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,15 @@ public enum Feature {
/** COMMENT ON EVENT TRIGGER, PUBLICATION or SUBSCRIPTION. */
commentOnEventTrigger, commentOnPublication, commentOnSubscription,

/** COMMENT ON OPERATOR, OPERATOR CLASS or OPERATOR FAMILY. */
commentOnOperator, commentOnOperatorClass, commentOnOperatorFamily,

/** COMMENT ON CONVERSION, LARGE OBJECT or TRANSFORM. */
commentOnConversion, commentOnLargeObject, commentOnTransform,

/** COMMENT ON TEXT SEARCH CONFIGURATION, DICTIONARY, PARSER or TEMPLATE. */
commentOnTextSearchConfiguration, commentOnTextSearchDictionary, commentOnTextSearchParser, commentOnTextSearchTemplate,

/**
* SQL "DESCRIBE" statement is allowed
*
Expand Down
108 changes: 105 additions & 3 deletions src/main/java/net/sf/jsqlparser/statement/comment/CommentTarget.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.RoutineReference;
import net.sf.jsqlparser.statement.create.table.ColDataType;
import net.sf.jsqlparser.statement.create.table.ExclusionOperator;

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

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

FOREIGN_DATA_WRAPPER, LANGUAGE, SERVER, EVENT_TRIGGER, PUBLICATION, SUBSCRIPTION;
FOREIGN_DATA_WRAPPER, LANGUAGE, SERVER, EVENT_TRIGGER, PUBLICATION, SUBSCRIPTION,

OPERATOR, OPERATOR_CLASS, OPERATOR_FAMILY, LARGE_OBJECT, CONVERSION, TRANSFORM,

TEXT_SEARCH_CONFIGURATION, TEXT_SEARCH_DICTIONARY, TEXT_SEARCH_PARSER, TEXT_SEARCH_TEMPLATE;

/**
* Whether the target is a routine signature, exposed by {@link CommentTarget#getRoutine()}.
Expand All @@ -40,6 +45,14 @@ public boolean isRoutine() {
public boolean isOwnedByRelation() {
return this == CONSTRAINT || this == TRIGGER || this == POLICY || this == RULE;
}

/**
* Whether the target is named within the index access method of
* {@link CommentTarget#getAccessMethod()}.
*/
public boolean isOwnedByAccessMethod() {
return this == OPERATOR_CLASS || this == OPERATOR_FAMILY;
}
}

private Kind kind;
Expand All @@ -50,6 +63,13 @@ public boolean isOwnedByRelation() {
private boolean procedural;
private ColDataType sourceType;
private ColDataType targetType;
private ExclusionOperator operator;
private ColDataType leftOperandType;
private ColDataType rightOperandType;
private String accessMethod;
private Long largeObjectOid;
private ColDataType transformType;
private String language;

public Kind getKind() {
return kind;
Expand All @@ -60,8 +80,8 @@ public void setKind(Kind kind) {
}

/**
* The object's identifier; using Table preserves the individual name components. Routine and
* cast targets have no name.
* The object's identifier; using Table preserves the individual name components. Routine, cast,
* operator, large object and transform targets have no name.
*/
public Table getName() {
return name;
Expand Down Expand Up @@ -126,6 +146,75 @@ public void setTargetType(ColDataType targetType) {
this.targetType = targetType;
}

/**
* The possibly schema-qualified operator of an {@link Kind#OPERATOR} target, read by the
* operator reference shared with exclusion constraints and ALTER EXTENSION.
*/
public ExclusionOperator getOperator() {
return operator;
}

public void setOperator(ExclusionOperator operator) {
this.operator = operator;
}

/** The left operand type of an {@link Kind#OPERATOR} target; null stands for NONE. */
public ColDataType getLeftOperandType() {
return leftOperandType;
}

public void setLeftOperandType(ColDataType leftOperandType) {
this.leftOperandType = leftOperandType;
}

/** The right operand type of an {@link Kind#OPERATOR} target; null stands for NONE. */
public ColDataType getRightOperandType() {
return rightOperandType;
}

public void setRightOperandType(ColDataType rightOperandType) {
this.rightOperandType = rightOperandType;
}

/**
* The index access method of an {@link Kind#OPERATOR_CLASS} or {@link Kind#OPERATOR_FAMILY}
* target.
*/
public String getAccessMethod() {
return accessMethod;
}

public void setAccessMethod(String accessMethod) {
this.accessMethod = accessMethod;
}

/** The OID of a {@link Kind#LARGE_OBJECT} target. */
public Long getLargeObjectOid() {
return largeObjectOid;
}

public void setLargeObjectOid(Long largeObjectOid) {
this.largeObjectOid = largeObjectOid;
}

/** The data type of a {@link Kind#TRANSFORM} target. */
public ColDataType getTransformType() {
return transformType;
}

public void setTransformType(ColDataType transformType) {
this.transformType = transformType;
}

/** The procedural language of a {@link Kind#TRANSFORM} target. */
public String getLanguage() {
return language;
}

public void setLanguage(String language) {
this.language = language;
}

/**
* Returns only an explicitly named table, foreign table or view, never an index, type,
* function, statistics object or domain.
Expand All @@ -150,6 +239,13 @@ public StringBuilder appendTo(StringBuilder builder, Consumer<Table> relationWri
builder.append(routine);
} else if (kind == Kind.CAST) {
builder.append('(').append(sourceType).append(" AS ").append(targetType).append(')');
} else if (kind == Kind.OPERATOR) {
builder.append(operator).append(" (").append(operandType(leftOperandType))
.append(", ").append(operandType(rightOperandType)).append(')');
} else if (kind == Kind.LARGE_OBJECT) {
builder.append(largeObjectOid);
} else if (kind == Kind.TRANSFORM) {
builder.append("FOR ").append(transformType).append(" LANGUAGE ").append(language);
} else if (namesRelation()) {
relationWriter.accept(name);
} else {
Expand All @@ -161,11 +257,17 @@ public StringBuilder appendTo(StringBuilder builder, Consumer<Table> relationWri
} else {
relationWriter.accept(relation);
}
} else if (kind.isOwnedByAccessMethod()) {
builder.append(" USING ").append(accessMethod);
}
}
return builder;
}

private static Object operandType(ColDataType type) {
return type == null ? "NONE" : type;
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@

import java.io.Serializable;

/** An exclusion operator, optionally qualified and enclosed in PostgreSQL OPERATOR(...). */
/**
* An exclusion operator, optionally qualified and enclosed in PostgreSQL OPERATOR(...). COMMENT ON
* OPERATOR uses the same reference without the OPERATOR keyword.
*/
public class ExclusionOperator implements Serializable {
private String schemaName;
private String name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ public enum PostgresqlVersion implements Version {
Feature.commentOnLanguage, Feature.commentOnServer,
Feature.commentOnEventTrigger, Feature.commentOnPublication,
Feature.commentOnSubscription,
Feature.commentOnOperator, Feature.commentOnOperatorClass,
Feature.commentOnOperatorFamily, Feature.commentOnConversion,
Feature.commentOnLargeObject, Feature.commentOnTransform,
Feature.commentOnTextSearchConfiguration,
Feature.commentOnTextSearchDictionary, Feature.commentOnTextSearchParser,
Feature.commentOnTextSearchTemplate,

// https://www.postgresql.org/docs/current/sql-createsequence.html
Feature.createSequence,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,26 @@ private static Feature targetFeature(CommentTarget.Kind kind) {
return Feature.commentOnPublication;
case SUBSCRIPTION:
return Feature.commentOnSubscription;
case OPERATOR:
return Feature.commentOnOperator;
case OPERATOR_CLASS:
return Feature.commentOnOperatorClass;
case OPERATOR_FAMILY:
return Feature.commentOnOperatorFamily;
case CONVERSION:
return Feature.commentOnConversion;
case LARGE_OBJECT:
return Feature.commentOnLargeObject;
case TRANSFORM:
return Feature.commentOnTransform;
case TEXT_SEARCH_CONFIGURATION:
return Feature.commentOnTextSearchConfiguration;
case TEXT_SEARCH_DICTIONARY:
return Feature.commentOnTextSearchDictionary;
case TEXT_SEARCH_PARSER:
return Feature.commentOnTextSearchParser;
case TEXT_SEARCH_TEMPLATE:
return Feature.commentOnTextSearchTemplate;
default:
throw new IllegalArgumentException("Unknown COMMENT target: " + kind);
}
Expand Down
102 changes: 95 additions & 7 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -21678,6 +21678,8 @@ CommentTarget CommentTarget():
String objectName;
RoutineReference routine;
ColDataType type;
ExclusionOperator operator;
Long oid;
}
{
kind=CommentTargetKind(result) { result.setKind(kind); }
Expand All @@ -21697,6 +21699,24 @@ CommentTarget CommentTarget():
"(" type=ColDataType() { result.setSourceType(type); }
<K_AS> type=ColDataType() { result.setTargetType(type); } ")"
|
LOOKAHEAD({ kind == CommentTarget.Kind.OPERATOR })
operator=PostgreSqlOperatorReference() { result.setOperator(operator); }
"(" type=ExtensionOperatorType() { result.setLeftOperandType(type); }
"," type=ExtensionOperatorType() { result.setRightOperandType(type); } ")"
{ requireTypeDdlSyntax(result.getLeftOperandType() != null || result.getRightOperandType() != null,
"An operator signature needs an operand type"); }
|
LOOKAHEAD({ kind.isOwnedByAccessMethod() })
name=CommentObjectName() { result.setName(name); }
<K_USING> objectName=RelObjectName() { result.setAccessMethod(objectName); }
|
LOOKAHEAD({ kind == CommentTarget.Kind.LARGE_OBJECT })
oid=ObjectIdentifier() { result.setLargeObjectOid(oid); }
|
LOOKAHEAD({ kind == CommentTarget.Kind.TRANSFORM })
<K_FOR> type=ColDataType() { result.setTransformType(type); }
ContextualKeyword("LANGUAGE") objectName=RelObjectName() { result.setLanguage(objectName); }
|
name=CommentObjectName() { result.setName(name); }
)
{ return result; }
Expand Down Expand Up @@ -21742,10 +21762,44 @@ CommentTarget.Kind CommentTargetKind(CommentTarget target):
| LOOKAHEAD({ isKeywordAhead("STATISTICS") }) ContextualKeyword("STATISTICS") { kind = CommentTarget.Kind.STATISTICS; }
| LOOKAHEAD({ isKeywordAhead("SUBSCRIPTION") }) ContextualKeyword("SUBSCRIPTION") { kind = CommentTarget.Kind.SUBSCRIPTION; }
| <K_TABLESPACE> { kind = CommentTarget.Kind.TABLESPACE; }
| LOOKAHEAD({ isKeywordAhead("OPERATOR") }) ContextualKeyword("OPERATOR") { kind = CommentTarget.Kind.OPERATOR; }
// a schema named CLASS or FAMILY may qualify an operator, as in class.+
[ LOOKAHEAD({ (isKeywordAhead("CLASS") || isKeywordAhead("FAMILY")) && !".".equals(getToken(2).image) })
( LOOKAHEAD({ isKeywordAhead("CLASS") }) ContextualKeyword("CLASS") { kind = CommentTarget.Kind.OPERATOR_CLASS; }
| ContextualKeyword("FAMILY") { kind = CommentTarget.Kind.OPERATOR_FAMILY; } ) ]
| LOOKAHEAD({ isKeywordAhead("LARGE") }) ContextualKeyword("LARGE") ContextualKeyword("OBJECT")
{ kind = CommentTarget.Kind.LARGE_OBJECT; }
| LOOKAHEAD({ isKeywordAhead("CONVERSION") }) ContextualKeyword("CONVERSION") { kind = CommentTarget.Kind.CONVERSION; }
| LOOKAHEAD({ isKeywordAhead("TRANSFORM") }) ContextualKeyword("TRANSFORM") { kind = CommentTarget.Kind.TRANSFORM; }
| LOOKAHEAD({ isKeywordAhead("TEXT") }) <K_TEXT_LITERAL> <K_SEARCH>
(
<K_PARSER> { kind = CommentTarget.Kind.TEXT_SEARCH_PARSER; }
| LOOKAHEAD({ isKeywordAhead("CONFIGURATION") }) ContextualKeyword("CONFIGURATION")
{ kind = CommentTarget.Kind.TEXT_SEARCH_CONFIGURATION; }
| LOOKAHEAD({ isKeywordAhead("DICTIONARY") }) ContextualKeyword("DICTIONARY")
{ kind = CommentTarget.Kind.TEXT_SEARCH_DICTIONARY; }
| ContextualKeyword("TEMPLATE") { kind = CommentTarget.Kind.TEXT_SEARCH_TEMPLATE; }
)
)
{ return kind; }
}

/** An unsigned 32-bit PostgreSQL object identifier, such as the OID of a large object. */
Long ObjectIdentifier():
{ Token token; long value; }
{
token=<S_LONG>
{
try {
value = Long.parseLong(token.image);
} catch (NumberFormatException exception) {
value = -1;
}
requireTypeDdlSyntax(value >= 0 && value <= 0xFFFFFFFFL, "OID out of range: " + token.image);
return value;
}
}

/** Consumes a keyword that remains an ordinary identifier outside this grammar context. */
void ContextualKeyword(String expected):
{ Token keyword; }
Expand Down Expand Up @@ -23201,15 +23255,49 @@ String ExtensionOperatorName():

/** Shared schema-qualified operator spelling used in DDL references. */
ExclusionOperator PostgreSqlOperatorReference():
{ String schema = null; Token token; }
{ String schema = null; String name; }
{
[ LOOKAHEAD(RelObjectName() ".") schema=RelObjectName() "." ]
( token="+" | token="-" | token="*" | token="/" | token="%" | token="="
| token=<OP_NOTEQUALSSTANDARD> | token=<OP_NOTEQUALSBANG> | token="<" | token=">"
| token=<OP_MINORTHANEQUALS> | token=<OP_GREATERTHANEQUALS> | token="&&" | token="@>" | token="<@" | token="~"
| token="~*" | token="!~" | token="!~*" | token="?" | token="?|" | token="?&"
| token=<OP_CONCAT> )
{ return new ExclusionOperator().withSchemaName(schema).withName(token.image); }
name=PostgreSqlOperatorName()
{ return new ExclusionOperator().withSchemaName(schema).withName(name); }
}

/**
* The lexer splits some PostgreSQL operators, such as <<= or -|-, into several tokens. Parts that
* touch each other are joined, because PostgreSQL reads them as one operator.
*/
String PostgreSqlOperatorName():
{ StringBuilder name = new StringBuilder(); Token token; }
{
token=PostgreSqlOperatorPart() { name.append(token.image); }
(
LOOKAHEAD(1, { getToken(1).specialToken == null && getToken(1).beginLine == getToken(0).endLine
&& getToken(1).beginColumn == getToken(0).endColumn + 1 })
token=PostgreSqlOperatorPart() { name.append(token.image); }
)*
{ return name.toString(); }
}

Token PostgreSqlOperatorPart():
{ Token token; }
{
(
token="+" | token="-" | token="*" | token="/" | token="%" | token=<OP_CARET> | token=<OP_POWER>
| token="<" | token=">" | token="=" | token="==" | token=<OP_MINORTHANEQUALS> | token=<OP_GREATERTHANEQUALS>
| token=<OP_NOTEQUALSSTANDARD> | token=<OP_NOTEQUALSBANG> | token=<OP_NOTEQUALSHAT> | token="*=" | token="=*"
| token=<OP_COSINESIMILARITY> | token=<OP_CONCAT> | token=<OP_PIPE> | token="|>" | token="&" | token=<OP_DOUBLEAND>
| token=<OP_CONTAINS> | token=<OP_CONTAINEDBY> | token=<OP_LSHIFT> | token=<OP_RSHIFT>
| token="~" | token="~*" | token="!~" | token="!~*" | token="~~" | token="~~*" | token="!~~" | token="!~~*"
| token="~~~" | token="!" | token=<K_AT_SIGN> | token="@>" | token="<@" | token="@@" | token=<OP_JSON_PATH_EXISTS>
| token="?" | token="?|" | token="?&" | token="->" | token="->>" | token=<S_HASH_OPERATOR> | token="#>"
| token="#>>" | token=<OP_JSON_PATH_DELETE> | token="-#" | token="<->" | token="<#>"
)
{
// PostgreSQL reads "< =" as two operators, while the lexer tolerates whitespace inside it
requireTypeDdlSyntax(!token.image.matches("(?s).*\\s.*"),
"Unexpected whitespace in operator " + token.image);
return token;
}
}

ColDataType ExtensionOperatorType():
Expand Down
Loading
Loading