001/*-
002 * #%L
003 * HAPI FHIR Server - SQL Migration
004 * %%
005 * Copyright (C) 2014 - 2025 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.i18n.Msg;
023import ca.uhn.fhir.jpa.migrate.JdbcUtils;
024import jakarta.annotation.Nonnull;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028import java.sql.SQLException;
029import java.util.Set;
030
031public class AddColumnTask extends BaseTableColumnTypeTask {
032
033        private static final Logger ourLog = LoggerFactory.getLogger(AddColumnTask.class);
034
035        public static AddColumnTask lowerCase(Set<ColumnDriverMappingOverride> theColumnDriverMappingOverrides) {
036                return new AddColumnTask(null, null, ColumnNameCase.ALL_LOWER, theColumnDriverMappingOverrides);
037        }
038
039        public AddColumnTask(String theProductVersion, String theSchemaVersion) {
040                super(theProductVersion, theSchemaVersion);
041        }
042
043        private AddColumnTask(
044                        String theProductVersion,
045                        String theSchemaVersion,
046                        ColumnNameCase theColumnNameCase,
047                        Set<ColumnDriverMappingOverride> theColumnDriverMappingOverrides) {
048                super(theProductVersion, theSchemaVersion, theColumnNameCase, theColumnDriverMappingOverrides);
049        }
050
051        @Override
052        public void validate() {
053                super.validate();
054                setDescription("Add column " + getColumnName() + " on table " + getTableName());
055        }
056
057        @Override
058        public void doExecute() throws SQLException {
059                if (myCheckForExistingTables) {
060                        Set<String> columnNames = JdbcUtils.getColumnNames(getConnectionProperties(), getTableName());
061                        if (columnNames.contains(getColumnName())) {
062                                logInfo(
063                                                ourLog,
064                                                "Column {} already exists on table {} - No action performed",
065                                                getColumnName(),
066                                                getTableName());
067                                return;
068                        }
069                }
070
071                String typeStatement = getTypeStatement();
072
073                String sql;
074                switch (getDriverType()) {
075                        case MYSQL_5_7:
076                        case MARIADB_10_1:
077                                // Quote the column name as "SYSTEM" is a reserved word in MySQL
078                                sql = "alter table " + getTableName() + " add column `" + getColumnName() + "` " + typeStatement
079                                                + buildDefaultClauseIfApplicable();
080                                break;
081                        case DERBY_EMBEDDED:
082                        case POSTGRES_9_4:
083                        case COCKROACHDB_21_1:
084                                sql = "alter table " + getTableName() + " add column " + getColumnName() + " " + typeStatement
085                                                + buildDefaultClauseIfApplicable();
086                                break;
087                        case MSSQL_2012:
088                        case ORACLE_12C:
089                        case H2_EMBEDDED:
090                                sql = "alter table " + getTableName() + " add " + getColumnName() + " " + typeStatement
091                                                + buildDefaultClauseIfApplicable();
092                                break;
093                        default:
094                                throw new IllegalStateException(Msg.code(60));
095                }
096
097                logInfo(ourLog, "Adding column {} of type {} to table {}", getColumnName(), getSqlType(), getTableName());
098                executeSql(getTableName(), sql);
099        }
100
101        @Nonnull
102        private String buildDefaultClauseIfApplicable() {
103                return buildString(getDefaultValue(), (obj -> " default " + obj), "");
104        }
105
106        public String getTypeStatement() {
107                String type = getSqlType();
108                String nullable = getSqlNotNull();
109                if (isNullable()) {
110                        nullable = "";
111                }
112                if (myPrettyPrint) {
113                        nullable = nullable.trim();
114                }
115                String space = isNullable() ? "" : " ";
116                return type + space + nullable;
117        }
118}