001package io.ebeaninternal.dbmigration; 002 003import io.ebean.DB; 004import io.ebean.Database; 005import io.ebean.annotation.Platform; 006import io.ebean.config.DatabaseConfig; 007import io.ebean.config.DbConstraintNaming; 008import io.ebean.config.PlatformConfig; 009import io.ebean.config.PropertiesWrapper; 010import io.ebean.config.dbplatform.DatabasePlatform; 011import io.ebean.config.dbplatform.clickhouse.ClickHousePlatform; 012import io.ebean.config.dbplatform.cockroach.CockroachPlatform; 013import io.ebean.config.dbplatform.db2.DB2Platform; 014import io.ebean.config.dbplatform.h2.H2Platform; 015import io.ebean.config.dbplatform.hana.HanaPlatform; 016import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform; 017import io.ebean.config.dbplatform.mariadb.MariaDbPlatform; 018import io.ebean.config.dbplatform.mysql.MySql55Platform; 019import io.ebean.config.dbplatform.mysql.MySqlPlatform; 020import io.ebean.config.dbplatform.nuodb.NuoDbPlatform; 021import io.ebean.config.dbplatform.oracle.Oracle11Platform; 022import io.ebean.config.dbplatform.oracle.OraclePlatform; 023import io.ebean.config.dbplatform.postgres.Postgres9Platform; 024import io.ebean.config.dbplatform.postgres.PostgresPlatform; 025import io.ebean.config.dbplatform.sqlanywhere.SqlAnywherePlatform; 026import io.ebean.config.dbplatform.sqlite.SQLitePlatform; 027import io.ebean.config.dbplatform.sqlserver.SqlServer16Platform; 028import io.ebean.config.dbplatform.sqlserver.SqlServer17Platform; 029import io.ebean.dbmigration.DbMigration; 030import io.ebeaninternal.api.DbOffline; 031import io.ebeaninternal.api.SpiEbeanServer; 032import io.ebeaninternal.dbmigration.ddlgeneration.DdlOptions; 033import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite; 034import io.ebeaninternal.dbmigration.migration.Migration; 035import io.ebeaninternal.dbmigration.migrationreader.MigrationXmlWriter; 036import io.ebeaninternal.dbmigration.model.CurrentModel; 037import io.ebeaninternal.dbmigration.model.MConfiguration; 038import io.ebeaninternal.dbmigration.model.MigrationModel; 039import io.ebeaninternal.dbmigration.model.ModelContainer; 040import io.ebeaninternal.dbmigration.model.ModelDiff; 041import io.ebeaninternal.dbmigration.model.PlatformDdlWriter; 042import io.ebeaninternal.extraddl.model.DdlScript; 043import io.ebeaninternal.extraddl.model.ExtraDdl; 044import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader; 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047 048import java.io.File; 049import java.io.FileWriter; 050import java.io.IOException; 051import java.util.ArrayList; 052import java.util.List; 053import java.util.Properties; 054 055import static io.ebeaninternal.api.PlatformMatch.matchPlatform; 056 057/** 058 * Generates DB Migration xml and sql scripts. 059 * <p> 060 * Reads the prior migrations and compares with the current model of the EbeanServer 061 * and generates a migration 'diff' in the form of xml document with the logical schema 062 * changes and a series of sql scripts to apply, rollback the applied changes if necessary 063 * and drop objects (drop tables, drop columns). 064 * </p> 065 * <p> 066 * This does not run the migration or ddl scripts but just generates them. 067 * </p> 068 * <pre>{@code 069 * 070 * DbMigration migration = DbMigration.create(); 071 * migration.setPathToResources("src/main/resources"); 072 * migration.setPlatform(Platform.POSTGRES); 073 * 074 * migration.generateMigration(); 075 * 076 * }</pre> 077 */ 078public class DefaultDbMigration implements DbMigration { 079 080 protected static final Logger logger = LoggerFactory.getLogger("io.ebean.GenerateMigration"); 081 private static final String initialVersion = "1.0"; 082 private static final String GENERATED_COMMENT = "THIS IS A GENERATED FILE - DO NOT MODIFY"; 083 084 protected final boolean online; 085 private boolean logToSystemOut = true; 086 protected SpiEbeanServer server; 087 protected String pathToResources = "src/main/resources"; 088 089 protected String migrationPath = "dbmigration"; 090 protected String migrationInitPath = "dbinit"; 091 protected String modelPath = "model"; 092 protected String modelSuffix = ".model.xml"; 093 094 protected DatabasePlatform databasePlatform; 095 private boolean vanillaPlatform; 096 protected List<Pair> platforms = new ArrayList<>(); 097 protected DatabaseConfig databaseConfig; 098 protected DbConstraintNaming constraintNaming; 099 protected Boolean strictMode; 100 protected Boolean includeGeneratedFileComment; 101 protected String header; 102 protected String applyPrefix = ""; 103 protected String version; 104 protected String name; 105 protected String generatePendingDrop; 106 private boolean addForeignKeySkipCheck; 107 private int lockTimeoutSeconds; 108 protected boolean includeBuiltInPartitioning = true; 109 protected boolean includeIndex; 110 111 /** 112 * Create for offline migration generation. 113 */ 114 public DefaultDbMigration() { 115 this.online = false; 116 } 117 118 @Override 119 public void setPathToResources(String pathToResources) { 120 this.pathToResources = pathToResources; 121 } 122 123 @Override 124 public void setMigrationPath(String migrationPath) { 125 this.migrationPath = migrationPath; 126 } 127 128 @Override 129 public void setServer(Database database) { 130 this.server = (SpiEbeanServer) database; 131 setServerConfig(server.config()); 132 } 133 134 @Override 135 public void setServerConfig(DatabaseConfig config) { 136 if (this.databaseConfig == null) { 137 this.databaseConfig = config; 138 } 139 if (constraintNaming == null) { 140 this.constraintNaming = databaseConfig.getConstraintNaming(); 141 } 142 Properties properties = config.getProperties(); 143 if (properties != null) { 144 PropertiesWrapper props = new PropertiesWrapper("ebean", config.getName(), properties, null); 145 migrationPath = props.get("migration.migrationPath", migrationPath); 146 migrationInitPath = props.get("migration.migrationInitPath", migrationInitPath); 147 pathToResources = props.get("migration.pathToResources", pathToResources); 148 } 149 } 150 151 @Override 152 public void setStrictMode(boolean strictMode) { 153 this.strictMode = strictMode; 154 } 155 156 @Override 157 public void setApplyPrefix(String applyPrefix) { 158 this.applyPrefix = applyPrefix; 159 } 160 161 @Override 162 public void setVersion(String version) { 163 this.version = version; 164 } 165 166 @Override 167 public void setName(String name) { 168 this.name = name; 169 } 170 171 @Override 172 public void setAddForeignKeySkipCheck(boolean addForeignKeySkipCheck) { 173 this.addForeignKeySkipCheck = addForeignKeySkipCheck; 174 } 175 176 @Override 177 public void setLockTimeout(int seconds) { 178 this.lockTimeoutSeconds = seconds; 179 } 180 181 @Override 182 public void setGeneratePendingDrop(String generatePendingDrop) { 183 this.generatePendingDrop = generatePendingDrop; 184 } 185 186 @Override 187 public void setIncludeIndex(boolean includeIndex) { 188 this.includeIndex = includeIndex; 189 } 190 191 @Override 192 public void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment) { 193 this.includeGeneratedFileComment = includeGeneratedFileComment; 194 } 195 196 @Override 197 public void setIncludeBuiltInPartitioning(boolean includeBuiltInPartitioning) { 198 this.includeBuiltInPartitioning = includeBuiltInPartitioning; 199 } 200 201 @Override 202 public void setHeader(String header) { 203 this.header = header; 204 } 205 206 /** 207 * Set the specific platform to generate DDL for. 208 * <p> 209 * If not set this defaults to the platform of the default server. 210 * </p> 211 */ 212 @Override 213 public void setPlatform(Platform platform) { 214 vanillaPlatform = true; 215 setPlatform(platform(platform)); 216 } 217 218 /** 219 * Set the specific platform to generate DDL for. 220 * <p> 221 * If not set this defaults to the platform of the default server. 222 * </p> 223 */ 224 @Override 225 public void setPlatform(DatabasePlatform databasePlatform) { 226 this.databasePlatform = databasePlatform; 227 if (!online) { 228 DbOffline.setPlatform(databasePlatform.getPlatform()); 229 } 230 } 231 232 @Override 233 public void addPlatform(Platform platform) { 234 String prefix = platform.base().name().toLowerCase(); 235 addPlatform(platform, prefix); 236 } 237 238 @Override 239 public void addPlatform(Platform platform, String prefix) { 240 platforms.add(new Pair(platform(platform), prefix)); 241 } 242 243 @Override 244 public void addDatabasePlatform(DatabasePlatform databasePlatform, String prefix) { 245 platforms.add(new Pair(databasePlatform, prefix)); 246 } 247 248 /** 249 * Generate the next migration xml file and associated apply and rollback sql scripts. 250 * <p> 251 * This does not run the migration or ddl scripts but just generates them. 252 * </p> 253 * <h3>Example: Run for a single specific platform</h3> 254 * <pre>{@code 255 * 256 * DbMigration migration = DbMigration.create(); 257 * migration.setPathToResources("src/main/resources"); 258 * migration.setPlatform(Platform.ORACLE); 259 * 260 * migration.generateMigration(); 261 * 262 * }</pre> 263 * <p> 264 * <h3>Example: Run migration generating DDL for multiple platforms</h3> 265 * <pre>{@code 266 * 267 * DbMigration migration = DbMigration.create(); 268 * migration.setPathToResources("src/main/resources"); 269 * 270 * migration.addPlatform(Platform.POSTGRES); 271 * migration.addPlatform(Platform.MYSQL); 272 * migration.addPlatform(Platform.ORACLE); 273 * 274 * migration.generateMigration(); 275 * 276 * }</pre> 277 * 278 * @return the generated migration or null 279 */ 280 @Override 281 public String generateMigration() throws IOException { 282 final String version = generateMigrationFor(false); 283 if (includeIndex) { 284 generateIndex(); 285 } 286 return version; 287 } 288 289 /** 290 * Generate the {@code idx_platform.migrations} file. 291 */ 292 private void generateIndex() throws IOException { 293 final File topDir = migrationDirectory(false); 294 if (!platforms.isEmpty()) { 295 for (Pair pair : platforms) { 296 new IndexMigration(topDir, pair).generate(); 297 } 298 } else { 299 new IndexMigration(topDir, databasePlatform).generate(); 300 } 301 } 302 303 @Override 304 public String generateInitMigration() throws IOException { 305 return generateMigrationFor(true); 306 } 307 308 private String generateMigrationFor(boolean initMigration) throws IOException { 309 if (!online) { 310 // use this flag to stop other plugins like full DDL generation 311 DbOffline.setGenerateMigration(); 312 if (databasePlatform == null && !platforms.isEmpty()) { 313 // for multiple platform generation the first platform 314 // is used to generate the "logical" model diff 315 setPlatform(platforms.get(0).platform); 316 } 317 } 318 setDefaults(); 319 if (!platforms.isEmpty()) { 320 configurePlatforms(); 321 } 322 try { 323 Request request = createRequest(initMigration); 324 if (!initMigration) { 325 // repeatable migrations 326 if (platforms.isEmpty()) { 327 generateExtraDdl(request.migrationDir, databasePlatform, request.isTablePartitioning()); 328 } else { 329 for (Pair pair : platforms) { 330 PlatformDdlWriter platformWriter = createDdlWriter(pair.platform); 331 File subPath = platformWriter.subPath(request.migrationDir, pair.prefix); 332 generateExtraDdl(subPath, pair.platform, request.isTablePartitioning()); 333 } 334 } 335 } 336 337 String pendingVersion = generatePendingDrop(); 338 if (pendingVersion != null) { 339 return generatePendingDrop(request, pendingVersion); 340 } else { 341 return generateDiff(request); 342 } 343 344 } catch (UnknownResourcePathException e) { 345 logError("ERROR - " + e.getMessage()); 346 logError("Check the working directory or change dbMigration.setPathToResources() value?"); 347 return null; 348 349 } finally { 350 if (!online) { 351 DbOffline.reset(); 352 } 353 } 354 } 355 356 /** 357 * Return the versions containing pending drops. 358 */ 359 @Override 360 public List<String> getPendingDrops() { 361 if (!online) { 362 DbOffline.setGenerateMigration(); 363 } 364 setDefaults(); 365 try { 366 return createRequest(false).getPendingDrops(); 367 } finally { 368 if (!online) { 369 DbOffline.reset(); 370 } 371 } 372 } 373 374 /** 375 * Load the configuration for each of the target platforms. 376 */ 377 private void configurePlatforms() { 378 for (Pair pair : platforms) { 379 PlatformConfig config = databaseConfig.newPlatformConfig("dbmigration.platform", pair.prefix); 380 pair.platform.configure(config); 381 } 382 } 383 384 /** 385 * Generate "repeatable" migration scripts. 386 * <p> 387 * These take scrips from extra-ddl.xml (typically views) and outputs "repeatable" 388 * migration scripts (starting with "R__") to be run by FlywayDb or Ebean's own 389 * migration runner. 390 * </p> 391 */ 392 private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform, boolean tablePartitioning) throws IOException { 393 if (dbPlatform != null) { 394 if (tablePartitioning && includeBuiltInPartitioning) { 395 generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltinTablePartitioning()); 396 } 397 generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltin()); 398 generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.read()); 399 } 400 } 401 402 private void generateExtraDdlFor(File migrationDir, DatabasePlatform dbPlatform, ExtraDdl extraDdl) throws IOException { 403 if (extraDdl != null) { 404 List<DdlScript> ddlScript = extraDdl.getDdlScript(); 405 for (DdlScript script : ddlScript) { 406 if (!script.isDrop() && matchPlatform(dbPlatform.getPlatform(), script.getPlatforms())) { 407 writeExtraDdl(migrationDir, script); 408 } 409 } 410 } 411 } 412 413 /** 414 * Write (or override) the "repeatable" migration script. 415 */ 416 private void writeExtraDdl(File migrationDir, DdlScript script) throws IOException { 417 String fullName = repeatableMigrationName(script.isInit(), script.getName()); 418 logger.debug("writing repeatable script {}", fullName); 419 File file = new File(migrationDir, fullName); 420 try (FileWriter writer = new FileWriter(file)) { 421 writer.write(script.getValue()); 422 writer.flush(); 423 } 424 } 425 426 @Override 427 public void setLogToSystemOut(boolean logToSystemOut) { 428 this.logToSystemOut = logToSystemOut; 429 } 430 431 private void logError(String message) { 432 if (logToSystemOut) { 433 System.out.println("DbMigration> " + message); 434 } else { 435 logger.error(message); 436 } 437 } 438 439 private void logInfo(String message, Object value) { 440 if (value != null) { 441 message = String.format(message, value); 442 } 443 if (logToSystemOut) { 444 System.out.println("DbMigration> " + message); 445 } else { 446 logger.info(message); 447 } 448 } 449 450 private String repeatableMigrationName(boolean init, String scriptName) { 451 StringBuilder sb = new StringBuilder(); 452 if (init) { 453 sb.append("I__"); 454 } else { 455 sb.append("R__"); 456 } 457 sb.append(scriptName.replace(' ', '_')); 458 sb.append(".sql"); 459 return sb.toString(); 460 } 461 462 /** 463 * Generate the diff migration. 464 */ 465 private String generateDiff(Request request) throws IOException { 466 List<String> pendingDrops = request.getPendingDrops(); 467 if (!pendingDrops.isEmpty()) { 468 logInfo("Pending un-applied drops in versions %s", pendingDrops); 469 } 470 Migration migration = request.createDiffMigration(); 471 if (migration == null) { 472 logInfo("no changes detected - no migration written", null); 473 return null; 474 } else { 475 // there were actually changes to write 476 return generateMigration(request, migration, null); 477 } 478 } 479 480 /** 481 * Generate the migration based on the pendingDrops from a prior version. 482 */ 483 private String generatePendingDrop(Request request, String pendingVersion) throws IOException { 484 Migration migration = request.migrationForPendingDrop(pendingVersion); 485 String version = generateMigration(request, migration, pendingVersion); 486 List<String> pendingDrops = request.getPendingDrops(); 487 if (!pendingDrops.isEmpty()) { 488 logInfo("... remaining pending un-applied drops in versions %s", pendingDrops); 489 } 490 return version; 491 } 492 493 private Request createRequest(boolean initMigration) { 494 return new Request(initMigration); 495 } 496 497 private class Request { 498 499 final boolean initMigration; 500 final File migrationDir; 501 final File modelDir; 502 final CurrentModel currentModel; 503 final ModelContainer migrated; 504 final ModelContainer current; 505 506 private Request(boolean initMigration) { 507 this.initMigration = initMigration; 508 this.currentModel = new CurrentModel(server, constraintNaming); 509 this.current = currentModel.read(); 510 this.migrationDir = migrationDirectory(initMigration); 511 if (initMigration) { 512 this.modelDir = null; 513 this.migrated = new ModelContainer(); 514 } else { 515 this.modelDir = modelDirectory(migrationDir); 516 MigrationModel migrationModel = new MigrationModel(modelDir, modelSuffix); 517 this.migrated = migrationModel.read(false); 518 } 519 } 520 521 boolean isTablePartitioning() { 522 return current.isTablePartitioning(); 523 } 524 525 /** 526 * Return the next migration version (based on existing migration versions). 527 */ 528 String nextVersion() { 529 // always read the next version using the main migration directory (not dbinit) 530 File migDirectory = migrationDirectory(false); 531 File modelDir = modelDirectory(migDirectory); 532 return LastMigration.nextVersion(migDirectory, modelDir, initMigration); 533 } 534 535 /** 536 * Return the migration for the pending drops for a given version. 537 */ 538 Migration migrationForPendingDrop(String pendingVersion) { 539 Migration migration = migrated.migrationForPendingDrop(pendingVersion); 540 // register any remaining pending drops 541 migrated.registerPendingHistoryDropColumns(current); 542 return migration; 543 } 544 545 /** 546 * Return the list of versions that have pending un-applied drops. 547 */ 548 public List<String> getPendingDrops() { 549 return migrated.getPendingDrops(); 550 } 551 552 /** 553 * Create and return the diff of the current model to the migration model. 554 */ 555 Migration createDiffMigration() { 556 ModelDiff diff = new ModelDiff(migrated); 557 diff.compareTo(current); 558 return diff.isEmpty() ? null : diff.getMigration(); 559 } 560 } 561 562 private String generateMigration(Request request, Migration dbMigration, String dropsFor) throws IOException { 563 String fullVersion = fullVersion(request.nextVersion(), dropsFor); 564 logInfo("generating migration:%s", fullVersion); 565 if (!request.initMigration && !writeMigrationXml(dbMigration, request.modelDir, fullVersion)) { 566 logError("migration already exists, not generating DDL"); 567 return null; 568 } else { 569 if (!platforms.isEmpty()) { 570 writeExtraPlatformDdl(fullVersion, request.currentModel, dbMigration, request.migrationDir); 571 572 } else if (databasePlatform != null) { 573 // writer needs the current model to provide table/column details for 574 // history ddl generation (triggers, history tables etc) 575 DdlOptions options = new DdlOptions(addForeignKeySkipCheck); 576 DdlWrite write = new DdlWrite(new MConfiguration(), request.current, options); 577 PlatformDdlWriter writer = createDdlWriter(databasePlatform); 578 writer.processMigration(dbMigration, write, request.migrationDir, fullVersion); 579 } 580 return fullVersion; 581 } 582 } 583 584 /** 585 * Return true if the next pending drop changeSet should be generated as the next migration. 586 */ 587 private String generatePendingDrop() { 588 String nextDrop = System.getProperty("ddl.migration.pendingDropsFor"); 589 if (nextDrop != null) { 590 return nextDrop; 591 } 592 return generatePendingDrop; 593 } 594 595 /** 596 * Return the full version for the migration being generated. 597 * <p> 598 * The full version can contain a comment suffix after a "__" double underscore. 599 */ 600 private String fullVersion(String nextVersion, String dropsFor) { 601 String version = version(); 602 if (version == null) { 603 version = (nextVersion != null) ? nextVersion : initialVersion; 604 } 605 606 String fullVersion = applyPrefix + version; 607 String name = name(); 608 if (name != null) { 609 fullVersion += "__" + toUnderScore(name); 610 611 } else if (dropsFor != null) { 612 fullVersion += "__" + toUnderScore("dropsFor_" + trimDropsFor(dropsFor)); 613 614 } else if (version.equals(initialVersion)) { 615 fullVersion += "__initial"; 616 } 617 return fullVersion; 618 } 619 620 String trimDropsFor(String dropsFor) { 621 if (dropsFor.startsWith("V") || dropsFor.startsWith("v")) { 622 dropsFor = dropsFor.substring(1); 623 } 624 int commentStart = dropsFor.indexOf("__"); 625 if (commentStart > -1) { 626 // trim off the trailing comment 627 dropsFor = dropsFor.substring(0, commentStart); 628 } 629 return dropsFor; 630 } 631 632 /** 633 * Replace spaces with underscores. 634 */ 635 private String toUnderScore(String name) { 636 return name.replace(' ', '_'); 637 } 638 639 /** 640 * Write any extra platform ddl. 641 */ 642 private void writeExtraPlatformDdl(String fullVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException { 643 DdlOptions options = new DdlOptions(addForeignKeySkipCheck); 644 for (Pair pair : platforms) { 645 DdlWrite platformBuffer = new DdlWrite(new MConfiguration(), currentModel.read(), options); 646 PlatformDdlWriter platformWriter = createDdlWriter(pair.platform); 647 File subPath = platformWriter.subPath(writePath, pair.prefix); 648 platformWriter.processMigration(dbMigration, platformBuffer, subPath, fullVersion); 649 } 650 } 651 652 private PlatformDdlWriter createDdlWriter(DatabasePlatform platform) { 653 return new PlatformDdlWriter(platform, databaseConfig, lockTimeoutSeconds); 654 } 655 656 /** 657 * Write the migration xml. 658 */ 659 private boolean writeMigrationXml(Migration dbMigration, File resourcePath, String fullVersion) { 660 String modelFile = fullVersion + modelSuffix; 661 File file = new File(resourcePath, modelFile); 662 if (file.exists()) { 663 return false; 664 } 665 String comment = Boolean.TRUE.equals(includeGeneratedFileComment) ? GENERATED_COMMENT : null; 666 MigrationXmlWriter xmlWriter = new MigrationXmlWriter(comment); 667 xmlWriter.write(dbMigration, file); 668 return true; 669 } 670 671 /** 672 * Set default server and platform if necessary. 673 */ 674 private void setDefaults() { 675 if (server == null) { 676 setServer(DB.getDefault()); 677 } 678 if (vanillaPlatform || databasePlatform == null) { 679 // not explicitly set so use the platform of the server 680 databasePlatform = server.databasePlatform(); 681 } 682 if (databaseConfig != null) { 683 if (strictMode != null) { 684 databaseConfig.setDdlStrictMode(strictMode); 685 } 686 if (header != null) { 687 databaseConfig.setDdlHeader(header); 688 } 689 } 690 } 691 692 /** 693 * Return the migration version (typically FlywayDb compatible). 694 * <p> 695 * Example: 1.1.1_2 696 * <p> 697 * The version is expected to be the combination of the current pom version plus 698 * a 'feature' id. The combined version must be unique and ordered to work with 699 * FlywayDb so each developer sets a unique version so that the migration script 700 * generated is unique (typically just prior to being submitted as a merge request). 701 */ 702 private String version() { 703 String envVersion = readEnvironment("ddl.migration.version"); 704 if (!isEmpty(envVersion)) { 705 return envVersion.trim(); 706 } 707 return version; 708 } 709 710 /** 711 * Return the migration name which is short description text that can be appended to 712 * the migration version to become the ddl script file name. 713 * <p> 714 * So if the name is "a foo table" then the ddl script file could be: 715 * "1.1.1_2__a-foo-table.sql" 716 * </p> 717 * <p> 718 * When the DB migration relates to a git feature (merge request) then this description text 719 * is a short description of the feature. 720 * </p> 721 */ 722 private String name() { 723 String envName = readEnvironment("ddl.migration.name"); 724 if (!isEmpty(envName)) { 725 return envName.trim(); 726 } 727 return name; 728 } 729 730 /** 731 * Return true if the string is null or empty. 732 */ 733 private boolean isEmpty(String val) { 734 return val == null || val.trim().isEmpty(); 735 } 736 737 /** 738 * Return the system or environment property. 739 */ 740 private String readEnvironment(String key) { 741 String val = System.getProperty(key); 742 if (val == null) { 743 val = System.getenv(key); 744 } 745 return val; 746 } 747 748 /** 749 * Return the main migration directory. 750 */ 751 File migrationDirectory() { 752 return migrationDirectory(false); 753 } 754 755 /** 756 * Return the file path to write the xml and sql to. 757 */ 758 File migrationDirectory(boolean initMigration) { 759 // path to src/main/resources in typical maven project 760 File resourceRootDir = new File(pathToResources); 761 if (!resourceRootDir.exists()) { 762 String msg = String.format("Error - path to resources %s does not exist. Absolute path is %s", pathToResources, resourceRootDir.getAbsolutePath()); 763 throw new UnknownResourcePathException(msg); 764 } 765 String resourcePath = migrationPath(initMigration); 766 // expect to be a path to something like - src/main/resources/dbmigration 767 File path = new File(resourceRootDir, resourcePath); 768 if (!path.exists()) { 769 if (!path.mkdirs()) { 770 logInfo("Warning - Unable to ensure migration directory exists at %s", path.getAbsolutePath()); 771 } 772 } 773 return path; 774 } 775 776 private String migrationPath(boolean initMigration) { 777 return initMigration ? migrationInitPath : migrationPath; 778 } 779 780 /** 781 * Return the model directory (relative to the migration directory). 782 */ 783 private File modelDirectory(File migrationDirectory) { 784 if (modelPath == null || modelPath.isEmpty()) { 785 return migrationDirectory; 786 } 787 File modelDir = new File(migrationDirectory, modelPath); 788 if (!modelDir.exists() && !modelDir.mkdirs()) { 789 logInfo("Warning - Unable to ensure migration model directory exists at %s", modelDir.getAbsolutePath()); 790 } 791 return modelDir; 792 } 793 794 /** 795 * Return the DatabasePlatform given the platform key. 796 */ 797 protected DatabasePlatform platform(Platform platform) { 798 switch (platform) { 799 case H2: 800 return new H2Platform(); 801 case HSQLDB: 802 return new HsqldbPlatform(); 803 case POSTGRES9: 804 return new Postgres9Platform(); 805 case POSTGRES: 806 return new PostgresPlatform(); 807 case MARIADB: 808 return new MariaDbPlatform(); 809 case MYSQL55: 810 return new MySql55Platform(); 811 case MYSQL: 812 return new MySqlPlatform(); 813 case ORACLE: 814 return new OraclePlatform(); 815 case ORACLE11: 816 return new Oracle11Platform(); 817 case SQLANYWHERE: 818 return new SqlAnywherePlatform(); 819 case SQLSERVER16: 820 return new SqlServer16Platform(); 821 case SQLSERVER17: 822 return new SqlServer17Platform(); 823 case SQLSERVER: 824 throw new IllegalArgumentException("Please choose the more specific SQLSERVER16 or SQLSERVER17 platform. Refer to issue #1340 for details"); 825 case DB2: 826 return new DB2Platform(); 827 case SQLITE: 828 return new SQLitePlatform(); 829 case HANA: 830 return new HanaPlatform(); 831 case NUODB: 832 return new NuoDbPlatform(); 833 case COCKROACH: 834 return new CockroachPlatform(); 835 case CLICKHOUSE: 836 return new ClickHousePlatform(); 837 838 case GENERIC: 839 return new DatabasePlatform(); 840 841 default: 842 throw new IllegalArgumentException("Platform missing? " + platform); 843 } 844 } 845 846 /** 847 * Holds a platform and prefix. Used to generate multiple platform specific DDL 848 * for a single migration. 849 */ 850 static class Pair { 851 852 /** 853 * The platform to generate the DDL for. 854 */ 855 final DatabasePlatform platform; 856 857 /** 858 * A prefix included into the file/resource names indicating the platform. 859 */ 860 final String prefix; 861 862 Pair(DatabasePlatform platform, String prefix) { 863 this.platform = platform; 864 this.prefix = prefix; 865 } 866 } 867 868}