
org.robolectric.shadows.ShadowLegacySQLiteConnection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of shadows-framework Show documentation
Show all versions of shadows-framework Show documentation
An alternative Android testing framework.
The newest version!
package org.robolectric.shadows;
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 android.os.Build.VERSION_CODES.TIRAMISU;
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.base.MoreObjects;
import com.google.common.collect.ImmutableMap;
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 long nativeOpen(
String path, int openFlags, String label, boolean enableTrace, boolean enableProfile) {
SQLiteLibraryLoader.load();
return 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);
}
@Implementation
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
protected static void nativeClose(long connectionPtr) {
CONNECTIONS.close(connectionPtr);
}
@Implementation
protected static void nativeFinalizeStatement(long connectionPtr, long statementPtr) {
CONNECTIONS.finalizeStmt(connectionPtr, statementPtr);
}
@Implementation
protected static int nativeGetParameterCount(final long connectionPtr, final long statementPtr) {
return CONNECTIONS.getParameterCount(connectionPtr, statementPtr);
}
@Implementation
protected static boolean nativeIsReadOnly(final long connectionPtr, final long statementPtr) {
return CONNECTIONS.isReadOnly(connectionPtr, statementPtr);
}
@Implementation
protected static long nativeExecuteForLong(final long connectionPtr, final long statementPtr) {
return CONNECTIONS.executeForLong(connectionPtr, statementPtr);
}
@Implementation(maxSdk = S_V2)
protected static void nativeExecute(final long connectionPtr, final long statementPtr) {
CONNECTIONS.executeStatement(connectionPtr, statementPtr);
}
@Implementation(minSdk = TIRAMISU)
protected static void nativeExecute(
final long connectionPtr, final long statementPtr, boolean isPragmaStmt) {
CONNECTIONS.executeStatement(connectionPtr, statementPtr);
}
@Implementation
protected static String nativeExecuteForString(
final long connectionPtr, final long statementPtr) {
return CONNECTIONS.executeForString(connectionPtr, statementPtr);
}
@Implementation
protected static int nativeGetColumnCount(final long connectionPtr, final long statementPtr) {
return CONNECTIONS.getColumnCount(connectionPtr, statementPtr);
}
@Implementation
protected static String nativeGetColumnName(
final long connectionPtr, final long statementPtr, final int index) {
return CONNECTIONS.getColumnName(connectionPtr, statementPtr, index);
}
@Implementation
protected static void nativeBindNull(
final long connectionPtr, final long statementPtr, final int index) {
CONNECTIONS.bindNull(connectionPtr, statementPtr, index);
}
@Implementation
protected static void nativeBindLong(
final long connectionPtr, final long statementPtr, final int index, final long value) {
CONNECTIONS.bindLong(connectionPtr, statementPtr, index, value);
}
@Implementation
protected static void nativeBindDouble(
final long connectionPtr, final long statementPtr, final int index, final double value) {
CONNECTIONS.bindDouble(connectionPtr, statementPtr, index, value);
}
@Implementation
protected static void nativeBindString(
final long connectionPtr, final long statementPtr, final int index, final String value) {
CONNECTIONS.bindString(connectionPtr, statementPtr, index, value);
}
@Implementation
protected static void nativeBindBlob(
final long connectionPtr, final long statementPtr, final int index, final byte[] value) {
CONNECTIONS.bindBlob(connectionPtr, statementPtr, index, value);
}
@Implementation
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
protected static int nativeExecuteForChangedRowCount(
final long connectionPtr, final long statementPtr) {
return CONNECTIONS.executeForChangedRowCount(connectionPtr, statementPtr);
}
@Implementation
protected static long nativeExecuteForLastInsertedRowId(
final long connectionPtr, final long statementPtr) {
return CONNECTIONS.executeForLastInsertedRowId(connectionPtr, statementPtr);
}
@Implementation
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
protected static void nativeResetStatementAndClearBindings(
final long connectionPtr, final long statementPtr) {
CONNECTIONS.resetStatementAndClearBindings(connectionPtr, statementPtr);
}
@Implementation
protected static void nativeCancel(long connectionPtr) {
CONNECTIONS.cancel(connectionPtr);
}
@Implementation
protected static void nativeResetCancel(long connectionPtr, boolean cancelable) {
// handled in com.almworks.sqlite4java.SQLiteConnection#exec
}
@Implementation(maxSdk = Q)
protected static void nativeRegisterCustomFunction(
long connectionPtr, SQLiteCustomFunction function) {
// not supported
}
@Implementation
protected static int nativeExecuteForBlobFileDescriptor(long connectionPtr, long statementPtr) {
// impossible to support without native code?
return -1;
}
@Implementation
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(
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(
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(
() -> {
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(
executorService.submit(
() -> {
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(
() -> {
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(
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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(
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(
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,
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,
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 StatementOperation statementOperation) {
synchronized (lock) {
final SQLiteStatement statement = getStatement(connectionPtr, statementPtr);
return execute(
() -> {
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 Callable work) {
synchronized (lock) {
return PerfStatsCollector.getInstance()
.measure("sqlite", () -> getFuture(dbExecutor.submit(work)));
}
}
private static T getFuture(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) {
SQLiteException sqliteException = (SQLiteException) t;
final RuntimeException sqlException =
getSqliteException(sqliteException.getMessage(), sqliteException.getErrorCode());
sqlException.initCause(e);
throw sqlException;
} else if (t instanceof android.database.sqlite.SQLiteException) {
throw (android.database.sqlite.SQLiteException) t;
} else {
throw new RuntimeException(e);
}
}
}
// These are from android_database_SQLiteCommon.cpp
private static final ImmutableMap ERROR_CODE_MAP =
new ImmutableMap.Builder()
.put(4, "SQLITE_ABORT")
.put(23, "SQLITE_AUTH")
.put(5, "SQLITE_BUSY")
.put(14, "SQLITE_CANTOPEN")
.put(19, "SQLITE_CONSTRAINT")
.put(11, "SQLITE_CORRUPT")
.put(101, "SQLITE_DONE")
.put(16, "SQLITE_EMPTY")
.put(1, "SQLITE_ERROR")
.put(24, "SQLITE_FORMAT")
.put(13, "SQLITE_FULL")
.put(2, "SQLITE_INTERNAL")
.put(9, "SQLITE_INTERRUPT")
.put(10, "SQLITE_IOERR")
.put(6, "SQLITE_LOCKED")
.put(20, "SQLITE_MISMATCH")
.put(21, "SQLITE_MISUSE")
.put(22, "SQLITE_NOLFS")
.put(7, "SQLITE_NOMEM")
.put(26, "SQLITE_NOTADB")
.put(12, "SQLITE_NOTFOUND")
.put(27, "SQLITE_NOTICE")
.put(0, "SQLITE_OK")
.put(3, "SQLITE_PERM")
.put(15, "SQLITE_PROTOCOL")
.put(25, "SQLITE_RANGE")
.put(8, "SQLITE_READONLY")
.put(100, "SQLITE_ROW")
.put(17, "SQLITE_SCHEMA")
.put(18, "SQLITE_TOOBIG")
.put(28, "SQLITE_WARNING")
// Extended Result Code List
.put(516, "SQLITE_ABORT_ROLLBACK")
.put(261, "SQLITE_BUSY_RECOVERY")
.put(517, "SQLITE_BUSY_SNAPSHOT")
.put(1038, "SQLITE_CANTOPEN_CONVPATH")
.put(782, "SQLITE_CANTOPEN_FULLPATH")
.put(526, "SQLITE_CANTOPEN_ISDIR")
.put(270, "SQLITE_CANTOPEN_NOTEMPDIR")
.put(275, "SQLITE_CONSTRAINT_CHECK")
.put(531, "SQLITE_CONSTRAINT_COMMITHOOK")
.put(787, "SQLITE_CONSTRAINT_FOREIGNKEY")
.put(1043, "SQLITE_CONSTRAINT_FUNCTION")
.put(1299, "SQLITE_CONSTRAINT_NOTNULL")
.put(1555, "SQLITE_CONSTRAINT_PRIMARYKEY")
.put(2579, "SQLITE_CONSTRAINT_ROWID")
.put(1811, "SQLITE_CONSTRAINT_TRIGGER")
.put(2067, "SQLITE_CONSTRAINT_UNIQUE")
.put(2323, "SQLITE_CONSTRAINT_VTAB")
.put(267, "SQLITE_CORRUPT_VTAB")
.put(3338, "SQLITE_IOERR_ACCESS")
.put(2826, "SQLITE_IOERR_BLOCKED")
.put(3594, "SQLITE_IOERR_CHECKRESERVEDLOCK")
.put(4106, "SQLITE_IOERR_CLOSE")
.put(6666, "SQLITE_IOERR_CONVPATH")
.put(2570, "SQLITE_IOERR_DELETE")
.put(5898, "SQLITE_IOERR_DELETE_NOENT")
.put(4362, "SQLITE_IOERR_DIR_CLOSE")
.put(1290, "SQLITE_IOERR_DIR_FSYNC")
.put(1802, "SQLITE_IOERR_FSTAT")
.put(1034, "SQLITE_IOERR_FSYNC")
.put(6410, "SQLITE_IOERR_GETTEMPPATH")
.put(3850, "SQLITE_IOERR_LOCK")
.put(6154, "SQLITE_IOERR_MMAP")
.put(3082, "SQLITE_IOERR_NOMEM")
.put(2314, "SQLITE_IOERR_RDLOCK")
.put(266, "SQLITE_IOERR_READ")
.put(5642, "SQLITE_IOERR_SEEK")
.put(5130, "SQLITE_IOERR_SHMLOCK")
.put(5386, "SQLITE_IOERR_SHMMAP")
.put(4618, "SQLITE_IOERR_SHMOPEN")
.put(4874, "SQLITE_IOERR_SHMSIZE")
.put(522, "SQLITE_IOERR_SHORT_READ")
.put(1546, "SQLITE_IOERR_TRUNCATE")
.put(2058, "SQLITE_IOERR_UNLOCK")
.put(778, "SQLITE_IOERR_WRITE")
.put(262, "SQLITE_LOCKED_SHAREDCACHE")
.put(539, "SQLITE_NOTICE_RECOVER_ROLLBACK")
.put(283, "SQLITE_NOTICE_RECOVER_WAL")
.put(256, "SQLITE_OK_LOAD_PERMANENTLY")
.put(520, "SQLITE_READONLY_CANTLOCK")
.put(1032, "SQLITE_READONLY_DBMOVED")
.put(264, "SQLITE_READONLY_RECOVERY")
.put(776, "SQLITE_READONLY_ROLLBACK")
.put(284, "SQLITE_WARNING_AUTOINDEX")
.build();
private static RuntimeException getSqliteException(
final String sqliteErrorMessage, final int errorCode) {
final int baseErrorCode = errorCode & 0xff;
// Remove redundant error code prefix from sqlite4java. The error code is added
// as a suffix below.
String errorMessageWithoutCode = sqliteErrorMessage.replaceAll("^\\[\\d+\\] ?", "");
StringBuilder fullMessage = new StringBuilder(errorMessageWithoutCode);
fullMessage.append(" (code ");
fullMessage.append(errorCode);
String errorCodeMessage = ERROR_CODE_MAP.getOrDefault(errorCode, "");
if (MoreObjects.firstNonNull(errorCodeMessage, "").length() > 0) {
fullMessage.append(" ").append(errorCodeMessage);
}
fullMessage.append(")");
String message = fullMessage.toString();
// 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