Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
package net.sf.jsqlparser.statement.create.table;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -268,9 +269,10 @@ public ConstraintKind getConstraintKind() {
}
}

/** Creates an opaque option with its own copy of the supplied tokens, without parsing them. */
public static ColumnOption raw(List<String> tokens) {
ColumnOption option = new ColumnOption();
option.tokens = tokens;
option.setTokens(tokens);
return option;
}

Expand All @@ -295,33 +297,60 @@ public Kind getKind() {
return kind;
}

/**
* Returns an unmodifiable, detached token snapshot for both raw and structured options. An
* unset raw option returns null; an explicitly empty raw option returns an empty snapshot. Use
* {@link #setTokens(List)} to edit an opaque option, or the typed setters and mutable child
* nodes to edit structured options. Later changes to the option do not change this snapshot.
*/
public List<String> getTokens() {
switch (kind) {
case DEFAULT:
return Arrays.asList("DEFAULT", String.valueOf(defaultExpression));
return Collections.unmodifiableList(
Arrays.asList("DEFAULT", String.valueOf(defaultExpression)));
case NULLABILITY:
return nullable ? Collections.singletonList("NULL") : Arrays.asList("NOT", "NULL");
return nullable ? Collections.singletonList("NULL")
: Collections.unmodifiableList(Arrays.asList("NOT", "NULL"));
case COLLATE:
return Arrays.asList("COLLATE", collation);
return Collections.unmodifiableList(Arrays.asList("COLLATE", collation));
case COMMENT:
return Arrays.asList("COMMENT", comment.toString());
return Collections.unmodifiableList(Arrays.asList("COMMENT", comment.toString()));
case ON_UPDATE:
return Arrays.asList("ON", "UPDATE", onUpdateExpression.toString());
return Collections.unmodifiableList(
Arrays.asList("ON", "UPDATE", onUpdateExpression.toString()));
case GENERATED:
return generatedDefinition.getTokens();
case CONSTRAINT:
if ("PRIMARY KEY".equals(constraint.toString())) {
return Arrays.asList("PRIMARY", "KEY");
return Collections.unmodifiableList(Arrays.asList("PRIMARY", "KEY"));
}
return Collections.singletonList(toString());
case OTHER:
case SERIAL_DEFAULT_VALUE:
return tokens;
return tokens == null ? null
: Collections.unmodifiableList(new ArrayList<>(tokens));
default:
return Collections.singletonList(toString());
}
}

/**
* Replaces the tokens of an opaque {@link Kind#OTHER} option with a copy of the supplied list.
* Null unsets the tokens; an empty list retains an explicitly empty option. Tokens are never
* parsed or classified. To replace a structured option, including SERIAL DEFAULT VALUE, with
* raw text, replace its node in {@link ColumnDefinition#getColumnOptions()} using
* {@link #raw(List)} instead.
*
* @throws IllegalStateException if this is a structured option
*/
public void setTokens(List<String> tokens) {
if (kind != Kind.OTHER) {
throw new IllegalStateException(
"Only opaque options accept tokens; use typed setters or replace the option");
}
this.tokens = tokens == null ? null : new ArrayList<>(tokens);
}

public ForeignKeyReference getForeignKeyReference() {
return foreignKeyReference;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
Expand Down Expand Up @@ -55,6 +56,10 @@ public void setStorage(Storage storage) {
this.storage = storage;
}

/**
* Returns an unmodifiable token snapshot. Use the setters or edit {@link #getExpression()} to
* change the definition; later changes do not affect previously returned snapshots.
*/
public List<String> getTokens() {
List<String> tokens = new ArrayList<>();
if (generatedAlways) {
Expand All @@ -66,7 +71,7 @@ public List<String> getTokens() {
if (storage != null) {
tokens.add(storage.name());
}
return tokens;
return Collections.unmodifiableList(tokens);
}

public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressionPrinter) {
Expand Down
29 changes: 29 additions & 0 deletions src/site/sphinx/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,35 @@ setter receives null. Appending preserves the current nodes and accepts an
immutable input list. An empty append preserves existing options and creates an
empty option list when none was set.

``ColumnOption.getTokens()`` and ``GeneratedColumnDefinition.getTokens()`` also
return unmodifiable, detached snapshots. Reading tokens does not detach the
underlying expressions or other child nodes: their setters and direct edits
remain visible in SQL output, visitors and newly requested snapshots.

For opaque ``Kind.OTHER`` options, copy the tokens, edit the copy, then call
``setTokens``. The setter and both ``ColumnOption.raw`` factories copy their
inputs; subsequent changes to the supplied list or array do not edit the AST.
Null raw tokens remain unset and return null; an empty list returns an empty
snapshot. Raw tokens are never parsed or classified.

.. code-block:: java

ColumnOption raw = ColumnOption.raw("COLUMN_FORMAT", "DYNAMIC");
List<String> tokens = new ArrayList<>(raw.getTokens());
tokens.set(1, "FIXED");
raw.setTokens(tokens); // COLUMN_FORMAT FIXED

Migration: raw token getter edits now throw ``UnsupportedOperationException``,
as do edits to structured token snapshots that previously changed a discarded
copy. ``setTokens`` only accepts opaque options; it throws
``IllegalStateException`` for structured options, including SERIAL DEFAULT
VALUE. Use typed setters for structured edits. To explicitly replace a
structured option with raw text, replace its node in the column's option list:

.. code-block:: java

column.getColumnOptions().set(0, ColumnOption.raw("DEFAULT", "8"));

Inspect logical replication statements
======================================

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@ void specificationSnapshotsAreReadOnlyForRawAndStructuredOptions(String sql)
if (option.getKind() == ColumnOption.Kind.OTHER) {
assertTrue(column.getColumnOptions().stream()
.allMatch(raw -> raw.getKind() == ColumnOption.Kind.OTHER));
List<String> rawTokens = column.getColumnOptions().stream()
.map(ColumnOption::getTokens).filter(tokens -> tokens.contains("DYNAMIC"))
ColumnOption rawOption = column.getColumnOptions().stream()
.filter(raw -> raw.getTokens().contains("DYNAMIC"))
.findFirst().orElseThrow();
List<String> rawTokens = new ArrayList<>(rawOption.getTokens());
rawTokens.set(rawTokens.indexOf("DYNAMIC"), "FIXED");
rawOption.setTokens(rawTokens);
assertEquals(List.of("COLUMN_FORMAT", "DYNAMIC"), snapshot);
assertEquals(List.of("COLUMN_FORMAT", "FIXED"), column.getColumnSpecs());
} else {
Expand Down
Loading
Loading