Skip to content

Commit 85cfccf

Browse files
committed
feat: support PostgreSQL CHECK NO INHERIT
Signed-off-by: mj-db <mj.db@kakaocorp.com>
1 parent 60c8dc2 commit 85cfccf

4 files changed

Lines changed: 132 additions & 1 deletion

File tree

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public class CheckConstraint extends NamedConstraint {
2424

2525
private Boolean enforced;
2626

27+
private boolean noInherit;
28+
2729
public CheckConstraint() {
2830
setKind(Kind.CHECK);
2931
}
@@ -44,6 +46,20 @@ public void setExpression(Expression expression) {
4446
this.expression = expression;
4547
}
4648

49+
/** Whether PostgreSQL should keep this CHECK from being inherited by child tables. */
50+
public boolean isNoInherit() {
51+
return noInherit;
52+
}
53+
54+
public void setNoInherit(boolean noInherit) {
55+
this.noInherit = noInherit;
56+
}
57+
58+
public CheckConstraint withNoInherit(boolean noInherit) {
59+
setNoInherit(noInherit);
60+
return this;
61+
}
62+
4763
public Boolean getEnforced() {
4864
return enforced;
4965
}
@@ -62,6 +78,9 @@ public void appendTo(StringBuilder b, Consumer<Expression> expressionPrinter) {
6278
expressionPrinter.accept(expression);
6379
}
6480
b.append(')');
81+
if (noInherit) {
82+
b.append(" NO INHERIT");
83+
}
6584
if (enforced != null) {
6685
b.append(enforced ? " ENFORCED" : " NOT ENFORCED");
6786
}

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14132,6 +14132,7 @@ ColumnOption ColumnDefinitionOption(): {
1413214132
ColumnOption option;
1413314133
IdentityDefinition identity;
1413414134
NamedConstraint constraint;
14135+
String constraintName = null;
1413514136
Expression defaultExpression;
1413614137
GeneratedColumnDefinition generated;
1413714138
ObjectNames collationNames;
@@ -14171,6 +14172,12 @@ ColumnOption ColumnDefinitionOption(): {
1417114172
LOOKAHEAD(<K_PRIMARY> <K_KEY>) <K_PRIMARY> <K_KEY>
1417214173
{ option = ColumnOption.constraint(new NamedConstraint().withType("PRIMARY KEY")); }
1417314174
|
14175+
LOOKAHEAD([ <K_CONSTRAINT> RelObjectName() ] <K_CHECK>,
14176+
{ Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect)) })
14177+
[ <K_CONSTRAINT> constraintName=RelObjectName() ]
14178+
constraint=CheckConstraintSpec(constraintName)
14179+
{ option = ColumnOption.constraint(constraint); }
14180+
|
1417414181
LOOKAHEAD(<K_UNIQUE>) constraint=ColumnUniqueConstraint()
1417514182
{ option = ColumnOption.constraint(constraint); }
1417614183
|
@@ -15759,17 +15766,21 @@ CheckConstraint CheckConstraintSpec(String constraintName):
1575915766
{
1576015767
Expression exp = null;
1576115768
Boolean enforced = null;
15769+
boolean noInherit = false;
1576215770
CheckConstraint checkConstraint;
1576315771
}
1576415772
{
1576515773
<K_CHECK> "(" exp = Expression() ")"
15774+
[ LOOKAHEAD({ Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect))
15775+
&& getToken(1).kind == K_NO && "INHERIT".equalsIgnoreCase(getToken(2).image) })
15776+
<K_NO> TypeDdlKeyword("INHERIT") { noInherit = true; } ]
1576615777
[ LOOKAHEAD(2)
1576715778
[ <K_NOT> { enforced = false; } ]
1576815779
<K_ENFORCED> { if (enforced == null) { enforced = true; } }
1576915780
]
1577015781
{
1577115782
checkConstraint = new CheckConstraint().withName(constraintName).withExpression(exp)
15772-
.withEnforced(enforced);
15783+
.withEnforced(enforced).withNoInherit(noInherit);
1577315784
checkConstraint.setKind(Index.Kind.CHECK);
1577415785
return checkConstraint;
1577515786
}

‎src/site/sphinx/usage.rst‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,19 @@ The fastest way to learn the object model is to look at it. Paste your SQL into
231231
Read that as a map: each line is a getter away. ``select.getSelectItems()``, ``select.getFromItem()``, ``select.getWhere()``. Once the tree gets deeper than a couple of levels, stop casting by hand and use :ref:`Use the Visitor Patterns`.
232232

233233

234+
PostgreSQL CHECK inheritance
235+
----------------------------
236+
237+
With ``Dialect.POSTGRESQL``, CREATE and ALTER CHECK constraints support
238+
``NO INHERIT``. ``CheckConstraint.isNoInherit()`` reports this flag; use
239+
``setNoInherit`` or ``withNoInherit`` to change it. The check expression remains
240+
an editable ``Expression`` visited by the existing table traversal and deparsers.
241+
242+
For ``ALTER TABLE t ADD CHECK (id > 0) NO INHERIT NOT VALID``, the two options
243+
are independent: ``isNoInherit()`` is true and
244+
``getConstraintAttributes().isNotValid()`` is true. Clearing one flag preserves
245+
the other when the statement is rendered.
246+
234247
DROP INDEX owners
235248
-----------------
236249

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
import java.util.ArrayList;
14+
import java.util.List;
15+
import net.sf.jsqlparser.JSQLParserException;
16+
import net.sf.jsqlparser.expression.Expression;
17+
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
18+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
19+
import net.sf.jsqlparser.statement.Statement;
20+
import net.sf.jsqlparser.statement.create.table.CheckConstraint;
21+
import net.sf.jsqlparser.statement.create.table.CreateTable;
22+
import net.sf.jsqlparser.util.TableDefinitionTraversal;
23+
import net.sf.jsqlparser.util.deparser.StatementDeParser;
24+
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.params.ParameterizedTest;
26+
import org.junit.jupiter.params.provider.ValueSource;
27+
28+
class PostgreSqlCheckNoInheritTest {
29+
@ParameterizedTest
30+
@ValueSource(strings = {
31+
"ALTER TABLE t ADD CONSTRAINT ck CHECK (id > 0) NO INHERIT",
32+
"ALTER TABLE t ADD CHECK (id > 0) NO INHERIT NOT VALID",
33+
"ALTER TABLE t ADD CONSTRAINT ck CHECK (id > 0) NO INHERIT NOT VALID, ADD COLUMN extra INT",
34+
"CREATE TABLE t (id INT, CONSTRAINT ck CHECK (id > 0) NO INHERIT)",
35+
"CREATE TABLE t (id INT CHECK (id > 0) NO INHERIT)",
36+
"CREATE TABLE t (id INT CONSTRAINT ck CHECK (id > 0) NO INHERIT)"})
37+
void roundTripsSharedCreateAndAlterCheck(String sql) throws JSQLParserException {
38+
Statement statement = parse(sql);
39+
assertEquals(sql, statement.toString());
40+
roundTrip(statement);
41+
}
42+
43+
@Test
44+
void exposesIndependentInheritanceAndValidationFlags() throws JSQLParserException {
45+
Alter alter = (Alter) parse(
46+
"ALTER TABLE t ADD CONSTRAINT ck CHECK (id > 0) NO INHERIT NOT VALID");
47+
CheckConstraint constraint =
48+
(CheckConstraint) alter.getAlterExpressions().get(0).getIndex();
49+
assertTrue(constraint.isNoInherit());
50+
assertTrue(constraint.getConstraintAttributes().isNotValid());
51+
constraint.setNoInherit(false);
52+
constraint.setExpression(CCJSqlParserUtil.parseExpression("id > 10"));
53+
assertEquals("ALTER TABLE t ADD CONSTRAINT ck CHECK (id > 10) NOT VALID", alter.toString());
54+
roundTrip(alter);
55+
List<Expression> visited = new ArrayList<>();
56+
TableDefinitionTraversal.visit(alter.getAlterExpressions().get(0), visited::add, table -> {
57+
});
58+
assertEquals(List.of(constraint.getExpression()), visited);
59+
alter.getAlterExpressions().get(0).setIndex(new CheckConstraint().withName("new_ck")
60+
.withExpression(CCJSqlParserUtil.parseExpression("id > 20")).withNoInherit(true));
61+
assertEquals("ALTER TABLE t ADD CONSTRAINT new_ck CHECK (id > 20) NO INHERIT",
62+
alter.toString());
63+
roundTrip(alter);
64+
}
65+
66+
@Test
67+
void preservesExistingCheckOptions() throws JSQLParserException {
68+
CreateTable table = (CreateTable) parse("CREATE TABLE t (id INT, CHECK (id > 0))");
69+
assertFalse(((CheckConstraint) table.getIndexes().get(0)).isNoInherit());
70+
for (String suffix : new String[] {"", " ENFORCED", " NOT ENFORCED"}) {
71+
String sql = "CREATE TABLE t (id INT, CHECK (id > 0)" + suffix + ")";
72+
assertEquals(sql, CCJSqlParserUtil.parse(sql).toString());
73+
assertEquals(sql,
74+
CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.MYSQL)).toString());
75+
}
76+
}
77+
78+
private static Statement parse(String sql) throws JSQLParserException {
79+
return CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.POSTGRESQL));
80+
}
81+
82+
private static void roundTrip(Statement statement) throws JSQLParserException {
83+
StringBuilder sql = new StringBuilder();
84+
statement.accept(new StatementDeParser(sql), null);
85+
assertEquals(statement.toString(), sql.toString());
86+
assertEquals(sql.toString(), parse(sql.toString()).toString());
87+
}
88+
}

0 commit comments

Comments
 (0)