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