001/*-
002 * #%L
003 * HAPI FHIR Server - SQL Migration
004 * %%
005 * Copyright (C) 2014 - 2024 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.taskdef;
021
022import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
023import ca.uhn.fhir.jpa.migrate.JdbcUtils;
024import org.apache.commons.lang3.Validate;
025import org.apache.commons.lang3.builder.EqualsBuilder;
026import org.apache.commons.lang3.builder.HashCodeBuilder;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030import java.sql.SQLException;
031import java.util.ArrayList;
032import java.util.Comparator;
033import java.util.List;
034import java.util.stream.Collectors;
035
036public class AddTableByColumnTask extends BaseTableTask {
037
038        private static final Logger ourLog = LoggerFactory.getLogger(AddTableByColumnTask.class);
039
040        private final List<AddColumnTask> myAddColumnTasks = new ArrayList<>();
041        private List<String> myPkColumns;
042        private final List<ForeignKeyContainer> myFKColumns = new ArrayList<>();
043        private final Comparator<AddColumnTask> myColumnSortingRules;
044
045        public AddTableByColumnTask() {
046                this(null);
047        }
048
049        public AddTableByColumnTask(Comparator<AddColumnTask> theColumnSortingRules) {
050                this(null, null, theColumnSortingRules);
051                setDryRun(true);
052                myCheckForExistingTables = false;
053        }
054
055        public AddTableByColumnTask(String theProductVersion, String theSchemaVersion) {
056                this(theProductVersion, theSchemaVersion, null);
057        }
058
059        public AddTableByColumnTask(
060                        String theProductVersion, String theSchemaVersion, Comparator<AddColumnTask> theColumnSortingRules) {
061                super(theProductVersion, theSchemaVersion);
062                myColumnSortingRules = theColumnSortingRules;
063        }
064
065        @Override
066        public void validate() {
067                super.validate();
068                setDescription("Add table " + getTableName());
069        }
070
071        public void addAddColumnTask(AddColumnTask theTask) {
072                Validate.notNull(theTask);
073                myAddColumnTasks.add(theTask);
074        }
075
076        public void setPkColumns(List<String> thePkColumns) {
077                myPkColumns = thePkColumns;
078        }
079
080        public void addForeignKey(ForeignKeyContainer theForeignKeyContainer) {
081                myFKColumns.add(theForeignKeyContainer);
082        }
083
084        public List<String> getPkColumns() {
085                return myPkColumns;
086        }
087
088        public String generateSQLCreateScript() {
089                StringBuilder sb = new StringBuilder();
090                sb.append("CREATE TABLE ");
091                sb.append(getTableName());
092                sb.append(" (");
093                if (myPrettyPrint) {
094                        sb.append("\n");
095                } else {
096                        sb.append(" ");
097                }
098
099                for (AddColumnTask next : getOrderedAddColumnTasks()) {
100                        next.setDriverType(getDriverType());
101                        next.setTableName(getTableName());
102                        next.validate();
103
104                        if (myPrettyPrint) {
105                                sb.append("\t");
106                        }
107
108                        sb.append(next.getColumnName());
109                        sb.append(" ");
110                        sb.append(next.getTypeStatement());
111                        sb.append(",");
112                        if (myPrettyPrint) {
113                                sb.append("\n");
114                        } else {
115                                sb.append(" ");
116                        }
117                }
118
119                // primary keys
120                if (myPrettyPrint) {
121                        sb.append("\t");
122                } else {
123                        sb.append(" ");
124                }
125                sb.append("PRIMARY KEY (");
126                for (int i = 0; i < myPkColumns.size(); i++) {
127                        if (i > 0) {
128                                sb.append(", ");
129                        }
130                        sb.append(myPkColumns.get(i));
131                }
132
133                boolean hasForeignKeys = !myFKColumns.isEmpty();
134
135                sb.append(")");
136                if (hasForeignKeys) {
137                        sb.append(",");
138                }
139                if (myPrettyPrint) {
140                        sb.append("\n");
141                } else {
142                        sb.append(" ");
143                }
144
145                DriverTypeEnum sqlEngine = getDriverType();
146
147                // foreign keys
148                if (!myFKColumns.isEmpty()) {
149                        for (int i = 0; i < myFKColumns.size(); i++) {
150                                if (i > 0) {
151                                        sb.append(", ");
152                                }
153                                ForeignKeyContainer fk = myFKColumns.get(i);
154                                if (myPrettyPrint) {
155                                        sb.append("\t");
156                                }
157                                sb.append(fk.generateSQL(sqlEngine, myPrettyPrint));
158                                if (myPrettyPrint) {
159                                        sb.append("\n");
160                                } else {
161                                        sb.append(" ");
162                                }
163                        }
164                }
165
166                sb.append(")");
167
168                switch (sqlEngine) {
169                        case MARIADB_10_1:
170                        case MYSQL_5_7:
171                                sb.append(" engine=InnoDB");
172                                break;
173                        case DERBY_EMBEDDED:
174                        case POSTGRES_9_4:
175                        case ORACLE_12C:
176                        case MSSQL_2012:
177                        case H2_EMBEDDED:
178                        case COCKROACHDB_21_1:
179                                break;
180                }
181
182                return sb.toString();
183        }
184
185        @Override
186        public void doExecute() throws SQLException {
187                if (myCheckForExistingTables
188                                && JdbcUtils.getTableNames(getConnectionProperties()).contains(getTableName())) {
189                        logInfo(ourLog, "Already have table named {} - No action performed", getTableName());
190                        return;
191                }
192
193                executeSql(getTableName(), generateSQLCreateScript());
194        }
195
196        @Override
197        protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) {
198                super.generateEquals(theBuilder, theOtherObject);
199                AddTableByColumnTask otherObject = (AddTableByColumnTask) theOtherObject;
200                theBuilder.append(myAddColumnTasks, otherObject.myAddColumnTasks);
201                theBuilder.append(myPkColumns, otherObject.myPkColumns);
202        }
203
204        @Override
205        protected void generateHashCode(HashCodeBuilder theBuilder) {
206                super.generateHashCode(theBuilder);
207                theBuilder.append(myAddColumnTasks);
208                theBuilder.append(myPkColumns);
209        }
210
211        private List<AddColumnTask> getOrderedAddColumnTasks() {
212                if (myColumnSortingRules == null) {
213                        return myAddColumnTasks;
214                }
215
216                return myAddColumnTasks.stream().sorted(myColumnSortingRules).collect(Collectors.toUnmodifiableList());
217        }
218}