001/*-
002 * #%L
003 * HAPI FHIR Server - SQL Migration
004 * %%
005 * Copyright (C) 2014 - 2023 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.jpa.migrate.tasks.api;
021
022import ca.uhn.fhir.i18n.Msg;
023import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
024import ca.uhn.fhir.jpa.migrate.taskdef.AddColumnTask;
025import ca.uhn.fhir.jpa.migrate.taskdef.AddForeignKeyTask;
026import ca.uhn.fhir.jpa.migrate.taskdef.AddIdGeneratorTask;
027import ca.uhn.fhir.jpa.migrate.taskdef.AddIndexTask;
028import ca.uhn.fhir.jpa.migrate.taskdef.AddTableByColumnTask;
029import ca.uhn.fhir.jpa.migrate.taskdef.AddTableRawSqlTask;
030import ca.uhn.fhir.jpa.migrate.taskdef.BaseTableTask;
031import ca.uhn.fhir.jpa.migrate.taskdef.BaseTask;
032import ca.uhn.fhir.jpa.migrate.taskdef.ColumnTypeEnum;
033import ca.uhn.fhir.jpa.migrate.taskdef.DropColumnTask;
034import ca.uhn.fhir.jpa.migrate.taskdef.DropForeignKeyTask;
035import ca.uhn.fhir.jpa.migrate.taskdef.DropIdGeneratorTask;
036import ca.uhn.fhir.jpa.migrate.taskdef.DropIndexTask;
037import ca.uhn.fhir.jpa.migrate.taskdef.DropTableTask;
038import ca.uhn.fhir.jpa.migrate.taskdef.ExecuteRawSqlTask;
039import ca.uhn.fhir.jpa.migrate.taskdef.InitializeSchemaTask;
040import ca.uhn.fhir.jpa.migrate.taskdef.MigratePostgresTextClobToBinaryClobTask;
041import ca.uhn.fhir.jpa.migrate.taskdef.ModifyColumnTask;
042import ca.uhn.fhir.jpa.migrate.taskdef.NopTask;
043import ca.uhn.fhir.jpa.migrate.taskdef.RenameColumnTask;
044import ca.uhn.fhir.jpa.migrate.taskdef.RenameIndexTask;
045import org.apache.commons.lang3.Validate;
046import org.intellij.lang.annotations.Language;
047
048import java.util.Arrays;
049import java.util.Collections;
050import java.util.HashMap;
051import java.util.List;
052import java.util.Map;
053import java.util.Set;
054import java.util.stream.Collectors;
055
056public class Builder {
057
058        private final String myRelease;
059        private final BaseMigrationTasks.IAcceptsTasks mySink;
060
061        public Builder(String theRelease, BaseMigrationTasks.IAcceptsTasks theSink) {
062                myRelease = theRelease;
063                mySink = theSink;
064        }
065
066        public BuilderWithTableName onTable(String theTableName) {
067                return new BuilderWithTableName(myRelease, mySink, theTableName);
068        }
069
070        public void addTask(BaseTask theTask) {
071                mySink.addTask(theTask);
072        }
073
074        public BuilderAddTableRawSql addTableRawSql(String theVersion, String theTableName) {
075                return new BuilderAddTableRawSql(theVersion, theTableName);
076        }
077
078        public BuilderCompleteTask executeRawSql(String theVersion, @Language("SQL") String theSql) {
079                ExecuteRawSqlTask task = executeRawSqlOptional(false, theVersion, theSql);
080                return new BuilderCompleteTask(task);
081        }
082
083        public void executeRawSqlStub(String theVersion, @Language("SQL") String theSql) {
084                executeRawSqlOptional(true, theVersion, theSql);
085        }
086
087        private ExecuteRawSqlTask executeRawSqlOptional(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 InitializeSchemaTask initializeSchema(String theVersion, ISchemaInitializationProvider theSchemaInitializationProvider) {
095                InitializeSchemaTask task = new InitializeSchemaTask(myRelease, theVersion, theSchemaInitializationProvider);
096                mySink.addTask(task);
097                return task;
098        }
099
100        @SuppressWarnings("unused")
101        public InitializeSchemaTask 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 task;
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}