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
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*-
* #%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.table;

import java.io.Serializable;

/** An exclusion operator, optionally qualified and enclosed in PostgreSQL OPERATOR(...). */
public class ExclusionOperator implements Serializable {
private String schemaName;
private String name;
private boolean useOperatorKeyword;

public String getSchemaName() {
return schemaName;
}

public void setSchemaName(String schemaName) {
this.schemaName = schemaName;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public boolean isUseOperatorKeyword() {
return useOperatorKeyword;
}

public void setUseOperatorKeyword(boolean useOperatorKeyword) {
this.useOperatorKeyword = useOperatorKeyword;
}

public ExclusionOperator withSchemaName(String schemaName) {
setSchemaName(schemaName);
return this;
}

public ExclusionOperator withName(String name) {
setName(name);
return this;
}

public ExclusionOperator withUseOperatorKeyword(boolean useOperatorKeyword) {
setUseOperatorKeyword(useOperatorKeyword);
return this;
}

/** Returns the qualified name without an OPERATOR wrapper, preserving identifier quotes. */
public String getQualifiedName() {
return (schemaName == null ? "" : schemaName + ".") + name;
}

@Override
public String toString() {
return useOperatorKeyword ? "OPERATOR(" + getQualifiedName() + ")" : getQualifiedName();
}
}
20 changes: 18 additions & 2 deletions src/main/java/net/sf/jsqlparser/statement/create/table/Index.java
Original file line number Diff line number Diff line change
Expand Up @@ -414,16 +414,32 @@ public enum NullOrdering {
private List<Option> operatorClassParameters;
private SortOrder sortOrder;
private NullOrdering nullOrdering;
private String exclusionOperator;
private ExclusionOperator exclusionOperator;

public String getExclusionOperator() {
return exclusionOperator;
return exclusionOperator == null ? null : exclusionOperator.toString();
}

/** Replaces the complete operator text; retained for source compatibility. */
public void setExclusionOperator(String exclusionOperator) {
this.exclusionOperator = exclusionOperator == null ? null
: new ExclusionOperator().withName(exclusionOperator);
}

/** Returns the editable operator reference, or null when this is not an exclusion key. */
public ExclusionOperator getExclusionOperatorReference() {
return exclusionOperator;
}

public void setExclusionOperatorReference(ExclusionOperator exclusionOperator) {
this.exclusionOperator = exclusionOperator;
}

public ColumnParams withExclusionOperatorReference(ExclusionOperator exclusionOperator) {
setExclusionOperatorReference(exclusionOperator);
return this;
}

public ColumnParams(String columnName) {
this.columnName = columnName;
this.params = null;
Expand Down
26 changes: 21 additions & 5 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -14918,12 +14918,23 @@ Index.ColumnParams PostgreSqlExcludeElement():
{
Index.ColumnParams column;
Token operator;
ExclusionOperator reference;
}
{
column=IndexColumnWithParams() <K_WITH>
( operator="=" | operator=<OP_NOTEQUALSSTANDARD> | operator=<OP_NOTEQUALSBANG> | operator="<" | operator=">"
| operator=<OP_MINORTHANEQUALS> | operator=<OP_GREATERTHANEQUALS> | operator="&&" | operator="@>" | operator="<@" )
{ column.setExclusionOperator(operator.image); return column; }
(
LOOKAHEAD({ Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect))
&& isKeywordAhead("OPERATOR") })
TypeDdlKeyword("OPERATOR") "(" reference=PostgreSqlOperatorReference() ")" {
reference.setUseOperatorKeyword(true);
column.setExclusionOperatorReference(reference);
}
|
( operator="=" | operator=<OP_NOTEQUALSSTANDARD> | operator=<OP_NOTEQUALSBANG> | operator="<" | operator=">"
| operator=<OP_MINORTHANEQUALS> | operator=<OP_GREATERTHANEQUALS> | operator="&&" | operator="@>" | operator="<@" )
{ column.setExclusionOperator(operator.image); }
)
{ return column; }
}


Expand Down Expand Up @@ -19131,15 +19142,20 @@ RoutineReference RoutineReference(boolean aggregate):
}

String ExtensionOperatorName():
{ String schema = null; String operator = ""; Token token; }
{ ExclusionOperator operator; }
{ operator=PostgreSqlOperatorReference() { return operator.getQualifiedName(); } }

/** Shared schema-qualified operator spelling used in DDL references. */
ExclusionOperator PostgreSqlOperatorReference():
{ String schema = null; Token token; }
{
[ 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> )
{ operator = token.image; return (schema == null ? "" : schema + ".") + operator; }
{ return new ExclusionOperator().withSchemaName(schema).withName(token.image); }
}

ColDataType ExtensionOperatorType():
Expand Down
9 changes: 9 additions & 0 deletions src/site/sphinx/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,15 @@ Ordinary views expose ordered ``ViewOption`` values for ``security_barrier``, ``

Table constraints expose ``Index.getNullsDistinct()``, ``getIncludeColumns()``, storage parameters and ``ConstraintAttributes``. ``ExcludeConstraint`` reuses ``Index.ColumnParams`` for its keys; each key exposes its expression and exclusion operator. Column identity clauses are represented by ``ColumnOption.Kind.IDENTITY`` and ``IdentityDefinition``, with a generation mode and ordered ``Sequence.Parameter`` values. These APIs cover the schema clauses described in `CREATE TABLE <https://www.postgresql.org/docs/18/sql-createtable.html>`_.

Exclusion keys expose ``getExclusionOperatorReference()`` for structured access
to an operator's ``schemaName``, ``name`` and ``useOperatorKeyword`` flag. In
``Dialect.POSTGRESQL``, both CREATE and ALTER accept ``WITH OPERATOR(pg_catalog.&&)``.
Editing the reference updates both statement renderers. The existing string
``getExclusionOperator()`` returns its complete SQL spelling; its setter remains
available for replacing opaque operator text. New code can construct a reference
with ``new ExclusionOperator().withSchemaName("pg_catalog").withName("&&")
.withUseOperatorKeyword(true)``.

.. code-block:: java

Alter alter = (Alter) CCJSqlParserUtil.parse(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*-
* #%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.alter;

import static org.junit.jupiter.api.Assertions.*;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.create.table.CreateTable;
import net.sf.jsqlparser.statement.create.table.ExcludeConstraint;
import net.sf.jsqlparser.statement.create.table.ExclusionOperator;
import net.sf.jsqlparser.statement.create.table.Index;
import net.sf.jsqlparser.util.deparser.StatementDeParser;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class PostgreSqlExclusionOperatorTest {
@ParameterizedTest
@ValueSource(strings = {"OPERATOR(pg_catalog.&&)", "OPERATOR(&&)", "OPERATOR(\"My.Schema\".&&)",
"OPERATOR(pg_catalog.=)", "&&"})
void supportsSharedCreateAndAlterGrammar(String operator) throws JSQLParserException {
for (String sql : new String[] {
"CREATE TABLE t (c INT4RANGE, CONSTRAINT ex EXCLUDE USING gist (c WITH " + operator
+ "))",
"ALTER TABLE t ADD CONSTRAINT ex EXCLUDE USING gist (c WITH " + operator + ")"}) {
Statement statement = parse(sql);
assertEquals(sql, statement.toString());
assertEquals(operator,
constraint(statement).getColumns().get(0).getExclusionOperator());
roundTrip(statement);
}
}

@Test
void exposesEditableSchemaAndSymbolAndSupportsLegacySetter() throws JSQLParserException {
Statement statement = parse(
"ALTER TABLE t ADD CONSTRAINT ex EXCLUDE USING gist (c WITH OPERATOR(pg_catalog.&&))");
Index.ColumnParams key = constraint(statement).getColumns().get(0);
ExclusionOperator operator = key.getExclusionOperatorReference();
assertEquals("pg_catalog", operator.getSchemaName());
assertEquals("&&", operator.getName());
assertTrue(operator.isUseOperatorKeyword());
operator.setSchemaName("\"My.Schema\"");
operator.setName("=");
assertEquals("OPERATOR(\"My.Schema\".=)", key.getExclusionOperator());
roundTrip(statement);
key.setExclusionOperator("&&");
assertEquals("&&", key.getExclusionOperator());
assertNull(key.getExclusionOperatorReference().getSchemaName());
roundTrip(statement);
key.setExclusionOperator(null);
assertNull(key.getExclusionOperatorReference());
key.setExclusionOperatorReference(new ExclusionOperator().withSchemaName("pg_catalog")
.withName("&&").withUseOperatorKeyword(true));
roundTrip(statement);
}

@Test
void preservesElementOptionsAndMultipleElementsAndDefaultSyntax() throws JSQLParserException {
roundTrip(parse(
"ALTER TABLE t ADD EXCLUDE USING gist (c WITH OPERATOR(pg_catalog.&&), d WITH =) WHERE (c IS NOT NULL) DEFERRABLE INITIALLY DEFERRED"
.replace("\n", "")));
String legacy = "CREATE TABLE t (c INT4RANGE, EXCLUDE USING gist (c WITH &&))";
assertEquals(legacy, CCJSqlParserUtil.parse(legacy).toString());
roundTrip(parse(
"ALTER TABLE t ADD EXCLUDE USING gist ((c) WITH OPERATOR(pg_catalog.&&)), ADD COLUMN extra INT"));
}

private static ExcludeConstraint constraint(Statement statement) {
return (ExcludeConstraint) (statement instanceof Alter
? ((Alter) statement).getAlterExpressions().get(0).getIndex()
: ((CreateTable) statement).getIndexes().get(0));
}

private static Statement parse(String sql) throws JSQLParserException {
return CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.POSTGRESQL));
}

private static void roundTrip(Statement statement) throws JSQLParserException {
StringBuilder sql = new StringBuilder();
statement.accept(new StatementDeParser(sql), null);
assertEquals(statement.toString(), sql.toString());
assertEquals(sql.toString(), parse(sql.toString()).toString());
}
}
Loading