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