Skip to content
Open
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 @@ -75,8 +75,9 @@ public void setColumnSpecs(List<String> list) {
}

/**
* Returns column options in source order, including structured defaults, references and MySQL
* {@code SERIAL DEFAULT VALUE}.
* Returns column options in source order, including defaults, references, generated columns,
* nullability, collation, comments, visibility and MySQL attributes. Unrecognized options
* remain raw. Structured option keywords use canonical capitalization when rendered.
*/
public List<ColumnOption> getColumnOptions() {
return columnOptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@
import java.util.Objects;
import java.util.function.Consumer;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.statement.select.PlainSelect;

/** A structured option following a column data type. */
public class ColumnOption implements Serializable {

public enum Kind {
SERIAL_DEFAULT_VALUE, REFERENCE, IDENTITY, CONSTRAINT, DEFAULT, OTHER
SERIAL_DEFAULT_VALUE, REFERENCE, IDENTITY, CONSTRAINT, DEFAULT, NULLABILITY, COLLATE, COMMENT, ON_UPDATE, GENERATED, AUTO_INCREMENT, VISIBILITY, OTHER
}

private Kind kind = Kind.OTHER;
Expand All @@ -31,6 +32,122 @@ public enum Kind {
private IdentityDefinition identityDefinition;
private Index constraint;
private Expression defaultExpression;
private Boolean nullable;
private Boolean visible;
private String collation;
private StringValue comment;
private Expression onUpdateExpression;
private GeneratedColumnDefinition generatedDefinition;

public static ColumnOption nullability(boolean nullable) {
ColumnOption option = new ColumnOption();
option.kind = Kind.NULLABILITY;
option.nullable = nullable;
return option;
}

public Boolean getNullable() {
return nullable;
}

public void setNullable(boolean nullable) {
this.nullable = nullable;
}

public static ColumnOption visibility(boolean visible) {
ColumnOption option = new ColumnOption();
option.kind = Kind.VISIBILITY;
option.visible = visible;
return option;
}

public Boolean getVisible() {
return visible;
}

public void setVisible(boolean visible) {
this.visible = visible;
}

public static ColumnOption autoIncrement() {
ColumnOption option = new ColumnOption();
option.kind = Kind.AUTO_INCREMENT;
return option;
}

public static ColumnOption collate(String collation) {
ColumnOption option = new ColumnOption();
option.kind = Kind.COLLATE;
option.setCollation(collation);
return option;
}

public String getCollation() {
return collation;
}

public void setCollation(String collation) {
this.collation = Objects.requireNonNull(collation, "collation");
}

public static ColumnOption comment(StringValue comment) {
ColumnOption option = new ColumnOption();
option.kind = Kind.COMMENT;
option.setComment(comment);
return option;
}

public StringValue getComment() {
return comment;
}

public void setComment(StringValue comment) {
this.comment = Objects.requireNonNull(comment, "comment");
}

public static ColumnOption onUpdate(Expression expression) {
ColumnOption option = new ColumnOption();
option.kind = Kind.ON_UPDATE;
option.setOnUpdateExpression(expression);
return option;
}

public Expression getOnUpdateExpression() {
return onUpdateExpression;
}

public void setOnUpdateExpression(Expression expression) {
onUpdateExpression = Objects.requireNonNull(expression, "expression");
}

public static ColumnOption generated(GeneratedColumnDefinition definition) {
ColumnOption option = new ColumnOption();
option.kind = Kind.GENERATED;
option.setGeneratedDefinition(definition);
return option;
}

public GeneratedColumnDefinition getGeneratedDefinition() {
return generatedDefinition;
}

public void setGeneratedDefinition(GeneratedColumnDefinition definition) {
generatedDefinition = Objects.requireNonNull(definition, "definition");
}

/** Visits expressions of the selected option kind, including comment literals. */
public void visitExpressions(Consumer<Expression> visitor) {
if (kind == Kind.DEFAULT) {
visitor.accept(defaultExpression);
} else if (kind == Kind.COMMENT) {
visitor.accept(comment);
} else if (kind == Kind.ON_UPDATE) {
visitor.accept(onUpdateExpression);
} else if (kind == Kind.GENERATED) {
visitor.accept(generatedDefinition.getExpression());
}
}


/** Creates a DEFAULT option. Use a NullValue expression for SQL NULL. */
public static ColumnOption defaultValue(Expression expression) {
Expand Down Expand Up @@ -99,11 +216,30 @@ public Kind getKind() {
}

public List<String> getTokens() {
if (kind == Kind.DEFAULT) {
return Arrays.asList("DEFAULT", String.valueOf(defaultExpression));
switch (kind) {
case DEFAULT:
return Arrays.asList("DEFAULT", String.valueOf(defaultExpression));
case NULLABILITY:
return nullable ? Collections.singletonList("NULL") : Arrays.asList("NOT", "NULL");
case COLLATE:
return Arrays.asList("COLLATE", collation);
case COMMENT:
return Arrays.asList("COMMENT", comment.toString());
case ON_UPDATE:
return 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.singletonList(toString());
case OTHER:
case SERIAL_DEFAULT_VALUE:
return tokens;
default:
return Collections.singletonList(toString());
}
return kind == Kind.OTHER || kind == Kind.SERIAL_DEFAULT_VALUE ? tokens
: Collections.singletonList(toString());
}

public ForeignKeyReference getForeignKeyReference() {
Expand All @@ -124,6 +260,29 @@ public void appendTo(StringBuilder builder, Consumer<Expression> expressionPrint
builder.append("DEFAULT ");
expressionPrinter.accept(defaultExpression);
break;
case NULLABILITY:
builder.append(nullable ? "NULL" : "NOT NULL");
break;
case VISIBILITY:
builder.append(visible ? "VISIBLE" : "INVISIBLE");
break;
case AUTO_INCREMENT:
builder.append("AUTO_INCREMENT");
break;
case COLLATE:
builder.append("COLLATE ").append(collation);
break;
case COMMENT:
builder.append("COMMENT ");
expressionPrinter.accept(comment);
break;
case ON_UPDATE:
builder.append("ON UPDATE ");
expressionPrinter.accept(onUpdateExpression);
break;
case GENERATED:
generatedDefinition.appendTo(builder, expressionPrinter);
break;
case REFERENCE:
builder.append(foreignKeyReference);
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2026 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement.create.table;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import net.sf.jsqlparser.expression.Expression;

/** A parenthesized generated column expression, distinct from an identity declaration. */
public class GeneratedColumnDefinition implements Serializable {
public enum Storage {
STORED, VIRTUAL
}

private Expression expression;
private boolean generatedAlways;
private Storage storage;

public GeneratedColumnDefinition(Expression expression) {
setExpression(expression);
}

public Expression getExpression() {
return expression;
}

public void setExpression(Expression expression) {
this.expression = Objects.requireNonNull(expression, "expression");
}

public boolean isGeneratedAlways() {
return generatedAlways;
}

public void setGeneratedAlways(boolean generatedAlways) {
this.generatedAlways = generatedAlways;
}

/** Returns null when the SQL does not specify STORED or VIRTUAL. */
public Storage getStorage() {
return storage;
}

public void setStorage(Storage storage) {
this.storage = storage;
}

public List<String> getTokens() {
List<String> tokens = new ArrayList<>();
if (generatedAlways) {
tokens.add("GENERATED");
tokens.add("ALWAYS");
}
tokens.add("AS");
tokens.add("(" + expression + ")");
if (storage != null) {
tokens.add(storage.name());
}
return tokens;
}

public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressionPrinter) {
if (generatedAlways) {
builder.append("GENERATED ALWAYS ");
}
builder.append("AS (");
expressionPrinter.accept(expression);
builder.append(')');
if (storage != null) {
builder.append(' ').append(storage);
}
return builder;
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
return appendTo(builder, builder::append).toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public static void visit(TableElement element, Consumer<Expression> expressions,
ColumnDefinition column = (ColumnDefinition) element;
if (column.getColumnOptions() != null) {
for (ColumnOption option : column.getColumnOptions()) {
accept(option.getDefaultExpression(), expressions);
option.visitExpressions(expressions);
if (option.getForeignKeyReference() != null) {
accept(option.getForeignKeyReference().getTable(), tables);
}
Expand Down
54 changes: 54 additions & 0 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -12651,12 +12651,44 @@ ColumnOption ColumnDefinitionOption(): {
IdentityDefinition identity;
NamedConstraint constraint;
Expression defaultExpression;
GeneratedColumnDefinition generated;
ObjectNames collationNames;
String collation;
} {
(
LOOKAHEAD({ isKeywordAhead("GENERATED")
&& (getToken(4).kind == K_IDENTITY || getToken(5).kind == K_IDENTITY) })
identity=IdentityDefinition() { option = ColumnOption.identity(identity); }
|
LOOKAHEAD({ (isKeywordAhead("GENERATED") && getToken(2).kind == K_ALWAYS
&& getToken(3).kind == K_AS && "(".equals(getToken(4).image))
|| (getToken(1).kind == K_AS && "(".equals(getToken(2).image)) })
generated=GeneratedColumnDefinition() { option = ColumnOption.generated(generated); }
|
LOOKAHEAD(<K_NOT> <K_NULL>) <K_NOT> <K_NULL> { option = ColumnOption.nullability(false); }
|
LOOKAHEAD(<K_NULL>) <K_NULL> { option = ColumnOption.nullability(true); }
|
LOOKAHEAD(<K_VISIBLE>) <K_VISIBLE> { option = ColumnOption.visibility(true); }
|
LOOKAHEAD(<K_INVISIBLE>) <K_INVISIBLE> { option = ColumnOption.visibility(false); }
|
LOOKAHEAD(<K_AUTO_INCREMENT>) <K_AUTO_INCREMENT> { option = ColumnOption.autoIncrement(); }
|
LOOKAHEAD(<K_COLLATE>) <K_COLLATE>
( tk=<S_CHAR_LITERAL> { collation = tk.image; }
| collationNames=RelObjectNames() { collation = String.join(".", collationNames.getNames()); } )
{ option = ColumnOption.collate(collation); }
|
LOOKAHEAD(<K_COMMENT>) <K_COMMENT> tk=<S_CHAR_LITERAL>
{ option = ColumnOption.comment(new StringValue(tk.image)); }
|
LOOKAHEAD(<K_ON> <K_UPDATE>) <K_ON> <K_UPDATE> defaultExpression=Expression()
{ option = ColumnOption.onUpdate(defaultExpression); }
|
LOOKAHEAD(<K_PRIMARY> <K_KEY>) <K_PRIMARY> <K_KEY>
{ option = ColumnOption.constraint(new NamedConstraint().withType("PRIMARY KEY")); }
|
LOOKAHEAD(<K_UNIQUE>) constraint=ColumnUniqueConstraint()
{ option = ColumnOption.constraint(constraint); }
|
Expand Down Expand Up @@ -12685,6 +12717,28 @@ ColumnOption ColumnDefinitionOption(): {
{ return option; }
}

GeneratedColumnDefinition GeneratedColumnDefinition():
{
Expression expression;
boolean generatedAlways = false;
GeneratedColumnDefinition.Storage storage = null;
GeneratedColumnDefinition result;
}
{
[ LOOKAHEAD({ isKeywordAhead("GENERATED") }) ContextualKeyword("GENERATED") <K_ALWAYS>
{ generatedAlways = true; } ]
<K_AS> "(" expression=Expression() ")"
[ LOOKAHEAD({ getToken(1).kind == K_STORED || isKeywordAhead("VIRTUAL") })
( <K_STORED> { storage = GeneratedColumnDefinition.Storage.STORED; }
| ContextualKeyword("VIRTUAL") { storage = GeneratedColumnDefinition.Storage.VIRTUAL; } ) ]
{
result = new GeneratedColumnDefinition(expression);
result.setGeneratedAlways(generatedAlways);
result.setStorage(storage);
return result;
}
}

NamedConstraint ColumnUniqueConstraint():
{
NamedConstraint constraint = new NamedConstraint().withType("UNIQUE");
Expand Down
Loading
Loading