Some refactoring/extensions in the ATDB API + documentation.
Change-Id: I0fd34e37cbcbbe456cfbb8c258ad69ff35a917ec
Signed-off-by: Raphael Weber <raphael.weber@vector.com>
diff --git a/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventChainImporter.java b/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventChainImporter.java
index 62f32a6..fd28fc1 100644
--- a/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventChainImporter.java
+++ b/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventChainImporter.java
@@ -50,7 +50,8 @@
final List<AbstractEventChain> allECs = ConstraintsUtil.getAllAbstractEventChains(this.model.getConstraintsModel());
final int eventChainCount = allECs.size();
final SubMonitor subMon = SubMonitor.convert(progressMonitor, "Importing event chains from AMALTHEA...", eventChainCount);
- try (final Statement statement = this.con.createStatement()) {
+ try {
+ final Statement statement = this.con.createStatement();
statement.addBatch("INSERT OR IGNORE INTO entityType(name) VALUES('EC');");
statement.addBatch("INSERT OR IGNORE INTO property(name, type) VALUES('ecItems', 'entityIdRef');");
statement.addBatch("INSERT OR IGNORE INTO property(name, type) VALUES('ecStimulus', 'eventIdRef');");
diff --git a/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventChainMetricCalculator.java b/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventChainMetricCalculator.java
index 519c528..7170166 100644
--- a/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventChainMetricCalculator.java
+++ b/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventChainMetricCalculator.java
@@ -32,8 +32,9 @@
@Override
public void run(IProgressMonitor progressMonitor) throws InvocationTargetException, InterruptedException {
final SubMonitor subMon = SubMonitor.convert(progressMonitor, "Calculating event chain metrics...", 3);
- try (final PreparedStatement metricStmt = this.con.prepareStatement("INSERT OR IGNORE INTO metric(name, dimension) VALUES(?, ?);");
- final Statement mStmt = this.con.createStatement()) {
+ try {
+ final Statement mStmt = this.con.createStatement();
+ final PreparedStatement metricStmt = this.con.getPreparedStatementFor("INSERT OR IGNORE INTO metric(name, dimension) VALUES(?, ?);");
// create event chain instances with separate sql program (a sequence of queries)
String ecInstCalculationSQL = "";
try {
diff --git a/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventImporter.java b/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventImporter.java
index eb12bbf..69f335e 100644
--- a/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventImporter.java
+++ b/plugins/org.eclipse.app4mc.atdb.import.amalthea/src/org/eclipse/app4mc/atdb/_import/amalthea/EventImporter.java
@@ -49,11 +49,12 @@
if (this.model.getEventModel() != null) {
final int eventCount = this.model.getEventModel().getEvents().size();
final SubMonitor subMon = SubMonitor.convert(progressMonitor, "Importing events from AMALTHEA...", eventCount);
- try (final PreparedStatement entityStmt = this.con.prepareStatement("INSERT OR IGNORE INTO entity(name) VALUES(?);");
- final PreparedStatement evTypeStmt = this.con.prepareStatement("INSERT OR IGNORE INTO eventType(name) VALUES(?);");
- final PreparedStatement eventStmt = this.con.prepareStatement("INSERT OR IGNORE INTO event(name, eventTypeId, entityId, sourceEntityId) "//
- + "VALUES(?, (SELECT id FROM eventType WHERE name = ?), (SELECT id FROM entity WHERE name = ?), "//
- + "(SELECT id FROM entity WHERE name = ?));")) {
+ try {
+ final PreparedStatement entityStmt = this.con.getPreparedStatementFor("INSERT OR IGNORE INTO entity(name) VALUES(?);");
+ final PreparedStatement evTypeStmt = this.con.getPreparedStatementFor("INSERT OR IGNORE INTO eventType(name) VALUES(?);");
+ final PreparedStatement eventStmt = this.con.getPreparedStatementFor("INSERT OR IGNORE INTO event(name, eventTypeId, entityId, sourceEntityId) "//
+ + "VALUES(?, (SELECT id FROM eventType WHERE name = ?), (SELECT id FROM entity WHERE name = ?), "//
+ + "(SELECT id FROM entity WHERE name = ?));");
final List<Event> events = this.model.getEventModel().getEvents();
for(final Event event:events) {
// ignore event sets for now
diff --git a/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/ATDBMetricCalculator.java b/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/ATDBMetricCalculator.java
index b877fa6..d8df876 100644
--- a/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/ATDBMetricCalculator.java
+++ b/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/ATDBMetricCalculator.java
@@ -44,8 +44,9 @@
@Override
public void run(IProgressMonitor monitor) throws InvocationTargetException {
monitor.beginTask("Calculating metrics...", 100);
- try (final PreparedStatement metricStmt = this.con.prepareStatement("INSERT INTO metric(name, dimension) VALUES(?, ?);");
- final Statement statement = this.con.createStatement()) {
+ try {
+ final Statement statement = this.con.createStatement();
+ final PreparedStatement metricStmt = this.con.getPreparedStatementFor("INSERT INTO metric(name, dimension) VALUES(?, ?);");
final Set<String> entityInstanceTimeMetrics = new LinkedHashSet<>();
// for each state: add a '[state]Time' metric signifying how long the entity instance was in that state
diff --git a/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/BTFImporter.java b/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/BTFImporter.java
index e7b5a6c..a7def00 100644
--- a/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/BTFImporter.java
+++ b/plugins/org.eclipse.app4mc.atdb.import.btf/src/org/eclipse/app4mc/atdb/_import/btf/BTFImporter.java
@@ -53,19 +53,19 @@
final SubMonitor readingBTFMonitor = subMon.split(5);
readingBTFMonitor.beginTask("Reading BTF file...", 10_000);
try (final FileInputStream fis = new FileInputStream(this.btfFile);
- final Scanner sc = new Scanner(fis, "UTF-8");
- final PreparedStatement metaStmt = this.con.prepareStatement("INSERT INTO metaInformation VALUES(?, ?);");
- final PreparedStatement entTStmt = this.con.prepareStatement("INSERT INTO entityType(name) VALUES(?);");
- final PreparedStatement entStmt = this.con.prepareStatement("INSERT INTO entity(name, entityTypeId) VALUES(?,"
- + "(SELECT id FROM entityType WHERE name = ?));");
- final PreparedStatement instStmt = this.con.prepareStatement("INSERT INTO entityInstance VALUES("
- + "(SELECT id FROM entity WHERE name = ?), ?);");
- final PreparedStatement evTStmt = this.con.prepareStatement("INSERT INTO eventType(name) VALUES(?);");
- final PreparedStatement evStmt = this.con.prepareStatement("INSERT INTO traceEvent VALUES(?, ?,"
- + "(SELECT id FROM entity WHERE name = ?), ?,"//
- + "(SELECT id FROM entity WHERE name = ?), ?,"//
- + "(SELECT id FROM eventType WHERE name = ?), ?)");
- final Statement propStmt = this.con.createStatement()) {
+ final Scanner sc = new Scanner(fis, "UTF-8")) {
+ final Statement propStmt = this.con.createStatement();
+ final PreparedStatement metaStmt = this.con.getPreparedStatementFor("INSERT INTO metaInformation VALUES(?, ?);");
+ final PreparedStatement entTStmt = this.con.getPreparedStatementFor("INSERT INTO entityType(name) VALUES(?);");
+ final PreparedStatement entStmt = this.con.getPreparedStatementFor("INSERT INTO entity(name, entityTypeId) VALUES(?,"
+ + "(SELECT id FROM entityType WHERE name = ?));");
+ final PreparedStatement instStmt = this.con.getPreparedStatementFor("INSERT INTO entityInstance VALUES("
+ + "(SELECT id FROM entity WHERE name = ?), ?);");
+ final PreparedStatement evTStmt = this.con.getPreparedStatementFor("INSERT INTO eventType(name) VALUES(?);");
+ final PreparedStatement evStmt = this.con.getPreparedStatementFor("INSERT INTO traceEvent VALUES(?, ?,"
+ + "(SELECT id FROM entity WHERE name = ?), ?,"//
+ + "(SELECT id FROM entity WHERE name = ?), ?,"//
+ + "(SELECT id FROM eventType WHERE name = ?), ?)");
final long fileSize = new File(this.btfFile).length();
long currentTimestamp = Long.MIN_VALUE;
int currentSQCNR = 0;
diff --git a/plugins/org.eclipse.app4mc.atdb.metrics/src/org/eclipse/app4mc/atdb/metrics/DatabaseAccess.java b/plugins/org.eclipse.app4mc.atdb.metrics/src/org/eclipse/app4mc/atdb/metrics/DatabaseAccess.java
index 7044f1f..010c534 100644
--- a/plugins/org.eclipse.app4mc.atdb.metrics/src/org/eclipse/app4mc/atdb/metrics/DatabaseAccess.java
+++ b/plugins/org.eclipse.app4mc.atdb.metrics/src/org/eclipse/app4mc/atdb/metrics/DatabaseAccess.java
@@ -68,15 +68,7 @@
public DatabaseAccess(final IFile databaseFile) throws SQLException {
this.traceDbConnection = new ATDBConnection(databaseFile.getLocation().toFile().toString());
-
- final String timeUnitSelection = "SELECT value FROM metaInformation WHERE name LIKE 'timeBase';"; //$NON-NLS-1$
- this.timeBase = this.traceDbConnection.queryAndMapResult(timeUnitSelection, rs -> {
- if (rs.next()) {
- return rs.getString(1);
- } else {
- return "?";
- }
- });
+ this.timeBase = this.traceDbConnection.getTimeBase();
this.query2Result = new LinkedHashMap<>();
}
diff --git a/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/ATDBBuilder.java b/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/ATDBBuilder.java
index 922f4a6..dc8114a 100644
--- a/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/ATDBBuilder.java
+++ b/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/ATDBBuilder.java
@@ -20,9 +20,9 @@
public class ATDBBuilder {
- private final ATDBConnection connection;
+ private final DBConnection connection;
- public ATDBBuilder(final ATDBConnection connection) throws SQLException {
+ public ATDBBuilder(final DBConnection connection) throws SQLException {
this.connection = connection;
}
diff --git a/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/ATDBConnection.java b/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/ATDBConnection.java
index 9d44d44..502ea8b 100644
--- a/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/ATDBConnection.java
+++ b/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/ATDBConnection.java
@@ -13,114 +13,166 @@
package org.eclipse.app4mc.atdb;
-import java.sql.Connection;
-import java.sql.DriverManager;
import java.sql.PreparedStatement;
-import java.sql.ResultSet;
import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.Optional;
-import java.util.Properties;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-public final class ATDBConnection implements AutoCloseable {
+public class ATDBConnection extends DBConnection {
- private final Connection connection;
- private final Optional<Statement> statement;
-
- public ATDBConnection(final String atdbFile) throws SQLException {
- this(atdbFile, false);
- }
-
- public ATDBConnection(final String atdbFile, final boolean writeAccess) throws SQLException {
- try {
- Class.forName("org.sqlite.JDBC"); // init JDBC driver
- } catch (ClassNotFoundException e) {
- // fail silently. DriverManager will throw an SQL exception complaining about a missing driver
- }
- final String dbFileUrl = "jdbc:sqlite:" + atdbFile;
- if (!writeAccess) {
- final Properties properties = new Properties();
- properties.put("open_mode", "1"); //$NON-NLS-1$ //$NON-NLS-2$
- this.connection = DriverManager.getConnection(dbFileUrl, properties);
- this.statement = Optional.empty();
- } else {
- this.connection = DriverManager.getConnection(dbFileUrl);
- final Statement tmpStatement = this.connection.createStatement();
- tmpStatement.setQueryTimeout(30); // set timeout to 30 sec.
- tmpStatement.executeUpdate("PRAGMA foreign_keys = ON;"); // enable foreign key checks
- this.statement = Optional.of(tmpStatement);
- }
+ private static final String METAINFORMATION_QUERY = "SELECT value FROM metaInformation WHERE name = ?;";
+ private static final String ENTITY_BY_TYPE_QUERY = "SELECT name FROM entity WHERE entityTypeId = (SELECT id FROM entityType WHERE entityType.name = ?);";
+ private static final String EVENT_BY_TYPE_QUERY = "SELECT name FROM event WHERE eventTypeId = (SELECT id FROM eventType WHERE eventType.name = ?);";
+ private static final String EVENTCHAIN_EVENTREF_QUERY = "SELECT (SELECT GROUP_CONCAT((SELECT name FROM event WHERE id = value), ', ') "
+ + "FROM propertyValue WHERE entityId = ecEntity.id AND propertyId = (SELECT id FROM property WHERE name = ?)) AS evName "
+ + "FROM entity AS ecEntity WHERE ecEntity.name = ? AND entityTypeId = (SELECT id FROM entityType WHERE entityType.name = 'EC');";
+ private static final String EVENTCHAIN_ITEMS_QUERY = "SELECT (SELECT name FROM entity WHERE id = value) AS items "
+ + "FROM entity AS ecEntity, propertyValue WHERE ecEntity.name = ? AND entityTypeId = (SELECT id FROM entityType WHERE entityType.name = 'EC') "
+ + "AND propertyValue.entityId = ecEntity.id AND propertyValue.propertyId = (SELECT id FROM property WHERE name = 'ecItems');";
+ private static final String METRICS_QUERY = "SELECT name FROM metric;";
+ private static final String METRICVALUE_QUERY = "SELECT value FROM entityMetricValue WHERE entityId = (SELECT id FROM entity WHERE name = ?) "
+ + "AND metricId = (SELECT id FROM metric WHERE name = ?);";
+
+ public ATDBConnection(String dbFile) throws SQLException {
+ super(dbFile);
}
- @Override
- public void close() throws SQLException {
- if (this.statement.isPresent()) {
- this.statement.get().close();
+ public ATDBConnection(String dbFile, boolean writeAccess) throws SQLException {
+ super(dbFile, writeAccess);
+ }
+
+ private String getFirstStringFromPreparedQuery(final String query, final List<String> parameters, final String defaultValue) throws SQLException {
+ return getStringStreamFromPreparedQuery(query, parameters).findFirst().orElse(defaultValue);
+ }
+
+ private Stream<String> getStringStreamFromPreparedQuery(final String query, final List<String> parameters) throws SQLException {
+ final PreparedStatement prepStmt = super.getPrepareQueryFor(query);
+ for(int i = 0; i < parameters.size(); i++) {
+ prepStmt.setString(i + 1, parameters.get(i));
}
- this.connection.close();
+ return super.queryAndMapToStream(prepStmt, rs -> rs.getString(1)).filter(Objects::nonNull);
}
- public boolean executeUpdate(final String query) throws SQLException {
- if (this.statement.isPresent()) {
- this.statement.get().executeUpdate(query);
- return true;
- } else {
- return false;
- }
+ private List<String> getStringListFromPreparedQuery(final String query, final List<String> parameters) throws SQLException {
+ return getStringStreamFromPreparedQuery(query, parameters).collect(Collectors.toList());
}
- @FunctionalInterface
- public static interface ThrowingConsumer<T, E extends Exception> {
- void accept(T t) throws E;
+ private String getMetaInfo(final String metaInfoKey, final String defaultValue) throws SQLException {
+ return getFirstStringFromPreparedQuery(METAINFORMATION_QUERY, Arrays.asList(metaInfoKey), defaultValue);
}
- public void queryAndConsumeResult(final String query, final ThrowingConsumer<ResultSet, SQLException> consumer) throws SQLException {
- try (final Statement tmpStatement = this.connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
- final ResultSet resultSet = tmpStatement.executeQuery(query);
- consumer.accept(resultSet);
- }
+ /**
+ * Returns the time base of this ATDB.
+ *
+ * @return The time base unit. If not set, "?" will be returned.
+ * @throws SQLException
+ */
+ public String getTimeBase() throws SQLException {
+ return getMetaInfo("timeBase", "?");
}
- @FunctionalInterface
- public static interface ThrowingFunction<T, R, E extends Exception> {
- R apply(T t) throws E;
+ private List<String> getAllEntitiesByType(final String entityTypeName) throws SQLException {
+ return getStringListFromPreparedQuery(ENTITY_BY_TYPE_QUERY, Arrays.asList(entityTypeName));
}
- public <R> R queryAndMapResult(final String query, final ThrowingFunction<ResultSet, R, SQLException> mapper) throws SQLException {
- try (final Statement tmpStatement = this.connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
- final ResultSet resultSet = tmpStatement.executeQuery(query);
- return mapper.apply(resultSet);
- }
+ /**
+ * Returns all task names of this ATDB.
+ * @return A list of all task names.
+ * @throws SQLException
+ */
+ public List<String> getAllTasks() throws SQLException {
+ return getAllEntitiesByType("T");
}
- public PreparedStatement prepareStatement(final String query) throws SQLException {
- return this.connection.prepareStatement(query);
+ /**
+ * Returns all ISR (Interrupt Service Routine) names of this ATDB.
+ * @return A list of all ISR names.
+ * @throws SQLException
+ */
+ public List<String> getAllISRs() throws SQLException {
+ return getAllEntitiesByType("I");
}
- public Statement createStatement() throws SQLException {
- return this.connection.createStatement();
+ /**
+ * Returns all runnable names of this ATDB.
+ * @return A list of all runnable names.
+ * @throws SQLException
+ */
+ public List<String> getAllRunnables() throws SQLException {
+ return getAllEntitiesByType("R");
}
- public void executeBatchStatements(final Statement...statements) throws SQLException {
- this.connection.setAutoCommit(false);
- try {
- for(final Statement stmt:statements) {
- stmt.executeBatch();
- }
- } finally {
- this.connection.setAutoCommit(true);
- }
+ /**
+ * Returns all event chain names of this ATDB.
+ * @return A list of all event chain names.
+ * @throws SQLException
+ */
+ public List<String> getAllEventChains() throws SQLException {
+ return getAllEntitiesByType("EC");
}
- public boolean tableExists(final String...tableName) throws SQLException {
- try (final PreparedStatement tmpStatement = this.connection.prepareStatement("SELECT name FROM sqlite_master WHERE type = 'table' AND name IN (?);",
- ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
- tmpStatement.setString(1, Stream.of(tableName).collect(Collectors.joining(", ")));
- final ResultSet resultSet = tmpStatement.executeQuery();
- return resultSet.next();
- }
+ /**
+ * Returns all event (specification, not from the actual trace!) names of this ATDB of the given event type.
+ * @param eventTypeName The event type name of which the returned event names shall be.
+ * @return A list of all event names.
+ * @throws SQLException
+ */
+ public List<String> getAllEventsByType(final String eventTypeName) throws SQLException {
+ return getStringListFromPreparedQuery(EVENT_BY_TYPE_QUERY, Arrays.asList(eventTypeName));
+ }
+
+ /**
+ * Returns the stimulus event name for the given event chain name. If there are multiple stimulus events, they will be separated by a ', '.
+ * @param eventChainName The name of the event chain from which we want to know the stimulus event.
+ * @return The stimulus event name. Empty, if none was found.
+ * @throws SQLException
+ */
+ public String getEventChainStimulus(final String eventChainName) throws SQLException {
+ return getFirstStringFromPreparedQuery(EVENTCHAIN_EVENTREF_QUERY, Arrays.asList("ecStimulus", eventChainName), "");
+ }
+
+ /**
+ * Returns the response event name for the given event chain name. If there are multiple response events, they will be separated by a ', '.
+ * @param eventChainName The name of the event chain from which we want to know the response event.
+ * @return The response event name. Empty, if none was found.
+ * @throws SQLException
+ */
+ public String getEventChainResponse(final String eventChainName) throws SQLException {
+ return getFirstStringFromPreparedQuery(EVENTCHAIN_EVENTREF_QUERY, Arrays.asList("ecResponse", eventChainName), "");
+ }
+
+ /**
+ * Returns the names of the item event chains for the given event chain name.
+ * @param eventChainName The name of the event chain from which we want to know the item event chain names.
+ * @return The item event chain names.
+ * @throws SQLException
+ */
+ public List<String> getEventChainItems(final String eventChainName) throws SQLException {
+ return getStringListFromPreparedQuery(EVENTCHAIN_ITEMS_QUERY, Arrays.asList(eventChainName));
+ }
+
+ /**
+ * Returns all metric names contained in this ATDB, some of which are specific to certain entity types.
+ * @return All metric names.
+ * @throws SQLException
+ */
+ public List<String> getAllMetrics() throws SQLException {
+ return getStringListFromPreparedQuery(METRICS_QUERY, Collections.emptyList());
+ }
+
+ /**
+ * Returns the value of the metric for the given entity and metric names.
+ * @param entityName The name of the entity from which we want to know the metric value.
+ * @param metricName The name of the metric from which we want to know the value for the specified entity.
+ * @return The value for the metric and entity. Empty, if none is found.
+ * @throws SQLException
+ */
+ public String getValueForMetricAndEntity(final String entityName, final String metricName) throws SQLException {
+ return getFirstStringFromPreparedQuery(METRICVALUE_QUERY, Arrays.asList(entityName, metricName), "");
}
}
diff --git a/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/DBConnection.java b/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/DBConnection.java
new file mode 100644
index 0000000..2afd5af
--- /dev/null
+++ b/plugins/org.eclipse.app4mc.atdb/src/org/eclipse/app4mc/atdb/DBConnection.java
@@ -0,0 +1,233 @@
+/**
+ ********************************************************************************
+ * Copyright (c) 2020 Eclipse APP4MC contributors.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************
+ */
+
+package org.eclipse.app4mc.atdb;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.stream.Stream;
+import java.util.stream.Stream.Builder;
+
+public class DBConnection implements AutoCloseable {
+
+ private static final String TABLE_EXISTS_QUERY = "SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?;";
+
+ private final Connection connection;
+ private final Optional<Statement> statement;
+
+ private final Map<String, PreparedStatement> prepStmts;
+ private final Collection<Statement> stmts;
+
+ /**
+ * Creates a connection to the given db file in read-only mode.
+ *
+ * @param dbFile The path to the db file.
+ * @throws SQLException
+ */
+ public DBConnection(final String dbFile) throws SQLException {
+ this(dbFile, false);
+ }
+
+ /**
+ * Creates a connection to the given db file.
+ *
+ * @param dbFile The path to the db file.
+ * @param writeAccess Specify <code>TRUE</code> to open the db connection with write access. <code>FALSE</code> for read-only mode.
+ * @throws SQLException
+ */
+ public DBConnection(final String dbFile, final boolean writeAccess) throws SQLException {
+ try {
+ Class.forName("org.sqlite.JDBC"); // init JDBC driver
+ } catch (ClassNotFoundException e) {
+ // fail silently. DriverManager will throw an SQL exception complaining about a missing driver
+ }
+ final String dbFileUrl = "jdbc:sqlite:" + dbFile;
+ if (!writeAccess) {
+ final Properties properties = new Properties();
+ properties.put("open_mode", "1"); //$NON-NLS-1$ //$NON-NLS-2$
+ this.connection = DriverManager.getConnection(dbFileUrl, properties);
+ this.statement = Optional.empty();
+ } else {
+ this.connection = DriverManager.getConnection(dbFileUrl);
+ final Statement tmpStatement = this.connection.createStatement();
+ tmpStatement.setQueryTimeout(30); // set timeout to 30 sec.
+ tmpStatement.executeUpdate("PRAGMA foreign_keys = ON;"); // enable foreign key checks
+ this.statement = Optional.of(tmpStatement);
+ }
+ this.prepStmts = new LinkedHashMap<>();
+ this.stmts = new LinkedHashSet<>();
+ }
+
+ /**
+ * Closes all statements created by this connection. Then closes the connection to the data base itself.
+ */
+ @Override
+ public void close() throws SQLException {
+ for(final PreparedStatement ps:this.prepStmts.values()) {
+ ps.close();
+ }
+ this.prepStmts.clear();
+ if (this.statement.isPresent()) {
+ this.statement.get().close();
+ }
+ this.connection.close();
+ }
+
+ /**
+ * Executes the statement (changing the contents of the db) given in the query.
+ *
+ * @param query The statement that performs the changes in the db.
+ * @return <code>TRUE</code> if the statement was executed by the db, <code>FALSE</code> otherwise.
+ * @throws SQLException
+ */
+ public boolean executeUpdate(final String query) throws SQLException {
+ if (this.statement.isPresent()) {
+ this.statement.get().executeUpdate(query);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Same as {@link java.util.function.Consumer}, but may throw exception of type E.
+ * @param <T>
+ * @param <E>
+ */
+ @FunctionalInterface
+ public static interface ThrowingConsumer<T, E extends Exception> {
+ void accept(T t) throws E;
+ }
+
+ /**
+ * Executes a read-only query and consumes the result set via the given consumer function.
+ *
+ * @param query The read-only query to execute.
+ * @param consumer The function that consumes the result set.
+ * @throws SQLException
+ */
+ public void queryAndConsumeResult(final String query, final ThrowingConsumer<ResultSet, SQLException> consumer) throws SQLException {
+ try (final Statement tmpStatement = this.connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
+ final ResultSet resultSet = tmpStatement.executeQuery(query);
+ consumer.accept(resultSet);
+ }
+ }
+
+ /**
+ * Same as {@link java.util.function.Function}, but may throw exception of type E.
+ * @param <T>
+ * @param <R>
+ * @param <E>
+ */
+ @FunctionalInterface
+ public static interface ThrowingFunction<T, R, E extends Exception> {
+ R apply(T t) throws E;
+ }
+
+ protected <R> Stream<R> queryAndMapToStream(final PreparedStatement query, final ThrowingFunction<ResultSet, R, SQLException> rowMapper) throws SQLException {
+ final ResultSet resultSet = query.executeQuery();
+ final Builder<R> result = Stream.builder();
+ while (resultSet.next()) {
+ result.accept(rowMapper.apply(resultSet));
+ }
+ return result.build();
+ }
+
+ /**
+ * Returns a prepared statement for the the given query. Will return an existing one if the query was already created as a prepared statement.
+ * Since the prepared statement is internally cached, this DBConnection will also close it, when the connection is closed. Thus, the user must not
+ * close the prepared statement.
+ *
+ * @param query The query to put into the prepared statement.
+ * @return The prepared statement for the given query.
+ * @throws SQLException
+ */
+ public PreparedStatement getPreparedStatementFor(final String query) throws SQLException {
+ if (!this.prepStmts.containsKey(query)) {
+ this.prepStmts.put(query, this.connection.prepareStatement(query));
+ }
+ return this.prepStmts.get(query);
+ }
+
+ /**
+ * Returns a prepared query for the given query string. This query is only allowed read access to the data base. Will return an existing one if the query
+ * was already created as a prepared query. Since the prepared query is internally cached, this DBConnection will also close it, when the connection is
+ * closed. Thus, the user must not close the prepared query.
+ *
+ * @param query The query string to put into the prepared query.
+ * @return The prepared query for the given query string.
+ * @throws SQLException
+ */
+ public PreparedStatement getPrepareQueryFor(final String query) throws SQLException {
+ if (!this.prepStmts.containsKey(query)) {
+ this.prepStmts.put(query, this.connection.prepareStatement(query, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY));
+ }
+ return this.prepStmts.get(query);
+ }
+
+ /**
+ * Creates and returns a new statement. This DBConection will also close the statement, when the connection is closed. Thus, the user must not close the
+ * statement.
+ *
+ * @return A newly created statement.
+ * @throws SQLException
+ */
+ public Statement createStatement() throws SQLException {
+ final Statement stmt = this.connection.createStatement();
+ this.stmts.add(stmt);
+ return stmt;
+ }
+
+ /**
+ * Executes the given statement batches (calls {@link Statement#executeBatch()} on each provided statement).
+ *
+ * @param statements Batches of statements to be executed.
+ * @throws SQLException
+ */
+ public void executeBatchStatements(final Statement...statements) throws SQLException {
+ final boolean oldAutoCommit = this.connection.getAutoCommit();
+ if (oldAutoCommit) this.connection.setAutoCommit(false);
+ try {
+ for(final Statement stmt:statements) {
+ stmt.executeBatch();
+ }
+ } finally {
+ this.connection.setAutoCommit(oldAutoCommit);
+ }
+ }
+
+ /**
+ * Checks whether a table with the given tableName exists in the data base.
+ *
+ * @param tableName The name of the table to check for existence.
+ * @return <code>TRUE</code> if a table with the specified name exists, <code>FALSE</code> otherwise.
+ * @throws SQLException
+ */
+ public boolean tableExists(final String tableName) throws SQLException {
+ final PreparedStatement prepStmt = getPrepareQueryFor(TABLE_EXISTS_QUERY);
+ prepStmt.setString(1, tableName);
+ final ResultSet resultSet = prepStmt.executeQuery();
+ return resultSet.next();
+ }
+
+}