aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-db-core/src/test/java
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/src/test/java
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/src/test/java')
-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
21 files changed, 2769 insertions, 0 deletions
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");
+ }
+}