diff options
author | Sébastien Lesaint <sebastien.lesaint@sonarsource.com> | 2017-02-15 18:18:27 +0100 |
---|---|---|
committer | Sébastien Lesaint <sebastien.lesaint@sonarsource.com> | 2017-02-17 13:31:49 +0100 |
commit | 9824b94f16109305b4847a611379029695823427 (patch) | |
tree | 85b339103ce4c56df78e3e26f21117da7fb04976 /server/sonar-db-core | |
parent | 0d3be9943570cf5bfb7fbb72fdfe5a6fcb2798c0 (diff) | |
download | sonarqube-9824b94f16109305b4847a611379029695823427.tar.gz sonarqube-9824b94f16109305b4847a611379029695823427.zip |
split sonar-db into server/sonar-db-core and server/sonar-db-dao
Diffstat (limited to 'server/sonar-db-core')
63 files changed, 6684 insertions, 0 deletions
diff --git a/server/sonar-db-core/pom.xml b/server/sonar-db-core/pom.xml new file mode 100644 index 00000000000..9db00b7555c --- /dev/null +++ b/server/sonar-db-core/pom.xml @@ -0,0 +1,223 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.sonarsource.sonarqube</groupId> + <artifactId>server</artifactId> + <version>6.4-SNAPSHOT</version> + </parent> + + <artifactId>sonar-db-core</artifactId> + + <name>SonarQube :: Database Core</name> + <description>Core Database support</description> + + <dependencies> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>commons-codec</groupId> + <artifactId>commons-codec</artifactId> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-csv</artifactId> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sonar-scanner-protocol</artifactId> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sonar-core</artifactId> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sonar-plugin-api</artifactId> + </dependency> + <dependency> + <groupId>org.mybatis</groupId> + <artifactId>mybatis</artifactId> + </dependency> + <dependency> + <groupId>commons-dbcp</groupId> + <artifactId>commons-dbcp</artifactId> + </dependency> + <dependency> + <groupId>commons-dbutils</groupId> + <artifactId>commons-dbutils</artifactId> + </dependency> + <dependency> + <groupId>net.jpountz.lz4</groupId> + <artifactId>lz4</artifactId> + </dependency> + + <!-- logging --> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-core</artifactId> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sonar-process</artifactId> + <version>${project.version}</version> + </dependency> + + <!-- tests --> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sonar-testing-harness</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.dbunit</groupId> + <artifactId>dbunit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.google.code.bean-matchers</groupId> + <artifactId>bean-matchers</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.simpleframework</groupId> + <artifactId>simple</artifactId> + <version>4.1.21</version> + <scope>test</scope> + </dependency> + + + <!-- + JDBC drivers for MyBatis integration tests. + They can't be moved to the profile run-mybatis-its because + --> + <dependency> + <groupId>mysql</groupId> + <artifactId>mysql-connector-java</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.postgresql</groupId> + <artifactId>postgresql</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.h2database</groupId> + <artifactId>h2</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <skipTests>${skipServerTests}</skipTests> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>test-jar</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>create-db</id> + <activation> + <property> + <name>env.SONARSOURCE_QA</name> + <value>true</value> + </property> + </activation> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <id>download-sq</id> + <goals> + <goal>get</goal> + </goals> + <phase>generate-test-resources</phase> + <configuration> + <artifact>${project.groupId}:sonar-application:${project.version}:zip</artifact> + <transitive>false</transitive> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.sonarsource.orchestrator</groupId> + <artifactId>orchestrator-maven-plugin</artifactId> + <version>${orchestrator.version}</version> + <executions> + <execution> + <id>create-db</id> + <goals> + <goal>create-db</goal> + </goals> + <phase>generate-test-resources</phase> + <configuration> + <sqVersion>${project.version}</sqVersion> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + + <profile> + <!-- SonarSource internal use --> + <id>with-db-drivers</id> + <activation> + <property> + <name>with-db-drivers</name> + </property> + </activation> + <dependencies> + <dependency> + <groupId>com.microsoft.sqlserver</groupId> + <artifactId>mssql-jdbc</artifactId> + </dependency> + <dependency> + <groupId>mysql</groupId> + <artifactId>mysql-connector-java</artifactId> + </dependency> + <dependency> + <groupId>org.postgresql</groupId> + <artifactId>postgresql</artifactId> + </dependency> + <dependency> + <groupId>com.oracle</groupId> + <artifactId>ojdbc6</artifactId> + </dependency> + </dependencies> + </profile> + </profiles> +</project> diff --git a/server/sonar-db-core/src/main/java/org/sonar/core/DatabaseUtils.java b/server/sonar-db-core/src/main/java/org/sonar/core/DatabaseUtils.java new file mode 100644 index 00000000000..742f61cd39a --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/core/DatabaseUtils.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.core; + +/** + * Kept for backward compatibility of plugins/libs (like sonar-license) that are directly calling classes from the core + * + * @deprecated since 5.2, should be replaced by {@link org.sonar.db.DatabaseUtils} + */ +@Deprecated +public class DatabaseUtils extends org.sonar.db.DatabaseUtils { + +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/core/package-info.java b/server/sonar-db-core/src/main/java/org/sonar/core/package-info.java new file mode 100644 index 00000000000..d0f6201b2b6 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/core/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.core; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/Database.java b/server/sonar-db-core/src/main/java/org/sonar/db/Database.java new file mode 100644 index 00000000000..c5f7862a83f --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/Database.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +import javax.sql.DataSource; +import org.picocontainer.Startable; +import org.sonar.db.dialect.Dialect; + +/** + * @since 2.12 + */ +public interface Database extends Startable { + /** + * Returns the configured datasource. Null as long as start() is not executed. + */ + DataSource getDataSource(); + + /** + * @return the dialect or null if start() has not been executed + */ + Dialect getDialect(); + + void enableSqlLogging(boolean enable); +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/DatabaseChecker.java b/server/sonar-db-core/src/main/java/org/sonar/db/DatabaseChecker.java new file mode 100644 index 00000000000..110fcae0daa --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/DatabaseChecker.java @@ -0,0 +1,105 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; +import org.apache.commons.lang.StringUtils; +import org.picocontainer.Startable; +import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.Version; +import org.sonar.api.utils.log.Loggers; +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; + +/** + * Fail-fast checks of some database requirements + */ +public class DatabaseChecker implements Startable { + + private static final Map<String, Version> MINIMAL_SUPPORTED_DB_VERSIONS = ImmutableMap.of( + // MsSQL 2012 is 11.x + // MsSQL 2014 is 12.x + // https://support.microsoft.com/en-us/kb/321185 + MsSql.ID, Version.create(11, 0, 0), + MySql.ID, Version.create(5, 6, 0), + Oracle.ID, Version.create(11, 0, 0), + PostgreSql.ID, Version.create(8, 0, 0)); + + private final Database db; + + public DatabaseChecker(Database db) { + this.db = db; + } + + @Override + public void start() { + try { + checkMinDatabaseVersion(); + + // additional checks + if (H2.ID.equals(db.getDialect().getId())) { + Loggers.get(DatabaseChecker.class).warn("H2 database should be used for evaluation purpose only"); + } else if (Oracle.ID.equals(db.getDialect().getId())) { + checkOracleDriverVersion(); + } + } catch (SQLException e) { + Throwables.propagate(e); + } + } + + @Override + public void stop() { + // nothing to do + } + + private void checkMinDatabaseVersion() throws SQLException { + Version minDbVersion = MINIMAL_SUPPORTED_DB_VERSIONS.get(db.getDialect().getId()); + if (minDbVersion != null) { + try (Connection connection = db.getDataSource().getConnection()) { + int dbMajorVersion = connection.getMetaData().getDatabaseMajorVersion(); + int dbMinorVersion = connection.getMetaData().getDatabaseMinorVersion(); + Version dbVersion = Version.create(dbMajorVersion, dbMinorVersion, 0); + if (dbVersion.compareTo(minDbVersion) < 0) { + throw MessageException.of(String.format( + "Unsupported %s version: %s. Minimal supported version is %s.", db.getDialect().getId(), dbVersion, minDbVersion)); + } + } + } + } + + private void checkOracleDriverVersion() throws SQLException { + try (Connection connection = db.getDataSource().getConnection()) { + String driverVersion = connection.getMetaData().getDriverVersion(); + String[] parts = StringUtils.split(driverVersion, "."); + int intVersion = Integer.parseInt(parts[0]) * 100 + Integer.parseInt(parts[1]); + if (intVersion < 1102) { + throw MessageException.of(String.format( + "Unsupported Oracle driver version: %s. Minimal supported version is 11.2.", driverVersion)); + } + } + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/DatabaseUtils.java b/server/sonar-db-core/src/main/java/org/sonar/db/DatabaseUtils.java new file mode 100644 index 00000000000..9afd83c3135 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/DatabaseUtils.java @@ -0,0 +1,291 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +import com.google.common.base.Function; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Ordering; +import com.google.common.collect.Sets; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +import static com.google.common.collect.Lists.newArrayList; +import static java.lang.String.format; + +public class DatabaseUtils { + + public static final int PARTITION_SIZE_FOR_ORACLE = 1000; + + /** + * @see DatabaseMetaData#getTableTypes() + */ + private static final String[] TABLE_TYPE = {"TABLE"}; + + public static void closeQuietly(@Nullable Connection connection) { + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + Loggers.get(DatabaseUtils.class).warn("Fail to close connection", e); + // ignore + } + } + } + + public static void closeQuietly(@Nullable Statement stmt) { + if (stmt != null) { + try { + stmt.close(); + } catch (SQLException e) { + Loggers.get(DatabaseUtils.class).warn("Fail to close statement", e); + // ignore + } + } + } + + public static void closeQuietly(@Nullable ResultSet rs) { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + Loggers.get(DatabaseUtils.class).warn("Fail to close result set", e); + // ignore + } + } + } + + /** + * Partition by 1000 elements a list of input and execute a function on each part. + * + * The goal is to prevent issue with ORACLE when there's more than 1000 elements in a 'in ('X', 'Y', ...)' + * and with MsSQL when there's more than 2000 parameters in a query + */ + public static <OUTPUT, INPUT extends Comparable<INPUT>> List<OUTPUT> executeLargeInputs(Collection<INPUT> input, Function<List<INPUT>, List<OUTPUT>> function) { + return executeLargeInputs(input, function, size -> size == 0 ? Collections.emptyList() : new ArrayList<>(size)); + } + + public static <OUTPUT, INPUT extends Comparable<INPUT>> Set<OUTPUT> executeLargeInputsIntoSet(Collection<INPUT> input, Function<List<INPUT>, Set<OUTPUT>> function) { + return executeLargeInputs(input, function, size -> size == 0 ? Collections.emptySet() : new HashSet<>(size)); + } + + private static <OUTPUT, INPUT extends Comparable<INPUT>, RESULT extends Collection<OUTPUT>> RESULT executeLargeInputs(Collection<INPUT> input, + Function<List<INPUT>, RESULT> function, java.util.function.Function<Integer, RESULT> outputInitializer) { + if (input.isEmpty()) { + return outputInitializer.apply(0); + } + RESULT results = outputInitializer.apply(input.size()); + for (List<INPUT> partition : toUniqueAndSortedPartitions(input)) { + RESULT subResults = function.apply(partition); + if (subResults != null) { + results.addAll(subResults); + } + } + return results; + } + + /** + * Partition by 1000 elements a list of input and execute a consumer on each part. + * + * The goal is to prevent issue with ORACLE when there's more than 1000 elements in a 'in ('X', 'Y', ...)' + * and with MsSQL when there's more than 2000 parameters in a query + */ + public static <INPUT extends Comparable<INPUT>> void executeLargeUpdates(Collection<INPUT> inputs, Consumer<List<INPUT>> consumer) { + Iterable<List<INPUT>> partitions = toUniqueAndSortedPartitions(inputs); + for (List<INPUT> partition : partitions) { + consumer.accept(partition); + } + } + + /** + * Partition by 1000 elements a list of input and execute a consumer on each part. + * + * The goal is to prevent issue with ORACLE when there's more than 1000 elements in a 'in ('X', 'Y', ...)' + * and with MsSQL when there's more than 2000 parameters in a query + * + * @param sqlCaller a {@link Function} which calls the SQL update/delete and returns the number of updated/deleted rows. + * + * @return the total number of updated/deleted rows (computed as the sum of the values returned by {@code sqlCaller}). + */ + public static <INPUT extends Comparable<INPUT>> int executeLargeUpdates(Collection<INPUT> inputs, Function<List<INPUT>, Integer> sqlCaller) { + Iterable<List<INPUT>> partitions = toUniqueAndSortedPartitions(inputs); + Integer res = 0; + for (List<INPUT> partition : partitions) { + res += sqlCaller.apply(partition); + } + return res; + } + + /** + * Ensure values {@code inputs} are unique (which avoids useless arguments) and sorted before creating the partition. + */ + private static <INPUT extends Comparable<INPUT>> Iterable<List<INPUT>> toUniqueAndSortedPartitions(Collection<INPUT> inputs) { + return Iterables.partition(toUniqueAndSortedList(inputs), PARTITION_SIZE_FOR_ORACLE); + } + + /** + * Ensure values {@code inputs} are unique (which avoids useless arguments) and sorted so that there is little + * variations of SQL requests over time as possible with a IN clause and/or a group of OR clauses. Such requests can + * then be more easily optimized by the SGDB engine. + */ + public static <INPUT extends Comparable<INPUT>> List<INPUT> toUniqueAndSortedList(Iterable<INPUT> inputs) { + if (inputs instanceof Set) { + // inputs are unique but order is not enforced + return Ordering.natural().immutableSortedCopy(inputs); + } + // inputs are not unique and order is not guaranteed + return Ordering.natural().immutableSortedCopy(Sets.newHashSet(inputs)); + } + + /** + * Partition by 1000 elements a list of input and execute a function on each part. + * The function has not output (ex: delete operation) + * + * The goal is to prevent issue with ORACLE when there's more than 1000 elements in a 'in ('X', 'Y', ...)' + * and with MsSQL when there's more than 2000 parameters in a query + */ + public static <INPUT> void executeLargeInputsWithoutOutput(Collection<INPUT> input, Function<List<INPUT>, Void> function) { + if (input.isEmpty()) { + return; + } + + List<List<INPUT>> partitions = Lists.partition(newArrayList(input), PARTITION_SIZE_FOR_ORACLE); + for (List<INPUT> partition : partitions) { + function.apply(partition); + } + } + + public static String repeatCondition(String sql, int count, String separator) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < count; i++) { + sb.append(sql); + if (i < count - 1) { + sb.append(" ").append(separator).append(" "); + } + } + return sb.toString(); + } + + /** + * Logback does not log exceptions associated to {@link java.sql.SQLException#getNextException()}. + * See http://jira.qos.ch/browse/LOGBACK-775 + */ + public static void log(Logger logger, SQLException e) { + SQLException next = e.getNextException(); + while (next != null) { + logger.error("SQL error: {}. Message: {}", next.getSQLState(), next.getMessage()); + next = next.getNextException(); + } + } + + @CheckForNull + public static Long getLong(ResultSet rs, String columnName) throws SQLException { + long l = rs.getLong(columnName); + return rs.wasNull() ? null : l; + } + + @CheckForNull + public static Double getDouble(ResultSet rs, String columnName) throws SQLException { + double d = rs.getDouble(columnName); + return rs.wasNull() ? null : d; + } + + @CheckForNull + public static Integer getInt(ResultSet rs, String columnName) throws SQLException { + int i = rs.getInt(columnName); + return rs.wasNull() ? null : i; + } + + @CheckForNull + public static String getString(ResultSet rs, String columnName) throws SQLException { + String s = rs.getString(columnName); + return rs.wasNull() ? null : s; + } + + @CheckForNull + public static Long getLong(ResultSet rs, int columnIndex) throws SQLException { + long l = rs.getLong(columnIndex); + return rs.wasNull() ? null : l; + } + + @CheckForNull + public static Double getDouble(ResultSet rs, int columnIndex) throws SQLException { + double d = rs.getDouble(columnIndex); + return rs.wasNull() ? null : d; + } + + @CheckForNull + public static Integer getInt(ResultSet rs, int columnIndex) throws SQLException { + int i = rs.getInt(columnIndex); + return rs.wasNull() ? null : i; + } + + @CheckForNull + public static String getString(ResultSet rs, int columnIndex) throws SQLException { + String s = rs.getString(columnIndex); + return rs.wasNull() ? null : s; + } + + @CheckForNull + public static Date getDate(ResultSet rs, int columnIndex) throws SQLException { + Timestamp t = rs.getTimestamp(columnIndex); + return rs.wasNull() ? null : new Date(t.getTime()); + } + + /** + * @param table case-insensitive name of table + * @return true if a table exists with this name, otherwise false + * @throws SQLException + */ + public static boolean tableExists(String table, Connection connection) { + // table type is used to speed-up Oracle by removing introspection of system tables and aliases. + try (ResultSet rs = connection.getMetaData().getTables(null, null, null, TABLE_TYPE)) { + while (rs.next()) { + String name = rs.getString("TABLE_NAME"); + if (table.equalsIgnoreCase(name)) { + return true; + } + } + return false; + } catch (SQLException e) { + throw wrapSqlException(e, "Can not check that table %s exists", table); + } + } + + public static IllegalStateException wrapSqlException(SQLException e, String message, Object... messageArgs) { + return new IllegalStateException(format(message, messageArgs), e); + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/DdlUtils.java b/server/sonar-db-core/src/main/java/org/sonar/db/DdlUtils.java new file mode 100644 index 00000000000..32675984093 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/DdlUtils.java @@ -0,0 +1,71 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +import java.io.PrintWriter; +import java.sql.Connection; +import org.apache.commons.io.output.NullWriter; +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.jdbc.ScriptRunner; + +/** + * Util class to create Sonar database tables + * + * @since 2.12 + */ +public final class DdlUtils { + + private DdlUtils() { + } + + public static boolean supportsDialect(String dialect) { + return "h2".equals(dialect); + } + + /** + * The connection is commited in this method but not closed. + */ + public static void createSchema(Connection connection, String dialect, boolean createSchemaMigrations) { + if (createSchemaMigrations) { + executeScript(connection, "org/sonar/db/version/schema_migrations-" + dialect + ".ddl"); + } + executeScript(connection, "org/sonar/db/version/schema-" + dialect + ".ddl"); + executeScript(connection, "org/sonar/db/version/rows-" + dialect + ".sql"); + } + + public static void executeScript(Connection connection, String path) { + ScriptRunner scriptRunner = newScriptRunner(connection); + try { + scriptRunner.runScript(Resources.getResourceAsReader(path)); + connection.commit(); + + } catch (Exception e) { + throw new IllegalStateException("Fail to restore: " + path, e); + } + } + + private static ScriptRunner newScriptRunner(Connection connection) { + ScriptRunner scriptRunner = new ScriptRunner(connection); + scriptRunner.setDelimiter(";"); + scriptRunner.setStopOnError(true); + scriptRunner.setLogWriter(new PrintWriter(new NullWriter())); + return scriptRunner; + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/DefaultDatabase.java b/server/sonar-db-core/src/main/java/org/sonar/db/DefaultDatabase.java new file mode 100644 index 00000000000..c1a3c28dfff --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/DefaultDatabase.java @@ -0,0 +1,185 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +import ch.qos.logback.classic.Level; +import com.google.common.annotations.VisibleForTesting; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import javax.sql.DataSource; +import org.apache.commons.dbcp.BasicDataSource; +import org.apache.commons.dbcp.BasicDataSourceFactory; +import org.apache.commons.dbutils.DbUtils; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.config.Settings; +import org.sonar.api.database.DatabaseProperties; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.db.dialect.Dialect; +import org.sonar.db.dialect.DialectUtils; +import org.sonar.db.profiling.NullConnectionInterceptor; +import org.sonar.db.profiling.ProfiledConnectionInterceptor; +import org.sonar.db.profiling.ProfiledDataSource; +import org.sonar.process.logging.LogbackHelper; + +import static java.lang.String.format; + +/** + * @since 2.12 + */ +public class DefaultDatabase implements Database { + + private static final Logger LOG = Loggers.get(Database.class); + + private static final String DEFAULT_URL = "jdbc:h2:tcp://localhost/sonar"; + private static final String SONAR_JDBC = "sonar.jdbc."; + private static final String SONAR_JDBC_DIALECT = "sonar.jdbc.dialect"; + private static final String SONAR_JDBC_URL = "sonar.jdbc.url"; + + private final LogbackHelper logbackHelper; + private final Settings settings; + private ProfiledDataSource datasource; + private Dialect dialect; + private Properties properties; + + public DefaultDatabase(LogbackHelper logbackHelper, Settings settings) { + this.logbackHelper = logbackHelper; + this.settings = settings; + } + + @Override + public void start() { + try { + initSettings(); + initDataSource(); + checkConnection(); + + } catch (Exception e) { + throw new IllegalStateException("Fail to connect to database", e); + } + } + + @VisibleForTesting + void initSettings() { + properties = new Properties(); + completeProperties(settings, properties, SONAR_JDBC); + completeDefaultProperty(properties, DatabaseProperties.PROP_URL, DEFAULT_URL); + doCompleteProperties(properties); + + dialect = DialectUtils.find(properties.getProperty(SONAR_JDBC_DIALECT), properties.getProperty(SONAR_JDBC_URL)); + properties.setProperty(DatabaseProperties.PROP_DRIVER, dialect.getDefaultDriverClassName()); + } + + private void initDataSource() throws Exception { + // but it's correctly caught by start() + LOG.info("Create JDBC data source for {}", properties.getProperty(DatabaseProperties.PROP_URL, DEFAULT_URL)); + BasicDataSource basicDataSource = (BasicDataSource) BasicDataSourceFactory.createDataSource(extractCommonsDbcpProperties(properties)); + datasource = new ProfiledDataSource(basicDataSource, NullConnectionInterceptor.INSTANCE); + datasource.setConnectionInitSqls(dialect.getConnectionInitStatements()); + datasource.setValidationQuery(dialect.getValidationQuery()); + enableSqlLogging(datasource, logbackHelper.getLoggerLevel("sql") == Level.TRACE); + } + + private void checkConnection() { + Connection connection = null; + try { + connection = datasource.getConnection(); + } catch (SQLException e) { + throw new IllegalStateException("Can not connect to database. Please check connectivity and settings (see the properties prefixed by 'sonar.jdbc.').", e); + } finally { + DbUtils.closeQuietly(connection); + } + } + + @Override + public void stop() { + if (datasource != null) { + try { + datasource.close(); + } catch (SQLException e) { + throw new IllegalStateException("Fail to stop JDBC connection pool", e); + } + } + } + + @Override + public final Dialect getDialect() { + return dialect; + } + + @Override + public final DataSource getDataSource() { + return datasource; + } + + public final Properties getProperties() { + return properties; + } + + @Override + public void enableSqlLogging(boolean enable) { + enableSqlLogging(datasource, enable); + } + + private static void enableSqlLogging(ProfiledDataSource ds, boolean enable) { + ds.setConnectionInterceptor(enable ? ProfiledConnectionInterceptor.INSTANCE : NullConnectionInterceptor.INSTANCE); + } + + /** + * Override this method to add JDBC properties at runtime + */ + protected void doCompleteProperties(Properties properties) { + // open-close principle + } + + private static void completeProperties(Settings settings, Properties properties, String prefix) { + List<String> jdbcKeys = settings.getKeysStartingWith(prefix); + for (String jdbcKey : jdbcKeys) { + String value = settings.getString(jdbcKey); + properties.setProperty(jdbcKey, value); + } + } + + @VisibleForTesting + static Properties extractCommonsDbcpProperties(Properties properties) { + Properties result = new Properties(); + for (Map.Entry<Object, Object> entry : properties.entrySet()) { + String key = (String) entry.getKey(); + if (StringUtils.startsWith(key, SONAR_JDBC)) { + result.setProperty(StringUtils.removeStart(key, SONAR_JDBC), (String) entry.getValue()); + } + } + return result; + } + + private static void completeDefaultProperty(Properties props, String key, String defaultValue) { + if (props.getProperty(key) == null) { + props.setProperty(key, defaultValue); + } + } + + @Override + public String toString() { + return format("Database[%s]", properties != null ? properties.getProperty(SONAR_JDBC_URL) : "?"); + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/ResultSetIterator.java b/server/sonar-db-core/src/main/java/org/sonar/db/ResultSetIterator.java new file mode 100644 index 00000000000..6545555c6d2 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/ResultSetIterator.java @@ -0,0 +1,114 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +import java.io.Closeable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Iterator; +import java.util.NoSuchElementException; +import org.apache.commons.dbutils.DbUtils; + +/** + * Forward-only {@link java.util.Iterator} over a {@link java.sql.ResultSet}. Rows are + * lazily loaded. The underlying ResultSet must be closed by calling the method + * {@link #close()} + * <p/> + * As a safeguard, the ResultSet is automatically closed after the last element has + * been retrieved via {@link #next()} or {@link #hasNext()} is called (which will return false). + * This automagic behavior is not enough to remove explicit calls to {@link #close()} + * from caller methods. Errors raised before end of traversal must still be handled. + */ +public abstract class ResultSetIterator<E> implements Iterator<E>, Closeable { + + private final ResultSet rs; + private final PreparedStatement stmt; + + private volatile boolean didNext = false; + private volatile boolean hasNext = false; + private volatile boolean closed = false; + + public ResultSetIterator(PreparedStatement stmt) throws SQLException { + this.stmt = stmt; + this.rs = stmt.executeQuery(); + } + + protected ResultSetIterator(ResultSet rs) { + this.stmt = null; + this.rs = rs; + } + + @Override + public boolean hasNext() { + if (closed) { + return false; + } + if (!didNext) { + hasNext = doNextQuietly(); + if (hasNext) { + didNext = true; + } else { + close(); + } + } + return hasNext; + } + + @Override + public E next() { + if (!hasNext()) { + close(); + throw new NoSuchElementException(); + } + try { + return read(rs); + } catch (SQLException e) { + throw new IllegalStateException("Fail to read result set row", e); + } finally { + hasNext = doNextQuietly(); + if (!hasNext) { + close(); + } + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public void close() { + closed = true; + DbUtils.closeQuietly(rs); + DbUtils.closeQuietly(stmt); + } + + protected abstract E read(ResultSet rs) throws SQLException; + + private boolean doNextQuietly() { + try { + return rs.next(); + } catch (SQLException e) { + throw new IllegalStateException("Fail to read row of JDBC result set", e); + } + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/RowNotFoundException.java b/server/sonar-db-core/src/main/java/org/sonar/db/RowNotFoundException.java new file mode 100644 index 00000000000..86614240093 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/RowNotFoundException.java @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +/** + * The RuntimeException thrown by default when a element is not found at the DAO layer. + * When selecting by id or key, the methods respect one of the following pattern: + * <ul> + * <li>selectOrFailByKey return the element or throws a RowNotFoundException</li> + * <li>selectByUuid return an Optional (now) or a nullable element (legacy)</li> + * </ul> + */ +public class RowNotFoundException extends RuntimeException { + public RowNotFoundException(String message) { + super(message); + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/dialect/AbstractDialect.java b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/AbstractDialect.java new file mode 100644 index 00000000000..f442c088c21 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/AbstractDialect.java @@ -0,0 +1,90 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.dialect; + +import java.util.Collections; +import java.util.List; + +/** + * @since 3.2 + */ +abstract class AbstractDialect implements Dialect { + private final String id; + private final String activeRecordDialectCode; + private final String defaultDriverClassName; + private final String trueSqlValue; + private final String falseSqlValue; + private final String validationQuery; + + protected AbstractDialect(String id, String activeRecordDialectCode, String defaultDriverClassName, String trueSqlValue, String falseSqlValue, + String validationQuery) { + this.id = id; + this.activeRecordDialectCode = activeRecordDialectCode; + this.defaultDriverClassName = defaultDriverClassName; + this.trueSqlValue = trueSqlValue; + this.falseSqlValue = falseSqlValue; + this.validationQuery = validationQuery; + } + + @Override + public String getId() { + return id; + } + + @Override + public String getActiveRecordDialectCode() { + return activeRecordDialectCode; + } + + @Override + public String getDefaultDriverClassName() { + return defaultDriverClassName; + } + + @Override + public final String getTrueSqlValue() { + return trueSqlValue; + } + + @Override + public final String getFalseSqlValue() { + return falseSqlValue; + } + + @Override + public final String getValidationQuery() { + return validationQuery; + } + + @Override + public List<String> getConnectionInitStatements() { + return Collections.emptyList(); + } + + @Override + public int getScrollDefaultFetchSize() { + return 200; + } + + @Override + public int getScrollSingleRowFetchSize() { + return 1; + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/dialect/Dialect.java b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/Dialect.java new file mode 100644 index 00000000000..a7f02670fb9 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/Dialect.java @@ -0,0 +1,92 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.dialect; + +import java.util.List; + +/** + * @since 1.12 + */ +public interface Dialect { + + /** + * @return the sonar dialect Id to be matched with the sonar.jdbc.dialect property when provided + */ + String getId(); + + /** + * @return the activerecord dialect to be used + */ + String getActiveRecordDialectCode(); + + /** + * Used to autodetect a dialect for a given driver URL + * + * @param jdbcConnectionURL a jdbc driver url such as jdbc:mysql://localhost:3306/sonar + * @return true if the dialect supports surch url + */ + boolean matchesJdbcURL(String jdbcConnectionURL); + + /** + * @since 2.13 + */ + String getDefaultDriverClassName(); + + List<String> getConnectionInitStatements(); + + /** + * @since 2.14 + */ + String getTrueSqlValue(); + + /** + * @since 2.14 + */ + String getFalseSqlValue(); + + /** + * Query used to validate the jdbc connection. + * + * @since 3.2 + */ + String getValidationQuery(); + + /** + * Fetch size to be used when scrolling large result sets. + * + * @since 5.0 + */ + int getScrollDefaultFetchSize(); + + /** + * Fetch size to scroll one row at a time. It sounds strange because obviously value is 1 in most cases, + * but it's different on MySQL... + * + * @since 5.0 + */ + int getScrollSingleRowFetchSize(); + + /** + * Indicates whether DB migration can be perform on the DB vendor implementation associated with the current dialect. + * + * @return a boolean + */ + boolean supportsMigration(); +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/dialect/DialectUtils.java b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/DialectUtils.java new file mode 100644 index 00000000000..ce20c6cf893 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/DialectUtils.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.dialect; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterators; +import java.util.NoSuchElementException; +import javax.annotation.CheckForNull; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.utils.MessageException; + +public final class DialectUtils { + + private static final Dialect[] DIALECTS = new Dialect[] {new H2(), new MySql(), new Oracle(), new PostgreSql(), new MsSql()}; + + private DialectUtils() { + // only static stuff + } + + public static Dialect find(final String dialectId, final String jdbcConnectionUrl) { + Dialect match = StringUtils.isNotBlank(dialectId) ? findById(dialectId) : findByJdbcUrl(jdbcConnectionUrl); + if (match == null) { + throw MessageException.of("Unable to determine database dialect to use within sonar with dialect " + dialectId + " jdbc url " + jdbcConnectionUrl); + } + return match; + } + + @CheckForNull + private static Dialect findByJdbcUrl(final String jdbcConnectionUrl) { + return findDialect(dialect -> dialect != null && dialect.matchesJdbcURL(StringUtils.trimToEmpty(jdbcConnectionUrl))); + } + + @CheckForNull + private static Dialect findById(final String dialectId) { + return findDialect(dialect -> dialect != null && dialect.getId().equals(dialectId)); + } + + @CheckForNull + private static Dialect findDialect(Predicate<Dialect> predicate) { + try { + return Iterators.find(Iterators.forArray(DIALECTS), predicate); + } catch (NoSuchElementException ex) { + return null; + } + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/dialect/H2.java b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/H2.java new file mode 100644 index 00000000000..e94cbf0a348 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/H2.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.dialect; + +import org.apache.commons.lang.StringUtils; + +/** + * @since 1.12 + */ +public class H2 extends AbstractDialect { + + public static final String ID = "h2"; + + public H2() { + super(ID, ".h2.", "org.h2.Driver", "true", "false", "SELECT 1"); + } + + @Override + public boolean matchesJdbcURL(String jdbcConnectionURL) { + return StringUtils.startsWithIgnoreCase(jdbcConnectionURL, "jdbc:h2:"); + } + + @Override + public boolean supportsMigration() { + return false; + } + +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/dialect/MsSql.java b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/MsSql.java new file mode 100644 index 00000000000..802688af9b3 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/MsSql.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.dialect; + +import org.apache.commons.lang.StringUtils; + +public class MsSql extends AbstractDialect { + + public static final String ID = "mssql"; + + public MsSql() { + super(ID, "sqlserver", "com.microsoft.sqlserver.jdbc.SQLServerDriver", "1", "0", "SELECT 1"); + } + + @Override + public boolean matchesJdbcURL(String jdbcConnectionURL) { + return StringUtils.startsWithIgnoreCase(jdbcConnectionURL, "jdbc:sqlserver:"); + } + + @Override + public boolean supportsMigration() { + return true; + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/dialect/MySql.java b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/MySql.java new file mode 100644 index 00000000000..bb16869d798 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/MySql.java @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.dialect; + +import org.apache.commons.lang.StringUtils; + +/** + * @since 1.12 + */ +public class MySql extends AbstractDialect { + + public static final String ID = "mysql"; + + public MySql() { + super(ID, "mysql", "com.mysql.jdbc.Driver", "true", "false", "SELECT 1"); + } + + @Override + public boolean matchesJdbcURL(String jdbcConnectionURL) { + return StringUtils.startsWithIgnoreCase(jdbcConnectionURL, "jdbc:mysql:"); + } + + @Override + public int getScrollDefaultFetchSize() { + return Integer.MIN_VALUE; + } + + @Override + public int getScrollSingleRowFetchSize() { + return Integer.MIN_VALUE; + } + + @Override + public boolean supportsMigration() { + return true; + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/dialect/Oracle.java b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/Oracle.java new file mode 100644 index 00000000000..ac49a24a9ad --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/Oracle.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.dialect; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.apache.commons.lang.StringUtils; + +/** + * @since 1.12 + */ +public class Oracle extends AbstractDialect { + + public static final String ID = "oracle"; + private static final List<String> INIT_STATEMENTS = ImmutableList.of("ALTER SESSION SET NLS_SORT='BINARY'"); + + public Oracle() { + super(ID, "oracle", "oracle.jdbc.OracleDriver", "1", "0", "SELECT 1 FROM DUAL"); + } + + @Override + public boolean matchesJdbcURL(String jdbcConnectionURL) { + return StringUtils.startsWithIgnoreCase(jdbcConnectionURL, "jdbc:oracle:"); + } + + @Override + public boolean supportsMigration() { + return true; + } + + @Override + public List<String> getConnectionInitStatements() { + return INIT_STATEMENTS; + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/dialect/PostgreSql.java b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/PostgreSql.java new file mode 100644 index 00000000000..bdb81f80abc --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/PostgreSql.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.dialect; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.apache.commons.lang.StringUtils; + +/** + * @since 1.12 + */ +public class PostgreSql extends AbstractDialect { + + public static final String ID = "postgresql"; + static final List<String> INIT_STATEMENTS = ImmutableList.of("SET standard_conforming_strings=on", "SET backslash_quote=off"); + + public PostgreSql() { + super(ID, "postgre", "org.postgresql.Driver", "true", "false", "SELECT 1"); + } + + @Override + public boolean matchesJdbcURL(String jdbcConnectionURL) { + return StringUtils.startsWithIgnoreCase(jdbcConnectionURL, "jdbc:postgresql:"); + } + + @Override + public List<String> getConnectionInitStatements() { + return INIT_STATEMENTS; + } + + @Override + public boolean supportsMigration() { + return true; + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/dialect/package-info.java b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/package-info.java new file mode 100644 index 00000000000..5602ddce667 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/dialect/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.db.dialect; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/package-info.java b/server/sonar-db-core/src/main/java/org/sonar/db/package-info.java new file mode 100644 index 00000000000..0fa491e02ec --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.db; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ConnectionInterceptor.java b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ConnectionInterceptor.java new file mode 100644 index 00000000000..b3912651c85 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ConnectionInterceptor.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.profiling; + +import java.sql.Connection; +import java.sql.SQLException; +import org.apache.commons.dbcp.BasicDataSource; + +public interface ConnectionInterceptor { + + Connection getConnection(BasicDataSource dataSource) throws SQLException; + + Connection getConnection(BasicDataSource dataSource, String login, String password) throws SQLException; + +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/InvocationUtils.java b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/InvocationUtils.java new file mode 100644 index 00000000000..ad36c802e3f --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/InvocationUtils.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.profiling; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +class InvocationUtils { + + private InvocationUtils() { + // Only private stuff + } + + static Object invokeQuietly(Object target, Method method, Object[] params) throws Throwable { + Object result = null; + try { + result = method.invoke(target, params); + } catch (InvocationTargetException invocationException) { + for (Class<?> exceptionClass : method.getExceptionTypes()) { + if (exceptionClass.isInstance(invocationException.getCause())) { + throw invocationException.getCause(); + } + throw new IllegalStateException(invocationException.getCause()); + } + } + return result; + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/NullConnectionInterceptor.java b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/NullConnectionInterceptor.java new file mode 100644 index 00000000000..c4d83f456b0 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/NullConnectionInterceptor.java @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.profiling; + +import java.sql.Connection; +import java.sql.SQLException; +import org.apache.commons.dbcp.BasicDataSource; + +public enum NullConnectionInterceptor implements ConnectionInterceptor { + INSTANCE; + + @Override + public Connection getConnection(BasicDataSource dataSource) throws SQLException { + return dataSource.getConnection(); + } + + @Override + public Connection getConnection(BasicDataSource dataSource, String user, String password) throws SQLException { + return dataSource.getConnection(user, password); + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledConnectionInterceptor.java b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledConnectionInterceptor.java new file mode 100644 index 00000000000..ffc74810e73 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledConnectionInterceptor.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.profiling; + +import java.lang.reflect.Proxy; +import java.sql.Connection; +import java.sql.SQLException; +import org.apache.commons.dbcp.BasicDataSource; + +public enum ProfiledConnectionInterceptor implements ConnectionInterceptor { + INSTANCE; + + @Override + public Connection getConnection(BasicDataSource dataSource) throws SQLException { + return buildConnectionProxy(new ProfilingConnectionHandler(dataSource.getConnection())); + } + + @Override + public Connection getConnection(BasicDataSource dataSource, String login, String password) throws SQLException { + return buildConnectionProxy(new ProfilingConnectionHandler(dataSource.getConnection(login, password))); + } + + private static Connection buildConnectionProxy(ProfilingConnectionHandler connectionHandler) { + ClassLoader classloader = ProfiledConnectionInterceptor.class.getClassLoader(); + return (Connection) Proxy.newProxyInstance(classloader, new Class[] {Connection.class}, connectionHandler); + } + +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledDataSource.java b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledDataSource.java new file mode 100644 index 00000000000..ee831a9d95a --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledDataSource.java @@ -0,0 +1,420 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.profiling; + +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.Collection; +import org.apache.commons.dbcp.BasicDataSource; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +public class ProfiledDataSource extends BasicDataSource { + + static final Logger SQL_LOGGER = Loggers.get("sql"); + + private final BasicDataSource delegate; + private ConnectionInterceptor connectionInterceptor; + + public ProfiledDataSource(BasicDataSource delegate, ConnectionInterceptor connectionInterceptor) { + this.delegate = delegate; + this.connectionInterceptor = connectionInterceptor; + } + + public BasicDataSource getDelegate() { + return delegate; + } + + public synchronized void setConnectionInterceptor(ConnectionInterceptor ci) { + this.connectionInterceptor = ci; + } + + @Override + public boolean getDefaultAutoCommit() { + return delegate.getDefaultAutoCommit(); + } + + @Override + public void setDefaultAutoCommit(boolean defaultAutoCommit) { + delegate.setDefaultAutoCommit(defaultAutoCommit); + } + + @Override + public boolean getDefaultReadOnly() { + return delegate.getDefaultReadOnly(); + } + + @Override + public void setDefaultReadOnly(boolean defaultReadOnly) { + delegate.setDefaultReadOnly(defaultReadOnly); + } + + @Override + public int getDefaultTransactionIsolation() { + return delegate.getDefaultTransactionIsolation(); + } + + @Override + public void setDefaultTransactionIsolation(int defaultTransactionIsolation) { + delegate.setDefaultTransactionIsolation(defaultTransactionIsolation); + } + + @Override + public String getDefaultCatalog() { + return delegate.getDefaultCatalog(); + } + + @Override + public void setDefaultCatalog(String defaultCatalog) { + delegate.setDefaultCatalog(defaultCatalog); + } + + @Override + public synchronized String getDriverClassName() { + return delegate.getDriverClassName(); + } + + @Override + public synchronized void setDriverClassName(String driverClassName) { + delegate.setDriverClassName(driverClassName); + } + + @Override + public synchronized ClassLoader getDriverClassLoader() { + return delegate.getDriverClassLoader(); + } + + @Override + public synchronized void setDriverClassLoader(ClassLoader driverClassLoader) { + delegate.setDriverClassLoader(driverClassLoader); + } + + @Override + public synchronized int getMaxActive() { + return delegate.getMaxActive(); + } + + @Override + public synchronized void setMaxActive(int maxActive) { + delegate.setMaxActive(maxActive); + } + + @Override + public synchronized int getMaxIdle() { + return delegate.getMaxIdle(); + } + + @Override + public synchronized void setMaxIdle(int maxIdle) { + delegate.setMaxIdle(maxIdle); + } + + @Override + public synchronized int getMinIdle() { + return delegate.getMinIdle(); + } + + @Override + public synchronized void setMinIdle(int minIdle) { + delegate.setMinIdle(minIdle); + } + + @Override + public synchronized int getInitialSize() { + return delegate.getInitialSize(); + } + + @Override + public synchronized void setInitialSize(int initialSize) { + delegate.setInitialSize(initialSize); + } + + @Override + public synchronized long getMaxWait() { + return delegate.getMaxWait(); + } + + @Override + public synchronized void setMaxWait(long maxWait) { + delegate.setMaxWait(maxWait); + } + + @Override + public synchronized boolean isPoolPreparedStatements() { + return delegate.isPoolPreparedStatements(); + } + + @Override + public synchronized void setPoolPreparedStatements(boolean poolingStatements) { + delegate.setPoolPreparedStatements(poolingStatements); + } + + @Override + public synchronized int getMaxOpenPreparedStatements() { + return delegate.getMaxOpenPreparedStatements(); + } + + @Override + public synchronized void setMaxOpenPreparedStatements(int maxOpenStatements) { + delegate.setMaxOpenPreparedStatements(maxOpenStatements); + } + + @Override + public synchronized boolean getTestOnBorrow() { + return delegate.getTestOnBorrow(); + } + + @Override + public synchronized void setTestOnBorrow(boolean testOnBorrow) { + delegate.setTestOnBorrow(testOnBorrow); + } + + @Override + public synchronized boolean getTestOnReturn() { + return delegate.getTestOnReturn(); + } + + @Override + public synchronized void setTestOnReturn(boolean testOnReturn) { + delegate.setTestOnReturn(testOnReturn); + } + + @Override + public synchronized long getTimeBetweenEvictionRunsMillis() { + return delegate.getTimeBetweenEvictionRunsMillis(); + } + + @Override + public synchronized void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) { + delegate.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); + } + + @Override + public synchronized int getNumTestsPerEvictionRun() { + return delegate.getNumTestsPerEvictionRun(); + } + + @Override + public synchronized void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) { + delegate.setNumTestsPerEvictionRun(numTestsPerEvictionRun); + } + + @Override + public synchronized long getMinEvictableIdleTimeMillis() { + return delegate.getMinEvictableIdleTimeMillis(); + } + + @Override + public synchronized void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) { + delegate.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); + } + + @Override + public synchronized boolean getTestWhileIdle() { + return delegate.getTestWhileIdle(); + } + + @Override + public synchronized void setTestWhileIdle(boolean testWhileIdle) { + delegate.setTestWhileIdle(testWhileIdle); + } + + @Override + public synchronized int getNumActive() { + return delegate.getNumActive(); + } + + @Override + public synchronized int getNumIdle() { + return delegate.getNumIdle(); + } + + @Override + public String getPassword() { + return delegate.getPassword(); + } + + @Override + public void setPassword(String password) { + delegate.setPassword(password); + } + + @Override + public synchronized String getUrl() { + return delegate.getUrl(); + } + + @Override + public synchronized void setUrl(String url) { + delegate.setUrl(url); + } + + @Override + public String getUsername() { + return delegate.getUsername(); + } + + @Override + public void setUsername(String username) { + delegate.setUsername(username); + } + + @Override + public String getValidationQuery() { + return delegate.getValidationQuery(); + } + + @Override + public void setValidationQuery(String validationQuery) { + delegate.setValidationQuery(validationQuery); + } + + @Override + public int getValidationQueryTimeout() { + return delegate.getValidationQueryTimeout(); + } + + @Override + public void setValidationQueryTimeout(int timeout) { + delegate.setValidationQueryTimeout(timeout); + } + + @Override + public Collection getConnectionInitSqls() { + return delegate.getConnectionInitSqls(); + } + + @Override + public void setConnectionInitSqls(Collection connectionInitSqls) { + delegate.setConnectionInitSqls(connectionInitSqls); + } + + @Override + public synchronized boolean isAccessToUnderlyingConnectionAllowed() { + return delegate.isAccessToUnderlyingConnectionAllowed(); + } + + @Override + public synchronized void setAccessToUnderlyingConnectionAllowed(boolean allow) { + delegate.setAccessToUnderlyingConnectionAllowed(allow); + } + + @Override + public Connection getConnection() throws SQLException { + return connectionInterceptor.getConnection(delegate); + } + + @Override + public Connection getConnection(String login, String password) throws SQLException { + return connectionInterceptor.getConnection(this, login, password); + } + + @Override + public int getLoginTimeout() throws SQLException { + return delegate.getLoginTimeout(); + } + + @Override + public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException { + return java.util.logging.Logger.getLogger(getClass().getName()); + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + return delegate.getLogWriter(); + } + + @Override + public void setLoginTimeout(int loginTimeout) throws SQLException { + delegate.setLoginTimeout(loginTimeout); + } + + @Override + public void setLogWriter(PrintWriter logWriter) throws SQLException { + delegate.setLogWriter(logWriter); + } + + @Override + public boolean getRemoveAbandoned() { + return delegate.getRemoveAbandoned(); + } + + @Override + public void setRemoveAbandoned(boolean removeAbandoned) { + delegate.setRemoveAbandoned(removeAbandoned); + } + + @Override + public int getRemoveAbandonedTimeout() { + return delegate.getRemoveAbandonedTimeout(); + } + + @Override + public void setRemoveAbandonedTimeout(int removeAbandonedTimeout) { + delegate.setRemoveAbandonedTimeout(removeAbandonedTimeout); + } + + @Override + public boolean getLogAbandoned() { + return delegate.getLogAbandoned(); + } + + @Override + public void setLogAbandoned(boolean logAbandoned) { + delegate.setLogAbandoned(logAbandoned); + } + + @Override + public void addConnectionProperty(String name, String value) { + delegate.addConnectionProperty(name, value); + } + + @Override + public void removeConnectionProperty(String name) { + delegate.removeConnectionProperty(name); + } + + @Override + public void setConnectionProperties(String connectionProperties) { + delegate.setConnectionProperties(connectionProperties); + } + + @Override + public synchronized void close() throws SQLException { + delegate.close(); + } + + @Override + public synchronized boolean isClosed() { + return delegate.isClosed(); + } + + @Override + public boolean isWrapperFor(Class<?> iface) throws SQLException { + return delegate.isWrapperFor(iface); + } + + @Override + public <T> T unwrap(Class<T> iface) throws SQLException { + return delegate.unwrap(iface); + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfilingConnectionHandler.java b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfilingConnectionHandler.java new file mode 100644 index 00000000000..d6e63e21dbd --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfilingConnectionHandler.java @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.profiling; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.Statement; + +class ProfilingConnectionHandler implements InvocationHandler { + + private final Connection connection; + + ProfilingConnectionHandler(Connection connection) { + this.connection = connection; + } + + @Override + public Object invoke(Object target, Method method, Object[] args) throws Throwable { + Object result = InvocationUtils.invokeQuietly(connection, method, args); + if ("prepareStatement".equals(method.getName())) { + PreparedStatement statement = (PreparedStatement) result; + String sql = (String) args[0]; + return buildStatementProxy(PreparedStatement.class, new ProfilingPreparedStatementHandler(statement, sql)); + } + if ("createStatement".equals(method.getName())) { + Statement statement = (Statement) result; + return buildStatementProxy(Statement.class, new ProfilingStatementHandler(statement)); + } + return result; + } + + private static Object buildStatementProxy(Class<? extends Statement> stmtClass, InvocationHandler handler) { + return Proxy.newProxyInstance(ProfilingConnectionHandler.class.getClassLoader(), new Class[] {stmtClass}, handler); + } + +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfilingPreparedStatementHandler.java b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfilingPreparedStatementHandler.java new file mode 100644 index 00000000000..1c9bd8f3868 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfilingPreparedStatementHandler.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.profiling; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.sql.PreparedStatement; +import org.sonar.api.utils.log.Profiler; + +class ProfilingPreparedStatementHandler implements InvocationHandler { + + private final PreparedStatement statement; + private final String sql; + private final Object[] sqlParams; + + ProfilingPreparedStatementHandler(PreparedStatement statement, String sql) { + this.statement = statement; + this.sql = sql; + sqlParams = new Object[SqlLogFormatter.countArguments(sql)]; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().startsWith("execute")) { + Profiler profiler = Profiler.create(ProfiledDataSource.SQL_LOGGER).start(); + Object result = null; + try { + result = InvocationUtils.invokeQuietly(statement, method, args); + } finally { + profiler.addContext("sql", SqlLogFormatter.formatSql(sql)); + if (sqlParams.length > 0) { + profiler.addContext("params", SqlLogFormatter.formatParams(sqlParams)); + } + profiler.stopTrace(""); + } + return result; + } else if (method.getName().startsWith("set") && args.length > 1) { + sqlParams[(int) args[0] - 1] = args[1]; + return InvocationUtils.invokeQuietly(statement, method, args); + } else { + return InvocationUtils.invokeQuietly(statement, method, args); + } + } + +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfilingStatementHandler.java b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfilingStatementHandler.java new file mode 100644 index 00000000000..4a8c1ef6053 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfilingStatementHandler.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.profiling; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.sql.Statement; +import org.sonar.api.utils.log.Profiler; + +class ProfilingStatementHandler implements InvocationHandler { + + private final Statement statement; + + ProfilingStatementHandler(Statement statement) { + this.statement = statement; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().startsWith("execute")) { + Profiler profiler = Profiler.create(ProfiledDataSource.SQL_LOGGER).start(); + Object result = null; + try { + result = InvocationUtils.invokeQuietly(statement, method, args); + } finally { + String sql = (String) args[0]; + profiler.addContext("sql", SqlLogFormatter.formatSql(sql)); + profiler.stopTrace(""); + } + return result; + } else { + return InvocationUtils.invokeQuietly(statement, method, args); + } + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/SqlLogFormatter.java b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/SqlLogFormatter.java new file mode 100644 index 00000000000..fafc22ca46f --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/SqlLogFormatter.java @@ -0,0 +1,70 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.profiling; + +import java.util.regex.Pattern; +import javax.annotation.Nullable; +import org.apache.commons.lang.StringUtils; + +import static org.apache.commons.lang.StringUtils.abbreviate; + +public class SqlLogFormatter { + + public static final int PARAM_MAX_WIDTH = 500; + private static final String PARAM_NULL = "[null]"; + private static final Pattern NEWLINE_PATTERN = Pattern.compile("\\n"); + + private SqlLogFormatter() { + // only statics + } + + public static String formatSql(String sql) { + return StringUtils.replaceChars(sql, '\n', ' '); + } + + public static String formatParam(@Nullable Object param) { + if (param == null) { + return PARAM_NULL; + } + String abbreviated = abbreviate(param.toString(), PARAM_MAX_WIDTH); + return NEWLINE_PATTERN.matcher(abbreviated).replaceAll("\\\\n"); + } + + public static String formatParams(Object[] params) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < params.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(formatParam(params[i])); + } + return sb.toString(); + } + + public static int countArguments(String sql) { + int argCount = 0; + for (int i = 0; i < sql.length(); i++) { + if (sql.charAt(i) == '?') { + argCount++; + } + } + return argCount; + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/profiling/package-info.java b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/package-info.java new file mode 100644 index 00000000000..43c7f272860 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/profiling/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.db.profiling; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/semaphore/SemaphoresImpl.java b/server/sonar-db-core/src/main/java/org/sonar/db/semaphore/SemaphoresImpl.java new file mode 100644 index 00000000000..ef9030591aa --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/semaphore/SemaphoresImpl.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.semaphore; + +import org.sonar.api.utils.Semaphores; + +public class SemaphoresImpl implements Semaphores { + + @Override + public Semaphore acquire(String name, int maxAgeInSeconds, int updatePeriodInSeconds) { + throw fail(); + } + + @Override + public Semaphore acquire(String name) { + throw fail(); + } + + @Override + public void release(String name) { + throw fail(); + } + + private static RuntimeException fail() { + throw new UnsupportedOperationException("Semaphores are not supported since 5.2 and the drop of database connection from analyzer"); + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/semaphore/package-info.java b/server/sonar-db-core/src/main/java/org/sonar/db/semaphore/package-info.java new file mode 100644 index 00000000000..a6ff02997db --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/semaphore/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.db.semaphore; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java new file mode 100644 index 00000000000..968be26f244 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java @@ -0,0 +1,96 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.ImmutableSet; +import java.util.Set; + +public final class SqTables { + /** + * These tables are still involved in DB migrations, so potentially + * incorrect collation must be fixed so that joins with other + * tables are possible. + */ + public static final Set<String> OLD_DROPPED_TABLES = ImmutableSet.of( + "active_dashboards", + "activities", + "dashboards", + "issue_filters", + "issue_filter_favourites", + "measure_filters", + "measure_filter_favourites", + "resource_index", + "widgets", + "widget_properties"); + + /** + * List of all the tables. + * This list is hardcoded because we didn't succeed in using java.sql.DatabaseMetaData#getTables() in the same way + * for all the supported databases, particularly due to Oracle results. + */ + public static final Set<String> TABLES = ImmutableSet.of( + "active_rules", + "active_rule_parameters", + "authors", + "ce_activity", + "ce_queue", + "ce_task_input", + "ce_scanner_context", + "duplications_index", + "events", + "file_sources", + "groups", + "groups_users", + "group_roles", + "internal_properties", + "issues", + "issue_changes", + "loaded_templates", + "manual_measures", + "metrics", + "notifications", + "organizations", + "permission_templates", + "perm_templates_users", + "perm_templates_groups", + "perm_tpl_characteristics", + "quality_gates", + "quality_gate_conditions", + "projects", + "project_links", + "project_measures", + "project_qprofiles", + "properties", + "qprofile_changes", + "rules", + "rules_parameters", + "rules_profiles", + "rule_repositories", + "schema_migrations", + "snapshots", + "users", + "user_roles", + "user_tokens", + "webhook_deliveries"); + + private SqTables() { + // prevents instantiation + } +} diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/version/package-info.java b/server/sonar-db-core/src/main/java/org/sonar/db/version/package-info.java new file mode 100644 index 00000000000..225b5e6fba6 --- /dev/null +++ b/server/sonar-db-core/src/main/java/org/sonar/db/version/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.db.version; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql b/server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql new file mode 100644 index 00000000000..5892eee83ae --- /dev/null +++ b/server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql @@ -0,0 +1,554 @@ +-- All the rows inserted during Rails migrations. Rows inserted during server startup tasks (Java) are excluded : rules, profiles, metrics, ... + +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('2'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('10'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('11'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('14'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('35'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('48'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('49'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('53'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('54'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('59'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('61'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('62'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('79'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('80'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('86'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('87'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('88'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('93'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('95'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('111'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('115'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('118'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('119'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('120'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('131'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('132'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('133'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('134'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('135'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('136'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('137'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('138'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('139'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('140'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('141'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('142'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('151'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('160'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('162'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('163'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('165'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('166'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('167'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('168'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('169'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('170'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('180'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('190'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('191'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('200'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('201'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('202'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('203'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('210'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('211'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('212'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('213'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('214'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('215'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('216'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('217'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('220'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('221'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('222'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('230'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('231'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('232'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('233'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('234'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('235'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('236'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('237'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('238'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('239'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('240'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('241'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('250'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('251'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('252'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('254'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('255'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('256'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('257'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('258'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('259'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('260'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('261'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('262'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('263'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('280'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('281'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('282'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('283'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('284'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('285'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('286'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('287'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('300'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('301'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('302'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('303'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('304'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('305'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('306'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('320'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('321'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('330'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('331'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('332'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('333'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('334'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('335'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('350'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('351'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('352'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('353'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('354'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('355'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('356'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('358'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('359'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('360'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('361'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('362'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('363'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('370'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('379'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('380'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('381'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('382'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('383'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('387'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('388'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('391'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('392'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('394'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('397'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('398'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('399'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('400'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('401'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('402'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('403'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('404'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('405'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('406'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('410'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('411'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('412'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('413'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('414'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('415'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('416'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('417'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('418'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('419'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('430'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('431'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('432'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('433'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('434'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('440'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('441'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('442'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('443'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('444'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('460'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('461'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('462'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('463'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('464'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('465'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('466'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('467'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('480'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('481'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('482'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('483'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('484'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('485'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('486'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('488'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('489'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('490'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('492'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('493'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('494'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('495'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('496'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('497'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('498'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('510'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('511'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('512'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('513'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('514'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('515'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('516'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('517'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('518'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('519'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('520'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('521'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('522'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('523'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('524'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('525'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('526'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('530'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('531'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('532'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('533'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('534'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('535'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('536'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('537'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('539'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('540'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('541'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('542'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('544'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('545'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('546'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('547'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('548'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('549'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('551'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('552'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('553'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('554'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('555'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('556'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('580'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('581'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('582'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('583'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('584'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('600'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('601'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('603'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('604'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('605'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('702'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('703'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('704'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('705'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('706'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('707'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('708'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('709'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('710'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('711'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('712'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('713'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('714'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('715'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('716'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('717'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('718'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('719'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('720'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('721'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('750'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('752'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('753'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('754'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('755'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('756'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('757'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('758'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('759'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('760'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('761'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('762'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('763'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('764'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('765'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('766'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('768'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('769'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('770'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('771'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('772'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('773'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('775'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('776'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('777'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('778'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('779'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('780'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('781'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('782'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('783'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('784'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('786'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('787'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('788'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('789'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('790'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('791'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('792'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('793'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('794'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('795'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('796'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('900'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('901'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('902'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('903'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('904'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('905'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('906'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('907'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('908'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('912'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('913'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('915'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('916'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('917'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('918'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('919'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('920'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('921'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('922'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('923'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('924'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('925'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('926'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('927'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('929'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('930'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('931'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('932'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('933'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('934'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('935'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('936'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('937'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('938'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('939'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('940'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('941'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1000'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1001'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1003'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1004'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1005'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1006'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1007'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1008'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1009'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1010'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1011'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1012'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1013'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1014'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1015'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1017'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1100'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1101'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1102'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1103'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1104'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1105'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1106'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1107'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1108'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1109'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1110'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1111'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1112'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1113'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1114'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1115'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1116'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1117'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1118'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1119'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1120'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1121'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1122'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1123'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1124'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1125'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1150'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1151'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1152'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1153'); + +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1200'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1201'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1202'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1203'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1204'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1205'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1206'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1207'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1208'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1209'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1210'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1211'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1212'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1213'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1214'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1215'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1216'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1217'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1218'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1219'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1220'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1221'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1222'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1223'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1224'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1225'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1226'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1227'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1228'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1229'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1230'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1231'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1232'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1233'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1234'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1235'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1236'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1237'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1238'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1239'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1240'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1241'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1242'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1243'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1244'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1245'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1246'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1247'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1248'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1249'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1250'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1251'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1252'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1253'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1254'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1255'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1256'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1257'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1258'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1259'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1260'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1261'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1262'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1263'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1264'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1265'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1266'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1267'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1268'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1269'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1270'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1271'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1272'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1273'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1274'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1275'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1276'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1277'); + +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1300'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1301'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1302'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1303'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1304'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1306'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1307'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1309'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1310'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1311'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1312'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1313'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1314'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1315'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1316'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1317'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1318'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1319'); + +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1400'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1401'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1402'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1403'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1404'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1405'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1406'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1407'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1408'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1409'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1410'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1411'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1412'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1413'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1414'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1415'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1416'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1417'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1418'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1419'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1420'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1421'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1422'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1423'); + +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1500'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1501'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1502'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1503'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1504'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1505'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1506'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1507'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1508'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1509'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1510'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1511'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1512'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1513'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1514'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1515'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1516'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1517'); + +INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, EXTERNAL_IDENTITY, EXTERNAL_IDENTITY_PROVIDER, USER_LOCAL, CRYPTED_PASSWORD, SALT, IS_ROOT, CREATED_AT, UPDATED_AT) VALUES (1, 'admin', 'Administrator', '', 'admin', 'sonarqube', true, 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', false, '1418215735482', '1418215735482'); +ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2; + +INSERT INTO ORGANIZATIONS (UUID, KEE, NAME, GUARDED, CREATED_AT, UPDATED_AT) VALUES ('AVdqnciQUUs7Zd3KPvFD', 'default-organization', 'Default Organization', true, '1474962596482', '1474962596482'); +INSERT INTO INTERNAL_PROPERTIES (KEE, IS_EMPTY, TEXT_VALUE, CREATED_AT) VALUES ('organization.default', false, 'AVdqnciQUUs7Zd3KPvFD', '1474962596482'); + +INSERT INTO GROUPS(ID, ORGANIZATION_UUID, NAME, DESCRIPTION, CREATED_AT, UPDATED_AT) VALUES (1, 'AVdqnciQUUs7Zd3KPvFD', 'sonar-administrators', 'System administrators', '2011-09-26 22:27:51.0', '2011-09-26 22:27:51.0'); +INSERT INTO GROUPS(ID, ORGANIZATION_UUID, NAME, DESCRIPTION, CREATED_AT, UPDATED_AT) VALUES (2, 'AVdqnciQUUs7Zd3KPvFD', 'sonar-users', 'Any new users created will automatically join this group', '2011-09-26 22:27:51.0', '2011-09-26 22:27:51.0'); +ALTER TABLE GROUPS ALTER COLUMN ID RESTART WITH 3; + +INSERT INTO GROUP_ROLES(ID, ORGANIZATION_UUID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (1, 'AVdqnciQUUs7Zd3KPvFD', 1, null, 'admin'); +INSERT INTO GROUP_ROLES(ID, ORGANIZATION_UUID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (2, 'AVdqnciQUUs7Zd3KPvFD', 1, null, 'profileadmin'); +INSERT INTO GROUP_ROLES(ID, ORGANIZATION_UUID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (3, 'AVdqnciQUUs7Zd3KPvFD', 1, null, 'gateadmin'); +INSERT INTO GROUP_ROLES(ID, ORGANIZATION_UUID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (4, 'AVdqnciQUUs7Zd3KPvFD', null, null, 'scan'); +INSERT INTO GROUP_ROLES(ID, ORGANIZATION_UUID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (5, 'AVdqnciQUUs7Zd3KPvFD', null, null, 'provisioning'); +INSERT INTO GROUP_ROLES(ID, ORGANIZATION_UUID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (6, 'AVdqnciQUUs7Zd3KPvFD', 1, null, 'provisioning'); +ALTER TABLE GROUP_ROLES ALTER COLUMN ID RESTART WITH 7; + +INSERT INTO GROUPS_USERS(USER_ID, GROUP_ID) VALUES (1, 1); +INSERT INTO GROUPS_USERS(USER_ID, GROUP_ID) VALUES (1, 2); diff --git a/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl new file mode 100644 index 00000000000..4c459333be0 --- /dev/null +++ b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl @@ -0,0 +1,625 @@ +CREATE TABLE "ORGANIZATIONS" ( + "UUID" VARCHAR(40) NOT NULL PRIMARY KEY, + "KEE" VARCHAR(32) NOT NULL, + "NAME" VARCHAR(64) NOT NULL, + "DESCRIPTION" VARCHAR(256), + "URL" VARCHAR(256), + "AVATAR_URL" VARCHAR(256), + "GUARDED" BOOLEAN NOT NULL, + "USER_ID" INTEGER, + "DEFAULT_PERM_TEMPLATE_PROJECT" VARCHAR(40), + "DEFAULT_PERM_TEMPLATE_VIEW" VARCHAR(40), + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL +); +CREATE UNIQUE INDEX "PK_ORGANIZATIONS" ON "ORGANIZATIONS" ("UUID"); +CREATE UNIQUE INDEX "ORGANIZATION_KEY" ON "ORGANIZATIONS" ("KEE"); + + +CREATE TABLE "GROUPS_USERS" ( + "USER_ID" INTEGER, + "GROUP_ID" INTEGER +); +CREATE INDEX "INDEX_GROUPS_USERS_ON_GROUP_ID" ON "GROUPS_USERS" ("GROUP_ID"); +CREATE INDEX "INDEX_GROUPS_USERS_ON_USER_ID" ON "GROUPS_USERS" ("USER_ID"); +CREATE UNIQUE INDEX "GROUPS_USERS_UNIQUE" ON "GROUPS_USERS" ("GROUP_ID", "USER_ID"); + + +CREATE TABLE "RULES_PARAMETERS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "RULE_ID" INTEGER NOT NULL, + "NAME" VARCHAR(128) NOT NULL, + "PARAM_TYPE" VARCHAR(512) NOT NULL, + "DEFAULT_VALUE" VARCHAR(4000), + "DESCRIPTION" VARCHAR(4000) +); +CREATE INDEX "RULES_PARAMETERS_RULE_ID" ON "RULES_PARAMETERS" ("RULE_ID"); + + +CREATE TABLE "RULES_PROFILES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "NAME" VARCHAR(100) NOT NULL, + "LANGUAGE" VARCHAR(20), + "KEE" VARCHAR(255) NOT NULL, + "PARENT_KEE" VARCHAR(255), + "RULES_UPDATED_AT" VARCHAR(100), + "IS_DEFAULT" BOOLEAN NOT NULL DEFAULT FALSE, + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP, + "LAST_USED" BIGINT, + "USER_UPDATED_AT" BIGINT +); +CREATE UNIQUE INDEX "UNIQ_QPROF_KEY" ON "RULES_PROFILES" ("KEE"); + + +CREATE TABLE "PROJECT_QPROFILES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "PROJECT_UUID" VARCHAR(50) NOT NULL, + "PROFILE_KEY" VARCHAR(255) NOT NULL +); +CREATE UNIQUE INDEX "UNIQ_PROJECT_QPROFILES" ON "PROJECT_QPROFILES" ("PROJECT_UUID", "PROFILE_KEY"); + + + +CREATE TABLE "GROUPS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "ORGANIZATION_UUID" VARCHAR(40) NOT NULL, + "NAME" VARCHAR(500), + "DESCRIPTION" VARCHAR(200), + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP +); + + +CREATE TABLE "SNAPSHOTS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "UUID" VARCHAR(50) NOT NULL, + "CREATED_AT" BIGINT, + "BUILD_DATE" BIGINT, + "COMPONENT_UUID" VARCHAR(50) NOT NULL, + "STATUS" VARCHAR(4) NOT NULL DEFAULT 'U', + "PURGE_STATUS" INTEGER, + "ISLAST" BOOLEAN NOT NULL DEFAULT FALSE, + "VERSION" VARCHAR(500), + "PERIOD1_MODE" VARCHAR(100), + "PERIOD1_PARAM" VARCHAR(100), + "PERIOD1_DATE" BIGINT, + "PERIOD2_MODE" VARCHAR(100), + "PERIOD2_PARAM" VARCHAR(100), + "PERIOD2_DATE" BIGINT, + "PERIOD3_MODE" VARCHAR(100), + "PERIOD3_PARAM" VARCHAR(100), + "PERIOD3_DATE" BIGINT, + "PERIOD4_MODE" VARCHAR(100), + "PERIOD4_PARAM" VARCHAR(100), + "PERIOD4_DATE" BIGINT, + "PERIOD5_MODE" VARCHAR(100), + "PERIOD5_PARAM" VARCHAR(100), + "PERIOD5_DATE" BIGINT +); +CREATE INDEX "SNAPSHOT_COMPONENT" ON "SNAPSHOTS" ("COMPONENT_UUID"); +CREATE UNIQUE INDEX "ANALYSES_UUID" ON "SNAPSHOTS" ("UUID"); + +CREATE TABLE "GROUP_ROLES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "ORGANIZATION_UUID" VARCHAR(40) NOT NULL, + "GROUP_ID" INTEGER, + "RESOURCE_ID" INTEGER, + "ROLE" VARCHAR(64) NOT NULL +); +CREATE INDEX "GROUP_ROLES_RESOURCE" ON "GROUP_ROLES" ("RESOURCE_ID"); +CREATE UNIQUE INDEX "UNIQ_GROUP_ROLES" ON "GROUP_ROLES" ("ORGANIZATION_UUID", "GROUP_ID", "RESOURCE_ID", "ROLE"); + + +CREATE TABLE "RULE_REPOSITORIES" ( + "KEE" VARCHAR(200) NOT NULL PRIMARY KEY, + "LANGUAGE" VARCHAR(20) NOT NULL, + "NAME" VARCHAR(4000) NOT NULL, + "CREATED_AT" BIGINT +); + + +CREATE TABLE "RULES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "PLUGIN_RULE_KEY" VARCHAR(200) NOT NULL, + "PLUGIN_NAME" VARCHAR(255) NOT NULL, + "DESCRIPTION" VARCHAR(16777215), + "DESCRIPTION_FORMAT" VARCHAR(20), + "PRIORITY" INTEGER, + "IS_TEMPLATE" BOOLEAN DEFAULT FALSE, + "TEMPLATE_ID" INTEGER, + "PLUGIN_CONFIG_KEY" VARCHAR(200), + "NAME" VARCHAR(200), + "STATUS" VARCHAR(40), + "LANGUAGE" VARCHAR(20), + "NOTE_DATA" CLOB(2147483647), + "NOTE_USER_LOGIN" VARCHAR(255), + "NOTE_CREATED_AT" TIMESTAMP, + "NOTE_UPDATED_AT" TIMESTAMP, + "REMEDIATION_FUNCTION" VARCHAR(20), + "DEF_REMEDIATION_FUNCTION" VARCHAR(20), + "REMEDIATION_GAP_MULT" VARCHAR(20), + "DEF_REMEDIATION_GAP_MULT" VARCHAR(20), + "REMEDIATION_BASE_EFFORT" VARCHAR(20), + "DEF_REMEDIATION_BASE_EFFORT" VARCHAR(20), + "GAP_DESCRIPTION" VARCHAR(4000), + "TAGS" VARCHAR(4000), + "SYSTEM_TAGS" VARCHAR(4000), + "RULE_TYPE" TINYINT, + "CREATED_AT" BIGINT, + "UPDATED_AT" BIGINT +); +CREATE UNIQUE INDEX "RULES_REPO_KEY" ON "RULES" ("PLUGIN_NAME", "PLUGIN_RULE_KEY"); + + +CREATE TABLE "EVENTS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "UUID" VARCHAR(40) NOT NULL, + "NAME" VARCHAR(400), + "ANALYSIS_UUID" VARCHAR(50) NOT NULL, + "COMPONENT_UUID" VARCHAR(50), + "CATEGORY" VARCHAR(50), + "EVENT_DATE" BIGINT NOT NULL, + "CREATED_AT" BIGINT NOT NULL, + "DESCRIPTION" VARCHAR(4000), + "EVENT_DATA" VARCHAR(4000) +); +CREATE INDEX "EVENTS_ANALYSIS" ON "EVENTS" ("ANALYSIS_UUID"); +CREATE INDEX "EVENTS_COMPONENT_UUID" ON "EVENTS" ("COMPONENT_UUID"); +CREATE UNIQUE INDEX "EVENTS_UUID" ON "EVENTS" ("UUID"); + +CREATE TABLE "QUALITY_GATES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "NAME" VARCHAR(100) NOT NULL, + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP, +); +CREATE UNIQUE INDEX "UNIQ_QUALITY_GATES" ON "QUALITY_GATES" ("NAME"); + + +CREATE TABLE "QUALITY_GATE_CONDITIONS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "QGATE_ID" INTEGER, + "METRIC_ID" INTEGER, + "OPERATOR" VARCHAR(3), + "VALUE_ERROR" VARCHAR(64), + "VALUE_WARNING" VARCHAR(64), + "PERIOD" INTEGER, + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP, +); + + +CREATE TABLE "PROPERTIES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "PROP_KEY" VARCHAR(512) NOT NULL, + "RESOURCE_ID" INTEGER, + "USER_ID" INTEGER, + "IS_EMPTY" BOOLEAN NOT NULL, + "TEXT_VALUE" VARCHAR(4000), + "CLOB_VALUE" CLOB(2147483647), + "CREATED_AT" BIGINT +); +CREATE INDEX "PROPERTIES_KEY" ON "PROPERTIES" ("PROP_KEY"); + + +CREATE TABLE "PROJECT_LINKS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "COMPONENT_UUID" VARCHAR(50), + "LINK_TYPE" VARCHAR(20), + "NAME" VARCHAR(128), + "HREF" VARCHAR(2048) NOT NULL +); + + +CREATE TABLE "DUPLICATIONS_INDEX" ( + "ID" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "ANALYSIS_UUID" VARCHAR(50) NOT NULL, + "COMPONENT_UUID" VARCHAR(50) NOT NULL, + "HASH" VARCHAR(50) NOT NULL, + "INDEX_IN_FILE" INTEGER NOT NULL, + "START_LINE" INTEGER NOT NULL, + "END_LINE" INTEGER NOT NULL +); +CREATE INDEX "DUPLICATIONS_INDEX_HASH" ON "DUPLICATIONS_INDEX" ("HASH"); +CREATE INDEX "DUPLICATION_ANALYSIS_COMPONENT" ON "DUPLICATIONS_INDEX" ("ANALYSIS_UUID", "COMPONENT_UUID"); + + +CREATE TABLE "PROJECT_MEASURES" ( + "ID" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "VALUE" DOUBLE, + "METRIC_ID" INTEGER NOT NULL, + "COMPONENT_UUID" VARCHAR(50) NOT NULL, + "ANALYSIS_UUID" VARCHAR(50) NOT NULL, + "TEXT_VALUE" VARCHAR(4000), + "ALERT_STATUS" VARCHAR(5), + "ALERT_TEXT" VARCHAR(4000), + "DESCRIPTION" VARCHAR(4000), + "PERSON_ID" INTEGER, + "VARIATION_VALUE_1" DOUBLE, + "VARIATION_VALUE_2" DOUBLE, + "VARIATION_VALUE_3" DOUBLE, + "VARIATION_VALUE_4" DOUBLE, + "VARIATION_VALUE_5" DOUBLE, + "MEASURE_DATA" BINARY +); +CREATE INDEX "MEASURES_COMPONENT_UUID" ON "PROJECT_MEASURES" ("COMPONENT_UUID"); +CREATE INDEX "MEASURES_ANALYSIS_METRIC" ON "PROJECT_MEASURES" ("ANALYSIS_UUID", "METRIC_ID"); +CREATE INDEX "MEASURES_PERSON" ON "PROJECT_MEASURES" ("PERSON_ID"); + + +CREATE TABLE "INTERNAL_PROPERTIES" ( + "KEE" VARCHAR(50) NOT NULL PRIMARY KEY, + "IS_EMPTY" BOOLEAN NOT NULL, + "TEXT_VALUE" VARCHAR(4000), + "CLOB_VALUE" CLOB, + "CREATED_AT" BIGINT +); +CREATE UNIQUE INDEX "UNIQ_INTERNAL_PROPERTIES" ON "INTERNAL_PROPERTIES" ("KEE"); + + +CREATE TABLE "PROJECTS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "ORGANIZATION_UUID" VARCHAR(40) NOT NULL, + "KEE" VARCHAR(400), + "UUID" VARCHAR(50) NOT NULL, + "UUID_PATH" VARCHAR(1500) NOT NULL, + "ROOT_UUID" VARCHAR(50) NOT NULL, + "PROJECT_UUID" VARCHAR(50), + "MODULE_UUID" VARCHAR(50), + "MODULE_UUID_PATH" VARCHAR(1500), + "NAME" VARCHAR(2000), + "DESCRIPTION" VARCHAR(2000), + "ENABLED" BOOLEAN NOT NULL DEFAULT TRUE, + "SCOPE" VARCHAR(3), + "QUALIFIER" VARCHAR(10), + "DEPRECATED_KEE" VARCHAR(400), + "PATH" VARCHAR(2000), + "LANGUAGE" VARCHAR(20), + "COPY_COMPONENT_UUID" VARCHAR(50), + "LONG_NAME" VARCHAR(2000), + "DEVELOPER_UUID" VARCHAR(50), + "CREATED_AT" TIMESTAMP, + "AUTHORIZATION_UPDATED_AT" BIGINT, + "B_CHANGED" BOOLEAN, + "B_COPY_COMPONENT_UUID" VARCHAR(50), + "B_DESCRIPTION" VARCHAR(2000), + "B_ENABLED" BOOLEAN, + "B_UUID_PATH" VARCHAR(1500), + "B_LANGUAGE" VARCHAR(20), + "B_LONG_NAME" VARCHAR(500), + "B_MODULE_UUID" VARCHAR(50), + "B_MODULE_UUID_PATH" VARCHAR(1500), + "B_NAME" VARCHAR(500), + "B_PATH" VARCHAR(2000), + "B_QUALIFIER" VARCHAR(10) +); +CREATE INDEX "PROJECTS_ORGANIZATION" ON "PROJECTS" ("ORGANIZATION_UUID"); +CREATE UNIQUE INDEX "PROJECTS_KEE" ON "PROJECTS" ("KEE"); +CREATE INDEX "PROJECTS_ROOT_UUID" ON "PROJECTS" ("ROOT_UUID"); +CREATE UNIQUE INDEX "PROJECTS_UUID" ON "PROJECTS" ("UUID"); +CREATE INDEX "PROJECTS_PROJECT_UUID" ON "PROJECTS" ("PROJECT_UUID"); +CREATE INDEX "PROJECTS_MODULE_UUID" ON "PROJECTS" ("MODULE_UUID"); +CREATE INDEX "PROJECTS_QUALIFIER" ON "PROJECTS" ("QUALIFIER"); + + +CREATE TABLE "MANUAL_MEASURES" ( + "ID" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "METRIC_ID" INTEGER NOT NULL, + "COMPONENT_UUID" VARCHAR(50), + "VALUE" DOUBLE, + "TEXT_VALUE" VARCHAR(4000), + "USER_LOGIN" VARCHAR(255), + "DESCRIPTION" VARCHAR(4000), + "CREATED_AT" BIGINT, + "UPDATED_AT" BIGINT +); +CREATE INDEX "MANUAL_MEASURES_COMPONENT_UUID" ON "MANUAL_MEASURES" ("COMPONENT_UUID"); + + +CREATE TABLE "ACTIVE_RULES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "PROFILE_ID" INTEGER NOT NULL, + "RULE_ID" INTEGER NOT NULL, + "FAILURE_LEVEL" INTEGER NOT NULL, + "INHERITANCE" VARCHAR(10), + "CREATED_AT" BIGINT, + "UPDATED_AT" BIGINT +); +CREATE UNIQUE INDEX "ACTIVE_RULES_UNIQUE" ON "ACTIVE_RULES" ("PROFILE_ID","RULE_ID"); + + +CREATE TABLE "NOTIFICATIONS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "DATA" BLOB +); + + +CREATE TABLE "USER_ROLES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "ORGANIZATION_UUID" VARCHAR(40) NOT NULL, + "USER_ID" INTEGER, + "RESOURCE_ID" INTEGER, + "ROLE" VARCHAR(64) NOT NULL +); +CREATE INDEX "USER_ROLES_RESOURCE" ON "USER_ROLES" ("RESOURCE_ID"); +CREATE INDEX "USER_ROLES_USER" ON "USER_ROLES" ("USER_ID"); + + +CREATE TABLE "ACTIVE_RULE_PARAMETERS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "ACTIVE_RULE_ID" INTEGER NOT NULL, + "RULES_PARAMETER_ID" INTEGER NOT NULL, + "RULES_PARAMETER_KEY" VARCHAR(128), + "VALUE" VARCHAR(4000) +); + + +CREATE TABLE "USERS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "LOGIN" VARCHAR(255), + "NAME" VARCHAR(200), + "EMAIL" VARCHAR(100), + "CRYPTED_PASSWORD" VARCHAR(40), + "SALT" VARCHAR(40), + "ACTIVE" BOOLEAN DEFAULT TRUE, + "SCM_ACCOUNTS" VARCHAR(4000), + "EXTERNAL_IDENTITY" VARCHAR(255), + "EXTERNAL_IDENTITY_PROVIDER" VARCHAR(100), + "IS_ROOT" BOOLEAN NOT NULL, + "USER_LOCAL" BOOLEAN, + "CREATED_AT" BIGINT, + "UPDATED_AT" BIGINT +); +CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS" ("LOGIN"); +CREATE INDEX "USERS_UPDATED_AT" ON "USERS" ("UPDATED_AT"); + + +CREATE TABLE "METRICS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "NAME" VARCHAR(64) NOT NULL, + "DESCRIPTION" VARCHAR(255), + "DIRECTION" INTEGER NOT NULL DEFAULT 0, + "DOMAIN" VARCHAR(64), + "SHORT_NAME" VARCHAR(64), + "QUALITATIVE" BOOLEAN NOT NULL DEFAULT FALSE, + "VAL_TYPE" VARCHAR(8), + "USER_MANAGED" BOOLEAN DEFAULT FALSE, + "ENABLED" BOOLEAN DEFAULT TRUE, + "WORST_VALUE" DOUBLE, + "BEST_VALUE" DOUBLE, + "OPTIMIZED_BEST_VALUE" BOOLEAN, + "HIDDEN" BOOLEAN, + "DELETE_HISTORICAL_DATA" BOOLEAN, + "DECIMAL_SCALE" INTEGER +); +CREATE UNIQUE INDEX "METRICS_UNIQUE_NAME" ON "METRICS" ("NAME"); + + +CREATE TABLE "LOADED_TEMPLATES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "KEE" VARCHAR(200), + "TEMPLATE_TYPE" VARCHAR(15) +); + + +CREATE TABLE "AUTHORS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "PERSON_ID" INTEGER, + "LOGIN" VARCHAR(255), + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP +); +CREATE UNIQUE INDEX "UNIQ_AUTHOR_LOGINS" ON "AUTHORS" ("LOGIN"); + + +CREATE TABLE "ISSUES" ( + "ID" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "KEE" VARCHAR(50) UNIQUE NOT NULL, + "COMPONENT_UUID" VARCHAR(50), + "PROJECT_UUID" VARCHAR(50), + "RULE_ID" INTEGER, + "SEVERITY" VARCHAR(10), + "MANUAL_SEVERITY" BOOLEAN NOT NULL, + "MESSAGE" VARCHAR(4000), + "LINE" INTEGER, + "GAP" DOUBLE, + "EFFORT" INTEGER, + "STATUS" VARCHAR(20), + "RESOLUTION" VARCHAR(20), + "CHECKSUM" VARCHAR(1000), + "REPORTER" VARCHAR(255), + "ASSIGNEE" VARCHAR(255), + "AUTHOR_LOGIN" VARCHAR(255), + "ACTION_PLAN_KEY" VARCHAR(50) NULL, + "ISSUE_ATTRIBUTES" VARCHAR(4000), + "TAGS" VARCHAR(4000), + "ISSUE_CREATION_DATE" BIGINT, + "ISSUE_CLOSE_DATE" BIGINT, + "ISSUE_UPDATE_DATE" BIGINT, + "CREATED_AT" BIGINT, + "UPDATED_AT" BIGINT, + "LOCATIONS" BLOB, + "ISSUE_TYPE" TINYINT +); +CREATE UNIQUE INDEX "ISSUES_KEE" ON "ISSUES" ("KEE"); +CREATE INDEX "ISSUES_COMPONENT_UUID" ON "ISSUES" ("COMPONENT_UUID"); +CREATE INDEX "ISSUES_PROJECT_UUID" ON "ISSUES" ("PROJECT_UUID"); +CREATE INDEX "ISSUES_RULE_ID" ON "ISSUES" ("RULE_ID"); +CREATE INDEX "ISSUES_RESOLUTION" ON "ISSUES" ("RESOLUTION"); +CREATE INDEX "ISSUES_ASSIGNEE" ON "ISSUES" ("ASSIGNEE"); +CREATE INDEX "ISSUES_CREATION_DATE" ON "ISSUES" ("ISSUE_CREATION_DATE"); +CREATE INDEX "ISSUES_UPDATED_AT" ON "ISSUES" ("UPDATED_AT"); + + +CREATE TABLE "ISSUE_CHANGES" ( + "ID" BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "KEE" VARCHAR(50), + "ISSUE_KEY" VARCHAR(50) NOT NULL, + "USER_LOGIN" VARCHAR(255), + "CHANGE_TYPE" VARCHAR(40), + "CHANGE_DATA" VARCHAR(16777215), + "CREATED_AT" BIGINT, + "UPDATED_AT" BIGINT, + "ISSUE_CHANGE_CREATION_DATE" BIGINT +); +CREATE INDEX "ISSUE_CHANGES_KEE" ON "ISSUE_CHANGES" ("KEE"); +CREATE INDEX "ISSUE_CHANGES_ISSUE_KEY" ON "ISSUE_CHANGES" ("ISSUE_KEY"); + + +CREATE TABLE "PERMISSION_TEMPLATES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "ORGANIZATION_UUID" VARCHAR(40) NOT NULL, + "NAME" VARCHAR(100) NOT NULL, + "KEE" VARCHAR(100) NOT NULL, + "DESCRIPTION" VARCHAR(4000), + "KEY_PATTERN" VARCHAR(500), + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP +); + + +CREATE TABLE "PERM_TPL_CHARACTERISTICS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "TEMPLATE_ID" INTEGER NOT NULL, + "PERMISSION_KEY" VARCHAR(64) NOT NULL, + "WITH_PROJECT_CREATOR" BOOLEAN NOT NULL DEFAULT FALSE, + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL +); +CREATE UNIQUE INDEX "UNIQ_PERM_TPL_CHARAC" ON "PERM_TPL_CHARACTERISTICS" ("TEMPLATE_ID", "PERMISSION_KEY"); + + +CREATE TABLE "PERM_TEMPLATES_USERS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "USER_ID" INTEGER NOT NULL, + "TEMPLATE_ID" INTEGER NOT NULL, + "PERMISSION_REFERENCE" VARCHAR(64) NOT NULL, + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP +); + + +CREATE TABLE "PERM_TEMPLATES_GROUPS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "GROUP_ID" INTEGER, + "TEMPLATE_ID" INTEGER NOT NULL, + "PERMISSION_REFERENCE" VARCHAR(64) NOT NULL, + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP +); + + +CREATE TABLE "QPROFILE_CHANGES" ( + "KEE" VARCHAR(40) NOT NULL PRIMARY KEY, + "QPROFILE_KEY" VARCHAR(255) NOT NULL, + "CHANGE_TYPE" VARCHAR(20) NOT NULL, + "CREATED_AT" BIGINT NOT NULL, + "USER_LOGIN" VARCHAR(255), + "CHANGE_DATA" CLOB +); +CREATE INDEX "QPROFILE_CHANGES_QPROFILE_KEY" ON "QPROFILE_CHANGES" ("QPROFILE_KEY"); + + +CREATE TABLE "FILE_SOURCES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "PROJECT_UUID" VARCHAR(50) NOT NULL, + "FILE_UUID" VARCHAR(50) NOT NULL, + "LINE_HASHES" CLOB(2147483647), + "BINARY_DATA" BLOB, + "DATA_TYPE" VARCHAR(20), + "DATA_HASH" VARCHAR(50), + "SRC_HASH" VARCHAR(50), + "REVISION" VARCHAR(100), + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL +); +CREATE INDEX "FILE_SOURCES_PROJECT_UUID" ON "FILE_SOURCES" ("PROJECT_UUID"); +CREATE UNIQUE INDEX "FILE_SOURCES_UUID_TYPE" ON "FILE_SOURCES" ("FILE_UUID", "DATA_TYPE"); +CREATE INDEX "FILE_SOURCES_UPDATED_AT" ON "FILE_SOURCES" ("UPDATED_AT"); + + +CREATE TABLE "CE_QUEUE" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "UUID" VARCHAR(40) NOT NULL, + "TASK_TYPE" VARCHAR(15) NOT NULL, + "COMPONENT_UUID" VARCHAR(40) NULL, + "STATUS" VARCHAR(15) NOT NULL, + "SUBMITTER_LOGIN" VARCHAR(255) NULL, + "STARTED_AT" BIGINT NULL, + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL +); +CREATE UNIQUE INDEX "CE_QUEUE_UUID" ON "CE_QUEUE" ("UUID"); +CREATE INDEX "CE_QUEUE_COMPONENT_UUID" ON "CE_QUEUE" ("COMPONENT_UUID"); +CREATE INDEX "CE_QUEUE_STATUS" ON "CE_QUEUE" ("STATUS"); + + +CREATE TABLE "CE_ACTIVITY" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "UUID" VARCHAR(40) NOT NULL, + "TASK_TYPE" VARCHAR(15) NOT NULL, + "COMPONENT_UUID" VARCHAR(40) NULL, + "ANALYSIS_UUID" VARCHAR(50) NULL, + "STATUS" VARCHAR(15) NOT NULL, + "IS_LAST" BOOLEAN NOT NULL, + "IS_LAST_KEY" VARCHAR(55) NOT NULL, + "SUBMITTER_LOGIN" VARCHAR(255) NULL, + "SUBMITTED_AT" BIGINT NOT NULL, + "STARTED_AT" BIGINT NULL, + "EXECUTED_AT" BIGINT NULL, + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL, + "EXECUTION_TIME_MS" BIGINT NULL, + "ERROR_MESSAGE" VARCHAR(1000), + "ERROR_STACKTRACE" CLOB(2147483647) +); +CREATE UNIQUE INDEX "CE_ACTIVITY_UUID" ON "CE_ACTIVITY" ("UUID"); +CREATE INDEX "CE_ACTIVITY_COMPONENT_UUID" ON "CE_ACTIVITY" ("COMPONENT_UUID"); +CREATE INDEX "CE_ACTIVITY_ISLASTKEY" ON "CE_ACTIVITY" ("IS_LAST_KEY"); +CREATE INDEX "CE_ACTIVITY_ISLAST_STATUS" ON "CE_ACTIVITY" ("IS_LAST", "STATUS"); + + +CREATE TABLE "CE_TASK_INPUT" ( + "TASK_UUID" VARCHAR(40) NOT NULL PRIMARY KEY, + "INPUT_DATA" BLOB, + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL +); + + +CREATE TABLE "CE_SCANNER_CONTEXT" ( + "TASK_UUID" VARCHAR(40) NOT NULL PRIMARY KEY, + "CONTEXT_DATA" BLOB NOT NULL, + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL +); + + +CREATE TABLE "USER_TOKENS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "LOGIN" VARCHAR(255) NOT NULL, + "NAME" VARCHAR(100) NOT NULL, + "TOKEN_HASH" VARCHAR(255) NOT NULL, + "CREATED_AT" BIGINT NOT NULL +); +CREATE UNIQUE INDEX "USER_TOKENS_TOKEN_HASH" ON "USER_TOKENS" ("TOKEN_HASH"); +CREATE UNIQUE INDEX "USER_TOKENS_LOGIN_NAME" ON "USER_TOKENS" ("LOGIN", "NAME"); + + +CREATE TABLE "WEBHOOK_DELIVERIES" ( + "UUID" VARCHAR(40) NOT NULL PRIMARY KEY, + "COMPONENT_UUID" VARCHAR(40) NOT NULL, + "CE_TASK_UUID" VARCHAR(40) NOT NULL, + "NAME" VARCHAR(100) NOT NULL, + "URL" VARCHAR(2000) NOT NULL, + "SUCCESS" BOOLEAN NOT NULL, + "HTTP_STATUS" INT, + "DURATION_MS" INT, + "PAYLOAD" CLOB NOT NULL, + "ERROR_STACKTRACE" CLOB, + "CREATED_AT" BIGINT NOT NULL +); +CREATE UNIQUE INDEX "PK_WEBHOOK_DELIVERIES" ON "WEBHOOK_DELIVERIES" ("UUID"); +CREATE INDEX "COMPONENT_UUID" ON "WEBHOOK_DELIVERIES" ("COMPONENT_UUID"); +CREATE INDEX "CE_TASK_UUID" ON "WEBHOOK_DELIVERIES" ("CE_TASK_UUID"); diff --git a/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema_migrations-h2.ddl b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema_migrations-h2.ddl new file mode 100644 index 00000000000..aad2a0750a7 --- /dev/null +++ b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema_migrations-h2.ddl @@ -0,0 +1,4 @@ +CREATE TABLE "SCHEMA_MIGRATIONS" ( + "VERSION" VARCHAR(256) NOT NULL +); +CREATE INDEX "UNIQUE_SCHEMA_MIGRATIONS" ON "SCHEMA_MIGRATIONS" ("VERSION"); diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java b/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java new file mode 100644 index 00000000000..5777e7cb974 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java @@ -0,0 +1,601 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import java.io.InputStream; +import java.math.BigDecimal; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.apache.commons.dbutils.QueryRunner; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.dbunit.Assertion; +import org.dbunit.DatabaseUnitException; +import org.dbunit.assertion.DiffCollectingFailureHandler; +import org.dbunit.assertion.Difference; +import org.dbunit.database.DatabaseConfig; +import org.dbunit.database.IDatabaseConnection; +import org.dbunit.dataset.CompositeDataSet; +import org.dbunit.dataset.IDataSet; +import org.dbunit.dataset.ITable; +import org.dbunit.dataset.ReplacementDataSet; +import org.dbunit.dataset.filter.DefaultColumnFilter; +import org.dbunit.dataset.xml.FlatXmlDataSet; +import org.dbunit.ext.mssql.InsertIdentityOperation; +import org.dbunit.operation.DatabaseOperation; +import org.junit.rules.ExternalResource; +import org.sonar.api.utils.log.Loggers; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.Lists.asList; +import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Maps.newHashMap; +import static java.sql.ResultSetMetaData.columnNoNulls; +import static java.sql.ResultSetMetaData.columnNullable; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +public class AbstractDbTester<T extends CoreTestDb> extends ExternalResource { + protected static final Joiner COMMA_JOINER = Joiner.on(", "); + protected final T db; + + public AbstractDbTester(T db) { + this.db = db; + } + + public void executeUpdateSql(String sql, Object... params) { + try (Connection connection = getConnection()) { + new QueryRunner().update(connection, sql, params); + if (!connection.getAutoCommit()) { + connection.commit(); + } + } catch (SQLException e) { + SQLException nextException = e.getNextException(); + if (nextException != null) { + throw new IllegalStateException("Fail to execute sql: " + sql, + new SQLException(e.getMessage(), nextException.getSQLState(), nextException.getErrorCode(), nextException)); + } + throw new IllegalStateException("Fail to execute sql: " + sql, e); + } catch (Exception e) { + throw new IllegalStateException("Fail to execute sql: " + sql, e); + } + } + + public void executeDdl(String ddl) { + try (Connection connection = getConnection(); + Statement stmt = connection.createStatement()) { + stmt.execute(ddl); + } catch (SQLException e) { + throw new IllegalStateException("Failed to execute DDL: " + ddl, e); + } + } + + /** + * Very simple helper method to insert some data into a table. + * It's the responsibility of the caller to convert column values to string. + */ + public void executeInsert(String table, String firstColumn, Object... others) { + executeInsert(table, mapOf(firstColumn, others)); + } + + private static Map<String, Object> mapOf(String firstColumn, Object... values) { + ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder(); + List<Object> args = asList(firstColumn, values); + for (int i = 0; i < args.size(); i++) { + String key = args.get(i).toString(); + Object value = args.get(i + 1); + if (value != null) { + builder.put(key, value); + } + i++; + } + return builder.build(); + } + + /** + * Very simple helper method to insert some data into a table. + * It's the responsibility of the caller to convert column values to string. + */ + public void executeInsert(String table, Map<String, Object> valuesByColumn) { + if (valuesByColumn.isEmpty()) { + throw new IllegalArgumentException("Values cannot be empty"); + } + + String sql = "insert into " + table.toLowerCase(Locale.ENGLISH) + " (" + + COMMA_JOINER.join(valuesByColumn.keySet()) + + ") values (" + + COMMA_JOINER.join(Collections.nCopies(valuesByColumn.size(), '?')) + + ")"; + executeUpdateSql(sql, valuesByColumn.values().toArray(new Object[valuesByColumn.size()])); + } + + /** + * Returns the number of rows in the table. Example: + * <pre>int issues = countRowsOfTable("issues")</pre> + */ + public int countRowsOfTable(String tableName) { + return countRowsOfTable(tableName, new NewConnectionSupplier()); + } + + protected int countRowsOfTable(String tableName, ConnectionSupplier connectionSupplier) { + checkArgument(StringUtils.containsNone(tableName, " "), "Parameter must be the name of a table. Got " + tableName); + return countSql("select count(1) from " + tableName.toLowerCase(Locale.ENGLISH), connectionSupplier); + } + + /** + * Executes a SQL request starting with "SELECT COUNT(something) FROM", for example: + * <pre>int OpenIssues = countSql("select count('id') from issues where status is not null")</pre> + */ + public int countSql(String sql) { + return countSql(sql, new NewConnectionSupplier()); + } + + protected int countSql(String sql, ConnectionSupplier connectionSupplier) { + checkArgument(StringUtils.contains(sql, "count("), + "Parameter must be a SQL request containing 'count(x)' function. Got " + sql); + try ( + ConnectionSupplier supplier = connectionSupplier; + PreparedStatement stmt = supplier.get().prepareStatement(sql); + ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + return rs.getInt(1); + } + throw new IllegalStateException("No results for " + sql); + + } catch (Exception e) { + throw new IllegalStateException("Fail to execute sql: " + sql, e); + } + } + + public List<Map<String, Object>> select(String selectSql) { + return select(selectSql, new NewConnectionSupplier()); + } + + protected List<Map<String, Object>> select(String selectSql, ConnectionSupplier connectionSupplier) { + try ( + ConnectionSupplier supplier = connectionSupplier; + PreparedStatement stmt = supplier.get().prepareStatement(selectSql); + ResultSet rs = stmt.executeQuery()) { + return getHashMap(rs); + } catch (Exception e) { + throw new IllegalStateException("Fail to execute sql: " + selectSql, e); + } + } + + public Map<String, Object> selectFirst(String selectSql) { + return selectFirst(selectSql, new NewConnectionSupplier()); + } + + protected Map<String, Object> selectFirst(String selectSql, ConnectionSupplier connectionSupplier) { + List<Map<String, Object>> rows = select(selectSql, connectionSupplier); + if (rows.isEmpty()) { + throw new IllegalStateException("No results for " + selectSql); + } else if (rows.size() > 1) { + throw new IllegalStateException("Too many results for " + selectSql); + } + return rows.get(0); + } + + private static List<Map<String, Object>> getHashMap(ResultSet resultSet) throws Exception { + ResultSetMetaData metaData = resultSet.getMetaData(); + int colCount = metaData.getColumnCount(); + List<Map<String, Object>> rows = newArrayList(); + while (resultSet.next()) { + Map<String, Object> columns = newHashMap(); + for (int i = 1; i <= colCount; i++) { + Object value = resultSet.getObject(i); + if (value instanceof Clob) { + Clob clob = (Clob) value; + value = IOUtils.toString((clob.getAsciiStream())); + doClobFree(clob); + } else if (value instanceof BigDecimal) { + // In Oracle, INTEGER types are mapped as BigDecimal + BigDecimal bgValue = ((BigDecimal) value); + if (bgValue.scale() == 0) { + value = bgValue.longValue(); + } else { + value = bgValue.doubleValue(); + } + } else if (value instanceof Integer) { + // To be consistent, all INTEGER types are mapped as Long + value = ((Integer) value).longValue(); + } + columns.put(metaData.getColumnLabel(i), value); + } + rows.add(columns); + } + return rows; + } + + public void prepareDbUnit(Class testClass, String... testNames) { + InputStream[] streams = new InputStream[testNames.length]; + try { + for (int i = 0; i < testNames.length; i++) { + String path = "/" + testClass.getName().replace('.', '/') + "/" + testNames[i]; + streams[i] = testClass.getResourceAsStream(path); + if (streams[i] == null) { + throw new IllegalStateException("DbUnit file not found: " + path); + } + } + + prepareDbUnit(streams); + db.getCommands().resetPrimaryKeys(db.getDatabase().getDataSource()); + } catch (SQLException e) { + throw translateException("Could not setup DBUnit data", e); + } finally { + for (InputStream stream : streams) { + IOUtils.closeQuietly(stream); + } + } + } + + private void prepareDbUnit(InputStream... dataSetStream) { + IDatabaseConnection connection = null; + try { + IDataSet[] dataSets = new IDataSet[dataSetStream.length]; + for (int i = 0; i < dataSetStream.length; i++) { + dataSets[i] = dbUnitDataSet(dataSetStream[i]); + } + db.getDbUnitTester().setDataSet(new CompositeDataSet(dataSets)); + connection = dbUnitConnection(); + new InsertIdentityOperation(DatabaseOperation.INSERT).execute(connection, db.getDbUnitTester().getDataSet()); + } catch (Exception e) { + throw translateException("Could not setup DBUnit data", e); + } finally { + closeQuietly(connection); + } + } + + public void assertDbUnitTable(Class testClass, String filename, String table, String... columns) { + IDatabaseConnection connection = dbUnitConnection(); + try { + IDataSet dataSet = connection.createDataSet(); + String path = "/" + testClass.getName().replace('.', '/') + "/" + filename; + IDataSet expectedDataSet = dbUnitDataSet(testClass.getResourceAsStream(path)); + ITable filteredTable = DefaultColumnFilter.includedColumnsTable(dataSet.getTable(table), columns); + ITable filteredExpectedTable = DefaultColumnFilter.includedColumnsTable(expectedDataSet.getTable(table), columns); + Assertion.assertEquals(filteredExpectedTable, filteredTable); + } catch (DatabaseUnitException e) { + fail(e.getMessage()); + } catch (SQLException e) { + throw translateException("Error while checking results", e); + } finally { + closeQuietly(connection); + } + } + + public void assertDbUnit(Class testClass, String filename, String... tables) { + assertDbUnit(testClass, filename, new String[0], tables); + } + + public void assertDbUnit(Class testClass, String filename, String[] excludedColumnNames, String... tables) { + IDatabaseConnection connection = null; + try { + connection = dbUnitConnection(); + + IDataSet dataSet = connection.createDataSet(); + String path = "/" + testClass.getName().replace('.', '/') + "/" + filename; + InputStream inputStream = testClass.getResourceAsStream(path); + if (inputStream == null) { + throw new IllegalStateException(String.format("File '%s' does not exist", path)); + } + IDataSet expectedDataSet = dbUnitDataSet(inputStream); + for (String table : tables) { + DiffCollectingFailureHandler diffHandler = new DiffCollectingFailureHandler(); + + ITable filteredTable = DefaultColumnFilter.excludedColumnsTable(dataSet.getTable(table), excludedColumnNames); + ITable filteredExpectedTable = DefaultColumnFilter.excludedColumnsTable(expectedDataSet.getTable(table), excludedColumnNames); + Assertion.assertEquals(filteredExpectedTable, filteredTable, diffHandler); + // Evaluate the differences and ignore some column values + List diffList = diffHandler.getDiffList(); + for (Object o : diffList) { + Difference diff = (Difference) o; + if (!"[ignore]".equals(diff.getExpectedValue())) { + throw new DatabaseUnitException(diff.toString()); + } + } + } + } catch (DatabaseUnitException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (Exception e) { + throw translateException("Error while checking results", e); + } finally { + closeQuietly(connection); + } + } + + public void assertColumnDefinition(String table, String column, int expectedType, @Nullable Integer expectedSize) { + assertColumnDefinition(table, column, expectedType, expectedSize, null); + } + + public void assertColumnDefinition(String table, String column, int expectedType, @Nullable Integer expectedSize, @Nullable Boolean isNullable) { + try (Connection connection = getConnection(); + PreparedStatement stmt = connection.prepareStatement("select * from " + table); + ResultSet res = stmt.executeQuery()) { + Integer columnIndex = getColumnIndex(res, column); + if (columnIndex == null) { + fail("The column '" + column + "' does not exist"); + } + + assertThat(res.getMetaData().getColumnType(columnIndex)).isEqualTo(expectedType); + if (expectedSize != null) { + assertThat(res.getMetaData().getColumnDisplaySize(columnIndex)).isEqualTo(expectedSize); + } + if (isNullable != null) { + assertThat(res.getMetaData().isNullable(columnIndex)).isEqualTo(isNullable ? columnNullable : columnNoNulls); + } + } catch (Exception e) { + throw new IllegalStateException("Fail to check column", e); + } + } + + public void assertTableDoesNotExist(String table) { + try (Connection connection = getConnection()) { + boolean tableExists = DatabaseUtils.tableExists(table, connection); + assertThat(tableExists).isFalse(); + } catch (Exception e) { + throw new IllegalStateException("Fail to check if table exists", e); + } + } + + /** + * Verify that non-unique index exists on columns + */ + public void assertIndex(String tableName, String indexName, String expectedColumn, String... expectedSecondaryColumns) { + assertIndexImpl(tableName, indexName, false, expectedColumn, expectedSecondaryColumns); + } + + /** + * Verify that unique index exists on columns + */ + public void assertUniqueIndex(String tableName, String indexName, String expectedColumn, String... expectedSecondaryColumns) { + assertIndexImpl(tableName, indexName, true, expectedColumn, expectedSecondaryColumns); + } + + private void assertIndexImpl(String tableName, String indexName, boolean expectedUnique, String expectedColumn, String... expectedSecondaryColumns) { + try (Connection connection = getConnection(); + ResultSet rs = connection.getMetaData().getIndexInfo(null, null, tableName.toUpperCase(Locale.ENGLISH), false, false)) { + List<String> onColumns = new ArrayList<>(); + while (rs.next()) { + if (indexName.equalsIgnoreCase(rs.getString("INDEX_NAME"))) { + assertThat(rs.getBoolean("NON_UNIQUE")).isEqualTo(!expectedUnique); + int position = rs.getInt("ORDINAL_POSITION"); + onColumns.add(position - 1, rs.getString("COLUMN_NAME").toLowerCase(Locale.ENGLISH)); + } + } + assertThat(asList(expectedColumn, expectedSecondaryColumns)).isEqualTo(onColumns); + } catch (SQLException e) { + throw new IllegalStateException("Fail to check index", e); + } + } + + /** + * Verify that index with name {@code indexName} does not exist on the table {@code tableName} + */ + public void assertIndexDoesNotExist(String tableName, String indexName) { + try (Connection connection = getConnection(); + ResultSet rs = connection.getMetaData().getIndexInfo(null, null, tableName.toUpperCase(Locale.ENGLISH), false, false)) { + List<String> indices = new ArrayList<>(); + while (rs.next()) { + indices.add(rs.getString("INDEX_NAME").toLowerCase(Locale.ENGLISH)); + } + assertThat(indices).doesNotContain(indexName); + } catch (SQLException e) { + throw new IllegalStateException("Fail to check existence of index", e); + } + } + + public void assertPrimaryKey(String tableName, @Nullable String expectedPkName, String columnName, String... otherColumnNames) { + try (Connection connection = getConnection()) { + PK pk = pkOf(connection, tableName.toUpperCase(Locale.ENGLISH)); + if (pk == null) { + pkOf(connection, tableName.toLowerCase(Locale.ENGLISH)); + } + assertThat(pk).as("No primary key is defined on table %s", tableName).isNotNull(); + if (expectedPkName != null) { + assertThat(pk.getName()).isEqualToIgnoringCase(expectedPkName); + } + List<String> expectedColumns = ImmutableList.copyOf(Iterables.concat(Collections.singletonList(columnName), Arrays.asList(otherColumnNames))); + assertThat(pk.getColumns()).as("Primary key does not have the '%s' expected columns", expectedColumns.size()).hasSize(expectedColumns.size()); + + Iterator<String> expectedColumnsIt = expectedColumns.iterator(); + Iterator<String> actualColumnsIt = pk.getColumns().iterator(); + while (expectedColumnsIt.hasNext() && actualColumnsIt.hasNext()) { + assertThat(actualColumnsIt.next()).isEqualToIgnoringCase(expectedColumnsIt.next()); + } + } catch (SQLException e) { + throw new IllegalStateException("Fail to check primary key", e); + } + } + + @CheckForNull + private PK pkOf(Connection connection, String tableName) throws SQLException { + try (ResultSet resultSet = connection.getMetaData().getPrimaryKeys(null, null, tableName)) { + String pkName = null; + ArrayList<String> columnNames = null; + while (resultSet.next()) { + if (columnNames == null) { + pkName = resultSet.getString("PK_NAME"); + columnNames = new ArrayList<>(1); + } else { + assertThat(pkName).as("Multiple primary keys found").isEqualTo(resultSet.getString("PK_NAME")); + } + columnNames.add(resultSet.getInt("KEY_SEQ") - 1, resultSet.getString("COLUMN_NAME")); + } + if (columnNames == null) { + return null; + } + return new PK(pkName, columnNames); + } + } + + @CheckForNull + private Integer getColumnIndex(ResultSet res, String column) { + try { + ResultSetMetaData meta = res.getMetaData(); + int numCol = meta.getColumnCount(); + for (int i = 1; i < numCol + 1; i++) { + if (meta.getColumnLabel(i).toLowerCase().equals(column.toLowerCase())) { + return i; + } + } + return null; + + } catch (Exception e) { + throw new IllegalStateException("Fail to get column index"); + } + } + + private IDataSet dbUnitDataSet(InputStream stream) { + try { + ReplacementDataSet dataSet = new ReplacementDataSet(new FlatXmlDataSet(stream)); + dataSet.addReplacementObject("[null]", null); + dataSet.addReplacementObject("[false]", Boolean.FALSE); + dataSet.addReplacementObject("[true]", Boolean.TRUE); + + return dataSet; + } catch (Exception e) { + throw translateException("Could not read the dataset stream", e); + } + } + + private IDatabaseConnection dbUnitConnection() { + try { + IDatabaseConnection connection = db.getDbUnitTester().getConnection(); + connection.getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, db.getDbUnitFactory()); + return connection; + } catch (Exception e) { + throw translateException("Error while getting connection", e); + } + } + + public static RuntimeException translateException(String msg, Exception cause) { + RuntimeException runtimeException = new RuntimeException(String.format("%s: [%s] %s", msg, cause.getClass().getName(), cause.getMessage())); + runtimeException.setStackTrace(cause.getStackTrace()); + return runtimeException; + } + + private static void doClobFree(Clob clob) throws SQLException { + try { + clob.free(); + } catch (AbstractMethodError e) { + // JTS driver do not implement free() as it's using JDBC 3.0 + } + } + + private void closeQuietly(@Nullable IDatabaseConnection connection) { + try { + if (connection != null) { + connection.close(); + } + } catch (SQLException e) { + // ignore + } + } + + public Connection openConnection() throws SQLException { + return getConnection(); + } + + private Connection getConnection() throws SQLException { + return db.getDatabase().getDataSource().getConnection(); + } + + public Database database() { + return db.getDatabase(); + } + + public DatabaseCommands getCommands() { + return db.getCommands(); + } + + /** + * An {@link AutoCloseable} supplier of {@link Connection}. + */ + protected interface ConnectionSupplier extends AutoCloseable { + Connection get() throws SQLException; + + @Override + void close(); + } + + private static class PK { + @CheckForNull + private final String name; + private final List<String> columns; + + private PK(@Nullable String name, List<String> columns) { + this.name = name; + this.columns = ImmutableList.copyOf(columns); + } + + @CheckForNull + public String getName() { + return name; + } + + public List<String> getColumns() { + return columns; + } + } + + private class NewConnectionSupplier implements ConnectionSupplier { + private Connection connection; + + @Override + public Connection get() throws SQLException { + if (this.connection == null) { + this.connection = getConnection(); + } + return this.connection; + } + + @Override + public void close() { + if (this.connection != null) { + try { + this.connection.close(); + } catch (SQLException e) { + Loggers.get(CoreDbTester.class).warn("Fail to close connection", e); + // do not re-throw the exception + } + } + } + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/CoreDbTester.java b/server/sonar-db-core/src/test/java/org/sonar/db/CoreDbTester.java new file mode 100644 index 00000000000..f2ac87ac736 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/CoreDbTester.java @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +import javax.annotation.Nullable; +import org.apache.commons.lang.StringUtils; + +/** + * This class should be called using @Rule. + * Data is truncated between each tests. The schema is created between each test. + */ +public class CoreDbTester extends AbstractDbTester<CoreTestDb> { + private CoreDbTester(@Nullable String schemaPath) { + super(CoreTestDb.create(schemaPath)); + } + + public static CoreDbTester createForSchema(Class testClass, String filename) { + String path = StringUtils.replaceChars(testClass.getCanonicalName(), '.', '/'); + String schemaPath = path + "/" + filename; + return new CoreDbTester(schemaPath); + } + + public static CoreDbTester createEmpty() { + String path = StringUtils.replaceChars(CoreDbTester.class.getCanonicalName(), '.', '/'); + String schemaPath = path + "/empty.sql"; + return new CoreDbTester(schemaPath); + } + + @Override + protected void before() throws Throwable { + db.start(); + db.truncateTables(); + } + + @Override + protected void after() { + db.stop(); + } + +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/CoreTestDb.java b/server/sonar-db-core/src/test/java/org/sonar/db/CoreTestDb.java new file mode 100644 index 00000000000..bc5cd3cb6d4 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/CoreTestDb.java @@ -0,0 +1,184 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +import java.io.File; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.sql.SQLException; +import java.util.Map; +import java.util.Properties; +import javax.annotation.Nullable; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.text.StrSubstitutor; +import org.dbunit.DataSourceDatabaseTester; +import org.dbunit.IDatabaseTester; +import org.dbunit.dataset.datatype.IDataTypeFactory; +import org.junit.AssumptionViolatedException; +import org.sonar.api.config.Settings; +import org.sonar.api.config.MapSettings; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.db.dialect.H2; +import org.sonar.process.logging.LogbackHelper; + +/** + * This class should be call using @ClassRule in order to create the schema once (if @Rule is used + * the schema will be recreated before each test). + */ +class CoreTestDb { + + private static CoreTestDb DEFAULT; + + private static final Logger LOG = Loggers.get(CoreTestDb.class); + + private Database db; + private DatabaseCommands commands; + private IDatabaseTester tester; + private boolean isDefault; + + static CoreTestDb create(@Nullable String schemaPath) { + if (schemaPath == null) { + if (DEFAULT == null) { + DEFAULT = new CoreTestDb(null); + } + return DEFAULT; + } + return new CoreTestDb(schemaPath); + } + + CoreTestDb(@Nullable String schemaPath) { + if (db == null) { + Settings settings = new MapSettings().addProperties(System.getProperties()); + if (settings.hasKey("orchestrator.configUrl")) { + loadOrchestratorSettings(settings); + } + String login = settings.getString("sonar.jdbc.username"); + for (String key : settings.getKeysStartingWith("sonar.jdbc")) { + LOG.info(key + ": " + settings.getString(key)); + } + String dialect = settings.getString("sonar.jdbc.dialect"); + if (dialect != null && !"h2".equals(dialect)) { + db = new DefaultDatabase(new LogbackHelper(), settings); + } else { + db = new H2Database("h2Tests" + DigestUtils.md5Hex(StringUtils.defaultString(schemaPath)), schemaPath == null); + } + db.start(); + if (schemaPath != null) { + // will fail if not H2 + if (db.getDialect().getId().equals("h2")) { + ((H2Database) db).executeScript(schemaPath); + } else { + db.stop(); + + } + } + isDefault = (schemaPath == null); + LOG.info("Test Database: " + db); + + commands = DatabaseCommands.forDialect(db.getDialect()); + tester = new DataSourceDatabaseTester(db.getDataSource(), commands.useLoginAsSchema() ? login : null); + + extendStart(db); + } + } + + /** + * to be overridden by subclasses to extend what's done when db is created + * @param db + */ + protected void extendStart(Database db) { + // nothing done here + } + + public void start() { + if (!isDefault && !H2.ID.equals(db.getDialect().getId())) { + throw new AssumptionViolatedException("Test disabled because it supports only H2"); + } + } + + void stop() { + if (!isDefault) { + db.stop(); + } + } + + void truncateTables() { + try { + commands.truncateDatabase(db.getDataSource()); + } catch (SQLException e) { + throw new IllegalStateException("Fail to truncate db tables", e); + } + } + + Database getDatabase() { + return db; + } + + DatabaseCommands getCommands() { + return commands; + } + + IDatabaseTester getDbUnitTester() { + return tester; + } + + IDataTypeFactory getDbUnitFactory() { + return commands.getDbUnitFactory(); + } + + private void loadOrchestratorSettings(Settings settings) { + String url = settings.getString("orchestrator.configUrl"); + InputStream input = null; + try { + URI uri = new URI(url); + if (url.startsWith("file:")) { + File file = new File(uri); + input = FileUtils.openInputStream(file); + } else { + HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection(); + int responseCode = connection.getResponseCode(); + if (responseCode >= 400) { + throw new IllegalStateException("Fail to request: " + uri + ". Status code=" + responseCode); + } + + input = connection.getInputStream(); + + } + Properties props = new Properties(); + props.load(input); + settings.addProperties(props); + for (Map.Entry<String, String> entry : settings.getProperties().entrySet()) { + String interpolatedValue = StrSubstitutor.replace(entry.getValue(), System.getenv(), "${", "}"); + settings.setProperty(entry.getKey(), interpolatedValue); + } + } catch (Exception e) { + throw new IllegalStateException(e); + } finally { + IOUtils.closeQuietly(input); + } + } + + +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseCheckerTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseCheckerTest.java new file mode 100644 index 00000000000..ebe2e86e4ba --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseCheckerTest.java @@ -0,0 +1,141 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mockito; +import org.sonar.api.utils.MessageException; +import org.sonar.db.dialect.Dialect; +import org.sonar.db.dialect.H2; +import org.sonar.db.dialect.MySql; +import org.sonar.db.dialect.Oracle; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DatabaseCheckerTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void requires_oracle_driver_11_2() throws Exception { + Database db = mockDb(new Oracle(), 11, 2, "11.2.0.0.1"); + new DatabaseChecker(db).start(); + // no error + + db = mockDb(new Oracle(), 11, 2, "11.3.1"); + new DatabaseChecker(db).start(); + // no error + + db = mockDb(new Oracle(), 11, 2, "12.0.2"); + new DatabaseChecker(db).start(); + // no error + + db = mockDb(new Oracle(), 11, 2, "11.1.0.2"); + try { + new DatabaseChecker(db).start(); + fail(); + } catch (MessageException e) { + assertThat(e).hasMessage("Unsupported Oracle driver version: 11.1.0.2. Minimal supported version is 11.2."); + } + } + + @Test + public void requires_oracle_11g_or_greater() throws Exception { + // oracle 11.0 is ok + Database db = mockDb(new Oracle(), 11, 0, "11.2.0.0.1"); + new DatabaseChecker(db).start(); + + // oracle 11.1 is ok + db = mockDb(new Oracle(), 11, 1, "11.2.0.0.1"); + new DatabaseChecker(db).start(); + + // oracle 11.2 is ok + db = mockDb(new Oracle(), 11, 2, "11.2.0.0.1"); + new DatabaseChecker(db).start(); + + // oracle 12 is ok + db = mockDb(new Oracle(), 12, 0, "11.2.0.0.1"); + new DatabaseChecker(db).start(); + + // oracle 10 is not supported + db = mockDb(new Oracle(), 10, 2, "11.2.0.0.1"); + try { + new DatabaseChecker(db).start(); + fail(); + } catch (MessageException e) { + assertThat(e).hasMessage("Unsupported oracle version: 10.2. Minimal supported version is 11.0."); + } + } + + @Test + public void log_warning_if_h2() throws Exception { + Database db = mockDb(new H2(), 13, 4, "13.4"); + DatabaseChecker checker = new DatabaseChecker(db); + checker.start(); + checker.stop(); + // TODO test log + } + + @Test + public void test_mysql() throws Exception { + Database db = mockDb(new MySql(), 5, 7, "5.7"); + new DatabaseChecker(db).start(); + // no error + } + + @Test + public void fail_if_mysql_less_than_5_6() throws Exception { + expectedException.expect(MessageException.class); + expectedException.expectMessage("Unsupported mysql version: 5.5. Minimal supported version is 5.6."); + + Database db = mockDb(new MySql(), 5, 5, "5.6"); + new DatabaseChecker(db).start(); + } + + @Test + public void fail_if_cant_get_db_version() throws Exception { + SQLException sqlException = new SQLException(); + expectedException.expect(RuntimeException.class); + expectedException.expectCause(is(sqlException)); + + Database db = mock(Database.class, Mockito.RETURNS_DEEP_STUBS); + when(db.getDialect()).thenReturn(new MySql()); + when(db.getDataSource().getConnection().getMetaData()).thenThrow(sqlException); + + new DatabaseChecker(db).start(); + } + + private Database mockDb(Dialect dialect, int dbMajorVersion, int dbMinorVersion, String driverVersion) throws SQLException { + Database db = mock(Database.class, Mockito.RETURNS_DEEP_STUBS); + when(db.getDialect()).thenReturn(dialect); + when(db.getDataSource().getConnection().getMetaData().getDatabaseMajorVersion()).thenReturn(dbMajorVersion); + when(db.getDataSource().getConnection().getMetaData().getDatabaseMinorVersion()).thenReturn(dbMinorVersion); + when(db.getDataSource().getConnection().getMetaData().getDriverVersion()).thenReturn(driverVersion); + return db; + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseCommands.java b/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseCommands.java new file mode 100644 index 00000000000..db10daec84c --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseCommands.java @@ -0,0 +1,215 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Arrays; +import java.util.List; +import javax.sql.DataSource; +import org.apache.commons.dbutils.DbUtils; +import org.apache.commons.lang.StringUtils; +import org.dbunit.dataset.datatype.DefaultDataTypeFactory; +import org.dbunit.dataset.datatype.IDataTypeFactory; +import org.dbunit.dataset.datatype.ToleratedDeltaMap; +import org.dbunit.ext.h2.H2DataTypeFactory; +import org.dbunit.ext.mssql.MsSqlDataTypeFactory; +import org.dbunit.ext.mysql.MySqlDataTypeFactory; +import org.dbunit.ext.oracle.Oracle10DataTypeFactory; +import org.dbunit.ext.postgresql.PostgresqlDataTypeFactory; +import org.sonar.db.dialect.Dialect; +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 org.sonar.db.version.SqTables; + +public abstract class DatabaseCommands { + private final IDataTypeFactory dbUnitFactory; + + private DatabaseCommands(DefaultDataTypeFactory dbUnitFactory) { + this.dbUnitFactory = dbUnitFactory; + + // Hack for MsSQL failure in IssueMapperTest. + // All the Double fields should be listed here. + dbUnitFactory.addToleratedDelta(new ToleratedDeltaMap.ToleratedDelta("issues", "effort_to_fix", 0.0001)); + } + + public final IDataTypeFactory getDbUnitFactory() { + return dbUnitFactory; + } + + abstract List<String> resetSequenceSql(String table, int minSequenceValue); + + String truncateSql(String table) { + return "TRUNCATE TABLE " + table; + } + + boolean useLoginAsSchema() { + return false; + } + + public static DatabaseCommands forDialect(Dialect dialect) { + DatabaseCommands command = ImmutableMap.of( + org.sonar.db.dialect.H2.ID, H2, + MsSql.ID, MSSQL, + MySql.ID, MYSQL, + Oracle.ID, ORACLE, + PostgreSql.ID, POSTGRESQL).get(dialect.getId()); + + return Preconditions.checkNotNull(command, "Unknown database: " + dialect); + } + + private static final DatabaseCommands H2 = new DatabaseCommands(new H2DataTypeFactory()) { + @Override + List<String> resetSequenceSql(String table, int minSequenceValue) { + return Arrays.asList("ALTER TABLE " + table + " ALTER COLUMN ID RESTART WITH " + minSequenceValue); + } + }; + + private static final DatabaseCommands POSTGRESQL = new DatabaseCommands(new PostgresqlDataTypeFactory()) { + @Override + List<String> resetSequenceSql(String table, int minSequenceValue) { + return Arrays.asList("ALTER SEQUENCE " + table + "_id_seq RESTART WITH " + minSequenceValue); + } + }; + + private static final DatabaseCommands ORACLE = new DatabaseCommands(new Oracle10DataTypeFactory()) { + @Override + List<String> resetSequenceSql(String table, int minSequenceValue) { + String sequence = StringUtils.upperCase(table) + "_SEQ"; + return Arrays.asList( + "DROP SEQUENCE " + sequence, + "CREATE SEQUENCE " + sequence + " INCREMENT BY 1 MINVALUE 1 START WITH " + minSequenceValue); + } + + @Override + String truncateSql(String table) { + return "TRUNCATE TABLE " + table + " REUSE STORAGE"; + } + + @Override + boolean useLoginAsSchema() { + return true; + } + }; + + private static final DatabaseCommands MSSQL = new DatabaseCommands(new MsSqlDataTypeFactory()) { + @Override + public void resetPrimaryKeys(DataSource dataSource) { + } + + @Override + List<String> resetSequenceSql(String table, int minSequenceValue) { + return null; + } + + @Override + protected boolean shouldTruncate(Connection connection, String table) throws SQLException { + // truncate all tables on mssql, else unexpected errors in some tests + return true; + } + }; + + private static final DatabaseCommands MYSQL = new DatabaseCommands(new MySqlDataTypeFactory()) { + @Override + public void resetPrimaryKeys(DataSource dataSource) { + } + + @Override + List<String> resetSequenceSql(String table, int minSequenceValue) { + return null; + } + }; + + public void truncateDatabase(DataSource dataSource) throws SQLException { + Connection connection = dataSource.getConnection(); + Statement statement = null; + try { + connection.setAutoCommit(false); + statement = connection.createStatement(); + for (String table : SqTables.TABLES) { + try { + if (shouldTruncate(connection, table)) { + statement.executeUpdate(truncateSql(table)); + connection.commit(); + } + } catch (Exception e) { + connection.rollback(); + throw new IllegalStateException("Fail to truncate table " + table, e); + } + } + } finally { + DbUtils.closeQuietly(connection); + DbUtils.closeQuietly(statement); + } + } + + protected boolean shouldTruncate(Connection connection, String table) throws SQLException { + Statement stmt = connection.createStatement(); + ResultSet rs = null; + try { + rs = stmt.executeQuery("select count(1) from " + table); + if (rs.next()) { + return rs.getInt(1) > 0; + } + + } catch (SQLException ignored) { + // probably because table does not exist. That's the case with H2 tests. + } finally { + DbUtils.closeQuietly(rs); + DbUtils.closeQuietly(stmt); + } + return false; + } + + public void resetPrimaryKeys(DataSource dataSource) throws SQLException { + Connection connection = null; + Statement statement = null; + ResultSet resultSet = null; + try { + connection = dataSource.getConnection(); + connection.setAutoCommit(false); + + statement = connection.createStatement(); + for (String table : SqTables.TABLES) { + try { + resultSet = statement.executeQuery("SELECT CASE WHEN MAX(ID) IS NULL THEN 1 ELSE MAX(ID)+1 END FROM " + table); + resultSet.next(); + int maxId = resultSet.getInt(1); + resultSet.close(); + + for (String resetCommand : resetSequenceSql(table, maxId)) { + statement.executeUpdate(resetCommand); + } + connection.commit(); + } catch (Exception e) { + connection.rollback(); // this table has no primary key + } + } + } finally { + DbUtils.closeQuietly(connection, statement, resultSet); + } + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseUtilsTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseUtilsTest.java new file mode 100644 index 00000000000..d5855c87a2c --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/DatabaseUtilsTest.java @@ -0,0 +1,325 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +import com.google.common.base.Function; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.api.utils.log.Loggers; +import org.sonar.core.util.stream.Collectors; +import org.sonar.db.dialect.Oracle; + +import static com.google.common.collect.Lists.newArrayList; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.sonar.db.DatabaseUtils.toUniqueAndSortedList; + +public class DatabaseUtilsTest { + + @Rule + public CoreDbTester dbTester = CoreDbTester.createForSchema(DatabaseUtilsTest.class, "just_one_table.sql"); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public LogTester logTester = new LogTester(); + + @Test + public void should_close_connection() throws Exception { + Connection connection = dbTester.openConnection(); + assertThat(isClosed(connection)).isFalse(); + + DatabaseUtils.closeQuietly(connection); + assertThat(isClosed(connection)).isTrue(); + } + + @Test + public void should_support_null_connection() { + DatabaseUtils.closeQuietly((Connection) null); + // no failure + } + + @Test + public void should_close_statement_and_resultset() throws Exception { + Connection connection = dbTester.openConnection(); + try { + PreparedStatement statement = connection.prepareStatement(selectDual()); + ResultSet rs = statement.executeQuery(); + + DatabaseUtils.closeQuietly(rs); + DatabaseUtils.closeQuietly(statement); + + assertThat(isClosed(statement)).isTrue(); + assertThat(isClosed(rs)).isTrue(); + } finally { + DatabaseUtils.closeQuietly(connection); + } + } + + @Test + public void should_not_fail_on_connection_errors() throws SQLException { + Connection connection = mock(Connection.class); + doThrow(new SQLException()).when(connection).close(); + + DatabaseUtils.closeQuietly(connection); + + // no failure + verify(connection).close(); // just to be sure + } + + @Test + public void should_not_fail_on_statement_errors() throws SQLException { + Statement statement = mock(Statement.class); + doThrow(new SQLException()).when(statement).close(); + + DatabaseUtils.closeQuietly(statement); + + // no failure + verify(statement).close(); // just to be sure + } + + @Test + public void should_not_fail_on_resulset_errors() throws SQLException { + ResultSet rs = mock(ResultSet.class); + doThrow(new SQLException()).when(rs).close(); + + DatabaseUtils.closeQuietly(rs); + + // no failure + verify(rs).close(); // just to be sure + } + + @Test + public void toUniqueAndSortedList_throws_NPE_if_arg_is_null() { + expectedException.expect(NullPointerException.class); + + toUniqueAndSortedList(null); + } + + @Test + public void toUniqueAndSortedList_throws_NPE_if_arg_contains_a_null() { + expectedException.expect(NullPointerException.class); + + toUniqueAndSortedList(asList("A", null, "C")); + } + + @Test + public void toUniqueAndSortedList_throws_NPE_if_arg_is_a_set_containing_a_null() { + expectedException.expect(NullPointerException.class); + + toUniqueAndSortedList(new HashSet<Comparable>(asList("A", null, "C"))); + } + + @Test + public void toUniqueAndSortedList_enforces_natural_order() { + assertThat(toUniqueAndSortedList(asList("A", "B", "C"))).containsExactly("A", "B", "C"); + assertThat(toUniqueAndSortedList(asList("B", "A", "C"))).containsExactly("A", "B", "C"); + assertThat(toUniqueAndSortedList(asList("B", "C", "A"))).containsExactly("A", "B", "C"); + } + + @Test + public void toUniqueAndSortedList_removes_duplicates() { + assertThat(toUniqueAndSortedList(asList("A", "A", "A"))).containsExactly("A"); + assertThat(toUniqueAndSortedList(asList("A", "C", "A"))).containsExactly("A", "C"); + assertThat(toUniqueAndSortedList(asList("C", "C", "B", "B", "A", "N", "C", "A"))).containsExactly("A", "B", "C", "N"); + } + + @Test + public void toUniqueAndSortedList_removes_duplicates_and_apply_natural_order_of_any_Comparable() { + assertThat( + toUniqueAndSortedList(asList(myComparable(2), myComparable(5), myComparable(2), myComparable(4), myComparable(-1), myComparable(10)))) + .containsExactly( + myComparable(-1), myComparable(2), myComparable(4), myComparable(5), myComparable(10)); + } + + private static DatabaseUtilsTest.MyComparable myComparable(int ordinal) { + return new DatabaseUtilsTest.MyComparable(ordinal); + } + + private static final class MyComparable implements Comparable<MyComparable> { + private final int ordinal; + + private MyComparable(int ordinal) { + this.ordinal = ordinal; + } + + @Override + public int compareTo(MyComparable o) { + return ordinal - o.ordinal; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MyComparable that = (MyComparable) o; + return ordinal == that.ordinal; + } + + @Override + public int hashCode() { + return Objects.hash(ordinal); + } + } + + /** + * Connection.isClosed() has been introduced in java 1.6 + */ + private boolean isClosed(Connection c) { + try { + c.createStatement().execute(selectDual()); + return false; + } catch (Exception e) { + return true; + } + } + + /** + * Statement.isClosed() has been introduced in java 1.6 + */ + private boolean isClosed(Statement s) { + try { + s.execute("SELECT 1"); + return false; + } catch (Exception e) { + return true; + } + } + + /** + * ResultSet.isClosed() has been introduced in java 1.6 + */ + private boolean isClosed(ResultSet rs) { + try { + rs.next(); + return false; + } catch (Exception e) { + return true; + } + } + + private String selectDual() { + String sql = "SELECT 1"; + if (Oracle.ID.equals(dbTester.database().getDialect().getId())) { + sql = "SELECT 1 FROM DUAL"; + } + return sql; + } + + @Test + public void repeatCondition() { + assertThat(DatabaseUtils.repeatCondition("uuid=?", 1, "or")).isEqualTo("uuid=?"); + assertThat(DatabaseUtils.repeatCondition("uuid=?", 3, "or")).isEqualTo("uuid=? or uuid=? or uuid=?"); + } + + @Test + public void executeLargeInputs() { + List<Integer> inputs = newArrayList(); + List<String> expectedOutputs = newArrayList(); + for (int i = 0; i < 2010; i++) { + inputs.add(i); + expectedOutputs.add(Integer.toString(i)); + } + + List<String> outputs = DatabaseUtils.executeLargeInputs(inputs, input -> { + // Check that each partition is only done on 1000 elements max + assertThat(input.size()).isLessThanOrEqualTo(1000); + return input.stream().map(String::valueOf).collect(Collectors.toList()); + }); + + assertThat(outputs).isEqualTo(expectedOutputs); + } + + @Test + public void executeLargeInputs_on_empty_list() { + List<String> outputs = DatabaseUtils.executeLargeInputs(Collections.emptyList(), new Function<List<Integer>, List<String>>() { + @Override + public List<String> apply(List<Integer> input) { + fail("No partition should be made on empty list"); + return Collections.emptyList(); + } + }); + + assertThat(outputs).isEmpty(); + } + + @Test + public void executeLargeUpdates() { + List<Integer> inputs = newArrayList(); + for (int i = 0; i < 2010; i++) { + inputs.add(i); + } + + List<Integer> processed = newArrayList(); + DatabaseUtils.executeLargeUpdates(inputs, input -> { + assertThat(input.size()).isLessThanOrEqualTo(1000); + processed.addAll(input); + }); + assertThat(processed).containsExactlyElementsOf(inputs); + } + + @Test + public void executeLargeUpdates_on_empty_list() { + DatabaseUtils.executeLargeUpdates(Collections.<Integer>emptyList(), input -> { + fail("No partition should be made on empty list"); + }); + } + + @Test + public void log_all_sql_exceptions() { + SQLException root = new SQLException("this is root", "123"); + SQLException next = new SQLException("this is next", "456"); + root.setNextException(next); + + DatabaseUtils.log(Loggers.get(getClass()), root); + + assertThat(logTester.logs(LoggerLevel.ERROR)).contains("SQL error: 456. Message: this is next"); + } + + @Test + public void tableExists_returns_true_if_table_is_referenced_in_db_metadata() throws Exception { + try (Connection connection = dbTester.openConnection()) { + assertThat(DatabaseUtils.tableExists("SCHEMA_MIGRATIONS", connection)).isTrue(); + assertThat(DatabaseUtils.tableExists("schema_migrations", connection)).isTrue(); + assertThat(DatabaseUtils.tableExists("schema_MIGRATIONS", connection)).isTrue(); + assertThat(DatabaseUtils.tableExists("foo", connection)).isFalse(); + } + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/DdlUtilsTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/DdlUtilsTest.java new file mode 100644 index 00000000000..68f64dbf321 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/DdlUtilsTest.java @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.assertj.core.api.Assertions; +import org.h2.Driver; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DdlUtilsTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void shouldSupportOnlyH2() { + Assertions.assertThat(DdlUtils.supportsDialect("h2")).isTrue(); + assertThat(DdlUtils.supportsDialect("mysql")).isFalse(); + assertThat(DdlUtils.supportsDialect("oracle")).isFalse(); + assertThat(DdlUtils.supportsDialect("mssql")).isFalse(); + } + + @Test + public void shouldCreateSchema_with_schema_migrations() throws SQLException { + DriverManager.registerDriver(new Driver()); + try (Connection connection = DriverManager.getConnection("jdbc:h2:mem:sonar_test");) { + DdlUtils.createSchema(connection, "h2", true); + + int tableCount = countTables(connection); + assertThat(tableCount).isGreaterThan(30); + + verifySchemaMigrations(connection); + } + } + + @Test + public void shouldCreateSchema_without_schema_migrations() throws SQLException { + DriverManager.registerDriver(new Driver()); + try (Connection connection = DriverManager.getConnection("jdbc:h2:mem:sonar_test2")) { + try (Statement statement = connection.createStatement()) { + statement.execute("create table schema_migrations (version varchar(255) not null)"); + } + DdlUtils.createSchema(connection, "h2", false); + + verifySchemaMigrations(connection); + } + } + + static int countTables(Connection connection) throws SQLException { + int count = 0; + ResultSet resultSet = connection.getMetaData().getTables("", null, null, new String[] {"TABLE"}); + while (resultSet.next()) { + count++; + } + resultSet.close(); + return count; + } + + private void verifySchemaMigrations(Connection connection) throws SQLException { + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("select count(*) from schema_migrations")) { + assertThat(resultSet.next()).isTrue(); + assertThat(resultSet.getLong(1)).isGreaterThan(150); + assertThat(resultSet.next()).isFalse(); + } + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/DefaultDatabaseTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/DefaultDatabaseTest.java new file mode 100644 index 00000000000..1ffc411cb44 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/DefaultDatabaseTest.java @@ -0,0 +1,116 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +import java.util.Properties; +import org.apache.commons.dbcp.BasicDataSource; +import org.junit.Test; +import org.sonar.api.config.Settings; +import org.sonar.api.config.MapSettings; +import org.sonar.db.dialect.PostgreSql; +import org.sonar.process.logging.LogbackHelper; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class DefaultDatabaseTest { + private LogbackHelper logbackHelper = mock(LogbackHelper.class); + + @Test + public void shouldLoadDefaultValues() { + DefaultDatabase db = new DefaultDatabase(logbackHelper, new MapSettings()); + db.initSettings(); + + Properties props = db.getProperties(); + assertThat(props.getProperty("sonar.jdbc.url")).isEqualTo("jdbc:h2:tcp://localhost/sonar"); + assertThat(props.getProperty("sonar.jdbc.driverClassName")).isEqualTo("org.h2.Driver"); + assertThat(db.toString()).isEqualTo("Database[jdbc:h2:tcp://localhost/sonar]"); + } + + @Test + public void shouldExtractCommonsDbcpProperties() { + Properties props = new Properties(); + props.setProperty("sonar.jdbc.driverClassName", "my.Driver"); + props.setProperty("sonar.jdbc.username", "me"); + props.setProperty("sonar.jdbc.maxActive", "5"); + + Properties commonsDbcpProps = DefaultDatabase.extractCommonsDbcpProperties(props); + + assertThat(commonsDbcpProps.getProperty("username")).isEqualTo("me"); + assertThat(commonsDbcpProps.getProperty("driverClassName")).isEqualTo("my.Driver"); + assertThat(commonsDbcpProps.getProperty("maxActive")).isEqualTo("5"); + } + + @Test + public void shouldCompleteProperties() { + Settings settings = new MapSettings(); + + DefaultDatabase db = new DefaultDatabase(logbackHelper, settings) { + @Override + protected void doCompleteProperties(Properties properties) { + properties.setProperty("sonar.jdbc.maxActive", "2"); + } + }; + db.initSettings(); + + Properties props = db.getProperties(); + + assertThat(props.getProperty("sonar.jdbc.maxActive")).isEqualTo("2"); + } + + @Test + public void shouldStart() { + Settings settings = new MapSettings(); + settings.setProperty("sonar.jdbc.url", "jdbc:h2:mem:sonar"); + settings.setProperty("sonar.jdbc.driverClassName", "org.h2.Driver"); + settings.setProperty("sonar.jdbc.username", "sonar"); + settings.setProperty("sonar.jdbc.password", "sonar"); + settings.setProperty("sonar.jdbc.maxActive", "1"); + + DefaultDatabase db = new DefaultDatabase(logbackHelper, settings); + db.start(); + db.stop(); + + assertThat(db.getDialect().getId()).isEqualTo("h2"); + assertThat(((BasicDataSource) db.getDataSource()).getMaxActive()).isEqualTo(1); + } + + @Test + public void shouldGuessDialectFromUrl() { + Settings settings = new MapSettings(); + settings.setProperty("sonar.jdbc.url", "jdbc:postgresql://localhost/sonar"); + + DefaultDatabase database = new DefaultDatabase(logbackHelper, settings); + database.initSettings(); + + assertThat(database.getDialect().getId()).isEqualTo(PostgreSql.ID); + } + + @Test + public void shouldGuessDefaultDriver() { + Settings settings = new MapSettings(); + settings.setProperty("sonar.jdbc.url", "jdbc:postgresql://localhost/sonar"); + + DefaultDatabase database = new DefaultDatabase(logbackHelper, settings); + database.initSettings(); + + assertThat(database.getProperties().getProperty("sonar.jdbc.driverClassName")).isEqualTo("org.postgresql.Driver"); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/H2Database.java b/server/sonar-db-core/src/test/java/org/sonar/db/H2Database.java new file mode 100644 index 00000000000..a6854973bd7 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/H2Database.java @@ -0,0 +1,122 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +import java.sql.Connection; +import java.sql.SQLException; +import javax.sql.DataSource; +import org.apache.commons.dbcp.BasicDataSource; +import org.apache.commons.dbutils.DbUtils; +import org.sonar.db.dialect.Dialect; +import org.sonar.db.dialect.H2; + +import static java.lang.String.format; + +/** + * H2 in-memory database, used for unit tests only. + * + * @since 3.2 + */ +public class H2Database implements Database { + private final String name; + private final boolean createSchema; + private BasicDataSource datasource; + + /** + * IMPORTANT: change DB name in order to not conflict with {@link DefaultDatabaseTest} + */ + public H2Database(String name, boolean createSchema) { + this.name = name; + this.createSchema = createSchema; + } + + @Override + public void start() { + startDatabase(); + if (createSchema) { + createSchema(); + } + } + + private void startDatabase() { + try { + datasource = new BasicDataSource(); + datasource.setDriverClassName("org.h2.Driver"); + datasource.setUsername("sonar"); + datasource.setPassword("sonar"); + datasource.setUrl("jdbc:h2:mem:" + name); + } catch (Exception e) { + throw new IllegalStateException("Fail to start H2", e); + } + } + + private void createSchema() { + Connection connection = null; + try { + connection = datasource.getConnection(); + DdlUtils.createSchema(connection, "h2", true); + + } catch (SQLException e) { + throw new IllegalStateException("Fail to create schema", e); + } finally { + DbUtils.closeQuietly(connection); + } + } + + public void executeScript(String classloaderPath) { + Connection connection = null; + try { + connection = datasource.getConnection(); + DdlUtils.executeScript(connection, classloaderPath); + + } catch (SQLException e) { + throw new IllegalStateException("Fail to execute script: " + classloaderPath, e); + } finally { + DbUtils.closeQuietly(connection); + } + } + + @Override + public void stop() { + try { + datasource.close(); + } catch (SQLException e) { + // Ignore error + } + } + + public DataSource getDataSource() { + return datasource; + } + + public Dialect getDialect() { + return new H2(); + } + + @Override + public void enableSqlLogging(boolean enable) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return format("H2 Database[%s]", name); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/H2DatabaseTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/H2DatabaseTest.java new file mode 100644 index 00000000000..c4cb43011d6 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/H2DatabaseTest.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +import java.sql.Connection; +import java.sql.SQLException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class H2DatabaseTest { + H2Database db = new H2Database("sonar2", true); + + @Before + public void startDb() { + db.start(); + } + + @After + public void stopDb() { + db.stop(); + } + + @Test + public void shouldExecuteDdlAtStartup() throws SQLException { + Connection connection = db.getDataSource().getConnection(); + int tableCount = DdlUtilsTest.countTables(connection); + connection.close(); + + assertThat(tableCount).isGreaterThan(30); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/ResultSetIteratorTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/ResultSetIteratorTest.java new file mode 100644 index 00000000000..19a9e7089ae --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/ResultSetIteratorTest.java @@ -0,0 +1,169 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.NoSuchElementException; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + + +public class ResultSetIteratorTest { + + @Rule + public CoreDbTester dbTester = CoreDbTester.createForSchema(ResultSetIteratorTest.class, "schema.sql"); + + @Test + public void create_iterator_from_statement() throws Exception { + dbTester.prepareDbUnit(getClass(), "feed.xml"); + + try (Connection connection = dbTester.openConnection()) { + PreparedStatement stmt = connection.prepareStatement("select * from issues order by id"); + FirstIntColumnIterator iterator = new FirstIntColumnIterator(stmt); + + assertThat(iterator.hasNext()).isTrue(); + + // calling multiple times hasNext() is ok + assertThat(iterator.hasNext()).isTrue(); + + assertThat(iterator.next()).isEqualTo(10); + assertThat(iterator.hasNext()).isTrue(); + assertThat(iterator.next()).isEqualTo(20); + + // call next() without calling hasNext() + assertThat(iterator.next()).isEqualTo(30); + assertThat(iterator.hasNext()).isFalse(); + + try { + iterator.next(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + + iterator.close(); + // statement is closed by ResultSetIterator + assertThat(stmt.isClosed()).isTrue(); + } + } + + @Test + public void iterate_empty_list() throws Exception { + dbTester.prepareDbUnit(getClass(), "feed.xml"); + + try (Connection connection = dbTester.openConnection()) { + PreparedStatement stmt = connection.prepareStatement("select * from issues where id < 0"); + FirstIntColumnIterator iterator = new FirstIntColumnIterator(stmt); + + assertThat(iterator.hasNext()).isFalse(); + } + } + + @Test + public void create_iterator_from_result_set() throws Exception { + dbTester.prepareDbUnit(getClass(), "feed.xml"); + + try (Connection connection = dbTester.openConnection()) { + PreparedStatement stmt = connection.prepareStatement("select * from issues order by id"); + ResultSet rs = stmt.executeQuery(); + FirstIntColumnIterator iterator = new FirstIntColumnIterator(rs); + + assertThat(iterator.next()).isEqualTo(10); + assertThat(iterator.next()).isEqualTo(20); + assertThat(iterator.next()).isEqualTo(30); + + iterator.close(); + assertThat(rs.isClosed()).isTrue(); + stmt.close(); + } + } + + @Test + public void remove_row_is_not_supported() throws Exception { + try (Connection connection = dbTester.openConnection()) { + PreparedStatement stmt = connection.prepareStatement("select * from issues order by id"); + FirstIntColumnIterator iterator = new FirstIntColumnIterator(stmt); + + try { + iterator.remove(); + fail(); + } catch (UnsupportedOperationException ok) { + // ok + } + + iterator.close(); + } + } + + @Test + public void fail_to_read_row() throws Exception { + dbTester.prepareDbUnit(getClass(), "feed.xml"); + + try (Connection connection = dbTester.openConnection()) { + PreparedStatement stmt = connection.prepareStatement("select * from issues order by id"); + FailIterator iterator = new FailIterator(stmt); + + assertThat(iterator.hasNext()).isTrue(); + try { + iterator.next(); + fail(); + } catch (IllegalStateException e) { + assertThat(e.getCause()).isInstanceOf(SQLException.class); + } + iterator.close(); + } + } + + private static class FirstIntColumnIterator extends ResultSetIterator<Integer> { + + public FirstIntColumnIterator(PreparedStatement stmt) throws SQLException { + super(stmt); + } + + public FirstIntColumnIterator(ResultSet rs) { + super(rs); + } + + @Override + protected Integer read(ResultSet rs) throws SQLException { + return rs.getInt(1); + } + } + + private static class FailIterator extends ResultSetIterator<Integer> { + + public FailIterator(PreparedStatement stmt) throws SQLException { + super(stmt); + } + + @Override + protected Integer read(ResultSet rs) throws SQLException { + // column does not exist + return rs.getInt(1234); + } + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/dialect/DialectUtilsTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/DialectUtilsTest.java new file mode 100644 index 00000000000..57aaa9132ce --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/DialectUtilsTest.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.dialect; + +import org.junit.Test; +import org.sonar.api.utils.MessageException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DialectUtilsTest { + + @Test + public void testFindById() { + Dialect d = DialectUtils.find("mysql", null); + assertThat(d).isInstanceOf(MySql.class); + } + + @Test + public void testFindByJdbcUrl() { + Dialect d = DialectUtils.find(null, "jdbc:mysql:foo:bar"); + assertThat(d).isInstanceOf(MySql.class); + } + + @Test(expected = MessageException.class) + public void testFindNoMatch() { + DialectUtils.find("foo", "bar"); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/dialect/H2Test.java b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/H2Test.java new file mode 100644 index 00000000000..93249a6d59c --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/H2Test.java @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.dialect; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class H2Test { + + H2 dialect = new H2(); + + @Test + public void matchesJdbcURL() { + assertThat(dialect.matchesJdbcURL("jdbc:h2:foo")).isTrue(); + assertThat(dialect.matchesJdbcURL("jdbc:hsql:foo")).isFalse(); + } + + @Test + public void testBooleanSqlValues() { + assertThat(dialect.getTrueSqlValue()).isEqualTo("true"); + assertThat(dialect.getFalseSqlValue()).isEqualTo("false"); + } + + @Test + public void should_configure() { + assertThat(dialect.getId()).isEqualTo("h2"); + assertThat(dialect.getActiveRecordDialectCode()).isEqualTo(".h2."); + assertThat(dialect.getDefaultDriverClassName()).isEqualTo("org.h2.Driver"); + assertThat(dialect.getValidationQuery()).isEqualTo("SELECT 1"); + } + + @Test + public void testFetchSizeForScrolling() throws Exception { + assertThat(dialect.getScrollDefaultFetchSize()).isEqualTo(200); + } + + @Test + public void h2_does_not_supportMigration() { + assertThat(dialect.supportsMigration()).isFalse(); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/dialect/MsSqlTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/MsSqlTest.java new file mode 100644 index 00000000000..0b03fa6bc0f --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/MsSqlTest.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.dialect; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MsSqlTest { + + private MsSql msSql = new MsSql(); + + @Test + public void matchesJdbcURL() { + assertThat(msSql.matchesJdbcURL("jdbc:sqlserver://localhost:1433;databasename=sonar")).isTrue(); + + assertThat(msSql.matchesJdbcURL("jdbc:hsql:foo")).isFalse(); + assertThat(msSql.matchesJdbcURL("jdbc:mysql:foo")).isFalse(); + } + + @Test + public void testBooleanSqlValues() { + assertThat(msSql.getTrueSqlValue()).isEqualTo("1"); + assertThat(msSql.getFalseSqlValue()).isEqualTo("0"); + } + + @Test + public void should_configure() { + assertThat(msSql.getId()).isEqualTo("mssql"); + assertThat(msSql.getActiveRecordDialectCode()).isEqualTo("sqlserver"); + assertThat(msSql.getDefaultDriverClassName()).isEqualTo("com.microsoft.sqlserver.jdbc.SQLServerDriver"); + assertThat(msSql.getValidationQuery()).isEqualTo("SELECT 1"); + } + + @Test + public void do_not_support_jtds_since_5_2() throws Exception { + assertThat(msSql.matchesJdbcURL("jdbc:jtds:sqlserver://localhost;databaseName=SONAR;SelectMethod=Cursor")).isFalse(); + + } + + @Test + public void msSql_does_supportMigration() { + assertThat(msSql.supportsMigration()).isTrue(); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/dialect/MySqlTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/MySqlTest.java new file mode 100644 index 00000000000..e191124a9ed --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/MySqlTest.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.dialect; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MySqlTest { + + private MySql mySql = new MySql(); + + @Test + public void matchesJdbcURL() { + assertThat(mySql.matchesJdbcURL("jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8")).isTrue(); + assertThat(mySql.matchesJdbcURL("JDBC:MYSQL://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8")).isTrue(); + + assertThat(mySql.matchesJdbcURL("jdbc:hsql:foo")).isFalse(); + assertThat(mySql.matchesJdbcURL("jdbc:oracle:foo")).isFalse(); + } + + @Test + public void testBooleanSqlValues() { + assertThat(mySql.getTrueSqlValue()).isEqualTo("true"); + assertThat(mySql.getFalseSqlValue()).isEqualTo("false"); + } + + @Test + public void should_configure() { + assertThat(mySql.getId()).isEqualTo("mysql"); + assertThat(mySql.getActiveRecordDialectCode()).isEqualTo("mysql"); + assertThat(mySql.getDefaultDriverClassName()).isEqualTo("com.mysql.jdbc.Driver"); + assertThat(mySql.getValidationQuery()).isEqualTo("SELECT 1"); + } + + @Test + public void testFetchSizeForScrolling() throws Exception { + assertThat(mySql.getScrollDefaultFetchSize()).isEqualTo(Integer.MIN_VALUE); + assertThat(mySql.getScrollSingleRowFetchSize()).isEqualTo(Integer.MIN_VALUE); + } + + @Test + public void mysql_does_supportMigration() { + assertThat(mySql.supportsMigration()).isTrue(); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/dialect/OracleTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/OracleTest.java new file mode 100644 index 00000000000..6b3698a6e2e --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/OracleTest.java @@ -0,0 +1,60 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.dialect; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OracleTest { + + private Oracle underTest = new Oracle(); + + @Test + public void matchesJdbcURL() { + assertThat(underTest.matchesJdbcURL("jdbc:oracle:thin:@localhost/XE")).isTrue(); + assertThat(underTest.matchesJdbcURL("jdbc:hsql:foo")).isFalse(); + } + + @Test + public void testBooleanSqlValues() { + assertThat(underTest.getTrueSqlValue()).isEqualTo("1"); + assertThat(underTest.getFalseSqlValue()).isEqualTo("0"); + } + + @Test + public void should_configure() { + assertThat(underTest.getId()).isEqualTo("oracle"); + assertThat(underTest.getActiveRecordDialectCode()).isEqualTo("oracle"); + assertThat(underTest.getDefaultDriverClassName()).isEqualTo("oracle.jdbc.OracleDriver"); + assertThat(underTest.getValidationQuery()).isEqualTo("SELECT 1 FROM DUAL"); + } + + @Test + public void testFetchSizeForScrolling() throws Exception { + assertThat(underTest.getScrollDefaultFetchSize()).isEqualTo(200); + assertThat(underTest.getScrollSingleRowFetchSize()).isEqualTo(1); + } + + @Test + public void oracle_does_supportMigration() { + assertThat(underTest.supportsMigration()).isTrue(); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/dialect/PostgreSqlTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/PostgreSqlTest.java new file mode 100644 index 00000000000..c42c50d7db5 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/dialect/PostgreSqlTest.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.dialect; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PostgreSqlTest { + + PostgreSql dialect = new PostgreSql(); + + @Test + public void matchesJdbcURL() { + assertThat(dialect.matchesJdbcURL("jdbc:postgresql://localhost/sonar")).isTrue(); + assertThat(dialect.matchesJdbcURL("jdbc:hsql:foo")).isFalse(); + } + + @Test + public void should_set_connection_properties() { + assertThat(dialect.getConnectionInitStatements()).isEqualTo(PostgreSql.INIT_STATEMENTS); + } + + @Test + public void testBooleanSqlValues() { + assertThat(dialect.getTrueSqlValue()).isEqualTo("true"); + assertThat(dialect.getFalseSqlValue()).isEqualTo("false"); + } + + @Test + public void should_configure() { + assertThat(dialect.getId()).isEqualTo("postgresql"); + assertThat(dialect.getActiveRecordDialectCode()).isEqualTo("postgre"); + assertThat(dialect.getDefaultDriverClassName()).isEqualTo("org.postgresql.Driver"); + assertThat(dialect.getValidationQuery()).isEqualTo("SELECT 1"); + } + + @Test + public void testFetchSizeForScrolling() throws Exception { + assertThat(dialect.getScrollDefaultFetchSize()).isEqualTo(200); + } + + @Test + public void postgres_does_supportMigration() { + assertThat(dialect.supportsMigration()).isTrue(); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/profiling/InvocationUtilsTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/profiling/InvocationUtilsTest.java new file mode 100644 index 00000000000..708c24f221a --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/profiling/InvocationUtilsTest.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.profiling; + +import java.sql.Connection; +import java.sql.SQLException; +import org.junit.Test; +import org.sonar.test.TestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class InvocationUtilsTest { + + @Test + public void should_return_result() throws Throwable { + String toString = "toString"; + Object target = mock(Object.class); + when(target.toString()).thenReturn(toString); + + assertThat(InvocationUtils.invokeQuietly(target, Object.class.getMethod("toString"), new Object[0])).isEqualTo(toString); + } + + @Test + public void should_throw_declared_exception() throws Throwable { + Connection target = mock(Connection.class); + String failSql = "any sql"; + when(target.prepareStatement(failSql)).thenThrow(new SQLException("Expected")); + + try { + InvocationUtils.invokeQuietly(target, Connection.class.getMethod("prepareStatement", String.class), new Object[] {failSql}); + fail(); + } catch (Throwable t) { + assertThat(t).isInstanceOf(SQLException.class); + } + } + + @Test + public void should_wrap_undeclared_exception() throws Throwable { + Connection target = mock(Connection.class); + String failSql = "any sql"; + when(target.prepareStatement(failSql)).thenThrow(new SQLException("Expected")); + + try { + InvocationUtils.invokeQuietly(target, Object.class.getMethod("wait"), new Object[0]); + fail(); + } catch (Throwable t) { + assertThat(t).isInstanceOf(IllegalStateException.class); + } + } + + @Test + public void only_static_methods() { + assertThat(TestUtils.hasOnlyPrivateConstructors(InvocationUtils.class)).isTrue(); + + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/profiling/ProfiledDataSourceTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/profiling/ProfiledDataSourceTest.java new file mode 100644 index 00000000000..3f0a29e0dea --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/profiling/ProfiledDataSourceTest.java @@ -0,0 +1,149 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.profiling; + +import java.io.ByteArrayInputStream; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.sql.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.Statement; +import java.sql.Timestamp; +import org.apache.commons.dbcp.BasicDataSource; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ProfiledDataSourceTest { + + @Rule + public LogTester logTester = new LogTester(); + + BasicDataSource originDataSource = mock(BasicDataSource.class); + + @Test + public void execute_and_log_statement() throws Exception { + logTester.setLevel(LoggerLevel.TRACE); + + Connection connection = mock(Connection.class); + when(originDataSource.getConnection()).thenReturn(connection); + + String sql = "select from dual"; + Statement stmt = mock(Statement.class); + when(connection.createStatement()).thenReturn(stmt); + when(stmt.execute(sql)).thenReturn(true); + + ProfiledDataSource underTest = new ProfiledDataSource(originDataSource, ProfiledConnectionInterceptor.INSTANCE); + + assertThat(underTest.getUrl()).isNull(); + assertThat(underTest.getConnection().getClientInfo()).isNull(); + final Statement statementProxy = underTest.getConnection().createStatement(); + assertThat(statementProxy.getConnection()).isNull(); + assertThat(statementProxy.execute(sql)).isTrue(); + + assertThat(logTester.logs(LoggerLevel.TRACE)).hasSize(1); + assertThat(logTester.logs(LoggerLevel.TRACE).get(0)) + .contains("sql=select from dual"); + } + + @Test + public void execute_and_log_prepared_statement_with_parameters() throws Exception { + logTester.setLevel(LoggerLevel.TRACE); + + Connection connection = mock(Connection.class); + when(originDataSource.getConnection()).thenReturn(connection); + + String sqlWithParams = "insert into polop (col1, col2, col3, col4) values (?, ?, ?, ?, ?)"; + int param1 = 42; + String param2 = "plouf"; + Date param3 = new Date(System.currentTimeMillis()); + Timestamp param4 = new Timestamp(System.currentTimeMillis()); + byte[] param5 = "blob".getBytes("UTF-8"); + + PreparedStatement preparedStatement = mock(PreparedStatement.class); + when(connection.prepareStatement(sqlWithParams)).thenReturn(preparedStatement); + when(preparedStatement.execute()).thenReturn(true); + + ProfiledDataSource ds = new ProfiledDataSource(originDataSource, ProfiledConnectionInterceptor.INSTANCE); + + assertThat(ds.getUrl()).isNull(); + assertThat(ds.getConnection().getClientInfo()).isNull(); + PreparedStatement preparedStatementProxy = ds.getConnection().prepareStatement(sqlWithParams); + preparedStatementProxy.setInt(1, param1); + preparedStatementProxy.setString(2, param2); + preparedStatementProxy.setDate(3, param3); + preparedStatementProxy.setTimestamp(4, param4); + preparedStatementProxy.setBlob(5, new ByteArrayInputStream(param5)); + assertThat(preparedStatementProxy.getConnection()).isNull(); + assertThat(preparedStatementProxy.execute()).isTrue(); + + assertThat(logTester.logs(LoggerLevel.TRACE)).hasSize(1); + assertThat(logTester.logs(LoggerLevel.TRACE).get(0)) + .contains("sql=insert into polop (col1, col2, col3, col4) values (?, ?, ?, ?, ?)") + .contains("params=42, plouf"); + } + + @Test + public void execute_and_log_prepared_statement_without_parameters() throws Exception { + logTester.setLevel(LoggerLevel.TRACE); + + Connection connection = mock(Connection.class); + when(originDataSource.getConnection()).thenReturn(connection); + + String sqlWithParams = "select from dual"; + PreparedStatement preparedStatement = mock(PreparedStatement.class); + when(connection.prepareStatement(sqlWithParams)).thenReturn(preparedStatement); + when(preparedStatement.execute()).thenReturn(true); + + ProfiledDataSource ds = new ProfiledDataSource(originDataSource, ProfiledConnectionInterceptor.INSTANCE); + + assertThat(ds.getUrl()).isNull(); + assertThat(ds.getConnection().getClientInfo()).isNull(); + PreparedStatement preparedStatementProxy = ds.getConnection().prepareStatement(sqlWithParams); + assertThat(preparedStatementProxy.getConnection()).isNull(); + assertThat(preparedStatementProxy.execute()).isTrue(); + + assertThat(logTester.logs(LoggerLevel.TRACE)).hasSize(1); + assertThat(logTester.logs(LoggerLevel.TRACE).get(0)) + .contains("sql=select from dual") + .doesNotContain("params="); + } + + @Test + public void delegate_to_underlying_data_source() throws Exception { + ProfiledDataSource proxy = new ProfiledDataSource(originDataSource, ProfiledConnectionInterceptor.INSTANCE); + + // painful to call all methods + // so using reflection to check that calls does not fail + // Limitation: methods with parameters are not tested and calls to + // underlying datasource are not verified + for (Method method : ProfiledDataSource.class.getDeclaredMethods()) { + if (method.getParameterTypes().length == 0 && Modifier.isPublic(method.getModifiers())) { + method.invoke(proxy); + } + } + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/profiling/SqlLogFormatterTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/profiling/SqlLogFormatterTest.java new file mode 100644 index 00000000000..c3f6f528c01 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/profiling/SqlLogFormatterTest.java @@ -0,0 +1,79 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.profiling; + +import org.junit.Test; + +import static org.apache.commons.lang.StringUtils.repeat; +import static org.assertj.core.api.Assertions.assertThat; + +public class SqlLogFormatterTest { + + @Test + public void formatSql() { + assertThat(SqlLogFormatter.formatSql("select *")).isEqualTo("select *"); + } + + @Test + public void formatSql_removes_newlines() { + assertThat(SqlLogFormatter.formatSql("select *\nfrom issues")).isEqualTo("select * from issues"); + } + + @Test + public void formatParam() { + assertThat(SqlLogFormatter.formatParam(null)).isEqualTo("[null]"); + assertThat(SqlLogFormatter.formatParam("")).isEqualTo(""); + assertThat(SqlLogFormatter.formatParam("foo")).isEqualTo("foo"); + } + + @Test + public void formatParam_escapes_newlines() { + assertThat(SqlLogFormatter.formatParam("foo\nbar\nbaz")).isEqualTo("foo\\nbar\\nbaz"); + } + + @Test + public void formatParam_truncates_if_too_long() { + String param = repeat("a", SqlLogFormatter.PARAM_MAX_WIDTH + 10); + String formattedParam = SqlLogFormatter.formatParam(param); + assertThat(formattedParam) + .hasSize(SqlLogFormatter.PARAM_MAX_WIDTH) + .endsWith("...") + .startsWith(repeat("a", SqlLogFormatter.PARAM_MAX_WIDTH - 3)); + } + + @Test + public void formatParams() { + String formattedParams = SqlLogFormatter.formatParams(new Object[] {"foo", 42, null, true}); + assertThat(formattedParams).isEqualTo("foo, 42, [null], true"); + } + + @Test + public void formatParams_returns_blank_if_zero_params() { + String formattedParams = SqlLogFormatter.formatParams(new Object[0]); + assertThat(formattedParams).isEqualTo(""); + } + + @Test + public void countArguments() { + assertThat(SqlLogFormatter.countArguments("select * from issues")).isEqualTo(0); + assertThat(SqlLogFormatter.countArguments("select * from issues where id=?")).isEqualTo(1); + assertThat(SqlLogFormatter.countArguments("select * from issues where id=? and kee=?")).isEqualTo(2); + } +} diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/semaphore/SemaphoresImplTest.java b/server/sonar-db-core/src/test/java/org/sonar/db/semaphore/SemaphoresImplTest.java new file mode 100644 index 00000000000..e83b1c608a7 --- /dev/null +++ b/server/sonar-db-core/src/test/java/org/sonar/db/semaphore/SemaphoresImplTest.java @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info 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.semaphore; + +import org.junit.Test; + +public class SemaphoresImplTest { + + @Test(expected = UnsupportedOperationException.class) + public void acquire_is_unsupported() { + new SemaphoresImpl().acquire("foo"); + } + + @Test(expected = UnsupportedOperationException.class) + public void acquire_with_timeout_is_unsupported() { + new SemaphoresImpl().acquire("foo", 1, 2); + } + + @Test(expected = UnsupportedOperationException.class) + public void release_is_unsupported() { + new SemaphoresImpl().release("foo"); + } +} diff --git a/server/sonar-db-core/src/test/resources/logback-test.xml b/server/sonar-db-core/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..4b0ff6da04b --- /dev/null +++ b/server/sonar-db-core/src/test/resources/logback-test.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<configuration> + + <appender name="STDOUT" + class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern> + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + </pattern> + </encoder> + </appender> + + <logger name="org.dbunit"> + <level value="WARN"/> + </logger> + + <!-- set to level DEBUG to log SQL requests executed by MyBatis --> + <logger name="java.sql"> + <level value="WARN"/> + </logger> + + <!-- required for DryRunDatabaseFactoryTest --> + <logger name="org.elasticsearch"> + <level value="WARN"/> + </logger> + + <root> + <level value="INFO"/> + <appender-ref ref="STDOUT"/> + </root> + +</configuration> diff --git a/server/sonar-db-core/src/test/resources/org/sonar/db/CoreDbTester/empty.sql b/server/sonar-db-core/src/test/resources/org/sonar/db/CoreDbTester/empty.sql new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/server/sonar-db-core/src/test/resources/org/sonar/db/CoreDbTester/empty.sql diff --git a/server/sonar-db-core/src/test/resources/org/sonar/db/DatabaseUtilsTest/just_one_table.sql b/server/sonar-db-core/src/test/resources/org/sonar/db/DatabaseUtilsTest/just_one_table.sql new file mode 100644 index 00000000000..a1019056ef5 --- /dev/null +++ b/server/sonar-db-core/src/test/resources/org/sonar/db/DatabaseUtilsTest/just_one_table.sql @@ -0,0 +1,3 @@ +CREATE TABLE "SCHEMA_MIGRATIONS" ( + "VERSION" VARCHAR(256) NOT NULL +); diff --git a/server/sonar-db-core/src/test/resources/org/sonar/db/ResultSetIteratorTest/feed.xml b/server/sonar-db-core/src/test/resources/org/sonar/db/ResultSetIteratorTest/feed.xml new file mode 100644 index 00000000000..e76d538413e --- /dev/null +++ b/server/sonar-db-core/src/test/resources/org/sonar/db/ResultSetIteratorTest/feed.xml @@ -0,0 +1,5 @@ +<dataset> + <issues id="10" kee="AB" /> + <issues id="20" kee="CD" /> + <issues id="30" kee="EF" /> +</dataset> diff --git a/server/sonar-db-core/src/test/resources/org/sonar/db/ResultSetIteratorTest/schema.sql b/server/sonar-db-core/src/test/resources/org/sonar/db/ResultSetIteratorTest/schema.sql new file mode 100644 index 00000000000..333bb0e193e --- /dev/null +++ b/server/sonar-db-core/src/test/resources/org/sonar/db/ResultSetIteratorTest/schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE "ISSUES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "KEE" VARCHAR(200) NOT NULL +); |