Skip to content

Commit 98b80ea

Browse files
committed
Unify column option storage and mutation contracts
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent cdbaf1a commit 98b80ea

8 files changed

Lines changed: 306 additions & 61 deletions

File tree

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

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
package net.sf.jsqlparser.statement.create.table;
1111

1212
import net.sf.jsqlparser.statement.imprt.ImportColumn;
13-
import net.sf.jsqlparser.statement.select.PlainSelect;
1413
import net.sf.jsqlparser.expression.Expression;
1514

1615
import java.io.Serializable;
@@ -19,7 +18,7 @@
1918
import java.util.Collection;
2019
import java.util.Collections;
2120
import java.util.List;
22-
import java.util.Optional;
21+
import java.util.Objects;
2322
import java.util.function.Consumer;
2423

2524
/**
@@ -29,7 +28,6 @@ public class ColumnDefinition implements ImportColumn, TableElement, Serializabl
2928

3029
private String columnName;
3130
private ColDataType colDataType;
32-
private List<String> columnSpecs;
3331
private List<ColumnOption> columnOptions;
3432
private boolean withOptions;
3533

@@ -50,42 +48,54 @@ public ColumnDefinition(String columnName, ColDataType colDataType) {
5048

5149
public ColumnDefinition(String columnName, ColDataType colDataType, List<String> columnSpecs) {
5250
this(columnName, colDataType);
53-
this.columnSpecs = columnSpecs;
51+
columnOptions = rawOptions(columnSpecs);
5452
}
5553

5654
/**
57-
* Returns raw specifications, or a token snapshot when structured options are present. Use the
58-
* option API or {@link #addColumnSpecs(Collection)} to append without discarding structured
59-
* expressions, references and constraints. DEFAULT values use the expression's SQL rendering.
55+
* Returns an unmodifiable token snapshot for both raw and structured options, or null when
56+
* options are unset. Use {@link #getColumnOptions()} to edit the nodes, or
57+
* {@link #addColumnSpecs(Collection)} to append without discarding structured expressions,
58+
* references and constraints. DEFAULT values use the expression's SQL rendering.
6059
*/
6160
public List<String> getColumnSpecs() {
6261
if (columnOptions != null) {
6362
List<String> tokens = new ArrayList<>();
6463
for (ColumnOption option : columnOptions) {
6564
tokens.addAll(option.getTokens());
6665
}
67-
return tokens;
66+
return Collections.unmodifiableList(tokens);
6867
}
69-
return columnSpecs;
68+
return null;
7069
}
7170

71+
/**
72+
* Replaces every option with opaque tokens, without parsing them. The supplied list is copied;
73+
* null clears the options, while an empty list stores an explicitly empty option list.
74+
*/
7275
public void setColumnSpecs(List<String> list) {
73-
columnSpecs = list;
74-
columnOptions = null;
76+
columnOptions = rawOptions(list);
77+
}
78+
79+
private static List<ColumnOption> rawOptions(List<String> list) {
80+
List<ColumnOption> options = list == null ? null : new ArrayList<>();
81+
if (list != null && !list.isEmpty()) {
82+
options.add(ColumnOption.raw(new ArrayList<>(list)));
83+
}
84+
return options;
7585
}
7686

7787
/**
78-
* Returns column options in source order, including defaults, references, generated columns,
79-
* nullability, collation, comments, visibility and MySQL attributes. Unrecognized options
80-
* remain raw. Structured option keywords use canonical capitalization when rendered.
88+
* Returns the mutable option list in source order, or null when options are unset. Opaque
89+
* specifications are included as {@link ColumnOption.Kind#OTHER} nodes. Structured option
90+
* keywords use canonical capitalization when rendered.
8191
*/
8292
public List<ColumnOption> getColumnOptions() {
8393
return columnOptions;
8494
}
8595

96+
/** Copies the list membership while retaining the supplied mutable option nodes. */
8697
public void setColumnOptions(List<ColumnOption> columnOptions) {
87-
this.columnOptions = columnOptions;
88-
this.columnSpecs = null;
98+
this.columnOptions = columnOptions == null ? null : new ArrayList<>(columnOptions);
8999
}
90100

91101
public boolean isSerialDefaultValue() {
@@ -110,13 +120,12 @@ public ColumnDefinition withColumnOptions(List<ColumnOption> columnOptions) {
110120
}
111121

112122
public ColumnDefinition addColumnOptions(ColumnOption... columnOptions) {
113-
List<ColumnOption> collection =
114-
Optional.ofNullable(getColumnOptions()).orElseGet(ArrayList::new);
115-
if (this.columnOptions == null && columnSpecs != null && !columnSpecs.isEmpty()) {
116-
collection.add(ColumnOption.raw(new ArrayList<>(columnSpecs)));
123+
Objects.requireNonNull(columnOptions, "columnOptions");
124+
if (this.columnOptions == null) {
125+
this.columnOptions = new ArrayList<>();
117126
}
118-
Collections.addAll(collection, columnOptions);
119-
return withColumnOptions(collection);
127+
Collections.addAll(this.columnOptions, columnOptions);
128+
return this;
120129
}
121130

122131
public ColDataType getColDataType() {
@@ -170,8 +179,6 @@ protected void appendDataTypeAndSpecTo(StringBuilder builder,
170179
builder.append(' ');
171180
option.appendTo(builder, expressionPrinter);
172181
}
173-
} else if (columnSpecs != null && !columnSpecs.isEmpty()) {
174-
builder.append(' ').append(PlainSelect.getStringList(columnSpecs, false, false));
175182
}
176183
}
177184

@@ -194,15 +201,15 @@ public ColumnDefinition addColumnSpecs(String... columnSpecs) {
194201
return addColumnSpecs(Arrays.asList(columnSpecs));
195202
}
196203

204+
/** Appends copied opaque tokens after the existing nodes, preserving their structure. */
197205
public ColumnDefinition addColumnSpecs(Collection<String> columnSpecs) {
198-
if (columnOptions != null) {
199-
if (!columnSpecs.isEmpty()) {
200-
columnOptions.add(ColumnOption.raw(new ArrayList<>(columnSpecs)));
201-
}
202-
return this;
206+
Objects.requireNonNull(columnSpecs, "columnSpecs");
207+
if (columnOptions == null) {
208+
columnOptions = new ArrayList<>();
203209
}
204-
List<String> collection = Optional.ofNullable(getColumnSpecs()).orElseGet(ArrayList::new);
205-
collection.addAll(columnSpecs);
206-
return this.withColumnSpecs(collection);
210+
if (!columnSpecs.isEmpty()) {
211+
columnOptions.add(ColumnOption.raw(new ArrayList<>(columnSpecs)));
212+
}
213+
return this;
207214
}
208215
}

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

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2095,15 +2095,6 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
20952095
}
20962096
}
20972097

2098-
private static boolean hasStructuredColumnOption(List<ColumnOption> options) {
2099-
for (ColumnOption option : options) {
2100-
if (option.getKind() != ColumnOption.Kind.OTHER) {
2101-
return true;
2102-
}
2103-
}
2104-
return false;
2105-
}
2106-
21072098
/** Builds names from lexer-separated components without reinterpreting PostgreSQL dots. */
21082099
protected Table createTable(List<String> names) {
21092100
return Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect)) && names.size() == 1
@@ -14553,23 +14544,19 @@ ColumnDefinition ColumnDefinition(): {
1455314544
ColumnDefinition coldef;
1455414545
String columnName;
1455514546
ColDataType colDataType;
14556-
List<String> columnSpecs = new ArrayList<String>();
1455714547
List<ColumnOption> columnOptions = new ArrayList<ColumnOption>();
1455814548
ColumnOption option;
1455914549
} {
1456014550
columnName=RelObjectName()
1456114551
colDataType=ColDataType()
1456214552
( LOOKAHEAD(2) option=ColumnDefinitionOption() {
1456314553
columnOptions.add(option);
14564-
columnSpecs.addAll(option.getTokens());
1456514554
} )*
1456614555
{
1456714556
coldef = new ColumnDefinition();
1456814557
coldef.setColumnName(columnName);
1456914558
coldef.setColDataType(colDataType);
14570-
if (columnSpecs.size() > 0)
14571-
coldef.setColumnSpecs(columnSpecs);
14572-
if (hasStructuredColumnOption(columnOptions))
14559+
if (!columnOptions.isEmpty())
1457314560
coldef.setColumnOptions(columnOptions);
1457414561
return coldef;
1457514562
}
@@ -17286,7 +17273,6 @@ AlterExpression.ColumnDataType AlterExpressionColumnDataType():
1728617273
boolean withType = false;
1728717274
boolean useSetData = false;
1728817275
ColDataType dataType = null;
17289-
List<String> columnSpecs = new ArrayList<String>();
1729017276
List<ColumnOption> columnOptions = new ArrayList<ColumnOption>();
1729117277
ColumnOption option = null;
1729217278
AlterExpression.ColumnDataType result;
@@ -17308,19 +17294,16 @@ AlterExpression.ColumnDataType AlterExpressionColumnDataType():
1730817294
( LOOKAHEAD(2) dataType = ColDataType() )?
1730917295
( LOOKAHEAD(2, { !(withType && getToken(1).kind == K_USING) }) option = ColumnDefinitionOption() {
1731017296
columnOptions.add(option);
17311-
columnSpecs.addAll(option.getTokens());
1731217297
} )*
1731317298
[ LOOKAHEAD({ withType && getToken(1).kind == K_USING }) <K_USING> usingExpression=Expression() ]
1731417299
)
1731517300
{
1731617301
requireDdlSyntax(!withType || dataType != null, "TYPE requires a data type");
17317-
result = new AlterExpression.ColumnDataType(columnName, withType, dataType, columnSpecs);
17302+
result = new AlterExpression.ColumnDataType(columnName, withType, dataType, null);
1731817303
result.setUseSetData(useSetData);
1731917304
result.setUsingExpression(usingExpression);
1732017305
result.setIdentityAlterations(identityAlterations);
17321-
if (hasStructuredColumnOption(columnOptions)) {
17322-
result.setColumnOptions(columnOptions);
17323-
}
17306+
result.setColumnOptions(columnOptions);
1732417307
return result;
1732517308
}
1732617309
}

‎src/site/sphinx/usage.rst‎

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -537,11 +537,36 @@ Charset remains in ``ColDataType.getCharacterSet()``; column collation is in its
537537
COLLATE option. Consumers can map both directly without reconstructing tokens.
538538
StringValue comment bodies retain their SQL escape representation.
539539

540-
The legacy ``getColumnSpecs()`` returns a token snapshot when options are
541-
structured; expression fragments may occupy one token and structured keywords
542-
use canonical capitalization. Mutate the option objects to change the AST.
543-
``addColumnSpecs`` preserves existing options; ``setColumnSpecs`` explicitly
544-
replaces them with raw specifications.
540+
``ColumnDefinition`` stores all options in one ordered list, including opaque
541+
``ColumnOption.Kind.OTHER`` nodes. ``getColumnOptions()`` returns that mutable
542+
list. ``getColumnSpecs()`` returns an **unmodifiable snapshot** for both raw and
543+
structured options; expression fragments may occupy one token and structured
544+
keywords use canonical capitalization. Unset options return null; an explicitly
545+
empty list returns an empty snapshot.
546+
547+
.. code-block:: java
548+
549+
CreateTable table = (CreateTable) CCJSqlParserUtil.parse(
550+
"CREATE TABLE t (id INT DEFAULT 1)");
551+
ColumnDefinition column = table.getColumnDefinitions().get(0);
552+
column.getColumnOptions().get(0).setDefaultExpression(new LongValue(2));
553+
column.addColumnSpecs("NOT", "NULL"); // keeps the structured DEFAULT node
554+
// CREATE TABLE t (id INT DEFAULT 2 NOT NULL)
555+
556+
Migration: calls such as ``getColumnSpecs().add(...)`` or ``.set(...)`` now throw
557+
``UnsupportedOperationException`` instead of sometimes changing the AST and
558+
sometimes editing a discarded copy. Use ``addColumnSpecs`` to append raw tokens,
559+
edit the option nodes directly, or pass a modified token-list copy to
560+
``setColumnSpecs`` to replace every option with opaque text. Raw tokens are never
561+
automatically parsed. Consequently ``getColumnOptions()`` is also non-null for
562+
raw specifications; inspect ``Kind`` to distinguish them.
563+
564+
The constructor and ``setColumnSpecs`` copy supplied tokens. ``setColumnOptions``
565+
copies list membership but retains the mutable child nodes; later changes to the
566+
caller's list do not change the column. Both getters return null after either
567+
setter receives null. Appending preserves the current nodes and accepts an
568+
immutable input list. An empty append preserves existing options and creates an
569+
empty option list when none was set.
545570

546571
Inspect logical replication statements
547572
======================================

‎src/test/java/net/sf/jsqlparser/statement/create/ColumnAttributeTest.java‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ void mutationsUpdateLegacyTokensAndBothRenderers() throws Exception {
156156
column(statement).addColumnSpecs("CUSTOM");
157157
assertEquals(ColumnOption.Kind.OTHER, options.get(options.size() - 1).getKind());
158158
column(statement).setColumnSpecs(List.of("NOT", "NULL"));
159-
assertNull(column(statement).getColumnOptions());
159+
assertEquals(ColumnOption.Kind.OTHER,
160+
column(statement).getColumnOptions().get(0).getKind());
160161
assertEquals("CREATE TABLE t (c INT NOT NULL)", statement.toString());
161162
}
162163

‎src/test/java/net/sf/jsqlparser/statement/create/ColumnDefaultExpressionTest.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ void replacingDefaultUpdatesLegacyTokensAndBothRenderers(String sql)
130130
assertNotNull(defaultOption(column).getDefaultExpression());
131131
assertRoundTrip(statement);
132132
column.setColumnSpecs(Arrays.asList("DEFAULT", "9"));
133-
assertNull(column.getColumnOptions());
133+
assertEquals(ColumnOption.Kind.OTHER, column.getColumnOptions().get(0).getKind());
134134
assertEquals(sql.replace("(1 + 2) NOT NULL", "9"), statement.toString());
135135
assertRoundTrip(statement);
136136
}

0 commit comments

Comments
 (0)