3 * Copyright (C) 2009-2017 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.platform.db.migration.sql;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.Iterator;
25 import java.util.List;
26 import org.sonar.db.dialect.Dialect;
27 import org.sonar.db.dialect.MySql;
28 import org.sonar.db.dialect.Oracle;
29 import org.sonar.db.dialect.PostgreSql;
30 import org.sonar.server.platform.db.migration.def.ColumnDef;
32 import static com.google.common.base.Preconditions.checkArgument;
33 import static com.google.common.collect.Lists.newArrayList;
36 * Generate SQL queries to update multiple columns of a single table.
38 * Note that this operation will not be re-entrant on:
40 * <li>Oracle 11G (may raise {@code ORA-01442: column to be modified to NOT NULL is already NOT NULL} or
41 * {@code ORA-01451: column to be modified to NULL cannot be modified to NULL})</li>
44 public class AlterColumnsBuilder {
46 private static final String ALTER_TABLE = "ALTER TABLE ";
47 private static final String ALTER_COLUMN = "ALTER COLUMN ";
49 private final Dialect dialect;
50 private final String tableName;
51 private final List<ColumnDef> columnDefs = newArrayList();
53 public AlterColumnsBuilder(Dialect dialect, String tableName) {
54 this.dialect = dialect;
55 this.tableName = tableName;
58 public AlterColumnsBuilder updateColumn(ColumnDef columnDef) {
59 // limitation of Oracle, only attribute changes must be defined in ALTER.
60 checkArgument(columnDef.getDefaultValue()==null, "Default value is not supported on alter of column '%s'", columnDef.getName());
61 columnDefs.add(columnDef);
65 public List<String> build() {
66 if (columnDefs.isEmpty()) {
67 throw new IllegalStateException("No column has been defined");
69 switch (dialect.getId()) {
71 return createPostgresQuery();
73 return createMySqlQuery();
75 return createOracleQuery();
77 return createMsSqlAndH2Queries();
81 private List<String> createPostgresQuery() {
82 StringBuilder sql = new StringBuilder(ALTER_TABLE + tableName + " ");
83 for (Iterator<ColumnDef> columnDefIterator = columnDefs.iterator(); columnDefIterator.hasNext();) {
84 ColumnDef columnDef = columnDefIterator.next();
85 sql.append(ALTER_COLUMN);
86 addColumn(sql, columnDef, "TYPE ", false);
88 sql.append(ALTER_COLUMN);
89 sql.append(columnDef.getName());
90 sql.append(' ').append(columnDef.isNullable() ? "DROP" : "SET").append(" NOT NULL");
91 if (columnDefIterator.hasNext()) {
95 return Collections.singletonList(sql.toString());
98 private List<String> createMySqlQuery() {
99 StringBuilder sql = new StringBuilder(ALTER_TABLE + tableName + " ");
100 addColumns(sql, "MODIFY COLUMN ", "", true);
101 return Collections.singletonList(sql.toString());
104 private List<String> createOracleQuery() {
105 List<String> sqls = new ArrayList<>();
106 for (ColumnDef columnDef : columnDefs) {
107 StringBuilder sql = new StringBuilder(ALTER_TABLE + tableName + " ").append("MODIFY (");
108 addColumn(sql, columnDef, "", true);
110 sqls.add(sql.toString());
115 private List<String> createMsSqlAndH2Queries() {
116 List<String> sqls = new ArrayList<>();
117 for (ColumnDef columnDef : columnDefs) {
118 StringBuilder defaultQuery = new StringBuilder(ALTER_TABLE + tableName + " ");
119 defaultQuery.append(ALTER_COLUMN);
120 addColumn(defaultQuery, columnDef, "", true);
121 sqls.add(defaultQuery.toString());
126 private void addColumns(StringBuilder sql, String updateKeyword, String typePrefix, boolean addNotNullableProperty) {
127 for (Iterator<ColumnDef> columnDefIterator = columnDefs.iterator(); columnDefIterator.hasNext();) {
128 sql.append(updateKeyword);
129 addColumn(sql, columnDefIterator.next(), typePrefix, addNotNullableProperty);
130 if (columnDefIterator.hasNext()) {
136 private void addColumn(StringBuilder sql, ColumnDef columnDef, String typePrefix, boolean addNotNullableProperty) {
137 sql.append(columnDef.getName())
140 .append(columnDef.generateSqlType(dialect));
141 if (addNotNullableProperty) {
142 sql.append(columnDef.isNullable() ? " NULL" : " NOT NULL");