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