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

io.ebeaninternal.dbmigration.model.MTable Maven / Gradle / Ivy

There is a newer version: 15.8.0
Show newest version
package io.ebeaninternal.dbmigration.model;

import io.avaje.applog.AppLog;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp;
import io.ebeaninternal.dbmigration.migration.*;
import io.ebeaninternal.server.deploy.*;

import java.util.*;

import static io.ebeaninternal.dbmigration.ddlgeneration.platform.SplitColumns.split;
import static io.ebeaninternal.dbmigration.model.MTableIdentity.fromCreateTable;
import static io.ebeaninternal.dbmigration.model.MTableIdentity.toCreateTable;
import static java.lang.System.Logger.Level.TRACE;

/**
 * Holds the logical model for a given Table and everything associated to it.
 * 

* This effectively represents a table, its columns and all associated * constraints, foreign keys and indexes. *

* Migrations can be applied to this such that it represents the state * of a given table after various migrations have been applied. *

* This table model can also be derived from the EbeanServer bean descriptor * and associated properties. */ public class MTable { private static final System.Logger logger = AppLog.getLogger(MTable.class); private String name; /** * Marked true for draft tables. These need to have their FK references adjusted * after all the draft tables have been identified. */ private PartitionMeta partitionMeta; private TablespaceMeta tablespaceMeta; private String pkName; private String comment; private String storageEngine; private IdentityMode identityMode; private boolean withHistory; private final Map columns = new LinkedHashMap<>(); /** * Compound unique constraints. */ private final List uniqueConstraints = new ArrayList<>(); /** * Compound foreign keys. */ private final List compoundKeys = new ArrayList<>(); /** * Column name for the 'When created' column. This can be used for the initial effective start date when adding * history to an existing table and maps to a @WhenCreated or @CreatedTimestamp column. */ private String whenCreatedColumn; /** * Temporary - holds addColumn settings. */ private AddColumn addColumn; private final List droppedColumns = new ArrayList<>(); public MTable(BeanDescriptor descriptor) { this.name = descriptor.baseTable(); this.identityMode = descriptor.identityMode(); this.storageEngine = descriptor.storageEngine(); this.partitionMeta = descriptor.partitionMeta(); this.tablespaceMeta = descriptor.tablespaceMeta(); this.comment = descriptor.dbComment(); if (descriptor.isHistorySupport()) { withHistory = true; BeanProperty whenCreated = descriptor.whenCreatedProperty(); if (whenCreated != null) { whenCreatedColumn = whenCreated.dbColumn(); } } } /** * Constructor for test cases only! */ MTable(String name) { this(name, null, null); } /** * Construct for element collection or intersection table. They have same table space and storage engine. */ public MTable(String name, BeanDescriptor descriptor) { this(name, descriptor.tablespaceMeta(), descriptor.storageEngine()); } /** * Constructor for dependant tables (draft/element collection or intersection). */ private MTable(String name, TablespaceMeta tablespaceMeta, String storageEngine) { this.name = name; this.identityMode = IdentityMode.NONE; this.tablespaceMeta = tablespaceMeta; this.storageEngine = storageEngine; } /** * Construct for migration. */ public MTable(CreateTable createTable) { this.name = createTable.getName(); this.pkName = createTable.getPkName(); this.comment = createTable.getComment(); this.storageEngine = createTable.getStorageEngine(); if (createTable.getTablespace() != null) { this.tablespaceMeta = new TablespaceMeta(createTable.getTablespace(), createTable.getIndexTablespace() != null ? createTable.getIndexTablespace() : createTable.getTablespace(), createTable.getLobTablespace() != null ? createTable.getLobTablespace() : createTable.getTablespace()); } else { this.tablespaceMeta = null; } this.withHistory = Boolean.TRUE.equals(createTable.isWithHistory()); this.identityMode = fromCreateTable(createTable); List cols = createTable.getColumn(); for (Column column : cols) { addColumn(column); } for (UniqueConstraint uq : createTable.getUniqueConstraint()) { uniqueConstraints.add(new MCompoundUniqueConstraint(uq)); } for (ForeignKey fk : createTable.getForeignKey()) { if (DdlHelp.isDropForeignKey(fk.getColumnNames())) { removeForeignKey(fk.getName()); } else { addForeignKey(fk.getName(), fk.getRefTableName(), fk.getIndexName(), fk.getColumnNames(), fk.getRefColumnNames()); } } } public void addForeignKey(String name, String refTableName, String indexName, String columnNames, String refColumnNames) { MCompoundForeignKey foreignKey = new MCompoundForeignKey(name, refTableName, indexName); String[] cols = split(columnNames); String[] refCols = split(refColumnNames); for (int i = 0; i < cols.length && i < refCols.length; i++) { foreignKey.addColumnPair(cols[i], refCols[i]); } addForeignKey(foreignKey); } /** * Return the DropTable migration for this table. */ public DropTable dropTable() { DropTable dropTable = new DropTable(); dropTable.setName(name); // we must add pk col name & sequence name, as we have to delete the sequence also. if (identityMode.isDatabaseIdentity()) { String pkCol = null; for (MColumn column : columns.values()) { if (column.isPrimaryKey()) { if (pkCol == null) { pkCol = column.getName(); } else { // multiple pk cols -> no sequence pkCol = null; break; } } } if (pkCol != null) { dropTable.setSequenceCol(pkCol); dropTable.setSequenceName(identityMode.getSequenceName()); } } return dropTable; } /** * Return the CreateTable migration for this table. */ public CreateTable createTable() { CreateTable createTable = new CreateTable(); createTable.setName(name); createTable.setPkName(pkName); createTable.setComment(comment); if (partitionMeta != null) { createTable.setPartitionMode(partitionMeta.getMode().name()); createTable.setPartitionColumn(partitionMeta.getProperty()); } createTable.setStorageEngine(storageEngine); if (tablespaceMeta != null) { createTable.setTablespace(tablespaceMeta.getTablespaceName()); createTable.setIndexTablespace(tablespaceMeta.getIndexTablespace()); createTable.setLobTablespace(tablespaceMeta.getLobTablespace()); } toCreateTable(identityMode, createTable); if (withHistory) { createTable.setWithHistory(Boolean.TRUE); } for (MColumn column : allColumns()) { createTable.getColumn().add(column.createColumn()); } for (MCompoundForeignKey compoundKey : compoundKeys) { createTable.getForeignKey().add(compoundKey.createForeignKey()); } for (MCompoundUniqueConstraint constraint : uniqueConstraints) { createTable.getUniqueConstraint().add(constraint.getUniqueConstraint()); } return createTable; } /** * Compare to another version of the same table to perform a diff. */ public void compare(ModelDiff modelDiff, MTable newTable) { if (withHistory != newTable.withHistory) { if (withHistory) { DropHistoryTable dropHistoryTable = new DropHistoryTable(); dropHistoryTable.setBaseTable(name); modelDiff.addDropHistoryTable(dropHistoryTable); } else { AddHistoryTable addHistoryTable = new AddHistoryTable(); addHistoryTable.setBaseTable(name); modelDiff.addAddHistoryTable(addHistoryTable); } } compareColumns(modelDiff, newTable); if (MColumn.different(comment, newTable.comment)) { AddTableComment addTableComment = new AddTableComment(); addTableComment.setName(name); if (newTable.comment == null) { addTableComment.setComment(DdlHelp.DROP_COMMENT); } else { addTableComment.setComment(newTable.comment); } modelDiff.addTableComment(addTableComment); } compareCompoundKeys(modelDiff, newTable); compareUniqueKeys(modelDiff, newTable); compareTableAttrs(modelDiff, newTable); } private void compareColumns(ModelDiff modelDiff, MTable newTable) { addColumn = null; Map newColumnMap = newTable.getColumns(); // compare newColumns to existing columns (look for new and diff columns) for (MColumn newColumn : newColumnMap.values()) { MColumn localColumn = getColumn(newColumn.getName()); if (localColumn == null) { diffNewColumn(newColumn, newTable); } else { localColumn.compare(modelDiff, this, newColumn); } } // compare existing columns (look for dropped columns) for (MColumn existingColumn : allColumns()) { MColumn newColumn = newColumnMap.get(existingColumn.getName()); if (newColumn == null) { diffDropColumn(modelDiff, existingColumn); } } if (addColumn != null) { modelDiff.addAddColumn(addColumn); } } private void compareCompoundKeys(ModelDiff modelDiff, MTable newTable) { List newKeys = new ArrayList<>(newTable.getCompoundKeys()); List currentKeys = new ArrayList<>(getCompoundKeys()); // remove keys that have not changed currentKeys.removeAll(newTable.getCompoundKeys()); newKeys.removeAll(getCompoundKeys()); for (MCompoundForeignKey currentKey : currentKeys) { modelDiff.addAlterForeignKey(currentKey.dropForeignKey(name)); } for (MCompoundForeignKey newKey : newKeys) { modelDiff.addAlterForeignKey(newKey.addForeignKey(name)); } } private void compareUniqueKeys(ModelDiff modelDiff, MTable newTable) { List newKeys = new ArrayList<>(newTable.getUniqueConstraints()); List currentKeys = new ArrayList<>(getUniqueConstraints()); // remove keys that have not changed currentKeys.removeAll(newTable.getUniqueConstraints()); newKeys.removeAll(getUniqueConstraints()); for (MCompoundUniqueConstraint currentKey : currentKeys) { modelDiff.addUniqueConstraint(currentKey.dropUniqueConstraint(name)); } for (MCompoundUniqueConstraint newKey : newKeys) { modelDiff.addUniqueConstraint(newKey.addUniqueConstraint(name)); } } private void compareTableAttrs(ModelDiff modelDiff, MTable newTable) { AlterTable alterTable = new AlterTable(); alterTable.setName(newTable.getName()); boolean altered = false; if (!Objects.equals(tablespaceMeta, newTable.getTablespaceMeta())) { if (newTable.getTablespaceMeta() == null) { alterTable.setTablespace(DdlHelp.TABLESPACE_DEFAULT); alterTable.setIndexTablespace(DdlHelp.TABLESPACE_DEFAULT); alterTable.setLobTablespace(DdlHelp.TABLESPACE_DEFAULT); } else { alterTable.setTablespace(newTable.getTablespaceMeta().getTablespaceName()); alterTable.setIndexTablespace(newTable.getTablespaceMeta().getIndexTablespace()); alterTable.setLobTablespace(newTable.getTablespaceMeta().getLobTablespace()); } altered = true; } if (altered) { modelDiff.addAlterTable(alterTable); } } /** * Apply AddColumn migration. */ public void apply(AddColumn addColumn) { checkTableName(addColumn.getTableName()); for (Column column : addColumn.getColumn()) { addColumn(column); } } /** * Apply AddColumn migration. */ public void apply(AlterColumn alterColumn) { checkTableName(alterColumn.getTableName()); String columnName = alterColumn.getColumnName(); MColumn existingColumn = getColumn(columnName); if (existingColumn == null) { throw new IllegalStateException("Column [" + columnName + "] does not exist for AlterColumn change?"); } existingColumn.apply(alterColumn); } /** * Apply DropColumn migration. */ public void apply(DropColumn dropColumn) { checkTableName(dropColumn.getTableName()); MColumn removed = columns.remove(dropColumn.getColumnName()); if (removed == null) { throw new IllegalStateException("Column [" + dropColumn.getColumnName() + "] does not exist for DropColumn change on table [" + dropColumn.getTableName() + "]?"); } } public void apply(RenameColumn renameColumn) { checkTableName(renameColumn.getTableName()); MColumn column = columns.remove(renameColumn.getOldName()); if (column == null) { throw new IllegalStateException("Column [" + renameColumn.getOldName() + "] does not exist for RenameColumn change on table [" + renameColumn.getTableName() + "]?"); } addColumn(column.rename(renameColumn.getNewName())); } /** * Apply table rename to the model. */ public void apply(RenameTable renameTable) { checkTableName(renameTable.getOldName()); this.name = renameTable.getNewName(); } public String getName() { return name; } public String getSchema() { int pos = name.indexOf('.'); return pos == -1 ? null : name.substring(0, pos); } /** * Return true if this table is partitioned. */ public boolean isPartitioned() { return partitionMeta != null; } /** * Return the partition meta for this table. */ public PartitionMeta getPartitionMeta() { return partitionMeta; } public String getPkName() { return pkName; } public void setPkName(String pkName) { this.pkName = pkName; } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } public void setTablespaceMeta(TablespaceMeta tablespaceMeta) { this.tablespaceMeta = tablespaceMeta; } public TablespaceMeta getTablespaceMeta() { return tablespaceMeta; } public boolean isWithHistory() { return withHistory; } public MTable setWithHistory(boolean withHistory) { this.withHistory = withHistory; return this; } public List allHistoryColumns(boolean includeDropped) { List columnNames = new ArrayList<>(columns.size()); for (MColumn column : columns.values()) { if (column.isIncludeInHistory()) { columnNames.add(column.getName()); } } if (includeDropped && !droppedColumns.isEmpty()) { columnNames.addAll(droppedColumns); } return columnNames; } /** * Returns true, if there are pending dropped columns. */ public boolean hasDroppedColumns() { return !droppedColumns.isEmpty(); } /** * Return all the columns (excluding columns marked as dropped). */ public Collection allColumns() { return columns.values(); } /** * Return the column by name. */ public MColumn getColumn(String name) { return columns.get(name); } private Map getColumns() { return columns; } public List getUniqueConstraints() { return uniqueConstraints; } public List getCompoundKeys() { return compoundKeys; } public String getWhenCreatedColumn() { return whenCreatedColumn; } /** * Return the list of columns that make the primary key. */ public List primaryKeyColumns() { List pk = new ArrayList<>(3); for (MColumn column : allColumns()) { if (column.isPrimaryKey()) { pk.add(column); } } return pk; } /** * Return the primary key column if it is a simple primary key. */ public String singlePrimaryKey() { List columns = primaryKeyColumns(); if (columns.size() == 1) { return columns.get(0).getName(); } return null; } private void checkTableName(String tableName) { if (!name.equals(tableName)) { throw new IllegalArgumentException("tableName [" + tableName + "] does not match [" + name + "]"); } } /** * Add a column via migration data. */ private void addColumn(Column column) { columns.put(column.getName(), new MColumn(column)); } /** * Add a model column (typically from EbeanServer meta data). */ public void addColumn(MColumn column) { columns.put(column.getName(), column); } /** * Add a unique constraint. */ public void addUniqueConstraint(MCompoundUniqueConstraint uniqueConstraint) { uniqueConstraints.add(uniqueConstraint); } /** * Add a compound foreign key. */ public void addForeignKey(MCompoundForeignKey compoundKey) { compoundKeys.add(compoundKey); } /** * Add a column checking if it already exists and if so return the existing column. * Sometimes the case for a primaryKey that is also a foreign key. */ public MColumn addColumn(String dbCol, String columnDefn, boolean notnull) { MColumn existingColumn = getColumn(dbCol); if (existingColumn != null) { if (notnull) { existingColumn.setNotnull(true); } return existingColumn; } MColumn newCol = new MColumn(dbCol, columnDefn, notnull); addColumn(newCol); return newCol; } public MColumn addColumnScalar(String dbColumn, String columnDefn) { MColumn existingColumn = getColumn(dbColumn); if (existingColumn != null) { return existingColumn; } MColumn newCol = new MColumn(dbColumn, columnDefn); addColumn(newCol); return newCol; } /** * Add a 'new column' to the AddColumn migration object. */ private void diffNewColumn(MColumn newColumn, MTable newTable) { if (addColumn == null) { addColumn = new AddColumn(); addColumn.setTableName(name); if (newTable.isWithHistory()) { // These addColumns need to occur on the history // table as well as the base table addColumn.setWithHistory(Boolean.TRUE); } } addColumn.getColumn().add(newColumn.createColumn()); } /** * Add a 'drop column' to the diff. */ private void diffDropColumn(ModelDiff modelDiff, MColumn existingColumn) { DropColumn dropColumn = new DropColumn(); dropColumn.setTableName(name); dropColumn.setColumnName(existingColumn.getName()); if (withHistory) { // These dropColumns should occur on the history // table as well as the base table dropColumn.setWithHistory(Boolean.TRUE); } modelDiff.addDropColumn(dropColumn); } /** * Register a pending un-applied drop column. *

* This means this column still needs to be included in history views/triggers etc even * though it is not part of the current model. */ public void registerPendingDropColumn(String columnName) { droppedColumns.add(columnName); } /** * Check if there are duplicate foreign keys. *

* This can occur when an ManyToMany relates back to itself. *

*/ public void checkDuplicateForeignKeys() { if (hasDuplicateForeignKeys()) { int counter = 1; for (MCompoundForeignKey fk : compoundKeys) { fk.addNameSuffix(counter++); } } } /** * Return true if the foreign key names are not unique. */ private boolean hasDuplicateForeignKeys() { Set fkNames = new HashSet<>(); for (MCompoundForeignKey fk : compoundKeys) { if (!fkNames.add(fk.getName())) { return true; } } return false; } /** * This method adds information which columns are nullable or not to the compound indices. */ public void updateCompoundIndices() { for (MCompoundUniqueConstraint uniq : uniqueConstraints) { List nullableColumns = new ArrayList<>(); for (String columnName : uniq.getColumns()) { MColumn col = getColumn(columnName); if (col != null && !col.isNotnull()) { nullableColumns.add(columnName); } } uniq.setNullableColumns(nullableColumns.toArray(new String[0])); } } public void removeForeignKey(String name) { compoundKeys.removeIf(fk -> fk.getName().equals(name)); } /** * Clear the indexes on the foreign keys as they are covered by unique constraints. */ public void clearForeignKeyIndexes() { for (MCompoundForeignKey compoundKey : compoundKeys) { compoundKey.setIndexName(null); } } /** * Clear foreign key as this element collection table logically references * back to multiple tables. */ public MIndex setReusedElementCollection() { MIndex index = null; for (MColumn column : columns.values()) { final String references = column.getReferences(); if (references != null) { index = new MIndex(column.getForeignKeyIndex(), name, column.getName()); column.clearForeignKey(); } } return index; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy