Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.robolectric.shadows.ShadowSQLiteConnection Maven / Gradle / Ivy
package org.robolectric.shadows;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
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 org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadows.util.SQLiteLibraryLoader;
import java.io.File;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Generated;
/**
* Shadow for {@link android.database.sqlite.SQLiteConnection}.
*/
@Implements(value = android.database.sqlite.SQLiteConnection.class, isInAndroidSdk = false)
public class 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;
private static SQLiteConnection connection(final long pointer) {
return CONNECTIONS.getConnection(pointer);
}
private static SQLiteStatement stmt(final long connectionPtr, final long pointer) {
return CONNECTIONS.getStatement(connectionPtr, pointer);
}
@Implementation
public static long nativeOpen(String path, int openFlags, String label, boolean enableTrace, boolean enableProfile) {
SQLiteLibraryLoader.load();
return CONNECTIONS.open(path);
}
@Implementation
public 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
public static void nativeClose(long connectionPtr) {
CONNECTIONS.close(connectionPtr);
}
@Implementation
public static void nativeFinalizeStatement(long connectionPtr, long statementPtr) {
CONNECTIONS.finalizeStmt(connectionPtr, statementPtr);
}
@Implementation
public static int nativeGetParameterCount(final long connectionPtr, final long statementPtr) {
if (statementPtr == IGNORED_REINDEX_STMT) { return 0; }
return CONNECTIONS.execute("get parameters count in prepared statement", new Callable() {
@Override
public Integer call() throws SQLiteException {
SQLiteStatement stmt = stmt(connectionPtr, statementPtr);
return stmt.getBindParameterCount();
}
});
}
@Implementation
public static boolean nativeIsReadOnly(final long connectionPtr, final long statementPtr) {
if (statementPtr == IGNORED_REINDEX_STMT) { return true; }
return CONNECTIONS.execute("call isReadOnly", new Callable() {
@Override
public Boolean call() throws SQLiteException {
SQLiteStatement stmt = stmt(connectionPtr, statementPtr);
return stmt.isReadOnly();
}
});
}
@Implementation
public static long nativeExecuteForLong(final long connectionPtr, final long statementPtr) {
return CONNECTIONS.execute("execute for long", new Callable() {
@Override
public Long call() throws SQLiteException {
SQLiteStatement stmt = stmt(connectionPtr, statementPtr);
if (!stmt.step()) {
throw new SQLiteException(SQLiteConstants.SQLITE_DONE, "No rows returned from query");
}
return stmt.columnLong(0);
}
});
}
@Implementation
public static void nativeExecute(final long connectionPtr, final long statementPtr) {
if (statementPtr == IGNORED_REINDEX_STMT) { return; }
CONNECTIONS.execute("execute", new Callable() {
@Override
public Object call() throws SQLiteException {
SQLiteStatement stmt = stmt(connectionPtr, statementPtr);
stmt.stepThrough();
return null;
}
});
}
@Implementation
public static String nativeExecuteForString(final long connectionPtr, final long statementPtr) {
return CONNECTIONS.execute("execute for string", new Callable() {
@Override
public String call() throws SQLiteException {
SQLiteStatement stmt = stmt(connectionPtr, statementPtr);
if (!stmt.step()) {
throw new SQLiteException(SQLiteConstants.SQLITE_DONE, "No rows returned from query");
}
return stmt.columnString(0);
}
});
}
@Implementation
public static int nativeGetColumnCount(final long connectionPtr, final long statementPtr) {
return CONNECTIONS.execute("get columns count", new Callable() {
@Override
public Integer call() throws SQLiteException {
SQLiteStatement stmt = stmt(connectionPtr, statementPtr);
return stmt.columnCount();
}
});
}
@Implementation
public static String nativeGetColumnName(final long connectionPtr, final long statementPtr, final int index) {
return CONNECTIONS.execute("get column name at index " + index, new Callable() {
@Override
public String call() throws SQLiteException {
SQLiteStatement stmt = stmt(connectionPtr, statementPtr);
return stmt.getColumnName(index);
}
});
}
@Implementation
public static void nativeBindNull(final long connectionPtr, final long statementPtr, final int index) {
CONNECTIONS.execute("bind null at index " + index, new Callable() {
@Override
public Object call() throws Exception {
SQLiteStatement stmt = stmt(connectionPtr, statementPtr);
stmt.bindNull(index);
return null;
}
});
}
@Implementation
public static void nativeBindLong(final long connectionPtr, final long statementPtr, final int index, final long value) {
CONNECTIONS.execute("bind long at index " + index + " with value " + value, new Callable() {
@Override
public Object call() throws Exception {
SQLiteStatement stmt = stmt(connectionPtr, statementPtr);
stmt.bind(index, value);
return null;
}
});
}
@Implementation
public static void nativeBindDouble(final long connectionPtr, final long statementPtr, final int index, final double value) {
CONNECTIONS.execute("bind double at index " + index + " with value " + value, new Callable() {
@Override
public Object call() throws Exception {
SQLiteStatement stmt = stmt(connectionPtr, statementPtr);
stmt.bind(index, value);
return null;
}
});
}
@Implementation
public static void nativeBindString(final long connectionPtr, final long statementPtr, final int index, final String value) {
CONNECTIONS.execute("bind string at index " + index, new Callable() {
@Override
public Object call() throws Exception {
SQLiteStatement stmt = stmt(connectionPtr, statementPtr);
stmt.bind(index, value);
return null;
}
});
}
@Implementation
public static void nativeBindBlob(final long connectionPtr, final long statementPtr, final int index, final byte[] value) {
CONNECTIONS.execute("bind blob at index " + index, new Callable() {
@Override
public Object call() throws Exception {
SQLiteStatement stmt = stmt(connectionPtr, statementPtr);
stmt.bind(index, value);
return null;
}
});
}
@Implementation
public 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
public static int nativeExecuteForChangedRowCount(final long connectionPtr, final long statementPtr) {
return CONNECTIONS.execute("execute for changed row count", new Callable() {
@Override
public Integer call() throws Exception {
SQLiteStatement stmt = stmt(connectionPtr, statementPtr);
stmt.stepThrough();
return connection(connectionPtr).getChanges();
}
});
}
@Implementation
public static long nativeExecuteForLastInsertedRowId(final long connectionPtr, final long statementPtr) {
return CONNECTIONS.execute("execute for last inserted row ID", new Callable() {
@Override
public Long call() throws Exception {
SQLiteStatement stmt = stmt(connectionPtr, statementPtr);
stmt.stepThrough();
return connection(connectionPtr).getLastInsertId();
}
});
}
@Implementation
public static long nativeExecuteForCursorWindow(final long connectionPtr, final long statementPtr, final long windowPtr,
final int startPos, final int requiredPos, final boolean countAllRows) {
return CONNECTIONS.execute("execute for cursor window", new Callable() {
@Override
public Integer call() throws Exception {
SQLiteStatement stmt = stmt(connectionPtr, statementPtr);
return ShadowCursorWindow.setData(windowPtr, stmt);
}
});
}
@Implementation
public static void nativeResetStatementAndClearBindings(final long connectionPtr, final long statementPtr) {
CONNECTIONS.execute("reset statement", new Callable() {
@Override
public Object call() throws Exception {
SQLiteStatement stmt = stmt(connectionPtr, statementPtr);
stmt.reset(true);
return null;
}
});
}
@Implementation
public static void nativeCancel(long connectionPtr) {
CONNECTIONS.cancel(connectionPtr);
}
@Implementation
public static void nativeResetCancel(long connectionPtr, boolean cancelable) {
// handled in com.almworks.sqlite4java.SQLiteConnection#exec
}
@Implementation
public static void nativeRegisterCustomFunction(long connectionPtr, SQLiteCustomFunction function) {
// not supported
}
@Implementation
public static int nativeExecuteForBlobFileDescriptor(long connectionPtr, long statementPtr) {
// impossible to support without native code?
return -1;
}
@Implementation
public static int nativeGetDbLookaside(long connectionPtr) {
// not supported by sqlite4java
return 0;
}
// VisibleForTesting
static class Connections {
private final AtomicLong pointerCounter = new AtomicLong(0);
private final Map statementsMap = new ConcurrentHashMap<>();
private final Map connectionsMap = new ConcurrentHashMap<>();
private ExecutorService dbExecutor = Executors.newSingleThreadExecutor();
public SQLiteConnection getConnection(final long pointer) {
SQLiteConnection connection = connectionsMap.get(pointer);
if (connection == null) {
throw new IllegalStateException("Illegal connection pointer " + pointer
+ ". Current pointers for thread " + Thread.currentThread() + " " + connectionsMap.keySet());
}
return connection;
}
public SQLiteStatement getStatement(final long connectionPtr, final long pointer) {
// ensure connection is ok
getConnection(connectionPtr);
SQLiteStatement stmt = statementsMap.get(pointer);
if (stmt == null) {
throw new IllegalArgumentException("Invalid prepared statement pointer: " + pointer + ". Current pointers: " + statementsMap.keySet());
}
if (stmt.isDisposed()) {
throw new IllegalStateException("Statement " + pointer + " " + stmt + " is disposed");
}
return stmt;
}
public long open(final String path) {
SQLiteConnection dbConnection = execute("open SQLite connection", new Callable() {
@Override
public SQLiteConnection call() throws Exception {
SQLiteConnection connection = IN_MEMORY_PATH.equals(path)
? new SQLiteConnection()
: new SQLiteConnection(new File(path));
connection.open();
return connection;
}
});
long ptr = pointerCounter.incrementAndGet();
connectionsMap.put(ptr, dbConnection);
return ptr;
}
public long prepareStatement(final long connectionPtr, final String sql) {
// TODO: find a way to create collators
if ("REINDEX LOCALIZED".equals(sql)) {
return IGNORED_REINDEX_STMT;
}
SQLiteStatement stmt = execute("prepare statement", new Callable() {
@Override
public SQLiteStatement call() throws Exception {
SQLiteConnection connection = getConnection(connectionPtr);
return connection.prepare(sql);
}
});
long pointer = pointerCounter.incrementAndGet();
statementsMap.put(pointer, stmt);
return pointer;
}
public void close(final long ptr) {
execute("close connection", new Callable() {
@Override
public Object call() throws Exception {
SQLiteConnection connection = getConnection(ptr);
connection.dispose();
return null;
}
});
}
public void reset() {
for (long connectionPtr : connectionsMap.keySet()) {
close(connectionPtr);
}
dbExecutor.shutdown();
try {
dbExecutor.awaitTermination(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
dbExecutor = Executors.newSingleThreadExecutor();
connectionsMap.clear();
statementsMap.clear();
}
public void finalizeStmt(final long connectionPtr, final long statementPtr) {
if (statementPtr == IGNORED_REINDEX_STMT) {
return;
}
execute("finalize statement", new Callable() {
@Override
public Object call() throws Exception {
SQLiteStatement stmt = getStatement(connectionPtr, statementPtr);
statementsMap.remove(statementPtr);
stmt.dispose();
return null;
}
});
}
public void cancel(long connectionPtr) {
getConnection(connectionPtr); // check connection
execute("cancel", new Callable() {
@Override
public Object call() throws Exception {
SQLiteStatement statement = statementsMap.get(pointerCounter.get());
if (statement != null) {
statement.cancel();
}
return null;
}
});
}
public T execute(final String comment, final Callable work) {
try {
return Uninterruptibles.getUninterruptibly(dbExecutor.submit(work));
// No need to catch cancellationexception - we never cancel these futures
} catch (ExecutionException e) {
Throwable t = e.getCause();
if (t instanceof SQLiteException) {
RuntimeException sqlException = getSqliteException("Cannot " + comment,
((SQLiteException) t).getBaseErrorCode());
sqlException.initCause(e);
throw sqlException;
} else {
throw new RuntimeException(e);
}
}
}
private RuntimeException getSqliteException(String message, 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);
}
}
}
}