001/*-
002 * #%L
003 * HAPI FHIR Server - SQL Migration
004 * %%
005 * Copyright (C) 2014 - 2026 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 sql = generateSql();
072                logInfo(ourLog, "Adding column {} of type {} to table {}", getColumnName(), getSqlType(), getTableName());
073                executeSql(getTableName(), sql);
074        }
075
076        String generateSql() {
077                String typeStatement = getTypeStatement();
078
079                String sql;
080                switch (getDriverType()) {
081                        case MYSQL_5_7:
082                        case MARIADB_10_1:
083                                // Quote the column name as "SYSTEM" is a reserved word in MySQL
084                                sql = "alter table " + getTableName() + " add column `" + getColumnName() + "` " + typeStatement
085                                                + buildDefaultClauseIfApplicable();
086                                break;
087                        case DERBY_EMBEDDED:
088                        case POSTGRES_9_4:
089                        case COCKROACHDB_21_1:
090                                sql = "alter table " + getTableName() + " add column " + getColumnName() + " " + typeStatement
091                                                + buildDefaultClauseIfApplicable();
092                                break;
093                        case MSSQL_2012:
094                        case ORACLE_12C:
095                        case H2_EMBEDDED:
096                                sql = "alter table " + getTableName() + " add " + getColumnName() + " " + typeStatement
097                                                + buildDefaultClauseIfApplicable();
098                                break;
099                        default:
100                                throw new IllegalStateException(Msg.code(60));
101                }
102
103                return sql;
104        }
105
106        @Nonnull
107        private String buildDefaultClauseIfApplicable() {
108                return buildString(getDefaultValue(), (obj -> " default " + obj), "");
109        }
110
111        public String getTypeStatement() {
112                String type = getSqlType();
113                String nullable = getSqlNotNull();
114                if (isNullable()) {
115                        nullable = "";
116                }
117                if (myPrettyPrint) {
118                        nullable = nullable.trim();
119                }
120                String space = isNullable() ? "" : " ";
121                return type + space + nullable;
122        }
123}