]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8025 add CreateTableBuilder
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Mon, 12 Sep 2016 15:05:18 +0000 (17:05 +0200)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 16 Sep 2016 10:22:11 +0000 (12:22 +0200)
sonar-db/src/main/java/org/sonar/db/version/CreateTableBuilder.java [new file with mode: 0644]
sonar-db/src/test/java/org/sonar/db/version/CreateTableBuilderDbTesterTest.java [new file with mode: 0644]
sonar-db/src/test/java/org/sonar/db/version/CreateTableBuilderTest.java [new file with mode: 0644]

diff --git a/sonar-db/src/main/java/org/sonar/db/version/CreateTableBuilder.java b/sonar-db/src/main/java/org/sonar/db/version/CreateTableBuilder.java
new file mode 100644 (file)
index 0000000..46c498c
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * 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(',');
+      }
+    }
+  }
+
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/version/CreateTableBuilderDbTesterTest.java b/sonar-db/src/test/java/org/sonar/db/version/CreateTableBuilderDbTesterTest.java
new file mode 100644 (file)
index 0000000..ff9ea51
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * 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);
+  }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/version/CreateTableBuilderTest.java b/sonar-db/src/test/java/org/sonar/db/version/CreateTableBuilderTest.java
new file mode 100644 (file)
index 0000000..160d6c8
--- /dev/null
@@ -0,0 +1,251 @@
+/*
+ * 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";
+  }
+
+}