001package ca.uhn.fhir.jpa.migrate.taskdef; 002 003/*- 004 * #%L 005 * HAPI FHIR Server - SQL Migration 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; 024import ca.uhn.fhir.jpa.migrate.JdbcUtils; 025import org.apache.commons.lang3.Validate; 026import org.apache.commons.lang3.builder.EqualsBuilder; 027import org.apache.commons.lang3.builder.HashCodeBuilder; 028import org.intellij.lang.annotations.Language; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031import org.springframework.jdbc.core.JdbcTemplate; 032import org.springframework.jdbc.core.RowMapperResultSetExtractor; 033import org.springframework.jdbc.core.SingleColumnRowMapper; 034 035import javax.sql.DataSource; 036import java.sql.SQLException; 037import java.util.ArrayList; 038import java.util.Collections; 039import java.util.List; 040import java.util.Objects; 041import java.util.Set; 042 043public class DropIndexTask extends BaseTableTask { 044 045 private static final Logger ourLog = LoggerFactory.getLogger(DropIndexTask.class); 046 private String myIndexName; 047 048 public DropIndexTask(String theProductVersion, String theSchemaVersion) { 049 super(theProductVersion, theSchemaVersion); 050 } 051 052 static List<String> createDropIndexSql(DriverTypeEnum.ConnectionProperties theConnectionProperties, String theTableName, String theIndexName, DriverTypeEnum theDriverType) throws SQLException { 053 Validate.notBlank(theIndexName, "theIndexName must not be blank"); 054 Validate.notBlank(theTableName, "theTableName must not be blank"); 055 056 if (!JdbcUtils.getIndexNames(theConnectionProperties, theTableName).contains(theIndexName)) { 057 return Collections.emptyList(); 058 } 059 060 boolean isUnique = JdbcUtils.isIndexUnique(theConnectionProperties, theTableName, theIndexName); 061 062 List<String> sql = new ArrayList<>(); 063 064 if (isUnique) { 065 // Drop constraint 066 switch (theDriverType) { 067 case MYSQL_5_7: 068 case MARIADB_10_1: 069 // Need to quote the index name as the word "PRIMARY" is reserved in MySQL 070 sql.add("alter table " + theTableName + " drop index `" + theIndexName + "`"); 071 break; 072 case H2_EMBEDDED: 073 sql.add("drop index " + theIndexName); 074 break; 075 case DERBY_EMBEDDED: 076 sql.add("alter table " + theTableName + " drop constraint " + theIndexName); 077 break; 078 case ORACLE_12C: 079 sql.add("drop index " + theIndexName); 080 break; 081 case MSSQL_2012: 082 sql.add("drop index " + theIndexName + " on " + theTableName); 083 break; 084 case POSTGRES_9_4: 085 sql.add("alter table " + theTableName + " drop constraint if exists " + theIndexName + " cascade"); 086 sql.add("drop index if exists " + theIndexName + " cascade"); 087 break; 088 } 089 } else { 090 // Drop index 091 switch (theDriverType) { 092 case MYSQL_5_7: 093 case MARIADB_10_1: 094 sql.add("alter table " + theTableName + " drop index " + theIndexName); 095 break; 096 case POSTGRES_9_4: 097 case DERBY_EMBEDDED: 098 case H2_EMBEDDED: 099 case ORACLE_12C: 100 sql.add("drop index " + theIndexName); 101 break; 102 case MSSQL_2012: 103 sql.add("drop index " + theTableName + "." + theIndexName); 104 break; 105 } 106 } 107 return sql; 108 } 109 110 @Override 111 public void validate() { 112 super.validate(); 113 Validate.notBlank(myIndexName, "The index name must not be blank"); 114 115 setDescription("Drop index " + myIndexName + " from table " + getTableName()); 116 } 117 118 @Override 119 public void doExecute() throws SQLException { 120 /* 121 * Derby and H2 both behave a bit weirdly if you create a unique constraint 122 * using the @UniqueConstraint annotation in hibernate - They will create a 123 * constraint with that name, but will then create a shadow index with a different 124 * name, and it's that different name that gets reported when you query for the 125 * list of indexes. 126 * 127 * For example, on H2 if you create a constraint named "IDX_FOO", the system 128 * will create an index named "IDX_FOO_INDEX_A" and a constraint named "IDX_FOO". 129 * 130 * The following is a solution that uses appropriate native queries to detect 131 * on the given platforms whether an index name actually corresponds to a 132 * constraint, and delete that constraint. 133 */ 134 135 if (getDriverType() == DriverTypeEnum.H2_EMBEDDED) { 136 @Language("SQL") String findConstraintSql = "SELECT DISTINCT constraint_name FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE constraint_name = ? AND table_name = ?"; 137 @Language("SQL") String dropConstraintSql = "ALTER TABLE " + getTableName() + " DROP CONSTRAINT ?"; 138 findAndDropConstraint(findConstraintSql, dropConstraintSql); 139 } else if (getDriverType() == DriverTypeEnum.DERBY_EMBEDDED) { 140 @Language("SQL") String findConstraintSql = "SELECT c.constraintname FROM sys.sysconstraints c, sys.systables t WHERE c.tableid = t.tableid AND c.constraintname = ? AND t.tablename = ?"; 141 @Language("SQL") String dropConstraintSql = "ALTER TABLE " + getTableName() + " DROP CONSTRAINT ?"; 142 findAndDropConstraint(findConstraintSql, dropConstraintSql); 143 } else if (getDriverType() == DriverTypeEnum.ORACLE_12C) { 144 @Language("SQL") String findConstraintSql = "SELECT DISTINCT constraint_name FROM user_cons_columns WHERE constraint_name = ? AND table_name = ?"; 145 @Language("SQL") String dropConstraintSql = "ALTER TABLE " + getTableName() + " DROP CONSTRAINT ?"; 146 findAndDropConstraint(findConstraintSql, dropConstraintSql); 147 findConstraintSql = "SELECT DISTINCT constraint_name FROM all_constraints WHERE index_name = ? AND table_name = ?"; 148 findAndDropConstraint(findConstraintSql, dropConstraintSql); 149 } else if (getDriverType() == DriverTypeEnum.MSSQL_2012) { 150 // Legacy deletion for SQL Server unique indexes 151 @Language("SQL") String findConstraintSql = "SELECT tc.CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc WHERE tc.CONSTRAINT_NAME = ? AND tc.TABLE_NAME = ?"; 152 @Language("SQL") String dropConstraintSql = "ALTER TABLE " + getTableName() + " DROP CONSTRAINT ?"; 153 findAndDropConstraint(findConstraintSql, dropConstraintSql); 154 } 155 156 Set<String> indexNames = JdbcUtils.getIndexNames(getConnectionProperties(), getTableName()); 157 158 if (!indexNames.contains(myIndexName)) { 159 logInfo(ourLog, "Index {} does not exist on table {} - No action needed", myIndexName, getTableName()); 160 return; 161 } 162 163 boolean isUnique = JdbcUtils.isIndexUnique(getConnectionProperties(), getTableName(), myIndexName); 164 String uniquenessString = isUnique ? "unique" : "non-unique"; 165 166 List<String> sqls = createDropIndexSql(getConnectionProperties(), getTableName(), myIndexName, getDriverType()); 167 if (!sqls.isEmpty()) { 168 logInfo(ourLog, "Dropping {} index {} on table {}", uniquenessString, myIndexName, getTableName()); 169 } 170 for (@Language("SQL") String sql : sqls) { 171 executeSql(getTableName(), sql); 172 } 173 } 174 175 public void findAndDropConstraint(String theFindConstraintSql, String theDropConstraintSql) { 176 DataSource dataSource = Objects.requireNonNull(getConnectionProperties().getDataSource()); 177 getConnectionProperties().getTxTemplate().executeWithoutResult(t -> { 178 JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); 179 RowMapperResultSetExtractor<String> resultSetExtractor = new RowMapperResultSetExtractor<>(new SingleColumnRowMapper<>(String.class)); 180 List<String> outcome = jdbcTemplate.query(theFindConstraintSql, new Object[]{myIndexName, getTableName()}, resultSetExtractor); 181 assert outcome != null; 182 for (String next : outcome) { 183 String sql = theDropConstraintSql.replace("?", next); 184 executeSql(getTableName(), sql); 185 } 186 }); 187 } 188 189 public DropIndexTask setIndexName(String theIndexName) { 190 myIndexName = theIndexName; 191 return this; 192 } 193 194 @Override 195 protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) { 196 DropIndexTask otherObject = (DropIndexTask) theOtherObject; 197 super.generateEquals(theBuilder, otherObject); 198 theBuilder.append(myIndexName, otherObject.myIndexName); 199 } 200 201 @Override 202 protected void generateHashCode(HashCodeBuilder theBuilder) { 203 super.generateHashCode(theBuilder); 204 theBuilder.append(myIndexName); 205 } 206}