package org.jooq.impl;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static org.jooq.Clause.CREATE_TABLE;
import static org.jooq.Clause.CREATE_TABLE_AS;
import static org.jooq.Clause.CREATE_TABLE_COLUMNS;
import static org.jooq.Clause.CREATE_TABLE_CONSTRAINTS;
import static org.jooq.Clause.CREATE_TABLE_NAME;
import static org.jooq.SQLDialect.CUBRID;
import static org.jooq.SQLDialect.DERBY;
import static org.jooq.SQLDialect.FIREBIRD;
import static org.jooq.SQLDialect.H2;
import static org.jooq.SQLDialect.HSQLDB;
import static org.jooq.SQLDialect.MARIADB;
import static org.jooq.SQLDialect.MYSQL;
import static org.jooq.SQLDialect.POSTGRES;
import static org.jooq.SQLDialect.SQLITE;
import static org.jooq.impl.DSL.asterisk;
import static org.jooq.impl.DSL.commentOnTable;
import static org.jooq.impl.DSL.createIndex;
import static org.jooq.impl.DSL.field;
import static org.jooq.impl.DSL.insertInto;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.DSL.select;
import static org.jooq.impl.DSL.sql;
import static org.jooq.impl.DSL.table;
import static org.jooq.impl.Keywords.K_AS;
import static org.jooq.impl.Keywords.K_COMMENT;
import static org.jooq.impl.Keywords.K_CREATE;
import static org.jooq.impl.Keywords.K_GLOBAL_TEMPORARY;
import static org.jooq.impl.Keywords.K_IF_NOT_EXISTS;
import static org.jooq.impl.Keywords.K_INDEX;
import static org.jooq.impl.Keywords.K_ON_COMMIT_DELETE_ROWS;
import static org.jooq.impl.Keywords.K_ON_COMMIT_DROP;
import static org.jooq.impl.Keywords.K_ON_COMMIT_PRESERVE_ROWS;
import static org.jooq.impl.Keywords.K_TABLE;
import static org.jooq.impl.Keywords.K_TEMPORARY;
import static org.jooq.impl.Keywords.K_UNIQUE;
import static org.jooq.impl.Keywords.K_WITH_DATA;
import static org.jooq.impl.Keywords.K_WITH_NO_DATA;
import static org.jooq.impl.Tools.EMPTY_FIELD;
import static org.jooq.impl.Tools.begin;
import static org.jooq.impl.Tools.beginExecuteImmediate;
import static org.jooq.impl.Tools.end;
import static org.jooq.impl.Tools.endExecuteImmediate;
import static org.jooq.impl.Tools.enumLiterals;
import static org.jooq.impl.Tools.storedEnumType;
import static org.jooq.impl.Tools.BooleanDataKey.DATA_SELECT_NO_DATA;
import static org.jooq.impl.Tools.DataKey.DATA_SELECT_INTO_TABLE;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.jooq.Comment;
import org.jooq.Configuration;
import org.jooq.Constraint;
import org.jooq.Context;
import org.jooq.CreateTableColumnStep;
import org.jooq.CreateTableWithDataStep;
import org.jooq.DataType;
import org.jooq.EnumType;
import org.jooq.Field;
import org.jooq.Index;
import org.jooq.Name;
import org.jooq.Nullability;
import org.jooq.QueryPart;
import org.jooq.Record;
import org.jooq.SQL;
import org.jooq.SQLDialect;
import org.jooq.Select;
import org.jooq.Table;
import org.jooq.TableOptions.OnCommit;
final class CreateTableImpl extends AbstractRowCountQuery implements
CreateTableWithDataStep,
CreateTableColumnStep {
private static final long serialVersionUID = 8904572826501186329L;
private static final Set<SQLDialect> NO_SUPPORT_IF_NOT_EXISTS = SQLDialect.supportedBy(DERBY, FIREBIRD);
private static final Set<SQLDialect> NO_SUPPORT_WITH_DATA = SQLDialect.supportedBy(H2, MARIADB, MYSQL, SQLITE);
private static final Set<SQLDialect> NO_SUPPORT_CTAS_COLUMN_NAMES = SQLDialect.supportedBy(H2);
private static final Set<SQLDialect> EMULATE_INDEXES_IN_BLOCK = SQLDialect.supportedBy(FIREBIRD, POSTGRES);
private static final Set<SQLDialect> EMULATE_SOME_ENUM_TYPES_AS_CHECK = SQLDialect.supportedBy(CUBRID, DERBY, FIREBIRD, HSQLDB, POSTGRES, SQLITE);
private static final Set<SQLDialect> EMULATE_STORED_ENUM_TYPES_AS_CHECK = SQLDialect.supportedBy(CUBRID, DERBY, FIREBIRD, HSQLDB, SQLITE);
private static final Set<SQLDialect> REQUIRES_WITH_DATA = SQLDialect.supportedBy(HSQLDB);
private static final Set<SQLDialect> WRAP_SELECT_IN_PARENS = SQLDialect.supportedBy(HSQLDB);
private static final Set<SQLDialect> SUPPORT_TEMPORARY = SQLDialect.supportedBy(MARIADB, MYSQL, POSTGRES);
private static final Set<SQLDialect> = SQLDialect.supportedBy(FIREBIRD, POSTGRES);
private static final Set<SQLDialect> REQUIRE_EXECUTE_IMMEDIATE = SQLDialect.supportedBy(FIREBIRD);
private static final Set<SQLDialect> NO_SUPPORT_NULLABLE_PRIMARY_KEY = SQLDialect.supportedBy(MARIADB, MYSQL);
private final Table<?> table;
private Select<?> select;
private Boolean withData;
private final List<Field<?>> columnFields;
private final List<DataType<?>> columnTypes;
private final List<Constraint> constraints;
private final List<Index> indexes;
private final boolean temporary;
private final boolean ifNotExists;
private OnCommit onCommit;
private Comment ;
private SQL storage;
CreateTableImpl(Configuration configuration, Table<?> table, boolean temporary, boolean ifNotExists) {
super(configuration);
this.table = table;
this.temporary = temporary;
this.ifNotExists = ifNotExists;
this.columnFields = new ArrayList<>();
this.columnTypes = new ArrayList<>();
this.constraints = new ArrayList<>();
this.indexes = new ArrayList<>();
}
final Table<?> $table() { return table; }
final boolean $temporary() { return temporary; }
final OnCommit $onCommit() { return onCommit; }
final Select<?> $select() { return select; }
final List<Field<?>> $columnFields() { return columnFields; }
final List<DataType<?>> $columnTypes() { return columnTypes; }
final List<Constraint> $constraints() { return constraints; }
final List<Index> $indexes() { return indexes; }
final boolean $ifNotExists() { return ifNotExists; }
final Comment () { return comment; }
@Override
public final CreateTableImpl as(Select<? extends Record> s) {
this.select = s;
return this;
}
@Override
public final CreateTableImpl withData() {
withData = true;
return this;
}
@Override
public final CreateTableImpl withNoData() {
withData = false;
return this;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public final CreateTableImpl column(Field<?> field) {
return column((Field) field, field.getDataType());
}
@Override
public final CreateTableImpl columns(Field<?>... fields) {
return columns(Arrays.asList(fields));
}
@Override
public final CreateTableImpl columns(Name... fields) {
return columns(Tools.fieldsByName(fields));
}
@Override
public final CreateTableImpl columns(String... fields) {
return columns(Tools.fieldsByName(fields));
}
@Override
public final CreateTableImpl columns(Collection<? extends Field<?>> fields) {
for (Field<?> field : fields)
column(field);
return this;
}
@Override
public final <T> CreateTableImpl column(Field<T> field, DataType<T> type) {
columnFields.add(field);
columnTypes.add(type);
return this;
}
@Override
public final CreateTableImpl column(Name field, DataType<?> type) {
columnFields.add(field(field, type));
columnTypes.add(type);
return this;
}
@Override
public final CreateTableImpl column(String field, DataType<?> type) {
return column(name(field), type);
}
@Override
public final CreateTableImpl constraint(Constraint c) {
return constraints(Arrays.asList(c));
}
@Override
public final CreateTableImpl constraints(Constraint... c) {
return constraints(Arrays.asList(c));
}
@Override
public final CreateTableImpl constraints(Collection<? extends Constraint> c) {
constraints.addAll(c);
return this;
}
@Override
public final CreateTableImpl index(Index i) {
return indexes(Arrays.asList(i));
}
@Override
public final CreateTableImpl indexes(Index... i) {
return indexes(Arrays.asList(i));
}
@Override
public final CreateTableImpl indexes(Collection<? extends Index> i) {
indexes.addAll(i);
return this;
}
@Override
public final CreateTableImpl onCommitDeleteRows() {
onCommit = OnCommit.DELETE_ROWS;
return this;
}
@Override
public final CreateTableImpl onCommitPreserveRows() {
onCommit = OnCommit.PRESERVE_ROWS;
return this;
}
@Override
public final CreateTableImpl onCommitDrop() {
onCommit = OnCommit.DROP;
return this;
}
@Override
public final CreateTableImpl (String c) {
return comment(DSL.comment(c));
}
@Override
public final CreateTableImpl (Comment c) {
comment = c;
return this;
}
@Override
public final CreateTableImpl storage(SQL sql) {
storage = sql;
return this;
}
@Override
public final CreateTableImpl storage(String sql) {
return storage(sql(sql));
}
@Override
public final CreateTableImpl storage(String sql, Object... bindings) {
return storage(sql(sql, bindings));
}
@Override
public final CreateTableImpl storage(String sql, QueryPart... parts) {
return storage(sql(sql, parts));
}
private final boolean supportsIfNotExists(Context<?> ctx) {
return !NO_SUPPORT_IF_NOT_EXISTS.contains(ctx.dialect());
}
@Override
public final void accept(Context<?> ctx) {
if (ifNotExists && !supportsIfNotExists(ctx)) {
Tools.beginTryCatch(ctx, DDLStatementType.CREATE_TABLE);
accept0(ctx);
Tools.endTryCatch(ctx, DDLStatementType.CREATE_TABLE);
}
else {
accept0(ctx);
}
}
private final void accept0(Context<?> ctx) {
boolean c = comment != null && EMULATE_COMMENT_IN_BLOCK.contains(ctx.dialect());
boolean i = !indexes.isEmpty() && EMULATE_INDEXES_IN_BLOCK.contains(ctx.dialect());
if (c || i) {
begin(ctx);
if (REQUIRE_EXECUTE_IMMEDIATE.contains(ctx.dialect()))
beginExecuteImmediate(ctx);
accept1(ctx);
if (REQUIRE_EXECUTE_IMMEDIATE.contains(ctx.dialect()))
endExecuteImmediate(ctx);
else
ctx.sql(';');
if (c) {
ctx.formatSeparator();
if (REQUIRE_EXECUTE_IMMEDIATE.contains(ctx.dialect()))
beginExecuteImmediate(ctx);
ctx.visit(commentOnTable(table).is(comment));
if (REQUIRE_EXECUTE_IMMEDIATE.contains(ctx.dialect()))
endExecuteImmediate(ctx);
else
ctx.sql(';');
}
if (i) {
for (Index index : indexes) {
ctx.formatSeparator();
if (REQUIRE_EXECUTE_IMMEDIATE.contains(ctx.dialect()))
beginExecuteImmediate(ctx);
if ("".equals(index.getName()))
ctx.visit(createIndex().on(index.getTable(), index.getFields()));
else
ctx.visit(createIndex(index.getUnqualifiedName()).on(index.getTable(), index.getFields()));
if (REQUIRE_EXECUTE_IMMEDIATE.contains(ctx.dialect()))
endExecuteImmediate(ctx);
else
ctx.sql(';');
}
}
end(ctx);
return;
}
accept1(ctx);
}
private final void accept1(Context<?> ctx) {
ctx.start(CREATE_TABLE);
if (select != null) {
acceptCreateTableAsSelect(ctx);
}
else {
toSQLCreateTable(ctx);
toSQLOnCommit(ctx);
}
if (comment != null && !EMULATE_COMMENT_IN_BLOCK.contains(ctx.dialect()))
ctx.formatSeparator()
.visit(K_COMMENT).sql(' ').visit(comment);
if (storage != null && ctx.configuration().data("org.jooq.ddl.ignore-storage-clauses") == null)
ctx.formatSeparator()
.visit(storage);
ctx.end(CREATE_TABLE);
}
private void toSQLCreateTable(Context<?> ctx) {
toSQLCreateTableName(ctx);
if (!columnFields.isEmpty()
&& (select == null || !NO_SUPPORT_CTAS_COLUMN_NAMES.contains(ctx.dialect()))) {
ctx.sql(" (")
.start(CREATE_TABLE_COLUMNS)
.formatIndentStart()
.formatNewLine();
Field<?> identity = null;
boolean qualify = ctx.qualify();
ctx.qualify(false);
for (int i = 0; i < columnFields.size(); i++) {
DataType<?> type = columnType(ctx, i);
if (identity == null && type.identity())
identity = columnFields.get(i);
ctx.visit(columnFields.get(i));
if (select == null) {
ctx.sql(' ');
Tools.toSQLDDLTypeDeclarationForAddition(ctx, type);
}
if (i < columnFields.size() - 1)
ctx.sql(',').formatSeparator();
}
ctx.qualify(qualify);
ctx.end(CREATE_TABLE_COLUMNS)
.start(CREATE_TABLE_CONSTRAINTS);
if (!constraints.isEmpty())
for (Constraint constraint : constraints)
if (ctx.family() != SQLITE || !matchingPrimaryKey(constraint, identity))
ctx.sql(',')
.formatSeparator()
.visit(constraint);
if (EMULATE_SOME_ENUM_TYPES_AS_CHECK.contains(ctx.dialect())) {
for (int i = 0; i < columnFields.size(); i++) {
DataType<?> type = columnTypes.get(i);
if (EnumType.class.isAssignableFrom(type.getType())) {
@SuppressWarnings("unchecked")
DataType<EnumType> enumType = (DataType<EnumType>) type;
if (EMULATE_STORED_ENUM_TYPES_AS_CHECK.contains(ctx.dialect()) || !storedEnumType(enumType)) {
Field<?> field = columnFields.get(i);
ctx.sql(',')
.formatSeparator()
.visit(DSL.constraint(table.getName() + "_" + field.getName() + "_chk")
.check(((Field) field).in(Tools.inline(enumLiterals(enumType.getType())))));
}
}
}
}
ctx.end(CREATE_TABLE_CONSTRAINTS);
if (!indexes.isEmpty() && !EMULATE_INDEXES_IN_BLOCK.contains(ctx.dialect())) {
ctx.qualify(false);
for (Index index : indexes) {
ctx.sql(',').formatSeparator();
if (index.getUnique())
ctx.visit(K_UNIQUE).sql(' ');
ctx.visit(K_INDEX);
if (!"".equals(index.getName()))
ctx.sql(' ').visit(index.getUnqualifiedName());
ctx.sql(" (")
.visit(new SortFieldList(index.getFields()))
.sql(')');
}
ctx.qualify(qualify);
}
ctx.formatIndentEnd()
.formatNewLine()
.sql(')');
}
}
private final DataType<?> columnType(Context<?> ctx, int i) {
DataType<?> type = columnTypes.get(i);
if (NO_SUPPORT_NULLABLE_PRIMARY_KEY.contains(ctx.dialect()) && type.nullability() == Nullability.DEFAULT && isPrimaryKey(i))
type = type.nullable(false);
return type;
}
private final boolean isPrimaryKey(int i) {
for (Constraint constraint : constraints)
if (constraint instanceof ConstraintImpl)
if (((ConstraintImpl) constraint).$primaryKey() != null)
for (Field<?> field : ((ConstraintImpl) constraint).$primaryKey())
if (field.equals(columnFields.get(i)))
return true;
return false;
}
private final boolean matchingPrimaryKey(Constraint constraint, Field<?> identity) {
if (constraint instanceof ConstraintImpl)
return ((ConstraintImpl) constraint).matchingPrimaryKey(identity);
return false;
}
private final void acceptCreateTableAsSelect(Context<?> ctx) {
toSQLCreateTable(ctx);
toSQLOnCommit(ctx);
ctx.formatSeparator()
.visit(K_AS);
if (WRAP_SELECT_IN_PARENS.contains(ctx.dialect()))
ctx.sql(" (")
.formatIndentStart()
.formatNewLine();
else
ctx.formatSeparator();
if (FALSE.equals(withData) && NO_SUPPORT_WITH_DATA.contains(ctx.dialect()))
ctx.data(DATA_SELECT_NO_DATA, true);
ctx.start(CREATE_TABLE_AS);
if (!columnFields.isEmpty() && NO_SUPPORT_CTAS_COLUMN_NAMES.contains(ctx.dialect()))
ctx.visit(select(asterisk()).from(select.asTable(table(name("t")), columnFields.toArray(EMPTY_FIELD))));
else
ctx.visit(select);
ctx.end(CREATE_TABLE_AS);
if (FALSE.equals(withData) && NO_SUPPORT_WITH_DATA.contains(ctx.dialect()))
ctx.data().remove(DATA_SELECT_NO_DATA);
if (WRAP_SELECT_IN_PARENS.contains(ctx.dialect())) {
ctx.formatIndentEnd()
.formatNewLine()
.sql(')');
}
if (FALSE.equals(withData) && !NO_SUPPORT_WITH_DATA.contains(ctx.dialect()))
ctx.formatSeparator()
.visit(K_WITH_NO_DATA);
else if (TRUE.equals(withData) && !NO_SUPPORT_WITH_DATA.contains(ctx.dialect()))
ctx.formatSeparator()
.visit(K_WITH_DATA);
else if (REQUIRES_WITH_DATA.contains(ctx.dialect()))
ctx.formatSeparator()
.visit(K_WITH_DATA);
}
private final void toSQLCreateTableName(Context<?> ctx) {
ctx.start(CREATE_TABLE_NAME)
.visit(K_CREATE)
.sql(' ');
if (temporary)
if (SUPPORT_TEMPORARY.contains(ctx.dialect()))
ctx.visit(K_TEMPORARY).sql(' ');
else
ctx.visit(K_GLOBAL_TEMPORARY).sql(' ');
ctx.visit(K_TABLE)
.sql(' ');
if (ifNotExists && supportsIfNotExists(ctx))
ctx.visit(K_IF_NOT_EXISTS)
.sql(' ');
ctx.visit(table)
.end(CREATE_TABLE_NAME);
}
private final void toSQLOnCommit(Context<?> ctx) {
if (temporary && onCommit != null) {
switch (onCommit) {
case DELETE_ROWS: ctx.formatSeparator().visit(K_ON_COMMIT_DELETE_ROWS); break;
case PRESERVE_ROWS: ctx.formatSeparator().visit(K_ON_COMMIT_PRESERVE_ROWS); break;
case DROP: ctx.formatSeparator().visit(K_ON_COMMIT_DROP); break;
}
}
}
}