Skip to content

Commit f270c2e

Browse files
authored
feat: reuse PostgreSQL constraint attributes in ALTER CONSTRAINT (#2717)
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 485e6f9 commit f270c2e

3 files changed

Lines changed: 185 additions & 4 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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.alter;
11+
12+
import java.util.Objects;
13+
import net.sf.jsqlparser.statement.create.table.ConstraintAttributes;
14+
15+
/** Changes an existing constraint using the attributes shared with CREATE and ADD. */
16+
public class AlterConstraintAttributes extends AlterExpression {
17+
private ConstraintAttributes attributes = new ConstraintAttributes();
18+
19+
public AlterConstraintAttributes() {
20+
setOperation(AlterOperation.ALTER);
21+
setConstraintType("CONSTRAINT");
22+
}
23+
24+
public ConstraintAttributes getAttributes() {
25+
return attributes;
26+
}
27+
28+
public void setAttributes(ConstraintAttributes attributes) {
29+
this.attributes = Objects.requireNonNull(attributes, "attributes");
30+
}
31+
32+
@Override
33+
public String getConstraintName() {
34+
return getConstraintSymbol();
35+
}
36+
37+
@Override
38+
public void setConstraintName(String name) {
39+
setConstraintSymbol(name);
40+
}
41+
42+
@Override
43+
public boolean isEnforced() {
44+
return Boolean.TRUE.equals(attributes.getEnforced());
45+
}
46+
47+
@Override
48+
public void setEnforced(boolean enforced) {
49+
attributes.setEnforced(enforced);
50+
}
51+
52+
@Override
53+
protected void appendBody(StringBuilder builder) {
54+
builder.append("ALTER CONSTRAINT ").append(getConstraintSymbol());
55+
attributes.appendTo(builder);
56+
}
57+
}

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

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1543,11 +1543,16 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
15431543
}
15441544

15451545
private boolean isPostgreSqlConstraintAttributeAhead() {
1546-
int kind = getToken(1).kind;
1547-
int next = getToken(2).kind;
1548-
return kind == K_DEFERRABLE || kind == K_ENFORCED || isKeywordAhead("INITIALLY")
1546+
return isPostgreSqlConstraintAttributeAhead(1);
1547+
}
1548+
1549+
private boolean isPostgreSqlConstraintAttributeAhead(int offset) {
1550+
int kind = getToken(offset).kind;
1551+
int next = getToken(offset + 1).kind;
1552+
return kind == K_DEFERRABLE || kind == K_ENFORCED
1553+
|| "INITIALLY".equalsIgnoreCase(getToken(offset).image)
15491554
|| kind == K_NOT && (next == K_DEFERRABLE || next == K_ENFORCED
1550-
|| "VALID".equalsIgnoreCase(getToken(2).image));
1555+
|| "VALID".equalsIgnoreCase(getToken(offset + 1).image));
15511556
}
15521557

15531558
private boolean isMySqlStatisticsOptionAhead() {
@@ -18297,6 +18302,24 @@ AlterExpressionPartition AlterExpressionPostgreSqlPartitionOp():
1829718302
* Dispatcher production for all ALTER TABLE expression types.
1829818303
* Delegates to focused sub-productions for each operation category.
1829918304
*/
18305+
AlterConstraintAttributes AlterConstraintAttributes():
18306+
{
18307+
AlterConstraintAttributes action = new AlterConstraintAttributes();
18308+
String name;
18309+
ConstraintAttributes attributes;
18310+
}
18311+
{
18312+
<K_ALTER> <K_CONSTRAINT> name=RelObjectName()
18313+
attributes=PostgreSqlConstraintAttributeList(null)
18314+
{
18315+
requireDdlSyntax(attributes != null && !attributes.isNotValid(),
18316+
"ALTER CONSTRAINT requires attributes and does not accept NOT VALID");
18317+
action.setConstraintSymbol(name);
18318+
action.setAttributes(attributes);
18319+
return action;
18320+
}
18321+
}
18322+
1830018323
AlterExpression AlterExpression():
1830118324
{
1830218325
AlterExpression alterExp = null;
@@ -18318,6 +18341,9 @@ AlterExpression AlterExpression():
1831818341
|
1831918342
LOOKAHEAD({ isMySqlTableOptionAhead() })
1832018343
alterExp=AlterSharedTableOption()
18344+
|
18345+
LOOKAHEAD({ getToken(1).kind == K_ALTER && getToken(2).kind == K_CONSTRAINT
18346+
&& isPostgreSqlConstraintAttributeAhead(4) }) alterExp=AlterConstraintAttributes()
1832118347
|
1832218348
LOOKAHEAD({ isPostgreSqlRelationActionAhead() }) alterExp=PostgreSqlRelationAction() {
1832318349
requireDdlSyntax(((RelationAlterAction) alterExp).getColumnNumber() == null,
@@ -18435,6 +18461,7 @@ AlterExpression AlterExpression():
1843518461
i++;
1843618462
}
1843718463

18464+
requireDdlSyntax(i > 0, "Expected an ALTER TABLE action");
1843818465
alterExp.setOptionalSpecifier( optionalSpecifier.toString() );
1843918466
}
1844018467
)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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.alter;
11+
12+
import static org.junit.jupiter.api.Assertions.*;
13+
14+
import java.util.Set;
15+
import net.sf.jsqlparser.JSQLParserException;
16+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
17+
import net.sf.jsqlparser.statement.create.table.ConstraintAttributes;
18+
import net.sf.jsqlparser.statement.create.table.CreateTable;
19+
import net.sf.jsqlparser.util.TablesNamesFinder;
20+
import net.sf.jsqlparser.util.deparser.StatementDeParser;
21+
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.params.ParameterizedTest;
23+
import org.junit.jupiter.params.provider.ValueSource;
24+
25+
class PostgreSqlConstraintAttributesChangeTest {
26+
@ParameterizedTest
27+
@ValueSource(strings = {"DEFERRABLE", "NOT DEFERRABLE", "INITIALLY DEFERRED",
28+
"INITIALLY IMMEDIATE", "DEFERRABLE INITIALLY DEFERRED",
29+
"INITIALLY DEFERRED DEFERRABLE", "ENFORCED", "NOT ENFORCED",
30+
"DEFERRABLE INITIALLY DEFERRED NOT ENFORCED",
31+
"NOT ENFORCED INITIALLY IMMEDIATE NOT DEFERRABLE"})
32+
void sharesCreateAddAndAlterAttributes(String attributes) throws JSQLParserException {
33+
String sql = "ALTER TABLE t ALTER CONSTRAINT fk " + attributes + ", ADD COLUMN z INT";
34+
Alter table = (Alter) parse(sql);
35+
AlterConstraintAttributes change = assertInstanceOf(AlterConstraintAttributes.class,
36+
table.getAlterExpressions().get(0));
37+
assertEquals(AlterOperation.ALTER, change.getOperation());
38+
assertEquals("CONSTRAINT", change.getConstraintType());
39+
assertEquals("fk", change.getConstraintSymbol());
40+
assertEquals("fk", change.getConstraintName());
41+
String declaration = "CONSTRAINT fk FOREIGN KEY(id) REFERENCES p(id) " + attributes;
42+
CreateTable create = (CreateTable) parse("CREATE TABLE t(id INT, "
43+
+ declaration + ")");
44+
Alter add = (Alter) parse("ALTER TABLE t ADD " + declaration);
45+
assertEquals(create.getIndexes().get(0).getConstraintAttributes().toString(),
46+
change.getAttributes().toString());
47+
assertEquals(
48+
add.getAlterExpressions().get(0).getIndex().getConstraintAttributes().toString(),
49+
change.getAttributes().toString());
50+
assertEquals(2, table.getAlterExpressions().size());
51+
roundTrip(table);
52+
assertEquals(2, CCJSqlParserUtil.parseStatements(sql + "; SELECT 1").size());
53+
}
54+
55+
@Test
56+
void legacyAndStructuredMutationsStayConsistent() throws JSQLParserException {
57+
Alter table = (Alter) parse("ALTER TABLE t ALTER CONSTRAINT fk ENFORCED");
58+
AlterConstraintAttributes action =
59+
(AlterConstraintAttributes) table.getAlterExpressions().get(0);
60+
action.setConstraintName("\"new fk\"");
61+
assertEquals("\"new fk\"", action.getConstraintSymbol());
62+
action.setEnforced(false);
63+
assertEquals(Boolean.FALSE, action.getAttributes().getEnforced());
64+
action.getAttributes().setDeferrable(true);
65+
action.getAttributes().setInitially(ConstraintAttributes.Initially.DEFERRED);
66+
assertEquals(
67+
"ALTER TABLE t ALTER CONSTRAINT \"new fk\" DEFERRABLE INITIALLY DEFERRED NOT ENFORCED",
68+
table.toString());
69+
action.getAttributes().setEnforced(true);
70+
assertTrue(action.isEnforced());
71+
assertEquals(Set.of("t"), new TablesNamesFinder().getTables(table));
72+
roundTrip(table);
73+
}
74+
75+
@ParameterizedTest
76+
@ValueSource(strings = {"NOT VALID", "DEFERRABLE NOT VALID", "INITIALLY UNKNOWN",
77+
"DEFERRABLE NOT DEFERRABLE", "ENFORCED NOT ENFORCED",
78+
"INITIALLY DEFERRED INITIALLY IMMEDIATE", "DEFERRABLE,"})
79+
void rejectsWrongOrDuplicateAlterAttributes(String attributes) {
80+
assertThrows(JSQLParserException.class,
81+
() -> parse("ALTER TABLE t ALTER CONSTRAINT fk " + attributes));
82+
}
83+
84+
private static net.sf.jsqlparser.statement.Statement parse(String sql)
85+
throws JSQLParserException {
86+
return CCJSqlParserUtil.parse(sql,
87+
parser -> parser.withDialect(
88+
net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect.POSTGRESQL));
89+
}
90+
91+
private static void roundTrip(Alter table) throws JSQLParserException {
92+
StringBuilder output = new StringBuilder();
93+
table.accept(new StatementDeParser(output));
94+
assertEquals(table.toString(), output.toString());
95+
assertEquals(output.toString(), parse(output.toString()).toString());
96+
}
97+
}

0 commit comments

Comments
 (0)