Skip to content

Commit ac34c3f

Browse files
authored
fix: keep PostgreSQL column constraint names and attributes together (#2698)
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent ef66801 commit ac34c3f

3 files changed

Lines changed: 141 additions & 15 deletions

File tree

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ public enum MatchType {
3030
}
3131

3232
private Table table;
33+
private String constraintName;
34+
35+
/** Optional name of a column REFERENCES constraint. Table constraints own their name. */
36+
public String getConstraintName() {
37+
return constraintName;
38+
}
39+
40+
public void setConstraintName(String constraintName) {
41+
this.constraintName = constraintName;
42+
}
43+
3344
private List<String> referencedColumnNames;
3445
private MatchType matchType;
3546
private boolean usingPeriod;
@@ -148,7 +159,11 @@ public ForeignKeyReference addReferencedColumnNames(
148159

149160
@Override
150161
public String toString() {
151-
StringBuilder builder = new StringBuilder("REFERENCES ").append(table);
162+
StringBuilder builder = new StringBuilder();
163+
if (constraintName != null) {
164+
builder.append("CONSTRAINT ").append(constraintName).append(' ');
165+
}
166+
builder.append("REFERENCES ").append(table);
152167
if (referencedColumnNames != null) {
153168
builder.append('(');
154169
for (int i = 0; i < referencedColumnNames.size(); i++) {

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

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14172,24 +14172,23 @@ ColumnOption ColumnDefinitionOption(): {
1417214172
LOOKAHEAD(<K_ON> <K_UPDATE>) <K_ON> <K_UPDATE> defaultExpression=Expression()
1417314173
{ option = ColumnOption.onUpdate(defaultExpression); }
1417414174
|
14175-
LOOKAHEAD(<K_PRIMARY> <K_KEY>) <K_PRIMARY> <K_KEY>
14176-
{ option = ColumnOption.constraint(new NamedConstraint().withType("PRIMARY KEY")); }
14175+
LOOKAHEAD([ <K_CONSTRAINT> RelObjectName() ] ( <K_PRIMARY> <K_KEY> | <K_UNIQUE> ))
14176+
constraint=ColumnKeyConstraint() { option = ColumnOption.constraint(constraint); }
1417714177
|
1417814178
LOOKAHEAD([ <K_CONSTRAINT> RelObjectName() ] <K_CHECK>)
1417914179
[ <K_CONSTRAINT> constraintName=RelObjectName() ]
1418014180
constraint=CheckConstraintSpec(constraintName)
1418114181
{ option = ColumnOption.constraint(constraint); }
1418214182
|
14183-
LOOKAHEAD(<K_UNIQUE>) constraint=ColumnUniqueConstraint()
14184-
{ option = ColumnOption.constraint(constraint); }
14185-
|
1418614183
LOOKAHEAD({ isKeywordAhead("SERIAL")
1418714184
&& getToken(2).kind == K_DEFAULT && getToken(3).kind == K_VALUE })
1418814185
tk=<S_IDENTIFIER> <K_DEFAULT> <K_VALUE>
1418914186
{ option = ColumnOption.serialDefaultValue(); }
1419014187
|
14191-
LOOKAHEAD(<K_REFERENCES>) reference=ForeignKeyReferenceSpec(true)
14192-
{ option = ColumnOption.reference(reference); }
14188+
LOOKAHEAD([ <K_CONSTRAINT> RelObjectName() ] <K_REFERENCES>)
14189+
[ <K_CONSTRAINT> constraintName=RelObjectName() ]
14190+
reference=ForeignKeyReferenceSpec(true)
14191+
{ reference.setConstraintName(constraintName); option = ColumnOption.reference(reference); }
1419314192
|
1419414193
LOOKAHEAD(<K_DROP> <K_DEFAULT>) <K_DROP> <K_DEFAULT>
1419514194
{ option = ColumnOption.raw("DROP", "DEFAULT"); }
@@ -14230,17 +14229,30 @@ GeneratedColumnDefinition GeneratedColumnDefinition():
1423014229
}
1423114230
}
1423214231

14233-
NamedConstraint ColumnUniqueConstraint():
14232+
/** Keeps a column key's name and attributes on the same node in CREATE and ALTER. */
14233+
NamedConstraint ColumnKeyConstraint():
1423414234
{
14235-
NamedConstraint constraint = new NamedConstraint().withType("UNIQUE");
14235+
NamedConstraint constraint = new NamedConstraint();
14236+
String name;
1423614237
Boolean nullsDistinct = null;
1423714238
}
1423814239
{
14239-
<K_UNIQUE>
14240-
[ <K_NULLS> [ <K_NOT> { nullsDistinct = false; } ] <K_DISTINCT> {
14241-
constraint.setNullsDistinct(nullsDistinct == null ? true : nullsDistinct);
14242-
} ]
14243-
{ return constraint; }
14240+
[ <K_CONSTRAINT> name=RelObjectName() { constraint.setName(name); } ]
14241+
(
14242+
<K_PRIMARY> <K_KEY> { constraint.setType("PRIMARY KEY"); }
14243+
|
14244+
<K_UNIQUE> { constraint.setType("UNIQUE"); }
14245+
[ <K_NULLS> [ <K_NOT> { nullsDistinct = false; } ] <K_DISTINCT> {
14246+
constraint.setNullsDistinct(nullsDistinct == null ? true : nullsDistinct);
14247+
} ]
14248+
)
14249+
PostgreSqlConstraintAttributes(constraint)
14250+
{
14251+
requireDdlSyntax(constraint.getConstraintAttributes() == null
14252+
|| !constraint.getConstraintAttributes().isNotValid(),
14253+
"NOT VALID requires a table constraint");
14254+
return constraint;
14255+
}
1424414256
}
1424514257

1424614258
IdentityDefinition IdentityDefinition():
@@ -14841,6 +14853,15 @@ void PostgreSqlConstraintAttributes(Index index):
1484114853
{ ConstraintAttributes attributes; }
1484214854
{
1484314855
attributes=PostgreSqlConstraintAttributeList(index.getConstraintAttributes())
14856+
( LOOKAHEAD({ Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect))
14857+
&& getToken(1).kind == K_NO && "INHERIT".equalsIgnoreCase(getToken(2).image) })
14858+
<K_NO> TypeDdlKeyword("INHERIT") {
14859+
requireDdlSyntax(index instanceof CheckConstraint && !((CheckConstraint) index).isNoInherit(),
14860+
"NO INHERIT requires a CHECK constraint and cannot be repeated");
14861+
((CheckConstraint) index).setNoInherit(true);
14862+
}
14863+
attributes=PostgreSqlConstraintAttributeList(attributes)
14864+
)*
1484414865
{
1484514866
requireDdlSyntax(attributes == null || attributes.getEnforced() == null
1484614867
|| index instanceof CheckConstraint || index instanceof ForeignKeyIndex,
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement.create;
11+
12+
import static org.junit.jupiter.api.Assertions.*;
13+
14+
import net.sf.jsqlparser.JSQLParserException;
15+
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
16+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
17+
import net.sf.jsqlparser.statement.Statement;
18+
import net.sf.jsqlparser.statement.alter.Alter;
19+
import net.sf.jsqlparser.statement.create.table.*;
20+
import net.sf.jsqlparser.util.deparser.StatementDeParser;
21+
import org.junit.jupiter.params.ParameterizedTest;
22+
import org.junit.jupiter.params.provider.ValueSource;
23+
24+
class PostgreSqlColumnKeyMutationTest {
25+
@ParameterizedTest
26+
@ValueSource(strings = {"PRIMARY KEY", "UNIQUE", "UNIQUE NULLS NOT DISTINCT"})
27+
void keyNameAndAttributesAreOwnedByOneNode(String key) throws JSQLParserException {
28+
for (boolean alter : new boolean[] {false, true}) {
29+
String prefix = alter ? "ALTER TABLE t ADD COLUMN " : "CREATE TABLE t (";
30+
String suffix = alter ? "" : ")";
31+
Statement statement = parse(prefix + "id INT CONSTRAINT old_name " + key
32+
+ " DEFERRABLE INITIALLY DEFERRED NOT NULL" + suffix);
33+
ColumnDefinition column = alter
34+
? ((Alter) statement).getAlterExpressions().get(0).getColDataTypeList().get(0)
35+
: ((CreateTable) statement).getColumnDefinitions().get(0);
36+
assertEquals(2, column.getColumnOptions().size());
37+
Index constraint = column.getColumnOptions().get(0).getConstraint();
38+
assertEquals("old_name", constraint.getName());
39+
assertTrue(constraint.getConstraintAttributes().getDeferrable());
40+
constraint.setName("new_name");
41+
constraint.getConstraintAttributes().setDeferrable(false);
42+
constraint.getConstraintAttributes()
43+
.setInitially(ConstraintAttributes.Initially.IMMEDIATE);
44+
assertSql(statement, prefix + "id INT CONSTRAINT new_name " + key
45+
+ " NOT DEFERRABLE INITIALLY IMMEDIATE NOT NULL" + suffix);
46+
}
47+
}
48+
49+
@ParameterizedTest
50+
@ValueSource(strings = {"CREATE TABLE t (id INT CONSTRAINT old_fk REFERENCES parent(id))",
51+
"ALTER TABLE t ADD COLUMN id INT CONSTRAINT old_fk REFERENCES parent(id)"})
52+
void referenceNameCanBeReplacedAndRemoved(String sql) throws JSQLParserException {
53+
Statement statement = parse(sql);
54+
ForeignKeyReference reference = statement instanceof CreateTable
55+
? ((CreateTable) statement).getColumnDefinitions().get(0).getForeignKeyReference()
56+
: ((Alter) statement).getAlterExpressions().get(0).getColDataTypeList().get(0)
57+
.getForeignKeyReference();
58+
assertEquals("old_fk", reference.getConstraintName());
59+
reference.setConstraintName("new_fk");
60+
assertSql(statement, sql.replace("old_fk", "new_fk"));
61+
reference.setConstraintName(null);
62+
assertSql(statement, sql.replace("CONSTRAINT old_fk ", ""));
63+
}
64+
65+
@ParameterizedTest
66+
@ValueSource(strings = {"NOT ENFORCED NO INHERIT", "NO INHERIT NOT ENFORCED",
67+
"NOT VALID NO INHERIT NOT ENFORCED", "NOT ENFORCED NOT VALID NO INHERIT"})
68+
void tableCheckAllowsInheritanceAmongAttributes(String attributes) throws JSQLParserException {
69+
Alter alter = (Alter) parse("ALTER TABLE t ADD CHECK (id > 0) " + attributes);
70+
CheckConstraint check = (CheckConstraint) alter.getAlterExpressions().get(0).getIndex();
71+
assertTrue(check.isNoInherit());
72+
assertFalse(check.getEnforced());
73+
assertEquals(attributes.contains("NOT VALID"),
74+
check.getConstraintAttributes().isNotValid());
75+
assertSql(alter, "ALTER TABLE t ADD CHECK (id > 0) NO INHERIT NOT ENFORCED"
76+
+ (attributes.contains("NOT VALID") ? " NOT VALID" : ""));
77+
}
78+
79+
private static Statement parse(String sql) throws JSQLParserException {
80+
return CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.POSTGRESQL));
81+
}
82+
83+
private static void assertSql(Statement statement, String expected) throws JSQLParserException {
84+
assertEquals(expected, statement.toString());
85+
StringBuilder buffer = new StringBuilder();
86+
statement.accept(new StatementDeParser(buffer), null);
87+
assertEquals(expected, buffer.toString());
88+
assertEquals(expected, parse(expected).toString());
89+
}
90+
}

0 commit comments

Comments
 (0)