org.robolectric.shadows.ShadowLegacySQLiteConnection Maven / Gradle / Ivy
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.S_V2;
import static org.robolectric.RuntimeEnvironment.castNativePtr;
import android.database.sqlite.SQLiteAbortException;
import android.database.sqlite.SQLiteAccessPermException;
import android.database.sqlite.SQLiteBindOrColumnIndexOutOfRangeException;
import android.database.sqlite.SQLiteBlobTooBigException;
import android.database.sqlite.SQLiteCantOpenDatabaseException;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteCustomFunction;
import android.database.sqlite.SQLiteDatabaseCorruptException;
import android.database.sqlite.SQLiteDatabaseLockedException;
import android.database.sqlite.SQLiteDatatypeMismatchException;
import android.database.sqlite.SQLiteDiskIOException;
import android.database.sqlite.SQLiteDoneException;
import android.database.sqlite.SQLiteFullException;
import android.database.sqlite.SQLiteMisuseException;
import android.database.sqlite.SQLiteOutOfMemoryException;
import android.database.sqlite.SQLiteReadOnlyDatabaseException;
import android.database.sqlite.SQLiteTableLockedException;
import android.os.OperationCanceledException;
import com.almworks.sqlite4java.SQLiteConnection;
import com.almworks.sqlite4java.SQLiteConstants;
import com.almworks.sqlite4java.SQLiteException;
import com.almworks.sqlite4java.SQLiteStatement;
import com.google.common.util.concurrent.Uninterruptibles;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadows.util.SQLiteLibraryLoader;
import org.robolectric.util.PerfStatsCollector;
/** Shadow for {@link android.database.sqlite.SQLiteConnection} that is backed by sqlite4java. */
@Implements(value = android.database.sqlite.SQLiteConnection.class, isInAndroidSdk = false)
public class ShadowLegacySQLiteConnection extends ShadowSQLiteConnection {
private static final String IN_MEMORY_PATH = ":memory:";
private static final Connections CONNECTIONS = new Connections();
private static final Pattern COLLATE_LOCALIZED_UNICODE_PATTERN =
Pattern.compile("\\s+COLLATE\\s+(LOCALIZED|UNICODE)", Pattern.CASE_INSENSITIVE);
// indicates an ignored statement
private static final int IGNORED_REINDEX_STMT = -2;
@Implementation(maxSdk = O)
protected static Number nativeOpen(
String path, int openFlags, String label, boolean enableTrace, boolean enableProfile) {
SQLiteLibraryLoader.load();
return castNativePtr(CONNECTIONS.open(path));
}
@Implementation(minSdk = O_MR1)
protected static long nativeOpen(
String path,
int openFlags,
String label,
boolean enableTrace,
boolean enableProfile,
int lookasideSlotSize,
int lookasideSlotCount) {
return nativeOpen(path, openFlags, label, enableTrace, enableProfile).longValue();
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static int nativePrepareStatement(int connectionPtr, String sql) {
return (int) nativePrepareStatement((long) connectionPtr, sql);
}
@Implementation(minSdk = LOLLIPOP)
protected static long nativePrepareStatement(long connectionPtr, String sql) {
final String newSql = convertSQLWithLocalizedUnicodeCollator(sql);
return CONNECTIONS.prepareStatement(connectionPtr, newSql);
}
/**
* Convert SQL with phrase COLLATE LOCALIZED or COLLATE UNICODE to COLLATE NOCASE.
*/
static String convertSQLWithLocalizedUnicodeCollator(String sql) {
Matcher matcher = COLLATE_LOCALIZED_UNICODE_PATTERN.matcher(sql);
return matcher.replaceAll(" COLLATE NOCASE");
}
@Resetter
public static void reset() {
CONNECTIONS.reset();
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static void nativeClose(int connectionPtr) {
nativeClose((long) connectionPtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeClose(long connectionPtr) {
CONNECTIONS.close(connectionPtr);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static void nativeFinalizeStatement(int connectionPtr, int statementPtr) {
nativeFinalizeStatement((long) connectionPtr, statementPtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeFinalizeStatement(long connectionPtr, long statementPtr) {
CONNECTIONS.finalizeStmt(connectionPtr, statementPtr);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static int nativeGetParameterCount(int connectionPtr, int statementPtr) {
return nativeGetParameterCount((long) connectionPtr, statementPtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static int nativeGetParameterCount(final long connectionPtr, final long statementPtr) {
return CONNECTIONS.getParameterCount(connectionPtr, statementPtr);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static boolean nativeIsReadOnly(int connectionPtr, int statementPtr) {
return nativeIsReadOnly((long) connectionPtr, (long) statementPtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static boolean nativeIsReadOnly(final long connectionPtr, final long statementPtr) {
return CONNECTIONS.isReadOnly(connectionPtr, statementPtr);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static long nativeExecuteForLong(int connectionPtr, int statementPtr) {
return nativeExecuteForLong((long) connectionPtr, (long) statementPtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static long nativeExecuteForLong(final long connectionPtr, final long statementPtr) {
return CONNECTIONS.executeForLong(connectionPtr, statementPtr);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static void nativeExecute(int connectionPtr, int statementPtr) {
nativeExecute((long) connectionPtr, (long) statementPtr);
}
@Implementation(minSdk = LOLLIPOP, maxSdk = S_V2)
protected static void nativeExecute(final long connectionPtr, final long statementPtr) {
CONNECTIONS.executeStatement(connectionPtr, statementPtr);
}
@Implementation(minSdk = 33)
protected static void nativeExecute(
final long connectionPtr, final long statementPtr, boolean isPragmaStmt) {
CONNECTIONS.executeStatement(connectionPtr, statementPtr);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static String nativeExecuteForString(int connectionPtr, int statementPtr) {
return nativeExecuteForString((long) connectionPtr, (long) statementPtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static String nativeExecuteForString(
final long connectionPtr, final long statementPtr) {
return CONNECTIONS.executeForString(connectionPtr, statementPtr);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static int nativeGetColumnCount(int connectionPtr, int statementPtr) {
return nativeGetColumnCount((long) connectionPtr, (long) statementPtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static int nativeGetColumnCount(final long connectionPtr, final long statementPtr) {
return CONNECTIONS.getColumnCount(connectionPtr, statementPtr);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static String nativeGetColumnName(int connectionPtr, int statementPtr, int index) {
return nativeGetColumnName((long) connectionPtr, (long) statementPtr, index);
}
@Implementation(minSdk = LOLLIPOP)
protected static String nativeGetColumnName(
final long connectionPtr, final long statementPtr, final int index) {
return CONNECTIONS.getColumnName(connectionPtr, statementPtr, index);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static void nativeBindNull(int connectionPtr, int statementPtr, int index) {
nativeBindNull((long) connectionPtr, (long) statementPtr, index);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeBindNull(
final long connectionPtr, final long statementPtr, final int index) {
CONNECTIONS.bindNull(connectionPtr, statementPtr, index);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static void nativeBindLong(int connectionPtr, int statementPtr, int index, long value) {
nativeBindLong((long) connectionPtr, (long) statementPtr, index, value);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeBindLong(
final long connectionPtr, final long statementPtr, final int index, final long value) {
CONNECTIONS.bindLong(connectionPtr, statementPtr, index, value);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static void nativeBindDouble(
int connectionPtr, int statementPtr, int index, double value) {
nativeBindDouble((long) connectionPtr, (long) statementPtr, index, value);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeBindDouble(
final long connectionPtr, final long statementPtr, final int index, final double value) {
CONNECTIONS.bindDouble(connectionPtr, statementPtr, index, value);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static void nativeBindString(
int connectionPtr, int statementPtr, int index, String value) {
nativeBindString((long) connectionPtr, (long) statementPtr, index, value);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeBindString(
final long connectionPtr, final long statementPtr, final int index, final String value) {
CONNECTIONS.bindString(connectionPtr, statementPtr, index, value);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static void nativeBindBlob(
int connectionPtr, int statementPtr, int index, byte[] value) {
nativeBindBlob((long) connectionPtr, (long) statementPtr, index, value);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeBindBlob(
final long connectionPtr, final long statementPtr, final int index, final byte[] value) {
CONNECTIONS.bindBlob(connectionPtr, statementPtr, index, value);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static void nativeRegisterLocalizedCollators(int connectionPtr, String locale) {
nativeRegisterLocalizedCollators((long) connectionPtr, locale);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeRegisterLocalizedCollators(long connectionPtr, String locale) {
// TODO: find a way to create a collator
// http://www.sqlite.org/c3ref/create_collation.html
// xerial jdbc driver does not have a Java method for sqlite3_create_collation
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static int nativeExecuteForChangedRowCount(int connectionPtr, int statementPtr) {
return nativeExecuteForChangedRowCount((long) connectionPtr, (long) statementPtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static int nativeExecuteForChangedRowCount(
final long connectionPtr, final long statementPtr) {
return CONNECTIONS.executeForChangedRowCount(connectionPtr, statementPtr);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static long nativeExecuteForLastInsertedRowId(int connectionPtr, int statementPtr) {
return nativeExecuteForLastInsertedRowId((long) connectionPtr, (long) statementPtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static long nativeExecuteForLastInsertedRowId(
final long connectionPtr, final long statementPtr) {
return CONNECTIONS.executeForLastInsertedRowId(connectionPtr, statementPtr);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static long nativeExecuteForCursorWindow(
int connectionPtr,
int statementPtr,
int windowPtr,
int startPos,
int requiredPos,
boolean countAllRows) {
return nativeExecuteForCursorWindow((long) connectionPtr, (long) statementPtr, (long) windowPtr,
startPos, requiredPos, countAllRows);
}
@Implementation(minSdk = LOLLIPOP)
protected static long nativeExecuteForCursorWindow(
final long connectionPtr,
final long statementPtr,
final long windowPtr,
final int startPos,
final int requiredPos,
final boolean countAllRows) {
return CONNECTIONS.executeForCursorWindow(connectionPtr, statementPtr, windowPtr);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static void nativeResetStatementAndClearBindings(int connectionPtr, int statementPtr) {
nativeResetStatementAndClearBindings((long) connectionPtr, (long) statementPtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeResetStatementAndClearBindings(
final long connectionPtr, final long statementPtr) {
CONNECTIONS.resetStatementAndClearBindings(connectionPtr, statementPtr);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static void nativeCancel(int connectionPtr) {
nativeCancel((long) connectionPtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeCancel(long connectionPtr) {
CONNECTIONS.cancel(connectionPtr);
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static void nativeResetCancel(int connectionPtr, boolean cancelable) {
nativeResetCancel((long) connectionPtr, cancelable);
}
@Implementation(minSdk = LOLLIPOP)
protected static void nativeResetCancel(long connectionPtr, boolean cancelable) {
// handled in com.almworks.sqlite4java.SQLiteConnection#exec
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static void nativeRegisterCustomFunction(
int connectionPtr, SQLiteCustomFunction function) {
nativeRegisterCustomFunction((long) connectionPtr, function);
}
@Implementation(minSdk = LOLLIPOP, maxSdk = Q)
protected static void nativeRegisterCustomFunction(
long connectionPtr, SQLiteCustomFunction function) {
// not supported
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static int nativeExecuteForBlobFileDescriptor(int connectionPtr, int statementPtr) {
return nativeExecuteForBlobFileDescriptor((long) connectionPtr, (long) statementPtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static int nativeExecuteForBlobFileDescriptor(long connectionPtr, long statementPtr) {
// impossible to support without native code?
return -1;
}
@Implementation(maxSdk = KITKAT_WATCH)
protected static int nativeGetDbLookaside(int connectionPtr) {
return nativeGetDbLookaside((long) connectionPtr);
}
@Implementation(minSdk = LOLLIPOP)
protected static int nativeGetDbLookaside(long connectionPtr) {
// not supported by sqlite4java
return 0;
}
// VisibleForTesting
static class Connections {
private final Object lock = new Object();
private final AtomicLong pointerCounter = new AtomicLong(0);
private final Map statementsMap = new HashMap<>();
private final Map connectionsMap = new HashMap<>();
private final Map> statementPtrsForConnection = new HashMap<>();
private ExecutorService dbExecutor = Executors.newSingleThreadExecutor(threadFactory());
static ThreadFactory threadFactory() {
ThreadFactory delegate = Executors.defaultThreadFactory();
return r -> {
Thread worker = delegate.newThread(r);
worker.setName(ShadowLegacySQLiteConnection.class.getSimpleName() + " worker");
return worker;
};
}
SQLiteConnection getConnection(final long connectionPtr) {
synchronized (lock) {
final SQLiteConnection connection = connectionsMap.get(connectionPtr);
if (connection == null) {
throw new IllegalStateException(
"Illegal connection pointer "
+ connectionPtr
+ ". Current pointers for thread "
+ Thread.currentThread()
+ " "
+ connectionsMap.keySet());
}
return connection;
}
}
SQLiteStatement getStatement(final long connectionPtr, final long statementPtr) {
synchronized (lock) {
// ensure connection is ok
getConnection(connectionPtr);
final SQLiteStatement statement = statementsMap.get(statementPtr);
if (statement == null) {
throw new IllegalArgumentException(
"Invalid prepared statement pointer: "
+ statementPtr
+ ". Current pointers: "
+ statementsMap.keySet());
}
if (statement.isDisposed()) {
throw new IllegalStateException(
"Statement " + statementPtr + " " + statement + " is disposed");
}
return statement;
}
}
long open(final String path) {
synchronized (lock) {
final SQLiteConnection dbConnection =
execute(
"open SQLite connection",
new Callable() {
@Override
public SQLiteConnection call() throws Exception {
SQLiteConnection connection =
useInMemoryDatabase.get() || IN_MEMORY_PATH.equals(path)
? new SQLiteConnection()
: new SQLiteConnection(new File(path));
connection.open();
return connection;
}
});
final long connectionPtr = pointerCounter.incrementAndGet();
connectionsMap.put(connectionPtr, dbConnection);
statementPtrsForConnection.put(connectionPtr, new ArrayList<>());
return connectionPtr;
}
}
long prepareStatement(final long connectionPtr, final String sql) {
// TODO: find a way to create collators
if ("REINDEX LOCALIZED".equals(sql)) {
return IGNORED_REINDEX_STMT;
}
synchronized (lock) {
final SQLiteConnection connection = getConnection(connectionPtr);
final SQLiteStatement statement =
execute(
"prepare statement",
new Callable() {
@Override
public SQLiteStatement call() throws Exception {
return connection.prepare(sql);
}
});
final long statementPtr = pointerCounter.incrementAndGet();
statementsMap.put(statementPtr, statement);
statementPtrsForConnection.get(connectionPtr).add(statementPtr);
return statementPtr;
}
}
void close(final long connectionPtr) {
synchronized (lock) {
final SQLiteConnection connection = getConnection(connectionPtr);
execute("close connection", new Callable() {
@Override
public Void call() throws Exception {
connection.dispose();
return null;
}
});
connectionsMap.remove(connectionPtr);
statementPtrsForConnection.remove(connectionPtr);
}
}
void reset() {
ExecutorService oldDbExecutor;
Collection openConnections;
synchronized (lock) {
oldDbExecutor = dbExecutor;
openConnections = new ArrayList<>(connectionsMap.values());
dbExecutor = Executors.newSingleThreadExecutor(threadFactory());
connectionsMap.clear();
statementsMap.clear();
statementPtrsForConnection.clear();
}
shutdownDbExecutor(oldDbExecutor, openConnections);
}
private static void shutdownDbExecutor(ExecutorService executorService, Collection connections) {
for (final SQLiteConnection connection : connections) {
getFuture("close connection on reset", executorService.submit(new Callable() {
@Override
public Void call() throws Exception {
connection.dispose();
return null;
}
}));
}
executorService.shutdown();
try {
executorService.awaitTermination(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
void finalizeStmt(final long connectionPtr, final long statementPtr) {
if (statementPtr == IGNORED_REINDEX_STMT) {
return;
}
synchronized (lock) {
final SQLiteStatement statement = getStatement(connectionPtr, statementPtr);
statementsMap.remove(statementPtr);
execute("finalize statement", new Callable() {
@Override
public Void call() throws Exception {
statement.dispose();
return null;
}
});
}
}
void cancel(final long connectionPtr) {
synchronized (lock) {
getConnection(connectionPtr); // check connection
for (Long statementPtr : statementPtrsForConnection.get(connectionPtr)) {
final SQLiteStatement statement = statementsMap.get(statementPtr);
if (statement != null) {
execute("cancel", new Callable() {
@Override
public Void call() throws Exception {
statement.cancel();
return null;
}
});
}
}
}
}
int getParameterCount(final long connectionPtr, final long statementPtr) {
if (statementPtr == IGNORED_REINDEX_STMT) {
return 0;
}
return executeStatementOperation(
connectionPtr,
statementPtr,
"get parameters count in prepared statement",
new StatementOperation() {
@Override
public Integer call(final SQLiteStatement statement) throws Exception {
return statement.getBindParameterCount();
}
});
}
boolean isReadOnly(final long connectionPtr, final long statementPtr) {
if (statementPtr == IGNORED_REINDEX_STMT) {
return true;
}
return executeStatementOperation(
connectionPtr,
statementPtr,
"call isReadOnly",
new StatementOperation() {
@Override
public Boolean call(final SQLiteStatement statement) throws Exception {
return statement.isReadOnly();
}
});
}
long executeForLong(final long connectionPtr, final long statementPtr) {
return executeStatementOperation(
connectionPtr,
statementPtr,
"execute for long",
new StatementOperation() {
@Override
public Long call(final SQLiteStatement statement) throws Exception {
if (!statement.step()) {
throw new SQLiteException(
SQLiteConstants.SQLITE_DONE, "No rows returned from query");
}
return statement.columnLong(0);
}
});
}
void executeStatement(final long connectionPtr, final long statementPtr) {
if (statementPtr == IGNORED_REINDEX_STMT) {
return;
}
executeStatementOperation(
connectionPtr,
statementPtr,
"execute",
new StatementOperation() {
@Override
public Void call(final SQLiteStatement statement) throws Exception {
statement.stepThrough();
return null;
}
});
}
String executeForString(final long connectionPtr, final long statementPtr) {
return executeStatementOperation(
connectionPtr,
statementPtr,
"execute for string",
new StatementOperation() {
@Override
public String call(final SQLiteStatement statement) throws Exception {
if (!statement.step()) {
throw new SQLiteException(
SQLiteConstants.SQLITE_DONE, "No rows returned from query");
}
return statement.columnString(0);
}
});
}
int getColumnCount(final long connectionPtr, final long statementPtr) {
return executeStatementOperation(
connectionPtr,
statementPtr,
"get columns count",
new StatementOperation() {
@Override
public Integer call(final SQLiteStatement statement) throws Exception {
return statement.columnCount();
}
});
}
String getColumnName(final long connectionPtr, final long statementPtr, final int index) {
return executeStatementOperation(
connectionPtr,
statementPtr,
"get column name at index " + index,
new StatementOperation() {
@Override
public String call(final SQLiteStatement statement) throws Exception {
return statement.getColumnName(index);
}
});
}
void bindNull(final long connectionPtr, final long statementPtr, final int index) {
executeStatementOperation(
connectionPtr,
statementPtr,
"bind null at index " + index,
new StatementOperation() {
@Override
public Void call(final SQLiteStatement statement) throws Exception {
statement.bindNull(index);
return null;
}
});
}
void bindLong(final long connectionPtr, final long statementPtr, final int index, final long value) {
executeStatementOperation(
connectionPtr,
statementPtr,
"bind long at index " + index + " with value " + value,
new StatementOperation() {
@Override
public Void call(final SQLiteStatement statement) throws Exception {
statement.bind(index, value);
return null;
}
});
}
void bindDouble(final long connectionPtr, final long statementPtr, final int index, final double value) {
executeStatementOperation(
connectionPtr,
statementPtr,
"bind double at index " + index + " with value " + value,
new StatementOperation() {
@Override
public Void call(final SQLiteStatement statement) throws Exception {
statement.bind(index, value);
return null;
}
});
}
void bindString(final long connectionPtr, final long statementPtr, final int index, final String value) {
executeStatementOperation(
connectionPtr,
statementPtr,
"bind string at index " + index,
new StatementOperation() {
@Override
public Void call(final SQLiteStatement statement) throws Exception {
statement.bind(index, value);
return null;
}
});
}
void bindBlob(final long connectionPtr, final long statementPtr, final int index, final byte[] value) {
executeStatementOperation(
connectionPtr,
statementPtr,
"bind blob at index " + index,
new StatementOperation() {
@Override
public Void call(final SQLiteStatement statement) throws Exception {
statement.bind(index, value);
return null;
}
});
}
int executeForChangedRowCount(final long connectionPtr, final long statementPtr) {
synchronized (lock) {
final SQLiteConnection connection = getConnection(connectionPtr);
final SQLiteStatement statement = getStatement(connectionPtr, statementPtr);
return execute(
"execute for changed row count",
new Callable() {
@Override
public Integer call() throws Exception {
if (statement.step()) {
throw new android.database.sqlite.SQLiteException(
"Queries can be performed using SQLiteDatabase query or rawQuery methods"
+ " only.");
}
return connection.getChanges();
}
});
}
}
long executeForLastInsertedRowId(final long connectionPtr, final long statementPtr) {
synchronized (lock) {
final SQLiteConnection connection = getConnection(connectionPtr);
final SQLiteStatement statement = getStatement(connectionPtr, statementPtr);
return execute(
"execute for last inserted row ID",
new Callable() {
@Override
public Long call() throws Exception {
statement.stepThrough();
return connection.getChanges() > 0 ? connection.getLastInsertId() : -1L;
}
});
}
}
long executeForCursorWindow(final long connectionPtr, final long statementPtr, final long windowPtr) {
return executeStatementOperation(
connectionPtr,
statementPtr,
"execute for cursor window",
new StatementOperation() {
@Override
public Integer call(final SQLiteStatement statement) throws Exception {
return ShadowLegacyCursorWindow.setData(windowPtr, statement);
}
});
}
void resetStatementAndClearBindings(final long connectionPtr, final long statementPtr) {
executeStatementOperation(
connectionPtr,
statementPtr,
"reset statement",
new StatementOperation() {
@Override
public Void call(final SQLiteStatement statement) throws Exception {
statement.reset(true);
return null;
}
});
}
interface StatementOperation {
T call(final SQLiteStatement statement) throws Exception;
}
private T executeStatementOperation(final long connectionPtr,
final long statementPtr,
final String comment,
final StatementOperation statementOperation) {
synchronized (lock) {
final SQLiteStatement statement = getStatement(connectionPtr, statementPtr);
return execute(comment, new Callable() {
@Override
public T call() throws Exception {
return statementOperation.call(statement);
}
});
}
}
/**
* Any Callable passed in to execute must not synchronize on lock, as this will result in a deadlock
*/
private T execute(final String comment, final Callable work) {
synchronized (lock) {
return PerfStatsCollector.getInstance()
.measure("sqlite", () -> getFuture(comment, dbExecutor.submit(work)));
}
}
private static T getFuture(final String comment, final Future future) {
try {
return Uninterruptibles.getUninterruptibly(future);
// No need to catch cancellationexception - we never cancel these futures
} catch (ExecutionException e) {
Throwable t = e.getCause();
if (t instanceof SQLiteException) {
final RuntimeException sqlException =
getSqliteException("Cannot " + comment, ((SQLiteException) t).getBaseErrorCode());
sqlException.initCause(e);
throw sqlException;
} else if (t instanceof android.database.sqlite.SQLiteException) {
throw (android.database.sqlite.SQLiteException) t;
} else {
throw new RuntimeException(e);
}
}
}
private static RuntimeException getSqliteException(final String message, final int baseErrorCode) {
// Mapping is from throw_sqlite3_exception in android_database_SQLiteCommon.cpp
switch (baseErrorCode) {
case SQLiteConstants.SQLITE_ABORT: return new SQLiteAbortException(message);
case SQLiteConstants.SQLITE_PERM: return new SQLiteAccessPermException(message);
case SQLiteConstants.SQLITE_RANGE: return new SQLiteBindOrColumnIndexOutOfRangeException(message);
case SQLiteConstants.SQLITE_TOOBIG: return new SQLiteBlobTooBigException(message);
case SQLiteConstants.SQLITE_CANTOPEN: return new SQLiteCantOpenDatabaseException(message);
case SQLiteConstants.SQLITE_CONSTRAINT: return new SQLiteConstraintException(message);
case SQLiteConstants.SQLITE_NOTADB: // fall through
case SQLiteConstants.SQLITE_CORRUPT: return new SQLiteDatabaseCorruptException(message);
case SQLiteConstants.SQLITE_BUSY: return new SQLiteDatabaseLockedException(message);
case SQLiteConstants.SQLITE_MISMATCH: return new SQLiteDatatypeMismatchException(message);
case SQLiteConstants.SQLITE_IOERR: return new SQLiteDiskIOException(message);
case SQLiteConstants.SQLITE_DONE: return new SQLiteDoneException(message);
case SQLiteConstants.SQLITE_FULL: return new SQLiteFullException(message);
case SQLiteConstants.SQLITE_MISUSE: return new SQLiteMisuseException(message);
case SQLiteConstants.SQLITE_NOMEM: return new SQLiteOutOfMemoryException(message);
case SQLiteConstants.SQLITE_READONLY: return new SQLiteReadOnlyDatabaseException(message);
case SQLiteConstants.SQLITE_LOCKED: return new SQLiteTableLockedException(message);
case SQLiteConstants.SQLITE_INTERRUPT: return new OperationCanceledException(message);
default: return new android.database.sqlite.SQLiteException(message
+ ", base error code: " + baseErrorCode);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy