3 * Copyright (C) 2009-2023 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 com.tngtech.java.junit.dataprovider.DataProvider;
23 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
24 import com.tngtech.java.junit.dataprovider.UseDataProvider;
25 import java.util.Arrays;
26 import java.util.List;
27 import org.junit.Test;
28 import org.junit.runner.RunWith;
29 import org.sonar.db.dialect.Dialect;
30 import org.sonar.db.dialect.H2;
31 import org.sonar.db.dialect.MsSql;
32 import org.sonar.db.dialect.Oracle;
33 import org.sonar.db.dialect.PostgreSql;
34 import org.sonar.server.platform.db.migration.def.ColumnDef;
35 import org.sonar.server.platform.db.migration.def.TinyIntColumnDef;
37 import static org.assertj.core.api.Assertions.assertThat;
38 import static org.assertj.core.api.Assertions.assertThatThrownBy;
39 import static org.assertj.core.api.Assertions.fail;
40 import static org.mockito.Mockito.mock;
41 import static org.sonar.server.platform.db.migration.def.BigIntegerColumnDef.newBigIntegerColumnDefBuilder;
42 import static org.sonar.server.platform.db.migration.def.BlobColumnDef.newBlobColumnDefBuilder;
43 import static org.sonar.server.platform.db.migration.def.BooleanColumnDef.newBooleanColumnDefBuilder;
44 import static org.sonar.server.platform.db.migration.def.ClobColumnDef.newClobColumnDefBuilder;
45 import static org.sonar.server.platform.db.migration.def.DecimalColumnDef.newDecimalColumnDefBuilder;
46 import static org.sonar.server.platform.db.migration.def.IntegerColumnDef.newIntegerColumnDefBuilder;
47 import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;
48 import static org.sonar.server.platform.db.migration.sql.CreateTableBuilder.ColumnFlag.AUTO_INCREMENT;
50 @RunWith(DataProviderRunner.class)
51 public class CreateTableBuilderTest {
52 private static final H2 H2 = new H2();
53 private static final Oracle ORACLE = new Oracle();
54 private static final PostgreSql POSTGRESQL = new PostgreSql();
55 private static final MsSql MS_SQL = new MsSql();
56 private static final Dialect[] ALL_DIALECTS = {H2, MS_SQL, POSTGRESQL, ORACLE};
57 private static final String TABLE_NAME = "table_42";
58 private CreateTableBuilder underTest = new CreateTableBuilder(mock(Dialect.class), TABLE_NAME);
61 public void constructor_fails_with_NPE_if_dialect_is_null() {
62 assertThatThrownBy(() -> new CreateTableBuilder(null, TABLE_NAME))
63 .isInstanceOf(NullPointerException.class)
64 .hasMessageContaining("dialect can't be null");
68 public void constructor_fails_with_NPE_if_tablename_is_null() {
69 assertThatThrownBy(() -> new CreateTableBuilder(mock(Dialect.class), null))
70 .isInstanceOf(NullPointerException.class)
71 .hasMessageContaining("Table name can't be null");
75 public void constructor_throws_IAE_if_table_name_is_not_lowercase() {
76 assertThatThrownBy(() -> new CreateTableBuilder(mock(Dialect.class), "Tooo"))
77 .isInstanceOf(IllegalArgumentException.class)
78 .hasMessageContaining("Table name must be lower case and contain only alphanumeric chars or '_', got 'Tooo");
82 public void constructor_throws_IAE_if_table_name_is_26_chars_long() {
83 assertThatThrownBy(() -> new CreateTableBuilder(mock(Dialect.class), "abcdefghijklmnopqrstuvwxyz"))
84 .isInstanceOf(IllegalArgumentException.class)
85 .hasMessageContaining("Table name length can't be more than 25");
89 public void constructor_does_not_fail_if_table_name_is_25_chars_long() {
90 new CreateTableBuilder(mock(Dialect.class), "abcdefghijklmnopqrstuvwxy");
94 public void constructor_does_not_fail_if_table_name_contains_ascii_letters() {
95 new CreateTableBuilder(mock(Dialect.class), "abcdefghijklmnopqrstuvwxy");
96 new CreateTableBuilder(mock(Dialect.class), "z");
100 public void constructor_throws_IAE_if_table_name_starts_with_underscore() {
101 assertThatThrownBy(() -> new CreateTableBuilder(mock(Dialect.class), "_a"))
102 .isInstanceOf(IllegalArgumentException.class)
103 .hasMessageContaining("Table name must not start by a number or '_', got '_a'");
107 @UseDataProvider("digitCharsDataProvider")
108 public void constructor_throws_IAE_if_table_name_starts_with_number(char number) {
109 assertThatThrownBy(() -> new CreateTableBuilder(mock(Dialect.class), number + "a"))
110 .isInstanceOf(IllegalArgumentException.class)
111 .hasMessageContaining("Table name must not start by a number or '_', got '" + number + "a'");
115 public static Object[][] digitCharsDataProvider() {
116 return new Object[][]{
131 public void constructor_does_not_fail_if_table_name_contains_underscore_or_numbers() {
132 new CreateTableBuilder(mock(Dialect.class), "a1234567890");
133 new CreateTableBuilder(mock(Dialect.class), "a_");
137 public void build_throws_ISE_if_no_column_has_been_set() {
138 assertThatThrownBy(() -> underTest.build())
139 .isInstanceOf(IllegalStateException.class)
140 .hasMessageContaining("at least one column must be specified");
144 public void addColumn_throws_NPE_if_ColumnDef_is_null() {
145 assertThatThrownBy(() -> underTest.addColumn(null))
146 .isInstanceOf(NullPointerException.class)
147 .hasMessageContaining("column def can't be null");
151 public void addPkColumn_throws_NPE_if_ColumnDef_is_null() {
152 assertThatThrownBy(() -> underTest.addPkColumn(null))
153 .isInstanceOf(NullPointerException.class)
154 .hasMessageContaining("column def can't be null");
158 public void addPkColumn_throws_IAE_when_AUTO_INCREMENT_flag_is_provided_with_column_name_other_than_id() {
159 assertThatThrownBy(() -> underTest.addPkColumn(newIntegerColumnDefBuilder().setColumnName("toto").build(), AUTO_INCREMENT))
160 .isInstanceOf(IllegalArgumentException.class)
161 .hasMessageContaining("Auto increment column name must be id");
165 public void addPkColumn_throws_ISE_when_adding_multiple_autoincrement_columns() {
166 underTest.addPkColumn(newIntegerColumnDefBuilder().setColumnName("id").setIsNullable(false).build(), AUTO_INCREMENT);
168 assertThatThrownBy(() -> underTest.addPkColumn(newIntegerColumnDefBuilder().setColumnName("id").setIsNullable(false).build(), AUTO_INCREMENT))
169 .isInstanceOf(IllegalStateException.class)
170 .hasMessageContaining("There can't be more than one auto increment column");
174 public void addPkColumn_throws_IAE_when_AUTO_INCREMENT_flag_is_provided_with_def_other_than_Integer_and_BigInteger() {
175 ColumnDef[] columnDefs = {
176 newBooleanColumnDefBuilder().setColumnName("id").build(),
177 newClobColumnDefBuilder().setColumnName("id").build(),
178 newDecimalColumnDefBuilder().setColumnName("id").build(),
179 new TinyIntColumnDef.Builder().setColumnName("id").build(),
180 newVarcharColumnDefBuilder().setColumnName("id").setLimit(40).build(),
181 newBlobColumnDefBuilder().setColumnName("id").build()
183 Arrays.stream(columnDefs)
184 .forEach(columnDef -> {
186 underTest.addPkColumn(columnDef, AUTO_INCREMENT);
187 fail("A IllegalArgumentException should have been raised");
188 } catch (IllegalArgumentException e) {
189 assertThat(e).hasMessage("Auto increment column must either be BigInteger or Integer");
195 public void addPkColumn_throws_IAE_when_AUTO_INCREMENT_flag_is_provided_and_column_is_nullable() {
196 ColumnDef[] columnDefs = {
197 newIntegerColumnDefBuilder().setColumnName("id").build(),
198 newBigIntegerColumnDefBuilder().setColumnName("id").build()
200 Arrays.stream(columnDefs)
201 .forEach(columnDef -> {
203 underTest.addPkColumn(columnDef, AUTO_INCREMENT);
204 fail("A IllegalArgumentException should have been raised");
205 } catch (IllegalArgumentException e) {
206 assertThat(e).hasMessage("Auto increment column can't be nullable");
212 public void build_sets_type_SERIAL_for_autoincrement_integer_pk_column_on_Postgresql() {
213 List<String> stmts = new CreateTableBuilder(POSTGRESQL, TABLE_NAME)
214 .addPkColumn(newIntegerColumnDefBuilder().setColumnName("id").setIsNullable(false).build(), AUTO_INCREMENT)
216 assertThat(stmts).hasSize(1);
217 assertThat(stmts.iterator().next())
219 "CREATE TABLE table_42 (id SERIAL NOT NULL, CONSTRAINT pk_table_42 PRIMARY KEY (id))");
223 public void build_sets_type_BIGSERIAL_for_autoincrement_biginteger_pk_column_on_Postgresql() {
224 List<String> stmts = new CreateTableBuilder(POSTGRESQL, TABLE_NAME)
225 .addPkColumn(newBigIntegerColumnDefBuilder().setColumnName("id").setIsNullable(false).build(), AUTO_INCREMENT)
227 assertThat(stmts).hasSize(1);
228 assertThat(stmts.iterator().next())
230 "CREATE TABLE table_42 (id BIGSERIAL NOT NULL, CONSTRAINT pk_table_42 PRIMARY KEY (id))");
234 public void build_generates_a_create_trigger_statement_when_an_autoincrement_pk_column_is_specified_and_on_Oracle() {
235 List<String> stmts = new CreateTableBuilder(ORACLE, TABLE_NAME)
236 .addPkColumn(newIntegerColumnDefBuilder().setColumnName("id").setIsNullable(false).build(), AUTO_INCREMENT)
238 assertThat(stmts).hasSize(3);
239 assertThat(stmts.get(0))
240 .isEqualTo("CREATE TABLE table_42 (id NUMBER(38,0) NOT NULL, CONSTRAINT pk_table_42 PRIMARY KEY (id))");
241 assertThat(stmts.get(1))
242 .isEqualTo("CREATE SEQUENCE table_42_seq START WITH 1 INCREMENT BY 1");
243 assertThat(stmts.get(2))
244 .isEqualTo("CREATE OR REPLACE TRIGGER table_42_idt" +
245 " BEFORE INSERT ON table_42" +
248 " IF :new.id IS null THEN" +
249 " SELECT table_42_seq.nextval INTO :new.id FROM dual;" +
255 public void build_adds_IDENTITY_clause_on_MsSql() {
256 List<String> stmts = new CreateTableBuilder(MS_SQL, TABLE_NAME)
257 .addPkColumn(newIntegerColumnDefBuilder().setColumnName("id").setIsNullable(false).build(), AUTO_INCREMENT)
259 assertThat(stmts).hasSize(1);
260 assertThat(stmts.iterator().next())
262 "CREATE TABLE table_42 (id INT NOT NULL IDENTITY (1,1), CONSTRAINT pk_table_42 PRIMARY KEY (id))");
266 public void build_adds_AUTO_INCREMENT_clause_on_H2() {
267 List<String> stmts = new CreateTableBuilder(H2, TABLE_NAME)
268 .addPkColumn(newIntegerColumnDefBuilder().setColumnName("id").setIsNullable(false).build(), AUTO_INCREMENT)
270 assertThat(stmts).hasSize(1);
271 assertThat(stmts.iterator().next())
273 "CREATE TABLE table_42 (id INTEGER NOT NULL AUTO_INCREMENT (1,1), CONSTRAINT pk_table_42 PRIMARY KEY (id))");
277 public void withPkConstraintName_throws_NPE_if_name_is_null() {
278 assertThatThrownBy(() -> underTest.withPkConstraintName(null))
279 .isInstanceOf(NullPointerException.class)
280 .hasMessageContaining("Constraint name can't be null");
284 public void withPkConstraintName_throws_IAE_if_name_is_not_lowercase() {
285 assertThatThrownBy(() -> underTest.withPkConstraintName("Too"))
286 .isInstanceOf(IllegalArgumentException.class)
287 .hasMessageContaining("Constraint name must be lower case and contain only alphanumeric chars or '_', got 'Too'");
291 public void withPkConstraintName_throws_IAE_if_name_is_more_than_30_char_long() {
292 assertThatThrownBy(() -> underTest.withPkConstraintName("abcdefghijklmnopqrstuvwxyzabcdf"))
293 .isInstanceOf(IllegalArgumentException.class)
294 .hasMessageContaining("Constraint name length can't be more than 30");
298 public void withPkConstraintName_throws_IAE_if_name_starts_with_underscore() {
299 assertThatThrownBy(() -> underTest.withPkConstraintName("_a"))
300 .isInstanceOf(IllegalArgumentException.class)
301 .hasMessageContaining("Constraint name must not start by a number or '_', got '_a'");
305 @UseDataProvider("digitCharsDataProvider")
306 public void withPkConstraintName_throws_IAE_if_name_starts_with_number(char number) {
307 assertThatThrownBy(() -> underTest.withPkConstraintName(number + "a"))
308 .isInstanceOf(IllegalArgumentException.class)
309 .hasMessageContaining("Constraint name must not start by a number or '_', got '" + number + "a'");
313 public void withPkConstraintName_does_not_fail_if_name_is_30_char_long() {
314 underTest.withPkConstraintName("abcdefghijklmnopqrstuvwxyzabcd");
318 public void withPkConstraintName_does_not_fail_if_name_contains_ascii_letters() {
319 underTest.withPkConstraintName("abcdefghijklmnopqrstuvwxyz");
323 public void withPkConstraintName_does_not_fail_if_name_contains_underscore() {
324 underTest.withPkConstraintName("a_");
328 public void withPkConstraintName_does_not_fail_if_name_contains_numbers() {
329 underTest.withPkConstraintName("a0123456789");
333 public void build_adds_NULL_when_column_is_nullable_for_all_DBs() {
334 Arrays.stream(ALL_DIALECTS)
335 .forEach(dialect -> {
336 List<String> stmts = new CreateTableBuilder(dialect, TABLE_NAME)
337 .addColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col").build())
339 assertThat(stmts).hasSize(1);
341 assertThat(stmts.iterator().next())
342 .startsWith("CREATE TABLE " + TABLE_NAME + " (" +
344 bigIntSqlType(dialect) + " NULL" +
350 public void build_adds_NOT_NULL_when_column_is_not_nullable_for_all_DBs() {
351 Arrays.stream(ALL_DIALECTS)
352 .forEach(dialect -> {
353 List<String> stmts = new CreateTableBuilder(dialect, TABLE_NAME)
354 .addColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col").setIsNullable(false).build())
356 assertThat(stmts).hasSize(1);
358 assertThat(stmts.iterator().next())
359 .startsWith("CREATE TABLE " + TABLE_NAME + " (" +
361 bigIntSqlType(dialect) +
368 public void build_of_single_column_table() {
369 List<String> stmts = new CreateTableBuilder(H2, TABLE_NAME)
370 .addColumn(newBooleanColumnDefBuilder().setColumnName("bool_col_1").build())
372 assertThat(stmts).hasSize(1);
374 assertThat(stmts.iterator().next()).isEqualTo("CREATE TABLE table_42 (bool_col_1 BOOLEAN NULL)");
378 public void build_table_with_pk() {
379 List<String> stmts = new CreateTableBuilder(H2, TABLE_NAME)
380 .addPkColumn(newBooleanColumnDefBuilder().setColumnName("bool_col").build())
381 .addColumn(newVarcharColumnDefBuilder().setColumnName("varchar_col").setLimit(40).build())
383 assertThat(stmts).hasSize(1);
385 assertThat(stmts.iterator().next())
386 .isEqualTo("CREATE TABLE " + TABLE_NAME + " (" +
387 "bool_col BOOLEAN NULL," +
388 "varchar_col VARCHAR (40) NULL," +
389 " CONSTRAINT pk_" + TABLE_NAME + " PRIMARY KEY (bool_col)" +
395 public void build_adds_PRIMARY_KEY_constraint_on_single_column_with_name_computed_from_tablename() {
396 Arrays.asList(ALL_DIALECTS)
397 .forEach(dialect -> {
398 List<String> stmts = new CreateTableBuilder(dialect, TABLE_NAME)
399 .addPkColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col").setIsNullable(false).build())
401 assertThat(stmts).hasSize(1);
403 assertThat(stmts.iterator().next())
404 .startsWith("CREATE TABLE " + TABLE_NAME + " (" +
405 "bg_col " + bigIntSqlType(dialect) + " NOT NULL," +
406 " CONSTRAINT pk_" + TABLE_NAME + " PRIMARY KEY (bg_col)" +
412 public void build_adds_PRIMARY_KEY_constraint_on_single_column_with_lower_case_of_specified_name() {
413 Arrays.asList(ALL_DIALECTS)
414 .forEach(dialect -> {
415 List<String> stmts = new CreateTableBuilder(dialect, TABLE_NAME)
416 .addPkColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col").setIsNullable(false).build())
417 .withPkConstraintName("my_pk")
419 assertThat(stmts).hasSize(1);
421 assertThat(stmts.iterator().next())
422 .startsWith("CREATE TABLE " + TABLE_NAME + " (" +
424 bigIntSqlType(dialect) +
426 " CONSTRAINT my_pk PRIMARY KEY (bg_col)" +
432 public void build_adds_PRIMARY_KEY_constraint_on_multiple_columns_with_name_computed_from_tablename() {
433 Arrays.asList(ALL_DIALECTS)
434 .forEach(dialect -> {
435 List<String> stmts = new CreateTableBuilder(dialect, TABLE_NAME)
436 .addPkColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col_1").setIsNullable(false).build())
437 .addPkColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col_2").setIsNullable(false).build())
439 assertThat(stmts).hasSize(1);
441 assertThat(stmts.iterator().next())
442 .startsWith("CREATE TABLE " + TABLE_NAME + " (" +
443 "bg_col_1 " + bigIntSqlType(dialect) + " NOT NULL," +
444 "bg_col_2 " + bigIntSqlType(dialect) + " NOT NULL," +
445 " CONSTRAINT pk_" + TABLE_NAME + " PRIMARY KEY (bg_col_1,bg_col_2)" +
451 public void build_adds_PRIMARY_KEY_constraint_on_multiple_columns_with_lower_case_of_specified_name() {
452 Arrays.asList(ALL_DIALECTS)
453 .forEach(dialect -> {
454 List<String> stmts = new CreateTableBuilder(dialect, TABLE_NAME)
455 .addPkColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col_1").setIsNullable(false).build())
456 .addPkColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col_2").setIsNullable(false).build())
457 .withPkConstraintName("my_pk")
459 assertThat(stmts).hasSize(1);
461 assertThat(stmts.iterator().next())
462 .startsWith("CREATE TABLE " + TABLE_NAME + " (" +
463 "bg_col_1 " + bigIntSqlType(dialect) + " NOT NULL," +
464 "bg_col_2 " + bigIntSqlType(dialect) + " NOT NULL," +
465 " CONSTRAINT my_pk PRIMARY KEY (bg_col_1,bg_col_2)" +
471 public void build_adds_DEFAULT_clause_on_varchar_column_on_H2() {
472 verifyDefaultClauseOnVarcharColumn(H2, "CREATE TABLE table_42 (status VARCHAR (1) DEFAULT 'P' NOT NULL)");
476 public void build_adds_DEFAULT_clause_on_varchar_column_on_MSSQL() {
477 verifyDefaultClauseOnVarcharColumn(MS_SQL, "CREATE TABLE table_42 (status NVARCHAR (1) DEFAULT 'P' NOT NULL)");
481 public void build_adds_DEFAULT_clause_on_varchar_column_on_Oracle() {
482 verifyDefaultClauseOnVarcharColumn(ORACLE, "CREATE TABLE table_42 (status VARCHAR2 (1 CHAR) DEFAULT 'P' NOT NULL)");
486 public void build_adds_DEFAULT_clause_on_varchar_column_on_PostgreSQL() {
487 verifyDefaultClauseOnVarcharColumn(POSTGRESQL, "CREATE TABLE table_42 (status VARCHAR (1) DEFAULT 'P' NOT NULL)");
490 private static void verifyDefaultClauseOnVarcharColumn(Dialect dialect, String expectedSql) {
491 List<String> stmts = new CreateTableBuilder(dialect, TABLE_NAME)
492 .addColumn(newVarcharColumnDefBuilder().setColumnName("status").setLimit(1).setIsNullable(false).setDefaultValue("P").build())
494 assertThat(stmts).containsExactly(expectedSql);
498 public void build_adds_DEFAULT_clause_on_boolean_column_on_H2() {
499 verifyDefaultClauseOnBooleanColumn(H2, "CREATE TABLE table_42 (enabled BOOLEAN DEFAULT true NOT NULL)");
503 public void build_adds_DEFAULT_clause_on_boolean_column_on_MSSQL() {
504 verifyDefaultClauseOnBooleanColumn(MS_SQL, "CREATE TABLE table_42 (enabled BIT DEFAULT 1 NOT NULL)");
508 public void build_adds_DEFAULT_clause_on_boolean_column_on_Oracle() {
509 verifyDefaultClauseOnBooleanColumn(ORACLE, "CREATE TABLE table_42 (enabled NUMBER(1) DEFAULT 1 NOT NULL)");
513 public void build_adds_DEFAULT_clause_on_boolean_column_on_PostgreSQL() {
514 verifyDefaultClauseOnBooleanColumn(POSTGRESQL, "CREATE TABLE table_42 (enabled BOOLEAN DEFAULT true NOT NULL)");
517 private static void verifyDefaultClauseOnBooleanColumn(Dialect dialect, String expectedSql) {
518 List<String> stmts = new CreateTableBuilder(dialect, TABLE_NAME)
519 .addColumn(newBooleanColumnDefBuilder().setColumnName("enabled").setIsNullable(false).setDefaultValue(true).build())
521 assertThat(stmts).containsExactly(expectedSql);
524 private static String bigIntSqlType(Dialect dialect) {
525 return Oracle.ID.equals(dialect.getId()) ? "NUMBER (38)" : "BIGINT";