001package io.ebeaninternal.dbmigration.model;
002
003import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp;
004import io.ebeaninternal.dbmigration.migration.AddColumn;
005import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
006import io.ebeaninternal.dbmigration.migration.AddTableComment;
007import io.ebeaninternal.dbmigration.migration.AlterColumn;
008import io.ebeaninternal.dbmigration.migration.AlterTable;
009import io.ebeaninternal.dbmigration.migration.Column;
010import io.ebeaninternal.dbmigration.migration.CreateTable;
011import io.ebeaninternal.dbmigration.migration.DropColumn;
012import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
013import io.ebeaninternal.dbmigration.migration.DropTable;
014import io.ebeaninternal.dbmigration.migration.ForeignKey;
015import io.ebeaninternal.dbmigration.migration.RenameColumn;
016import io.ebeaninternal.dbmigration.migration.UniqueConstraint;
017import io.ebeaninternal.server.deploy.BeanDescriptor;
018import io.ebeaninternal.server.deploy.BeanProperty;
019import io.ebeaninternal.server.deploy.IdentityMode;
020import io.ebeaninternal.server.deploy.PartitionMeta;
021import io.ebeaninternal.server.deploy.TablespaceMeta;
022
023import org.slf4j.Logger;
024import org.slf4j.LoggerFactory;
025
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.HashSet;
029import java.util.LinkedHashMap;
030import java.util.List;
031import java.util.Map;
032import java.util.Objects;
033import java.util.Set;
034
035import static io.ebeaninternal.dbmigration.ddlgeneration.platform.SplitColumns.split;
036import static io.ebeaninternal.dbmigration.model.MTableIdentity.fromCreateTable;
037import static io.ebeaninternal.dbmigration.model.MTableIdentity.toCreateTable;
038
039/**
040 * Holds the logical model for a given Table and everything associated to it.
041 * <p>
042 * This effectively represents a table, its columns and all associated
043 * constraints, foreign keys and indexes.
044 * <p>
045 * Migrations can be applied to this such that it represents the state
046 * of a given table after various migrations have been applied.
047 * <p>
048 * This table model can also be derived from the EbeanServer bean descriptor
049 * and associated properties.
050 */
051public class MTable {
052
053  private static final Logger logger = LoggerFactory.getLogger(MTable.class);
054
055  private final String name;
056  private MTable draftTable;
057  /**
058   * Marked true for draft tables. These need to have their FK references adjusted
059   * after all the draft tables have been identified.
060   */
061  private boolean draft;
062  private PartitionMeta partitionMeta;
063  private TablespaceMeta tablespaceMeta;
064  private String pkName;
065  private String comment;
066  private String storageEngine;
067  private IdentityMode identityMode;
068  private boolean withHistory;
069  private final Map<String, MColumn> columns = new LinkedHashMap<>();
070
071  /**
072   * Compound unique constraints.
073   */
074  private final List<MCompoundUniqueConstraint> uniqueConstraints = new ArrayList<>();
075
076  /**
077   * Compound foreign keys.
078   */
079  private final List<MCompoundForeignKey> compoundKeys = new ArrayList<>();
080
081  /**
082   * Column name for the 'When created' column. This can be used for the initial effective start date when adding
083   * history to an existing table and maps to a @WhenCreated or @CreatedTimestamp column.
084   */
085  private String whenCreatedColumn;
086
087  /**
088   * Temporary - holds addColumn settings.
089   */
090  private AddColumn addColumn;
091
092  private final List<String> droppedColumns = new ArrayList<>();
093
094  public MTable(BeanDescriptor<?> descriptor) {
095    this.name = descriptor.baseTable();
096    this.identityMode = descriptor.identityMode();
097    this.storageEngine = descriptor.storageEngine();
098    this.partitionMeta = descriptor.partitionMeta();
099    this.tablespaceMeta = descriptor.tablespaceMeta();
100    this.comment = descriptor.dbComment();
101    if (descriptor.isHistorySupport()) {
102      withHistory = true;
103      BeanProperty whenCreated = descriptor.whenCreatedProperty();
104      if (whenCreated != null) {
105        whenCreatedColumn = whenCreated.dbColumn();
106      }
107    }
108  }
109
110  /**
111   * Constructor for test cases only!
112   */
113  @Deprecated
114  public MTable(String name) {
115    this(name, null, null);
116  }
117
118  /**
119   * Construct for element collection or intersection table. They have same table space and storage engine.
120   */
121  public MTable(String name, BeanDescriptor<?> descriptor) {
122    this(name, descriptor.tablespaceMeta(), descriptor.storageEngine());
123  }
124  
125  /**
126   * Constructor for dependant tables (draft/element collection or intersection).
127   */
128  private MTable(String name, TablespaceMeta tablespaceMeta, String storageEngine) {
129    this.name = name;
130    this.identityMode = IdentityMode.NONE;
131    this.tablespaceMeta = tablespaceMeta;
132    this.storageEngine = storageEngine;
133  }
134
135  /**
136   * Create a copy of this table structure as a 'draft' table.
137   * <p>
138   * Note that both tables contain @DraftOnly MColumns and these are filtered out
139   * later when creating the CreateTable object.
140   */
141  public MTable createDraftTable() {
142    draftTable = new MTable(name + "_draft", this.tablespaceMeta, this.storageEngine);
143    draftTable.draft = true;
144    draftTable.whenCreatedColumn = whenCreatedColumn;
145    // compoundKeys
146    // compoundUniqueConstraints
147    draftTable.identityMode = identityMode;
148    for (MColumn col : allColumns()) {
149      draftTable.addColumn(col.copyForDraft());
150    }
151    return draftTable;
152  }
153
154  /**
155   * Construct for migration.
156   */
157  public MTable(CreateTable createTable) {
158    this.name = createTable.getName();
159    this.pkName = createTable.getPkName();
160    this.comment = createTable.getComment();
161    this.storageEngine = createTable.getStorageEngine();
162    if (createTable.getTablespace() != null) {
163      this.tablespaceMeta = new TablespaceMeta(createTable.getTablespace(),
164          createTable.getIndexTablespace() != null ? createTable.getIndexTablespace() : createTable.getTablespace(),
165          createTable.getLobTablespace() != null ? createTable.getLobTablespace() : createTable.getTablespace());
166    } else {
167      this.tablespaceMeta = null;
168    }
169    this.withHistory = Boolean.TRUE.equals(createTable.isWithHistory());
170    this.draft = Boolean.TRUE.equals(createTable.isDraft());
171    this.identityMode = fromCreateTable(createTable);
172    List<Column> cols = createTable.getColumn();
173    for (Column column : cols) {
174      addColumn(column);
175    }
176    for (UniqueConstraint uq : createTable.getUniqueConstraint()) {
177      uniqueConstraints.add(new MCompoundUniqueConstraint(uq));
178    }
179
180    for (ForeignKey fk : createTable.getForeignKey()) {
181      if (DdlHelp.isDropForeignKey(fk.getColumnNames())) {
182        removeForeignKey(fk.getName());
183      } else {
184        addForeignKey(fk.getName(), fk.getRefTableName(), fk.getIndexName(), fk.getColumnNames(), fk.getRefColumnNames());
185      }
186    }
187  }
188
189  public void addForeignKey(String name, String refTableName, String indexName, String columnNames, String refColumnNames) {
190    MCompoundForeignKey foreignKey = new MCompoundForeignKey(name, refTableName, indexName);
191    String[] cols = split(columnNames);
192    String[] refCols = split(refColumnNames);
193    for (int i = 0; i < cols.length && i < refCols.length; i++) {
194      foreignKey.addColumnPair(cols[i], refCols[i]);
195    }
196    addForeignKey(foreignKey);
197  }
198
199  /**
200   * Return the DropTable migration for this table.
201   */
202  public DropTable dropTable() {
203    DropTable dropTable = new DropTable();
204    dropTable.setName(name);
205    // we must add pk col name & sequence name, as we have to delete the sequence also.
206    if (identityMode.isDatabaseIdentity()) {
207      String pkCol = null;
208      for (MColumn column : columns.values()) {
209        if (column.isPrimaryKey()) {
210          if (pkCol == null) {
211            pkCol = column.getName();
212          } else { // multiple pk cols -> no sequence
213            pkCol = null;
214            break;
215          }
216        }
217      }
218      if (pkCol != null) {
219        dropTable.setSequenceCol(pkCol);
220        dropTable.setSequenceName(identityMode.getSequenceName());
221      }
222    }
223    return dropTable;
224  }
225
226  /**
227   * Return the CreateTable migration for this table.
228   */
229  public CreateTable createTable() {
230    CreateTable createTable = new CreateTable();
231    createTable.setName(name);
232    createTable.setPkName(pkName);
233    createTable.setComment(comment);
234    if (partitionMeta != null) {
235      createTable.setPartitionMode(partitionMeta.getMode().name());
236      createTable.setPartitionColumn(partitionMeta.getProperty());
237    }
238    createTable.setStorageEngine(storageEngine);
239    if (tablespaceMeta != null) {
240      createTable.setTablespace(tablespaceMeta.getTablespaceName());
241      createTable.setIndexTablespace(tablespaceMeta.getIndexTablespace());
242      createTable.setLobTablespace(tablespaceMeta.getLobTablespace());
243    }
244    toCreateTable(identityMode, createTable);
245    if (withHistory) {
246      createTable.setWithHistory(Boolean.TRUE);
247    }
248    if (draft) {
249      createTable.setDraft(Boolean.TRUE);
250    }
251    for (MColumn column : allColumns()) {
252      // filter out draftOnly columns from the base table
253      if (draft || !column.isDraftOnly()) {
254        createTable.getColumn().add(column.createColumn());
255      }
256    }
257    for (MCompoundForeignKey compoundKey : compoundKeys) {
258      createTable.getForeignKey().add(compoundKey.createForeignKey());
259    }
260    for (MCompoundUniqueConstraint constraint : uniqueConstraints) {
261      createTable.getUniqueConstraint().add(constraint.getUniqueConstraint());
262    }
263    return createTable;
264  }
265
266  /**
267   * Compare to another version of the same table to perform a diff.
268   */
269  public void compare(ModelDiff modelDiff, MTable newTable) {
270    if (withHistory != newTable.withHistory) {
271      if (withHistory) {
272        DropHistoryTable dropHistoryTable = new DropHistoryTable();
273        dropHistoryTable.setBaseTable(name);
274        modelDiff.addDropHistoryTable(dropHistoryTable);
275
276      } else {
277        AddHistoryTable addHistoryTable = new AddHistoryTable();
278        addHistoryTable.setBaseTable(name);
279        modelDiff.addAddHistoryTable(addHistoryTable);
280      }
281    }
282
283    compareColumns(modelDiff, newTable);
284
285    if (MColumn.different(comment, newTable.comment)) {
286      AddTableComment addTableComment = new AddTableComment();
287      addTableComment.setName(name);
288      if (newTable.comment == null) {
289        addTableComment.setComment(DdlHelp.DROP_COMMENT);
290      } else {
291        addTableComment.setComment(newTable.comment);
292      }
293      modelDiff.addTableComment(addTableComment);
294    }
295
296    compareCompoundKeys(modelDiff, newTable);
297    compareUniqueKeys(modelDiff, newTable);
298    compareTableAttrs(modelDiff, newTable);
299  
300  }
301
302  private void compareColumns(ModelDiff modelDiff, MTable newTable) {
303    addColumn = null;
304    Map<String, MColumn> newColumnMap = newTable.getColumns();
305
306    // compare newColumns to existing columns (look for new and diff columns)
307    for (MColumn newColumn : newColumnMap.values()) {
308      MColumn localColumn = getColumn(newColumn.getName());
309      if (localColumn == null) {
310        // can ignore if draftOnly column and non-draft table
311        if (!newColumn.isDraftOnly() || draft) {
312          diffNewColumn(newColumn, newTable);
313        }
314      } else {
315        localColumn.compare(modelDiff, this, newColumn);
316      }
317    }
318
319    // compare existing columns (look for dropped columns)
320    for (MColumn existingColumn : allColumns()) {
321      MColumn newColumn = newColumnMap.get(existingColumn.getName());
322      if (newColumn == null) {
323        diffDropColumn(modelDiff, existingColumn);
324      } else if (newColumn.isDraftOnly() && !draft) {
325        // effectively a drop column (draft only column on a non-draft table)
326        logger.trace("... drop column {} from table {} as now draftOnly", newColumn.getName(), name);
327        diffDropColumn(modelDiff, existingColumn);
328      }
329    }
330
331    if (addColumn != null) {
332      modelDiff.addAddColumn(addColumn);
333    }
334  }
335
336
337  private void compareCompoundKeys(ModelDiff modelDiff, MTable newTable) {
338    List<MCompoundForeignKey> newKeys = new ArrayList<>(newTable.getCompoundKeys());
339    List<MCompoundForeignKey> currentKeys = new ArrayList<>(getCompoundKeys());
340
341    // remove keys that have not changed
342    currentKeys.removeAll(newTable.getCompoundKeys());
343    newKeys.removeAll(getCompoundKeys());
344
345    for (MCompoundForeignKey currentKey : currentKeys) {
346      modelDiff.addAlterForeignKey(currentKey.dropForeignKey(name));
347    }
348
349    for (MCompoundForeignKey newKey : newKeys) {
350      modelDiff.addAlterForeignKey(newKey.addForeignKey(name));
351    }
352  }
353
354  private void compareUniqueKeys(ModelDiff modelDiff, MTable newTable) {
355    List<MCompoundUniqueConstraint> newKeys = new ArrayList<>(newTable.getUniqueConstraints());
356    List<MCompoundUniqueConstraint> currentKeys = new ArrayList<>(getUniqueConstraints());
357
358    // remove keys that have not changed
359    currentKeys.removeAll(newTable.getUniqueConstraints());
360    newKeys.removeAll(getUniqueConstraints());
361
362    for (MCompoundUniqueConstraint currentKey : currentKeys) {
363      modelDiff.addUniqueConstraint(currentKey.dropUniqueConstraint(name));
364    }
365    for (MCompoundUniqueConstraint newKey : newKeys) {
366      modelDiff.addUniqueConstraint(newKey.addUniqueConstraint(name));
367    }
368  }
369  
370  private void compareTableAttrs(ModelDiff modelDiff, MTable newTable) {
371    AlterTable alterTable = new AlterTable();
372    alterTable.setName(newTable.getName());
373    boolean altered = false;
374
375    if (!Objects.equals(tablespaceMeta, newTable.getTablespaceMeta())) {
376      if (newTable.getTablespaceMeta() == null) {
377        alterTable.setTablespace(DdlHelp.TABLESPACE_DEFAULT);
378        alterTable.setIndexTablespace(DdlHelp.TABLESPACE_DEFAULT);
379        alterTable.setLobTablespace(DdlHelp.TABLESPACE_DEFAULT);
380      } else {
381        alterTable.setTablespace(newTable.getTablespaceMeta().getTablespaceName());
382        alterTable.setIndexTablespace(newTable.getTablespaceMeta().getIndexTablespace());
383        alterTable.setLobTablespace(newTable.getTablespaceMeta().getLobTablespace());
384      }
385      altered = true;
386    }
387
388    if (altered) {
389      modelDiff.addAlterTable(alterTable);
390    }
391  }
392
393  /**
394   * Apply AddColumn migration.
395   */
396  public void apply(AddColumn addColumn) {
397    checkTableName(addColumn.getTableName());
398    for (Column column : addColumn.getColumn()) {
399      addColumn(column);
400    }
401  }
402
403  /**
404   * Apply AddColumn migration.
405   */
406  public void apply(AlterColumn alterColumn) {
407    checkTableName(alterColumn.getTableName());
408    String columnName = alterColumn.getColumnName();
409    MColumn existingColumn = getColumn(columnName);
410    if (existingColumn == null) {
411      throw new IllegalStateException("Column [" + columnName + "] does not exist for AlterColumn change?");
412    }
413    existingColumn.apply(alterColumn);
414  }
415
416  /**
417   * Apply DropColumn migration.
418   */
419  public void apply(DropColumn dropColumn) {
420    checkTableName(dropColumn.getTableName());
421    MColumn removed = columns.remove(dropColumn.getColumnName());
422    if (removed == null) {
423      throw new IllegalStateException("Column [" + dropColumn.getColumnName() + "] does not exist for DropColumn change on table [" + dropColumn.getTableName() + "]?");
424    }
425  }
426
427  public void apply(RenameColumn renameColumn) {
428    checkTableName(renameColumn.getTableName());
429    MColumn column = columns.remove(renameColumn.getOldName());
430    if (column == null) {
431      throw new IllegalStateException("Column [" + renameColumn.getOldName() + "] does not exist for RenameColumn change on table [" + renameColumn.getTableName() + "]?");
432    }
433    addColumn(column.rename(renameColumn.getNewName()));
434  }
435
436  public String getName() {
437    return name;
438  }
439
440  public String getSchema() {
441    int pos = name.indexOf('.');
442    return pos == -1 ? null : name.substring(0, pos);
443  }
444
445  /**
446   * Return true if this table is a 'Draft' table.
447   */
448  public boolean isDraft() {
449    return draft;
450  }
451
452  /**
453   * Return true if this table is partitioned.
454   */
455  public boolean isPartitioned() {
456    return partitionMeta != null;
457  }
458
459  /**
460   * Return the partition meta for this table.
461   */
462  public PartitionMeta getPartitionMeta() {
463    return partitionMeta;
464  }
465
466  public String getPkName() {
467    return pkName;
468  }
469
470  public void setPkName(String pkName) {
471    this.pkName = pkName;
472  }
473
474  public String getComment() {
475    return comment;
476  }
477
478  public void setComment(String comment) {
479    this.comment = comment;
480  }
481  
482  public void setTablespaceMeta(TablespaceMeta tablespaceMeta) {
483    this.tablespaceMeta = tablespaceMeta;
484  }
485  
486  public TablespaceMeta getTablespaceMeta() {
487    return tablespaceMeta;
488  }
489
490  public boolean isWithHistory() {
491    return withHistory;
492  }
493
494  public MTable setWithHistory(boolean withHistory) {
495    this.withHistory = withHistory;
496    return this;
497  }
498
499  public List<String> allHistoryColumns(boolean includeDropped) {
500    List<String> columnNames = new ArrayList<>(columns.size());
501    for (MColumn column : columns.values()) {
502      if (column.isIncludeInHistory()) {
503        columnNames.add(column.getName());
504      }
505    }
506    if (includeDropped && !droppedColumns.isEmpty()) {
507      columnNames.addAll(droppedColumns);
508    }
509    return columnNames;
510  }
511
512  /**
513   * Returns true, if there are pending dropped columns.
514   */
515  public boolean hasDroppedColumns() {
516    return !droppedColumns.isEmpty();
517  }
518
519  /**
520   * Return all the columns (excluding columns marked as dropped).
521   */
522  public Collection<MColumn> allColumns() {
523    return columns.values();
524  }
525
526  /**
527   * Return the column by name.
528   */
529  public MColumn getColumn(String name) {
530    return columns.get(name);
531  }
532
533  private Map<String, MColumn> getColumns() {
534    return columns;
535  }
536
537  public List<MCompoundUniqueConstraint> getUniqueConstraints() {
538    return uniqueConstraints;
539  }
540
541  public List<MCompoundForeignKey> getCompoundKeys() {
542    return compoundKeys;
543  }
544
545  public String getWhenCreatedColumn() {
546    return whenCreatedColumn;
547  }
548
549  /**
550   * Return the list of columns that make the primary key.
551   */
552  public List<MColumn> primaryKeyColumns() {
553    List<MColumn> pk = new ArrayList<>(3);
554    for (MColumn column : allColumns()) {
555      if (column.isPrimaryKey()) {
556        pk.add(column);
557      }
558    }
559    return pk;
560  }
561
562  /**
563   * Return the primary key column if it is a simple primary key.
564   */
565  public String singlePrimaryKey() {
566    List<MColumn> columns = primaryKeyColumns();
567    if (columns.size() == 1) {
568      return columns.get(0).getName();
569    }
570    return null;
571  }
572
573  private void checkTableName(String tableName) {
574    if (!name.equals(tableName)) {
575      throw new IllegalArgumentException("addColumn tableName [" + tableName + "] does not match [" + name + "]");
576    }
577  }
578
579  /**
580   * Add a column via migration data.
581   */
582  private void addColumn(Column column) {
583    columns.put(column.getName(), new MColumn(column));
584  }
585
586  /**
587   * Add a model column (typically from EbeanServer meta data).
588   */
589  public void addColumn(MColumn column) {
590    columns.put(column.getName(), column);
591  }
592
593  /**
594   * Add a unique constraint.
595   */
596  public void addUniqueConstraint(MCompoundUniqueConstraint uniqueConstraint) {
597    uniqueConstraints.add(uniqueConstraint);
598  }
599
600  /**
601   * Add a compound foreign key.
602   */
603  public void addForeignKey(MCompoundForeignKey compoundKey) {
604    compoundKeys.add(compoundKey);
605  }
606
607  /**
608   * Add a column checking if it already exists and if so return the existing column.
609   * Sometimes the case for a primaryKey that is also a foreign key.
610   */
611  public MColumn addColumn(String dbCol, String columnDefn, boolean notnull) {
612    MColumn existingColumn = getColumn(dbCol);
613    if (existingColumn != null) {
614      if (notnull) {
615        existingColumn.setNotnull(true);
616      }
617      return existingColumn;
618    }
619
620    MColumn newCol = new MColumn(dbCol, columnDefn, notnull);
621    addColumn(newCol);
622    return newCol;
623  }
624
625  public MColumn addColumnScalar(String dbColumn, String columnDefn) {
626    MColumn existingColumn = getColumn(dbColumn);
627    if (existingColumn != null) {
628      return existingColumn;
629    }
630    MColumn newCol = new MColumn(dbColumn, columnDefn);
631    addColumn(newCol);
632    return newCol;
633  }
634
635  /**
636   * Add a 'new column' to the AddColumn migration object.
637   */
638  private void diffNewColumn(MColumn newColumn, MTable newTable) {
639    if (addColumn == null) {
640      addColumn = new AddColumn();
641      addColumn.setTableName(name);
642      if (newTable.isWithHistory()) {
643        // These addColumns need to occur on the history
644        // table as well as the base table
645        addColumn.setWithHistory(Boolean.TRUE);
646      }
647    }
648
649    addColumn.getColumn().add(newColumn.createColumn());
650  }
651
652  /**
653   * Add a 'drop column' to the diff.
654   */
655  private void diffDropColumn(ModelDiff modelDiff, MColumn existingColumn) {
656    DropColumn dropColumn = new DropColumn();
657    dropColumn.setTableName(name);
658    dropColumn.setColumnName(existingColumn.getName());
659    if (withHistory) {
660      // These dropColumns should occur on the history
661      // table as well as the base table
662      dropColumn.setWithHistory(Boolean.TRUE);
663    }
664    modelDiff.addDropColumn(dropColumn);
665  }
666
667  /**
668   * Register a pending un-applied drop column.
669   * <p>
670   * This means this column still needs to be included in history views/triggers etc even
671   * though it is not part of the current model.
672   */
673  public void registerPendingDropColumn(String columnName) {
674    droppedColumns.add(columnName);
675  }
676
677  /**
678   * Check if there are duplicate foreign keys.
679   * <p>
680   * This can occur when an ManyToMany relates back to itself.
681   * </p>
682   */
683  public void checkDuplicateForeignKeys() {
684    if (hasDuplicateForeignKeys()) {
685      int counter = 1;
686      for (MCompoundForeignKey fk : compoundKeys) {
687        fk.addNameSuffix(counter++);
688      }
689    }
690  }
691
692  /**
693   * Return true if the foreign key names are not unique.
694   */
695  private boolean hasDuplicateForeignKeys() {
696    Set<String> fkNames = new HashSet<>();
697    for (MCompoundForeignKey fk : compoundKeys) {
698      if (!fkNames.add(fk.getName())) {
699        return true;
700      }
701    }
702    return false;
703  }
704
705  /**
706   * Adjust the references (FK) if it should relate to a draft table.
707   */
708  public void adjustReferences(ModelContainer modelContainer) {
709    Collection<MColumn> cols = allColumns();
710    for (MColumn col : cols) {
711      String references = col.getReferences();
712      if (references != null) {
713        String baseTable = extractBaseTable(references);
714        MTable refBaseTable = modelContainer.getTable(baseTable);
715        if (refBaseTable.draftTable != null) {
716          // change references to another associated 'draft' table
717          String newReferences = deriveReferences(references, refBaseTable.draftTable.getName());
718          col.setReferences(newReferences);
719        }
720      }
721    }
722  }
723
724  /**
725   * Return the base table name from references (table.column).
726   */
727  private String extractBaseTable(String references) {
728    int lastDot = references.lastIndexOf('.');
729    return references.substring(0, lastDot);
730  }
731
732  /**
733   * Return the new references using the given draftTableName.
734   * (The referenced column is the same as before).
735   */
736  private String deriveReferences(String references, String draftTableName) {
737    int lastDot = references.lastIndexOf('.');
738    return draftTableName + "." + references.substring(lastDot + 1);
739  }
740
741  /**
742   * This method adds information which columns are nullable or not to the compound indices.
743   */
744  public void updateCompoundIndices() {
745    for (MCompoundUniqueConstraint uniq : uniqueConstraints) {
746      List<String> nullableColumns = new ArrayList<>();
747      for (String columnName : uniq.getColumns()) {
748        MColumn col = getColumn(columnName);
749        if (col != null && !col.isNotnull()) {
750          nullableColumns.add(columnName);
751        }
752      }
753      uniq.setNullableColumns(nullableColumns.toArray(new String[0]));
754    }
755  }
756
757  public void removeForeignKey(String name) {
758    compoundKeys.removeIf(fk -> fk.getName().equals(name));
759  }
760
761  /**
762   * Clear the indexes on the foreign keys as they are covered by unique constraints.
763   */
764  public void clearForeignKeyIndexes() {
765    for (MCompoundForeignKey compoundKey : compoundKeys) {
766      compoundKey.setIndexName(null);
767    }
768  }
769
770  /**
771   * Clear foreign key as this element collection table logically references
772   * back to multiple tables.
773   */
774  public MIndex setReusedElementCollection() {
775    MIndex index = null;
776    for (MColumn column : columns.values()) {
777      final String references = column.getReferences();
778      if (references != null) {
779        index = new MIndex(column.getForeignKeyIndex(), name, column.getName());
780        column.clearForeignKey();
781      }
782    }
783    return index;
784  }
785}