aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-db-core
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2017-02-15 18:18:27 +0100
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>2017-02-17 13:31:49 +0100
commit9824b94f16109305b4847a611379029695823427 (patch)
tree85b339103ce4c56df78e3e26f21117da7fb04976 /server/sonar-db-core
parent0d3be9943570cf5bfb7fbb72fdfe5a6fcb2798c0 (diff)
downloadsonarqube-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')
-rw-r--r--server/sonar-db-core/pom.xml223
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/core/DatabaseUtils.java30
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/core/package-info.java24
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/Database.java41
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/DatabaseChecker.java105
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/DatabaseUtils.java291
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/DdlUtils.java71
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/DefaultDatabase.java185
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/ResultSetIterator.java114
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/RowNotFoundException.java34
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/dialect/AbstractDialect.java90
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/dialect/Dialect.java92
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/dialect/DialectUtils.java63
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/dialect/H2.java45
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/dialect/MsSql.java41
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/dialect/MySql.java54
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/dialect/Oracle.java52
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/dialect/PostgreSql.java52
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/dialect/package-info.java24
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/package-info.java24
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/profiling/ConnectionInterceptor.java32
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/profiling/InvocationUtils.java45
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/profiling/NullConnectionInterceptor.java38
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledConnectionInterceptor.java45
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfiledDataSource.java420
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfilingConnectionHandler.java56
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfilingPreparedStatementHandler.java62
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/profiling/ProfilingStatementHandler.java52
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/profiling/SqlLogFormatter.java70
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/profiling/package-info.java24
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/semaphore/SemaphoresImpl.java44
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/semaphore/package-info.java24
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java96
-rw-r--r--server/sonar-db-core/src/main/java/org/sonar/db/version/package-info.java24
-rw-r--r--server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql554
-rw-r--r--server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl625
-rw-r--r--server/sonar-db-core/src/main/resources/org/sonar/db/version/schema_migrations-h2.ddl4
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java601
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/CoreDbTester.java57
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/CoreTestDb.java184
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/DatabaseCheckerTest.java141
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/DatabaseCommands.java215
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/DatabaseUtilsTest.java325
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/DdlUtilsTest.java91
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/DefaultDatabaseTest.java116
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/H2Database.java122
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/H2DatabaseTest.java51
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/ResultSetIteratorTest.java169
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/dialect/DialectUtilsTest.java45
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/dialect/H2Test.java59
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/dialect/MsSqlTest.java62
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/dialect/MySqlTest.java63
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/dialect/OracleTest.java60
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/dialect/PostgreSqlTest.java64
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/profiling/InvocationUtilsTest.java76
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/profiling/ProfiledDataSourceTest.java149
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/profiling/SqlLogFormatterTest.java79
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/semaphore/SemaphoresImplTest.java40
-rw-r--r--server/sonar-db-core/src/test/resources/logback-test.xml33
-rw-r--r--server/sonar-db-core/src/test/resources/org/sonar/db/CoreDbTester/empty.sql0
-rw-r--r--server/sonar-db-core/src/test/resources/org/sonar/db/DatabaseUtilsTest/just_one_table.sql3
-rw-r--r--server/sonar-db-core/src/test/resources/org/sonar/db/ResultSetIteratorTest/feed.xml5
-rw-r--r--server/sonar-db-core/src/test/resources/org/sonar/db/ResultSetIteratorTest/schema.sql4
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
+);