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.i18n.Msg;
023import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
024import jakarta.annotation.Nonnull;
025import jakarta.annotation.Nullable;
026import org.intellij.lang.annotations.Language;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030import java.sql.SQLException;
031
032/**
033 * Migration task that handles cross-database logic for dropping a primary key.
034 * <p>
035 * The process involves 2 steps for most databases:
036 * <ol>
037 *     <li>Running SQL to introspect the metadata tables to determine the name of the primary key.</li>
038 *     <li>Running an ALTER TABLE to drop the constraint found above by name.</li>
039 * </ol>
040 */
041public class DropPrimaryKeyTask extends BaseTableTask {
042        private static final Logger ourLog = LoggerFactory.getLogger(DropPrimaryKeyTask.class);
043
044        public DropPrimaryKeyTask(String theProductVersion, String theSchemaVersion, String theTableName) {
045                super(theProductVersion, theSchemaVersion);
046                setTableName(theTableName);
047        }
048
049        @Nonnull
050        private String generateSql() {
051                ourLog.debug("DropPrimaryKeyTask.generateSql()");
052
053                @Nullable
054                @Language("SQL")
055                final String primaryKeyNameSql = generatePrimaryKeyNameSql();
056
057                @Nullable
058                final String primaryKeyName = primaryKeyNameSql != null
059                                ? newJdbcTemplate()
060                                                .queryForObject(primaryKeyNameSql, String.class, getTableNameWithDatabaseExpectedCase())
061                                : null;
062
063                ourLog.debug("primaryKeyName: {} for driver: {}", primaryKeyName, getDriverType());
064
065                return generateDropPrimaryKeySql(primaryKeyName);
066        }
067
068        private String getTableNameWithDatabaseExpectedCase() {
069                if (DriverTypeEnum.ORACLE_12C == getDriverType()) {
070                        return getTableName().toUpperCase();
071                }
072
073                return getTableName().toLowerCase();
074        }
075
076        @Override
077        protected void doExecute() throws SQLException {
078                logInfo(ourLog, "Going to DROP the PRIMARY KEY on table {}", getTableName());
079
080                executeSql(getTableName(), generateSql());
081        }
082
083        private String generateDropPrimaryKeySql(@Nullable String thePrimaryKeyName) {
084                switch (getDriverType()) {
085                        case MARIADB_10_1:
086                        case DERBY_EMBEDDED:
087                        case H2_EMBEDDED:
088                                @Language("SQL")
089                                final String sqlH2 = "ALTER TABLE %s DROP PRIMARY KEY";
090                                return String.format(sqlH2, getTableName());
091                        case POSTGRES_9_4:
092                        case ORACLE_12C:
093                        case MSSQL_2012:
094                        case MYSQL_5_7:
095                                assert thePrimaryKeyName != null;
096                                @Language("SQL")
097                                final String sql = "ALTER TABLE %s DROP CONSTRAINT %s";
098                                return String.format(sql, getTableName(), thePrimaryKeyName);
099                        default:
100                                throw new IllegalStateException(String.format(
101                                                "%s Unknown driver type: %s.  Cannot drop primary key: %s for task %s",
102                                                Msg.code(2529), getDriverType(), getMigrationVersion(), getTableName()));
103                }
104        }
105
106        @Language("SQL")
107        @Nullable
108        private String generatePrimaryKeyNameSql() {
109                switch (getDriverType()) {
110                        case MYSQL_5_7:
111                        case MARIADB_10_1:
112                        case DERBY_EMBEDDED:
113                        case COCKROACHDB_21_1:
114                        case H2_EMBEDDED:
115                                return null; // Irrelevant:  We don't need to run the SQL for these databases.
116                        case POSTGRES_9_4:
117                                return "SELECT constraint_name " + "FROM information_schema.table_constraints "
118                                                + "WHERE table_schema = coalesce(current_schema(), 'public') "
119                                                + "AND constraint_type = 'PRIMARY KEY' "
120                                                + "AND table_name = ?";
121                        case ORACLE_12C:
122                                return "SELECT constraint_name " + "FROM user_constraints "
123                                                + "WHERE constraint_type = 'P' "
124                                                + "AND table_name = ?";
125                        case MSSQL_2012:
126                                return "SELECT tc.constraint_name " + "FROM information_schema.table_constraints tc "
127                                                + "JOIN information_schema.constraint_column_usage ccu ON tc.constraint_name = ccu.constraint_name "
128                                                + "WHERE tc.constraint_type = 'PRIMARY KEY' "
129                                                + "AND  tc.table_name = ?";
130                        default:
131                                throw new IllegalStateException(String.format(
132                                                "%s Unknown driver type: %s  Cannot find primary key to drop for task %s",
133                                                Msg.code(2530), getDriverType(), getMigrationVersion()));
134                }
135        }
136}