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

net.java.ao.schema.ddl.SchemaReader Maven / Gradle / Ivy

Go to download

This is the full Active Objects library, if you don't know which one to use, you probably want this one.

There is a newer version: 6.1.1
Show newest version
/*
 * Copyright 2007 Daniel Spiewak
 *
 * 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.
 */
package net.java.ao.schema.ddl;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import net.java.ao.Common;
import net.java.ao.DatabaseProvider;
import net.java.ao.SchemaConfiguration;
import net.java.ao.schema.Case;
import net.java.ao.schema.NameConverters;
import net.java.ao.schema.helper.DatabaseMetaDataReader;
import net.java.ao.schema.helper.DatabaseMetaDataReaderImpl;
import net.java.ao.schema.helper.Field;
import net.java.ao.schema.helper.ForeignKey;
import net.java.ao.schema.helper.Index;
import net.java.ao.types.TypeInfo;
import net.java.ao.types.TypeManager;
import net.java.ao.types.TypeQualifiers;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static com.google.common.collect.Lists.newArrayList;
import static net.java.ao.sql.SqlUtils.closeQuietly;

/**
 * WARNING: Not part of the public API.  This class is public only
 * to allow its use within other packages in the ActiveObjects library.
 *
 * @author Daniel Spiewak
 */
public final class SchemaReader {
    static {
        try {
            DEFAULT_MYSQL_TIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("0000-00-00 00:00:00").getTime();
        } catch (ParseException e) {
            throw new IllegalStateException(e);
        }
    }

    private static final long DEFAULT_MYSQL_TIME;

    /**
     * Currently doesn't account for:
     *
     * 
    *
  • setUnique
  • *
*/ public static DDLTable[] readSchema(DatabaseProvider provider, NameConverters nameConverters, SchemaConfiguration schemaConfiguration) throws SQLException { return readSchema(provider, nameConverters, schemaConfiguration, true); } public static DDLTable[] readSchema(DatabaseProvider provider, NameConverters nameConverters, SchemaConfiguration schemaConfiguration, final boolean includeForeignKeys) throws SQLException { Connection connection = null; try { connection = provider.getConnection(); return readSchema(connection, provider, nameConverters, schemaConfiguration, includeForeignKeys); } finally { closeQuietly(connection); } } public static DDLTable[] readSchema(Connection connection, DatabaseProvider provider, NameConverters nameConverters, SchemaConfiguration schemaConfiguration, final boolean includeForeignKeys) throws SQLException { final DatabaseMetaDataReader databaseMetaDataReader = new DatabaseMetaDataReaderImpl(provider, nameConverters, schemaConfiguration); final DatabaseMetaData databaseMetaData = connection.getMetaData(); final List tables = newArrayList(Iterables.transform(databaseMetaDataReader.getTableNames(databaseMetaData), new Function() { public DDLTable apply(String tableName) { return readTable(databaseMetaDataReader, databaseMetaData, tableName, includeForeignKeys); } })); return tables.toArray(new DDLTable[tables.size()]); } private static DDLTable readTable(DatabaseMetaDataReader databaseMetaDataReader, DatabaseMetaData databaseMetaData, String tableName, boolean includeForeignKeys) { DDLTable table = new DDLTable(); table.setName(tableName); final List fields = readFields(databaseMetaDataReader, databaseMetaData, tableName); table.setFields(fields.toArray(new DDLField[fields.size()])); if (includeForeignKeys) { final List foreignKeys = readForeignKeys(databaseMetaDataReader, databaseMetaData, tableName); table.setForeignKeys(foreignKeys.toArray(new DDLForeignKey[foreignKeys.size()])); } final List indexes = readIndexes(databaseMetaDataReader, databaseMetaData, tableName); table.setIndexes(indexes.toArray(new DDLIndex[indexes.size()])); return table; } private static List readFields(DatabaseMetaDataReader databaseMetaDataReader, DatabaseMetaData databaseMetaData, String tableName) { return newArrayList(Iterables.transform(databaseMetaDataReader. getFields(databaseMetaData, tableName), new Function() { public DDLField apply(Field from) { DDLField field = new DDLField(); field.setAutoIncrement(from.isAutoIncrement()); field.setDefaultValue(from.getDefaultValue()); field.setName(from.getName()); field.setNotNull(from.isNotNull()); field.setPrimaryKey(from.isPrimaryKey()); field.setType(from.getDatabaseType()); field.setJdbcType(from.getJdbcType()); field.setUnique(from.isUnique()); return field; } })); } private static List readForeignKeys(DatabaseMetaDataReader databaseMetaDataReader, DatabaseMetaData databaseMetaData, String tableName) { return newArrayList(Iterables.transform(databaseMetaDataReader.getForeignKeys(databaseMetaData, tableName), new Function() { public DDLForeignKey apply(ForeignKey from) { DDLForeignKey key = new DDLForeignKey(); key.setForeignField(from.getForeignFieldName()); key.setField(from.getLocalFieldName()); key.setTable(from.getForeignTableName()); key.setDomesticTable(from.getLocalTableName()); return key; } })); } private static List readIndexes(DatabaseMetaDataReader databaseMetaDataReader, DatabaseMetaData databaseMetaData, final String tableName) { final Iterable indexes = databaseMetaDataReader.getIndexes(databaseMetaData, tableName); return StreamSupport.stream(indexes.spliterator(), false) .map(index -> toDDLIndex(index, tableName)) .collect(Collectors.toList()); } private static DDLIndex toDDLIndex(Index index, String tableName) { return DDLIndex.builder() .indexName(index.getIndexName()) .table(tableName) .fields(index.getFieldNames().stream() .map(SchemaReader::toDDLIndexField) .toArray(DDLIndexField[]::new)) .build(); } private static DDLIndexField toDDLIndexField(String fieldName) { return DDLIndexField.builder().fieldName(fieldName).build(); } /** * Returns the difference between from and * onto with a bias toward from. * Thus, if a table is present in from but not * onto, a CREATE TABLE * statement will be generated. */ public static DDLAction[] diffSchema(TypeManager typeManager, DDLTable[] fromArray, DDLTable[] ontoArray, boolean caseSensitive) { Set actions = new HashSet(); List createTables = new ArrayList(); List dropTables = new ArrayList(); List alterTables = new ArrayList(); Map from = new HashMap(); Map onto = new HashMap(); for (DDLTable table : fromArray) { final String tableName = transform(table.getName(), caseSensitive); from.put(tableName, table); } for (DDLTable table : ontoArray) { final String tableName = transform(table.getName(), caseSensitive); onto.put(tableName, table); } for (DDLTable table : fromArray) { final String tableName = transform(table.getName(), caseSensitive); if (onto.containsKey(tableName)) { alterTables.add(table); } else { createTables.add(table); } } for (DDLTable table : ontoArray) { String tableName = transform(table.getName(), caseSensitive); if (!from.containsKey(tableName)) { dropTables.add(table); } } for (DDLTable table : createTables) { DDLAction action = new DDLAction(DDLActionType.CREATE); action.setTable(table); actions.add(action); for (DDLIndex index : table.getIndexes()) { DDLAction indexAction = new DDLAction(DDLActionType.CREATE_INDEX); indexAction.setIndex(index); actions.add(indexAction); } } List dropKeys = new ArrayList(); for (DDLTable table : dropTables) { DDLAction action = new DDLAction(DDLActionType.DROP); action.setTable(table); actions.add(action); // remove all foreign keys on that table dropKeys.addAll(Arrays.asList(table.getForeignKeys())); // remove all foreign to that table for (DDLTable alterTable : alterTables) { for (DDLForeignKey fKey : alterTable.getForeignKeys()) { if (equals(fKey.getTable(), table.getName(), caseSensitive)) { dropKeys.add(fKey); } } } } for (DDLTable fromTable : alterTables) { final String s = fromTable.getName(); String tableName = transform(s, caseSensitive); DDLTable ontoTable = onto.get(tableName); List createFields = new ArrayList(); List dropFields = new ArrayList(); List alterFields = new ArrayList(); Map fromFields = new HashMap(); Map ontoFields = new HashMap(); for (DDLField field : fromTable.getFields()) { String fieldName = transform(field.getName(), caseSensitive); fromFields.put(fieldName, field); } for (DDLField field : ontoTable.getFields()) { String fieldName = transform(field.getName(), caseSensitive); ontoFields.put(fieldName, field); } for (DDLField field : fromTable.getFields()) { String fieldName = transform(field.getName(), caseSensitive); if (ontoFields.containsKey(fieldName)) { alterFields.add(field); } else { createFields.add(field); } } for (DDLField field : ontoTable.getFields()) { String fieldName = transform(field.getName(), caseSensitive); if (!fromFields.containsKey(fieldName)) { dropFields.add(field); } } for (DDLField field : createFields) { DDLAction action = new DDLAction(DDLActionType.ALTER_ADD_COLUMN); action.setTable(fromTable); action.setField(field); actions.add(action); } for (DDLField field : dropFields) { DDLAction action = new DDLAction(DDLActionType.ALTER_DROP_COLUMN); action.setTable(fromTable); action.setField(field); actions.add(action); } for (DDLField fromField : alterFields) { final String fieldName = transform(fromField.getName(), caseSensitive); final DDLField ontoField = ontoFields.get(fieldName); if (fromField.getDefaultValue() == null && ontoField.getDefaultValue() != null) { actions.add(createColumnAlterAction(fromTable, ontoField, fromField)); } else if (fromField.getDefaultValue() != null && !Common.fuzzyCompare(typeManager, fromField.getDefaultValue(), ontoField.getDefaultValue())) { actions.add(createColumnAlterAction(fromTable, ontoField, fromField)); } else if (!physicalTypesEqual(fromField.getType(), ontoField.getType())) { actions.add(createColumnAlterAction(fromTable, ontoField, fromField)); } else if (fromField.isNotNull() != ontoField.isNotNull()) { actions.add(createColumnAlterAction(fromTable, ontoField, fromField)); } else if (!fromField.isPrimaryKey() && (fromField.isUnique() != ontoField.isUnique())) { actions.add(createColumnAlterAction(fromTable, ontoField, fromField)); } } // foreign keys List addKeys = new ArrayList(); for (DDLForeignKey fromKey : fromTable.getForeignKeys()) { for (DDLForeignKey ontoKey : ontoTable.getForeignKeys()) { if (!(fromKey.getTable().equalsIgnoreCase(ontoKey.getTable()) && fromKey.getForeignField().equalsIgnoreCase(ontoKey.getForeignField())) && fromKey.getField().equalsIgnoreCase(ontoKey.getField()) && fromKey.getDomesticTable().equalsIgnoreCase(ontoKey.getDomesticTable())) { addKeys.add(fromKey); } } } for (DDLForeignKey ontoKey : ontoTable.getForeignKeys()) { if (containsField(dropFields, ontoKey.getField())) { dropKeys.add(ontoKey); continue; } for (DDLForeignKey fromKey : fromTable.getForeignKeys()) { if (!(ontoKey.getTable().equalsIgnoreCase(fromKey.getTable()) && ontoKey.getForeignField().equalsIgnoreCase(fromKey.getForeignField())) && ontoKey.getField().equalsIgnoreCase(fromKey.getField()) && ontoKey.getDomesticTable().equalsIgnoreCase(fromKey.getDomesticTable())) { dropKeys.add(ontoKey); } } } for (DDLForeignKey key : addKeys) { DDLAction action = new DDLAction(DDLActionType.ALTER_ADD_KEY); action.setKey(key); actions.add(action); } // field indexes List addIndexes = new ArrayList(); List dropIndexes = new ArrayList(); for (DDLIndex fromIndex : fromTable.getIndexes()) { final boolean present = Stream.of(ontoTable.getIndexes()) .filter(index -> index.equals(fromIndex)) .findAny() .isPresent(); if (!present) { addIndexes.add(fromIndex); } } for (DDLIndex ontoIndex : ontoTable.getIndexes()) { final boolean present = Stream.of(fromTable.getIndexes()) .filter(index -> index.equals(ontoIndex)) .findAny() .isPresent(); if (!present) { dropIndexes.add(ontoIndex); } } for (DDLIndex index : addIndexes) { DDLAction action = new DDLAction(DDLActionType.CREATE_INDEX); action.setIndex(index); actions.add(action); } for (DDLIndex index : dropIndexes) { DDLAction action = new DDLAction(DDLActionType.DROP_INDEX); action.setIndex(index); actions.add(action); } } for (DDLForeignKey key : dropKeys) { DDLAction action = new DDLAction(DDLActionType.ALTER_DROP_KEY); action.setKey(key); actions.add(action); } return actions.toArray(new DDLAction[actions.size()]); } private static boolean physicalTypesEqual(TypeInfo from, TypeInfo onto) { // We need to check qualifier compatibility instead of equality because there can be a mismatch between entity annotation derived // qualifiers vs. those derived from table metadata even when the schema hasn't changed. return TypeQualifiers.areCompatible(from.getQualifiers(), onto.getQualifiers()) && from.getSchemaProperties().equals(onto.getSchemaProperties()); } private static boolean equals(String s, String s1, boolean caseSensitive) { return transform(s, caseSensitive).equals(transform(s1, caseSensitive)); } private static String transform(String s, boolean caseSensitive) { if (!caseSensitive) { return Case.LOWER.apply(s); } else { return s; } } public static DDLAction[] sortTopologically(DDLAction[] actions) { List back = new LinkedList(); Map> deps = new HashMap>(); List roots = new LinkedList(); Set covered = new HashSet(); performSort(actions, deps, roots); while (!roots.isEmpty()) { DDLAction[] rootsArray = roots.toArray(new DDLAction[roots.size()]); roots.remove(rootsArray[0]); if (covered.contains(rootsArray[0])) { throw new RuntimeException("Circular dependency detected in or below " + rootsArray[0].getTable().getName()); } covered.add(rootsArray[0]); back.add(rootsArray[0]); List toRemove = new LinkedList(); Iterator depIterator = deps.keySet().iterator(); while (depIterator.hasNext()) { DDLAction depAction = depIterator.next(); Set individualDeps = deps.get(depAction); individualDeps.remove(rootsArray[0]); if (individualDeps.isEmpty()) { roots.add(depAction); toRemove.add(depAction); } } for (DDLAction action : toRemove) { deps.remove(action); } } return back.toArray(new DDLAction[back.size()]); } /* * DROP_KEY * DROP_INDEX * DROP_COLUMN * CHANGE_COLUMN * DROP * CREATE * ADD_COLUMN * ADD_KEY * CREATE_INDEX */ private static void performSort(DDLAction[] actions, Map> deps, List roots) { List dropKeys = new LinkedList(); List dropIndexes = new LinkedList(); List dropColumns = new LinkedList(); List changeColumns = new LinkedList(); List drops = new LinkedList(); List creates = new LinkedList(); List addColumns = new LinkedList(); List addKeys = new LinkedList(); List createIndexes = new LinkedList(); for (DDLAction action : actions) { switch (action.getActionType()) { case ALTER_DROP_KEY: dropKeys.add(action); break; case DROP_INDEX: dropIndexes.add(action); break; case ALTER_DROP_COLUMN: dropColumns.add(action); break; case ALTER_CHANGE_COLUMN: changeColumns.add(action); break; case DROP: drops.add(action); break; case CREATE: creates.add(action); break; case ALTER_ADD_COLUMN: addColumns.add(action); break; case ALTER_ADD_KEY: addKeys.add(action); break; case CREATE_INDEX: createIndexes.add(action); break; } } roots.addAll(dropKeys); roots.addAll(dropIndexes); for (DDLAction action : dropColumns) { Set dependencies = new HashSet(); for (DDLAction depAction : dropKeys) { DDLForeignKey key = depAction.getKey(); if ((key.getTable().equals(action.getTable().getName()) && key.getForeignField().equals(action.getField().getName())) || (key.getDomesticTable().equals(action.getTable().getName()) && key.getField().equals(action.getField().getName()))) { dependencies.add(depAction); } } if (dependencies.size() == 0) { roots.add(action); } else { deps.put(action, dependencies); } } for (DDLAction action : changeColumns) { Set dependencies = new HashSet(); for (DDLAction depAction : dropKeys) { DDLForeignKey key = depAction.getKey(); if ((key.getTable().equals(action.getTable().getName()) && key.getForeignField().equals(action.getField().getName())) || (key.getDomesticTable().equals(action.getTable().getName()) && key.getField().equals(action.getField().getName()))) { dependencies.add(depAction); } } for (DDLAction depAction : dropColumns) { if ((depAction.getTable().equals(action.getTable()) && depAction.getField().equals(action.getField())) || (depAction.getTable().equals(action.getTable()) && depAction.getField().equals(action.getOldField()))) { dependencies.add(depAction); } } if (dependencies.size() == 0) { roots.add(action); } else { deps.put(action, dependencies); } } for (DDLAction action : drops) { Set dependencies = new HashSet(); for (DDLAction depAction : dropKeys) { DDLForeignKey key = depAction.getKey(); if (key.getTable().equals(action.getTable().getName()) || key.getDomesticTable().equals(action.getTable().getName())) { dependencies.add(depAction); } } for (DDLAction depAction : dropColumns) { if (depAction.getTable().equals(action.getTable())) { dependencies.add(depAction); } } for (DDLAction depAction : changeColumns) { if (depAction.getTable().equals(action.getTable())) { dependencies.add(depAction); } } if (dependencies.size() == 0) { roots.add(action); } else { deps.put(action, dependencies); } } for (DDLAction action : creates) { Set dependencies = new HashSet(); for (DDLForeignKey key : action.getTable().getForeignKeys()) { for (DDLAction depAction : creates) { if (depAction != action && depAction.getTable().getName().equals(key.getTable())) { dependencies.add(depAction); } } for (DDLAction depAction : addColumns) { if (depAction.getTable().getName().equals(key.getTable()) && depAction.getField().getName().equals(key.getForeignField())) { dependencies.add(depAction); } } for (DDLAction depAction : changeColumns) { if (depAction.getTable().getName().equals(key.getTable()) && depAction.getField().getName().equals(key.getForeignField())) { dependencies.add(depAction); } } } if (dependencies.size() == 0) { roots.add(action); } else { deps.put(action, dependencies); } } for (DDLAction action : addColumns) { Set dependencies = new HashSet(); for (DDLAction depAction : creates) { if (depAction.getTable().equals(action.getTable())) { dependencies.add(depAction); } } if (dependencies.size() == 0) { roots.add(action); } else { deps.put(action, dependencies); } } for (DDLAction action : addKeys) { Set dependencies = new HashSet(); DDLForeignKey key = action.getKey(); for (DDLAction depAction : creates) { if (depAction.getTable().getName().equals(key.getTable()) || depAction.getTable().getName().equals(key.getDomesticTable())) { dependencies.add(depAction); } } for (DDLAction depAction : addColumns) { if ((depAction.getTable().getName().equals(key.getTable()) && depAction.getField().getName().equals(key.getForeignField())) || (depAction.getTable().getName().equals(key.getDomesticTable())) && depAction.getField().getName().equals(key.getField())) { dependencies.add(depAction); } } for (DDLAction depAction : changeColumns) { if ((depAction.getTable().getName().equals(key.getTable()) && depAction.getField().getName().equals(key.getForeignField())) || (depAction.getTable().getName().equals(key.getDomesticTable())) && depAction.getField().getName().equals(key.getField())) { dependencies.add(depAction); } } if (dependencies.size() == 0) { roots.add(action); } else { deps.put(action, dependencies); } } for (DDLAction action : createIndexes) { Set dependencies = new HashSet(); DDLIndex index = action.getIndex(); List indexFieldNames = Stream.of(index.getFields()) .map(DDLIndexField::getFieldName) .collect(Collectors.toList()); for (DDLAction depAction : creates) { if (depAction.getTable().getName().equals(index.getTable())) { dependencies.add(depAction); } } for (DDLAction depAction : addColumns) { if (depAction.getTable().getName().equals(index.getTable()) || indexFieldNames.contains(depAction.getField().getName())) { dependencies.add(depAction); } } for (DDLAction depAction : changeColumns) { if (depAction.getTable().getName().equals(index.getTable()) || indexFieldNames.contains(depAction.getField().getName())) { dependencies.add(depAction); } } if (dependencies.size() == 0) { roots.add(action); } else { deps.put(action, dependencies); } } } private static boolean containsField(Iterable fields, final String fieldName) { return Iterables.tryFind(fields, new Predicate() { @Override public boolean apply(DDLField field) { return field.getName().equalsIgnoreCase(fieldName); } }).isPresent(); } private static DDLAction createColumnAlterAction(DDLTable table, DDLField oldField, DDLField field) { DDLAction action = new DDLAction(DDLActionType.ALTER_CHANGE_COLUMN); action.setTable(table); action.setField(field); action.setOldField(oldField); return action; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy