Skip to content

Commit fec4ec4

Browse files
authored
fix: preserve structured column options when editing specifications (#2608)
1 parent d730bf0 commit fec4ec4

2 files changed

Lines changed: 108 additions & 3 deletions

File tree

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import java.io.Serializable;
1616
import java.util.ArrayList;
17+
import java.util.Arrays;
1718
import java.util.Collection;
1819
import java.util.Collections;
1920
import java.util.List;
@@ -50,6 +51,11 @@ public ColumnDefinition(String columnName, ColDataType colDataType, List<String>
5051
this.columnSpecs = columnSpecs;
5152
}
5253

54+
/**
55+
* Returns raw specifications, or a token snapshot when structured options are present. Use the
56+
* option API or {@link #addColumnSpecs(Collection)} to append without discarding structured
57+
* references and constraints.
58+
*/
5359
public List<String> getColumnSpecs() {
5460
if (columnOptions != null) {
5561
List<String> tokens = new ArrayList<>();
@@ -76,6 +82,7 @@ public List<ColumnOption> getColumnOptions() {
7682

7783
public void setColumnOptions(List<ColumnOption> columnOptions) {
7884
this.columnOptions = columnOptions;
85+
this.columnSpecs = null;
7986
}
8087

8188
public boolean isSerialDefaultValue() {
@@ -102,6 +109,9 @@ public ColumnDefinition withColumnOptions(List<ColumnOption> columnOptions) {
102109
public ColumnDefinition addColumnOptions(ColumnOption... columnOptions) {
103110
List<ColumnOption> collection =
104111
Optional.ofNullable(getColumnOptions()).orElseGet(ArrayList::new);
112+
if (this.columnOptions == null && columnSpecs != null && !columnSpecs.isEmpty()) {
113+
collection.add(ColumnOption.raw(new ArrayList<>(columnSpecs)));
114+
}
105115
Collections.addAll(collection, columnOptions);
106116
return withColumnOptions(collection);
107117
}
@@ -153,12 +163,16 @@ public ColumnDefinition withColumnSpecs(List<String> columnSpecs) {
153163
}
154164

155165
public ColumnDefinition addColumnSpecs(String... columnSpecs) {
156-
List<String> collection = Optional.ofNullable(getColumnSpecs()).orElseGet(ArrayList::new);
157-
Collections.addAll(collection, columnSpecs);
158-
return this.withColumnSpecs(collection);
166+
return addColumnSpecs(Arrays.asList(columnSpecs));
159167
}
160168

161169
public ColumnDefinition addColumnSpecs(Collection<String> columnSpecs) {
170+
if (columnOptions != null) {
171+
if (!columnSpecs.isEmpty()) {
172+
columnOptions.add(ColumnOption.raw(new ArrayList<>(columnSpecs)));
173+
}
174+
return this;
175+
}
162176
List<String> collection = Optional.ofNullable(getColumnSpecs()).orElseGet(ArrayList::new);
163177
collection.addAll(columnSpecs);
164178
return this.withColumnSpecs(collection);
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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.assertEquals;
13+
import static org.junit.jupiter.api.Assertions.assertNull;
14+
import static org.junit.jupiter.api.Assertions.assertSame;
15+
import static org.junit.jupiter.api.Assertions.assertTrue;
16+
17+
import java.util.Arrays;
18+
import net.sf.jsqlparser.JSQLParserException;
19+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
20+
import net.sf.jsqlparser.statement.create.table.ColumnDefinition;
21+
import net.sf.jsqlparser.statement.create.table.ColumnOption;
22+
import net.sf.jsqlparser.statement.create.table.CreateTable;
23+
import net.sf.jsqlparser.statement.create.table.ForeignKeyReference;
24+
import net.sf.jsqlparser.util.TablesNamesFinder;
25+
import net.sf.jsqlparser.util.deparser.StatementDeParser;
26+
import org.junit.jupiter.api.Test;
27+
28+
class ColumnOptionMutationTest {
29+
@Test
30+
void clearingIdentityDoesNotRestoreRawSpecifications() throws JSQLParserException {
31+
CreateTable table = parse("id INT GENERATED ALWAYS AS IDENTITY");
32+
ColumnDefinition column = table.getColumnDefinitions().get(0);
33+
column.getColumnOptions().clear();
34+
assertTrue(column.getColumnSpecs().isEmpty());
35+
assertSql(table, "CREATE TABLE t (id INT)");
36+
}
37+
38+
@Test
39+
void legacyAppendPreservesReferenceAndTraversal() throws JSQLParserException {
40+
CreateTable table = parse("id INT REFERENCES parent(id)");
41+
ColumnDefinition column = table.getColumnDefinitions().get(0);
42+
ForeignKeyReference reference = column.getForeignKeyReference();
43+
column.addColumnSpecs("NOT", "NULL");
44+
column.addColumnSpecs(Arrays.asList("DEFAULT", "1"));
45+
assertSame(reference, column.getForeignKeyReference());
46+
assertTrue(new TablesNamesFinder().getTables(table).contains("parent"));
47+
assertSql(table, "CREATE TABLE t (id INT REFERENCES parent(id) NOT NULL DEFAULT 1)");
48+
}
49+
50+
@Test
51+
void addingStructuredOptionPreservesExistingRawSpecifications() throws JSQLParserException {
52+
CreateTable table = parse("id INT NOT NULL");
53+
ColumnDefinition column = table.getColumnDefinitions().get(0);
54+
assertNull(column.getColumnOptions());
55+
ForeignKeyReference reference = parse("id INT REFERENCES parent(id)")
56+
.getColumnDefinitions().get(0).getForeignKeyReference();
57+
column.addColumnOptions(ColumnOption.reference(reference));
58+
assertSql(table, "CREATE TABLE t (id INT NOT NULL REFERENCES parent(id))");
59+
assertTrue(new TablesNamesFinder().getTables(table).contains("parent"));
60+
}
61+
62+
@Test
63+
void replacingOptionsWithNullClearsPreviousSpecifications() throws JSQLParserException {
64+
CreateTable table = parse("id INT GENERATED ALWAYS AS IDENTITY");
65+
ColumnDefinition column = table.getColumnDefinitions().get(0);
66+
column.setColumnOptions(null);
67+
assertNull(column.getColumnSpecs());
68+
assertSql(table, "CREATE TABLE t (id INT)");
69+
}
70+
71+
@Test
72+
void legacySetterStillReplacesStructuredOptions() throws JSQLParserException {
73+
CreateTable table = parse("id INT REFERENCES parent(id)");
74+
ColumnDefinition column = table.getColumnDefinitions().get(0);
75+
column.setColumnSpecs(Arrays.asList("NOT", "NULL"));
76+
assertNull(column.getForeignKeyReference());
77+
assertSql(table, "CREATE TABLE t (id INT NOT NULL)");
78+
}
79+
80+
private static CreateTable parse(String definition) throws JSQLParserException {
81+
return (CreateTable) CCJSqlParserUtil.parse("CREATE TABLE t (" + definition + ")");
82+
}
83+
84+
private static void assertSql(CreateTable table, String expected) throws JSQLParserException {
85+
assertEquals(expected, table.toString());
86+
StringBuilder buffer = new StringBuilder();
87+
table.accept(new StatementDeParser(buffer), null);
88+
assertEquals(expected, buffer.toString());
89+
assertEquals(expected, CCJSqlParserUtil.parse(expected).toString());
90+
}
91+
}

0 commit comments

Comments
 (0)