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
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ public enum MatchType {
}

private Table table;
private String constraintName;

/** Optional name of a column REFERENCES constraint. Table constraints own their name. */
public String getConstraintName() {
return constraintName;
}

public void setConstraintName(String constraintName) {
this.constraintName = constraintName;
}

private List<String> referencedColumnNames;
private MatchType matchType;
private boolean usingPeriod;
Expand Down Expand Up @@ -148,7 +159,11 @@ public ForeignKeyReference addReferencedColumnNames(

@Override
public String toString() {
StringBuilder builder = new StringBuilder("REFERENCES ").append(table);
StringBuilder builder = new StringBuilder();
if (constraintName != null) {
builder.append("CONSTRAINT ").append(constraintName).append(' ');
}
builder.append("REFERENCES ").append(table);
if (referencedColumnNames != null) {
builder.append('(');
for (int i = 0; i < referencedColumnNames.size(); i++) {
Expand Down
49 changes: 35 additions & 14 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -14172,24 +14172,23 @@ ColumnOption ColumnDefinitionOption(): {
LOOKAHEAD(<K_ON> <K_UPDATE>) <K_ON> <K_UPDATE> defaultExpression=Expression()
{ option = ColumnOption.onUpdate(defaultExpression); }
|
LOOKAHEAD(<K_PRIMARY> <K_KEY>) <K_PRIMARY> <K_KEY>
{ option = ColumnOption.constraint(new NamedConstraint().withType("PRIMARY KEY")); }
LOOKAHEAD([ <K_CONSTRAINT> RelObjectName() ] ( <K_PRIMARY> <K_KEY> | <K_UNIQUE> ))
constraint=ColumnKeyConstraint() { option = ColumnOption.constraint(constraint); }
|
LOOKAHEAD([ <K_CONSTRAINT> RelObjectName() ] <K_CHECK>)
[ <K_CONSTRAINT> constraintName=RelObjectName() ]
constraint=CheckConstraintSpec(constraintName)
{ option = ColumnOption.constraint(constraint); }
|
LOOKAHEAD(<K_UNIQUE>) constraint=ColumnUniqueConstraint()
{ option = ColumnOption.constraint(constraint); }
|
LOOKAHEAD({ isKeywordAhead("SERIAL")
&& getToken(2).kind == K_DEFAULT && getToken(3).kind == K_VALUE })
tk=<S_IDENTIFIER> <K_DEFAULT> <K_VALUE>
{ option = ColumnOption.serialDefaultValue(); }
|
LOOKAHEAD(<K_REFERENCES>) reference=ForeignKeyReferenceSpec(true)
{ option = ColumnOption.reference(reference); }
LOOKAHEAD([ <K_CONSTRAINT> RelObjectName() ] <K_REFERENCES>)
[ <K_CONSTRAINT> constraintName=RelObjectName() ]
reference=ForeignKeyReferenceSpec(true)
{ reference.setConstraintName(constraintName); option = ColumnOption.reference(reference); }
|
LOOKAHEAD(<K_DROP> <K_DEFAULT>) <K_DROP> <K_DEFAULT>
{ option = ColumnOption.raw("DROP", "DEFAULT"); }
Expand Down Expand Up @@ -14230,17 +14229,30 @@ GeneratedColumnDefinition GeneratedColumnDefinition():
}
}

NamedConstraint ColumnUniqueConstraint():
/** Keeps a column key's name and attributes on the same node in CREATE and ALTER. */
NamedConstraint ColumnKeyConstraint():
{
NamedConstraint constraint = new NamedConstraint().withType("UNIQUE");
NamedConstraint constraint = new NamedConstraint();
String name;
Boolean nullsDistinct = null;
}
{
<K_UNIQUE>
[ <K_NULLS> [ <K_NOT> { nullsDistinct = false; } ] <K_DISTINCT> {
constraint.setNullsDistinct(nullsDistinct == null ? true : nullsDistinct);
} ]
{ return constraint; }
[ <K_CONSTRAINT> name=RelObjectName() { constraint.setName(name); } ]
(
<K_PRIMARY> <K_KEY> { constraint.setType("PRIMARY KEY"); }
|
<K_UNIQUE> { constraint.setType("UNIQUE"); }
[ <K_NULLS> [ <K_NOT> { nullsDistinct = false; } ] <K_DISTINCT> {
constraint.setNullsDistinct(nullsDistinct == null ? true : nullsDistinct);
} ]
)
PostgreSqlConstraintAttributes(constraint)
{
requireDdlSyntax(constraint.getConstraintAttributes() == null
|| !constraint.getConstraintAttributes().isNotValid(),
"NOT VALID requires a table constraint");
return constraint;
}
}

IdentityDefinition IdentityDefinition():
Expand Down Expand Up @@ -14841,6 +14853,15 @@ void PostgreSqlConstraintAttributes(Index index):
{ ConstraintAttributes attributes; }
{
attributes=PostgreSqlConstraintAttributeList(index.getConstraintAttributes())
( LOOKAHEAD({ Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect))
&& getToken(1).kind == K_NO && "INHERIT".equalsIgnoreCase(getToken(2).image) })
<K_NO> TypeDdlKeyword("INHERIT") {
requireDdlSyntax(index instanceof CheckConstraint && !((CheckConstraint) index).isNoInherit(),
"NO INHERIT requires a CHECK constraint and cannot be repeated");
((CheckConstraint) index).setNoInherit(true);
}
attributes=PostgreSqlConstraintAttributeList(attributes)
)*
{
requireDdlSyntax(attributes == null || attributes.getEnforced() == null
|| index instanceof CheckConstraint || index instanceof ForeignKeyIndex,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*-
* #%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;

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.alter.Alter;
import net.sf.jsqlparser.statement.create.table.*;
import net.sf.jsqlparser.util.deparser.StatementDeParser;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class PostgreSqlColumnKeyMutationTest {
@ParameterizedTest
@ValueSource(strings = {"PRIMARY KEY", "UNIQUE", "UNIQUE NULLS NOT DISTINCT"})
void keyNameAndAttributesAreOwnedByOneNode(String key) throws JSQLParserException {
for (boolean alter : new boolean[] {false, true}) {
String prefix = alter ? "ALTER TABLE t ADD COLUMN " : "CREATE TABLE t (";
String suffix = alter ? "" : ")";
Statement statement = parse(prefix + "id INT CONSTRAINT old_name " + key
+ " DEFERRABLE INITIALLY DEFERRED NOT NULL" + suffix);
ColumnDefinition column = alter
? ((Alter) statement).getAlterExpressions().get(0).getColDataTypeList().get(0)
: ((CreateTable) statement).getColumnDefinitions().get(0);
assertEquals(2, column.getColumnOptions().size());
Index constraint = column.getColumnOptions().get(0).getConstraint();
assertEquals("old_name", constraint.getName());
assertTrue(constraint.getConstraintAttributes().getDeferrable());
constraint.setName("new_name");
constraint.getConstraintAttributes().setDeferrable(false);
constraint.getConstraintAttributes()
.setInitially(ConstraintAttributes.Initially.IMMEDIATE);
assertSql(statement, prefix + "id INT CONSTRAINT new_name " + key
+ " NOT DEFERRABLE INITIALLY IMMEDIATE NOT NULL" + suffix);
}
}

@ParameterizedTest
@ValueSource(strings = {"CREATE TABLE t (id INT CONSTRAINT old_fk REFERENCES parent(id))",
"ALTER TABLE t ADD COLUMN id INT CONSTRAINT old_fk REFERENCES parent(id)"})
void referenceNameCanBeReplacedAndRemoved(String sql) throws JSQLParserException {
Statement statement = parse(sql);
ForeignKeyReference reference = statement instanceof CreateTable
? ((CreateTable) statement).getColumnDefinitions().get(0).getForeignKeyReference()
: ((Alter) statement).getAlterExpressions().get(0).getColDataTypeList().get(0)
.getForeignKeyReference();
assertEquals("old_fk", reference.getConstraintName());
reference.setConstraintName("new_fk");
assertSql(statement, sql.replace("old_fk", "new_fk"));
reference.setConstraintName(null);
assertSql(statement, sql.replace("CONSTRAINT old_fk ", ""));
}

@ParameterizedTest
@ValueSource(strings = {"NOT ENFORCED NO INHERIT", "NO INHERIT NOT ENFORCED",
"NOT VALID NO INHERIT NOT ENFORCED", "NOT ENFORCED NOT VALID NO INHERIT"})
void tableCheckAllowsInheritanceAmongAttributes(String attributes) throws JSQLParserException {
Alter alter = (Alter) parse("ALTER TABLE t ADD CHECK (id > 0) " + attributes);
CheckConstraint check = (CheckConstraint) alter.getAlterExpressions().get(0).getIndex();
assertTrue(check.isNoInherit());
assertFalse(check.getEnforced());
assertEquals(attributes.contains("NOT VALID"),
check.getConstraintAttributes().isNotValid());
assertSql(alter, "ALTER TABLE t ADD CHECK (id > 0) NO INHERIT NOT ENFORCED"
+ (attributes.contains("NOT VALID") ? " NOT VALID" : ""));
}

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

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