19package org.sleuthkit.datamodel;
21import com.google.common.annotations.Beta;
22import java.sql.PreparedStatement;
23import java.sql.ResultSet;
24import java.sql.SQLException;
25import java.sql.Statement;
27import java.sql.Timestamp;
28import java.text.MessageFormat;
30import java.util.logging.Level;
31import java.util.logging.Logger;
32import org.sleuthkit.datamodel.SleuthkitCase.CaseDbConnection;
33import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
34import static org.sleuthkit.datamodel.SleuthkitCase.closeStatement;
35import org.sleuthkit.datamodel.TskData.DbType;
42public final class CaseDbAccessManager {
62 private static final Logger logger = Logger.getLogger(CaseDbAccessManager.class.getName());
87 boolean doesColumnExists =
false;
90 doesColumnExists =
columnExists(tableName, columnName, localTrans);
95 if (
null != localTrans) {
99 logger.log(Level.SEVERE,
"Failed to rollback transaction after exception", ex);
104 return doesColumnExists;
120 Statement statement =
null;
121 ResultSet resultSet =
null;
123 CaseDbConnection connection = transaction.getConnection();
124 statement = connection.createStatement();
126 String tableInfoQuery =
"PRAGMA table_info(%s)";
127 resultSet = statement.executeQuery(String.format(tableInfoQuery, tableName));
128 while (resultSet.next()) {
129 if (resultSet.getString(
"name").equalsIgnoreCase(columnName)) {
136 String tableInfoQueryTemplate =
"SELECT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='%s' AND column_name='%s')";
137 resultSet = statement.executeQuery(String.format(tableInfoQueryTemplate, tableName.toLowerCase(), columnName.toLowerCase()));
138 if (resultSet.next()) {
143 catch (SQLException ex) {
144 throw new TskCoreException(
"Error checking if column " + columnName +
"exists ", ex);
147 if (resultSet !=
null) {
150 }
catch (SQLException ex2) {
151 logger.log(Level.WARNING,
"Failed to to close resultset after checking column", ex2);
154 closeStatement(statement);
169 boolean doesTableExist =
false;
172 doesTableExist =
tableExists(tableName, localTrans);
177 if (
null != localTrans) {
181 logger.log(Level.SEVERE,
"Failed to rollback transaction after exception", ex);
186 return doesTableExist;
201 Statement statement =
null;
202 ResultSet resultSet =
null;
204 CaseDbConnection connection = transaction.getConnection();
205 statement = connection.createStatement();
207 resultSet = statement.executeQuery(
"SELECT name FROM sqlite_master WHERE type='table'");
208 while (resultSet.next()) {
209 if (resultSet.getString(
"name").equalsIgnoreCase(tableName)) {
216 String tableInfoQueryTemplate =
"SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name='%s')";
217 resultSet = statement.executeQuery(String.format(tableInfoQueryTemplate, tableName.toLowerCase()));
218 if (resultSet.next()) {
223 catch (SQLException ex) {
224 throw new TskCoreException(
"Error checking if table " + tableName +
"exists ", ex);
226 if (resultSet !=
null) {
229 }
catch (SQLException ex2) {
230 logger.log(Level.WARNING,
"Failed to to close resultset after checking table", ex2);
233 closeStatement(statement);
253 validateTableName(tableName);
254 validateSQL(tableSchema);
256 tskDB.acquireSingleUserCaseWriteLock();
257 String createSQL =
"CREATE TABLE IF NOT EXISTS " + tableName +
" " + tableSchema;
258 try (CaseDbConnection connection = tskDB.getConnection();
259 Statement statement = connection.createStatement();) {
260 statement.execute(createSQL);
261 }
catch (SQLException ex) {
264 tskDB.releaseSingleUserCaseWriteLock();
284 if (
null != localTrans) {
288 logger.log(Level.SEVERE,
"Failed to rollback transaction after exception", ex);
305 validateTableName(tableName);
306 validateSQL(alterSQL);
308 CaseDbConnection connection = transaction.getConnection();
310 Statement statement =
null;
311 String sql =
"ALTER TABLE " + tableName +
" " + alterSQL;
314 statement = connection.createStatement();
315 statement.execute(sql);
316 }
catch (SQLException ex) {
321 alterSQL.toLowerCase().contains(
"add column") &&
322 ex.getMessage().toLowerCase().contains(
"duplicate column name")) {
323 logger.log(Level.WARNING, String.format(
"Column being added by SQL = %s already exists in table %s", alterSQL, tableName));
326 throw new TskCoreException(String.format(
"Error altering table %s with SQL = %s", tableName, sql), ex);
328 closeStatement(statement);
348 validateTableName(tableName);
349 validateIndexName(indexName);
350 validateSQL(colsSQL);
352 tskDB.acquireSingleUserCaseWriteLock();
353 String indexSQL =
"CREATE INDEX IF NOT EXISTS " + indexName +
" ON " + tableName +
" " + colsSQL;
354 try (CaseDbConnection connection = tskDB.getConnection();
355 Statement statement = connection.createStatement(); ) {
356 statement.execute(indexSQL);
357 }
catch (SQLException ex) {
360 tskDB.releaseSingleUserCaseWriteLock();
378 long rowId =
insert(tableName, sql, localTrans);
383 if (
null != localTrans) {
387 logger.log(Level.SEVERE,
"Failed to rollback transaction after exception", ex);
413 validateTableName(tableName);
416 CaseDbConnection connection = transaction.getConnection();
418 PreparedStatement statement =
null;
420 String insertSQL =
"INSERT";
422 insertSQL +=
" OR IGNORE";
425 insertSQL = insertSQL+
" INTO " + tableName +
" " + sql;
427 statement = connection.prepareStatement(insertSQL, Statement.RETURN_GENERATED_KEYS);
428 connection.executeUpdate(statement);
430 resultSet = statement.getGeneratedKeys();
431 if (resultSet.next()) {
432 rowId = resultSet.getLong(1);
434 }
catch (SQLException ex) {
435 throw new TskCoreException(
"Error inserting row in table " + tableName +
" with sql = "+ insertSQL, ex);
437 closeStatement(statement);
466 if (
null != localTrans) {
470 logger.log(Level.SEVERE,
"Failed to rollback transaction after exception", ex);
496 validateTableName(tableName);
499 CaseDbConnection connection = transaction.getConnection();
501 PreparedStatement statement =
null;
503 String insertSQL =
"INSERT";
505 insertSQL +=
" OR REPLACE";
508 insertSQL +=
" INTO " + tableName +
" " + sql;
510 statement = connection.prepareStatement(insertSQL, Statement.RETURN_GENERATED_KEYS);
511 connection.executeUpdate(statement);
513 resultSet = statement.getGeneratedKeys();
515 rowId = resultSet.getLong(1);
516 }
catch (SQLException ex) {
517 throw new TskCoreException(
"Error inserting row in table " + tableName +
" with sql = "+ insertSQL, ex);
519 closeStatement(statement);
540 validateTableName(tableName);
543 String updateSQL =
"UPDATE " + tableName +
" " + sql;
547 }
catch (SQLException ex) {
548 throw new TskCoreException(
"Error creating update prepared statement for query:\n" + updateSQL, ex);
563 throw new TskCoreException(
"CaseDbPreparedStatement has incorrect type for update operation");
567 preparedStatement.getStatement().executeUpdate();
568 }
catch (SQLException ex) {
569 throw new TskCoreException(
"Error updating row in table " +
"" +
" with sql = "+
"", ex);
584 update(tableName, sql, localTrans);
588 if (
null != localTrans) {
592 logger.log(Level.SEVERE,
"Failed to rollback transaction after exception", ex);
611 validateTableName(tableName);
614 CaseDbConnection connection = transaction.getConnection();
616 Statement statement =
null;
617 String updateSQL =
"UPDATE " + tableName +
" " + sql;
620 statement = connection.createStatement();
621 statement.executeUpdate(updateSQL);
622 }
catch (SQLException ex) {
625 closeStatement(statement);
639 if (queryCallback ==
null) {
645 tskDB.acquireSingleUserCaseReadLock();
646 String selectSQL =
"SELECT " + sql;
647 try (CaseDbConnection connection = tskDB.getConnection();
648 Statement statement = connection.createStatement();
649 ResultSet resultSet = statement.executeQuery(selectSQL)) {
650 queryCallback.process(resultSet);
651 }
catch (SQLException ex) {
654 tskDB.releaseSingleUserCaseReadLock();
678 String selectSQL =
"SELECT " + sql;
681 }
catch (SQLException ex) {
682 throw new TskCoreException(
"Error creating select prepared statement for query:\n" + selectSQL, ex);
701 String selectSQL =
"SELECT " + sql;
705 }
catch (SQLException ex) {
706 throw new TskCoreException(
"Error creating select prepared statement for query:\n" + selectSQL, ex);
722 throw new TskCoreException(
"CaseDbPreparedStatement has incorrect type for select operation");
725 try (ResultSet resultSet = preparedStatement.getStatement().executeQuery()) {
726 queryCallback.process(resultSet);
727 }
catch (SQLException ex) {
728 throw new TskCoreException(MessageFormat.format(
"Error running SELECT query:\n{0}", preparedStatement.getOriginalSql()), ex);
753 try (Statement optimizeStmt = preparedStatement.connection.createStatement()) {
754 optimizeStmt.execute(
"PRAGMA optimize");
755 }
catch (SQLException ex) {
756 throw new TskCoreException(
"An error occurred while attempting to optimize the call", ex);
760 select(preparedStatement, queryCallback);
778 CaseDbConnection connection = localTrans.getConnection();
780 try (Statement statement = connection.createStatement()) {
781 statement.executeUpdate(
"ANALYZE");
782 }
catch (SQLException ex) {
783 throw new TskCoreException(
"An error occurred while attempting to run ANALYZE", ex);
788 if (
null != localTrans) {
792 logger.log(Level.SEVERE,
"Failed to rollback transaction after exception", ex);
815 validateTableName(tableName);
818 String insertSQL =
"INSERT";
820 insertSQL +=
" OR IGNORE";
822 insertSQL = insertSQL +
" INTO " + tableName +
" " + sql;
826 }
catch (SQLException ex) {
827 throw new TskCoreException(
"Error creating insert prepared statement for query:\n" + insertSQL, ex);
842 throw new TskCoreException(
"CaseDbPreparedStatement has incorrect type for insert operation");
846 preparedStatement.getStatement().executeUpdate();
847 }
catch (SQLException ex) {
848 throw new TskCoreException(
"Error inserting row in table " +
"" +
" with sql = "+
"", ex);
861 validateTableName(tableName);
864 tskDB.acquireSingleUserCaseWriteLock();
865 String deleteSQL =
"DELETE FROM " + tableName +
" " + sql;
866 try (CaseDbConnection connection = tskDB.getConnection();
867 Statement statement = connection.createStatement();) {
868 statement.executeUpdate(deleteSQL);
869 }
catch (SQLException ex) {
870 throw new TskCoreException(
"Error deleting row from table " + tableName, ex);
872 tskDB.releaseSingleUserCaseWriteLock();
886 if (
SleuthkitCase.getCoreTableNames().contains(tableName.toLowerCase())) {
887 throw new TskCoreException(
"Attempt to modify a core TSK table " + tableName);
889 if (tableName.toLowerCase().startsWith(
"tsk_")) {
890 throw new TskCoreException(
"Modifying tables with tsk_ prefix is not allowed. ");
902 private void validateIndexName(String indexName)
throws TskCoreException {
904 if (indexName.isEmpty()) {
905 throw new TskCoreException(
"Invalid index name " + indexName);
908 if (SleuthkitCase.getCoreIndexNames().contains(indexName.toLowerCase())) {
909 throw new TskCoreException(
"Attempt to modify a core TSK index " + indexName);
920 private void validateSQL(String sql)
throws TskCoreException {
949 public class CaseDbPreparedStatement
implements AutoCloseable {
951 private final CaseDbConnection connection;
952 private final PreparedStatement preparedStatement;
953 private final String originalSql;
979 if (isWriteLockRequired) {
980 CaseDbAccessManager.this.tskDB.acquireSingleUserCaseWriteLock();
983 CaseDbAccessManager.this.tskDB.acquireSingleUserCaseReadLock();
986 this.connection = tskDB.getConnection();
987 this.preparedStatement = connection.getPreparedStatement(query, Statement.NO_GENERATED_KEYS);
988 this.originalSql = query;
1004 this.connection = trans.getConnection();
1005 this.preparedStatement = connection.getPreparedStatement(query, Statement.NO_GENERATED_KEYS);
1006 this.originalSql = query;
1015 private PreparedStatement getStatement() {
1016 return preparedStatement;
1033 private String getOriginalSql() {
1044 preparedStatement.clearParameters();
1045 }
catch (SQLException ex) {
1046 throw new TskCoreException(
"An error occurred while clearing parameters.", ex);
1060 preparedStatement.setBoolean(parameterIndex, x);
1061 }
catch (SQLException ex) {
1062 throw new TskCoreException(MessageFormat.format(
"There was an error setting the value at index: {0} to {1}", parameterIndex, x), ex);
1076 preparedStatement.setByte(parameterIndex, x);
1077 }
catch (SQLException ex) {
1078 throw new TskCoreException(MessageFormat.format(
"There was an error setting the value at index: {0} to {1}", parameterIndex, x), ex);
1092 preparedStatement.setInt(parameterIndex, x);
1093 }
catch (SQLException ex) {
1094 throw new TskCoreException(MessageFormat.format(
"There was an error setting the value at index: {0} to {1}", parameterIndex, x), ex);
1108 preparedStatement.setLong(parameterIndex, x);
1109 }
catch (SQLException ex) {
1110 throw new TskCoreException(MessageFormat.format(
"There was an error setting the value at index: {0} to {1}", parameterIndex, x), ex);
1124 preparedStatement.setDouble(parameterIndex, x);
1125 }
catch (SQLException ex) {
1126 throw new TskCoreException(MessageFormat.format(
"There was an error setting the value at index: {0} to {1}", parameterIndex, x), ex);
1140 preparedStatement.setString(parameterIndex, x);
1141 }
catch (SQLException ex) {
1142 throw new TskCoreException(MessageFormat.format(
"There was an error setting the value at index: {0} to {1}", parameterIndex, x), ex);
1156 preparedStatement.setDate(parameterIndex, x);
1157 }
catch (SQLException ex) {
1158 throw new TskCoreException(MessageFormat.format(
"There was an error setting the value at index: {0} to {1}", parameterIndex, x), ex);
1172 preparedStatement.setTime(parameterIndex, x);
1173 }
catch (SQLException ex) {
1174 throw new TskCoreException(MessageFormat.format(
"There was an error setting the value at index: {0} to {1}", parameterIndex, x), ex);
1188 preparedStatement.setTimestamp(parameterIndex, x);
1189 }
catch (SQLException ex) {
1190 throw new TskCoreException(MessageFormat.format(
"There was an error setting the value at index: {0} to {1}", parameterIndex, x), ex);
1206 preparedStatement.setObject(parameterIndex, x);
1207 }
catch (SQLException ex) {
1208 throw new TskCoreException(MessageFormat.format(
"There was an error setting the value at index: {0} to {1}", parameterIndex, x), ex);
1213 public void close() throws SQLException {
1223 CaseDbAccessManager.this.tskDB.releaseSingleUserCaseWriteLock();
1225 CaseDbAccessManager.this.tskDB.releaseSingleUserCaseReadLock();
void setBoolean(int parameterIndex, boolean x)
void setString(int parameterIndex, String x)
void setObject(int parameterIndex, Object x)
void setLong(int parameterIndex, long x)
void setTimestamp(int parameterIndex, Timestamp x)
void setDouble(int parameterIndex, double x)
void setTime(int parameterIndex, Time x)
void setDate(int parameterIndex, Date x)
void setByte(int parameterIndex, byte x)
void setInt(int parameterIndex, int x)
void createIndex(final String indexName, final String tableName, final String colsSQL)
boolean columnExists(String tableName, String columnName)
void update(final String tableName, final String sql, CaseDbTransaction transaction)
void update(CaseDbPreparedStatement preparedStatement)
long insertOrUpdate(final String tableName, final String sql)
boolean tableExists(String tableName)
void update(final String tableName, final String sql)
void select(CaseDbPreparedStatement preparedStatement, CaseDbAccessQueryCallback queryCallback)
void alterTable(final String tableName, final String alterSQL, final CaseDbTransaction transaction)
boolean tableExists(String tableName, CaseDbTransaction transaction)
CaseDbPreparedStatement prepareSelect(String sql)
void createTable(final String tableName, final String tableSchema)
void select(CaseDbPreparedStatement preparedStatement, boolean runOptimize, CaseDbAccessQueryCallback queryCallback)
void alterTable(final String tableName, final String alterSQL)
void insert(CaseDbPreparedStatement preparedStatement)
void select(final String sql, final CaseDbAccessQueryCallback queryCallback)
long insert(final String tableName, final String sql, final CaseDbTransaction transaction)
long insertOrUpdate(final String tableName, final String sql, final CaseDbTransaction transaction)
boolean columnExists(String tableName, String columnName, CaseDbTransaction transaction)
CaseDbPreparedStatement prepareInsert(String tableName, String sql, CaseDbTransaction trans)
long insert(final String tableName, final String sql)
CaseDbPreparedStatement prepareSelect(String sql, CaseDbTransaction trans)
CaseDbPreparedStatement prepareUpdate(String tableName, String sql, CaseDbTransaction trans)
void delete(final String tableName, final String sql)
void process(ResultSet resultSet)