Skip to content

Commit 329ee6b

Browse files
authored
Support Informix trailing constraint names and share constraint rendering (#2570)
* Support Informix trailing constraint names and share constraint rendering * fix(parser): gate Informix constraints on dialect selection
1 parent 8ff4f64 commit 329ee6b

9 files changed

Lines changed: 289 additions & 19 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ and missing syntax gets added on demand — [open an issue](https://github.com/J
142142
| **DML** | `INSERT` · `UPDATE` · `UPSERT` · `MERGE` · `DELETE` · `TRUNCATE TABLE` |
143143
| **DDL** | `CREATE …` · `ALTER …` · `DROP …` |
144144
| **PostgreSQL RLS** | `CREATE POLICY` · `ALTER TABLE … ENABLE`/`DISABLE`/`FORCE`/`NO FORCE ROW LEVEL SECURITY` |
145+
| **Informix constraints** | `ALTER TABLE … ADD CONSTRAINT` with trailing constraint names for primary, unique, foreign and check constraints; enable with `parser.withDialect(Dialect.INFORMIX)` |
145146
| **Salesforce SOQL** | `INCLUDES` · `EXCLUDES` |
146147

147148
Beyond statement shapes, the grammar handles nested sub-selects, bind parameters (`?`,

src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public enum Dialect {
4646
AdjacentStringLiterals.WHITESPACE,
4747
Feature.allowDoubleQuotedStrings,
4848
Feature.allowBackslashEscapeCharacter), SNOWFLAKE(
49-
Feature.allowBackslashEscapeCharacter);
49+
Feature.allowBackslashEscapeCharacter), INFORMIX;
5050

5151
private final Set<Feature> lexerFeatures;
5252
private final AdjacentStringLiterals adjacentStringLiterals;

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,12 @@ public void setEnforced(Boolean enforced) {
5454
@Override
5555
public String toString() {
5656
StringBuilder b = new StringBuilder();
57-
if (isUseConstraintKeyword() || getName() != null) {
58-
b.append("CONSTRAINT");
59-
if (getName() != null) {
60-
b.append(" ").append(getName());
61-
}
62-
b.append(" ");
63-
}
57+
appendConstraintPrefixTo(b);
6458
b.append("CHECK (").append(expression).append(")");
6559
if (enforced != null) {
6660
b.append(enforced ? " ENFORCED" : " NOT ENFORCED");
6761
}
62+
appendConstraintSuffixTo(b);
6863
appendConstraintAttributesTo(b);
6964
return b.toString();
7065
}

src/main/java/net/sf/jsqlparser/statement/create/table/ForeignKeyIndex.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ public String toString() {
174174
.append(PlainSelect.getStringList(getReferencedColumnNames(), true, true));
175175
referentialActions.forEach(b::append);
176176
}
177+
appendConstraintSuffixTo(b);
177178
appendConstraintAttributesTo(b);
178179
return b.toString();
179180
}

src/main/java/net/sf/jsqlparser/statement/create/table/NamedConstraint.java

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,45 @@ public class NamedConstraint extends Index {
1818

1919
private String indexName;
2020
private boolean useConstraintKeyword;
21+
private ConstraintNamePosition constraintNamePosition = ConstraintNamePosition.BEFORE;
22+
23+
/** Position of the constraint symbol relative to its definition. */
24+
public enum ConstraintNamePosition {
25+
BEFORE, AFTER
26+
}
27+
28+
public ConstraintNamePosition getConstraintNamePosition() {
29+
return constraintNamePosition;
30+
}
31+
32+
public void setConstraintNamePosition(ConstraintNamePosition position) {
33+
constraintNamePosition = java.util.Objects.requireNonNull(position, "position");
34+
}
35+
36+
public NamedConstraint withConstraintNamePosition(ConstraintNamePosition position) {
37+
setConstraintNamePosition(position);
38+
return this;
39+
}
40+
41+
/** Appends the leading keyword and, for the usual syntax, the constraint name. */
42+
public void appendConstraintPrefixTo(StringBuilder builder) {
43+
boolean leadingName = getName() != null
44+
&& constraintNamePosition == ConstraintNamePosition.BEFORE;
45+
if (useConstraintKeyword || leadingName) {
46+
builder.append("CONSTRAINT");
47+
if (leadingName) {
48+
builder.append(' ').append(getName());
49+
}
50+
builder.append(' ');
51+
}
52+
}
53+
54+
/** Appends an Informix constraint name after the complete constraint definition. */
55+
public void appendConstraintSuffixTo(StringBuilder builder) {
56+
if (constraintNamePosition == ConstraintNamePosition.AFTER && getName() != null) {
57+
builder.append(" CONSTRAINT ").append(getName());
58+
}
59+
}
2160

2261
/**
2362
* Returns the optional index name declared after the constraint type. This is distinct from
@@ -44,9 +83,6 @@ public void setUseConstraintKeyword(boolean useConstraintKeyword) {
4483
@Override
4584
public String toString() {
4685
String idxSpecText = PlainSelect.getStringList(getIndexSpec(), false, false);
47-
String head = useConstraintKeyword || getName() != null
48-
? "CONSTRAINT" + (getName() != null ? " " + getName() : "") + " "
49-
: "";
5086
String keyword = getIndexKeyword() != null
5187
&& !getType().toUpperCase(java.util.Locale.ROOT)
5288
.endsWith(getIndexKeyword().toUpperCase(java.util.Locale.ROOT))
@@ -61,9 +97,12 @@ public String toString() {
6197
: " " + PlainSelect.getStringList(getColumnsNames(), true, true))
6298
+
6399
(!"".equals(idxSpecText) ? " " + idxSpecText : "");
64-
StringBuilder sql = new StringBuilder(head).append(tail);
100+
StringBuilder sql = new StringBuilder();
101+
appendConstraintPrefixTo(sql);
102+
sql.append(tail);
65103
appendConstraintOptionsTo(sql);
66104
if (getKind() != Kind.FOREIGN_KEY) {
105+
appendConstraintSuffixTo(sql);
67106
appendConstraintAttributesTo(sql);
68107
}
69108
return sql.toString();

src/main/java/net/sf/jsqlparser/util/deparser/TableElementDeParser.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,7 @@ private void deParseExclude(ExcludeConstraint constraint) {
9494
}
9595

9696
private void deParseCheck(CheckConstraint constraint) {
97-
if (constraint.getName() != null || constraint.isUseConstraintKeyword()) {
98-
builder.append("CONSTRAINT");
99-
if (constraint.getName() != null) {
100-
builder.append(' ').append(constraint.getName());
101-
}
102-
builder.append(' ');
103-
}
97+
constraint.appendConstraintPrefixTo(builder);
10498
builder.append("CHECK (");
10599
if (constraint.getExpression() != null) {
106100
constraint.getExpression().accept(expressionVisitor, null);
@@ -111,6 +105,7 @@ private void deParseCheck(CheckConstraint constraint) {
111105
if (constraint.getEnforced() != null) {
112106
builder.append(constraint.getEnforced() ? " ENFORCED" : " NOT ENFORCED");
113107
}
108+
constraint.appendConstraintSuffixTo(builder);
114109
constraint.appendConstraintAttributesTo(builder);
115110
}
116111
}

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14554,6 +14554,36 @@ DefaultConstraint DefaultConstraintSpec():
1455414554
{ return constraint; }
1455514555
}
1455614556

14557+
/** Parses Informix's ADD CONSTRAINT definition [CONSTRAINT name]. */
14558+
NamedConstraint InformixConstraint():
14559+
{
14560+
NamedConstraint constraint;
14561+
Token type;
14562+
List<String> columns;
14563+
String name;
14564+
}
14565+
{
14566+
<K_CONSTRAINT>
14567+
(
14568+
( type=<K_PRIMARY> <K_KEY> | type=<K_UNIQUE> )
14569+
columns=ColumnsNamesList() {
14570+
constraint = new NamedConstraint()
14571+
.withType(type.kind == K_PRIMARY ? "PRIMARY KEY" : type.image)
14572+
.withColumnsNames(columns);
14573+
}
14574+
|
14575+
constraint=ForeignKeySpec(null)
14576+
|
14577+
constraint=CheckConstraintSpec(null)
14578+
)
14579+
[ <K_CONSTRAINT> name=RelObjectName() { constraint.setName(name); } ]
14580+
{
14581+
constraint.setUseConstraintKeyword(true);
14582+
constraint.setConstraintNamePosition(NamedConstraint.ConstraintNamePosition.AFTER);
14583+
return constraint;
14584+
}
14585+
}
14586+
1455714587
/**
1455814588
* Parses ADD/ALTER CONSTRAINT clause within AlterExpression.
1455914589
* Handles: CONSTRAINT [UNIQUE [KEY|INDEX]] name columns
@@ -14854,6 +14884,15 @@ AlterExpression AlterExpressionAddAlterModify():
1485414884
alterExp.setIndex(index);
1485514885
}
1485614886
|
14887+
LOOKAHEAD(<K_CONSTRAINT> (<K_PRIMARY> <K_KEY> | <K_FOREIGN> <K_KEY>
14888+
| <K_UNIQUE> | <K_CHECK>) "(",
14889+
{ Dialect.INFORMIX.name().equals(getAsString(Feature.dialect)) })
14890+
index=InformixConstraint() {
14891+
requireDdlSyntax(alterExp.getOperation() == AlterOperation.ADD,
14892+
"Informix constraint definitions require ADD");
14893+
alterExp.setIndex(index);
14894+
}
14895+
|
1485714896
LOOKAHEAD({ isTableIndexAhead() }) index=TableIndexSpec(false) {
1485814897
alterExp.setIndex(index);
1485914898
if (index.getKind() == Index.Kind.PRIMARY_KEY) {

src/site/sphinx/usage.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,9 +711,20 @@ One grammar covers every supported RDBMS, but a few pieces of syntax mean differ
711711
- ``withDoubleQuotedStrings``, ``withBackslashEscapeCharacter``, any-whitespace rule for adjacent string literals
712712
* - ``SNOWFLAKE``
713713
- ``withBackslashEscapeCharacter`` only, double quotes stay quoted identifiers
714+
* - ``INFORMIX``
715+
- Informix ``ALTER TABLE ... ADD CONSTRAINT`` definitions with optional trailing constraint names
714716

715717
Features set explicitly *after* the preset win over it.
716718

719+
Informix's constraint form requires an explicit dialect selection:
720+
721+
.. code-block:: java
722+
723+
Statement stmt = CCJSqlParserUtil.parse(
724+
"ALTER TABLE child ADD CONSTRAINT FOREIGN KEY (id) "
725+
+ "REFERENCES parent(id) CONSTRAINT fk_child",
726+
parser -> parser.withDialect(Dialect.INFORMIX));
727+
717728
The individual features
718729
------------------------------
719730

0 commit comments

Comments
 (0)