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