001/*- 002 * #%L 003 * HAPI FHIR Server - SQL Migration 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.migrate.tasks.api; 021 022import ca.uhn.fhir.i18n.Msg; 023import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; 024import ca.uhn.fhir.jpa.migrate.MigrationJdbcUtils; 025import ca.uhn.fhir.jpa.migrate.taskdef.AddColumnTask; 026import ca.uhn.fhir.jpa.migrate.taskdef.AddForeignKeyTask; 027import ca.uhn.fhir.jpa.migrate.taskdef.AddIdGeneratorTask; 028import ca.uhn.fhir.jpa.migrate.taskdef.AddIndexTask; 029import ca.uhn.fhir.jpa.migrate.taskdef.AddTableByColumnTask; 030import ca.uhn.fhir.jpa.migrate.taskdef.AddTableRawSqlTask; 031import ca.uhn.fhir.jpa.migrate.taskdef.BaseTableTask; 032import ca.uhn.fhir.jpa.migrate.taskdef.BaseTask; 033import ca.uhn.fhir.jpa.migrate.taskdef.ColumnTypeEnum; 034import ca.uhn.fhir.jpa.migrate.taskdef.DropColumnTask; 035import ca.uhn.fhir.jpa.migrate.taskdef.DropForeignKeyTask; 036import ca.uhn.fhir.jpa.migrate.taskdef.DropIdGeneratorTask; 037import ca.uhn.fhir.jpa.migrate.taskdef.DropIndexTask; 038import ca.uhn.fhir.jpa.migrate.taskdef.DropTableTask; 039import ca.uhn.fhir.jpa.migrate.taskdef.ExecuteRawSqlTask; 040import ca.uhn.fhir.jpa.migrate.taskdef.ExecuteTaskPrecondition; 041import ca.uhn.fhir.jpa.migrate.taskdef.InitializeSchemaTask; 042import ca.uhn.fhir.jpa.migrate.taskdef.MigrateColumBlobTypeToBinaryTypeTask; 043import ca.uhn.fhir.jpa.migrate.taskdef.MigrateColumnClobTypeToTextTypeTask; 044import ca.uhn.fhir.jpa.migrate.taskdef.MigratePostgresTextClobToBinaryClobTask; 045import ca.uhn.fhir.jpa.migrate.taskdef.ModifyColumnTask; 046import ca.uhn.fhir.jpa.migrate.taskdef.NopTask; 047import ca.uhn.fhir.jpa.migrate.taskdef.RenameColumnTask; 048import ca.uhn.fhir.jpa.migrate.taskdef.RenameIndexTask; 049import ca.uhn.fhir.jpa.migrate.taskdef.RenameTableTask; 050import org.apache.commons.lang3.Validate; 051import org.intellij.lang.annotations.Language; 052import org.slf4j.Logger; 053import org.slf4j.LoggerFactory; 054 055import java.util.Arrays; 056import java.util.Collections; 057import java.util.HashMap; 058import java.util.List; 059import java.util.Map; 060import java.util.Optional; 061import java.util.Set; 062import java.util.stream.Collectors; 063 064public class Builder { 065 private static final Logger ourLog = LoggerFactory.getLogger(Builder.class); 066 067 private final String myRelease; 068 private final BaseMigrationTasks.IAcceptsTasks mySink; 069 070 public Builder(String theRelease, BaseMigrationTasks.IAcceptsTasks theSink) { 071 myRelease = theRelease; 072 mySink = theSink; 073 } 074 075 public BuilderWithTableName onTable(String theTableName) { 076 return new BuilderWithTableName(myRelease, mySink, theTableName); 077 } 078 079 public void addTask(BaseTask theTask) { 080 mySink.addTask(theTask); 081 } 082 083 public BuilderAddTableRawSql addTableRawSql(String theVersion, String theTableName) { 084 return new BuilderAddTableRawSql(theVersion, theTableName); 085 } 086 087 public BuilderCompleteTask executeRawSql(String theVersion, @Language("SQL") String theSql) { 088 ExecuteRawSqlTask task = executeRawSqlOptional(false, theVersion, theSql); 089 return new BuilderCompleteTask(task); 090 } 091 092 public void executeRawSqlStub(String theVersion, @Language("SQL") String theSql) { 093 executeRawSqlOptional(true, theVersion, theSql); 094 } 095 096 private ExecuteRawSqlTask executeRawSqlOptional( 097 boolean theDoNothing, String theVersion, @Language("SQL") String theSql) { 098 ExecuteRawSqlTask task = new ExecuteRawSqlTask(myRelease, theVersion).addSql(theSql); 099 task.setDoNothing(theDoNothing); 100 mySink.addTask(task); 101 return task; 102 } 103 104 public InitializeSchemaTask initializeSchema( 105 String theVersion, ISchemaInitializationProvider theSchemaInitializationProvider) { 106 InitializeSchemaTask task = new InitializeSchemaTask(myRelease, theVersion, theSchemaInitializationProvider); 107 mySink.addTask(task); 108 return task; 109 } 110 111 @SuppressWarnings("unused") 112 public InitializeSchemaTask initializeSchema( 113 String theVersion, String theSchemaName, ISchemaInitializationProvider theSchemaInitializationProvider) { 114 InitializeSchemaTask task = new InitializeSchemaTask(myRelease, theVersion, theSchemaInitializationProvider); 115 task.setDescription("Initialize " + theSchemaName + " schema"); 116 mySink.addTask(task); 117 return task; 118 } 119 120 public Builder executeRawSql(String theVersion, DriverTypeEnum theDriver, @Language("SQL") String theSql) { 121 mySink.addTask(new ExecuteRawSqlTask(myRelease, theVersion).addSql(theDriver, theSql)); 122 return this; 123 } 124 125 /** 126 * Builder method to define a raw SQL execution migration that needs to take place against multiple database types, 127 * and the SQL they need to use is not equal. Provide a map of driver types to SQL statements. 128 * 129 * @param theVersion The version of the migration. 130 * @param theDriverToSql Map of driver types to SQL statements. 131 */ 132 public Builder executeRawSql(String theVersion, Map<DriverTypeEnum, String> theDriverToSql) { 133 Map<DriverTypeEnum, List<String>> singleSqlStatementMap = new HashMap<>(); 134 theDriverToSql.entrySet().stream().forEach(entry -> { 135 singleSqlStatementMap.put(entry.getKey(), Collections.singletonList(entry.getValue())); 136 }); 137 return executeRawSqls(theVersion, singleSqlStatementMap); 138 } 139 140 /** 141 * Builder method to define a raw SQL execution migration that needs to take place against multiple database types, 142 * and the SQL they need to use is not equal, and there are multiple sql commands for a given database. 143 * Provide a map of driver types to list of SQL statements. 144 * 145 * @param theVersion The version of the migration. 146 * @param theDriverToSqls Map of driver types to list of SQL statements. 147 */ 148 public Builder executeRawSqls(String theVersion, Map<DriverTypeEnum, List<String>> theDriverToSqls) { 149 ExecuteRawSqlTask executeRawSqlTask = new ExecuteRawSqlTask(myRelease, theVersion); 150 theDriverToSqls.entrySet().stream().forEach(entry -> { 151 entry.getValue().forEach(sql -> executeRawSqlTask.addSql(entry.getKey(), sql)); 152 }); 153 mySink.addTask(executeRawSqlTask); 154 return this; 155 } 156 157 // Flyway doesn't support these kinds of migrations 158 @Deprecated 159 public Builder startSectionWithMessage(String theMessage) { 160 // Do nothing 161 return this; 162 } 163 164 public BuilderAddTableByColumns addTableByColumns( 165 String theVersion, String theTableName, String... thePkColumnNames) { 166 return new BuilderAddTableByColumns( 167 myRelease, theVersion, mySink, theTableName, Arrays.asList(thePkColumnNames)); 168 } 169 170 public void addIdGenerator(String theVersion, String theGeneratorName) { 171 AddIdGeneratorTask task = new AddIdGeneratorTask(myRelease, theVersion, theGeneratorName); 172 addTask(task); 173 } 174 175 public DropIdGeneratorTask dropIdGenerator(String theVersion, String theIdGeneratorName) { 176 DropIdGeneratorTask task = new DropIdGeneratorTask(myRelease, theVersion, theIdGeneratorName); 177 addTask(task); 178 return task; 179 } 180 181 public void addNop(String theVersion) { 182 addTask(new NopTask(myRelease, theVersion)); 183 } 184 185 public static class BuilderWithTableName implements BaseMigrationTasks.IAcceptsTasks { 186 private final String myRelease; 187 private final BaseMigrationTasks.IAcceptsTasks mySink; 188 private final String myTableName; 189 private BaseTask myLastAddedTask; 190 191 public BuilderWithTableName(String theRelease, BaseMigrationTasks.IAcceptsTasks theSink, String theTableName) { 192 myRelease = theRelease; 193 mySink = theSink; 194 myTableName = theTableName; 195 } 196 197 public String getTableName() { 198 return myTableName; 199 } 200 201 public BuilderCompleteTask dropIndex(String theVersion, String theIndexName) { 202 BaseTask task = dropIndexOptional(false, theVersion, theIndexName); 203 return new BuilderCompleteTask(task); 204 } 205 206 /** 207 * Drop index without taking write lock on PG, Oracle, MSSQL. 208 */ 209 public BuilderCompleteTask dropIndexOnline(String theVersion, String theIndexName) { 210 DropIndexTask task = dropIndexOptional(false, theVersion, theIndexName); 211 task.setOnline(true); 212 return new BuilderCompleteTask(task); 213 } 214 215 public void dropIndexStub(String theVersion, String theIndexName) { 216 dropIndexOptional(true, theVersion, theIndexName); 217 } 218 219 private DropIndexTask dropIndexOptional(boolean theDoNothing, String theVersion, String theIndexName) { 220 DropIndexTask task = new DropIndexTask(myRelease, theVersion); 221 task.setIndexName(theIndexName); 222 task.setTableName(myTableName); 223 task.setDoNothing(theDoNothing); 224 addTask(task); 225 return task; 226 } 227 228 /** 229 * @deprecated Do not rename indexes - It is too hard to figure out what happened if something goes wrong 230 */ 231 @Deprecated 232 public void renameIndex(String theVersion, String theOldIndexName, String theNewIndexName) { 233 renameIndexOptional(false, theVersion, theOldIndexName, theNewIndexName); 234 } 235 236 /** 237 * @deprecated Do not rename indexes - It is too hard to figure out what happened if something goes wrong 238 */ 239 public void renameIndexStub(String theVersion, String theOldIndexName, String theNewIndexName) { 240 renameIndexOptional(true, theVersion, theOldIndexName, theNewIndexName); 241 } 242 243 private void renameIndexOptional( 244 boolean theDoNothing, String theVersion, String theOldIndexName, String theNewIndexName) { 245 RenameIndexTask task = new RenameIndexTask(myRelease, theVersion); 246 task.setOldIndexName(theOldIndexName); 247 task.setNewIndexName(theNewIndexName); 248 task.setTableName(myTableName); 249 task.setDoNothing(theDoNothing); 250 addTask(task); 251 } 252 253 public void dropThisTable(String theVersion) { 254 DropTableTask task = new DropTableTask(myRelease, theVersion); 255 task.setTableName(myTableName); 256 addTask(task); 257 } 258 259 public BuilderWithTableName.BuilderAddIndexWithName addIndex(String theVersion, String theIndexName) { 260 return new BuilderWithTableName.BuilderAddIndexWithName(theVersion, theIndexName); 261 } 262 263 public BuilderWithTableName.BuilderAddColumnWithName addColumn(String theVersion, String theColumnName) { 264 return new BuilderWithTableName.BuilderAddColumnWithName(myRelease, theVersion, theColumnName, this); 265 } 266 267 public BuilderCompleteTask dropColumn(String theVersion, String theColumnName) { 268 Validate.notBlank(theColumnName); 269 DropColumnTask task = new DropColumnTask(myRelease, theVersion); 270 task.setTableName(myTableName); 271 task.setColumnName(theColumnName); 272 addTask(task); 273 return new BuilderCompleteTask(task); 274 } 275 276 @Override 277 public void addTask(BaseTask theTask) { 278 ((BaseTableTask) theTask).setTableName(myTableName); 279 myLastAddedTask = theTask; 280 mySink.addTask(theTask); 281 } 282 283 public BuilderWithTableName.BuilderModifyColumnWithName modifyColumn(String theVersion, String theColumnName) { 284 return new BuilderWithTableName.BuilderModifyColumnWithName(theVersion, theColumnName); 285 } 286 287 public BuilderWithTableName.BuilderAddForeignKey addForeignKey(String theVersion, String theForeignKeyName) { 288 return new BuilderWithTableName.BuilderAddForeignKey(theVersion, theForeignKeyName); 289 } 290 291 public BuilderWithTableName renameColumn(String theVersion, String theOldName, String theNewName) { 292 return renameColumn(theVersion, theOldName, theNewName, false, false); 293 } 294 295 /** 296 * @param theOldName The old column name 297 * @param theNewName The new column name 298 * @param isOkayIfNeitherColumnExists Setting this to true means that it's not an error if neither column exists 299 * @param theDeleteTargetColumnFirstIfBothExist Setting this to true causes the migrator to be ok with the target column existing. It will make sure that there is no data in the column with the new name, then delete it if so in order to make room for the renamed column. If there is data it will still bomb out. 300 */ 301 public BuilderWithTableName renameColumn( 302 String theVersion, 303 String theOldName, 304 String theNewName, 305 boolean isOkayIfNeitherColumnExists, 306 boolean theDeleteTargetColumnFirstIfBothExist) { 307 RenameColumnTask task = new RenameColumnTask(myRelease, theVersion); 308 task.setTableName(myTableName); 309 task.setOldName(theOldName); 310 task.setNewName(theNewName); 311 task.setOkayIfNeitherColumnExists(isOkayIfNeitherColumnExists); 312 task.setDeleteTargetColumnFirstIfBothExist(theDeleteTargetColumnFirstIfBothExist); 313 addTask(task); 314 return this; 315 } 316 317 public Optional<BaseTask> getLastAddedTask() { 318 return Optional.ofNullable(myLastAddedTask); 319 } 320 321 /** 322 * @param theFkName the name of the foreign key 323 * @param theParentTableName the name of the table that exports the foreign key 324 */ 325 public void dropForeignKey(String theVersion, String theFkName, String theParentTableName) { 326 DropForeignKeyTask task = new DropForeignKeyTask(myRelease, theVersion); 327 task.setConstraintName(theFkName); 328 task.setTableName(getTableName()); 329 task.setParentTableName(theParentTableName); 330 addTask(task); 331 } 332 333 public BuilderCompleteTask renameTable(String theVersion, String theNewTableName) { 334 RenameTableTask task = new RenameTableTask(myRelease, theVersion, getTableName(), theNewTableName); 335 addTask(task); 336 return new BuilderCompleteTask(task); 337 } 338 339 public BuilderCompleteTask migratePostgresTextClobToBinaryClob(String theVersion, String theColumnName) { 340 MigratePostgresTextClobToBinaryClobTask task = 341 new MigratePostgresTextClobToBinaryClobTask(myRelease, theVersion); 342 task.setTableName(getTableName()); 343 task.setColumnName(theColumnName); 344 addTask(task); 345 return new BuilderCompleteTask(task); 346 } 347 348 public BuilderCompleteTask migrateBlobToBinary( 349 String theVersion, String theFromColumName, String theToColumName) { 350 MigrateColumBlobTypeToBinaryTypeTask task = new MigrateColumBlobTypeToBinaryTypeTask( 351 myRelease, theVersion, getTableName(), theFromColumName, theToColumName); 352 353 addTask(task); 354 return new BuilderCompleteTask(task); 355 } 356 357 public BuilderCompleteTask migrateClobToText( 358 String theVersion, String theFromColumName, String theToColumName) { 359 MigrateColumnClobTypeToTextTypeTask task = new MigrateColumnClobTypeToTextTypeTask( 360 myRelease, theVersion, getTableName(), theFromColumName, theToColumName); 361 362 addTask(task); 363 return new BuilderCompleteTask(task); 364 } 365 366 public class BuilderAddIndexWithName { 367 private final String myVersion; 368 private final String myIndexName; 369 370 public BuilderAddIndexWithName(String theVersion, String theIndexName) { 371 myVersion = theVersion; 372 myIndexName = theIndexName; 373 } 374 375 public BuilderWithTableName.BuilderAddIndexWithName.BuilderAddIndexUnique unique(boolean theUnique) { 376 return new BuilderWithTableName.BuilderAddIndexWithName.BuilderAddIndexUnique(myVersion, theUnique); 377 } 378 379 public class BuilderAddIndexUnique { 380 private final String myVersion; 381 private final boolean myUnique; 382 private String[] myIncludeColumns; 383 private boolean myOnline; 384 385 public BuilderAddIndexUnique(String theVersion, boolean theUnique) { 386 myVersion = theVersion; 387 myUnique = theUnique; 388 } 389 390 public void withColumnsStub(String... theColumnNames) { 391 withColumnsOptional(true, theColumnNames); 392 } 393 394 public BuilderCompleteTask withColumns(String... theColumnNames) { 395 BaseTask task = withColumnsOptional(false, theColumnNames); 396 return new BuilderCompleteTask(task); 397 } 398 399 private AddIndexTask withColumnsOptional(boolean theDoNothing, String... theColumnNames) { 400 AddIndexTask task = new AddIndexTask(myRelease, myVersion); 401 task.setTableName(myTableName); 402 task.setIndexName(myIndexName); 403 task.setUnique(myUnique); 404 task.setColumns(theColumnNames); 405 task.setDoNothing(theDoNothing); 406 task.setOnline(myOnline); 407 if (myIncludeColumns != null) { 408 task.setIncludeColumns(myIncludeColumns); 409 } 410 addTask(task); 411 return task; 412 } 413 414 public BuilderAddIndexUnique includeColumns(String... theIncludeColumns) { 415 myIncludeColumns = theIncludeColumns; 416 return this; 417 } 418 419 /** 420 * Add the index without locking the table. 421 */ 422 public BuilderAddIndexUnique online(boolean theOnlineFlag) { 423 myOnline = theOnlineFlag; 424 return this; 425 } 426 } 427 } 428 429 public class BuilderModifyColumnWithName { 430 private final String myVersion; 431 private final String myColumnName; 432 433 public BuilderModifyColumnWithName(String theVersion, String theColumnName) { 434 myVersion = theVersion; 435 myColumnName = theColumnName; 436 } 437 438 public String getColumnName() { 439 return myColumnName; 440 } 441 442 public BuilderWithTableName.BuilderModifyColumnWithName.BuilderModifyColumnWithNameAndNullable nullable() { 443 return new BuilderWithTableName.BuilderModifyColumnWithName.BuilderModifyColumnWithNameAndNullable( 444 myVersion, true); 445 } 446 447 public BuilderWithTableName.BuilderModifyColumnWithName.BuilderModifyColumnWithNameAndNullable 448 nonNullable() { 449 return new BuilderWithTableName.BuilderModifyColumnWithName.BuilderModifyColumnWithNameAndNullable( 450 myVersion, false); 451 } 452 453 public class BuilderModifyColumnWithNameAndNullable { 454 private final String myVersion; 455 private final boolean myNullable; 456 private boolean myFailureAllowed; 457 458 public BuilderModifyColumnWithNameAndNullable(String theVersion, boolean theNullable) { 459 myVersion = theVersion; 460 myNullable = theNullable; 461 } 462 463 public void withType(ColumnTypeEnum theColumnType) { 464 withType(theColumnType, null); 465 } 466 467 public void withType(ColumnTypeEnum theColumnType, Integer theLength) { 468 if (theColumnType == ColumnTypeEnum.STRING) { 469 if (theLength == null || theLength == 0) { 470 throw new IllegalArgumentException( 471 Msg.code(52) + "Can not specify length 0 for column of type " + theColumnType); 472 } 473 } else { 474 if (theLength != null) { 475 throw new IllegalArgumentException( 476 Msg.code(53) + "Can not specify length for column of type " + theColumnType); 477 } 478 } 479 480 ModifyColumnTask task = new ModifyColumnTask(myRelease, myVersion); 481 task.setColumnName(myColumnName); 482 task.setTableName(myTableName); 483 if (theLength != null) { 484 task.setColumnLength(theLength); 485 } 486 task.setNullable(myNullable); 487 task.setColumnType(theColumnType); 488 task.setFailureAllowed(myFailureAllowed); 489 addTask(task); 490 } 491 492 public BuilderModifyColumnWithNameAndNullable failureAllowed() { 493 myFailureAllowed = true; 494 return this; 495 } 496 } 497 } 498 499 public class BuilderAddForeignKey { 500 private final String myVersion; 501 private final String myForeignKeyName; 502 503 public BuilderAddForeignKey(String theVersion, String theForeignKeyName) { 504 myVersion = theVersion; 505 myForeignKeyName = theForeignKeyName; 506 } 507 508 public BuilderWithTableName.BuilderAddForeignKey.BuilderAddForeignKeyToColumn toColumn( 509 String theColumnName) { 510 return new BuilderWithTableName.BuilderAddForeignKey.BuilderAddForeignKeyToColumn( 511 myVersion, theColumnName); 512 } 513 514 public class BuilderAddForeignKeyToColumn extends BuilderWithTableName.BuilderModifyColumnWithName { 515 public BuilderAddForeignKeyToColumn(String theVersion, String theColumnName) { 516 super(theVersion, theColumnName); 517 } 518 519 public BuilderCompleteTask references(String theForeignTable, String theForeignColumn) { 520 AddForeignKeyTask task = new AddForeignKeyTask(myRelease, myVersion); 521 task.setTableName(myTableName); 522 task.setConstraintName(myForeignKeyName); 523 task.setColumnName(getColumnName()); 524 task.setForeignTableName(theForeignTable); 525 task.setForeignColumnName(theForeignColumn); 526 addTask(task); 527 return new BuilderCompleteTask(task); 528 } 529 } 530 } 531 532 public static class BuilderAddColumnWithName { 533 private final String myRelease; 534 private final String myVersion; 535 private final String myColumnName; 536 private final BaseMigrationTasks.IAcceptsTasks myTaskSink; 537 538 public BuilderAddColumnWithName( 539 String theRelease, 540 String theVersion, 541 String theColumnName, 542 BaseMigrationTasks.IAcceptsTasks theTaskSink) { 543 myRelease = theRelease; 544 myVersion = theVersion; 545 myColumnName = theColumnName; 546 myTaskSink = theTaskSink; 547 } 548 549 public BuilderWithTableName.BuilderAddColumnWithName.BuilderAddColumnWithNameNullable nullable() { 550 return new BuilderWithTableName.BuilderAddColumnWithName.BuilderAddColumnWithNameNullable( 551 myRelease, myVersion, true); 552 } 553 554 public BuilderWithTableName.BuilderAddColumnWithName.BuilderAddColumnWithNameNullable nonNullable() { 555 return new BuilderWithTableName.BuilderAddColumnWithName.BuilderAddColumnWithNameNullable( 556 myRelease, myVersion, false); 557 } 558 559 public class BuilderAddColumnWithNameNullable { 560 private final boolean myNullable; 561 private final String myRelease; 562 private final String myVersion; 563 564 public BuilderAddColumnWithNameNullable(String theRelease, String theVersion, boolean theNullable) { 565 myRelease = theRelease; 566 myVersion = theVersion; 567 myNullable = theNullable; 568 } 569 570 public BuilderCompleteTask type(ColumnTypeEnum theColumnType) { 571 return type(theColumnType, null); 572 } 573 574 public BuilderCompleteTask type(ColumnTypeEnum theColumnType, Integer theLength) { 575 AddColumnTask task = new AddColumnTask(myRelease, myVersion); 576 task.setColumnName(myColumnName); 577 task.setNullable(myNullable); 578 task.setColumnType(theColumnType); 579 if (theLength != null) { 580 task.setColumnLength(theLength); 581 } 582 myTaskSink.addTask(task); 583 584 return new BuilderCompleteTask(task); 585 } 586 } 587 } 588 } 589 590 public static class BuilderCompleteTask { 591 592 private final BaseTask myTask; 593 594 public BuilderCompleteTask(BaseTask theTask) { 595 myTask = theTask; 596 } 597 598 public BuilderCompleteTask failureAllowed() { 599 myTask.setFailureAllowed(true); 600 return this; 601 } 602 603 public BuilderCompleteTask doNothing() { 604 myTask.setDoNothing(true); 605 return this; 606 } 607 608 public BuilderCompleteTask onlyAppliesToPlatforms(DriverTypeEnum... theTypes) { 609 Set<DriverTypeEnum> typesSet = Arrays.stream(theTypes).collect(Collectors.toSet()); 610 myTask.setOnlyAppliesToPlatforms(typesSet); 611 return this; 612 } 613 614 /** 615 * Introduce precondition checking logic into the execution of the enclosed task. This conditional logic will 616 * be implemented by running an SQL SELECT (including CTEs) to obtain a boolean indicating whether a certain 617 * condition has been met. 618 * One example is to check for a specific collation on a column to decide whether to create a new index. 619 * <p/> 620 * This method may be called multiple times to add multiple preconditions. The precondition that evaluates to 621 * false will stop execution of the task irrespective of any or all other tasks evaluating to true. 622 * 623 * @param theSql The SELECT or CTE used to determine if the precondition is valid. 624 * @param reason A String to indicate the text that is logged if the precondition is not met. 625 * @return The BuilderCompleteTask in order to chain further method calls on this builder. 626 */ 627 public BuilderCompleteTask onlyIf(@Language("SQL") String theSql, String reason) { 628 if (!theSql.toUpperCase().startsWith("WITH") 629 && !theSql.toUpperCase().startsWith("SELECT")) { 630 throw new IllegalArgumentException(Msg.code(2455) 631 + String.format( 632 "Only SELECT statements (including CTEs) are allowed here. Please check your SQL: [%s]", 633 theSql)); 634 } 635 ourLog.debug("SQL to evaluate: {}", theSql); 636 637 myTask.addPrecondition(new ExecuteTaskPrecondition( 638 () -> { 639 ourLog.debug("Checking precondition for SQL: {}", theSql); 640 return MigrationJdbcUtils.queryForSingleBooleanResultMultipleThrowsException( 641 theSql, myTask.newJdbcTemplate()); 642 }, 643 reason)); 644 645 return this; 646 } 647 648 public BuilderCompleteTask runEvenDuringSchemaInitialization() { 649 myTask.setRunDuringSchemaInitialization(true); 650 return this; 651 } 652 653 public BuilderCompleteTask setTransactional(boolean theFlag) { 654 myTask.setTransactional(theFlag); 655 return this; 656 } 657 } 658 659 public class BuilderAddTableRawSql { 660 661 private final AddTableRawSqlTask myTask; 662 663 protected BuilderAddTableRawSql(String theVersion, String theTableName) { 664 myTask = new AddTableRawSqlTask(myRelease, theVersion); 665 myTask.setTableName(theTableName); 666 addTask(myTask); 667 } 668 669 public BuilderAddTableRawSql addSql(DriverTypeEnum theDriverTypeEnum, @Language("SQL") String theSql) { 670 myTask.addSql(theDriverTypeEnum, theSql); 671 return this; 672 } 673 674 public void addSql(@Language("SQL") String theSql) { 675 myTask.addSql(theSql); 676 } 677 } 678 679 public class BuilderAddTableByColumns extends BuilderWithTableName implements BaseMigrationTasks.IAcceptsTasks { 680 private final String myVersion; 681 private final AddTableByColumnTask myTask; 682 683 public BuilderAddTableByColumns( 684 String theRelease, 685 String theVersion, 686 BaseMigrationTasks.IAcceptsTasks theSink, 687 String theTableName, 688 List<String> thePkColumnNames) { 689 super(theRelease, theSink, theTableName); 690 myVersion = theVersion; 691 myTask = new AddTableByColumnTask(myRelease, theVersion); 692 myTask.setTableName(theTableName); 693 myTask.setPkColumns(thePkColumnNames); 694 theSink.addTask(myTask); 695 } 696 697 public BuilderAddColumnWithName addColumn(String theColumnName) { 698 return new BuilderAddColumnWithName(myRelease, myVersion, theColumnName, this); 699 } 700 701 @Override 702 public void addTask(BaseTask theTask) { 703 if (theTask instanceof AddColumnTask) { 704 myTask.addAddColumnTask((AddColumnTask) theTask); 705 } else { 706 super.addTask(theTask); 707 } 708 } 709 710 public BuilderAddTableByColumns failureAllowed() { 711 myTask.setFailureAllowed(true); 712 return this; 713 } 714 } 715 716 public String getRelease() { 717 return myRelease; 718 } 719}