--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import javax.annotation.CheckForNull;
+import org.sonar.db.dialect.Dialect;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
+public class CreateTableBuilder {
+ private final Dialect dialect;
+ private final String tableName;
+ private final List<ColumnDef> columnDefs = new ArrayList<>();
+ private final List<ColumnDef> pkColumnDefs = new ArrayList<>(2);
+ @CheckForNull
+ private String pkConstraintName;
+
+ public CreateTableBuilder(Dialect dialect, String tableName) {
+ this.dialect = requireNonNull(dialect, "dialect can't be null");
+ this.tableName = requireNonNull(tableName, "table name can't be null").toLowerCase(Locale.ENGLISH);
+ }
+
+ public List<String> build() {
+ checkState(!columnDefs.isEmpty() || !pkColumnDefs.isEmpty(), "at least one column must be specified");
+
+ return Collections.singletonList(createTableStatement());
+ }
+
+ public CreateTableBuilder addColumn(ColumnDef columnDef) {
+ columnDefs.add(requireNonNull(columnDef, "column def can't be null"));
+ return this;
+ }
+
+ public CreateTableBuilder addPkColumn(ColumnDef columnDef) {
+ pkColumnDefs.add(requireNonNull(columnDef, "column def can't be null"));
+ return this;
+ }
+
+ public CreateTableBuilder withPkConstraintName(String pkConstraintName) {
+ this.pkConstraintName = requireNonNull(pkConstraintName, "primary key constraint name can't be null");
+ return this;
+ }
+
+ private String createTableStatement() {
+ StringBuilder res = new StringBuilder("CREATE TABLE ");
+ res.append(tableName);
+ res.append(" (");
+ addPkColumns(res);
+ addColumns(res, dialect, columnDefs);
+ addPkConstraint(res);
+ res.append(")");
+ return res.toString();
+ }
+
+ private void addPkColumns(StringBuilder res) {
+ addColumns(res, dialect, pkColumnDefs);
+ if (!pkColumnDefs.isEmpty() && !columnDefs.isEmpty()) {
+ res.append(',');
+ }
+ }
+
+ private static void addColumns(StringBuilder res, Dialect dialect, List<ColumnDef> columnDefs) {
+ if (columnDefs.isEmpty()) {
+ return;
+ }
+ Iterator<ColumnDef> columnDefIterator = columnDefs.iterator();
+ while (columnDefIterator.hasNext()) {
+ ColumnDef columnDef = columnDefIterator.next();
+ res.append(columnDef.getName()).append(' ').append(columnDef.generateSqlType(dialect));
+ addNullConstraint(res, columnDef);
+ if (columnDefIterator.hasNext()) {
+ res.append(',');
+ }
+ }
+ }
+
+ private static void addNullConstraint(StringBuilder res, ColumnDef columnDef) {
+ if (columnDef.isNullable()) {
+ res.append(" NULL");
+ } else {
+ res.append(" NOT NULL");
+ }
+ }
+
+ private void addPkConstraint(StringBuilder res) {
+ if (pkColumnDefs.isEmpty()) {
+ return;
+ }
+ res.append(", ");
+ res.append("CONSTRAINT ");
+ addPkConstraintName(res);
+ res.append(" PRIMARY KEY ");
+ res.append('(');
+ appendColumnNames(res, pkColumnDefs);
+ res.append(')');
+ }
+
+ private void addPkConstraintName(StringBuilder res) {
+ if (pkConstraintName == null) {
+ res.append("pk_").append(tableName);
+ } else {
+ res.append(pkConstraintName.toLowerCase(Locale.ENGLISH));
+ }
+ }
+
+ private static void appendColumnNames(StringBuilder res, List<ColumnDef> columnDefs) {
+ Iterator<ColumnDef> columnDefIterator = columnDefs.iterator();
+ while (columnDefIterator.hasNext()) {
+ res.append(columnDefIterator.next().getName());
+ if (columnDefIterator.hasNext()) {
+ res.append(',');
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+
+import static org.sonar.db.version.BigIntegerColumnDef.newBigIntegerColumnDefBuilder;
+import static org.sonar.db.version.BooleanColumnDef.newBooleanColumnDefBuilder;
+import static org.sonar.db.version.ClobColumnDef.newClobColumnDefBuilder;
+import static org.sonar.db.version.DecimalColumnDef.newDecimalColumnDefBuilder;
+import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class CreateTableBuilderDbTesterTest {
+ @ClassRule
+ public static final DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+ @Test
+ public void create_no_primary_key_table() {
+ String createTableStmt = new CreateTableBuilder(dbTester.getDbClient().getDatabase().getDialect(), "TABLE_1")
+ .addColumn(newBooleanColumnDefBuilder().setColumnName("bool_col_1").build())
+ .addColumn(newBooleanColumnDefBuilder().setColumnName("bool_col_2").setIsNullable(false).build())
+ .addColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col_1").build())
+ .addColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col_2").setIsNullable(false).build())
+ .addColumn(newClobColumnDefBuilder().setColumnName("clob_col_1").build())
+ .addColumn(newClobColumnDefBuilder().setColumnName("clob_col_2").setIsNullable(false).build())
+ .addColumn(newDecimalColumnDefBuilder().setColumnName("dec_col_1").build())
+ .addColumn(newDecimalColumnDefBuilder().setColumnName("dec_col_2").setIsNullable(false).build())
+ .addColumn(new TinyIntColumnDef.Builder().setColumnName("tiny_col_1").build())
+ .addColumn(new TinyIntColumnDef.Builder().setColumnName("tiny_col_2").setIsNullable(false).build())
+ .addColumn(newVarcharColumnDefBuilder().setColumnName("varchar_col_1").setLimit(40).build())
+ .addColumn(newVarcharColumnDefBuilder().setColumnName("varchar_col_2").setLimit(40).setIsNullable(false).build())
+ .build()
+ .iterator().next();
+
+ dbTester.executeDdl(createTableStmt);
+ }
+
+ @Test
+ public void create_single_column_primary_key_table() {
+ String createTableStmt = new CreateTableBuilder(dbTester.getDbClient().getDatabase().getDialect(), "TABLE_2")
+ .addPkColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col_1").setIsNullable(false).build())
+ .addColumn(newVarcharColumnDefBuilder().setColumnName("varchar_col_2").setLimit(40).setIsNullable(false).build())
+ .build()
+ .iterator().next();
+
+ dbTester.executeDdl(createTableStmt);
+ }
+
+ @Test
+ public void create_multi_column_primary_key_table() {
+ String createTableStmt = new CreateTableBuilder(dbTester.getDbClient().getDatabase().getDialect(), "TABLE_3")
+ .addPkColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col_1").setIsNullable(false).build())
+ .addPkColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col_2").setIsNullable(false).build())
+ .addColumn(newVarcharColumnDefBuilder().setColumnName("varchar_col_2").setLimit(40).setIsNullable(false).build())
+ .build()
+ .iterator().next();
+
+ dbTester.executeDdl(createTableStmt);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.db.version;
+
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.dialect.Dialect;
+import org.sonar.db.dialect.H2;
+import org.sonar.db.dialect.MsSql;
+import org.sonar.db.dialect.MySql;
+import org.sonar.db.dialect.Oracle;
+import org.sonar.db.dialect.PostgreSql;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.sonar.db.version.BigIntegerColumnDef.newBigIntegerColumnDefBuilder;
+import static org.sonar.db.version.BooleanColumnDef.newBooleanColumnDefBuilder;
+import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class CreateTableBuilderTest {
+ private static final H2 H2_DIALECT = new H2();
+ private static final Dialect[] ALL_DIALECTS = {H2_DIALECT, new MySql(), new MsSql(), new PostgreSql(), new Oracle()};
+ private static final String TABLE_NAME = "table_42";
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private CreateTableBuilder underTest = new CreateTableBuilder(mock(Dialect.class), TABLE_NAME);
+
+ @Test
+ public void constructor_fails_with_NPE_if_dialect_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("dialect can't be null");
+
+ new CreateTableBuilder(null, TABLE_NAME);
+ }
+
+ @Test
+ public void constructor_fails_with_NPE_if_tablename_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("table name can't be null");
+
+ new CreateTableBuilder(mock(Dialect.class), null);
+ }
+
+ @Test
+ public void build_throws_ISE_if_no_column_has_been_set() {
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("at least one column must be specified");
+
+ underTest.build();
+ }
+
+ @Test
+ public void addColumn_throws_NPE_if_ColumnDef_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("column def can't be null");
+
+ underTest.addColumn(null);
+ }
+
+ @Test
+ public void addPkColumn_throws_NPE_if_ColumnDef_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("column def can't be null");
+
+ underTest.addPkColumn(null);
+ }
+
+ @Test
+ public void withPkConstraintName_throws_NPE_if_ColumnDef_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("primary key constraint name can't be null");
+
+ underTest.withPkConstraintName(null);
+ }
+
+ @Test
+ public void build_lowers_case_of_table_name() {
+ List<String> stmts = new CreateTableBuilder(H2_DIALECT, "SOmE_TABLe_NamE")
+ .addColumn(newBooleanColumnDefBuilder().setColumnName("bool_col").build())
+ .build();
+ assertThat(stmts).hasSize(1);
+ assertThat(stmts.iterator().next())
+ .startsWith("CREATE TABLE some_table_name (")
+ .endsWith(")");
+ }
+
+ @Test
+ public void build_adds_NULL_when_column_is_nullable_for_all_DBs() {
+ Arrays.stream(ALL_DIALECTS)
+ .forEach(dialect -> {
+ List<String> stmts = new CreateTableBuilder(dialect, TABLE_NAME)
+ .addColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col").build())
+ .build();
+ assertThat(stmts).hasSize(1);
+
+ assertThat(stmts.iterator().next())
+ .isEqualTo("CREATE TABLE " + TABLE_NAME + " (" +
+ "bg_col " +
+ bigIntSqlType(dialect) + " NULL" +
+ ")");
+ });
+ }
+
+ @Test
+ public void build_adds_NOT_NULL_when_column_is_not_nullable_for_all_DBs() {
+ Arrays.stream(ALL_DIALECTS)
+ .forEach(dialect -> {
+ List<String> stmts = new CreateTableBuilder(dialect, TABLE_NAME)
+ .addColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col").setIsNullable(false).build())
+ .build();
+ assertThat(stmts).hasSize(1);
+
+ assertThat(stmts.iterator().next())
+ .isEqualTo("CREATE TABLE " + TABLE_NAME + " (" +
+ "bg_col " +
+ bigIntSqlType(dialect) +
+ " NOT NULL" +
+ ")");
+ });
+ }
+
+ @Test
+ public void build_of_single_column_table() {
+ List<String> stmts = new CreateTableBuilder(H2_DIALECT, TABLE_NAME)
+ .addColumn(newBooleanColumnDefBuilder().setColumnName("bool_col_1").build())
+ .build();
+ assertThat(stmts).hasSize(1);
+
+ assertThat(stmts.iterator().next()).isEqualTo("CREATE TABLE table_42 (bool_col_1 BOOLEAN NULL)");
+ }
+
+ @Test
+ public void build_table_with_pk() {
+ List<String> stmts = new CreateTableBuilder(H2_DIALECT, TABLE_NAME)
+ .addPkColumn(newBooleanColumnDefBuilder().setColumnName("bool_col").build())
+ .addColumn(newVarcharColumnDefBuilder().setColumnName("varchar_col").setLimit(40).build())
+ .build();
+ assertThat(stmts).hasSize(1);
+
+ assertThat(stmts.iterator().next())
+ .isEqualTo("CREATE TABLE " + TABLE_NAME + " (" +
+ "bool_col BOOLEAN NULL," +
+ "varchar_col VARCHAR (40) NULL," +
+ " CONSTRAINT pk_" + TABLE_NAME + " PRIMARY KEY (bool_col)" +
+ ")");
+
+ }
+
+ @Test
+ public void build_adds_PRIMARY_KEY_constraint_on_single_column_with_name_computed_from_tablename() {
+ Arrays.asList(ALL_DIALECTS)
+ .forEach(dialect -> {
+ List<String> stmts = new CreateTableBuilder(dialect, TABLE_NAME)
+ .addPkColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col").setIsNullable(false).build())
+ .build();
+ assertThat(stmts).hasSize(1);
+
+ assertThat(stmts.iterator().next())
+ .isEqualTo("CREATE TABLE " + TABLE_NAME + " (" +
+ "bg_col " + bigIntSqlType(dialect) + " NOT NULL," +
+ " CONSTRAINT pk_" + TABLE_NAME + " PRIMARY KEY (bg_col)" +
+ ")");
+ });
+ }
+
+ @Test
+ public void build_adds_PRIMARY_KEY_constraint_on_single_column_with_lower_case_of_specified_name() {
+ Arrays.asList(ALL_DIALECTS)
+ .forEach(dialect -> {
+ List<String> stmts = new CreateTableBuilder(dialect, TABLE_NAME)
+ .addPkColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col").setIsNullable(false).build())
+ .withPkConstraintName("My_PK")
+ .build();
+ assertThat(stmts).hasSize(1);
+
+ assertThat(stmts.iterator().next())
+ .isEqualTo("CREATE TABLE " + TABLE_NAME + " (" +
+ "bg_col " +
+ bigIntSqlType(dialect) +
+ " NOT NULL," +
+ " CONSTRAINT my_pk PRIMARY KEY (bg_col)" +
+ ")");
+ });
+ }
+
+ @Test
+ public void build_adds_PRIMARY_KEY_constraint_on_multiple_columns_with_name_computed_from_tablename() {
+ Arrays.asList(ALL_DIALECTS)
+ .forEach(dialect -> {
+ List<String> stmts = new CreateTableBuilder(dialect, TABLE_NAME)
+ .addPkColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col_1").setIsNullable(false).build())
+ .addPkColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col_2").setIsNullable(false).build())
+ .build();
+ assertThat(stmts).hasSize(1);
+
+ assertThat(stmts.iterator().next())
+ .isEqualTo("CREATE TABLE " + TABLE_NAME + " (" +
+ "bg_col_1 " + bigIntSqlType(dialect) + " NOT NULL," +
+ "bg_col_2 " + bigIntSqlType(dialect) + " NOT NULL," +
+ " CONSTRAINT pk_" + TABLE_NAME + " PRIMARY KEY (bg_col_1,bg_col_2)" +
+ ")");
+ });
+ }
+
+ @Test
+ public void build_adds_PRIMARY_KEY_constraint_on_multiple_columns_with_lower_case_of_specified_name() {
+ Arrays.asList(ALL_DIALECTS)
+ .forEach(dialect -> {
+ List<String> stmts = new CreateTableBuilder(dialect, TABLE_NAME)
+ .addPkColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col_1").setIsNullable(false).build())
+ .addPkColumn(newBigIntegerColumnDefBuilder().setColumnName("bg_col_2").setIsNullable(false).build())
+ .withPkConstraintName("My_PK")
+ .build();
+ assertThat(stmts).hasSize(1);
+
+ assertThat(stmts.iterator().next())
+ .isEqualTo("CREATE TABLE " + TABLE_NAME + " (" +
+ "bg_col_1 " + bigIntSqlType(dialect) + " NOT NULL," +
+ "bg_col_2 " + bigIntSqlType(dialect) + " NOT NULL," +
+ " CONSTRAINT my_pk PRIMARY KEY (bg_col_1,bg_col_2)" +
+ ")");
+ });
+ }
+
+ private static String bigIntSqlType(Dialect dialect) {
+ return Oracle.ID.equals(dialect.getId()) ? "NUMBER (38)" : "BIGINT";
+ }
+
+}