]> source.dussan.org Git - sonarqube.git/blob
7343fc5a9796b271f87168ab5e5eabd9d3d9fca5
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
20 package org.sonar.server.platform.db.migration.sql;
21
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.stream.Collectors;
25 import javax.annotation.Nullable;
26 import org.sonar.db.dialect.Dialect;
27 import org.sonar.db.dialect.MsSql;
28 import org.sonar.server.platform.db.migration.def.ColumnDef;
29
30 import static com.google.common.base.Preconditions.checkArgument;
31 import static com.google.common.base.Preconditions.checkState;
32 import static java.util.Objects.requireNonNull;
33 import static org.sonar.server.platform.db.migration.def.Validations.validateTableName;
34
35 /**
36  * Creates a new table based on an existing table.
37  * With Oracle, H2 and PSQL it uses the 'CREATE TABLE [...] AS' statement. This is not supported in SQL Server, so we use 'SELECT [...] INTO [new_table] FROM [old_table]'.
38  * Note that indexes are not kept. Constraints are also not kept except for 'NOT NULL' in some dbs and under certain conditions. Some dbs also allow to specify 'NOT NULL'
39  * constraint or even data type when specifying the new table.
40  * For simplicity, we explicitly add NOT NULL constrains with separate statements for all DBs, since it's a fast operation.
41  */
42 public class CreateTableAsBuilder {
43   private final Dialect dialect;
44   private final String tableName;
45   private final String fromTableName;
46   private final List<Column> columns = new ArrayList<>();
47
48   public CreateTableAsBuilder(Dialect dialect, String tableName, String fromTableName) {
49     this.dialect = requireNonNull(dialect, "dialect can't be null");
50     this.tableName = validateTableName(tableName);
51     this.fromTableName = validateTableName(fromTableName);
52     checkArgument(!tableName.equals(fromTableName), "Table names must be different");
53   }
54
55   public CreateTableAsBuilder addColumn(ColumnDef column) {
56     columns.add(new Column(column, null));
57     return this;
58   }
59
60   public CreateTableAsBuilder addColumnWithCast(ColumnDef column, String castFrom) {
61     columns.add(new Column(column, castFrom));
62     return this;
63   }
64
65   public List<String> build() {
66     checkState(!columns.isEmpty(), "Columns need to be specified");
67
68     List<String> sql = new ArrayList<>();
69
70     String select = columns.stream().map(this::toSelect).collect(Collectors.joining(", "));
71
72     if (dialect.getId().equals(MsSql.ID)) {
73       sql.add("SELECT " + select + " INTO " + tableName + " FROM " + fromTableName);
74     } else {
75       StringBuilder sb = new StringBuilder("CREATE TABLE " + tableName + " (");
76       appendColumnNames(sb);
77       sb.append(") AS (SELECT ").append(select).append(" FROM ").append(fromTableName).append(")");
78       sql.add(sb.toString());
79     }
80
81     List<Column> notNullColumns = columns.stream().filter(c -> !c.definition().isNullable()).collect(Collectors.toList());
82     for (Column c : notNullColumns) {
83       sql.addAll(new AlterColumnsBuilder(dialect, tableName).updateColumn(c.definition()).build());
84     }
85
86     return sql;
87   }
88
89   private String toSelect(Column column) {
90     if (column.castFrom() == null) {
91       return column.definition().getName();
92     }
93     // Example: CAST (metric_id AS VARCHAR(40)) AS metric_uuid
94     return "CAST (" + column.castFrom() + " AS " + column.definition().generateSqlType(dialect) + ") AS " + column.definition().getName();
95   }
96
97   private void appendColumnNames(StringBuilder res) {
98     res.append(columns.stream().map(c -> c.definition().getName()).collect(Collectors.joining(", ")));
99   }
100
101   private static class Column {
102     private final ColumnDef columnDef;
103     private final String castFrom;
104
105     public Column(ColumnDef columnDef, @Nullable String castFrom) {
106       this.columnDef = columnDef;
107       this.castFrom = castFrom;
108     }
109
110     private ColumnDef definition() {
111       return columnDef;
112     }
113
114     private String castFrom() {
115       return castFrom;
116     }
117   }
118 }