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 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}