[jOOQ/jOOQ#6492] Add support for computed columns

- Add DataType<T>.generatedAlwaysAs(Field<T>)
- Add code generation support for:
  - H2
  - PostgreSQL
- Added parser support
- Added code generation tests
- Added DDL support
This commit is contained in:
Lukas Eder 2021-11-23 16:45:28 +01:00
parent b2465ca45b
commit 08be0478da
19 changed files with 471 additions and 85 deletions

View File

@ -8489,6 +8489,7 @@ public class JavaGenerator extends AbstractGenerator {
false,
false,
null,
null,
baseType
) + ".getArrayDataType()";
}
@ -8504,6 +8505,7 @@ public class JavaGenerator extends AbstractGenerator {
type.isNullable(),
type.isIdentity(),
type.isReadonly(),
type.getGeneratedAlwaysAs(),
type.getDefaultValue(),
type.getQualifiedUserType()
);
@ -8749,7 +8751,21 @@ public class JavaGenerator extends AbstractGenerator {
return type;
}
protected String getTypeReference(Database db, SchemaDefinition schema, JavaWriter out, String t, int p, int s, int l, boolean n, boolean i, boolean r, String d, Name u) {
protected String getTypeReference(
Database db,
SchemaDefinition schema,
JavaWriter out,
String t,
int p,
int s,
int l,
boolean n,
boolean i,
boolean r,
String g,
String d,
Name u
) {
StringBuilder sb = new StringBuilder();
if (db.getArray(schema, u) != null) {
@ -8808,6 +8824,8 @@ public class JavaGenerator extends AbstractGenerator {
if (d != null)
dataType = dataType.defaultValue((Field) DSL.field(d, dataType));
@ -8863,6 +8881,18 @@ public class JavaGenerator extends AbstractGenerator {
// [#5291] Some dialects report valid SQL expresions (e.g. PostgreSQL), others
// report actual values (e.g. MySQL).
if (dataType.defaulted()) {

View File

@ -50,7 +50,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
// ...
import org.jooq.meta.jaxb.SyntheticReadonlyColumnType;
import org.jooq.meta.jaxb.SyntheticReadonlyRowidType;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.StringUtils;

View File

@ -115,6 +115,16 @@ public interface DataTypeDefinition {
*/
boolean isReadonly();
/**
* Whether this data type is computed.
*/
boolean isComputed();
/**
* The computed column expression.
*/
String getGeneratedAlwaysAs();
/**
* Whether this data type is an identity.
*/

View File

@ -73,6 +73,7 @@ public class DefaultDataTypeDefinition implements DataTypeDefinition {
private final String binding;
private final boolean nullable;
private boolean readonly;
private String generatedAlwaysAs;
private boolean identity;
private final String defaultValue;
private final int length;
@ -188,10 +189,10 @@ public class DefaultDataTypeDefinition implements DataTypeDefinition {
}
public DefaultDataTypeDefinition(Database database, SchemaDefinition schema, String typeName, Number length, Number precision, Number scale, Boolean nullable, String defaultValue, boolean isIdentity, Name userType, String converter, String binding, String javaType) {
this(database, schema, typeName, length, precision, scale, nullable, false, defaultValue, isIdentity, userType, converter, binding, javaType);
this(database, schema, typeName, length, precision, scale, nullable, false, null, defaultValue, isIdentity, userType, converter, binding, javaType);
}
public DefaultDataTypeDefinition(Database database, SchemaDefinition schema, String typeName, Number length, Number precision, Number scale, Boolean nullable, boolean readonly, String defaultValue, boolean identity, Name userType, String converter, String binding, String javaType) {
public DefaultDataTypeDefinition(Database database, SchemaDefinition schema, String typeName, Number length, Number precision, Number scale, Boolean nullable, boolean readonly, String generatedAlwaysAs, String defaultValue, boolean identity, Name userType, String converter, String binding, String javaType) {
this.database = database;
this.schema = schema;
@ -221,6 +222,7 @@ public class DefaultDataTypeDefinition implements DataTypeDefinition {
this.scale = scale == null ? 0 : scale.intValue();
this.nullable = nullable == null ? true : nullable.booleanValue();
this.readonly = readonly;
this.generatedAlwaysAs = generatedAlwaysAs;
this.defaultValue = defaultValue;
this.identity = identity;
}
@ -254,6 +256,21 @@ public class DefaultDataTypeDefinition implements DataTypeDefinition {
return readonly;
}
@Override
public final boolean isComputed() {
return getGeneratedAlwaysAs() != null;
}
@Override
public final String getGeneratedAlwaysAs() {
return generatedAlwaysAs;
}
public final DefaultDataTypeDefinition generatedAlwaysAs(String g) {
this.generatedAlwaysAs = g;
return this;
}
public final DefaultDataTypeDefinition identity(boolean i) {
this.identity = i;
return this;

View File

@ -109,6 +109,7 @@ public class H2TableDefinition extends AbstractTableDefinition {
COLUMNS.NUMERIC_PRECISION.decode(maxP, zero(), COLUMNS.NUMERIC_PRECISION).as(COLUMNS.NUMERIC_PRECISION),
COLUMNS.NUMERIC_SCALE.decode(maxS, zero(), COLUMNS.NUMERIC_SCALE).as(COLUMNS.NUMERIC_SCALE),
COLUMNS.IS_NULLABLE,
COLUMNS.IS_COMPUTED,
COLUMNS.COLUMN_DEFAULT,
COLUMNS.REMARKS,
COLUMNS.SEQUENCE_NAME,
@ -132,6 +133,7 @@ public class H2TableDefinition extends AbstractTableDefinition {
null != record.get(COLUMNS.SEQUENCE_NAME)
|| defaultString(record.get(COLUMNS.COLUMN_DEFAULT)).trim().toLowerCase().startsWith("nextval");
boolean isComputed = record.get(COLUMNS.IS_COMPUTED, boolean.class);
// [#7644] H2 puts DATETIME_PRECISION in NUMERIC_SCALE column
boolean isTimestamp = record.get(COLUMNS.TYPE_NAME).trim().toLowerCase().startsWith("timestamp");
@ -153,9 +155,9 @@ public class H2TableDefinition extends AbstractTableDefinition {
? 0
: record.get(COLUMNS.NUMERIC_SCALE),
record.get(COLUMNS.IS_NULLABLE, boolean.class),
isIdentity ? null : record.get(COLUMNS.COLUMN_DEFAULT),
isIdentity || isComputed ? null : record.get(COLUMNS.COLUMN_DEFAULT),
userType
);
).generatedAlwaysAs(isComputed ? record.get(COLUMNS.COLUMN_DEFAULT) : null);
result.add(new DefaultColumnDefinition(
getDatabase().getTable(getSchema(), getName()),

View File

@ -51,7 +51,6 @@ import static org.jooq.meta.postgres.pg_catalog.Tables.PG_ATTRIBUTE;
import static org.jooq.meta.postgres.pg_catalog.Tables.PG_CLASS;
import static org.jooq.meta.postgres.pg_catalog.Tables.PG_DESCRIPTION;
import static org.jooq.meta.postgres.pg_catalog.Tables.PG_NAMESPACE;
import static org.jooq.util.postgres.PostgresDSL.oid;
import java.sql.SQLException;
import java.util.ArrayList;
@ -124,6 +123,7 @@ public class PostgresTableDefinition extends AbstractTableDefinition {
COLUMNS.NUMERIC_SCALE,
(when(isIdentity, inline("YES"))).as(COLUMNS.IS_IDENTITY),
COLUMNS.IS_NULLABLE,
COLUMNS.GENERATION_EXPRESSION,
(when(isIdentity, inline(null, String.class)).else_(COLUMNS.COLUMN_DEFAULT)).as(COLUMNS.COLUMN_DEFAULT),
coalesce(COLUMNS.DOMAIN_SCHEMA, udtSchema).as(COLUMNS.UDT_SCHEMA),
coalesce(COLUMNS.DOMAIN_NAME, COLUMNS.UDT_NAME).as(COLUMNS.UDT_NAME),
@ -166,7 +166,7 @@ public class PostgresTableDefinition extends AbstractTableDefinition {
record.get(COLUMNS.UDT_SCHEMA),
record.get(COLUMNS.UDT_NAME)
)
);
).generatedAlwaysAs(record.get(COLUMNS.GENERATION_EXPRESSION));
ColumnDefinition column = new DefaultColumnDefinition(
getDatabase().getTable(getSchema(), getName()),

View File

@ -443,6 +443,44 @@ public interface DataType<T> extends Named {
*/
boolean readonly();
/**
* Whether this column is computed.
* <p>
* This feature is implemented in commercial distributions only.
*/
boolean computed();
/**
* Set the computed column expression of this data type.
* <p>
* This implicitly sets {@link #readonly()} to <code>true</code>.
* <p>
* This feature is implemented in commercial distributions only.
*/
@NotNull
@Support({ POSTGRES })
DataType<T> generatedAlwaysAs(T generatedAlwaysAsValue);
/**
* Set the computed column expression of this data type.
* <p>
* This implicitly sets {@link #readonly()} to <code>true</code>.
* <p>
* This feature is implemented in commercial distributions only.
*/
@NotNull
@Support({ POSTGRES })
DataType<T> generatedAlwaysAs(Field<T> generatedAlwaysAsValue);
/**
* Get the computed column expression of this data type, if any.
* <p>
* This feature is implemented in commercial distributions only.
*/
@Nullable
@Support({ POSTGRES })
Field<T> generatedAlwaysAs();
/**
* Synonym for {@link #nullable(boolean)}, passing <code>true</code> as an
* argument.

View File

@ -145,6 +145,22 @@ implements
@Override
public abstract DataType<T> readonly(boolean r);
@Override
public final boolean computed() {
return generatedAlwaysAs() != null;
}
@Override
public final DataType<T> generatedAlwaysAs(T g) {
return generatedAlwaysAs(Tools.field(g, this));
}
@Override
public abstract DataType<T> generatedAlwaysAs(Field<T> generatedAlwaysAsValue);
@Override
public abstract Field<T> generatedAlwaysAs();
@Override
public abstract DataType<T> collation(Collation c);

View File

@ -47,8 +47,6 @@ import org.jooq.Field;
import org.jooq.Name;
import org.jooq.Nullability;
import org.jetbrains.annotations.NotNull;
/**
* @author Lukas Eder
*/
@ -67,6 +65,7 @@ abstract class AbstractDataTypeX<T> extends AbstractDataType<T> {
Integer newLength,
Nullability newNullability,
boolean newReadonly,
Field<T> newGeneratedAlwaysAs,
Collation newCollation,
CharacterSet newCharacterSet,
boolean newIdentity,
@ -75,46 +74,161 @@ abstract class AbstractDataTypeX<T> extends AbstractDataType<T> {
@Override
public final DataType<T> nullability(Nullability n) {
return construct(precision0(), scale0(), length0(), n, readonly(), collation(), characterSet(), !n.nullable() && identity(), defaultValue());
return construct(
precision0(),
scale0(),
length0(),
n,
readonly(),
generatedAlwaysAs(),
collation(),
characterSet(),
!n.nullable() && identity(),
defaultValue()
);
}
@Override
public final DataType<T> readonly(boolean r) {
return construct(precision0(), scale0(), length0(), nullability(), r, collation(), characterSet(), identity(), defaultValue());
return construct(
precision0(),
scale0(),
length0(),
nullability(),
r,
generatedAlwaysAs(),
collation(),
characterSet(),
identity(),
defaultValue()
);
}
@Override
public final DataType<T> generatedAlwaysAs(Field<T> g) {
return construct(
precision0(),
scale0(),
length0(),
nullability(),
readonly(),
g,
collation(),
characterSet(),
identity(),
defaultValue()
);
}
@Override
public final DataType<T> collation(Collation c) {
return construct(precision0(), scale0(), length0(), nullability(), readonly(), c, characterSet(), identity(), defaultValue());
return construct(
precision0(),
scale0(),
length0(),
nullability(),
readonly(),
generatedAlwaysAs(),
c,
characterSet(),
identity(),
defaultValue()
);
}
@Override
public final DataType<T> characterSet(CharacterSet c) {
return construct(precision0(), scale0(), length0(), nullability(), readonly(), collation(), c, identity(), defaultValue());
return construct(
precision0(),
scale0(),
length0(),
nullability(),
readonly(),
generatedAlwaysAs(),
collation(),
c,
identity(),
defaultValue()
);
}
@Override
public final DataType<T> identity(boolean i) {
return construct(precision0(), scale0(), length0(), i ? NOT_NULL : nullability(), readonly(), collation(), characterSet(), i, defaultValue());
return construct(
precision0(),
scale0(),
length0(),
i ? NOT_NULL : nullability(),
readonly(),
generatedAlwaysAs(),
collation(),
characterSet(),
i,
defaultValue()
);
}
@Override
public final DataType<T> default_(Field<T> d) {
return construct(precision0(), scale0(), length0(), nullability(), readonly(), collation(), characterSet(), identity(), d);
return construct(
precision0(),
scale0(),
length0(),
nullability(),
readonly(),
generatedAlwaysAs(),
collation(),
characterSet(),
identity(),
d
);
}
@Override
final AbstractDataTypeX<T> precision1(Integer p, Integer s) {
return construct(p, s, length0(), nullability(), readonly(), collation(), characterSet(), identity(), defaultValue());
return construct(
p,
s,
length0(),
nullability(),
readonly(),
generatedAlwaysAs(),
collation(),
characterSet(),
identity(),
defaultValue()
);
}
@Override
final AbstractDataTypeX<T> scale1(Integer s) {
return construct(precision0(), s, length0(), nullability(), readonly(), collation(), characterSet(), identity(), defaultValue());
return construct(
precision0(),
s,
length0(),
nullability(),
readonly(),
generatedAlwaysAs(),
collation(),
characterSet(),
identity(),
defaultValue()
);
}
@Override
final AbstractDataTypeX<T> length1(Integer l) {
return construct(precision0(), scale0(), l, nullability(), readonly(), collation(), characterSet(), identity(), defaultValue());
return construct(
precision0(),
scale0(),
l,
nullability(),
readonly(),
generatedAlwaysAs(),
collation(),
characterSet(),
identity(),
defaultValue()
);
}
}

View File

@ -70,12 +70,13 @@ final class ArrayDataType<T> extends DefaultDataType<T[]> {
Integer length,
Nullability nullability,
boolean readonly,
Field<T[]> generatedAlwaysAs,
Collation collation,
CharacterSet characterSet,
boolean identity,
Field<T[]> defaultValue
) {
super(t, precision, scale, length, nullability, readonly, collation, characterSet, identity, defaultValue);
super(t, precision, scale, length, nullability, readonly, generatedAlwaysAs, collation, characterSet, identity, defaultValue);
this.elementType = elementType;
}
@ -88,6 +89,7 @@ final class ArrayDataType<T> extends DefaultDataType<T[]> {
Integer newLength,
Nullability newNullability,
boolean newReadonly,
Field<T[]> newGeneratedAlwaysAs,
Collation newCollation,
CharacterSet newCharacterSet,
boolean newIdentity,
@ -101,6 +103,7 @@ final class ArrayDataType<T> extends DefaultDataType<T[]> {
newLength,
newNullability,
newReadonly,
newGeneratedAlwaysAs,
newCollation,
newCharacterSet,
newIdentity,

View File

@ -82,6 +82,7 @@ final class ConvertedDataType<T, U> extends AbstractDataTypeX<U> {
Integer newLength,
Nullability newNullability,
boolean newReadonly,
Field<U> newGeneratedAlwaysAs,
Collation newCollation,
CharacterSet newCharacterSet,
boolean newIdentity,
@ -93,6 +94,7 @@ final class ConvertedDataType<T, U> extends AbstractDataTypeX<U> {
newLength,
newNullability,
newReadonly,
(Field) newGeneratedAlwaysAs,
newCollation,
newCharacterSet,
newIdentity,
@ -155,6 +157,11 @@ final class ConvertedDataType<T, U> extends AbstractDataTypeX<U> {
return delegate.readonly();
}
@Override
public final Field<U> generatedAlwaysAs() {
return (Field<U>) delegate.generatedAlwaysAs();
}
@Override
public final Collation collation() {
return delegate.collation();

View File

@ -63,13 +63,14 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
private final Integer overrideLength;
private final Nullability overrideNullability;
private final Boolean overrideReadonly;
private final Field<T> overrideGeneratedAlwaysAs;
private final Collation overrideCollation;
private final CharacterSet overrideCharacterSet;
private final Boolean overrideIdentity;
private final Field<T> overrideDefaultValue;
DataTypeProxy(AbstractDataType<T> type) {
this(type, null, null, null, null, null, null, null, null, null);
this(type, null, null, null, null, null, null, null, null, null, null);
}
private DataTypeProxy(
@ -79,6 +80,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
Integer overrideLength,
Nullability overrideNullability,
Boolean overrideReadonly,
Field<T> overrideGeneratedAlwaysAs,
Collation overrideCollation,
CharacterSet overrideCharacterSet,
Boolean overrideIdentity,
@ -92,6 +94,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
this.overrideLength = overrideLength;
this.overrideNullability = overrideNullability;
this.overrideReadonly = overrideReadonly;
this.overrideGeneratedAlwaysAs = overrideGeneratedAlwaysAs;
this.overrideCollation = overrideCollation;
this.overrideCharacterSet = overrideCharacterSet;
this.overrideIdentity = overrideIdentity;
@ -150,6 +153,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideLength,
n,
overrideReadonly,
overrideGeneratedAlwaysAs,
overrideCollation,
overrideCharacterSet,
overrideIdentity,
@ -171,6 +175,29 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideLength,
overrideNullability,
r,
overrideGeneratedAlwaysAs,
overrideCollation,
overrideCharacterSet,
overrideIdentity,
overrideDefaultValue
);
}
@Override
public final Field<T> generatedAlwaysAs() {
return defaultIfNull(overrideGeneratedAlwaysAs, type.generatedAlwaysAs());
}
@Override
public final DataType<T> generatedAlwaysAs(Field<T> g) {
return new DataTypeProxy<>(
this,
overridePrecision,
overrideScale,
overrideLength,
overrideNullability,
overrideReadonly,
g,
overrideCollation,
overrideCharacterSet,
overrideIdentity,
@ -192,6 +219,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideLength,
overrideNullability,
overrideReadonly,
overrideGeneratedAlwaysAs,
c,
overrideCharacterSet,
overrideIdentity,
@ -213,6 +241,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideLength,
overrideNullability,
overrideReadonly,
overrideGeneratedAlwaysAs,
overrideCollation,
c,
overrideIdentity,
@ -234,6 +263,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideLength,
overrideNullability,
overrideReadonly,
overrideGeneratedAlwaysAs,
overrideCollation,
overrideCharacterSet,
i,
@ -255,6 +285,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideLength,
overrideNullability,
overrideReadonly,
overrideGeneratedAlwaysAs,
overrideCollation,
overrideCharacterSet,
overrideIdentity,
@ -306,6 +337,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideLength,
overrideNullability,
overrideReadonly,
overrideGeneratedAlwaysAs,
overrideCollation,
overrideCharacterSet,
overrideIdentity,
@ -327,6 +359,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
overrideLength,
overrideNullability,
overrideReadonly,
overrideGeneratedAlwaysAs,
overrideCollation,
overrideCharacterSet,
overrideIdentity,
@ -348,6 +381,7 @@ final class DataTypeProxy<T> extends AbstractDataType<T> {
l,
overrideNullability,
overrideReadonly,
overrideGeneratedAlwaysAs,
overrideCollation,
overrideCharacterSet,
overrideIdentity,

View File

@ -51,9 +51,37 @@ import static org.jooq.impl.CommentImpl.NO_COMMENT;
import static org.jooq.impl.DSL.unquotedName;
import static org.jooq.impl.DefaultBinding.binding;
import static org.jooq.impl.SQLDataType.BIGINT;
import static org.jooq.impl.SQLDataType.*;
import static org.jooq.impl.SQLDataType.BINARY;
import static org.jooq.impl.SQLDataType.BIT;
import static org.jooq.impl.SQLDataType.BLOB;
import static org.jooq.impl.SQLDataType.BOOLEAN;
import static org.jooq.impl.SQLDataType.CHAR;
import static org.jooq.impl.SQLDataType.CLOB;
import static org.jooq.impl.SQLDataType.DATE;
import static org.jooq.impl.SQLDataType.DECIMAL;
import static org.jooq.impl.SQLDataType.DOUBLE;
import static org.jooq.impl.SQLDataType.FLOAT;
import static org.jooq.impl.SQLDataType.INTEGER;
import static org.jooq.impl.SQLDataType.LONGNVARCHAR;
import static org.jooq.impl.SQLDataType.LONGVARBINARY;
import static org.jooq.impl.SQLDataType.LONGVARCHAR;
import static org.jooq.impl.SQLDataType.NCHAR;
import static org.jooq.impl.SQLDataType.NCLOB;
import static org.jooq.impl.SQLDataType.NUMERIC;
import static org.jooq.impl.SQLDataType.NVARCHAR;
import static org.jooq.impl.SQLDataType.OTHER;
import static org.jooq.impl.SQLDataType.REAL;
import static org.jooq.impl.SQLDataType.RECORD;
import static org.jooq.impl.SQLDataType.RESULT;
import static org.jooq.impl.SQLDataType.SMALLINT;
import static org.jooq.impl.SQLDataType.TIME;
import static org.jooq.impl.SQLDataType.TIMESTAMP;
import static org.jooq.impl.SQLDataType.TIMESTAMPWITHTIMEZONE;
import static org.jooq.impl.SQLDataType.TIMEWITHTIMEZONE;
import static org.jooq.impl.SQLDataType.TINYINT;
import static org.jooq.impl.SQLDataType.VARBINARY;
import static org.jooq.impl.SQLDataType.VARCHAR;
import static org.jooq.impl.SQLDataType.XML;
import static org.jooq.tools.reflect.Reflect.wrapper;
import java.math.BigDecimal;
@ -221,6 +249,7 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
private final Nullability nullability;
private final boolean readonly;
private final Field<T> generatedAlwaysAs;
private final Collation collation;
private final CharacterSet characterSet;
private final boolean identity;
@ -290,10 +319,10 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
}
DefaultDataType(SQLDialect dialect, DataType<T> sqlDataType, Class<T> type, Binding<?, T> binding, Name qualifiedTypeName, String typeName, String castTypeName, Integer precision, Integer scale, Integer length, Nullability nullability, Field<T> defaultValue) {
this(dialect, sqlDataType, type, binding, qualifiedTypeName, typeName, castTypeName, precision, scale, length, nullability, false, null, null, false, defaultValue);
this(dialect, sqlDataType, type, binding, qualifiedTypeName, typeName, castTypeName, precision, scale, length, nullability, false, null, null, null, false, defaultValue);
}
DefaultDataType(SQLDialect dialect, DataType<T> sqlDataType, Class<T> type, Binding<?, T> binding, Name qualifiedTypeName, String typeName, String castTypeName, Integer precision, Integer scale, Integer length, Nullability nullability, boolean readonly, Collation collation, CharacterSet characterSet, boolean identity, Field<T> defaultValue) {
DefaultDataType(SQLDialect dialect, DataType<T> sqlDataType, Class<T> type, Binding<?, T> binding, Name qualifiedTypeName, String typeName, String castTypeName, Integer precision, Integer scale, Integer length, Nullability nullability, boolean readonly, Field<T> generatedAlwaysAs, Collation collation, CharacterSet characterSet, boolean identity, Field<T> defaultValue) {
super(qualifiedTypeName, NO_COMMENT);
// Initialise final instance members
@ -313,6 +342,7 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
this.nullability = nullability;
this.readonly = readonly;
this.generatedAlwaysAs = generatedAlwaysAs;
this.collation = collation;
this.characterSet = characterSet;
this.identity = identity;
@ -353,12 +383,25 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
Integer newLength,
Nullability newNullability,
boolean newReadonly,
Field<T> newGeneratedAlwaysAs,
Collation newCollation,
CharacterSet newCharacterSet,
boolean newIdentity,
Field<T> newDefaultValue
) {
return new DefaultDataType<>(this, newPrecision, newScale, newLength, newNullability, newReadonly, newCollation, newCharacterSet, newIdentity, newDefaultValue);
return new DefaultDataType<>(
this,
newPrecision,
newScale,
newLength,
newNullability,
newReadonly,
newGeneratedAlwaysAs,
newCollation,
newCharacterSet,
newIdentity,
newDefaultValue
);
}
/**
@ -371,6 +414,7 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
Integer length,
Nullability nullability,
boolean readonly,
Field<T> generatedAlwaysAs,
Collation collation,
CharacterSet characterSet,
boolean identity,
@ -389,6 +433,7 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
this.nullability = nullability;
this.readonly = readonly;
this.generatedAlwaysAs = generatedAlwaysAs;
this.collation = collation;
this.characterSet = characterSet;
this.identity = identity;
@ -428,6 +473,11 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
return readonly;
}
@Override
public final Field<T> generatedAlwaysAs() {
return generatedAlwaysAs;
}
@Override
public final Collation collation() {
return collation;
@ -484,7 +534,7 @@ public class DefaultDataType<T> extends AbstractDataTypeX<T> {
// ... and then, set them back to the original value
// [#2710] TODO: Remove this logic along with cached data types
return dataType.construct(precision, scale, length, nullability, readonly, collation, characterSet, identity, defaultValue);
return dataType.construct(precision, scale, length, nullability, readonly, generatedAlwaysAs, collation, characterSet, identity, defaultValue);
}
// If this is already the dialect's specific data type, return this

View File

@ -65,6 +65,7 @@ final class DomainDataType<T> extends DefaultDataType<T> {
baseType.lengthDefined() ? baseType.length() : null,
baseType.nullability(),
baseType.readonly(),
baseType.generatedAlwaysAs(),
null, // TODO: Collation
null, // TODO: CharacterSet (?)
false,

View File

@ -60,6 +60,7 @@ final class Keywords {
static final Keyword K_ALTER_INDEX = keyword("alter index");
static final Keyword K_ALTER_SCHEMA = keyword("alter schema");
static final Keyword K_ALTER_TABLE = keyword("alter table");
static final Keyword K_ALWAYS = keyword("always");
static final Keyword K_AND = keyword("and");
static final Keyword K_ARRAY = keyword("array");
static final Keyword K_AS = keyword("as");
@ -184,7 +185,7 @@ final class Keywords {
static final Keyword K_FOR_PORTION_OF = keyword("for portion of");
static final Keyword K_FROM = keyword("from");
static final Keyword K_FUNCTION = keyword("function");
static final Keyword K_GENERATED_BY_DEFAULT_AS_IDENTITY = keyword("generated by default as identity");
static final Keyword K_GENERATED = keyword("generated");
static final Keyword K_GLOBAL_TEMPORARY = keyword("global temporary");
static final Keyword K_GOTO = keyword("goto");
static final Keyword K_GRANT = keyword("grant");

View File

@ -85,12 +85,13 @@ final class MultisetDataType<R extends Record> extends DefaultDataType<Result<R>
Integer length,
Nullability nullability,
boolean readonly,
Field<Result<R>> generatedAlwaysAs,
Collation collation,
CharacterSet characterSet,
boolean identity,
Field<Result<R>> defaultValue
) {
super(t, precision, scale, length, nullability, readonly, collation, characterSet, identity, defaultValue);
super(t, precision, scale, length, nullability, readonly, generatedAlwaysAs, collation, characterSet, identity, defaultValue);
this.row = row;
this.recordType = recordType;
@ -104,6 +105,7 @@ final class MultisetDataType<R extends Record> extends DefaultDataType<Result<R>
Integer newLength,
Nullability newNullability,
boolean newReadonly,
Field<Result<R>> newGeneratedAlwaysAs,
Collation newCollation,
CharacterSet newCharacterSet,
boolean newIdentity,
@ -118,6 +120,7 @@ final class MultisetDataType<R extends Record> extends DefaultDataType<Result<R>
newLength,
newNullability,
newReadonly,
newGeneratedAlwaysAs,
newCollation,
newCharacterSet,
newIdentity,

View File

@ -4225,6 +4225,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
List<Index> indexes = new ArrayList<>();
boolean primary = false;
boolean identity = false;
boolean computed = false;
boolean readonly = false;
// Three valued boolean:
@ -4308,11 +4309,13 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
constraints,
primary,
identity,
computed,
readonly
);
primary = inlineConstraints.primary;
identity = inlineConstraints.identity;
computed = inlineConstraints.computed;
if (ctas)
fields.add(field(fieldName));
@ -4520,7 +4523,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
return storageStep;
}
private static final /* record */ class ParseInlineConstraints { private final DataType<?> type; private final Comment fieldComment; private final boolean primary; private final boolean identity; private final boolean readonly; public ParseInlineConstraints(DataType<?> type, Comment fieldComment, boolean primary, boolean identity, boolean readonly) { this.type = type; this.fieldComment = fieldComment; this.primary = primary; this.identity = identity; this.readonly = readonly; } public DataType<?> type() { return type; } public Comment fieldComment() { return fieldComment; } public boolean primary() { return primary; } public boolean identity() { return identity; } public boolean readonly() { return readonly; } @Override public boolean equals(Object o) { if (!(o instanceof ParseInlineConstraints)) return false; ParseInlineConstraints other = (ParseInlineConstraints) o; if (!java.util.Objects.equals(this.type, other.type)) return false; if (!java.util.Objects.equals(this.fieldComment, other.fieldComment)) return false; if (!java.util.Objects.equals(this.primary, other.primary)) return false; if (!java.util.Objects.equals(this.identity, other.identity)) return false; if (!java.util.Objects.equals(this.readonly, other.readonly)) return false; return true; } @Override public int hashCode() { return java.util.Objects.hash(this.type, this.fieldComment, this.primary, this.identity, this.readonly); } @Override public String toString() { return new StringBuilder("ParseInlineConstraints[").append("type=").append(this.type).append(", fieldComment=").append(this.fieldComment).append(", primary=").append(this.primary).append(", identity=").append(this.identity).append(", readonly=").append(this.readonly).append("]").toString(); } }
private static final /* record */ class ParseInlineConstraints { private final DataType<?> type; private final Comment fieldComment; private final boolean primary; private final boolean identity; private final boolean computed; private final boolean readonly; public ParseInlineConstraints(DataType<?> type, Comment fieldComment, boolean primary, boolean identity, boolean computed, boolean readonly) { this.type = type; this.fieldComment = fieldComment; this.primary = primary; this.identity = identity; this.computed = computed; this.readonly = readonly; } public DataType<?> type() { return type; } public Comment fieldComment() { return fieldComment; } public boolean primary() { return primary; } public boolean identity() { return identity; } public boolean computed() { return computed; } public boolean readonly() { return readonly; } @Override public boolean equals(Object o) { if (!(o instanceof ParseInlineConstraints)) return false; ParseInlineConstraints other = (ParseInlineConstraints) o; if (!java.util.Objects.equals(this.type, other.type)) return false; if (!java.util.Objects.equals(this.fieldComment, other.fieldComment)) return false; if (!java.util.Objects.equals(this.primary, other.primary)) return false; if (!java.util.Objects.equals(this.identity, other.identity)) return false; if (!java.util.Objects.equals(this.computed, other.computed)) return false; if (!java.util.Objects.equals(this.readonly, other.readonly)) return false; return true; } @Override public int hashCode() { return java.util.Objects.hash(this.type, this.fieldComment, this.primary, this.identity, this.computed, this.readonly); } @Override public String toString() { return new StringBuilder("ParseInlineConstraints[").append("type=").append(this.type).append(", fieldComment=").append(this.fieldComment).append(", primary=").append(this.primary).append(", identity=").append(this.identity).append(", computed=").append(this.computed).append(", readonly=").append(this.readonly).append("]").toString(); } }
private final ParseInlineConstraints parseInlineConstraints(
Name fieldName,
@ -4528,6 +4531,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
List<? super Constraint> constraints,
boolean primary,
boolean identity,
boolean computed,
boolean readonly
) {
boolean nullable = false;
@ -4538,6 +4542,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
Comment fieldComment = null;
identity |= type.identity();
computed |= type.computed();
readonly |= type.readonly();
for (;;) {
@ -4595,65 +4600,36 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
continue;
}
else if (!identity && parseKeywordIf("GENERATED")) {
if (!parseKeywordIf("ALWAYS")) {
else if (!computed && parseKeywordIf("AS") && requireProEdition()) {
}
else if (!identity && !computed && parseKeywordIf("GENERATED")) {
boolean always;
if (!(always = parseKeywordIf("ALWAYS"))) {
parseKeyword("BY DEFAULT");
// TODO: Ignored keyword from Oracle
parseKeywordIf("ON NULL");
}
parseKeyword("AS IDENTITY");
if (always ? parseKeywordIf("AS IDENTITY") : parseKeyword("AS IDENTITY")) {
parseIdentityOptionIf();
type = type.identity(true);
identity = true;
}
else if (parseKeyword("AS") && requireProEdition()) {
// TODO: Ignored identity options from Oracle
if (parseIf('(')) {
boolean identityOption = false;
for (;;) {
if (identityOption)
parseIf(',');
if (parseKeywordIf("START WITH")) {
if (!parseKeywordIf("LIMIT VALUE"))
parseUnsignedIntegerOrBindVariable();
identityOption = true;
continue;
}
else if (parseKeywordIf("INCREMENT BY")
|| parseKeywordIf("MAXVALUE")
|| parseKeywordIf("MINVALUE")
|| parseKeywordIf("CACHE")) {
parseUnsignedIntegerOrBindVariable();
identityOption = true;
continue;
}
else if (parseKeywordIf("NOMAXVALUE")
|| parseKeywordIf("NOMINVALUE")
|| parseKeywordIf("CYCLE")
|| parseKeywordIf("NOCYCLE")
|| parseKeywordIf("NOCACHE")
|| parseKeywordIf("ORDER")
|| parseKeywordIf("NOORDER")) {
identityOption = true;
continue;
}
else if (parseSignedIntegerLiteralIf() != null) {
identityOption = true;
continue;
}
if (identityOption)
break;
else
throw unsupportedClause();
}
parse(')');
}
type = type.identity(true);
defaultValue = true;
identity = true;
continue;
}
}
@ -4749,7 +4725,56 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
break;
}
return new ParseInlineConstraints(type, fieldComment, primary, identity, readonly);
return new ParseInlineConstraints(type, fieldComment, primary, identity, computed, readonly);
}
private final void parseIdentityOptionIf() {
// TODO: Ignored identity options from Oracle
if (parseIf('(')) {
boolean identityOption = false;
for (;;) {
if (identityOption)
parseIf(',');
if (parseKeywordIf("START WITH")) {
if (!parseKeywordIf("LIMIT VALUE"))
parseUnsignedIntegerOrBindVariable();
identityOption = true;
continue;
}
else if (parseKeywordIf("INCREMENT BY")
|| parseKeywordIf("MAXVALUE")
|| parseKeywordIf("MINVALUE")
|| parseKeywordIf("CACHE")) {
parseUnsignedIntegerOrBindVariable();
identityOption = true;
continue;
}
else if (parseKeywordIf("NOMAXVALUE")
|| parseKeywordIf("NOMINVALUE")
|| parseKeywordIf("CYCLE")
|| parseKeywordIf("NOCYCLE")
|| parseKeywordIf("NOCACHE")
|| parseKeywordIf("ORDER")
|| parseKeywordIf("NOORDER")) {
identityOption = true;
continue;
}
else if (parseSignedIntegerLiteralIf() != null) {
identityOption = true;
continue;
}
if (identityOption)
break;
else
throw unsupportedClause();
}
parse(')');
}
}
private final boolean parseSerialIf() {
@ -5227,7 +5252,7 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
DataType type = parseDataType();
int p = list == null ? -1 : list.size();
ParseInlineConstraints inline = parseInlineConstraints(fieldName, type, list, false, false, false);
ParseInlineConstraints inline = parseInlineConstraints(fieldName, type, list, false, false, false, false);
Field<?> result = field(fieldName, inline.type, inline.fieldComment);
if (list != null)

View File

@ -86,12 +86,13 @@ final class RecordDataType<R extends Record> extends DefaultDataType<R> {
Integer length,
Nullability nullability,
boolean readonly,
Field<R> generatedAlwaysAs,
Collation collation,
CharacterSet characterSet,
boolean identity,
Field<R> defaultValue
) {
super(t, precision, scale, length, nullability, readonly, collation, characterSet, identity, defaultValue);
super(t, precision, scale, length, nullability, readonly, generatedAlwaysAs, collation, characterSet, identity, defaultValue);
this.row = row;
}
@ -104,6 +105,7 @@ final class RecordDataType<R extends Record> extends DefaultDataType<R> {
Integer newLength,
Nullability newNullability,
boolean newReadonly,
Field<R> newGeneratedAlwaysAs,
Collation newCollation,
CharacterSet newCharacterSet,
boolean newIdentity,
@ -117,6 +119,7 @@ final class RecordDataType<R extends Record> extends DefaultDataType<R> {
newLength,
newNullability,
newReadonly,
newGeneratedAlwaysAs,
newCollation,
newCharacterSet,
newIdentity,

View File

@ -53,7 +53,7 @@ import static java.util.stream.Collectors.joining;
import static org.jooq.SQLDialect.DERBY;
// ...
import static org.jooq.SQLDialect.FIREBIRD;
import static org.jooq.SQLDialect.HSQLDB;
import static org.jooq.SQLDialect.*;
// ...
// ...
import static org.jooq.SQLDialect.MARIADB;
@ -120,6 +120,7 @@ import static org.jooq.impl.Identifiers.QUOTES;
import static org.jooq.impl.Identifiers.QUOTE_END_DELIMITER;
import static org.jooq.impl.Identifiers.QUOTE_END_DELIMITER_ESCAPED;
import static org.jooq.impl.Identifiers.QUOTE_START_DELIMITER;
import static org.jooq.impl.Keywords.K_ALWAYS;
import static org.jooq.impl.Keywords.K_AS;
import static org.jooq.impl.Keywords.K_ATOMIC;
import static org.jooq.impl.Keywords.K_AUTOINCREMENT;
@ -127,6 +128,7 @@ import static org.jooq.impl.Keywords.K_AUTO_INCREMENT;
import static org.jooq.impl.Keywords.K_BEGIN;
import static org.jooq.impl.Keywords.K_BEGIN_CATCH;
import static org.jooq.impl.Keywords.K_BEGIN_TRY;
import static org.jooq.impl.Keywords.K_BY;
import static org.jooq.impl.Keywords.K_CHARACTER_SET;
import static org.jooq.impl.Keywords.K_COLLATE;
import static org.jooq.impl.Keywords.K_DECLARE;
@ -145,7 +147,7 @@ import static org.jooq.impl.Keywords.K_EXEC;
import static org.jooq.impl.Keywords.K_EXECUTE_BLOCK;
import static org.jooq.impl.Keywords.K_EXECUTE_IMMEDIATE;
import static org.jooq.impl.Keywords.K_EXECUTE_STATEMENT;
import static org.jooq.impl.Keywords.K_GENERATED_BY_DEFAULT_AS_IDENTITY;
import static org.jooq.impl.Keywords.K_GENERATED;
import static org.jooq.impl.Keywords.K_IDENTITY;
import static org.jooq.impl.Keywords.K_IF;
import static org.jooq.impl.Keywords.K_INT;
@ -158,7 +160,6 @@ import static org.jooq.impl.Keywords.K_PRIMARY_KEY;
import static org.jooq.impl.Keywords.K_RAISE;
import static org.jooq.impl.Keywords.K_RAISERROR;
import static org.jooq.impl.Keywords.K_SERIAL;
import static org.jooq.impl.Keywords.K_SERIAL2;
import static org.jooq.impl.Keywords.K_SERIAL4;
import static org.jooq.impl.Keywords.K_SERIAL8;
import static org.jooq.impl.Keywords.K_SQLSTATE;
@ -5096,6 +5097,9 @@ final class Tools {
* keywords before the [ NOT ] NULL constraint.
*/
static final void toSQLDDLTypeDeclarationIdentityBeforeNull(Context<?> ctx, DataType<?> type) {
if (REQUIRE_IDENTITY_AFTER_NULL.contains(ctx.dialect()))
return;
if (type.identity()) {
switch (ctx.family()) {
@ -5109,7 +5113,7 @@ final class Tools {
case CUBRID: ctx.sql(' ').visit(K_AUTO_INCREMENT); break;
case HSQLDB: ctx.sql(' ').visit(K_GENERATED_BY_DEFAULT_AS_IDENTITY).sql('(').visit(K_START_WITH).sql(" 1)"); break;
case HSQLDB: ctx.sql(' ').visit(K_GENERATED).sql(' ').visit(K_BY).sql(' ').visit(K_DEFAULT).sql(' ').visit(K_AS).sql(' ').visit(K_IDENTITY).sql('(').visit(K_START_WITH).sql(" 1)"); break;
case SQLITE: ctx.sql(' ').visit(K_PRIMARY_KEY).sql(' ').visit(K_AUTOINCREMENT); break;
case POSTGRES:
switch (ctx.dialect()) {
@ -5119,23 +5123,38 @@ final class Tools {
case POSTGRES:
ctx.sql(' ').visit(K_GENERATED_BY_DEFAULT_AS_IDENTITY); break;
ctx.sql(' ').visit(K_GENERATED).sql(' ').visit(K_BY).sql(' ').visit(K_DEFAULT).sql(' ').visit(K_AS).sql(' ').visit(K_IDENTITY); break;
}
break;
case DERBY:
case FIREBIRD:
case YUGABYTE: ctx.sql(' ').visit(K_GENERATED_BY_DEFAULT_AS_IDENTITY); break;
case YUGABYTE: ctx.sql(' ').visit(K_GENERATED).sql(' ').visit(K_BY).sql(' ').visit(K_DEFAULT).sql(' ').visit(K_AS).sql(' ').visit(K_IDENTITY); break;
}
}
}
private static final Set<SQLDialect> REQUIRE_IDENTITY_AFTER_NULL = SQLDialect.supportedBy(H2, MARIADB, MYSQL);
/**
* If a type is an identity type, some dialects require the relevant
* keywords after the [ NOT ] NULL constraint.
*/
static final void toSQLDDLTypeDeclarationIdentityAfterNull(Context<?> ctx, DataType<?> type) {
if (!REQUIRE_IDENTITY_AFTER_NULL.contains(ctx.dialect()))
return;
if (type.identity()) {
// [#5062] H2's (and others') AUTO_INCREMENT flag is syntactically located *after* NULL flags.
@ -5143,7 +5162,7 @@ final class Tools {
case H2: ctx.sql(' ').visit(K_GENERATED_BY_DEFAULT_AS_IDENTITY); break;
case H2: ctx.sql(' ').visit(K_GENERATED).sql(' ').visit(K_BY).sql(' ').visit(K_DEFAULT).sql(' ').visit(K_AS).sql(' ').visit(K_IDENTITY); break;
@ -5154,6 +5173,20 @@ final class Tools {
case MYSQL: ctx.sql(' ').visit(K_AUTO_INCREMENT); break;
}
}
}
private static final void toSQLDDLTypeDeclarationDefault(Context<?> ctx, DataType<?> type) {