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