Skip to content
Open
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
18 changes: 18 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 @@ -647,6 +647,24 @@ public enum Feature {
/** Additional structured catalog targets of COMMENT ON. */
commentOnIndex, commentOnSchema, commentOnSequence, commentOnDomain, commentOnType, commentOnMaterializedView, commentOnFunction, commentOnConstraint,

/** COMMENT ON TRIGGER, POLICY or RULE within a table. */
commentOnTrigger, commentOnPolicy, commentOnRule,

/** COMMENT ON PROCEDURE, ROUTINE, AGGREGATE or CAST. */
commentOnProcedure, commentOnRoutine, commentOnAggregate, commentOnCast,

/** COMMENT ON COLLATION, FOREIGN TABLE or STATISTICS. */
commentOnCollation, commentOnForeignTable, commentOnStatistics,

/** COMMENT ON DATABASE, ROLE or TABLESPACE. */
commentOnDatabase, commentOnRole, commentOnTablespace,

/** COMMENT ON ACCESS METHOD, EXTENSION, FOREIGN DATA WRAPPER, LANGUAGE or SERVER. */
commentOnAccessMethod, commentOnExtension, commentOnForeignDataWrapper, commentOnLanguage, commentOnServer,

/** COMMENT ON EVENT TRIGGER, PUBLICATION or SUBSCRIPTION. */
commentOnEventTrigger, commentOnPublication, commentOnSubscription,

/**
* SQL "DESCRIBE" statement is allowed
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,43 @@
import java.util.function.Consumer;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.RoutineReference;
import net.sf.jsqlparser.statement.create.table.ColDataType;

/** A catalog object addressed by COMMENT, rather than a function invocation or a query. */
public class CommentTarget implements Serializable {
public enum Kind {
INDEX, SCHEMA, SEQUENCE, DOMAIN, TYPE, MATERIALIZED_VIEW, FUNCTION, CONSTRAINT
INDEX, SCHEMA, SEQUENCE, DOMAIN, TYPE, MATERIALIZED_VIEW, FUNCTION, CONSTRAINT,

TRIGGER, POLICY, RULE, PROCEDURE, ROUTINE, AGGREGATE, CAST,

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

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

/**
* Whether the target is a routine signature, exposed by {@link CommentTarget#getRoutine()}.
*/
public boolean isRoutine() {
return this == FUNCTION || this == PROCEDURE || this == ROUTINE || this == AGGREGATE;
}

/**
* Whether the target is named within the table or domain of
* {@link CommentTarget#getRelation()}.
*/
public boolean isOwnedByRelation() {
return this == CONSTRAINT || this == TRIGGER || this == POLICY || this == RULE;
}
}

private Kind kind;
private Table name;
private RoutineReference routine;
private Table relation;
private boolean onDomain;
private boolean procedural;
private ColDataType sourceType;
private ColDataType targetType;

public Kind getKind() {
return kind;
Expand All @@ -34,7 +59,10 @@ public void setKind(Kind kind) {
this.kind = kind;
}

/** The object's identifier; using Table preserves the individual name components. */
/**
* The object's identifier; using Table preserves the individual name components. Routine and
* cast targets have no name.
*/
public Table getName() {
return name;
}
Expand All @@ -51,7 +79,10 @@ public void setRoutine(RoutineReference routine) {
this.routine = routine;
}

/** The table or domain owning a constraint, distinguished by {@link #isOnDomain()}. */
/**
* The table owning a constraint, trigger, policy or rule, or the domain owning a constraint,
* distinguished by {@link #isOnDomain()}.
*/
public Table getRelation() {
return relation;
}
Expand All @@ -68,23 +99,62 @@ public void setOnDomain(boolean onDomain) {
this.onDomain = onDomain;
}

/** Returns only an explicitly named table/view, never an index, type, function or domain. */
/** Whether a {@link Kind#LANGUAGE} target was written as PROCEDURAL LANGUAGE. */
public boolean isProcedural() {
return procedural;
}

public void setProcedural(boolean procedural) {
this.procedural = procedural;
}

/** The source type of a {@link Kind#CAST} target. */
public ColDataType getSourceType() {
return sourceType;
}

public void setSourceType(ColDataType sourceType) {
this.sourceType = sourceType;
}

/** The target type of a {@link Kind#CAST} target. */
public ColDataType getTargetType() {
return targetType;
}

public void setTargetType(ColDataType targetType) {
this.targetType = targetType;
}

/**
* Returns only an explicitly named table, foreign table or view, never an index, type,
* function, statistics object or domain.
*/
public Table getReferencedRelation() {
if (kind == Kind.MATERIALIZED_VIEW) {
if (namesRelation()) {
return name;
}
return kind == Kind.CONSTRAINT && !onDomain ? relation : null;
return kind != null && kind.isOwnedByRelation() && !onDomain ? relation : null;
}

private boolean namesRelation() {
return kind == Kind.MATERIALIZED_VIEW || kind == Kind.FOREIGN_TABLE;
}

public StringBuilder appendTo(StringBuilder builder, Consumer<Table> relationWriter) {
if (kind == Kind.LANGUAGE && procedural) {
builder.append("PROCEDURAL ");
}
builder.append(kind.name().replace('_', ' ')).append(' ');
if (kind == Kind.FUNCTION) {
if (kind.isRoutine()) {
builder.append(routine);
} else if (kind == Kind.MATERIALIZED_VIEW) {
} else if (kind == Kind.CAST) {
builder.append('(').append(sourceType).append(" AS ").append(targetType).append(')');
} else if (namesRelation()) {
relationWriter.accept(name);
} else {
builder.append(name);
if (kind == Kind.CONSTRAINT) {
if (kind.isOwnedByRelation()) {
builder.append(" ON ");
if (onDomain) {
builder.append("DOMAIN ").append(relation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ public enum PostgresqlVersion implements Version {
Feature.commentOnDomain, Feature.commentOnType,
Feature.commentOnMaterializedView,
Feature.commentOnFunction, Feature.commentOnConstraint,
Feature.commentOnTrigger, Feature.commentOnPolicy, Feature.commentOnRule,
Feature.commentOnProcedure, Feature.commentOnRoutine,
Feature.commentOnAggregate, Feature.commentOnCast,
Feature.commentOnCollation, Feature.commentOnForeignTable,
Feature.commentOnStatistics, Feature.commentOnDatabase, Feature.commentOnRole,
Feature.commentOnTablespace, Feature.commentOnAccessMethod,
Feature.commentOnExtension, Feature.commentOnForeignDataWrapper,
Feature.commentOnLanguage, Feature.commentOnServer,
Feature.commentOnEventTrigger, Feature.commentOnPublication,
Feature.commentOnSubscription,

// 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 @@ -46,6 +46,48 @@ private static Feature targetFeature(CommentTarget.Kind kind) {
return Feature.commentOnFunction;
case CONSTRAINT:
return Feature.commentOnConstraint;
case TRIGGER:
return Feature.commentOnTrigger;
case POLICY:
return Feature.commentOnPolicy;
case RULE:
return Feature.commentOnRule;
case PROCEDURE:
return Feature.commentOnProcedure;
case ROUTINE:
return Feature.commentOnRoutine;
case AGGREGATE:
return Feature.commentOnAggregate;
case CAST:
return Feature.commentOnCast;
case COLLATION:
return Feature.commentOnCollation;
case FOREIGN_TABLE:
return Feature.commentOnForeignTable;
case STATISTICS:
return Feature.commentOnStatistics;
case DATABASE:
return Feature.commentOnDatabase;
case ROLE:
return Feature.commentOnRole;
case TABLESPACE:
return Feature.commentOnTablespace;
case ACCESS_METHOD:
return Feature.commentOnAccessMethod;
case EXTENSION:
return Feature.commentOnExtension;
case FOREIGN_DATA_WRAPPER:
return Feature.commentOnForeignDataWrapper;
case LANGUAGE:
return Feature.commentOnLanguage;
case SERVER:
return Feature.commentOnServer;
case EVENT_TRIGGER:
return Feature.commentOnEventTrigger;
case PUBLICATION:
return Feature.commentOnPublication;
case SUBSCRIPTION:
return Feature.commentOnSubscription;
default:
throw new IllegalArgumentException("Unknown COMMENT target: " + kind);
}
Expand Down
79 changes: 63 additions & 16 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -20291,38 +20291,83 @@ Table CommentObjectName():
: new Table(names.getNames()); }
}

/** The object kind decides the shape of the reference that follows it. */
CommentTarget CommentTarget():
{
CommentTarget result = new CommentTarget();
CommentTarget.Kind kind;
Table name;
String constraintName;
String objectName;
RoutineReference routine;
ColDataType type;
}
{
kind=CommentTargetKind(result) { result.setKind(kind); }
(
<K_FUNCTION> routine=RoutineReference(false)
{ result.setKind(CommentTarget.Kind.FUNCTION); result.setRoutine(routine); }
LOOKAHEAD({ kind.isRoutine() })
routine=RoutineReference(kind == CommentTarget.Kind.AGGREGATE) { result.setRoutine(routine); }
|
<K_CONSTRAINT> constraintName=RelObjectName()
{ result.setKind(CommentTarget.Kind.CONSTRAINT); result.setName(new Table(constraintName, false)); }
LOOKAHEAD({ kind.isOwnedByRelation() })
objectName=RelObjectName() { result.setName(new Table(objectName, false)); }
<K_ON>
[ LOOKAHEAD({ getToken(1).kind == K_DOMAIN && !".".equals(getToken(2).image)
&& getToken(2).kind != K_IS }) <K_DOMAIN> { result.setOnDomain(true); } ]
[ LOOKAHEAD({ kind == CommentTarget.Kind.CONSTRAINT && getToken(1).kind == K_DOMAIN
&& !".".equals(getToken(2).image) && getToken(2).kind != K_IS })
<K_DOMAIN> { result.setOnDomain(true); } ]
name=CommentObjectName() { result.setRelation(name); }
|
(
<K_INDEX> { result.setKind(CommentTarget.Kind.INDEX); }
| <K_SCHEMA> { result.setKind(CommentTarget.Kind.SCHEMA); }
| <K_SEQUENCE> { result.setKind(CommentTarget.Kind.SEQUENCE); }
| <K_MATERIALIZED> <K_VIEW> { result.setKind(CommentTarget.Kind.MATERIALIZED_VIEW); }
| <K_DOMAIN> { result.setKind(CommentTarget.Kind.DOMAIN); }
| <K_TYPE> { result.setKind(CommentTarget.Kind.TYPE); }
)
LOOKAHEAD({ kind == CommentTarget.Kind.CAST })
"(" type=ColDataType() { result.setSourceType(type); }
<K_AS> type=ColDataType() { result.setTargetType(type); } ")"
|
name=CommentObjectName() { result.setName(name); }
)
{ return result; }
}

/** Kind words without a token are matched contextually and remain ordinary identifiers. */
CommentTarget.Kind CommentTargetKind(CommentTarget target):
{ CommentTarget.Kind kind; }
{
(
<K_INDEX> { kind = CommentTarget.Kind.INDEX; }
| <K_SCHEMA> { kind = CommentTarget.Kind.SCHEMA; }
| <K_SEQUENCE> { kind = CommentTarget.Kind.SEQUENCE; }
| <K_MATERIALIZED> <K_VIEW> { kind = CommentTarget.Kind.MATERIALIZED_VIEW; }
| <K_DOMAIN> { kind = CommentTarget.Kind.DOMAIN; }
| <K_TYPE> { kind = CommentTarget.Kind.TYPE; }
| <K_FUNCTION> { kind = CommentTarget.Kind.FUNCTION; }
| <K_CONSTRAINT> { kind = CommentTarget.Kind.CONSTRAINT; }
| <K_TRIGGER> { kind = CommentTarget.Kind.TRIGGER; }
| <K_POLICY> { kind = CommentTarget.Kind.POLICY; }
| LOOKAHEAD({ isKeywordAhead("RULE") }) ContextualKeyword("RULE") { kind = CommentTarget.Kind.RULE; }
| <K_PROCEDURE> { kind = CommentTarget.Kind.PROCEDURE; }
| LOOKAHEAD({ isKeywordAhead("ROUTINE") }) ContextualKeyword("ROUTINE") { kind = CommentTarget.Kind.ROUTINE; }
| <K_AGGREGATE> { kind = CommentTarget.Kind.AGGREGATE; }
| <K_CAST> { kind = CommentTarget.Kind.CAST; }
| LOOKAHEAD({ isKeywordAhead("ACCESS") }) ContextualKeyword("ACCESS") ContextualKeyword("METHOD")
{ kind = CommentTarget.Kind.ACCESS_METHOD; }
| LOOKAHEAD({ isKeywordAhead("COLLATION") }) ContextualKeyword("COLLATION") { kind = CommentTarget.Kind.COLLATION; }
| <K_DATABASE> { kind = CommentTarget.Kind.DATABASE; }
| <K_EVENT> <K_TRIGGER> { kind = CommentTarget.Kind.EVENT_TRIGGER; }
| LOOKAHEAD({ isKeywordAhead("EXTENSION") }) ContextualKeyword("EXTENSION") { kind = CommentTarget.Kind.EXTENSION; }
| <K_FOREIGN>
(
<K_DATA> ContextualKeyword("WRAPPER") { kind = CommentTarget.Kind.FOREIGN_DATA_WRAPPER; }
| <K_TABLE> { kind = CommentTarget.Kind.FOREIGN_TABLE; }
)
| LOOKAHEAD({ isKeywordAhead("PROCEDURAL") || isKeywordAhead("LANGUAGE") })
[ LOOKAHEAD({ isKeywordAhead("PROCEDURAL") }) ContextualKeyword("PROCEDURAL") { target.setProcedural(true); } ]
ContextualKeyword("LANGUAGE") { kind = CommentTarget.Kind.LANGUAGE; }
| LOOKAHEAD({ isKeywordAhead("PUBLICATION") }) ContextualKeyword("PUBLICATION") { kind = CommentTarget.Kind.PUBLICATION; }
| LOOKAHEAD({ isKeywordAhead("ROLE") }) ContextualKeyword("ROLE") { kind = CommentTarget.Kind.ROLE; }
| LOOKAHEAD({ isKeywordAhead("SERVER") }) ContextualKeyword("SERVER") { kind = CommentTarget.Kind.SERVER; }
| LOOKAHEAD({ isKeywordAhead("STATISTICS") }) ContextualKeyword("STATISTICS") { kind = CommentTarget.Kind.STATISTICS; }
| LOOKAHEAD({ isKeywordAhead("SUBSCRIPTION") }) ContextualKeyword("SUBSCRIPTION") { kind = CommentTarget.Kind.SUBSCRIPTION; }
| <K_TABLESPACE> { kind = CommentTarget.Kind.TABLESPACE; }
)
{ return kind; }
}

/** Consumes a keyword that remains an ordinary identifier outside this grammar context. */
void ContextualKeyword(String expected):
{ Token keyword; }
Expand Down Expand Up @@ -21427,7 +21472,9 @@ RoutineReference RoutineReference(boolean aggregate):
(
"*" { requireTypeDdlSyntax(aggregate, "Only aggregate signatures accept *"); result.setAllArguments(true); }
|
[ argument=RoutineSignatureArgument() { arguments.add(argument); }
// ORDER is non-reserved, so ORDER BY without direct arguments is not an argument name
[ LOOKAHEAD(1, { getToken(1).kind != K_ORDER || getToken(2).kind != K_BY })
argument=RoutineSignatureArgument() { arguments.add(argument); }
( "," argument=RoutineSignatureArgument() { arguments.add(argument); } )* ]
[ <K_ORDER> <K_BY> { requireTypeDdlSyntax(aggregate, "ORDER BY requires an aggregate signature"); orderBy = new ArrayList<RoutineReference.Argument>(); }
argument=RoutineSignatureArgument() { orderBy.add(argument); }
Expand Down
Loading
Loading