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}