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