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