001package io.ebeaninternal.dbmigration.model; 002 003import io.ebean.migration.MigrationVersion; 004import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp; 005import io.ebeaninternal.dbmigration.migration.AddColumn; 006import io.ebeaninternal.dbmigration.migration.AddHistoryTable; 007import io.ebeaninternal.dbmigration.migration.AddTableComment; 008import io.ebeaninternal.dbmigration.migration.AddUniqueConstraint; 009import io.ebeaninternal.dbmigration.migration.AlterColumn; 010import io.ebeaninternal.dbmigration.migration.AlterForeignKey; 011import io.ebeaninternal.dbmigration.migration.AlterTable; 012import io.ebeaninternal.dbmigration.migration.ChangeSet; 013import io.ebeaninternal.dbmigration.migration.ChangeSetType; 014import io.ebeaninternal.dbmigration.migration.CreateIndex; 015import io.ebeaninternal.dbmigration.migration.CreateTable; 016import io.ebeaninternal.dbmigration.migration.DropColumn; 017import io.ebeaninternal.dbmigration.migration.DropHistoryTable; 018import io.ebeaninternal.dbmigration.migration.DropIndex; 019import io.ebeaninternal.dbmigration.migration.DropTable; 020import io.ebeaninternal.dbmigration.migration.Migration; 021import io.ebeaninternal.dbmigration.migration.RenameColumn; 022import io.ebeaninternal.dbmigration.migration.Sql; 023import io.ebeaninternal.server.deploy.TablespaceMeta; 024 025import java.util.ArrayList; 026import java.util.Collection; 027import java.util.LinkedHashMap; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031import java.util.TreeSet; 032 033/** 034 * Holds all the tables, views, indexes etc that represent the model. 035 * <p> 036 * Migration changeSets can be applied to the model. 037 * </p> 038 */ 039public class ModelContainer { 040 041 private final Set<String> schemas = new TreeSet<>(); 042 /** 043 * All the tables in the model. 044 */ 045 private final Map<String, MTable> tables = new LinkedHashMap<>(); 046 047 /** 048 * All the non-unique non-foreign key indexes. 049 */ 050 private final Map<String, MIndex> indexes = new LinkedHashMap<>(); 051 private final PendingDrops pendingDrops = new PendingDrops(); 052 private final List<MTable> partitionedTables = new ArrayList<>(); 053 054 public ModelContainer() { 055 } 056 057 /** 058 * Return the schemas. 059 */ 060 public Set<String> getSchemas() { 061 return schemas; 062 } 063 064 /** 065 * Return true if the model contains tables that are partitioned. 066 */ 067 public boolean isTablePartitioning() { 068 return !partitionedTables.isEmpty(); 069 } 070 071 /** 072 * Return the list of partitioned tables. 073 */ 074 public List<MTable> getPartitionedTables() { 075 return partitionedTables; 076 } 077 078 /** 079 * Adjust the FK references on all the draft tables. 080 */ 081 public void adjustDraftReferences() { 082 for (MTable table : this.tables.values()) { 083 if (table.isDraft()) { 084 table.adjustReferences(this); 085 } 086 } 087 } 088 089 /** 090 * Return the map of all the tables. 091 */ 092 public Map<String, MTable> getTables() { 093 return tables; 094 } 095 096 /** 097 * Return the table by name. 098 */ 099 public MTable getTable(String tableName) { 100 return tables.get(tableName); 101 } 102 103 /** 104 * Lookup the matching index during DIFF migration processing. 105 */ 106 public MIndex getIndex(MIndex newIndex) { 107 return indexes.get(newIndex.getKey()); 108 } 109 110 public Collection<MIndex> allIndexes() { 111 return indexes.values(); 112 } 113 114 /** 115 * Return true if the index does not exist and should be dropped. 116 */ 117 public boolean dropIndex(MIndex existingIndex) { 118 return !indexes.containsKey(existingIndex.getKey()); 119 } 120 121 /** 122 * Apply a migration with associated changeSets to the model. 123 */ 124 public void apply(Migration migration, MigrationVersion version) { 125 126 List<ChangeSet> changeSets = migration.getChangeSet(); 127 for (ChangeSet changeSet : changeSets) { 128 boolean pending = changeSet.getType() == ChangeSetType.PENDING_DROPS; 129 if (pending) { 130 // un-applied drop columns etc 131 pendingDrops.add(version, changeSet); 132 133 } else if (isDropsFor(changeSet)) { 134 pendingDrops.appliedDropsFor(changeSet); 135 } 136 if (!isDropsFor(changeSet)) { 137 applyChangeSet(changeSet); 138 } 139 } 140 } 141 142 /** 143 * Return true if the changeSet contains drops for a previous PENDING_DROPS changeSet. 144 */ 145 private boolean isDropsFor(ChangeSet changeSet) { 146 return changeSet.getDropsFor() != null; 147 } 148 149 /** 150 * Apply a changeSet to the model. 151 */ 152 protected void applyChangeSet(ChangeSet changeSet) { 153 154 List<Object> changeSetChildren = changeSet.getChangeSetChildren(); 155 for (Object change : changeSetChildren) { 156 if (change instanceof CreateTable) { 157 applyChange((CreateTable) change); 158 } else if (change instanceof DropTable) { 159 applyChange((DropTable) change); 160 } else if (change instanceof AlterTable) { 161 applyChange((AlterTable) change); 162 } else if (change instanceof AlterColumn) { 163 applyChange((AlterColumn) change); 164 } else if (change instanceof AddColumn) { 165 applyChange((AddColumn) change); 166 } else if (change instanceof DropColumn) { 167 applyChange((DropColumn) change); 168 } else if (change instanceof RenameColumn) { 169 applyChange((RenameColumn) change); 170 } else if (change instanceof CreateIndex) { 171 applyChange((CreateIndex) change); 172 } else if (change instanceof DropIndex) { 173 applyChange((DropIndex) change); 174 } else if (change instanceof AddHistoryTable) { 175 applyChange((AddHistoryTable) change); 176 } else if (change instanceof DropHistoryTable) { 177 applyChange((DropHistoryTable) change); 178 } else if (change instanceof AddUniqueConstraint) { 179 applyChange((AddUniqueConstraint) change); 180 } else if (change instanceof AlterForeignKey) { 181 applyChange((AlterForeignKey) change); 182 } else if (change instanceof AddTableComment) { 183 applyChange((AddTableComment) change); 184 } else if (change instanceof Sql) { 185 // do nothing 186 } else { 187 throw new IllegalArgumentException("No rule for " + change); 188 } 189 } 190 } 191 192 /** 193 * Set the withHistory flag on the associated base table. 194 */ 195 private void applyChange(AddHistoryTable change) { 196 197 MTable table = tables.get(change.getBaseTable()); 198 if (table == null) { 199 throw new IllegalStateException("Table [" + change.getBaseTable() + "] does not exist in model?"); 200 } 201 table.setWithHistory(true); 202 } 203 204 /** 205 * Unset the withHistory flag on the associated base table. 206 */ 207 protected void applyChange(DropHistoryTable change) { 208 209 MTable table = tables.get(change.getBaseTable()); 210 if (table != null) { 211 table.setWithHistory(false); 212 } 213 } 214 215 private void applyChange(AddUniqueConstraint change) { 216 MTable table = tables.get(change.getTableName()); 217 if (table == null) { 218 throw new IllegalStateException("Table [" + change.getTableName() + "] does not exist in model?"); 219 } 220 if (DdlHelp.isDropConstraint(change.getColumnNames())) { 221 table.getUniqueConstraints().removeIf(constraint -> constraint.getName().equals(change.getConstraintName())); 222 } else { 223 table.getUniqueConstraints().add(new MCompoundUniqueConstraint(change)); 224 } 225 } 226 227 private void applyChange(AlterForeignKey change) { 228 MTable table = tables.get(change.getTableName()); 229 if (table == null) { 230 throw new IllegalStateException("Table [" + change.getName() + "] does not exist in model?"); 231 } 232 if (DdlHelp.isDropForeignKey(change.getColumnNames())) { 233 table.removeForeignKey(change.getName()); 234 } else { 235 table.addForeignKey(change.getName(), change.getRefTableName(), change.getIndexName(), change.getColumnNames(), change.getRefColumnNames()); 236 } 237 } 238 239 private void applyChange(AddTableComment change) { 240 MTable table = tables.get(change.getName()); 241 if (table == null) { 242 throw new IllegalStateException("Table [" + change.getName() + "] does not exist in model?"); 243 } 244 if (DdlHelp.isDropComment(change.getComment())) { 245 table.setComment(null); 246 } else { 247 table.setComment(change.getComment()); 248 } 249 } 250 251 /** 252 * Apply a CreateTable change to the model. 253 */ 254 protected void applyChange(CreateTable createTable) { 255 String tableName = createTable.getName(); 256 if (tables.containsKey(tableName)) { 257 throw new IllegalStateException("Table [" + tableName + "] already exists in model?"); 258 } 259 tables.put(tableName, new MTable(createTable)); 260 } 261 262 /** 263 * Apply a DropTable change to the model. 264 */ 265 protected void applyChange(DropTable dropTable) { 266 tables.remove(dropTable.getName()); 267 } 268 269 /** 270 * Apply a AlterTable change to the model. 271 */ 272 protected void applyChange(AlterTable alterTable) { 273 MTable table = getTable(alterTable.getName()); 274 if (table == null) { 275 throw new IllegalStateException("Table [" + alterTable.getName() + "] does not exist in model?"); 276 } 277 // Handle Tablespace change 278 TablespaceMeta ts = table.getTablespaceMeta(); 279 280 if (alterTable.getTablespace() != null) { 281 String currentTableSpace = DdlHelp.toTablespace(alterTable.getTablespace()); 282 String currentIndexSpace = DdlHelp.toTablespace(alterTable.getIndexTablespace()); 283 String currentLobSpace = DdlHelp.toTablespace(alterTable.getLobTablespace()); 284 285 if (currentTableSpace != null) { 286 assert currentIndexSpace != null; 287 assert currentLobSpace != null; 288 table.setTablespaceMeta(new TablespaceMeta(currentTableSpace, currentIndexSpace, currentLobSpace)); 289 } else { 290 assert currentIndexSpace == null; 291 assert currentLobSpace == null; 292 table.setTablespaceMeta(null); 293 } 294 } 295 } 296 297 /** 298 * Apply a CreateTable change to the model. 299 */ 300 protected void applyChange(CreateIndex createIndex) { 301 String indexName = createIndex.getIndexName(); 302 if (indexes.containsKey(indexName)) { 303 throw new IllegalStateException("Index [" + indexName + "] already exists in model?"); 304 } 305 MIndex index = new MIndex(createIndex); 306 indexes.put(index.getKey(), index); 307 } 308 309 /** 310 * Apply a DropTable change to the model. 311 */ 312 protected void applyChange(DropIndex dropIndex) { 313 indexes.remove(dropIndex.getIndexName()); 314 } 315 316 /** 317 * Apply a AddColumn change to the model. 318 */ 319 protected void applyChange(AddColumn addColumn) { 320 MTable table = tables.get(addColumn.getTableName()); 321 if (table == null) { 322 throw new IllegalStateException("Table [" + addColumn.getTableName() + "] does not exist in model?"); 323 } 324 table.apply(addColumn); 325 } 326 327 /** 328 * Apply a AddColumn change to the model. 329 */ 330 protected void applyChange(AlterColumn alterColumn) { 331 MTable table = tables.get(alterColumn.getTableName()); 332 if (table == null) { 333 throw new IllegalStateException("Table [" + alterColumn.getTableName() + "] does not exist in model?"); 334 } 335 table.apply(alterColumn); 336 } 337 338 /** 339 * Apply a DropColumn change to the model. 340 */ 341 protected void applyChange(DropColumn dropColumn) { 342 MTable table = tables.get(dropColumn.getTableName()); 343 if (table == null) { 344 throw new IllegalStateException("Table [" + dropColumn.getTableName() + "] does not exist in model?"); 345 } 346 table.apply(dropColumn); 347 } 348 349 protected void applyChange(RenameColumn renameColumn) { 350 MTable table = tables.get(renameColumn.getTableName()); 351 if (table == null) { 352 throw new IllegalStateException("Table [" + renameColumn.getTableName() + "] does not exist in model?"); 353 } 354 table.apply(renameColumn); 355 } 356 357 /** 358 * Add a table (typically from reading EbeanServer meta data). 359 */ 360 public MTable addTable(MTable table) { 361 if (table.isPartitioned()) { 362 partitionedTables.add(table); 363 } 364 String schema = table.getSchema(); 365 if (schema != null) { 366 schemas.add(schema); 367 } 368 return tables.put(table.getName(), table); 369 } 370 371 /** 372 * Add an element table taking into account if it is reused/references back 373 * to multiple bean types (and so can't have foreign key). 374 */ 375 public void addTableElementCollection(MTable table) { 376 final MTable reusedElementCollection = tables.get(table.getName()); 377 if (reusedElementCollection != null) { 378 final MIndex index = reusedElementCollection.setReusedElementCollection(); 379 if (index != null) { 380 indexes.put(index.getKey(), index); 381 } 382 } else { 383 if (table.isPartitioned()) { 384 partitionedTables.add(table); 385 } 386 tables.put(table.getName(), table); 387 } 388 } 389 390 /** 391 * Add an index. 392 */ 393 public void addIndex(MIndex index) { 394 indexes.put(index.getKey(), index); 395 } 396 397 /** 398 * Return the list of versions containing un-applied pending drops. 399 */ 400 public List<String> getPendingDrops() { 401 return pendingDrops.pendingDrops(); 402 } 403 404 /** 405 * Return the migration for the pending drops for a given version. 406 */ 407 public Migration migrationForPendingDrop(String pendingVersion) { 408 return pendingDrops.migrationForVersion(pendingVersion); 409 } 410 411 /** 412 * Register the drop columns on history tables that have not been applied yet. 413 */ 414 public void registerPendingHistoryDropColumns(ModelContainer newModel) { 415 pendingDrops.registerPendingHistoryDropColumns(newModel); 416 } 417 418 /** 419 * Register any pending drop columns on history tables. These columns are now not in the current 420 * logical model but we still need to include them in the history views and triggers until they 421 * are actually dropped. 422 */ 423 public void registerPendingHistoryDropColumns(ChangeSet changeSet) { 424 for (Object change : changeSet.getChangeSetChildren()) { 425 if (change instanceof DropColumn) { 426 DropColumn dropColumn = (DropColumn) change; 427 registerPendingDropColumn(dropColumn); 428 } 429 } 430 } 431 432 /** 433 * Register a drop column on a history tables that has not been applied yet. 434 */ 435 private void registerPendingDropColumn(DropColumn dropColumn) { 436 437 MTable table = getTable(dropColumn.getTableName()); 438 if (table == null) { 439 throw new IllegalArgumentException("Table [" + dropColumn.getTableName() + "] not found?"); 440 } 441 table.registerPendingDropColumn(dropColumn.getColumnName()); 442 } 443}