org.sonar.db.version.CreateTableBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sonar-db Show documentation
Show all versions of sonar-db Show documentation
Create and request SonarQube schema
/*
* 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 com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.stream.Stream;
import javax.annotation.CheckForNull;
import org.sonar.core.util.stream.Collectors;
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 com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Stream.of;
import static org.sonar.db.version.Validations.CONSTRAINT_NAME_MAX_SIZE;
import static org.sonar.db.version.Validations.TABLE_NAME_MAX_SIZE;
import static org.sonar.db.version.Validations.checkDbIdentifier;
public class CreateTableBuilder {
private final Dialect dialect;
private final String tableName;
private final List columnDefs = new ArrayList<>();
private final List pkColumnDefs = new ArrayList<>(2);
private final Multimap flagsByColumn = HashMultimap.create(1, 1);
@CheckForNull
private String pkConstraintName;
public CreateTableBuilder(Dialect dialect, String tableName) {
this.dialect = requireNonNull(dialect, "dialect can't be null");
this.tableName = checkDbIdentifier(tableName, "Table name", TABLE_NAME_MAX_SIZE);
}
public List build() {
checkState(!columnDefs.isEmpty() || !pkColumnDefs.isEmpty(), "at least one column must be specified");
return Stream.concat(of(createTableStatement()), createOracleAutoIncrementStatements())
.collect(Collectors.toList());
}
public CreateTableBuilder addColumn(ColumnDef columnDef) {
columnDefs.add(requireNonNull(columnDef, "column def can't be null"));
return this;
}
public CreateTableBuilder addPkColumn(ColumnDef columnDef, ColumnFlag... flags) {
pkColumnDefs.add(requireNonNull(columnDef, "column def can't be null"));
addFlags(columnDef, flags);
return this;
}
private void addFlags(ColumnDef columnDef, ColumnFlag[] flags) {
Arrays.stream(flags)
.forEach(flag -> {
requireNonNull(flag, "flag can't be null");
if (flag == ColumnFlag.AUTO_INCREMENT) {
validateColumnDefForAutoIncrement(columnDef);
}
flagsByColumn.put(columnDef, flag);
});
}
private void validateColumnDefForAutoIncrement(ColumnDef columnDef) {
checkArgument("id".equals(columnDef.getName()),
"Auto increment column name must be id");
checkArgument(columnDef instanceof BigIntegerColumnDef
|| columnDef instanceof IntegerColumnDef,
"Auto increment column must either be BigInteger or Integer");
checkArgument(!columnDef.isNullable(),
"Auto increment column can't be nullable");
checkState(pkColumnDefs.stream().filter(this::isAutoIncrement).count() == 0,
"There can't be more than one auto increment column");
}
public CreateTableBuilder withPkConstraintName(String pkConstraintName) {
this.pkConstraintName = checkDbIdentifier(pkConstraintName, "Primary key constraint name", CONSTRAINT_NAME_MAX_SIZE);
return this;
}
private String createTableStatement() {
StringBuilder res = new StringBuilder("CREATE TABLE ");
res.append(tableName);
res.append(" (");
appendPkColumns(res);
appendColumns(res, dialect, columnDefs);
appendPkConstraint(res);
res.append(')');
appendCollationClause(res, dialect);
return res.toString();
}
private void appendPkColumns(StringBuilder res) {
appendColumns(res, dialect, pkColumnDefs);
if (!pkColumnDefs.isEmpty() && !columnDefs.isEmpty()) {
res.append(',');
}
}
private void appendColumns(StringBuilder res, Dialect dialect, List columnDefs) {
if (columnDefs.isEmpty()) {
return;
}
Iterator columnDefIterator = columnDefs.iterator();
while (columnDefIterator.hasNext()) {
ColumnDef columnDef = columnDefIterator.next();
res.append(columnDef.getName());
res.append(' ');
appendDataType(res, dialect, columnDef);
appendNullConstraint(res, columnDef);
appendColumnFlags(res, dialect, columnDef);
if (columnDefIterator.hasNext()) {
res.append(',');
}
}
}
private void appendDataType(StringBuilder res, Dialect dialect, ColumnDef columnDef) {
if (PostgreSql.ID.equals(dialect.getId()) && isAutoIncrement(columnDef)) {
if (columnDef instanceof BigIntegerColumnDef) {
res.append("BIGSERIAL");
} else if (columnDef instanceof IntegerColumnDef) {
res.append("SERIAL");
} else {
throw new IllegalStateException("Column with autoincrement is neither BigInteger nor Integer");
}
} else {
res.append(columnDef.generateSqlType(dialect));
}
}
private boolean isAutoIncrement(ColumnDef columnDef) {
Collection columnFlags = this.flagsByColumn.get(columnDef);
return columnFlags != null && columnFlags.contains(ColumnFlag.AUTO_INCREMENT);
}
private static void appendNullConstraint(StringBuilder res, ColumnDef columnDef) {
if (columnDef.isNullable()) {
res.append(" NULL");
} else {
res.append(" NOT NULL");
}
}
private void appendColumnFlags(StringBuilder res, Dialect dialect, ColumnDef columnDef) {
Collection columnFlags = this.flagsByColumn.get(columnDef);
if (columnFlags != null && columnFlags.contains(ColumnFlag.AUTO_INCREMENT)) {
switch (dialect.getId()) {
case Oracle.ID:
// no auto increment on Oracle, must use a sequence
break;
case PostgreSql.ID:
// no specific clause on PostgreSQL but a specific type
break;
case MsSql.ID:
res.append(" IDENTITY (0,1)");
break;
case MySql.ID:
res.append(" AUTO_INCREMENT");
break;
case H2.ID:
res.append(" AUTO_INCREMENT (0,1)");
break;
default:
throw new IllegalArgumentException("Unsupported dialect id " + dialect.getId());
}
}
}
private void appendPkConstraint(StringBuilder res) {
if (pkColumnDefs.isEmpty()) {
return;
}
res.append(", ");
res.append("CONSTRAINT ");
appendPkConstraintName(res);
res.append(" PRIMARY KEY ");
res.append('(');
appendColumnNames(res, pkColumnDefs);
res.append(')');
}
private void appendPkConstraintName(StringBuilder res) {
if (pkConstraintName == null) {
res.append("pk_").append(tableName);
} else {
res.append(pkConstraintName.toLowerCase(Locale.ENGLISH));
}
}
private static void appendColumnNames(StringBuilder res, List columnDefs) {
Iterator columnDefIterator = columnDefs.iterator();
while (columnDefIterator.hasNext()) {
res.append(columnDefIterator.next().getName());
if (columnDefIterator.hasNext()) {
res.append(',');
}
}
}
private static void appendCollationClause(StringBuilder res, Dialect dialect) {
if (MySql.ID.equals(dialect.getId())) {
res.append(" ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin");
}
}
private Stream createOracleAutoIncrementStatements() {
if (!Oracle.ID.equals(dialect.getId())) {
return Stream.empty();
}
return pkColumnDefs.stream()
.filter(this::isAutoIncrement)
.flatMap(columnDef -> of(createSequenceFor(tableName), createTriggerFor(tableName)));
}
private static String createSequenceFor(String tableName) {
return "CREATE SEQUENCE " + tableName + "_seq START WITH 1 INCREMENT BY 1";
}
private static String createTriggerFor(String tableName) {
return "CREATE OR REPLACE TRIGGER " + tableName + "_idt" +
" BEFORE INSERT ON " + tableName +
" FOR EACH ROW" +
" BEGIN" +
" IF :new.id IS null THEN" +
" SELECT " + tableName + "_seq.nextval INTO :new.id FROM dual;" +
" END IF;" +
" END;";
}
public enum ColumnFlag {
AUTO_INCREMENT
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy