All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jooq.impl.DDL Maven / Gradle / Ivy

There is a newer version: 3.19.15
Show newest version
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Other licenses:
 * -----------------------------------------------------------------------------
 * Commercial licenses for this work are available. These replace the above
 * Apache-2.0 license and offer limited warranties, support, maintenance, and
 * commercial database integrations.
 *
 * For more information, please visit: http://www.jooq.org/licenses
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package org.jooq.impl;

import static java.util.Arrays.asList;
import static org.jooq.DDLFlag.CHECK;
import static org.jooq.DDLFlag.COMMENT;
import static org.jooq.DDLFlag.DOMAIN;
import static org.jooq.DDLFlag.FOREIGN_KEY;
import static org.jooq.DDLFlag.INDEX;
import static org.jooq.DDLFlag.PRIMARY_KEY;
import static org.jooq.DDLFlag.SCHEMA;
import static org.jooq.DDLFlag.SEQUENCE;
import static org.jooq.DDLFlag.TABLE;
import static org.jooq.DDLFlag.UNIQUE;
// ...
import static org.jooq.SQLDialect.SQLITE;
import static org.jooq.TableOptions.TableType.VIEW;
import static org.jooq.impl.Comparators.KEY_COMP;
import static org.jooq.impl.Comparators.NAMED_COMP;
import static org.jooq.impl.Comparators.TABLE_VIEW_COMP;
import static org.jooq.impl.DSL.constraint;
import static org.jooq.impl.Tools.map;
import static org.jooq.tools.StringUtils.isEmpty;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import org.jooq.Check;
import org.jooq.Comment;
import org.jooq.Configuration;
import org.jooq.Constraint;
import org.jooq.ConstraintEnforcementStep;
import org.jooq.CreateDomainAsStep;
import org.jooq.CreateDomainConstraintStep;
import org.jooq.CreateDomainDefaultStep;
import org.jooq.CreateIndexIncludeStep;
import org.jooq.CreateSequenceFlagsStep;
import org.jooq.CreateTableOnCommitStep;
import org.jooq.CreateViewAsStep;
import org.jooq.DDLExportConfiguration;
import org.jooq.DDLFlag;
import org.jooq.DSLContext;
import org.jooq.Domain;
import org.jooq.Field;
import org.jooq.ForeignKey;
import org.jooq.Index;
import org.jooq.Key;
import org.jooq.Meta;
import org.jooq.Named;
// ...
import org.jooq.Queries;
import org.jooq.Query;
import org.jooq.Record;
import org.jooq.SQLDialect;
import org.jooq.Schema;
import org.jooq.Select;
import org.jooq.Sequence;
import org.jooq.SortOrder;
import org.jooq.Table;
import org.jooq.TableOptions;
import org.jooq.TableOptions.OnCommit;
import org.jooq.TableOptions.TableType;
import org.jooq.UniqueKey;
import org.jooq.DDLExportConfiguration.InlineForeignKeyConstraints;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.StringUtils;

/**
 * @author Lukas Eder
 */
final class DDL {

    private static final JooqLogger      log                             = JooqLogger.getLogger(DDL.class);




    static final Set         NO_SUPPORT_ALTER_ADD_CONSTRAINT = SQLDialect.supportedBy(SQLITE);

    private final DSLContext             ctx;
    private final DDLExportConfiguration configuration;

    DDL(DSLContext ctx, DDLExportConfiguration configuration) {
        this.ctx = ctx;
        this.configuration = configuration;
    }

    final List createTableOrView(Table table, Collection constraints) {
        boolean temporary = table.getTableType() == TableType.TEMPORARY;
        boolean view = table.getTableType().isView();
        OnCommit onCommit = table.getOptions().onCommit();

        if (view) {
            List result = new ArrayList<>();

            result.add(
                applyAs((configuration.createViewIfNotExists()
                        ? ctx.createViewIfNotExists(table, table.fields())
                        : configuration.createOrReplaceView()
                        ? ctx.createOrReplaceView(table, table.fields())
                        : ctx.createView(table, table.fields())), table.getOptions())
            );

            if (!constraints.isEmpty() && configuration.includeConstraintsOnViews())
                result.addAll(alterTableAddConstraints(table));

            return result;
        }

        CreateTableOnCommitStep s0 =
            (configuration.createTableIfNotExists()
                        ? temporary
                            ? ctx.createTemporaryTableIfNotExists(table)
                            : ctx.createTableIfNotExists(table)
                        : temporary
                            ? ctx.createTemporaryTable(table)
                            : ctx.createTable(table))
                .columns(sortIf(asList(table.fields()), !configuration.respectColumnOrder()))
                .constraints(constraints);

        if (temporary && onCommit != null) {
            switch (onCommit) {
                case DELETE_ROWS:
                    return asList(s0.onCommitDeleteRows());
                case PRESERVE_ROWS:
                    return asList(s0.onCommitPreserveRows());
                case DROP:
                    return asList(s0.onCommitDrop());
                default:
                    throw new IllegalStateException("Unsupported flag: " + onCommit);
            }
        }

        return asList(s0);
    }

    static final Pattern P_CREATE_VIEW = Pattern.compile("^(?i:\\bcreate\\b.*?\\bview\\b.*?\\bas\\b\\s+)(.*)$");

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private final Query applyAs(CreateViewAsStep q, TableOptions options) {
        if (options.select() != null)
            return q.as(options.select());
        else if (StringUtils.isBlank(options.source()))
            return q.as("");

        try {

            // [#16979] Internal, undocumented flag to prevent
            // [#17013] TODO: Use new Settings instead, once implemented
            Configuration c = ctx.configuration().derive();
            c.data("org.jooq.parser.delimiter-required", true);
            Query[] queries = c.dsl().parser().parse(options.source()).queries();

            if (queries.length > 0) {
                if (queries[0] instanceof CreateViewImpl cv)
                    return q.as(cv.$select());
                else if (queries[0] instanceof Select s)
                    return q.as(s);
                else
                    return q.as("");
            }
            else
                return q.as("");
        }
        catch (ParserException e) {
            log.info("Cannot parse view source: " + options.source(), e);

            // [#15238] If the command prefix is supplied, use a heursitic where the view
            //          body is expected to start after the first "AS" keyword
            if (options.source().toLowerCase().startsWith("create"))
                return q.as(P_CREATE_VIEW.matcher(options.source()).replaceFirst("$1"));
            else
                return q.as(options.source());
        }
    }

    final Query createSequence(Sequence sequence) {
        CreateSequenceFlagsStep result = configuration.createSequenceIfNotExists()
                    ? ctx.createSequenceIfNotExists(sequence)
                    : ctx.createSequence(sequence);

        if (sequence.getStartWith() != null)
            result = result.startWith(sequence.getStartWith());
        else if (configuration.defaultSequenceFlags())
            result = result.startWith(1);

        if (sequence.getIncrementBy() != null)
            result = result.incrementBy(sequence.getIncrementBy());
        else if (configuration.defaultSequenceFlags())
            result = result.incrementBy(1);

        if (sequence.getMinvalue() != null)
            result = result.minvalue(sequence.getMinvalue());
        else if (configuration.defaultSequenceFlags())
            result = result.noMinvalue();

        if (sequence.getMaxvalue() != null)
            result = result.maxvalue(sequence.getMaxvalue());
        else if (configuration.defaultSequenceFlags())
            result = result.noMaxvalue();

        if (sequence.getCycle())
            result = result.cycle();
        else if (configuration.defaultSequenceFlags())
            result = result.noCycle();

        if (sequence.getCache() != null)
            result = result.cache(sequence.getCache());
        else if (configuration.defaultSequenceFlags())
            result = result.noCache();

        return result;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    final Query createDomain(Domain domain) {
        CreateDomainAsStep s1 = configuration.createDomainIfNotExists()
                    ? ctx.createDomainIfNotExists(domain)
                    : ctx.createDomain(domain);

        CreateDomainDefaultStep s2 = s1.as(domain.getDataType());
        CreateDomainConstraintStep s3 = domain.getDataType().defaulted()
            ? s2.default_(domain.getDataType().default_())
            : s2;

        if (domain.getChecks().isEmpty())
            return s3;

        return s3.constraints(map(domain.getChecks(), c -> c.constraint()));
    }

    final List createTableOrView(Table table) {
        return createTableOrView(table, constraints(table, true));
    }

    final List createIndex(Table table) {
        List result = new ArrayList<>();

        if (configuration.flags().contains(DDLFlag.INDEX))
            for (Index i : sortIf(table.getIndexes(), !configuration.respectIndexOrder()))
                result.add(createIndex(i));

        return result;
    }

    final Query createIndex(Index i) {
        CreateIndexIncludeStep s1 =
            (configuration.createIndexIfNotExists()
                ? i.getUnique()
                    ? ctx.createUniqueIndexIfNotExists(i)
                    : ctx.createIndexIfNotExists(i)
                : i.getUnique()
                    ? ctx.createUniqueIndex(i)
                    : ctx.createIndex(i))
            .on(i.getTable(), i.getFields());

        return i.getWhere() != null ? s1.where(i.getWhere()) : s1;
    }

    final List alterTableAddConstraints(Table table) {
        return alterTableAddConstraints(table, constraints(table, true));
    }

    final List alterTableAddConstraints(Table table, List constraints) {
        return map(constraints, c -> ctx.alterTable(table).add(c));
    }

    final List constraints(Table table, boolean includeForeignKeys) {
        List result = new ArrayList<>();

        result.addAll(primaryKeys(table));
        result.addAll(uniqueKeys(table));
        if (includeForeignKeys)
            result.addAll(foreignKeys(table));
        result.addAll(checks(table));

        return result;
    }

    final List primaryKeys(Table table) {
        List result = new ArrayList<>();

        if (configuration.flags().contains(PRIMARY_KEY) && (table.getTableType() != VIEW || configuration.includeConstraintsOnViews()))
            for (UniqueKey key : table.getKeys())
                if (key.isPrimary())
                    result.add(enforced(constraint(key.getUnqualifiedName()).primaryKey(key.getFieldsArray()), key.enforced()));

        return result;
    }

    final List uniqueKeys(Table table) {
        List result = new ArrayList<>();

        if (configuration.flags().contains(UNIQUE) && (table.getTableType() != VIEW || configuration.includeConstraintsOnViews()))
            for (UniqueKey key : sortKeysIf(table.getKeys(), !configuration.respectConstraintOrder()))
                if (!key.isPrimary())
                    result.add(enforced(constraint(key.getUnqualifiedName()).unique(key.getFieldsArray()), key.enforced()));

        return result;
    }

    final List foreignKeys(Table table) {
        List result = new ArrayList<>();

        if (configuration.flags().contains(FOREIGN_KEY) && (table.getTableType() != VIEW || configuration.includeConstraintsOnViews()))
            for (ForeignKey key : sortKeysIf(table.getReferences(), !configuration.respectConstraintOrder()))
                result.add(enforced(constraint(key.getUnqualifiedName()).foreignKey(key.getFieldsArray()).references(key.getKey().getTable(), key.getKeyFieldsArray()), key.enforced()));

        return result;
    }

    final List checks(Table table) {
        List result = new ArrayList<>();

        if (configuration.flags().contains(CHECK) && (table.getTableType() != VIEW || configuration.includeConstraintsOnViews()))
            for (Check check : sortIf(table.getChecks(), !configuration.respectConstraintOrder()))
                result.add(enforced(constraint(check.getUnqualifiedName()).check(check.condition()), check.enforced()));

        return result;
    }

    final Queries queries(Table... tables) {
        List queries = new ArrayList<>();

        for (Table table : tables) {
            if (configuration.flags().contains(TABLE))
                queries.addAll(createTableOrView(table));
            else
                queries.addAll(alterTableAddConstraints(table));

            queries.addAll(createIndex(table));
            queries.addAll(commentOn(table));
        }

        return ctx.queries(queries);
    }

    final List commentOn(Table table) {
        List result = new ArrayList<>();

        if (configuration.flags().contains(COMMENT)) {
            Comment tComment = table.getCommentPart();

            if (!StringUtils.isEmpty(tComment.getComment()))
                if (table.getTableType().isView())
                    result.add(ctx.commentOnView(table).is(tComment));
                else
                    result.add(ctx.commentOnTable(table).is(tComment));

            for (Field field : sortIf(Arrays.asList(table.fields()), !configuration.respectColumnOrder())) {
                Comment fComment = field.getCommentPart();

                if (!StringUtils.isEmpty(fComment.getComment()))
                    result.add(ctx.commentOnColumn(field).is(fComment));
            }
        }

        return result;
    }

    final Queries queries(Meta meta) {
        List queries = new ArrayList<>();
        List schemas = sortIf(meta.getSchemas(), !configuration.respectSchemaOrder());

        for (Schema schema : schemas)
            if (configuration.flags().contains(SCHEMA) && !schema.getUnqualifiedName().empty())
                if (configuration.createSchemaIfNotExists())
                    queries.add(ctx.createSchemaIfNotExists(schema.getUnqualifiedName()));
                else
                    queries.add(ctx.createSchema(schema.getUnqualifiedName()));

        // [#15291] There exist some corner cases where inline constraints don't work, including when there's an
        //          explicit or implicit CONSTRAINT .. USING INDEX clause where an index has to be created before
        //          the constraint.
        Set> tablesWithInlineConstraints = new HashSet<>();

        if (configuration.flags().contains(TABLE)) {
            for (Schema schema : schemas) {
                for (Table table : sortTablesIf(schema.getTables(), !configuration.respectTableOrder())) {
                    List constraints = new ArrayList<>();

                    if (!hasConstraintsUsingIndexes(table)) {
                        tablesWithInlineConstraints.add(table);
                        constraints.addAll(constraints(table, inlineForeignKeyDefinitions()));
                    }

                    queries.addAll(createTableOrView(table, constraints));
                }
            }
        }

        if (configuration.flags().contains(INDEX))
            for (Schema schema : schemas)
                for (Table table : sortIf(schema.getTables(), !configuration.respectTableOrder()))
                    queries.addAll(createIndex(table));

        for (Schema schema : schemas) {
            if (configuration.flags().contains(PRIMARY_KEY))
                for (Table table : sortIf(schema.getTables(), !configuration.respectTableOrder()))
                    if (!tablesWithInlineConstraints.contains(table))
                        for (Constraint constraint : sortIf(primaryKeys(table), !configuration.respectConstraintOrder()))
                            queries.add(ctx.alterTable(table).add(constraint));

            if (configuration.flags().contains(UNIQUE))
                for (Table table : sortIf(schema.getTables(), !configuration.respectTableOrder()))
                    if (!tablesWithInlineConstraints.contains(table))
                        for (Constraint constraint : sortIf(uniqueKeys(table), !configuration.respectConstraintOrder()))
                            queries.add(ctx.alterTable(table).add(constraint));

            if (configuration.flags().contains(CHECK))
                for (Table table : sortIf(schema.getTables(), !configuration.respectTableOrder()))
                    if (!tablesWithInlineConstraints.contains(table))
                        for (Constraint constraint : sortIf(checks(table), !configuration.respectConstraintOrder()))
                            queries.add(ctx.alterTable(table).add(constraint));
        }

        if (configuration.flags().contains(FOREIGN_KEY) && !inlineForeignKeyDefinitions())
            for (Schema schema : schemas)
                for (Table table : sortIf(schema.getTables(), !configuration.respectTableOrder()))
                    for (Constraint constraint : foreignKeys(table))
                        queries.add(ctx.alterTable(table).add(constraint));

        if (configuration.flags().contains(DOMAIN))
            for (Schema schema : schemas)
                for (Domain domain : sortIf(schema.getDomains(), !configuration.respectDomainOrder()))
                    queries.add(createDomain(domain));

        if (configuration.flags().contains(SEQUENCE))
            for (Schema schema : schemas)
                for (Sequence sequence : sortIf(schema.getSequences(), !configuration.respectSequenceOrder()))
                    queries.add(createSequence(sequence));

        if (configuration.flags().contains(COMMENT))
            for (Schema schema : schemas)
                for (Table table : sortIf(schema.getTables(), !configuration.respectTableOrder()))
                    queries.addAll(commentOn(table));

        return ctx.queries(queries);
    }

    private final boolean inlineForeignKeyDefinitions() {
        return configuration.flags().contains(TABLE) && (
            configuration.inlineForeignKeyConstraints() == InlineForeignKeyConstraints.ALWAYS
         || configuration.inlineForeignKeyConstraints() == InlineForeignKeyConstraints.WHEN_NEEDED && NO_SUPPORT_ALTER_ADD_CONSTRAINT.contains(ctx.dialect())
        );
    }

    private final  boolean hasConstraintsUsingIndexes(Table table) {
















        return false;
    }

    private final > List sortKeysIf(List input, boolean sort) {
        if (sort) {
            List result = new ArrayList<>(input);
            result.sort(KEY_COMP);
            result.sort(NAMED_COMP);
            return result;
        }

        return input;
    }

    private final  List sortIf(List input, boolean sort) {
        if (sort) {
            List result = new ArrayList<>(input);
            result.sort(NAMED_COMP);
            return result;
        }

        return input;
    }

    private final > List sortTablesIf(List input, boolean sort) {

        if (sort) {
            List result = new ArrayList<>(input);
            result.sort(NAMED_COMP);

            // [#15326] Tables should always appear before views
            result.sort(TABLE_VIEW_COMP);
            return result;
        }

        return input;
    }

    private final Constraint enforced(ConstraintEnforcementStep check, boolean enforced) {
        return enforced ? check : check.notEnforced();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy