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