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