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 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.Set;
032
033import static org.apache.commons.lang3.StringUtils.isNotBlank;
034
035public class AddForeignKeyTask extends BaseTableColumnTask {
036
037        private static final Logger ourLog = LoggerFactory.getLogger(AddForeignKeyTask.class);
038        private String myConstraintName;
039        private String myForeignTableName;
040        private String myForeignColumnName;
041
042        public AddForeignKeyTask(String theProductVersion, String theSchemaVersion) {
043                super(theProductVersion, theSchemaVersion);
044        }
045
046        public void setConstraintName(String theConstraintName) {
047                myConstraintName = theConstraintName;
048        }
049
050        public void setForeignTableName(String theForeignTableName) {
051                myForeignTableName = theForeignTableName;
052        }
053
054        public void setForeignColumnName(String theForeignColumnName) {
055                myForeignColumnName = theForeignColumnName;
056        }
057
058        @Override
059        public void validate() {
060                super.validate();
061
062                Validate.isTrue(isNotBlank(myConstraintName));
063                Validate.isTrue(isNotBlank(myForeignTableName));
064                Validate.isTrue(isNotBlank(myForeignColumnName));
065                setDescription("Add foreign key " + myConstraintName + " from column " + getColumnName() + " of table "
066                                + getTableName() + " to column " + myForeignColumnName + " of table " + myForeignTableName);
067        }
068
069        @Override
070        public void doExecute() throws SQLException {
071
072                Set<String> existing = JdbcUtils.getForeignKeys(getConnectionProperties(), myForeignTableName, getTableName());
073                if (existing.contains(myConstraintName)) {
074                        logInfo(ourLog, "Already have constraint named {} - No action performed", myConstraintName);
075                        return;
076                }
077
078                String sql;
079                switch (getDriverType()) {
080                        case MARIADB_10_1:
081                        case MYSQL_5_7:
082                                // Quote the column names as "SYSTEM" is a reserved word in MySQL
083                                sql = "alter table " + getTableName() + " add constraint " + myConstraintName + " foreign key (`"
084                                                + getColumnName() + "`) references " + myForeignTableName + " (`" + myForeignColumnName + "`)";
085                                break;
086                        case COCKROACHDB_21_1:
087                        case POSTGRES_9_4:
088                        case DERBY_EMBEDDED:
089                        case H2_EMBEDDED:
090                        case ORACLE_12C:
091                        case MSSQL_2012:
092                                sql = "alter table " + getTableName() + " add constraint " + myConstraintName + " foreign key ("
093                                                + getColumnName() + ") references " + myForeignTableName;
094                                break;
095                        default:
096                                throw new IllegalStateException(Msg.code(68));
097                }
098
099                try {
100                        executeSql(getTableName(), sql);
101                } catch (Exception e) {
102                        if (e.toString().contains("already exists")) {
103                                ourLog.warn("Index {} already exists", myConstraintName);
104                        } else {
105                                throw e;
106                        }
107                }
108        }
109
110        @Override
111        protected void generateHashCode(HashCodeBuilder theBuilder) {
112                super.generateHashCode(theBuilder);
113                theBuilder.append(myConstraintName);
114                theBuilder.append(myForeignTableName);
115                theBuilder.append(myForeignColumnName);
116        }
117
118        @Override
119        protected void generateEquals(EqualsBuilder theBuilder, BaseTask theOtherObject) {
120                AddForeignKeyTask otherObject = (AddForeignKeyTask) theOtherObject;
121                super.generateEquals(theBuilder, otherObject);
122                theBuilder.append(myConstraintName, otherObject.myConstraintName);
123                theBuilder.append(myForeignTableName, otherObject.myForeignTableName);
124                theBuilder.append(myForeignColumnName, otherObject.myForeignColumnName);
125        }
126}