Build requery/sqlite inline

pull/209/head
M66B 2 years ago
parent f576d6c8c1
commit 41e8c3b950

@ -468,7 +468,8 @@ dependencies {
// https://www.sqlite.org/changes.html
// https://github.com/requery/sqlite-android/
// https://jitpack.io/#requery/sqlite-android
implementation "com.github.requery:sqlite-android:$requery_version"
//implementation "com.github.requery:sqlite-android:$requery_version"
implementation project(':sqlite-android')
// https://mvnrepository.com/artifact/androidx.paging/paging-runtime
// https://developer.android.com/jetpack/androidx/releases/paging

@ -1,2 +1,3 @@
include ':app', ':openpgp-api'
include ':app', ':openpgp-api', ':sqlite-android'
project(':openpgp-api').projectDir = new File('openpgp-api')
project(':sqlite-android').projectDir = new File('sqlite-android')

@ -0,0 +1,7 @@
/build
/src/main/jniLibs
/src/main/jni/sqlite/sqlite3.h
/src/main/jni/sqlite/sqlite3.c
/src/main/jni/sqlite.zip
/src/main/obj
.externalNativeBuild/

@ -0,0 +1,149 @@
plugins {
id 'de.undercouch.download'
id 'com.android.library'
id 'maven-publish'
}
group = 'io.requery'
version = '3.39.2'
description = 'Android SQLite compatibility library'
android {
compileSdkVersion 32
buildToolsVersion "33.0.0"
ndkVersion '25.0.8775105'
defaultConfig {
minSdkVersion 14
versionName project.version
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
consumerProguardFiles 'proguard-rules.pro'
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'
}
}
libraryVariants.all {
it.generateBuildConfigProvider.configure { enabled = false }
}
}
dependencies {
api 'androidx.sqlite:sqlite:2.2.0'
api 'androidx.core:core:1.8.0'
androidTestImplementation 'androidx.test:core:1.4.0'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
}
ext {
sqliteDistributionUrl = 'https://www.sqlite.org/2022/sqlite-amalgamation-3390200.zip'
}
task downloadSqlite(type: Download) {
src project.sqliteDistributionUrl
dest 'src/main/jni/sqlite.zip'
}
task installSqlite(dependsOn: downloadSqlite, type: Copy) {
from zipTree(downloadSqlite.dest).matching {
include '*/sqlite3.*'
eachFile { it.setPath(it.getName()) }
}
into 'src/main/jni/sqlite'
}
preBuild.dependsOn installSqlite
Properties properties = new Properties()
File localProperties = project.rootProject.file('local.properties')
if (localProperties.exists()) {
properties.load(localProperties.newDataInputStream())
}
task sourceJar(type: Jar) {
archiveClassifier.set('sources')
from android.sourceSets.main.java.srcDirs
}
task javadoc(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
android.libraryVariants.all { variant ->
if (variant.name == 'release') {
owner.classpath += variant.javaCompileProvider.get().classpath
}
}
exclude '**/R.html', '**/R.*.html', '**/index.html'
if (JavaVersion.current().isJava9Compatible()) {
options.addBooleanOption('html5', true)
}
failOnError false
}
task javadocJar(type: Jar, dependsOn: javadoc) {
archiveClassifier.set('javadoc')
from javadoc.destinationDir
}
preBuild.dependsOn installSqlite
// https://issuetracker.google.com/issues/207403732
tasks.whenTaskAdded { task ->
if (task.name.startsWith("configureNdkBuildDebug")
|| task.name.startsWith("configureNdkBuildRelease")) {
task.dependsOn preBuild
}
}
publishing {
publications {
maven(MavenPublication) {
groupId project.group
artifactId project.name
version project.version
pom {
name = project.name
description = project.description
url = 'https://github.com/requery/sqlite-android'
licenses {
license {
name = 'The Apache Software License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
distribution = 'repo'
}
}
scm {
url = 'https://github.com/requery/sqlite-android.git'
connection = 'scm:git:git://github.com/requery/sqlite-android.git'
developerConnection = 'scm:git:git@github.com/requery/sqlite-android.git'
}
}
afterEvaluate {
artifact bundleReleaseAar
artifact sourceJar
artifact javadocJar
}
}
}
}

@ -0,0 +1,14 @@
-keepclasseswithmembers class io.requery.android.database.** {
native <methods>;
public <init>(...);
}
-keepnames class io.requery.android.database.** { *; }
-keep public class io.requery.android.database.sqlite.SQLiteFunction { *; }
-keep public class io.requery.android.database.sqlite.SQLiteCustomFunction { *; }
-keep public class io.requery.android.database.sqlite.SQLiteCursor { *; }
-keep public class io.requery.android.database.sqlite.SQLiteDebug** { *; }
-keep public class io.requery.android.database.sqlite.SQLiteDatabase { *; }
-keep public class io.requery.android.database.sqlite.SQLiteOpenHelper { *; }
-keep public class io.requery.android.database.sqlite.SQLiteStatement { *; }
-keep public class io.requery.android.database.CursorWindow { *; }
-keepattributes Exceptions,InnerClasses

@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.requery.android.sqlite"/>

@ -0,0 +1,421 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database;
import android.content.ContentResolver;
import android.database.CharArrayBuffer;
import android.database.ContentObservable;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.CursorIndexOutOfBoundsException;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import java.lang.ref.WeakReference;
/**
* This is an abstract cursor class that handles a lot of the common code
* that all cursors need to deal with and is provided for convenience reasons.
*/
public abstract class AbstractCursor implements Cursor {
private static final String TAG = "Cursor";
protected int mPos;
protected boolean mClosed;
//@Deprecated // deprecated in AOSP but still used for non-deprecated methods
protected ContentResolver mContentResolver;
private Uri mNotifyUri;
private final Object mSelfObserverLock = new Object();
private ContentObserver mSelfObserver;
private boolean mSelfObserverRegistered;
private final DataSetObservable mDataSetObservable = new DataSetObservable();
private final ContentObservable mContentObservable = new ContentObservable();
private Bundle mExtras = Bundle.EMPTY;
@Override
abstract public int getCount();
@Override
abstract public String[] getColumnNames();
@Override
abstract public String getString(int column);
@Override
abstract public short getShort(int column);
@Override
abstract public int getInt(int column);
@Override
abstract public long getLong(int column);
@Override
abstract public float getFloat(int column);
@Override
abstract public double getDouble(int column);
@Override
abstract public boolean isNull(int column);
@Override
public abstract int getType(int column);
@Override
public byte[] getBlob(int column) {
throw new UnsupportedOperationException("getBlob is not supported");
}
@Override
public int getColumnCount() {
return getColumnNames().length;
}
@Override
public void deactivate() {
onDeactivateOrClose();
}
/** @hide */
protected void onDeactivateOrClose() {
if (mSelfObserver != null) {
mContentResolver.unregisterContentObserver(mSelfObserver);
mSelfObserverRegistered = false;
}
mDataSetObservable.notifyInvalidated();
}
@Override
public boolean requery() {
if (mSelfObserver != null && !mSelfObserverRegistered) {
mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
mSelfObserverRegistered = true;
}
mDataSetObservable.notifyChanged();
return true;
}
@Override
public boolean isClosed() {
return mClosed;
}
@Override
public void close() {
mClosed = true;
mContentObservable.unregisterAll();
onDeactivateOrClose();
}
@Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
// Default implementation, uses getString
String result = getString(columnIndex);
if (result != null) {
char[] data = buffer.data;
if (data == null || data.length < result.length()) {
buffer.data = result.toCharArray();
} else {
result.getChars(0, result.length(), data, 0);
}
buffer.sizeCopied = result.length();
} else {
buffer.sizeCopied = 0;
}
}
public AbstractCursor() {
mPos = -1;
}
@Override
public final int getPosition() {
return mPos;
}
@Override
public final boolean moveToPosition(int position) {
// Make sure position isn't past the end of the cursor
final int count = getCount();
if (position >= count) {
mPos = count;
return false;
}
// Make sure position isn't before the beginning of the cursor
if (position < 0) {
mPos = -1;
return false;
}
// Check for no-op moves, and skip the rest of the work for them
if (position == mPos) {
return true;
}
boolean result = onMove(mPos, position);
if (!result) {
mPos = -1;
} else {
mPos = position;
}
return result;
}
/**
* This function is called every time the cursor is successfully scrolled
* to a new position, giving the subclass a chance to update any state it
* may have. If it returns false the move function will also do so and the
* cursor will scroll to the beforeFirst position.
* <p>
* This function should be called by methods such as {@link #moveToPosition(int)},
* so it will typically not be called from outside of the cursor class itself.
* </p>
*
* @param oldPosition The position that we're moving from.
* @param newPosition The position that we're moving to.
* @return True if the move is successful, false otherwise.
*/
public abstract boolean onMove(int oldPosition, int newPosition);
@Override
public final boolean move(int offset) {
return moveToPosition(mPos + offset);
}
@Override
public final boolean moveToFirst() {
return moveToPosition(0);
}
@Override
public final boolean moveToLast() {
return moveToPosition(getCount() - 1);
}
@Override
public final boolean moveToNext() {
return moveToPosition(mPos + 1);
}
@Override
public final boolean moveToPrevious() {
return moveToPosition(mPos - 1);
}
@Override
public final boolean isFirst() {
return mPos == 0 && getCount() != 0;
}
@Override
public final boolean isLast() {
int cnt = getCount();
return mPos == (cnt - 1) && cnt != 0;
}
@Override
public final boolean isBeforeFirst() {
return getCount() == 0 || mPos == -1;
}
@Override
public final boolean isAfterLast() {
return getCount() == 0 || mPos == getCount();
}
@Override
public int getColumnIndex(String columnName) {
// Hack according to bug 903852
final int periodIndex = columnName.lastIndexOf('.');
if (periodIndex != -1) {
Exception e = new Exception();
Log.e(TAG, "requesting column name with table name -- " + columnName, e);
columnName = columnName.substring(periodIndex + 1);
}
String columnNames[] = getColumnNames();
int length = columnNames.length;
for (int i = 0; i < length; i++) {
if (columnNames[i].equalsIgnoreCase(columnName)) {
return i;
}
}
return -1;
}
@Override
public int getColumnIndexOrThrow(String columnName) {
final int index = getColumnIndex(columnName);
if (index < 0) {
throw new IllegalArgumentException("column '" + columnName + "' does not exist");
}
return index;
}
@Override
public String getColumnName(int columnIndex) {
return getColumnNames()[columnIndex];
}
@Override
public void registerContentObserver(ContentObserver observer) {
mContentObservable.registerObserver(observer);
}
@Override
public void unregisterContentObserver(ContentObserver observer) {
// cursor will unregister all observers when it close
if (!mClosed) {
mContentObservable.unregisterObserver(observer);
}
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
/**
* Subclasses must call this method when they finish committing updates to notify all
* observers.
*
* @param selfChange value
*/
@SuppressWarnings("deprecation")
protected void onChange(boolean selfChange) {
synchronized (mSelfObserverLock) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mContentObservable.dispatchChange(selfChange, null);
} else {
mContentObservable.dispatchChange(selfChange);
}
if (mNotifyUri != null && selfChange) {
mContentResolver.notifyChange(mNotifyUri, mSelfObserver);
}
}
}
/**
* Specifies a content URI to watch for changes.
*
* @param cr The content resolver from the caller's context.
* @param notifyUri The URI to watch for changes. This can be a
* specific row URI, or a base URI for a whole class of content.
*/
@Override
public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
synchronized (mSelfObserverLock) {
mNotifyUri = notifyUri;
mContentResolver = cr;
if (mSelfObserver != null) {
mContentResolver.unregisterContentObserver(mSelfObserver);
}
mSelfObserver = new SelfContentObserver(this);
mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
mSelfObserverRegistered = true;
}
}
@Override
public Uri getNotificationUri() {
synchronized (mSelfObserverLock) {
return mNotifyUri;
}
}
@Override
public boolean getWantsAllOnMoveCalls() {
return false;
}
@Override
public void setExtras(Bundle extras) {
mExtras = (extras == null) ? Bundle.EMPTY : extras;
}
@Override
public Bundle getExtras() {
return mExtras;
}
@Override
public Bundle respond(Bundle extras) {
return Bundle.EMPTY;
}
/**
* This function throws CursorIndexOutOfBoundsException if the cursor position is out of bounds.
* Subclass implementations of the get functions should call this before attempting to
* retrieve data.
*
* @throws CursorIndexOutOfBoundsException
*/
protected void checkPosition() {
if (-1 == mPos || getCount() == mPos) {
throw new CursorIndexOutOfBoundsException(mPos, getCount());
}
}
@SuppressWarnings("FinalizeDoesntCallSuperFinalize")
@Override
protected void finalize() {
if (mSelfObserver != null && mSelfObserverRegistered) {
mContentResolver.unregisterContentObserver(mSelfObserver);
}
try {
if (!mClosed) close();
} catch(Exception ignored) { }
}
/**
* Cursors use this class to track changes others make to their URI.
*/
protected static class SelfContentObserver extends ContentObserver {
WeakReference<AbstractCursor> mCursor;
public SelfContentObserver(AbstractCursor cursor) {
super(null);
mCursor = new WeakReference<>(cursor);
}
@Override
public boolean deliverSelfNotifications() {
return false;
}
@Override
public void onChange(boolean selfChange) {
AbstractCursor cursor = mCursor.get();
if (cursor != null) {
cursor.onChange(false);
}
}
}
}

@ -0,0 +1,177 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database;
import android.database.CharArrayBuffer;
import android.database.Cursor;
import android.database.StaleDataException;
/**
* A base class for Cursors that store their data in {@link android.database.CursorWindow}s.
* <p>
* The cursor owns the cursor window it uses. When the cursor is closed,
* its window is also closed. Likewise, when the window used by the cursor is
* changed, its old window is closed. This policy of strict ownership ensures
* that cursor windows are not leaked.
* </p><p>
* Subclasses are responsible for filling the cursor window with data during
* {@link #onMove(int, int)}, allocating a new cursor window if necessary.
* During {@link #requery()}, the existing cursor window should be cleared and
* filled with new data.
* </p><p>
* If the contents of the cursor change or become invalid, the old window must be closed
* (because it is owned by the cursor) and set to null.
* </p>
*/
@SuppressWarnings("unused")
public abstract class AbstractWindowedCursor extends AbstractCursor {
/**
* The cursor window owned by this cursor.
*/
protected CursorWindow mWindow;
@Override
public byte[] getBlob(int columnIndex) {
checkPosition();
return mWindow.getBlob(mPos, columnIndex);
}
@Override
public String getString(int columnIndex) {
checkPosition();
return mWindow.getString(mPos, columnIndex);
}
@Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
mWindow.copyStringToBuffer(mPos, columnIndex, buffer);
}
@Override
public short getShort(int columnIndex) {
checkPosition();
return mWindow.getShort(mPos, columnIndex);
}
@Override
public int getInt(int columnIndex) {
checkPosition();
return mWindow.getInt(mPos, columnIndex);
}
@Override
public long getLong(int columnIndex) {
checkPosition();
return mWindow.getLong(mPos, columnIndex);
}
@Override
public float getFloat(int columnIndex) {
checkPosition();
return mWindow.getFloat(mPos, columnIndex);
}
@Override
public double getDouble(int columnIndex) {
checkPosition();
return mWindow.getDouble(mPos, columnIndex);
}
@Override
public boolean isNull(int columnIndex) {
return mWindow.getType(mPos, columnIndex) == Cursor.FIELD_TYPE_NULL;
}
@Override
public int getType(int columnIndex) {
return mWindow.getType(mPos, columnIndex);
}
@Override
protected void checkPosition() {
super.checkPosition();
if (mWindow == null) {
throw new StaleDataException("Attempting to access a closed CursorWindow." +
"Most probable cause: cursor is deactivated prior to calling this method.");
}
}
public CursorWindow getWindow() {
return mWindow;
}
/**
* Sets a new cursor window for the cursor to use.
* <p>
* The cursor takes ownership of the provided cursor window; the cursor window
* will be closed when the cursor is closed or when the cursor adopts a new
* cursor window.
* </p><p>
* If the cursor previously had a cursor window, then it is closed when the
* new cursor window is assigned.
* </p>
*
* @param window The new cursor window, typically a remote cursor window.
*/
public void setWindow(CursorWindow window) {
if (window != mWindow) {
closeWindow();
mWindow = window;
}
}
/**
* Returns true if the cursor has an associated cursor window.
*
* @return True if the cursor has an associated cursor window.
*/
public boolean hasWindow() {
return mWindow != null;
}
/**
* Closes the cursor window and sets {@link #mWindow} to null.
* @hide
*/
protected void closeWindow() {
if (mWindow != null) {
mWindow.close();
mWindow = null;
}
}
/**
* If there is a window, clear it. Otherwise, creates a new window.
*
* @param name The window name.
* @hide
*/
protected void clearOrCreateWindow(String name) {
if (mWindow == null) {
mWindow = new CursorWindow(name);
} else {
mWindow.clear();
}
}
@Override
protected void onDeactivateOrClose() {
super.onDeactivateOrClose();
closeWindow();
}
}

@ -0,0 +1,507 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database;
import android.database.CharArrayBuffer;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import io.requery.android.database.sqlite.SQLiteClosable;
/**
* A buffer containing multiple cursor rows.
*/
@SuppressWarnings("unused")
public class CursorWindow extends SQLiteClosable {
private static final int WINDOW_SIZE_KB = 2048;
/** The cursor window size. resource xml file specifies the value in kB.
* convert it to bytes here by multiplying with 1024.
*/
private static final int sDefaultCursorWindowSize =
WINDOW_SIZE_KB * 1024;
private final int mWindowSizeBytes;
/**
* The native CursorWindow object pointer. (FOR INTERNAL USE ONLY)
*/
public long mWindowPtr;
private int mStartPos;
private final String mName;
private static native long nativeCreate(String name, int cursorWindowSize);
private static native void nativeDispose(long windowPtr);
private static native void nativeClear(long windowPtr);
private static native int nativeGetNumRows(long windowPtr);
private static native boolean nativeSetNumColumns(long windowPtr, int columnNum);
private static native boolean nativeAllocRow(long windowPtr);
private static native void nativeFreeLastRow(long windowPtr);
private static native int nativeGetType(long windowPtr, int row, int column);
private static native byte[] nativeGetBlob(long windowPtr, int row, int column);
private static native String nativeGetString(long windowPtr, int row, int column);
private static native long nativeGetLong(long windowPtr, int row, int column);
private static native double nativeGetDouble(long windowPtr, int row, int column);
private static native boolean nativePutBlob(long windowPtr, byte[] value, int row, int column);
private static native boolean nativePutString(long windowPtr, String value, int row, int column);
private static native boolean nativePutLong(long windowPtr, long value, int row, int column);
private static native boolean nativePutDouble(long windowPtr, double value, int row, int column);
private static native boolean nativePutNull(long windowPtr, int row, int column);
private static native String nativeGetName(long windowPtr);
/**
* Creates a new empty cursor with default cursor size (currently 2MB)
*/
public CursorWindow(String name) {
this(name, sDefaultCursorWindowSize);
}
/**
* Creates a new empty cursor window and gives it a name.
* <p>
* The cursor initially has no rows or columns. Call {@link #setNumColumns(int)} to
* set the number of columns before adding any rows to the cursor.
* </p>
*
* @param name The name of the cursor window, or null if none.
* @param windowSizeBytes Size of cursor window in bytes.
*
* Note: Memory is dynamically allocated as data rows are added to
* the window. Depending on the amount of data stored, the actual
* amount of memory allocated can be lower than specified size,
* but cannot exceed it. Value is a non-negative number of bytes.
*/
public CursorWindow(String name, int windowSizeBytes) {
/* In
https://developer.android.com/reference/android/database/CursorWindow#CursorWindow(java.lang.String,%20long)
windowSizeBytes is long. However windowSizeBytes is
eventually transformed into a size_t in cpp, and I can not
guarantee that long->size_t would be possible. I thus keep
int. This means that we can create cursor of size up to 4GiB
while upstream can theoretically create cursor of size up to
16 EiB. It is probably an acceptable restriction.*/
mStartPos = 0;
mWindowSizeBytes = windowSizeBytes;
mName = name != null && name.length() != 0 ? name : "<unnamed>";
mWindowPtr = nativeCreate(mName, windowSizeBytes);
if (mWindowPtr == 0) {
throw new CursorWindowAllocationException("Cursor window allocation of " +
(windowSizeBytes / 1024) + " kb failed. ");
}
}
@SuppressWarnings("ThrowFromFinallyBlock")
@Override
protected void finalize() throws Throwable {
try {
dispose();
} finally {
super.finalize();
}
}
private void dispose() {
if (mWindowPtr != 0) {
nativeDispose(mWindowPtr);
mWindowPtr = 0;
}
}
/**
* Gets the name of this cursor window, never null.
*/
public String getName() {
return mName;
}
/**
* Clears out the existing contents of the window, making it safe to reuse
* for new data.
* <p>
* The start position ({@link #getStartPosition()}), number of rows ({@link #getNumRows()}),
* and number of columns in the cursor are all reset to zero.
* </p>
*/
public void clear() {
mStartPos = 0;
nativeClear(mWindowPtr);
}
/**
* Gets the start position of this cursor window.
* <p>
* The start position is the zero-based index of the first row that this window contains
* relative to the entire result set of the {@link Cursor}.
* </p>
*
* @return The zero-based start position.
*/
public int getStartPosition() {
return mStartPos;
}
/**
* Sets the start position of this cursor window.
* <p>
* The start position is the zero-based index of the first row that this window contains
* relative to the entire result set of the {@link Cursor}.
* </p>
*
* @param pos The new zero-based start position.
*/
public void setStartPosition(int pos) {
mStartPos = pos;
}
/**
* Gets the number of rows in this window.
*
* @return The number of rows in this cursor window.
*/
public int getNumRows() {
return nativeGetNumRows(mWindowPtr);
}
/**
* Sets the number of columns in this window.
* <p>
* This method must be called before any rows are added to the window, otherwise
* it will fail to set the number of columns if it differs from the current number
* of columns.
* </p>
*
* @param columnNum The new number of columns.
* @return True if successful.
*/
public boolean setNumColumns(int columnNum) {
return nativeSetNumColumns(mWindowPtr, columnNum);
}
/**
* Allocates a new row at the end of this cursor window.
*
* @return True if successful, false if the cursor window is out of memory.
*/
public boolean allocRow(){
return nativeAllocRow(mWindowPtr);
}
/**
* Frees the last row in this cursor window.
*/
public void freeLastRow(){
nativeFreeLastRow(mWindowPtr);
}
/**
* Returns the type of the field at the specified row and column index.
* <p>
* The returned field types are:
* <ul>
* <li>{@link Cursor#FIELD_TYPE_NULL}</li>
* <li>{@link Cursor#FIELD_TYPE_INTEGER}</li>
* <li>{@link Cursor#FIELD_TYPE_FLOAT}</li>
* <li>{@link Cursor#FIELD_TYPE_STRING}</li>
* <li>{@link Cursor#FIELD_TYPE_BLOB}</li>
* </ul>
* </p>
*
* @param row The zero-based row index.
* @param column The zero-based column index.
* @return The field type.
*/
public int getType(int row, int column) {
return nativeGetType(mWindowPtr, row - mStartPos, column);
}
/**
* Gets the value of the field at the specified row and column index as a byte array.
* <p>
* The result is determined as follows:
* <ul>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
* is <code>null</code>.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then the result
* is the blob value.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
* is the array of bytes that make up the internal representation of the
* string value.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER} or
* {@link Cursor#FIELD_TYPE_FLOAT}, then a {@link SQLiteException} is thrown.</li>
* </ul>
* </p>
*
* @param row The zero-based row index.
* @param column The zero-based column index.
* @return The value of the field as a byte array.
*/
public byte[] getBlob(int row, int column) {
return nativeGetBlob(mWindowPtr, row - mStartPos, column);
}
/**
* Gets the value of the field at the specified row and column index as a string.
* <p>
* The result is determined as follows:
* <ul>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
* is <code>null</code>.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
* is the string value.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
* is a string representation of the integer in decimal, obtained by formatting the
* value with the <code>printf</code> family of functions using
* format specifier <code>%lld</code>.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
* is a string representation of the floating-point value in decimal, obtained by
* formatting the value with the <code>printf</code> family of functions using
* format specifier <code>%g</code>.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
* {@link SQLiteException} is thrown.</li>
* </ul>
* </p>
*
* @param row The zero-based row index.
* @param column The zero-based column index.
* @return The value of the field as a string.
*/
public String getString(int row, int column) {
return nativeGetString(mWindowPtr, row - mStartPos, column);
}
/**
* Copies the text of the field at the specified row and column index into
* a {@link CharArrayBuffer}.
* <p>
* The buffer is populated as follows:
* <ul>
* <li>If the buffer is too small for the value to be copied, then it is
* automatically resized.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the buffer
* is set to an empty string.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the buffer
* is set to the contents of the string.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the buffer
* is set to a string representation of the integer in decimal, obtained by formatting the
* value with the <code>printf</code> family of functions using
* format specifier <code>%lld</code>.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the buffer is
* set to a string representation of the floating-point value in decimal, obtained by
* formatting the value with the <code>printf</code> family of functions using
* format specifier <code>%g</code>.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
* {@link SQLiteException} is thrown.</li>
* </ul>
* </p>
*
* @param row The zero-based row index.
* @param column The zero-based column index.
* @param buffer The {@link CharArrayBuffer} to hold the string. It is automatically
* resized if the requested string is larger than the buffer's current capacity.
*/
public void copyStringToBuffer(int row, int column, CharArrayBuffer buffer) {
if (buffer == null) {
throw new IllegalArgumentException("CharArrayBuffer should not be null");
}
// TODO not as optimal as the original code
char[] chars = getString(row, column).toCharArray();
buffer.data = chars;
buffer.sizeCopied = chars.length;
}
/**
* Gets the value of the field at the specified row and column index as a <code>long</code>.
* <p>
* The result is determined as follows:
* <ul>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
* is <code>0L</code>.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
* is the value obtained by parsing the string value with <code>strtoll</code>.
* <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
* is the <code>long</code> value.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
* is the floating-point value converted to a <code>long</code>.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
* {@link SQLiteException} is thrown.</li>
* </ul>
* </p>
*
* @param row The zero-based row index.
* @param column The zero-based column index.
* @return The value of the field as a <code>long</code>.
*/
public long getLong(int row, int column) {
return nativeGetLong(mWindowPtr, row - mStartPos, column);
}
/**
* Gets the value of the field at the specified row and column index as a
* <code>double</code>.
* <p>
* The result is determined as follows:
* <ul>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
* is <code>0.0</code>.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
* is the value obtained by parsing the string value with <code>strtod</code>.
* <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
* is the integer value converted to a <code>double</code>.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
* is the <code>double</code> value.</li>
* <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
* {@link SQLiteException} is thrown.</li>
* </ul>
* </p>
*
* @param row The zero-based row index.
* @param column The zero-based column index.
* @return The value of the field as a <code>double</code>.
*/
public double getDouble(int row, int column) {
return nativeGetDouble(mWindowPtr, row - mStartPos, column);
}
/**
* Gets the value of the field at the specified row and column index as a
* <code>short</code>.
* <p>
* The result is determined by invoking {@link #getLong} and converting the
* result to <code>short</code>.
* </p>
*
* @param row The zero-based row index.
* @param column The zero-based column index.
* @return The value of the field as a <code>short</code>.
*/
public short getShort(int row, int column) {
return (short) getLong(row, column);
}
/**
* Gets the value of the field at the specified row and column index as an
* <code>int</code>.
* <p>
* The result is determined by invoking {@link #getLong} and converting the
* result to <code>int</code>.
* </p>
*
* @param row The zero-based row index.
* @param column The zero-based column index.
* @return The value of the field as an <code>int</code>.
*/
public int getInt(int row, int column) {
return (int) getLong(row, column);
}
/**
* Gets the value of the field at the specified row and column index as a
* <code>float</code>.
* <p>
* The result is determined by invoking {@link #getDouble} and converting the
* result to <code>float</code>.
* </p>
*
* @param row The zero-based row index.
* @param column The zero-based column index.
* @return The value of the field as an <code>float</code>.
*/
public float getFloat(int row, int column) {
return (float) getDouble(row, column);
}
/**
* Copies a byte array into the field at the specified row and column index.
*
* @param value The value to store.
* @param row The zero-based row index.
* @param column The zero-based column index.
* @return True if successful.
*/
public boolean putBlob(byte[] value, int row, int column) {
return nativePutBlob(mWindowPtr, value, row - mStartPos, column);
}
/**
* Copies a string into the field at the specified row and column index.
*
* @param value The value to store.
* @param row The zero-based row index.
* @param column The zero-based column index.
* @return True if successful.
*/
public boolean putString(String value, int row, int column) {
return nativePutString(mWindowPtr, value, row - mStartPos, column);
}
/**
* Puts a long integer into the field at the specified row and column index.
*
* @param value The value to store.
* @param row The zero-based row index.
* @param column The zero-based column index.
* @return True if successful.
*/
public boolean putLong(long value, int row, int column) {
return nativePutLong(mWindowPtr, value, row - mStartPos, column);
}
/**
* Puts a double-precision floating point value into the field at the
* specified row and column index.
*
* @param value The value to store.
* @param row The zero-based row index.
* @param column The zero-based column index.
* @return True if successful.
*/
public boolean putDouble(double value, int row, int column) {
return nativePutDouble(mWindowPtr, value, row - mStartPos, column);
}
/**
* Puts a null value into the field at the specified row and column index.
*
* @param row The zero-based row index.
* @param column The zero-based column index.
* @return True if successful.
*/
public boolean putNull(int row, int column) {
return nativePutNull(mWindowPtr, row - mStartPos, column);
}
@Override
protected void onAllReferencesReleased() {
dispose();
}
@Override
public String toString() {
return getName() + " {" + Long.toHexString(mWindowPtr) + "}";
}
public int getWindowSizeBytes() {
return mWindowSizeBytes;
}
}

@ -0,0 +1,29 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.requery.android.database;
/**
* This exception is thrown when a CursorWindow couldn't be allocated,
* most probably due to memory not being available.
*
* @hide
*/
public class CursorWindowAllocationException extends RuntimeException {
public CursorWindowAllocationException(String description) {
super(description);
}
}

@ -0,0 +1,33 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database;
import io.requery.android.database.sqlite.SQLiteDatabase;
/**
* An interface to let apps define an action to take when database corruption is detected.
*/
public interface DatabaseErrorHandler {
/**
* The method invoked when database corruption is detected.
* @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption
* is detected.
*/
void onCorruption(SQLiteDatabase dbObj);
}

@ -0,0 +1,106 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database;
import android.database.sqlite.SQLiteException;
import android.util.Log;
import android.util.Pair;
import io.requery.android.database.sqlite.SQLiteDatabase;
import java.io.File;
import java.util.List;
/**
* Default class used to define the actions to take when the database corruption is reported
* by sqlite.
* <p>
* An application can specify an implementation of {@link DatabaseErrorHandler} on the
* following:
* <ul>
* <li>{@link SQLiteDatabase#openOrCreateDatabase(String,
* SQLiteDatabase.CursorFactory, DatabaseErrorHandler)}</li>
* <li>{@link SQLiteDatabase#openDatabase(String,
* SQLiteDatabase.CursorFactory, int, DatabaseErrorHandler)}</li>
* </ul>
* The specified {@link DatabaseErrorHandler} is used to handle database corruption errors, if they
* occur.
* <p>
* If null is specified for DatabaeErrorHandler param in the above calls, then this class is used
* as the default {@link DatabaseErrorHandler}.
*/
public final class DefaultDatabaseErrorHandler implements DatabaseErrorHandler {
private static final String TAG = "DefaultDatabaseError";
@Override
public void onCorruption(SQLiteDatabase dbObj) {
Log.e(TAG, "Corruption reported by sqlite on database: " + dbObj.getPath());
// is the corruption detected even before database could be 'opened'?
if (!dbObj.isOpen()) {
// database files are not even openable. delete this database file.
// NOTE if the database has attached databases, then any of them could be corrupt.
// and not deleting all of them could cause corrupted database file to remain and
// make the application crash on database open operation. To avoid this problem,
// the application should provide its own {@link DatabaseErrorHandler} impl class
// to delete ALL files of the database (including the attached databases).
deleteDatabaseFile(dbObj.getPath());
return;
}
List<Pair<String, String>> attachedDbs = null;
try {
// Close the database, which will cause subsequent operations to fail.
// before that, get the attached database list first.
try {
attachedDbs = dbObj.getAttachedDbs();
} catch (SQLiteException e) {
/* ignore */
}
try {
dbObj.close();
} catch (SQLiteException e) {
/* ignore */
}
} finally {
// Delete all files of this corrupt database and/or attached databases
if (attachedDbs != null) {
for (Pair<String, String> p : attachedDbs) {
deleteDatabaseFile(p.second);
}
} else {
// attachedDbs = null is possible when the database is so corrupt that even
// "PRAGMA database_list;" also fails. delete the main database file
deleteDatabaseFile(dbObj.getPath());
}
}
}
private void deleteDatabaseFile(String fileName) {
if (fileName.equalsIgnoreCase(":memory:") || fileName.trim().length() == 0) {
return;
}
Log.e(TAG, "deleting the database file: " + fileName);
try {
SQLiteDatabase.deleteDatabase(new File(fileName));
} catch (Exception e) {
/* print warning and ignore exception */
Log.w(TAG, "delete failed: " + e.getMessage());
}
}
}

@ -0,0 +1,234 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database.sqlite;
import android.util.Log;
/**
* CloseGuard is a mechanism for flagging implicit finalizer cleanup of
* resources that should have been cleaned up by explicit close
* methods (aka "explicit termination methods" in Effective Java).
* <p>
* A simple example: <pre> {@code
* class Foo {
*
* private final CloseGuard guard = CloseGuard.get();
*
* ...
*
* public Foo() {
* ...;
* guard.open("cleanup");
* }
*
* public void cleanup() {
* guard.close();
* ...;
* }
*
* protected void finalize() throws Throwable {
* try {
* if (guard != null) {
* guard.warnIfOpen();
* }
* cleanup();
* } finally {
* super.finalize();
* }
* }
* }
* }</pre>
*
* In usage where the resource to be explicitly cleaned up are
* allocated after object construction, CloseGuard protection can
* be deferred. For example: <pre> {@code
* class Bar {
*
* private final CloseGuard guard = CloseGuard.get();
*
* ...
*
* public Bar() {
* ...;
* }
*
* public void connect() {
* ...;
* guard.open("cleanup");
* }
*
* public void cleanup() {
* guard.close();
* ...;
* }
*
* protected void finalize() throws Throwable {
* try {
* if (guard != null) {
* guard.warnIfOpen();
* }
* cleanup();
* } finally {
* super.finalize();
* }
* }
* }
* }</pre>
*
* When used in a constructor calls to {@code open} should occur at
* the end of the constructor since an exception that would cause
* abrupt termination of the constructor will mean that the user will
* not have a reference to the object to cleanup explicitly. When used
* in a method, the call to {@code open} should occur just after
* resource acquisition.
*
* <p>
*
* Note that the null check on {@code guard} in the finalizer is to
* cover cases where a constructor throws an exception causing the
* {@code guard} to be uninitialized.
*
* @hide
*/
@SuppressWarnings("unused")
public final class CloseGuard {
/**
* Instance used when CloseGuard is disabled to avoid allocation.
*/
private static final CloseGuard NOOP = new CloseGuard();
/**
* Enabled by default so we can catch issues early in VM startup.
* Note, however, that Android disables this early in its startup,
* but enables it with DropBoxing for system apps on debug builds.
*/
private static volatile boolean ENABLED = true;
/**
* Hook for customizing how CloseGuard issues are reported.
*/
private static volatile Reporter REPORTER = new DefaultReporter();
/**
* Returns a CloseGuard instance. If CloseGuard is enabled, {@code
* #open(String)} can be used to set up the instance to warn on
* failure to close. If CloseGuard is disabled, a non-null no-op
* instance is returned.
*/
public static CloseGuard get() {
if (!ENABLED) {
return NOOP;
}
return new CloseGuard();
}
/**
* Used to enable or disable CloseGuard. Note that CloseGuard only
* warns if it is enabled for both allocation and finalization.
*/
public static void setEnabled(boolean enabled) {
ENABLED = enabled;
}
/**
* Used to replace default Reporter used to warn of CloseGuard
* violations. Must be non-null.
*/
public static void setReporter(Reporter reporter) {
if (reporter == null) {
throw new NullPointerException("reporter == null");
}
REPORTER = reporter;
}
/**
* Returns non-null CloseGuard.Reporter.
*/
public static Reporter getReporter() {
return REPORTER;
}
private CloseGuard() {}
/**
* If CloseGuard is enabled, {@code open} initializes the instance
* with a warning that the caller should have explicitly called the
* {@code closer} method instead of relying on finalization.
*
* @param closer non-null name of explicit termination method
* @throws NullPointerException if closer is null, regardless of
* whether or not CloseGuard is enabled
*/
public void open(String closer) {
// always perform the check for valid API usage...
if (closer == null) {
throw new NullPointerException("closer == null");
}
// ...but avoid allocating an allocationSite if disabled
if (this == NOOP || !ENABLED) {
return;
}
String message = "Explicit termination method '" + closer + "' not called";
allocationSite = new Throwable(message);
}
private Throwable allocationSite;
/**
* Marks this CloseGuard instance as closed to avoid warnings on
* finalization.
*/
public void close() {
allocationSite = null;
}
/**
* If CloseGuard is enabled, logs a warning if the caller did not
* properly cleanup by calling an explicit close method
* before finalization. If CloseGuard is disabled, no action is
* performed.
*/
public void warnIfOpen() {
if (allocationSite == null || !ENABLED) {
return;
}
String message =
("A resource was acquired at attached stack trace but never released. "
+ "See java.io.Closeable for information on avoiding resource leaks.");
REPORTER.report(message, allocationSite);
}
/**
* Interface to allow customization of reporting behavior.
*/
public interface Reporter {
void report(String message, Throwable allocationSite);
}
/**
* Default Reporter which reports CloseGuard violations to the log.
*/
private static final class DefaultReporter implements Reporter {
@Override public void report (String message, Throwable allocationSite) {
Log.w("SQLite", message, allocationSite);
}
}
}

@ -0,0 +1,95 @@
package io.requery.android.database.sqlite;
import android.content.Context;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import io.requery.android.database.DatabaseErrorHandler;
import java.util.Collections;
/**
* Implements {@link SupportSQLiteOpenHelper.Factory} using the SQLite implementation shipped in
* this library.
*/
@SuppressWarnings("unused")
public final class RequerySQLiteOpenHelperFactory implements SupportSQLiteOpenHelper.Factory {
private final Iterable<ConfigurationOptions> configurationOptions;
@SuppressWarnings("WeakerAccess")
public RequerySQLiteOpenHelperFactory(Iterable<ConfigurationOptions> configurationOptions) {
this.configurationOptions = configurationOptions;
}
public RequerySQLiteOpenHelperFactory() {
this(Collections.<ConfigurationOptions>emptyList());
}
@Override
public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration config) {
return new CallbackSQLiteOpenHelper(config.context, config.name, config.callback, configurationOptions);
}
private static final class CallbackSQLiteOpenHelper extends SQLiteOpenHelper {
private final SupportSQLiteOpenHelper.Callback callback;
private final Iterable<ConfigurationOptions> configurationOptions;
CallbackSQLiteOpenHelper(Context context, String name, SupportSQLiteOpenHelper.Callback cb, Iterable<ConfigurationOptions> ops) {
super(context, name, null, cb.version, new CallbackDatabaseErrorHandler(cb));
this.callback = cb;
this.configurationOptions = ops;
}
@Override
public void onConfigure(SQLiteDatabase db) {
callback.onConfigure(db);
}
@Override
public void onCreate(SQLiteDatabase db) {
callback.onCreate(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
callback.onUpgrade(db, oldVersion, newVersion);
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
callback.onDowngrade(db, oldVersion, newVersion);
}
@Override
public void onOpen(SQLiteDatabase db) {
callback.onOpen(db);
}
@Override protected SQLiteDatabaseConfiguration createConfiguration(String path, int openFlags) {
SQLiteDatabaseConfiguration config = super.createConfiguration(path, openFlags);
for (ConfigurationOptions option : configurationOptions) {
config = option.apply(config);
}
return config;
}
}
private static final class CallbackDatabaseErrorHandler implements DatabaseErrorHandler {
private final SupportSQLiteOpenHelper.Callback callback;
CallbackDatabaseErrorHandler(SupportSQLiteOpenHelper.Callback callback) {
this.callback = callback;
}
@Override
public void onCorruption(SQLiteDatabase db) {
callback.onCorruption(db);
}
}
public interface ConfigurationOptions {
SQLiteDatabaseConfiguration apply(SQLiteDatabaseConfiguration configuration);
}
}

@ -0,0 +1,80 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database.sqlite;
import java.io.Closeable;
/**
* An object created from a SQLiteDatabase that can be closed.
*
* This class implements a primitive reference counting scheme for database objects.
*/
public abstract class SQLiteClosable implements Closeable {
private int mReferenceCount = 1;
/**
* Called when the last reference to the object was released by
* a call to {@link #releaseReference()} or {@link #close()}.
*/
protected abstract void onAllReferencesReleased();
/**
* Acquires a reference to the object.
*
* @throws IllegalStateException if the last reference to the object has already
* been released.
*/
public void acquireReference() {
synchronized(this) {
if (mReferenceCount <= 0) {
throw new IllegalStateException(
"attempt to re-open an already-closed object: " + this);
}
mReferenceCount++;
}
}
/**
* Releases a reference to the object, closing the object if the last reference
* was released.
*
* @see #onAllReferencesReleased()
*/
public void releaseReference() {
boolean refCountIsZero;
synchronized(this) {
refCountIsZero = --mReferenceCount == 0;
}
if (refCountIsZero) {
onAllReferencesReleased();
}
}
/**
* Releases a reference to the object, closing the object if the last reference
* was released.
*
* Calling this method is equivalent to calling {@link #releaseReference}.
*
* @see #releaseReference()
* @see #onAllReferencesReleased()
*/
public void close() {
releaseReference();
}
}

@ -0,0 +1,260 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database.sqlite;
import android.util.Log;
import android.util.SparseIntArray;
import io.requery.android.database.AbstractWindowedCursor;
import io.requery.android.database.CursorWindow;
import java.util.HashMap;
/**
* A Cursor implementation that exposes results from a query on a {@link SQLiteDatabase}.
*
* SQLiteCursor is not internally synchronized so code using a SQLiteCursor from multiple
* threads should perform its own synchronization when using the SQLiteCursor.
*/
public class SQLiteCursor extends AbstractWindowedCursor {
static final String TAG = "SQLiteCursor";
static final int NO_COUNT = -1;
/** The names of the columns in the rows */
private final String[] mColumns;
/** The query object for the cursor */
private final SQLiteQuery mQuery;
/** The compiled query this cursor came from */
private final SQLiteCursorDriver mDriver;
/** The number of rows in the cursor */
private int mCount = NO_COUNT;
/** The number of rows that can fit in the cursor window, 0 if unknown */
private int mCursorWindowCapacity;
/** A mapping of column names to column indices, to speed up lookups */
private SparseIntArray mColumnNameArray;
private HashMap<String, Integer> mColumnNameMap;
/** Used to find out where a cursor was allocated in case it never got released. */
private final CloseGuard mCloseGuard;
/**
* Execute a query and provide access to its result set through a Cursor
* interface. For a query such as: {@code SELECT name, birth, phone FROM
* myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth,
* phone) would be in the projection argument and everything from
* {@code FROM} onward would be in the params argument.
*
* @param editTable not used, present only for compatibility with
* {@link android.database.sqlite.SQLiteCursor}
* @param query the {@link SQLiteQuery} object associated with this cursor object.
*/
@SuppressWarnings("unused")
public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) {
if (query == null) {
throw new IllegalArgumentException("query object cannot be null");
}
mDriver = driver;
mQuery = query;
mCloseGuard = CloseGuard.get();
mColumns = query.getColumnNames();
}
/**
* Get the database that this cursor is associated with.
* @return the SQLiteDatabase that this cursor is associated with.
*/
public SQLiteDatabase getDatabase() {
return mQuery.getDatabase();
}
@Override
public boolean onMove(int oldPosition, int newPosition) {
// Make sure the row at newPosition is present in the window
if (mWindow == null || newPosition < mWindow.getStartPosition() ||
newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
fillWindow(newPosition);
}
return true;
}
@Override
public int getCount() {
if (mCount == NO_COUNT) {
fillWindow(0);
}
return mCount;
}
public static int cursorPickFillWindowStartPosition(
int cursorPosition, int cursorWindowCapacity) {
return Math.max(cursorPosition - cursorWindowCapacity / 3, 0);
}
private void fillWindow(int requiredPos) {
clearOrCreateWindow(getDatabase().getPath());
try {
if (mCount == NO_COUNT) {
int startPos = cursorPickFillWindowStartPosition(requiredPos, 0);
mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
mCursorWindowCapacity = mWindow.getNumRows();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
}
} else {
int startPos = cursorPickFillWindowStartPosition(requiredPos,
mCursorWindowCapacity);
mQuery.fillWindow(mWindow, startPos, requiredPos, false);
}
} catch (RuntimeException ex) {
// Close the cursor window if the query failed and therefore will
// not produce any results. This helps to avoid accidentally leaking
// the cursor window if the client does not correctly handle exceptions
// and fails to close the cursor.
setWindow(null);
throw ex;
}
}
@Override
public int getColumnIndex(String columnName) {
// Create mColumnNameMap on demand
if (mColumnNameArray == null && mColumnNameMap == null) {
String[] columns = mColumns;
int columnCount = columns.length;
SparseIntArray map = new SparseIntArray(columnCount);
boolean collision = false;
for (int i = 0; i < columnCount; i++) {
int key = columns[i].hashCode();
// check for hashCode collision
if (map.get(key, -1) != -1) {
collision = true;
break;
}
map.put(key, i);
}
if (collision) {
mColumnNameMap = new HashMap<>();
for (int i = 0; i < columnCount; i++) {
mColumnNameMap.put(columns[i], i);
}
} else {
mColumnNameArray = map;
}
}
// Hack according to bug 903852
final int periodIndex = columnName.lastIndexOf('.');
if (periodIndex != -1) {
Exception e = new Exception();
Log.e(TAG, "requesting column name with table name -- " + columnName, e);
columnName = columnName.substring(periodIndex + 1);
}
if (mColumnNameMap != null) {
Integer i = mColumnNameMap.get(columnName);
return i == null ? -1 : i;
} else {
return mColumnNameArray.get(columnName.hashCode(), -1);
}
}
@Override
public String[] getColumnNames() {
return mColumns;
}
@Override
public void deactivate() {
super.deactivate();
mDriver.cursorDeactivated();
}
@Override
public void close() {
super.close();
synchronized (this) {
mQuery.close();
mDriver.cursorClosed();
}
}
@Override
public boolean requery() {
if (isClosed()) {
return false;
}
synchronized (this) {
if (!mQuery.getDatabase().isOpen()) {
return false;
}
if (mWindow != null) {
mWindow.clear();
}
mPos = -1;
mCount = NO_COUNT;
mDriver.cursorRequeried(this);
}
try {
return super.requery();
} catch (IllegalStateException e) {
// for backwards compatibility, just return false
Log.w(TAG, "requery() failed " + e.getMessage(), e);
return false;
}
}
@Override
public void setWindow(CursorWindow window) {
super.setWindow(window);
mCount = NO_COUNT;
}
/**
* Changes the selection arguments. The new values take effect after a call to requery().
*/
public void setSelectionArguments(String[] selectionArgs) {
mDriver.setBindArguments(selectionArgs);
}
/**
* Release the native resources, if they haven't been released yet.
*/
@Override
protected void finalize() {
try {
// if the cursor hasn't been closed yet, close it first
if (mWindow != null) {
mCloseGuard.warnIfOpen();
close();
}
} finally {
super.finalize();
}
}
}

@ -0,0 +1,56 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database.sqlite;
import android.database.Cursor;
/**
* A driver for SQLiteCursors that is used to create them and gets notified
* by the cursors it creates on significant events in their lifetimes.
*/
public interface SQLiteCursorDriver {
/**
* Executes the query returning a Cursor over the result set.
*
* @param factory The CursorFactory to use when creating the Cursors, or
* null if standard SQLiteCursors should be returned.
* @return a Cursor over the result set
*/
Cursor query(SQLiteDatabase.CursorFactory factory, Object[] bindArgs);
/**
* Called by a SQLiteCursor when it is released.
*/
void cursorDeactivated();
/**
* Called by a SQLiteCursor when it is requeried.
*/
void cursorRequeried(Cursor cursor);
/**
* Called by a SQLiteCursor when it it closed to destroy this object as well.
*/
void cursorClosed();
/**
* Set new bind arguments. These will take effect in cursorRequeried().
* @param bindArgs the new arguments
*/
void setBindArguments(String[] bindArgs);
}

@ -0,0 +1,42 @@
/*
* Copyright 2016 requery.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.requery.android.database.sqlite;
/**
* Describes a SQLite extension entry point.
*/
public final class SQLiteCustomExtension {
public final String path;
public final String entryPoint;
/**
* Creates a SQLite extension description.
*
* @param path path to the loadable extension shared library
* e.g. "/data/data/(package)/lib/libSqliteICU.so"
* @param entryPoint extension entry point (optional)
* e.g. "sqlite3_icu_init"
*/
public SQLiteCustomExtension(String path, String entryPoint) {
if (path == null) {
throw new IllegalArgumentException("null path");
}
this.path = path;
this.entryPoint = entryPoint;
}
}

@ -0,0 +1,54 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database.sqlite;
/**
* Describes a custom SQL function.
*
* @hide
*/
public final class SQLiteCustomFunction {
public final String name;
public final int numArgs;
public final SQLiteDatabase.CustomFunction callback;
/**
* Create custom function.
*
* @param name The name of the sqlite3 function.
* @param numArgs The number of arguments for the function, or -1 to
* support any number of arguments.
* @param callback The callback to invoke when the function is executed.
*/
public SQLiteCustomFunction(String name, int numArgs,
SQLiteDatabase.CustomFunction callback) {
if (name == null) {
throw new IllegalArgumentException("name must not be null.");
}
this.name = name;
this.numArgs = numArgs;
this.callback = callback;
}
// Called from native.
@SuppressWarnings("unused")
private String dispatchCallback(String[] args) {
return callback.callback(args);
}
}

@ -0,0 +1,203 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database.sqlite;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
/**
* Describes how to configure a database.
* <p>
* The purpose of this object is to keep track of all of the little
* configuration settings that are applied to a database after it
* is opened so that they can be applied to all connections in the
* connection pool uniformly.
* </p><p>
* Each connection maintains its own copy of this object so it can
* keep track of which settings have already been applied.
* </p>
*
* @hide
*/
public final class SQLiteDatabaseConfiguration {
// The pattern we use to strip email addresses from database paths
// when constructing a label to use in log messages.
private static final Pattern EMAIL_IN_DB_PATTERN =
Pattern.compile("[\\w\\.\\-]+@[\\w\\.\\-]+");
/**
* Special path used by in-memory databases.
*/
public static final String MEMORY_DB_PATH = ":memory:";
/**
* The database path.
*/
public final String path;
/**
* The label to use to describe the database when it appears in logs.
* This is derived from the path but is stripped to remove PII.
*/
public final String label;
/**
* The flags used to open the database.
*/
public @SQLiteDatabase.OpenFlags int openFlags;
/**
* The maximum size of the prepared statement cache for each database connection.
* Must be non-negative.
*
* Default is 25.
*/
public int maxSqlCacheSize;
/**
* The database locale.
*
* Default is the value returned by {@link Locale#getDefault()}.
*/
public Locale locale;
/**
* True if foreign key constraints are enabled.
*
* Default is false.
*/
public boolean foreignKeyConstraintsEnabled;
/**
* The custom functions to register.
*
* This interface is deprecated; see {@link SQLiteFunction}
*/
@Deprecated
public final List<SQLiteCustomFunction> customFunctions = new ArrayList<>();
/**
* The {@link SQLiteFunction}s to register.
*/
public final List<SQLiteFunction> functions = new ArrayList<>();
/**
* The custom extensions to register.
*/
public final List<SQLiteCustomExtension> customExtensions = new ArrayList<>();
/**
* Creates a database configuration with the required parameters for opening a
* database and default values for all other parameters.
*
* @param path The database path.
* @param openFlags Open flags for the database, such as {@link SQLiteDatabase#OPEN_READWRITE}.
*/
public SQLiteDatabaseConfiguration(String path, @SQLiteDatabase.OpenFlags int openFlags) {
if (path == null) {
throw new IllegalArgumentException("path must not be null.");
}
this.path = path;
label = stripPathForLogs(path);
this.openFlags = openFlags;
// Set default values for optional parameters.
maxSqlCacheSize = 25;
locale = Locale.getDefault();
}
/**
* Creates a database configuration with the required parameters for opening a
* database and default values for all other parameters.
*
* @param path The database path.
* @param openFlags Open flags for the database, such as {@link SQLiteDatabase#OPEN_READWRITE}.
* @param functions custom functions to use.
* @param extensions custom extensions to use.
*/
public SQLiteDatabaseConfiguration(String path,
@SQLiteDatabase.OpenFlags int openFlags,
List<SQLiteCustomFunction> customFunctions,
List<SQLiteFunction> functions,
List<SQLiteCustomExtension> extensions) {
this(path, openFlags);
this.customFunctions.addAll(customFunctions);
this.customExtensions.addAll(extensions);
this.functions.addAll(functions);
}
/**
* Creates a database configuration as a copy of another configuration.
*
* @param other The other configuration.
*/
SQLiteDatabaseConfiguration(SQLiteDatabaseConfiguration other) {
if (other == null) {
throw new IllegalArgumentException("other must not be null.");
}
this.path = other.path;
this.label = other.label;
updateParametersFrom(other);
}
/**
* Updates the non-immutable parameters of this configuration object
* from the other configuration object.
*
* @param other The object from which to copy the parameters.
*/
void updateParametersFrom(SQLiteDatabaseConfiguration other) {
if (other == null) {
throw new IllegalArgumentException("other must not be null.");
}
if (!path.equals(other.path)) {
throw new IllegalArgumentException("other configuration must refer to "
+ "the same database.");
}
openFlags = other.openFlags;
maxSqlCacheSize = other.maxSqlCacheSize;
locale = other.locale;
foreignKeyConstraintsEnabled = other.foreignKeyConstraintsEnabled;
customFunctions.clear();
customFunctions.addAll(other.customFunctions);
customExtensions.clear();
customExtensions.addAll(other.customExtensions);
functions.clear();
functions.addAll(other.functions);
}
/**
* Returns true if the database is in-memory.
* @return True if the database is in-memory.
*/
public boolean isInMemoryDb() {
return path.equalsIgnoreCase(MEMORY_DB_PATH);
}
private static String stripPathForLogs(String path) {
if (path.indexOf('@') == -1) {
return path;
}
return EMAIL_IN_DB_PATTERN.matcher(path).replaceAll("XX@YY");
}
}

@ -0,0 +1,173 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database.sqlite;
import android.util.Log;
import android.util.Printer;
import java.util.ArrayList;
/**
* Provides debugging info about all SQLite databases running in the current process.
*
* {@hide}
*/
@SuppressWarnings("unused")
public final class SQLiteDebug {
private static native void nativeGetPagerStats(PagerStats stats);
/**
* Controls the printing of informational SQL log messages.
*
* Enable using "adb shell setprop log.tag.SQLiteLog VERBOSE".
*/
public static final boolean DEBUG_SQL_LOG =
Log.isLoggable("SQLiteLog", Log.VERBOSE);
/**
* Controls the printing of SQL statements as they are executed.
*
* Enable using "adb shell setprop log.tag.SQLiteStatements VERBOSE".
*/
public static final boolean DEBUG_SQL_STATEMENTS =
Log.isLoggable("SQLiteStatements", Log.VERBOSE);
/**
* Controls the printing of wall-clock time taken to execute SQL statements
* as they are executed.
*
* Enable using "adb shell setprop log.tag.SQLiteTime VERBOSE".
*/
public static final boolean DEBUG_SQL_TIME =
Log.isLoggable("SQLiteTime", Log.VERBOSE);
/**
* True to enable database performance testing instrumentation.
* @hide
*/
public static final boolean DEBUG_LOG_SLOW_QUERIES = false;
private SQLiteDebug() {
}
/**
* Determines whether a query should be logged.
*
* Reads the "db.log.slow_query_threshold" system property, which can be changed
* by the user at any time. If the value is zero, then all queries will
* be considered slow. If the value does not exist or is negative, then no queries will
* be considered slow.
*
* This value can be changed dynamically while the system is running.
* For example, "adb shell setprop db.log.slow_query_threshold 200" will
* log all queries that take 200ms or longer to run.
* @hide
*/
public static boolean shouldLogSlowQuery(long elapsedTimeMillis) {
int slowQueryMillis = Integer.parseInt(
System.getProperty("db.log.slow_query_threshold", "-1"));
return slowQueryMillis >= 0 && elapsedTimeMillis >= slowQueryMillis;
}
/**
* Contains statistics about the active pagers in the current process.
*
* @see #nativeGetPagerStats(PagerStats)
*/
public static class PagerStats {
/** the current amount of memory checked out by sqlite using sqlite3_malloc().
* documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
*/
public int memoryUsed;
/** the number of bytes of page cache allocation which could not be sattisfied by the
* SQLITE_CONFIG_PAGECACHE buffer and where forced to overflow to sqlite3_malloc().
* The returned value includes allocations that overflowed because they where too large
* (they were larger than the "sz" parameter to SQLITE_CONFIG_PAGECACHE) and allocations
* that overflowed because no space was left in the page cache.
* documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
*/
public int pageCacheOverflow;
/** records the largest memory allocation request handed to sqlite3.
* documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
*/
public int largestMemAlloc;
/** a list of {@link DbStats} - one for each main database opened by the applications
* running on the android device
*/
public ArrayList<DbStats> dbStats;
}
/**
* contains statistics about a database
*/
public static class DbStats {
/** name of the database */
public String dbName;
/** the page size for the database */
public long pageSize;
/** the database size */
public long dbSize;
/** documented here http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html */
public int lookaside;
/** statement cache stats: hits/misses/cachesize */
public String cache;
public DbStats(String dbName, long pageCount, long pageSize, int lookaside,
int hits, int misses, int cachesize) {
this.dbName = dbName;
this.pageSize = pageSize / 1024;
dbSize = (pageCount * pageSize) / 1024;
this.lookaside = lookaside;
this.cache = hits + "/" + misses + "/" + cachesize;
}
}
/**
* return all pager and database stats for the current process.
* @return {@link PagerStats}
*/
public static PagerStats getDatabaseInfo() {
PagerStats stats = new PagerStats();
nativeGetPagerStats(stats);
stats.dbStats = SQLiteDatabase.getDbStats();
return stats;
}
/**
* Dumps detailed information about all databases used by the process.
* @param printer The printer for dumping database state.
* @param args Command-line arguments supplied to dumpsys dbinfo
*/
public static void dump(Printer printer, String[] args) {
boolean verbose = false;
for (String arg : args) {
if (arg.equals("-v")) {
verbose = true;
}
}
SQLiteDatabase.dumpAll(printer, verbose);
}
}

@ -0,0 +1,85 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database.sqlite;
import android.database.Cursor;
import androidx.core.os.CancellationSignal;
/**
* A cursor driver that uses the given query directly.
*
* @hide
*/
public final class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
private final SQLiteDatabase mDatabase;
private final String mEditTable;
private final String mSql;
private final CancellationSignal mCancellationSignal;
private SQLiteQuery mQuery;
public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable,
CancellationSignal cancellationSignal) {
mDatabase = db;
mEditTable = editTable;
mSql = sql;
mCancellationSignal = cancellationSignal;
}
public Cursor query(SQLiteDatabase.CursorFactory factory, Object[] selectionArgs) {
SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, selectionArgs, mCancellationSignal);
final Cursor cursor;
try {
if (factory == null) {
cursor = new SQLiteCursor(this, mEditTable, query);
} else {
cursor = factory.newCursor(mDatabase, this, mEditTable, query);
}
} catch (RuntimeException ex) {
query.close();
throw ex;
}
mQuery = query;
return cursor;
}
@Override
public void cursorClosed() {
// Do nothing
}
@Override
public void setBindArguments(String[] bindArgs) {
mQuery.bindAllArgsAsStrings(bindArgs);
}
@Override
public void cursorDeactivated() {
// Do nothing
}
@Override
public void cursorRequeried(Cursor cursor) {
// Do nothing
}
@Override
public String toString() {
return "SQLiteDirectCursorDriver: " + mSql;
}
}

@ -0,0 +1,185 @@
package io.requery.android.database.sqlite;
/**
* @author dhleong
*/
public class SQLiteFunction {
public final String name;
public final int numArgs;
public final SQLiteDatabase.Function callback;
// accessed from native code
final int flags;
// NOTE: from a single database connection, all calls to
// functions are serialized by SQLITE-internal mutexes,
// so we save on GC churn by reusing a single, shared instance
private final MyArgs args = new MyArgs();
private final MyResult result = new MyResult();
/**
* Create custom function.
*
* @param name The name of the sqlite3 function.
* @param numArgs The number of arguments for the function, or -1 to
* support any number of arguments.
* @param callback The callback to invoke when the function is executed.
* @param flags Extra SQLITE flags to pass when creating the function
* in native code.
*/
public SQLiteFunction(String name, int numArgs,
SQLiteDatabase.Function callback) {
this(name, numArgs, callback, 0);
}
/**
* Create custom function.
*
* @param name The name of the sqlite3 function.
* @param numArgs The number of arguments for the function, or -1 to
* support any number of arguments.
* @param callback The callback to invoke when the function is executed.
* @param flags Extra SQLITE flags to pass when creating the function
* in native code.
*/
public SQLiteFunction(String name, int numArgs,
SQLiteDatabase.Function callback,
int flags) {
if (name == null) {
throw new IllegalArgumentException("name must not be null.");
}
this.name = name;
this.numArgs = numArgs;
this.callback = callback;
this.flags = flags;
}
// Called from native.
@SuppressWarnings("unused")
private void dispatchCallback(long contextPtr, long argsPtr, int argsCount) {
result.contextPtr = contextPtr;
args.argsPtr = argsPtr;
args.argsCount = argsCount;
try {
callback.callback(args, result);
if (!result.isSet) {
result.setNull();
}
} finally {
result.contextPtr = 0;
result.isSet = false;
args.argsPtr = 0;
args.argsCount = 0;
}
}
static native byte[] nativeGetArgBlob(long argsPtr, int arg);
static native String nativeGetArgString(long argsPtr, int arg);
static native double nativeGetArgDouble(long argsPtr, int arg);
static native int nativeGetArgInt(long argsPtr, int arg);
static native long nativeGetArgLong(long argsPtr, int arg);
static native void nativeSetResultBlob(long contextPtr, byte[] result);
static native void nativeSetResultString(long contextPtr, String result);
static native void nativeSetResultDouble(long contextPtr, double result);
static native void nativeSetResultInt(long contextPtr, int result);
static native void nativeSetResultLong(long contextPtr, long result);
static native void nativeSetResultError(long contextPtr, String error);
static native void nativeSetResultNull(long contextPtr);
private static class MyArgs implements SQLiteDatabase.Function.Args {
long argsPtr;
int argsCount;
@Override
public byte[] getBlob(int arg) {
return nativeGetArgBlob(argsPtr, checkArg(arg));
}
@Override
public String getString(int arg) {
return nativeGetArgString(argsPtr, checkArg(arg));
}
@Override
public double getDouble(int arg) {
return nativeGetArgDouble(argsPtr, checkArg(arg));
}
@Override
public int getInt(int arg) {
return nativeGetArgInt(argsPtr, checkArg(arg));
}
@Override
public long getLong(int arg) {
return nativeGetArgLong(argsPtr, checkArg(arg));
}
private int checkArg(int arg) {
if (arg < 0 || arg >= argsCount) {
throw new IllegalArgumentException(
"Requested arg " + arg + " but had " + argsCount
);
}
return arg;
}
}
private static class MyResult implements SQLiteDatabase.Function.Result {
long contextPtr;
boolean isSet;
@Override
public void set(byte[] value) {
checkSet();
nativeSetResultBlob(contextPtr, value);
}
@Override
public void set(double value) {
checkSet();
nativeSetResultDouble(contextPtr, value);
}
@Override
public void set(int value) {
checkSet();
nativeSetResultInt(contextPtr, value);
}
@Override
public void set(long value) {
checkSet();
nativeSetResultLong(contextPtr, value);
}
@Override
public void set(String value) {
checkSet();
nativeSetResultString(contextPtr, value);
}
@Override
public void setError(String error) {
checkSet();
nativeSetResultError(contextPtr, error);
}
@Override
public void setNull() {
checkSet();
nativeSetResultNull(contextPtr);
}
private void checkSet() {
if (isSet) throw new IllegalStateException("Result is already set");
isSet = true;
}
}
}

@ -0,0 +1,116 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
/*
** Modified to support SQLite extensions by the SQLite developers:
** sqlite-dev@sqlite.org.
*/
package io.requery.android.database.sqlite;
import android.os.StatFs;
/**
* Provides access to SQLite functions that affect all database connection,
* such as memory management.
*
* The native code associated with SQLiteGlobal is also sets global configuration options
* using sqlite3_config() then calls sqlite3_initialize() to ensure that the SQLite
* library is properly initialized exactly once before any other framework or application
* code has a chance to run.
*
* Verbose SQLite logging is enabled if the "log.tag.SQLiteLog" property is set to "V".
* (per {@link SQLiteDebug#DEBUG_SQL_LOG}).
*
* @hide
*/
public final class SQLiteGlobal {
private static final Object sLock = new Object();
private static int sDefaultPageSize;
private static native int nativeReleaseMemory();
private SQLiteGlobal() {
}
/**
* Attempts to release memory by pruning the SQLite page cache and other
* internal data structures.
*
* @return The number of bytes that were freed.
*/
public static int releaseMemory() {
return nativeReleaseMemory();
}
// values derived from:
// https://android.googlesource.com/platform/frameworks/base.git/+/master/core/res/res/values/config.xml
/**
* Gets the default page size to use when creating a database.
*/
@SuppressWarnings("deprecation")
public static int getDefaultPageSize() {
synchronized (sLock) {
if (sDefaultPageSize == 0) {
sDefaultPageSize = new StatFs("/data").getBlockSize();
}
return 1024;
}
}
/**
* Gets the default journal mode when WAL is not in use.
*/
public static String getDefaultJournalMode() {
return "TRUNCATE";
}
/**
* Gets the journal size limit in bytes.
*/
public static int getJournalSizeLimit() {
return 524288;
}
/**
* Gets the default database synchronization mode when WAL is not in use.
*/
public static String getDefaultSyncMode() {
return "FULL";
}
/**
* Gets the database synchronization mode when in WAL mode.
*/
public static String getWALSyncMode() {
return "normal";
}
/**
* Gets the WAL auto-checkpoint integer in database pages.
*/
public static int getWALAutoCheckpoint() {
return 1000;
}
/**
* Gets the connection pool size when in WAL mode.
*/
public static int getWALConnectionPoolSize() {
return 10;
}
}

@ -0,0 +1,410 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database.sqlite;
import android.content.Context;
import android.database.sqlite.SQLiteException;
import android.util.Log;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import io.requery.android.database.DatabaseErrorHandler;
/**
* A helper class to manage database creation and version management.
*
* <p>You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and
* optionally {@link #onOpen}, and this class takes care of opening the database
* if it exists, creating it if it does not, and upgrading it as necessary.
* Transactions are used to make sure the database is always in a sensible state.
*
* <p>This class makes it easy for {@link android.content.ContentProvider}
* implementations to defer opening and upgrading the database until first use,
* to avoid blocking application startup with long-running database upgrades.
*
* <p>For an example, see the NotePadProvider class in the NotePad sample application,
* in the <em>samples/</em> directory of the SDK.</p>
*
* <p class="note"><strong>Note:</strong> this class assumes
* monotonically increasing version numbers for upgrades.</p>
*/
@SuppressWarnings("unused")
public abstract class SQLiteOpenHelper implements SupportSQLiteOpenHelper {
private static final String TAG = SQLiteOpenHelper.class.getSimpleName();
// When true, getReadableDatabase returns a read-only database if it is just being opened.
// The database handle is reopened in read/write mode when getWritableDatabase is called.
// We leave this behavior disabled in production because it is inefficient and breaks
// many applications. For debugging purposes it can be useful to turn on strict
// read-only semantics to catch applications that call getReadableDatabase when they really
// wanted getWritableDatabase.
private static final boolean DEBUG_STRICT_READONLY = false;
private final Context mContext;
private final String mName;
private final SQLiteDatabase.CursorFactory mFactory;
private final int mNewVersion;
private SQLiteDatabase mDatabase;
private boolean mIsInitializing;
private boolean mEnableWriteAheadLogging;
private final DatabaseErrorHandler mErrorHandler;
/**
* Create a helper object to create, open, and/or manage a database.
* This method always returns very quickly. The database is not actually
* created or opened until one of {@link #getWritableDatabase} or
* {@link #getReadableDatabase} is called.
*
* @param context to use to open or create the database
* @param name of the database file, or null for an in-memory database
* @param factory to use for creating cursor objects, or null for the default
* @param version number of the database (starting at 1); if the database is older,
* {@link #onUpgrade} will be used to upgrade the database; if the database is
* newer, {@link #onDowngrade} will be used to downgrade the database
*/
public SQLiteOpenHelper(Context context,
String name,
SQLiteDatabase.CursorFactory factory,
int version) {
this(context, name, factory, version, null);
}
/**
* Create a helper object to create, open, and/or manage a database.
* The database is not actually created or opened until one of
* {@link #getWritableDatabase} or {@link #getReadableDatabase} is called.
*
* <p>Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be
* used to handle corruption when sqlite reports database corruption.</p>
*
* @param context to use to open or create the database
* @param name of the database file, or null for an in-memory database
* @param factory to use for creating cursor objects, or null for the default
* @param version number of the database (starting at 1); if the database is older,
* {@link #onUpgrade} will be used to upgrade the database; if the database is
* newer, {@link #onDowngrade} will be used to downgrade the database
* @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
* corruption, or null to use the default error handler.
*/
public SQLiteOpenHelper(Context context, String name,
SQLiteDatabase.CursorFactory factory,
int version,
DatabaseErrorHandler errorHandler) {
if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
mContext = context;
mName = name;
mFactory = factory;
mNewVersion = version;
mErrorHandler = errorHandler;
}
/**
* Return the name of the SQLite database being opened, as given to
* the constructor.
*/
@Override
public String getDatabaseName() {
return mName;
}
/**
* Enables or disables the use of write-ahead logging for the database.
*
* Write-ahead logging cannot be used with read-only databases so the value of
* this flag is ignored if the database is opened read-only.
*
* @param enabled True if write-ahead logging should be enabled, false if it
* should be disabled.
*
* @see SQLiteDatabase#enableWriteAheadLogging()
*/
@Override
public void setWriteAheadLoggingEnabled(boolean enabled) {
synchronized (this) {
if (mEnableWriteAheadLogging != enabled) {
if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
if (enabled) {
mDatabase.enableWriteAheadLogging();
} else {
mDatabase.disableWriteAheadLogging();
}
}
mEnableWriteAheadLogging = enabled;
}
}
}
/**
* Create and/or open a database that will be used for reading and writing.
* The first time this is called, the database will be opened and
* {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be
* called.
*
* <p>Once opened successfully, the database is cached, so you can
* call this method every time you need to write to the database.
* (Make sure to call {@link #close} when you no longer need the database.)
* Errors such as bad permissions or a full disk may cause this method
* to fail, but future attempts may succeed if the problem is fixed.</p>
*
* <p class="caution">Database upgrade may take a long time, you
* should not call this method from the application main thread, including
* from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
*
* @throws SQLiteException if the database cannot be opened for writing
* @return a read/write database object valid until {@link #close} is called
*/
@Override
public SQLiteDatabase getWritableDatabase() {
synchronized (this) {
return getDatabaseLocked(true);
}
}
/**
* Create and/or open a database. This will be the same object returned by
* {@link #getWritableDatabase} unless some problem, such as a full disk,
* requires the database to be opened read-only. In that case, a read-only
* database object will be returned. If the problem is fixed, a future call
* to {@link #getWritableDatabase} may succeed, in which case the read-only
* database object will be closed and the read/write object will be returned
* in the future.
*
* <p class="caution">Like {@link #getWritableDatabase}, this method may
* take a long time to return, so you should not call it from the
* application main thread, including from
* {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
*
* @throws SQLiteException if the database cannot be opened
* @return a database object valid until {@link #getWritableDatabase}
* or {@link #close} is called.
*/
@Override
public SQLiteDatabase getReadableDatabase() {
synchronized (this) {
return getDatabaseLocked(false);
}
}
private SQLiteDatabase getDatabaseLocked(boolean writable) {
if (mDatabase != null) {
if (!mDatabase.isOpen()) {
// Darn! The user closed the database by calling mDatabase.close().
mDatabase = null;
} else if (!writable || !mDatabase.isReadOnly()) {
// The database is already open for business.
return mDatabase;
}
}
if (mIsInitializing) {
throw new IllegalStateException("getDatabase called recursively");
}
SQLiteDatabase db = mDatabase;
try {
mIsInitializing = true;
if (db != null) {
if (db.isReadOnly()) {
db.reopenReadWrite();
}
} else if (mName == null) {
db = SQLiteDatabase.create(null);
} else {
try {
final String path = mContext.getDatabasePath(mName).getPath();
if (DEBUG_STRICT_READONLY && !writable) {
SQLiteDatabaseConfiguration configuration =
createConfiguration(path, SQLiteDatabase.OPEN_READONLY);
db = SQLiteDatabase.openDatabase(configuration, mFactory, mErrorHandler);
} else {
int flags = mEnableWriteAheadLogging ?
SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING : 0;
flags |= SQLiteDatabase.CREATE_IF_NECESSARY;
SQLiteDatabaseConfiguration configuration =
createConfiguration(path, flags);
db = SQLiteDatabase.openDatabase(configuration, mFactory, mErrorHandler);
}
} catch (SQLiteException ex) {
if (writable) {
throw ex;
}
Log.e(TAG, "Couldn't open " + mName
+ " for writing (will try read-only):", ex);
final String path = mContext.getDatabasePath(mName).getPath();
SQLiteDatabaseConfiguration configuration =
createConfiguration(path, SQLiteDatabase.OPEN_READONLY);
db = SQLiteDatabase.openDatabase(configuration, mFactory, mErrorHandler);
}
}
onConfigure(db);
final int version = db.getVersion();
if (version != mNewVersion) {
if (db.isReadOnly()) {
throw new SQLiteException("Can't upgrade read-only database from version " +
db.getVersion() + " to " + mNewVersion + ": " + mName);
}
db.beginTransaction();
try {
if (version == 0) {
onCreate(db);
} else {
if (version > mNewVersion) {
onDowngrade(db, version, mNewVersion);
} else {
onUpgrade(db, version, mNewVersion);
}
}
db.setVersion(mNewVersion);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
onOpen(db);
if (db.isReadOnly()) {
Log.w(TAG, "Opened " + mName + " in read-only mode");
}
mDatabase = db;
return db;
} finally {
mIsInitializing = false;
if (db != null && db != mDatabase) {
db.close();
}
}
}
/**
* Close any open database object.
*/
@Override
public synchronized void close() {
if (mIsInitializing) throw new IllegalStateException("Closed during initialization");
if (mDatabase != null && mDatabase.isOpen()) {
mDatabase.close();
mDatabase = null;
}
}
/**
* Called when the database connection is being configured, to enable features
* such as write-ahead logging or foreign key support.
* <p>
* This method is called before {@link #onCreate}, {@link #onUpgrade},
* {@link #onDowngrade}, or {@link #onOpen} are called. It should not modify
* the database except to configure the database connection as required.
* </p><p>
* This method should only call methods that configure the parameters of the
* database connection, such as {@link SQLiteDatabase#enableWriteAheadLogging}
* {@link SQLiteDatabase#setForeignKeyConstraintsEnabled},
* {@link SQLiteDatabase#setLocale}, {@link SQLiteDatabase#setMaximumSize},
* or executing PRAGMA statements.
* </p>
*
* @param db The database.
*/
public void onConfigure(SQLiteDatabase db) {}
/**
* Called when the database is created for the first time. This is where the
* creation of tables and the initial population of the tables should happen.
*
* @param db The database.
*/
public abstract void onCreate(SQLiteDatabase db);
/**
* Called when the database needs to be upgraded. The implementation
* should use this method to drop tables, add tables, or do anything else it
* needs to upgrade to the new schema version.
*
* <p>
* The SQLite ALTER TABLE documentation can be found
* <a href="http://sqlite.org/lang_altertable.html">here</a>. If you add new columns
* you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
* you can use ALTER TABLE to rename the old table, then create the new table and then
* populate the new table with the contents of the old table.
* </p><p>
* This method executes within a transaction. If an exception is thrown, all changes
* will automatically be rolled back.
* </p>
*
* @param db The database.
* @param oldVersion The old database version.
* @param newVersion The new database version.
*/
public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
/**
* Called when the database needs to be downgraded. This is strictly similar to
* {@link #onUpgrade} method, but is called whenever current version is newer than requested one.
* However, this method is not abstract, so it is not mandatory for a customer to
* implement it. If not overridden, default implementation will reject downgrade and
* throws SQLiteException
*
* <p>
* This method executes within a transaction. If an exception is thrown, all changes
* will automatically be rolled back.
* </p>
*
* @param db The database.
* @param oldVersion The old database version.
* @param newVersion The new database version.
*/
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
throw new SQLiteException("Can't downgrade database from version " +
oldVersion + " to " + newVersion);
}
/**
* Called when the database has been opened. The implementation
* should check {@link SQLiteDatabase#isReadOnly} before updating the
* database.
* <p>
* This method is called after the database connection has been configured
* and after the database schema has been created, upgraded or downgraded as necessary.
* If the database connection must be configured in some way before the schema
* is created, upgraded, or downgraded, do it in {@link #onConfigure} instead.
* </p>
*
* @param db The database.
*/
public void onOpen(SQLiteDatabase db) {}
/**
* Called before the database is opened. Provides the {@link SQLiteDatabaseConfiguration}
* instance that is used to initialize the database. Override this to create a configuration
* that has custom functions or extensions.
*
* @param path to database file to open and/or create
* @param openFlags to control database access mode
* @return {@link SQLiteDatabaseConfiguration} instance, cannot be null.
*/
protected SQLiteDatabaseConfiguration createConfiguration(String path,
@SQLiteDatabase.OpenFlags int openFlags) {
return new SQLiteDatabaseConfiguration(path, openFlags);
}
}

@ -0,0 +1,246 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database.sqlite;
import androidx.core.os.CancellationSignal;
import androidx.sqlite.db.SupportSQLiteProgram;
import java.util.Arrays;
/**
* A base class for compiled SQLite programs.
* <p>
* This class is not thread-safe.
* </p>
*/
@SuppressWarnings("unused")
public abstract class SQLiteProgram extends SQLiteClosable implements SupportSQLiteProgram {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private final SQLiteDatabase mDatabase;
private final String mSql;
private final boolean mReadOnly;
private final String[] mColumnNames;
private final int mNumParameters;
private final Object[] mBindArgs;
SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs,
CancellationSignal cancellationSignalForPrepare) {
mDatabase = db;
mSql = sql.trim();
int n = SQLiteStatementType.getSqlStatementType(mSql);
switch (n) {
case SQLiteStatementType.STATEMENT_BEGIN:
case SQLiteStatementType.STATEMENT_COMMIT:
case SQLiteStatementType.STATEMENT_ABORT:
mReadOnly = false;
mColumnNames = EMPTY_STRING_ARRAY;
mNumParameters = 0;
break;
default:
boolean assumeReadOnly = (n == SQLiteStatementType.STATEMENT_SELECT);
SQLiteStatementInfo info = new SQLiteStatementInfo();
db.getThreadSession().prepare(mSql,
db.getThreadDefaultConnectionFlags(assumeReadOnly),
cancellationSignalForPrepare, info);
mReadOnly = info.readOnly;
mColumnNames = info.columnNames;
mNumParameters = info.numParameters;
break;
}
if (bindArgs != null && bindArgs.length > mNumParameters) {
throw new IllegalArgumentException("Too many bind arguments. "
+ bindArgs.length + " arguments were provided but the statement needs "
+ mNumParameters + " arguments.");
}
if (mNumParameters != 0) {
mBindArgs = new Object[mNumParameters];
if (bindArgs != null) {
System.arraycopy(bindArgs, 0, mBindArgs, 0, bindArgs.length);
}
} else {
mBindArgs = null;
}
}
final SQLiteDatabase getDatabase() {
return mDatabase;
}
final String getSql() {
return mSql;
}
final Object[] getBindArgs() {
return mBindArgs;
}
final String[] getColumnNames() {
return mColumnNames;
}
/** @hide */
protected final SQLiteSession getSession() {
return mDatabase.getThreadSession();
}
/** @hide */
protected final int getConnectionFlags() {
return mDatabase.getThreadDefaultConnectionFlags(mReadOnly);
}
/** @hide */
protected final void onCorruption() {
mDatabase.onCorruption();
}
/**
* Bind a NULL value to this statement. The value remains bound until
* {@link #clearBindings} is called.
*
* @param index The 1-based index to the parameter to bind null to
*/
@Override
public void bindNull(int index) {
bind(index, null);
}
/**
* Bind a long value to this statement. The value remains bound until
* {@link #clearBindings} is called.
*addToBindArgs
* @param index The 1-based index to the parameter to bind
* @param value The value to bind
*/
@Override
public void bindLong(int index, long value) {
bind(index, value);
}
/**
* Bind a double value to this statement. The value remains bound until
* {@link #clearBindings} is called.
*
* @param index The 1-based index to the parameter to bind
* @param value The value to bind
*/
@Override
public void bindDouble(int index, double value) {
bind(index, value);
}
/**
* Bind a String value to this statement. The value remains bound until
* {@link #clearBindings} is called.
*
* @param index The 1-based index to the parameter to bind
* @param value The value to bind, must not be null
*/
@Override
public void bindString(int index, String value) {
if (value == null) {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
}
bind(index, value);
}
/**
* Bind a byte array value to this statement. The value remains bound until
* {@link #clearBindings} is called.
*
* @param index The 1-based index to the parameter to bind
* @param value The value to bind, must not be null
*/
@Override
public void bindBlob(int index, byte[] value) {
if (value == null) {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
}
bind(index, value);
}
/**
* Binds the given Object to the given SQLiteProgram using the proper
* typing. For example, bind numbers as longs/doubles, and everything else
* as a string by call toString() on it.
*
* @param index the 1-based index to bind at
* @param value the value to bind
*/
public void bindObject(int index, Object value) {
if (value == null) {
bindNull(index);
} else if (value instanceof Double || value instanceof Float) {
bindDouble(index, ((Number)value).doubleValue());
} else if (value instanceof Number) {
bindLong(index, ((Number)value).longValue());
} else if (value instanceof Boolean) {
Boolean bool = (Boolean)value;
if (bool) {
bindLong(index, 1);
} else {
bindLong(index, 0);
}
} else if (value instanceof byte[]){
bindBlob(index, (byte[]) value);
} else {
bindString(index, value.toString());
}
}
/**
* Clears all existing bindings. Unset bindings are treated as NULL.
*/
@Override
public void clearBindings() {
if (mBindArgs != null) {
Arrays.fill(mBindArgs, null);
}
}
/**
* Given an array of String bindArgs, this method binds all of them in one single call.
*
* @param bindArgs the String array of bind args, none of which must be null.
*/
public void bindAllArgsAsStrings(String[] bindArgs) {
if (bindArgs != null) {
for (int i = bindArgs.length; i != 0; i--) {
bindString(i, bindArgs[i - 1]);
}
}
}
@Override
protected void onAllReferencesReleased() {
clearBindings();
}
private void bind(int index, Object value) {
if (index < 1 || index > mNumParameters) {
throw new IllegalArgumentException("Cannot bind argument at index "
+ index + " because the index is out of range. "
+ "The statement has " + mNumParameters + " parameters.");
}
mBindArgs[index - 1] = value;
}
}

@ -0,0 +1,86 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database.sqlite;
import android.database.sqlite.SQLiteDatabaseCorruptException;
import android.database.sqlite.SQLiteException;
import android.util.Log;
import androidx.core.os.CancellationSignal;
import androidx.core.os.OperationCanceledException;
import io.requery.android.database.CursorWindow;
/**
* Represents a query that reads the resulting rows into a {@link SQLiteQuery}.
* This class is used by {@link SQLiteCursor} and isn't useful itself.
* <p>
* This class is not thread-safe.
* </p>
*/
public final class SQLiteQuery extends SQLiteProgram {
private static final String TAG = "SQLiteQuery";
private final CancellationSignal mCancellationSignal;
SQLiteQuery(SQLiteDatabase db, String query, Object[] bindArgs,
CancellationSignal cancellationSignal) {
super(db, query, bindArgs, cancellationSignal);
mCancellationSignal = cancellationSignal;
}
/**
* Reads rows into a buffer.
*
* @param window The window to fill into
* @param startPos The start position for filling the window.
* @param requiredPos The position of a row that MUST be in the window.
* If it won't fit, then the query should discard part of what it filled.
* @param countAllRows True to count all rows that the query would
* return regardless of whether they fit in the window.
* @return Number of rows that were enumerated. Might not be all rows
* unless countAllRows is true.
*
* @throws SQLiteException if an error occurs.
* @throws OperationCanceledException if the operation was canceled.
*/
int fillWindow(CursorWindow window, int startPos, int requiredPos, boolean countAllRows) {
acquireReference();
try {
window.acquireReference();
try {
return getSession().executeForCursorWindow(getSql(), getBindArgs(),
window, startPos, requiredPos, countAllRows, getConnectionFlags(),
mCancellationSignal);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} catch (SQLiteException ex) {
Log.e(TAG, "exception: " + ex.getMessage() + "; query: " + getSql());
throw ex;
} finally {
window.releaseReference();
}
} finally {
releaseReference();
}
}
@Override
public String toString() {
return "SQLiteQuery: " + getSql();
}
}

@ -0,0 +1,612 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database.sqlite;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.os.OperationCanceledException;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;
import androidx.core.os.CancellationSignal;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Pattern;
/**
* This is a convience class that helps build SQL queries to be sent to
* {@link SQLiteDatabase} objects.
*/
@SuppressWarnings("unused")
public class SQLiteQueryBuilder {
private static final String TAG = "SQLiteQueryBuilder";
private static final Pattern sLimitPattern =
Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?");
private Map<String, String> mProjectionMap = null;
private String mTables = "";
private StringBuilder mWhereClause = null; // lazily created
private boolean mDistinct;
private SQLiteDatabase.CursorFactory mFactory;
private boolean mStrict;
public SQLiteQueryBuilder() {
mDistinct = false;
mFactory = null;
}
/**
* Mark the query as DISTINCT.
*
* @param distinct if true the query is DISTINCT, otherwise it isn't
*/
public void setDistinct(boolean distinct) {
mDistinct = distinct;
}
/**
* Returns the list of tables being queried
*
* @return the list of tables being queried
*/
public String getTables() {
return mTables;
}
/**
* Sets the list of tables to query. Multiple tables can be specified to perform a join.
* For example:
* setTables("foo, bar")
* setTables("foo LEFT OUTER JOIN bar ON (foo.id = bar.foo_id)")
*
* @param inTables the list of tables to query on
*/
public void setTables(String inTables) {
mTables = inTables;
}
/**
* Append a chunk to the WHERE clause of the query. All chunks appended are surrounded
* by parenthesis and ANDed with the selection passed to {@link #query}. The final
* WHERE clause looks like:
*
* WHERE (&lt;append chunk 1>&lt;append chunk2>) AND (&lt;query() selection parameter>)
*
* @param inWhere the chunk of text to append to the WHERE clause.
*/
public void appendWhere(CharSequence inWhere) {
if (mWhereClause == null) {
mWhereClause = new StringBuilder(inWhere.length() + 16);
}
if (mWhereClause.length() == 0) {
mWhereClause.append('(');
}
mWhereClause.append(inWhere);
}
/**
* Append a chunk to the WHERE clause of the query. All chunks appended are surrounded
* by parenthesis and ANDed with the selection passed to {@link #query}. The final
* WHERE clause looks like:
*
* WHERE (&lt;append chunk 1>&lt;append chunk2>) AND (&lt;query() selection parameter>)
*
* @param inWhere the chunk of text to append to the WHERE clause. it will be escaped
* to avoid SQL injection attacks
*/
public void appendWhereEscapeString(String inWhere) {
if (mWhereClause == null) {
mWhereClause = new StringBuilder(inWhere.length() + 16);
}
if (mWhereClause.length() == 0) {
mWhereClause.append('(');
}
DatabaseUtils.appendEscapedSQLString(mWhereClause, inWhere);
}
/**
* Sets the projection map for the query. The projection map maps
* from column names that the caller passes into query to database
* column names. This is useful for renaming columns as well as
* disambiguating column names when doing joins. For example you
* could map "name" to "people.name". If a projection map is set
* it must contain all column names the user may request, even if
* the key and value are the same.
*
* @param columnMap maps from the user column names to the database column names
*/
public void setProjectionMap(Map<String, String> columnMap) {
mProjectionMap = columnMap;
}
/**
* Sets the cursor factory to be used for the query. You can use
* one factory for all queries on a database but it is normally
* easier to specify the factory when doing this query.
*
* @param factory the factory to use.
*/
public void setCursorFactory(SQLiteDatabase.CursorFactory factory) {
mFactory = factory;
}
/**
* When set, the selection is verified against malicious arguments.
* When using this class to create a statement using
* {@link #buildQueryString(boolean, String, String[], String, String, String, String, String)},
* non-numeric limits will raise an exception. If a projection map is specified, fields
* not in that map will be ignored.
* If this class is used to execute the statement directly using
* {@link #query(SQLiteDatabase, String[], String, String[], String, String, String)}
* or
* {@link #query(SQLiteDatabase, String[], String, String[], String, String, String, String)},
* additionally also parenthesis escaping selection are caught.
*
* To summarize: To get maximum protection against malicious third party apps (for example
* content provider consumers), make sure to do the following:
* <ul>
* <li>Set this value to true</li>
* <li>Use a projection map</li>
* <li>Use one of the query overloads instead of getting the statement as a sql string</li>
* </ul>
* By default, this value is false.
*/
public void setStrict(boolean flag) {
mStrict = flag;
}
/**
* Build an SQL query string from the given clauses.
*
* @param distinct true if you want each row to be unique, false otherwise.
* @param tables The table names to compile the query against.
* @param columns A list of which columns to return. Passing null will
* return all columns, which is discouraged to prevent reading
* data from storage that isn't going to be used.
* @param where A filter declaring which rows to return, formatted as an SQL
* WHERE clause (excluding the WHERE itself). Passing null will
* return all rows for the given URL.
* @param groupBy A filter declaring how to group rows, formatted as an SQL
* GROUP BY clause (excluding the GROUP BY itself). Passing null
* will cause the rows to not be grouped.
* @param having A filter declare which row groups to include in the cursor,
* if row grouping is being used, formatted as an SQL HAVING
* clause (excluding the HAVING itself). Passing null will cause
* all row groups to be included, and is required when row
* grouping is not being used.
* @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
* (excluding the ORDER BY itself). Passing null will use the
* default sort order, which may be unordered.
* @param limit Limits the number of rows returned by the query,
* formatted as LIMIT clause. Passing null denotes no LIMIT clause.
* @return the SQL query string
*/
public static String buildQueryString(
boolean distinct, String tables, String[] columns, String where,
String groupBy, String having, String orderBy, String limit) {
if (TextUtils.isEmpty(groupBy) && !TextUtils.isEmpty(having)) {
throw new IllegalArgumentException(
"HAVING clauses are only permitted when using a groupBy clause");
}
if (!TextUtils.isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) {
throw new IllegalArgumentException("invalid LIMIT clauses:" + limit);
}
StringBuilder query = new StringBuilder(120);
query.append("SELECT ");
if (distinct) {
query.append("DISTINCT ");
}
if (columns != null && columns.length != 0) {
appendColumns(query, columns);
} else {
query.append("* ");
}
query.append("FROM ");
query.append(tables);
appendClause(query, " WHERE ", where);
appendClause(query, " GROUP BY ", groupBy);
appendClause(query, " HAVING ", having);
appendClause(query, " ORDER BY ", orderBy);
appendClause(query, " LIMIT ", limit);
return query.toString();
}
private static void appendClause(StringBuilder s, String name, String clause) {
if (!TextUtils.isEmpty(clause)) {
s.append(name);
s.append(clause);
}
}
/**
* Add the names that are non-null in columns to s, separating
* them with commas.
*/
public static void appendColumns(StringBuilder s, String[] columns) {
int n = columns.length;
for (int i = 0; i < n; i++) {
String column = columns[i];
if (column != null) {
if (i > 0) {
s.append(", ");
}
s.append(column);
}
}
s.append(' ');
}
/**
* Perform a query by combining all current settings and the
* information passed into this method.
*
* @param db the database to query on
* @param projectionIn A list of which columns to return. Passing
* null will return all columns, which is discouraged to prevent
* reading data from storage that isn't going to be used.
* @param selection A filter declaring which rows to return,
* formatted as an SQL WHERE clause (excluding the WHERE
* itself). Passing null will return all rows for the given URL.
* @param selectionArgs You may include ?s in selection, which
* will be replaced by the values from selectionArgs, in order
* that they appear in the selection. The values will be bound
* as Strings.
* @param groupBy A filter declaring how to group rows, formatted
* as an SQL GROUP BY clause (excluding the GROUP BY
* itself). Passing null will cause the rows to not be grouped.
* @param having A filter declare which row groups to include in
* the cursor, if row grouping is being used, formatted as an
* SQL HAVING clause (excluding the HAVING itself). Passing
* null will cause all row groups to be included, and is
* required when row grouping is not being used.
* @param sortOrder How to order the rows, formatted as an SQL
* ORDER BY clause (excluding the ORDER BY itself). Passing null
* will use the default sort order, which may be unordered.
* @return a cursor over the result set
* @see android.content.ContentResolver#query(android.net.Uri, String[],
* String, String[], String)
*/
public Cursor query(SQLiteDatabase db, String[] projectionIn,
String selection, String[] selectionArgs, String groupBy,
String having, String sortOrder) {
return query(db, projectionIn, selection, selectionArgs, groupBy, having, sortOrder,
null /* limit */, null /* cancellationSignal */);
}
/**
* Perform a query by combining all current settings and the
* information passed into this method.
*
* @param db the database to query on
* @param projectionIn A list of which columns to return. Passing
* null will return all columns, which is discouraged to prevent
* reading data from storage that isn't going to be used.
* @param selection A filter declaring which rows to return,
* formatted as an SQL WHERE clause (excluding the WHERE
* itself). Passing null will return all rows for the given URL.
* @param selectionArgs You may include ?s in selection, which
* will be replaced by the values from selectionArgs, in order
* that they appear in the selection. The values will be bound
* as Strings.
* @param groupBy A filter declaring how to group rows, formatted
* as an SQL GROUP BY clause (excluding the GROUP BY
* itself). Passing null will cause the rows to not be grouped.
* @param having A filter declare which row groups to include in
* the cursor, if row grouping is being used, formatted as an
* SQL HAVING clause (excluding the HAVING itself). Passing
* null will cause all row groups to be included, and is
* required when row grouping is not being used.
* @param sortOrder How to order the rows, formatted as an SQL
* ORDER BY clause (excluding the ORDER BY itself). Passing null
* will use the default sort order, which may be unordered.
* @param limit Limits the number of rows returned by the query,
* formatted as LIMIT clause. Passing null denotes no LIMIT clause.
* @return a cursor over the result set
* @see android.content.ContentResolver#query(android.net.Uri, String[],
* String, String[], String)
*/
public Cursor query(SQLiteDatabase db, String[] projectionIn,
String selection, String[] selectionArgs, String groupBy,
String having, String sortOrder, String limit) {
return query(db, projectionIn, selection, selectionArgs,
groupBy, having, sortOrder, limit, null);
}
/**
* Perform a query by combining all current settings and the
* information passed into this method.
*
* @param db the database to query on
* @param projectionIn A list of which columns to return. Passing
* null will return all columns, which is discouraged to prevent
* reading data from storage that isn't going to be used.
* @param selection A filter declaring which rows to return,
* formatted as an SQL WHERE clause (excluding the WHERE
* itself). Passing null will return all rows for the given URL.
* @param selectionArgs You may include ?s in selection, which
* will be replaced by the values from selectionArgs, in order
* that they appear in the selection. The values will be bound
* as Strings.
* @param groupBy A filter declaring how to group rows, formatted
* as an SQL GROUP BY clause (excluding the GROUP BY
* itself). Passing null will cause the rows to not be grouped.
* @param having A filter declare which row groups to include in
* the cursor, if row grouping is being used, formatted as an
* SQL HAVING clause (excluding the HAVING itself). Passing
* null will cause all row groups to be included, and is
* required when row grouping is not being used.
* @param sortOrder How to order the rows, formatted as an SQL
* ORDER BY clause (excluding the ORDER BY itself). Passing null
* will use the default sort order, which may be unordered.
* @param limit Limits the number of rows returned by the query,
* formatted as LIMIT clause. Passing null denotes no LIMIT clause.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* If the operation is canceled, then {@link OperationCanceledException} will be thrown
* when the query is executed.
* @return a cursor over the result set
* @see android.content.ContentResolver#query(android.net.Uri, String[],
* String, String[], String)
*/
public Cursor query(SQLiteDatabase db, String[] projectionIn,
String selection, String[] selectionArgs, String groupBy,
String having, String sortOrder, String limit, CancellationSignal cancellationSignal) {
if (mTables == null) {
return null;
}
if (mStrict && selection != null && selection.length() > 0) {
// Validate the user-supplied selection to detect syntactic anomalies
// in the selection string that could indicate a SQL injection attempt.
// The idea is to ensure that the selection clause is a valid SQL expression
// by compiling it twice: once wrapped in parentheses and once as
// originally specified. An attacker cannot create an expression that
// would escape the SQL expression while maintaining balanced parentheses
// in both the wrapped and original forms.
String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy,
having, sortOrder, limit);
db.validateSql(sqlForValidation, cancellationSignal); // will throw if query is invalid
}
String sql = buildQuery(
projectionIn, selection, groupBy, having,
sortOrder, limit);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Performing query: " + sql);
}
return db.rawQueryWithFactory(
mFactory, sql, selectionArgs,
SQLiteDatabase.findEditTable(mTables),
cancellationSignal); // will throw if query is invalid
}
/**
* Construct a SELECT statement suitable for use in a group of
* SELECT statements that will be joined through UNION operators
* in buildUnionQuery.
*
* @param projectionIn A list of which columns to return. Passing
* null will return all columns, which is discouraged to
* prevent reading data from storage that isn't going to be
* used.
* @param selection A filter declaring which rows to return,
* formatted as an SQL WHERE clause (excluding the WHERE
* itself). Passing null will return all rows for the given
* URL.
* @param groupBy A filter declaring how to group rows, formatted
* as an SQL GROUP BY clause (excluding the GROUP BY itself).
* Passing null will cause the rows to not be grouped.
* @param having A filter declare which row groups to include in
* the cursor, if row grouping is being used, formatted as an
* SQL HAVING clause (excluding the HAVING itself). Passing
* null will cause all row groups to be included, and is
* required when row grouping is not being used.
* @param sortOrder How to order the rows, formatted as an SQL
* ORDER BY clause (excluding the ORDER BY itself). Passing null
* will use the default sort order, which may be unordered.
* @param limit Limits the number of rows returned by the query,
* formatted as LIMIT clause. Passing null denotes no LIMIT clause.
* @return the resulting SQL SELECT statement
*/
public String buildQuery(
String[] projectionIn, String selection, String groupBy,
String having, String sortOrder, String limit) {
String[] projection = computeProjection(projectionIn);
StringBuilder where = new StringBuilder();
boolean hasBaseWhereClause = mWhereClause != null && mWhereClause.length() > 0;
if (hasBaseWhereClause) {
where.append(mWhereClause.toString());
where.append(')');
}
// Tack on the user's selection, if present.
if (selection != null && selection.length() > 0) {
if (hasBaseWhereClause) {
where.append(" AND ");
}
where.append('(');
where.append(selection);
where.append(')');
}
return buildQueryString(
mDistinct, mTables, projection, where.toString(),
groupBy, having, sortOrder, limit);
}
/**
* Construct a SELECT statement suitable for use in a group of
* SELECT statements that will be joined through UNION operators
* in buildUnionQuery.
*
* @param typeDiscriminatorColumn the name of the result column
* whose cells will contain the name of the table from which
* each row was drawn.
* @param unionColumns the names of the columns to appear in the
* result. This may include columns that do not appear in the
* table this SELECT is querying (i.e. mTables), but that do
* appear in one of the other tables in the UNION query that we
* are constructing.
* @param columnsPresentInTable a Set of the names of the columns
* that appear in this table (i.e. in the table whose name is
* mTables). Since columns in unionColumns include columns that
* appear only in other tables, we use this array to distinguish
* which ones actually are present. Other columns will have
* NULL values for results from this subquery.
* @param computedColumnsOffset all columns in unionColumns before
* this index are included under the assumption that they're
* computed and therefore won't appear in columnsPresentInTable,
* e.g. "date * 1000 as normalized_date"
* @param typeDiscriminatorValue the value used for the
* type-discriminator column in this subquery
* @param selection A filter declaring which rows to return,
* formatted as an SQL WHERE clause (excluding the WHERE
* itself). Passing null will return all rows for the given
* URL.
* @param groupBy A filter declaring how to group rows, formatted
* as an SQL GROUP BY clause (excluding the GROUP BY itself).
* Passing null will cause the rows to not be grouped.
* @param having A filter declare which row groups to include in
* the cursor, if row grouping is being used, formatted as an
* SQL HAVING clause (excluding the HAVING itself). Passing
* null will cause all row groups to be included, and is
* required when row grouping is not being used.
* @return the resulting SQL SELECT statement
*/
public String buildUnionSubQuery(
String typeDiscriminatorColumn,
String[] unionColumns,
Set<String> columnsPresentInTable,
int computedColumnsOffset,
String typeDiscriminatorValue,
String selection,
String groupBy,
String having) {
int unionColumnsCount = unionColumns.length;
String[] projectionIn = new String[unionColumnsCount];
for (int i = 0; i < unionColumnsCount; i++) {
String unionColumn = unionColumns[i];
if (unionColumn.equals(typeDiscriminatorColumn)) {
projectionIn[i] = "'" + typeDiscriminatorValue + "' AS "
+ typeDiscriminatorColumn;
} else if (i <= computedColumnsOffset
|| columnsPresentInTable.contains(unionColumn)) {
projectionIn[i] = unionColumn;
} else {
projectionIn[i] = "NULL AS " + unionColumn;
}
}
return buildQuery(
projectionIn, selection, groupBy, having,
null /* sortOrder */,
null /* limit */);
}
/**
* Given a set of subqueries, all of which are SELECT statements,
* construct a query that returns the union of what those
* subqueries return.
* @param subQueries an array of SQL SELECT statements, all of
* which must have the same columns as the same positions in
* their results
* @param sortOrder How to order the rows, formatted as an SQL
* ORDER BY clause (excluding the ORDER BY itself). Passing
* null will use the default sort order, which may be unordered.
* @param limit The limit clause, which applies to the entire union result set
*
* @return the resulting SQL SELECT statement
*/
public String buildUnionQuery(String[] subQueries, String sortOrder, String limit) {
StringBuilder query = new StringBuilder(128);
int subQueryCount = subQueries.length;
String unionOperator = mDistinct ? " UNION " : " UNION ALL ";
for (int i = 0; i < subQueryCount; i++) {
if (i > 0) {
query.append(unionOperator);
}
query.append(subQueries[i]);
}
appendClause(query, " ORDER BY ", sortOrder);
appendClause(query, " LIMIT ", limit);
return query.toString();
}
private String[] computeProjection(String[] projectionIn) {
if (projectionIn != null && projectionIn.length > 0) {
if (mProjectionMap != null) {
String[] projection = new String[projectionIn.length];
int length = projectionIn.length;
for (int i = 0; i < length; i++) {
String userColumn = projectionIn[i];
String column = mProjectionMap.get(userColumn);
if (column != null) {
projection[i] = column;
continue;
}
if (!mStrict &&
( userColumn.contains(" AS ") || userColumn.contains(" as "))) {
/* A column alias already exist */
projection[i] = userColumn;
continue;
}
throw new IllegalArgumentException("Invalid column "
+ projectionIn[i]);
}
return projection;
} else {
return projectionIn;
}
} else if (mProjectionMap != null) {
// Return all columns in projection map.
Set<Entry<String, String>> entrySet = mProjectionMap.entrySet();
String[] projection = new String[entrySet.size()];
Iterator<Entry<String, String>> entryIter = entrySet.iterator();
int i = 0;
while (entryIter.hasNext()) {
Entry<String, String> entry = entryIter.next();
// Don't include the _count column when people ask for no projection.
if (entry.getKey().equals(BaseColumns._COUNT)) {
continue;
}
projection[i++] = entry.getValue();
}
return projection;
}
return null;
}
}

@ -0,0 +1,975 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
/*
** Modified to support SQLite extensions by the SQLite developers:
** sqlite-dev@sqlite.org.
*/
package io.requery.android.database.sqlite;
import android.annotation.SuppressLint;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteTransactionListener;
import android.os.ParcelFileDescriptor;
import androidx.core.os.CancellationSignal;
import androidx.core.os.OperationCanceledException;
import io.requery.android.database.CursorWindow;
/**
* Provides a single client the ability to use a database.
*
* <h2>About database sessions</h2>
* <p>
* Database access is always performed using a session. The session
* manages the lifecycle of transactions and database connections.
* </p><p>
* Sessions can be used to perform both read-only and read-write operations.
* There is some advantage to knowing when a session is being used for
* read-only purposes because the connection pool can optimize the use
* of the available connections to permit multiple read-only operations
* to execute in parallel whereas read-write operations may need to be serialized.
* </p><p>
* When <em>Write Ahead Logging (WAL)</em> is enabled, the database can
* execute simultaneous read-only and read-write transactions, provided that
* at most one read-write transaction is performed at a time. When WAL is not
* enabled, read-only transactions can execute in parallel but read-write
* transactions are mutually exclusive.
* </p>
*
* <h2>Ownership and concurrency guarantees</h2>
* <p>
* Session objects are not thread-safe. In fact, session objects are thread-bound.
* The {@link SQLiteDatabase} uses a thread-local variable to associate a session
* with each thread for the use of that thread alone. Consequently, each thread
* has its own session object and therefore its own transaction state independent
* of other threads.
* </p><p>
* A thread has at most one session per database. This constraint ensures that
* a thread can never use more than one database connection at a time for a
* given database. As the number of available database connections is limited,
* if a single thread tried to acquire multiple connections for the same database
* at the same time, it might deadlock. Therefore we allow there to be only
* one session (so, at most one connection) per thread per database.
* </p>
*
* <h2>Transactions</h2>
* <p>
* There are two kinds of transaction: implicit transactions and explicit
* transactions.
* </p><p>
* An implicit transaction is created whenever a database operation is requested
* and there is no explicit transaction currently in progress. An implicit transaction
* only lasts for the duration of the database operation in question and then it
* is ended. If the database operation was successful, then its changes are committed.
* </p><p>
* An explicit transaction is started by calling {@link #beginTransaction} and
* specifying the desired transaction mode. Once an explicit transaction has begun,
* all subsequent database operations will be performed as part of that transaction.
* To end an explicit transaction, first call {@link #setTransactionSuccessful} if the
* transaction was successful, then call {@link #endTransaction}. If the transaction was
* marked successful, its changes will be committed, otherwise they will be rolled back.
* </p><p>
* Explicit transactions can also be nested. A nested explicit transaction is
* started with {@link #beginTransaction}, marked successful with
* {@link #setTransactionSuccessful}and ended with {@link #endTransaction}.
* If any nested transaction is not marked successful, then the entire transaction
* including all of its nested transactions will be rolled back
* when the outermost transaction is ended.
* </p><p>
* To improve concurrency, an explicit transaction can be yielded by calling
* {@link #yieldTransaction}. If there is contention for use of the database,
* then yielding ends the current transaction, commits its changes, releases the
* database connection for use by another session for a little while, and starts a
* new transaction with the same properties as the original one.
* Changes committed by {@link #yieldTransaction} cannot be rolled back.
* </p><p>
* When a transaction is started, the client can provide a {@link SQLiteTransactionListener}
* to listen for notifications of transaction-related events.
* </p><p>
* Recommended usage:
* <code><pre>
* // First, begin the transaction.
* session.beginTransaction(SQLiteSession.TRANSACTION_MODE_DEFERRED, 0);
* try {
* // Then do stuff...
* session.execute("INSERT INTO ...", null, 0);
*
* // As the very last step before ending the transaction, mark it successful.
* session.setTransactionSuccessful();
* } finally {
* // Finally, end the transaction.
* // This statement will commit the transaction if it was marked successful or
* // roll it back otherwise.
* session.endTransaction();
* }
* </pre></code>
* </p>
*
* <h2>Database connections</h2>
* <p>
* A {@link SQLiteDatabase} can have multiple active sessions at the same
* time. Each session acquires and releases connections to the database
* as needed to perform each requested database transaction. If all connections
* are in use, then database transactions on some sessions will block until a
* connection becomes available.
* </p><p>
* The session acquires a single database connection only for the duration
* of a single (implicit or explicit) database transaction, then releases it.
* This characteristic allows a small pool of database connections to be shared
* efficiently by multiple sessions as long as they are not all trying to perform
* database transactions at the same time.
* </p>
*
* <h2>Responsiveness</h2>
* <p>
* Because there are a limited number of database connections and the session holds
* a database connection for the entire duration of a database transaction,
* it is important to keep transactions short. This is especially important
* for read-write transactions since they may block other transactions
* from executing. Consider calling {@link #yieldTransaction} periodically
* during long-running transactions.
* </p><p>
* Another important consideration is that transactions that take too long to
* run may cause the application UI to become unresponsive. Even if the transaction
* is executed in a background thread, the user will get bored and
* frustrated if the application shows no data for several seconds while
* a transaction runs.
* </p><p>
* Guidelines:
* <ul>
* <li>Do not perform database transactions on the UI thread.</li>
* <li>Keep database transactions as short as possible.</li>
* <li>Simple queries often run faster than complex queries.</li>
* <li>Measure the performance of your database transactions.</li>
* <li>Consider what will happen when the size of the data set grows.
* A query that works well on 100 rows may struggle with 10,000.</li>
* </ul>
*
* <h2>Reentrance</h2>
* <p>
* This class must tolerate reentrant execution of SQLite operations because
* triggers may call custom SQLite functions that perform additional queries.
* </p>
*
* @hide
*/
@SuppressWarnings({"unused", "JavaDoc"})
@SuppressLint("Assert")
public final class SQLiteSession {
private final SQLiteConnectionPool mConnectionPool;
private SQLiteConnection mConnection;
private int mConnectionFlags;
private int mConnectionUseCount;
private Transaction mTransactionPool;
private Transaction mTransactionStack;
/**
* Transaction mode: Deferred.
* <p>
* In a deferred transaction, no locks are acquired on the database
* until the first operation is performed. If the first operation is
* read-only, then a <code>SHARED</code> lock is acquired, otherwise
* a <code>RESERVED</code> lock is acquired.
* </p><p>
* While holding a <code>SHARED</code> lock, this session is only allowed to
* read but other sessions are allowed to read or write.
* While holding a <code>RESERVED</code> lock, this session is allowed to read
* or write but other sessions are only allowed to read.
* </p><p>
* Because the lock is only acquired when needed in a deferred transaction,
* it is possible for another session to write to the database first before
* this session has a chance to do anything.
* </p><p>
* Corresponds to the SQLite <code>BEGIN DEFERRED</code> transaction mode.
* </p>
*/
public static final int TRANSACTION_MODE_DEFERRED = 0;
/**
* Transaction mode: Immediate.
* <p>
* When an immediate transaction begins, the session acquires a
* <code>RESERVED</code> lock.
* </p><p>
* While holding a <code>RESERVED</code> lock, this session is allowed to read
* or write but other sessions are only allowed to read.
* </p><p>
* Corresponds to the SQLite <code>BEGIN IMMEDIATE</code> transaction mode.
* </p>
*/
public static final int TRANSACTION_MODE_IMMEDIATE = 1;
/**
* Transaction mode: Exclusive.
* <p>
* When an exclusive transaction begins, the session acquires an
* <code>EXCLUSIVE</code> lock.
* </p><p>
* While holding an <code>EXCLUSIVE</code> lock, this session is allowed to read
* or write but no other sessions are allowed to access the database.
* </p><p>
* Corresponds to the SQLite <code>BEGIN EXCLUSIVE</code> transaction mode.
* </p>
*/
public static final int TRANSACTION_MODE_EXCLUSIVE = 2;
/**
* Creates a session bound to the specified connection pool.
*
* @param connectionPool The connection pool.
*/
public SQLiteSession(SQLiteConnectionPool connectionPool) {
if (connectionPool == null) {
throw new IllegalArgumentException("connectionPool must not be null");
}
mConnectionPool = connectionPool;
}
/**
* Returns true if the session has a transaction in progress.
*
* @return True if the session has a transaction in progress.
*/
public boolean hasTransaction() {
return mTransactionStack != null;
}
/**
* Returns true if the session has a nested transaction in progress.
*
* @return True if the session has a nested transaction in progress.
*/
public boolean hasNestedTransaction() {
return mTransactionStack != null && mTransactionStack.mParent != null;
}
/**
* Returns true if the session has an active database connection.
*
* @return True if the session has an active database connection.
*/
public boolean hasConnection() {
return mConnection != null;
}
/**
* Begins a transaction.
* <p>
* Transactions may nest. If the transaction is not in progress,
* then a database connection is obtained and a new transaction is started.
* Otherwise, a nested transaction is started.
* </p><p>
* Each call to {@link #beginTransaction} must be matched exactly by a call
* to {@link #endTransaction}. To mark a transaction as successful,
* call {@link #setTransactionSuccessful} before calling {@link #endTransaction}.
* If the transaction is not successful, or if any of its nested
* transactions were not successful, then the entire transaction will
* be rolled back when the outermost transaction is ended.
* </p>
*
* @param transactionMode The transaction mode. One of: {@link #TRANSACTION_MODE_DEFERRED},
* {@link #TRANSACTION_MODE_IMMEDIATE}, or {@link #TRANSACTION_MODE_EXCLUSIVE}.
* Ignored when creating a nested transaction.
* @param transactionListener The transaction listener, or null if none.
* @param connectionFlags The connection flags to use if a connection must be
* acquired by this operation. Refer to {@link SQLiteConnectionPool}.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
*
* @throws IllegalStateException if {@link #setTransactionSuccessful} has already been
* called for the current transaction.
* @throws SQLiteException if an error occurs.
* @throws OperationCanceledException if the operation was canceled.
*
* @see #setTransactionSuccessful
* @see #yieldTransaction
* @see #endTransaction
*/
public void beginTransaction(int transactionMode,
SQLiteTransactionListener transactionListener,
int connectionFlags,
CancellationSignal cancellationSignal) {
throwIfTransactionMarkedSuccessful();
beginTransactionUnchecked(transactionMode, transactionListener, connectionFlags,
cancellationSignal);
}
private void beginTransactionUnchecked(int transactionMode,
SQLiteTransactionListener transactionListener, int connectionFlags,
CancellationSignal cancellationSignal) {
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
}
if (mTransactionStack == null) {
acquireConnection(null, connectionFlags, cancellationSignal); // might throw
}
try {
// Set up the transaction such that we can back out safely
// in case we fail part way.
if (mTransactionStack == null) {
// Execute SQL might throw a runtime exception.
switch (transactionMode) {
case TRANSACTION_MODE_IMMEDIATE:
mConnection.execute("BEGIN IMMEDIATE;", null,
cancellationSignal); // might throw
break;
case TRANSACTION_MODE_EXCLUSIVE:
mConnection.execute("BEGIN EXCLUSIVE;", null,
cancellationSignal); // might throw
break;
default:
mConnection.execute("BEGIN;", null, cancellationSignal); // might throw
break;
}
}
// Listener might throw a runtime exception.
if (transactionListener != null) {
try {
transactionListener.onBegin(); // might throw
} catch (RuntimeException ex) {
if (mTransactionStack == null) {
mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
}
throw ex;
}
}
// Bookkeeping can't throw, except an OOM, which is just too bad...
Transaction transaction = obtainTransaction(transactionMode, transactionListener);
transaction.mParent = mTransactionStack;
mTransactionStack = transaction;
} finally {
if (mTransactionStack == null) {
releaseConnection(); // might throw
}
}
}
/**
* Marks the current transaction as having completed successfully.
* <p>
* This method can be called at most once between {@link #beginTransaction} and
* {@link #endTransaction} to indicate that the changes made by the transaction should be
* committed. If this method is not called, the changes will be rolled back
* when the transaction is ended.
* </p>
*
* @throws IllegalStateException if there is no current transaction, or if
* {@link #setTransactionSuccessful} has already been called for the current transaction.
*
* @see #beginTransaction
* @see #endTransaction
*/
public void setTransactionSuccessful() {
throwIfNoTransaction();
throwIfTransactionMarkedSuccessful();
mTransactionStack.mMarkedSuccessful = true;
}
/**
* Ends the current transaction and commits or rolls back changes.
* <p>
* If this is the outermost transaction (not nested within any other
* transaction), then the changes are committed if {@link #setTransactionSuccessful}
* was called or rolled back otherwise.
* </p><p>
* This method must be called exactly once for each call to {@link #beginTransaction}.
* </p>
*
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
*
* @throws IllegalStateException if there is no current transaction.
* @throws SQLiteException if an error occurs.
* @throws OperationCanceledException if the operation was canceled.
*
* @see #beginTransaction
* @see #setTransactionSuccessful
* @see #yieldTransaction
*/
public void endTransaction(CancellationSignal cancellationSignal) {
throwIfNoTransaction();
assert mConnection != null;
endTransactionUnchecked(cancellationSignal, false);
}
private void endTransactionUnchecked(CancellationSignal cancellationSignal, boolean yielding) {
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
}
final Transaction top = mTransactionStack;
boolean successful = (top.mMarkedSuccessful || yielding) && !top.mChildFailed;
RuntimeException listenerException = null;
final SQLiteTransactionListener listener = top.mListener;
if (listener != null) {
try {
if (successful) {
listener.onCommit(); // might throw
} else {
listener.onRollback(); // might throw
}
} catch (RuntimeException ex) {
listenerException = ex;
successful = false;
}
}
mTransactionStack = top.mParent;
recycleTransaction(top);
if (mTransactionStack != null) {
if (!successful) {
mTransactionStack.mChildFailed = true;
}
} else {
try {
if (successful) {
mConnection.execute("COMMIT;", null, cancellationSignal); // might throw
} else {
mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
}
} finally {
releaseConnection(); // might throw
}
}
if (listenerException != null) {
throw listenerException;
}
}
/**
* Temporarily ends a transaction to let other threads have use of
* the database. Begins a new transaction after a specified delay.
* <p>
* If there are other threads waiting to acquire connections,
* then the current transaction is committed and the database
* connection is released. After a short delay, a new transaction
* is started.
* </p><p>
* The transaction is assumed to be successful so far. Do not call
* {@link #setTransactionSuccessful()} before calling this method.
* This method will fail if the transaction has already been marked
* successful.
* </p><p>
* The changes that were committed by a yield cannot be rolled back later.
* </p><p>
* Before this method was called, there must already have been
* a transaction in progress. When this method returns, there will
* still be a transaction in progress, either the same one as before
* or a new one if the transaction was actually yielded.
* </p><p>
* This method should not be called when there is a nested transaction
* in progress because it is not possible to yield a nested transaction.
* If <code>throwIfNested</code> is true, then attempting to yield
* a nested transaction will throw {@link IllegalStateException}, otherwise
* the method will return <code>false</code> in that case.
* </p><p>
* If there is no nested transaction in progress but a previous nested
* transaction failed, then the transaction is not yielded (because it
* must be rolled back) and this method returns <code>false</code>.
* </p>
*
* @param sleepAfterYieldDelayMillis A delay time to wait after yielding
* the database connection to allow other threads some time to run.
* If the value is less than or equal to zero, there will be no additional
* delay beyond the time it will take to begin a new transaction.
* @param throwIfUnsafe If true, then instead of returning false when no
* transaction is in progress, a nested transaction is in progress, or when
* the transaction has already been marked successful, throws {@link IllegalStateException}.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* @return True if the transaction was actually yielded.
*
* @throws IllegalStateException if <code>throwIfNested</code> is true and
* there is no current transaction, there is a nested transaction in progress or
* if {@link #setTransactionSuccessful} has already been called for the current transaction.
* @throws SQLiteException if an error occurs.
* @throws OperationCanceledException if the operation was canceled.
*
* @see #beginTransaction
* @see #endTransaction
*/
public boolean yieldTransaction(long sleepAfterYieldDelayMillis, boolean throwIfUnsafe,
CancellationSignal cancellationSignal) {
if (throwIfUnsafe) {
throwIfNoTransaction();
throwIfTransactionMarkedSuccessful();
throwIfNestedTransaction();
} else {
if (mTransactionStack == null || mTransactionStack.mMarkedSuccessful
|| mTransactionStack.mParent != null) {
return false;
}
}
assert mConnection != null;
if (mTransactionStack.mChildFailed) {
return false;
}
return yieldTransactionUnchecked(sleepAfterYieldDelayMillis,
cancellationSignal); // might throw
}
private boolean yieldTransactionUnchecked(long sleepAfterYieldDelayMillis,
CancellationSignal cancellationSignal) {
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
}
if (!mConnectionPool.shouldYieldConnection(mConnection, mConnectionFlags)) {
return false;
}
final int transactionMode = mTransactionStack.mMode;
final SQLiteTransactionListener listener = mTransactionStack.mListener;
final int connectionFlags = mConnectionFlags;
endTransactionUnchecked(cancellationSignal, true); // might throw
if (sleepAfterYieldDelayMillis > 0) {
try {
Thread.sleep(sleepAfterYieldDelayMillis);
} catch (InterruptedException ex) {
// we have been interrupted, that's all we need to do
}
}
beginTransactionUnchecked(transactionMode, listener, connectionFlags,
cancellationSignal); // might throw
return true;
}
/**
* Prepares a statement for execution but does not bind its parameters or execute it.
* <p>
* This method can be used to check for syntax errors during compilation
* prior to execution of the statement. If the {@code outStatementInfo} argument
* is not null, the provided {@link SQLiteStatementInfo} object is populated
* with information about the statement.
* </p><p>
* A prepared statement makes no reference to the arguments that may eventually
* be bound to it, consequently it it possible to cache certain prepared statements
* such as SELECT or INSERT/UPDATE statements. If the statement is cacheable,
* then it will be stored in the cache for later and reused if possible.
* </p>
*
* @param sql The SQL statement to prepare.
* @param connectionFlags The connection flags to use if a connection must be
* acquired by this operation. Refer to {@link SQLiteConnectionPool}.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* @param outStatementInfo The {@link SQLiteStatementInfo} object to populate
* with information about the statement, or null if none.
*
* @throws SQLiteException if an error occurs, such as a syntax error.
* @throws OperationCanceledException if the operation was canceled.
*/
public void prepare(String sql, int connectionFlags, CancellationSignal cancellationSignal,
SQLiteStatementInfo outStatementInfo) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
mConnection.prepare(sql, outStatementInfo); // might throw
} finally {
releaseConnection(); // might throw
}
}
/**
* Executes a statement that does not return a result.
*
* @param sql The SQL statement to execute.
* @param bindArgs The arguments to bind, or null if none.
* @param connectionFlags The connection flags to use if a connection must be
* acquired by this operation. Refer to {@link SQLiteConnectionPool}.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
*
* @throws SQLiteException if an error occurs, such as a syntax error
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
public void execute(String sql, Object[] bindArgs, int connectionFlags,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
return;
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
mConnection.execute(sql, bindArgs, cancellationSignal); // might throw
} finally {
releaseConnection(); // might throw
}
}
/**
* Executes a statement that returns a single <code>long</code> result.
*
* @param sql The SQL statement to execute.
* @param bindArgs The arguments to bind, or null if none.
* @param connectionFlags The connection flags to use if a connection must be
* acquired by this operation. Refer to {@link SQLiteConnectionPool}.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* @return The value of the first column in the first row of the result set
* as a <code>long</code>, or zero if none.
*
* @throws SQLiteException if an error occurs, such as a syntax error
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
public long executeForLong(String sql, Object[] bindArgs, int connectionFlags,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
return 0;
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
return mConnection.executeForLong(sql, bindArgs, cancellationSignal); // might throw
} finally {
releaseConnection(); // might throw
}
}
/**
* Executes a statement that returns a single {@link String} result.
*
* @param sql The SQL statement to execute.
* @param bindArgs The arguments to bind, or null if none.
* @param connectionFlags The connection flags to use if a connection must be
* acquired by this operation. Refer to {@link SQLiteConnectionPool}.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* @return The value of the first column in the first row of the result set
* as a <code>String</code>, or null if none.
*
* @throws SQLiteException if an error occurs, such as a syntax error
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
public String executeForString(String sql, Object[] bindArgs, int connectionFlags,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
return null;
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
return mConnection.executeForString(sql, bindArgs, cancellationSignal); // might throw
} finally {
releaseConnection(); // might throw
}
}
/**
* Executes a statement that returns a single BLOB result as a
* file descriptor to a shared memory region.
*
* @param sql The SQL statement to execute.
* @param bindArgs The arguments to bind, or null if none.
* @param connectionFlags The connection flags to use if a connection must be
* acquired by this operation. Refer to {@link SQLiteConnectionPool}.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* @return The file descriptor for a shared memory region that contains
* the value of the first column in the first row of the result set as a BLOB,
* or null if none.
*
* @throws SQLiteException if an error occurs, such as a syntax error
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs,
int connectionFlags, CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
return null;
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
return mConnection.executeForBlobFileDescriptor(sql, bindArgs,
cancellationSignal); // might throw
} finally {
releaseConnection(); // might throw
}
}
/**
* Executes a statement that returns a count of the number of rows
* that were changed. Use for UPDATE or DELETE SQL statements.
*
* @param sql The SQL statement to execute.
* @param bindArgs The arguments to bind, or null if none.
* @param connectionFlags The connection flags to use if a connection must be
* acquired by this operation. Refer to {@link SQLiteConnectionPool}.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* @return The number of rows that were changed.
*
* @throws SQLiteException if an error occurs, such as a syntax error
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
public int executeForChangedRowCount(String sql, Object[] bindArgs, int connectionFlags,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
return 0;
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
return mConnection.executeForChangedRowCount(sql, bindArgs,
cancellationSignal); // might throw
} finally {
releaseConnection(); // might throw
}
}
/**
* Executes a statement that returns the row id of the last row inserted
* by the statement. Use for INSERT SQL statements.
*
* @param sql The SQL statement to execute.
* @param bindArgs The arguments to bind, or null if none.
* @param connectionFlags The connection flags to use if a connection must be
* acquired by this operation. Refer to {@link SQLiteConnectionPool}.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* @return The row id of the last row that was inserted, or 0 if none.
*
* @throws SQLiteException if an error occurs, such as a syntax error
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
return 0;
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
return mConnection.executeForLastInsertedRowId(sql, bindArgs,
cancellationSignal); // might throw
} finally {
releaseConnection(); // might throw
}
}
/**
* Executes a statement and populates the specified {@link CursorWindow}
* with a range of results. Returns the number of rows that were counted
* during query execution.
*
* @param sql The SQL statement to execute.
* @param bindArgs The arguments to bind, or null if none.
* @param window The cursor window to clear and fill.
* @param startPos The start position for filling the window.
* @param requiredPos The position of a row that MUST be in the window.
* If it won't fit, then the query should discard part of what it filled
* so that it does. Must be greater than or equal to <code>startPos</code>.
* @param countAllRows True to count all rows that the query would return
* regagless of whether they fit in the window.
* @param connectionFlags The connection flags to use if a connection must be
* acquired by this operation. Refer to {@link SQLiteConnectionPool}.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* @return The number of rows that were counted during query execution. Might
* not be all rows in the result set unless <code>countAllRows</code> is true.
*
* @throws SQLiteException if an error occurs, such as a syntax error
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
public int executeForCursorWindow(String sql, Object[] bindArgs,
CursorWindow window, int startPos, int requiredPos,
boolean countAllRows,
int connectionFlags,
CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
if (window == null) {
throw new IllegalArgumentException("window must not be null.");
}
if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
window.clear();
return 0;
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
return mConnection.executeForCursorWindow(sql, bindArgs,
window, startPos, requiredPos, countAllRows,
cancellationSignal); // might throw
} finally {
releaseConnection(); // might throw
}
}
/**
* Performs special reinterpretation of certain SQL statements such as "BEGIN",
* "COMMIT" and "ROLLBACK" to ensure that transaction state invariants are
* maintained.
*
* This function is mainly used to support legacy apps that perform their
* own transactions by executing raw SQL rather than calling {@link #beginTransaction}
* and the like.
*
* @param sql The SQL statement to execute.
* @param bindArgs The arguments to bind, or null if none.
* @param connectionFlags The connection flags to use if a connection must be
* acquired by this operation. Refer to {@link SQLiteConnectionPool}.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* @return True if the statement was of a special form that was handled here,
* false otherwise.
*
* @throws SQLiteException if an error occurs, such as a syntax error
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
private boolean executeSpecial(String sql, Object[] bindArgs, int connectionFlags,
CancellationSignal cancellationSignal) {
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
}
final int type = SQLiteStatementType.getSqlStatementType(sql);
switch (type) {
case SQLiteStatementType.STATEMENT_BEGIN:
beginTransaction(TRANSACTION_MODE_EXCLUSIVE, null, connectionFlags,
cancellationSignal);
return true;
case SQLiteStatementType.STATEMENT_COMMIT:
setTransactionSuccessful();
endTransaction(cancellationSignal);
return true;
case SQLiteStatementType.STATEMENT_ABORT:
endTransaction(cancellationSignal);
return true;
}
return false;
}
private void acquireConnection(String sql, int connectionFlags,
CancellationSignal cancellationSignal) {
if (mConnection == null) {
assert mConnectionUseCount == 0;
mConnection = mConnectionPool.acquireConnection(sql, connectionFlags,
cancellationSignal); // might throw
mConnectionFlags = connectionFlags;
}
mConnectionUseCount += 1;
}
private void releaseConnection() {
assert mConnection != null;
assert mConnectionUseCount > 0;
if (--mConnectionUseCount == 0) {
try {
mConnectionPool.releaseConnection(mConnection); // might throw
} finally {
mConnection = null;
}
}
}
private void throwIfNoTransaction() {
if (mTransactionStack == null) {
throw new IllegalStateException("Cannot perform this operation because "
+ "there is no current transaction.");
}
}
private void throwIfTransactionMarkedSuccessful() {
if (mTransactionStack != null && mTransactionStack.mMarkedSuccessful) {
throw new IllegalStateException("Cannot perform this operation because "
+ "the transaction has already been marked successful. The only "
+ "thing you can do now is call endTransaction().");
}
}
private void throwIfNestedTransaction() {
if (hasNestedTransaction()) {
throw new IllegalStateException("Cannot perform this operation because "
+ "a nested transaction is in progress.");
}
}
private Transaction obtainTransaction(int mode, SQLiteTransactionListener listener) {
Transaction transaction = mTransactionPool;
if (transaction != null) {
mTransactionPool = transaction.mParent;
transaction.mParent = null;
transaction.mMarkedSuccessful = false;
transaction.mChildFailed = false;
} else {
transaction = new Transaction();
}
transaction.mMode = mode;
transaction.mListener = listener;
return transaction;
}
private void recycleTransaction(Transaction transaction) {
transaction.mParent = mTransactionPool;
transaction.mListener = null;
mTransactionPool = transaction;
}
private static final class Transaction {
public Transaction mParent;
public int mMode;
public SQLiteTransactionListener mListener;
public boolean mMarkedSuccessful;
public boolean mChildFailed;
}
}

@ -0,0 +1,172 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database.sqlite;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabaseCorruptException;
import android.database.sqlite.SQLiteDoneException;
import android.os.ParcelFileDescriptor;
import androidx.sqlite.db.SupportSQLiteStatement;
/**
* Represents a statement that can be executed against a database. The statement
* cannot return multiple rows or columns, but single value (1 x 1) result sets
* are supported.
* <p>
* This class is not thread-safe.
* </p>
*/
@SuppressWarnings("unused")
public final class SQLiteStatement extends SQLiteProgram implements SupportSQLiteStatement {
SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) {
super(db, sql, bindArgs, null);
}
/**
* Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example
* CREATE / DROP table, view, trigger, index etc.
*
* @throws SQLException If the SQL string is invalid for some reason
*/
@Override
public void execute() {
acquireReference();
try {
getSession().execute(getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
releaseReference();
}
}
/**
* Execute this SQL statement, if the the number of rows affected by execution of this SQL
* statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements.
*
* @return the number of rows affected by this SQL statement execution.
* @throws SQLException If the SQL string is invalid for some reason
*/
@Override
public int executeUpdateDelete() {
acquireReference();
try {
return getSession().executeForChangedRowCount(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
releaseReference();
}
}
/**
* Execute this SQL statement and return the ID of the row inserted due to this call.
* The SQL statement should be an INSERT for this to be a useful call.
*
* @return the row ID of the last row inserted, if this insert is successful. -1 otherwise.
*
* @throws SQLException If the SQL string is invalid for some reason
*/
@Override
public long executeInsert() {
acquireReference();
try {
return getSession().executeForLastInsertedRowId(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
releaseReference();
}
}
/**
* Execute a statement that returns a 1 by 1 table with a numeric value.
* For example, SELECT COUNT(*) FROM table;
*
* @return The result of the query.
*
* @throws SQLiteDoneException if the query returns zero rows
*/
@Override
public long simpleQueryForLong() {
acquireReference();
try {
return getSession().executeForLong(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
releaseReference();
}
}
/**
* Execute a statement that returns a 1 by 1 table with a text value.
* For example, SELECT COUNT(*) FROM table;
*
* @return The result of the query.
*
* @throws SQLiteDoneException if the query returns zero rows
*/
@Override
public String simpleQueryForString() {
acquireReference();
try {
return getSession().executeForString(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
releaseReference();
}
}
/**
* Executes a statement that returns a 1 by 1 table with a blob value.
*
* @return A read-only file descriptor for a copy of the blob value, or {@code null}
* if the value is null or could not be read for some reason.
*
* @throws SQLiteDoneException if the query returns zero rows
*/
public ParcelFileDescriptor simpleQueryForBlobFileDescriptor() {
acquireReference();
try {
return getSession().executeForBlobFileDescriptor(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
releaseReference();
}
}
@Override
public String toString() {
return "SQLiteProgram: " + getSql();
}
}

@ -0,0 +1,40 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database.sqlite;
/**
* Describes a SQLite statement.
*
* @hide
*/
public final class SQLiteStatementInfo {
/**
* The number of parameters that the statement has.
*/
public int numParameters;
/**
* The names of all columns in the result set of the statement.
*/
public String[] columnNames;
/**
* True if the statement is read-only.
*/
public boolean readOnly;
}

@ -0,0 +1,107 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
package io.requery.android.database.sqlite;
import java.util.Locale;
class SQLiteStatementType {
/** One of the values returned by {@link #getSqlStatementType(String)}. */
public static final int STATEMENT_SELECT = 1;
/** One of the values returned by {@link #getSqlStatementType(String)}. */
public static final int STATEMENT_UPDATE = 2;
/** One of the values returned by {@link #getSqlStatementType(String)}. */
public static final int STATEMENT_ATTACH = 3;
/** One of the values returned by {@link #getSqlStatementType(String)}. */
public static final int STATEMENT_BEGIN = 4;
/** One of the values returned by {@link #getSqlStatementType(String)}. */
public static final int STATEMENT_COMMIT = 5;
/** One of the values returned by {@link #getSqlStatementType(String)}. */
public static final int STATEMENT_ABORT = 6;
/** One of the values returned by {@link #getSqlStatementType(String)}. */
public static final int STATEMENT_PRAGMA = 7;
/** One of the values returned by {@link #getSqlStatementType(String)}. */
public static final int STATEMENT_DDL = 8;
/** One of the values returned by {@link #getSqlStatementType(String)}. */
public static final int STATEMENT_UNPREPARED = 9;
/** One of the values returned by {@link #getSqlStatementType(String)}. */
public static final int STATEMENT_OTHER = 99;
private SQLiteStatementType() {
}
/**
* Returns one of the following which represent the type of the given SQL statement.
* <ol>
* <li>{@link #STATEMENT_SELECT}</li>
* <li>{@link #STATEMENT_UPDATE}</li>
* <li>{@link #STATEMENT_ATTACH}</li>
* <li>{@link #STATEMENT_BEGIN}</li>
* <li>{@link #STATEMENT_COMMIT}</li>
* <li>{@link #STATEMENT_ABORT}</li>
* <li>{@link #STATEMENT_OTHER}</li>
* </ol>
* @param sql the SQL statement whose type is returned by this method
* @return one of the values listed above
*/
public static int getSqlStatementType(String sql) {
sql = sql.trim();
if (sql.length() < 3) {
return STATEMENT_OTHER;
}
String prefixSql = sql.substring(0, 3);
if (prefixSql.equalsIgnoreCase("SEL")
|| prefixSql.equalsIgnoreCase("WIT")) {
return STATEMENT_SELECT;
}
if (prefixSql.equalsIgnoreCase("INS")
|| prefixSql.equalsIgnoreCase("UPD")
|| prefixSql.equalsIgnoreCase("REP")
|| prefixSql.equalsIgnoreCase("DEL")) {
return STATEMENT_UPDATE;
}
if (prefixSql.equalsIgnoreCase("ATT")) {
return STATEMENT_ATTACH;
}
if (prefixSql.equalsIgnoreCase("COM")
|| prefixSql.equalsIgnoreCase("END")) {
return STATEMENT_COMMIT;
}
if (prefixSql.equalsIgnoreCase("ROL")) {
return STATEMENT_ABORT;
}
if (prefixSql.equalsIgnoreCase("BEG")) {
return STATEMENT_BEGIN;
}
if (prefixSql.equalsIgnoreCase("PRA")) {
return STATEMENT_PRAGMA;
}
if (prefixSql.equalsIgnoreCase("CRE")
|| prefixSql.equalsIgnoreCase("DRO")
|| prefixSql.equalsIgnoreCase("ALT")) {
return STATEMENT_DDL;
}
if (prefixSql.equalsIgnoreCase("ANA") || prefixSql.equalsIgnoreCase("DET")) {
return STATEMENT_UNPREPARED;
}
return STATEMENT_OTHER;
}
}

@ -0,0 +1,4 @@
LOCAL_PATH:= $(call my-dir)
include $(LOCAL_PATH)/sqlite/Android.mk

@ -0,0 +1,5 @@
APP_STL:=none
APP_OPTIM := release
APP_ABI := armeabi-v7a,arm64-v8a,x86,x86_64
NDK_TOOLCHAIN_VERSION := clang
NDK_APP_LIBS_OUT=../jniLibs

@ -0,0 +1,72 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef NATIVEHELPER_ALOGPRIV_H_
#define NATIVEHELPER_ALOGPRIV_H_
#include <android/log.h>
#ifndef LOG_NDEBUG
#ifdef NDEBUG
#define LOG_NDEBUG 1
#else
#define LOG_NDEBUG 0
#endif
#endif
/*
* Basic log message macros intended to emulate the behavior of log/log.h
* in system core. This should be dependent only on ndk exposed logging
* functionality.
*/
#ifndef ALOG
#define ALOG(priority, tag, fmt...) \
__android_log_print(ANDROID_##priority, tag, fmt)
#endif
#ifndef ALOGV
#if LOG_NDEBUG
#define ALOGV(...) ((void)0)
#else
#define ALOGV(...) ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
#endif
#endif
#ifndef ALOGD
#define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#endif
#ifndef ALOGI
#define ALOGI(...) ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__))
#endif
#ifndef ALOGW
#define ALOGW(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__))
#endif
#ifndef ALOGE
#define ALOGE(...) ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__))
#endif
/*
** Not quite the same as the core android LOG_FATAL_IF (which also
** sends a SIGTRAP), but close enough.
*/
#define LOG_FATAL_IF(bCond, zErr) if( bCond ) ALOGE(zErr);
#endif

@ -0,0 +1,67 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
# NOTE the following flags,
# SQLITE_TEMP_STORE=3 causes all TEMP files to go into RAM. and thats the behavior we want
# SQLITE_ENABLE_FTS3 enables usage of FTS3 - NOT FTS1 or 2.
# SQLITE_DEFAULT_AUTOVACUUM=1 causes the databases to be subject to auto-vacuum
sqlite_flags := \
-DNDEBUG=1 \
-DHAVE_USLEEP=1 \
-DSQLITE_HAVE_ISNAN \
-DSQLITE_DEFAULT_JOURNAL_SIZE_LIMIT=1048576 \
-DSQLITE_THREADSAFE=2 \
-DSQLITE_TEMP_STORE=3 \
-DSQLITE_POWERSAFE_OVERWRITE=1 \
-DSQLITE_DEFAULT_FILE_FORMAT=4 \
-DSQLITE_DEFAULT_AUTOVACUUM=1 \
-DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 \
-DSQLITE_ENABLE_FTS3 \
-DSQLITE_ENABLE_FTS3_PARENTHESIS \
-DSQLITE_ENABLE_FTS4 \
-DSQLITE_ENABLE_FTS4_PARENTHESIS \
-DSQLITE_ENABLE_FTS5 \
-DSQLITE_ENABLE_FTS5_PARENTHESIS \
-DSQLITE_ENABLE_JSON1 \
-DSQLITE_ENABLE_RTREE=1 \
-DSQLITE_UNTESTABLE \
-DSQLITE_OMIT_COMPILEOPTION_DIAGS \
-DSQLITE_DEFAULT_FILE_PERMISSIONS=0600 \
-DSQLITE_DEFAULT_MEMSTATUS=0 \
-DSQLITE_MAX_EXPR_DEPTH=0 \
-DSQLITE_USE_ALLOCA \
-DSQLITE_ENABLE_BATCH_ATOMIC_WRITE \
-O3
LOCAL_CFLAGS += $(sqlite_flags)
LOCAL_CFLAGS += -Wno-unused-parameter -Wno-int-to-pointer-cast
LOCAL_CFLAGS += -Wno-uninitialized -Wno-parentheses
LOCAL_CPPFLAGS += -Wno-conversion-null
ifeq ($(TARGET_ARCH), arm)
LOCAL_CFLAGS += -DPACKED="__attribute__ ((packed))"
else
LOCAL_CFLAGS += -DPACKED=""
endif
LOCAL_SRC_FILES:= \
android_database_SQLiteCommon.cpp \
android_database_SQLiteConnection.cpp \
android_database_SQLiteFunction.cpp \
android_database_SQLiteGlobal.cpp \
android_database_SQLiteDebug.cpp \
android_database_CursorWindow.cpp \
CursorWindow.cpp \
JNIHelp.cpp \
JNIString.cpp
LOCAL_SRC_FILES += sqlite3.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)
LOCAL_MODULE:= libsqlite3x
LOCAL_LDLIBS += -ldl -llog -latomic
include $(BUILD_SHARED_LIBRARY)

@ -0,0 +1,283 @@
/*
* Copyright (C) 2006-2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
#undef LOG_TAG
#define LOG_TAG "CursorWindow"
#include "CursorWindow.h"
#include "ALog-priv.h"
#include <assert.h>
#include <string.h>
#include <stdlib.h>
namespace android {
CursorWindow::CursorWindow(const char* name, void* data, size_t size, bool readOnly) :
mData(data), mSize(size), mReadOnly(readOnly) {
mName = strdup(name);
mHeader = static_cast<Header*>(mData);
}
CursorWindow::~CursorWindow() {
free(mName);
free(mData);
}
status_t CursorWindow::create(const char* name, size_t size, CursorWindow** outWindow) {
status_t result;
void* data = malloc(size);
if (!data) {
return NO_MEMORY;
}
CursorWindow* window = new CursorWindow(name, data, size, false);
result = window->clear();
if (!result) {
LOG_WINDOW("Created new CursorWindow: freeOffset=%d, "
"numRows=%d, numColumns=%d, mSize=%d, mData=%p",
window->mHeader->freeOffset,
window->mHeader->numRows,
window->mHeader->numColumns,
window->mSize, window->mData);
*outWindow = window;
return OK;
}
delete window;
return result;
}
status_t CursorWindow::clear() {
if (mReadOnly) {
return INVALID_OPERATION;
}
mHeader->freeOffset = sizeof(Header) + sizeof(RowSlotChunk);
mHeader->firstChunkOffset = sizeof(Header);
mHeader->numRows = 0;
mHeader->numColumns = 0;
RowSlotChunk* firstChunk = static_cast<RowSlotChunk*>(offsetToPtr(mHeader->firstChunkOffset));
firstChunk->nextChunkOffset = 0;
return OK;
}
status_t CursorWindow::setNumColumns(uint32_t numColumns) {
if (mReadOnly) {
return INVALID_OPERATION;
}
uint32_t cur = mHeader->numColumns;
if ((cur > 0 || mHeader->numRows > 0) && cur != numColumns) {
ALOGE("Trying to go from %d columns to %d", cur, numColumns);
return INVALID_OPERATION;
}
mHeader->numColumns = numColumns;
return OK;
}
status_t CursorWindow::allocRow() {
if (mReadOnly) {
return INVALID_OPERATION;
}
// Fill in the row slot
RowSlot* rowSlot = allocRowSlot();
if (rowSlot == NULL) {
return NO_MEMORY;
}
// Allocate the slots for the field directory
size_t fieldDirSize = mHeader->numColumns * sizeof(FieldSlot);
uint32_t fieldDirOffset = alloc(fieldDirSize, true /*aligned*/);
if (!fieldDirOffset) {
mHeader->numRows--;
LOG_WINDOW("The row failed, so back out the new row accounting "
"from allocRowSlot %d", mHeader->numRows);
return NO_MEMORY;
}
FieldSlot* fieldDir = static_cast<FieldSlot*>(offsetToPtr(fieldDirOffset));
memset(fieldDir, 0, fieldDirSize);
//LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %d bytes at offset %u\n",
// mHeader->numRows - 1, offsetFromPtr(rowSlot), fieldDirSize, fieldDirOffset);
rowSlot->offset = fieldDirOffset;
return OK;
}
status_t CursorWindow::freeLastRow() {
if (mReadOnly) {
return INVALID_OPERATION;
}
if (mHeader->numRows > 0) {
mHeader->numRows--;
}
return OK;
}
uint32_t CursorWindow::alloc(size_t size, bool aligned) {
uint32_t padding;
if (aligned) {
// 4 byte alignment
padding = (~mHeader->freeOffset + 1) & 3;
} else {
padding = 0;
}
uint32_t offset = mHeader->freeOffset + padding;
uint32_t nextFreeOffset = offset + size;
if (nextFreeOffset > mSize) {
ALOGW("Window is full: requested allocation %zu bytes, "
"free space %zu bytes, window size %zu bytes",
size, freeSpace(), mSize);
return 0;
}
mHeader->freeOffset = nextFreeOffset;
return offset;
}
CursorWindow::RowSlot* CursorWindow::getRowSlot(uint32_t row) {
uint32_t chunkPos = row;
RowSlotChunk* chunk = static_cast<RowSlotChunk*>(
offsetToPtr(mHeader->firstChunkOffset));
while (chunkPos >= ROW_SLOT_CHUNK_NUM_ROWS) {
chunk = static_cast<RowSlotChunk*>(offsetToPtr(chunk->nextChunkOffset));
chunkPos -= ROW_SLOT_CHUNK_NUM_ROWS;
}
return &chunk->slots[chunkPos];
}
CursorWindow::RowSlot* CursorWindow::allocRowSlot() {
uint32_t chunkPos = mHeader->numRows;
RowSlotChunk* chunk = static_cast<RowSlotChunk*>(
offsetToPtr(mHeader->firstChunkOffset));
while (chunkPos > ROW_SLOT_CHUNK_NUM_ROWS) {
chunk = static_cast<RowSlotChunk*>(offsetToPtr(chunk->nextChunkOffset));
chunkPos -= ROW_SLOT_CHUNK_NUM_ROWS;
}
if (chunkPos == ROW_SLOT_CHUNK_NUM_ROWS) {
if (!chunk->nextChunkOffset) {
chunk->nextChunkOffset = alloc(sizeof(RowSlotChunk), true /*aligned*/);
if (!chunk->nextChunkOffset) {
return NULL;
}
}
chunk = static_cast<RowSlotChunk*>(offsetToPtr(chunk->nextChunkOffset));
chunk->nextChunkOffset = 0;
chunkPos = 0;
}
mHeader->numRows += 1;
return &chunk->slots[chunkPos];
}
CursorWindow::FieldSlot* CursorWindow::getFieldSlot(uint32_t row, uint32_t column) {
if (row >= mHeader->numRows || column >= mHeader->numColumns) {
ALOGE("Failed to read row %d, column %d from a CursorWindow which "
"has %d rows, %d columns.",
row, column, mHeader->numRows, mHeader->numColumns);
return NULL;
}
RowSlot* rowSlot = getRowSlot(row);
if (!rowSlot) {
ALOGE("Failed to find rowSlot for row %d.", row);
return NULL;
}
FieldSlot* fieldDir = static_cast<FieldSlot*>(offsetToPtr(rowSlot->offset));
return &fieldDir[column];
}
status_t CursorWindow::putBlob(uint32_t row, uint32_t column, const void* value, size_t size) {
return putBlobOrString(row, column, value, size, FIELD_TYPE_BLOB);
}
status_t CursorWindow::putString(uint32_t row, uint32_t column, const char* value,
size_t sizeIncludingNull) {
return putBlobOrString(row, column, value, sizeIncludingNull, FIELD_TYPE_STRING);
}
status_t CursorWindow::putBlobOrString(uint32_t row, uint32_t column,
const void* value, size_t size, int32_t type) {
if (mReadOnly) {
return INVALID_OPERATION;
}
FieldSlot* fieldSlot = getFieldSlot(row, column);
if (!fieldSlot) {
return BAD_VALUE;
}
uint32_t offset = alloc(size);
if (!offset) {
return NO_MEMORY;
}
memcpy(offsetToPtr(offset), value, size);
fieldSlot->type = type;
fieldSlot->data.buffer.offset = offset;
fieldSlot->data.buffer.size = size;
return OK;
}
status_t CursorWindow::putLong(uint32_t row, uint32_t column, int64_t value) {
if (mReadOnly) {
return INVALID_OPERATION;
}
FieldSlot* fieldSlot = getFieldSlot(row, column);
if (!fieldSlot) {
return BAD_VALUE;
}
fieldSlot->type = FIELD_TYPE_INTEGER;
fieldSlot->data.l = value;
return OK;
}
status_t CursorWindow::putDouble(uint32_t row, uint32_t column, double value) {
if (mReadOnly) {
return INVALID_OPERATION;
}
FieldSlot* fieldSlot = getFieldSlot(row, column);
if (!fieldSlot) {
return BAD_VALUE;
}
fieldSlot->type = FIELD_TYPE_FLOAT;
fieldSlot->data.d = value;
return OK;
}
status_t CursorWindow::putNull(uint32_t row, uint32_t column) {
if (mReadOnly) {
return INVALID_OPERATION;
}
FieldSlot* fieldSlot = getFieldSlot(row, column);
if (!fieldSlot) {
return BAD_VALUE;
}
fieldSlot->type = FIELD_TYPE_NULL;
fieldSlot->data.buffer.offset = 0;
fieldSlot->data.buffer.size = 0;
return OK;
}
}; // namespace android

@ -0,0 +1,188 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
#ifndef _ANDROID__DATABASE_WINDOW_H
#define _ANDROID__DATABASE_WINDOW_H
#include "ALog-priv.h"
#include <stddef.h>
#include <stdint.h>
#include "Errors.h"
#if LOG_NDEBUG
#define IF_LOG_WINDOW() if (false)
#define LOG_WINDOW(...)
#else
#define IF_LOG_WINDOW() IF_ALOG(LOG_DEBUG, "CursorWindow")
#define LOG_WINDOW(...) ALOG(LOG_DEBUG, "CursorWindow", __VA_ARGS__)
#endif
namespace android {
/**
* This class stores a set of rows from a database in a buffer. The beginning of the
* window has first chunk of RowSlots, which are offsets to the row directory, followed by
* an offset to the next chunk in a linked-list of additional chunk of RowSlots in case
* the pre-allocated chunk isn't big enough to refer to all rows. Each row directory has a
* FieldSlot per column, which has the size, offset, and type of the data for that field.
* Note that the data types come from sqlite3.h.
*
* Strings are stored in UTF-8.
*/
class CursorWindow {
CursorWindow(const char* name, void* data, size_t size, bool readOnly);
public:
/* Field types. */
enum {
FIELD_TYPE_NULL = 0,
FIELD_TYPE_INTEGER = 1,
FIELD_TYPE_FLOAT = 2,
FIELD_TYPE_STRING = 3,
FIELD_TYPE_BLOB = 4,
};
/* Opaque type that describes a field slot. */
struct FieldSlot {
private:
int32_t type;
union {
double d;
int64_t l;
struct {
uint32_t offset;
uint32_t size;
} buffer;
} data;
friend class CursorWindow;
} __attribute((packed));
~CursorWindow();
static status_t create(const char* name, size_t size, CursorWindow** outCursorWindow);
inline const char* name() { return mName; }
inline size_t size() { return mSize; }
inline size_t freeSpace() { return mSize - mHeader->freeOffset; }
inline uint32_t getNumRows() { return mHeader->numRows; }
inline uint32_t getNumColumns() { return mHeader->numColumns; }
status_t clear();
status_t setNumColumns(uint32_t numColumns);
/**
* Allocate a row slot and its directory.
* The row is initialized will null entries for each field.
*/
status_t allocRow();
status_t freeLastRow();
status_t putBlob(uint32_t row, uint32_t column, const void* value, size_t size);
status_t putString(uint32_t row, uint32_t column, const char* value, size_t sizeIncludingNull);
status_t putLong(uint32_t row, uint32_t column, int64_t value);
status_t putDouble(uint32_t row, uint32_t column, double value);
status_t putNull(uint32_t row, uint32_t column);
/**
* Gets the field slot at the specified row and column.
* Returns null if the requested row or column is not in the window.
*/
FieldSlot* getFieldSlot(uint32_t row, uint32_t column);
inline int32_t getFieldSlotType(FieldSlot* fieldSlot) {
return fieldSlot->type;
}
inline int64_t getFieldSlotValueLong(FieldSlot* fieldSlot) {
return fieldSlot->data.l;
}
inline double getFieldSlotValueDouble(FieldSlot* fieldSlot) {
return fieldSlot->data.d;
}
inline const char* getFieldSlotValueString(FieldSlot* fieldSlot,
size_t* outSizeIncludingNull) {
*outSizeIncludingNull = fieldSlot->data.buffer.size;
return static_cast<char*>(offsetToPtr(fieldSlot->data.buffer.offset));
}
inline const void* getFieldSlotValueBlob(FieldSlot* fieldSlot, size_t* outSize) {
*outSize = fieldSlot->data.buffer.size;
return offsetToPtr(fieldSlot->data.buffer.offset);
}
private:
static const size_t ROW_SLOT_CHUNK_NUM_ROWS = 100;
struct Header {
// Offset of the lowest unused byte in the window.
uint32_t freeOffset;
// Offset of the first row slot chunk.
uint32_t firstChunkOffset;
uint32_t numRows;
uint32_t numColumns;
};
struct RowSlot {
uint32_t offset;
};
struct RowSlotChunk {
RowSlot slots[ROW_SLOT_CHUNK_NUM_ROWS];
uint32_t nextChunkOffset;
};
char* mName;
void* mData;
size_t mSize;
bool mReadOnly;
Header* mHeader;
inline void* offsetToPtr(uint32_t offset) {
return static_cast<uint8_t*>(mData) + offset;
}
inline uint32_t offsetFromPtr(void* ptr) {
return static_cast<uint8_t*>(ptr) - static_cast<uint8_t*>(mData);
}
/**
* Allocate a portion of the window. Returns the offset
* of the allocation, or 0 if there isn't enough space.
* If aligned is true, the allocation gets 4 byte alignment.
*/
uint32_t alloc(size_t size, bool aligned = false);
RowSlot* getRowSlot(uint32_t row);
RowSlot* allocRowSlot();
status_t putBlobOrString(uint32_t row, uint32_t column,
const void* value, size_t size, int32_t type);
};
}; // namespace android
#endif

@ -0,0 +1,88 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_ERRORS_H
#define ANDROID_ERRORS_H
#include <sys/types.h>
#include <errno.h>
namespace android {
// use this type to return error codes
#ifdef HAVE_MS_C_RUNTIME
typedef int status_t;
#else
typedef int32_t status_t;
#endif
/* the MS C runtime lacks a few error codes */
/*
* Error codes.
* All error codes are negative values.
*/
// Win32 #defines NO_ERROR as well. It has the same value, so there's no
// real conflict, though it's a bit awkward.
#ifdef _WIN32
# undef NO_ERROR
#endif
enum {
OK = 0, // Everything's swell.
NO_ERROR = 0, // No errors.
UNKNOWN_ERROR = 0x80000000,
NO_MEMORY = -ENOMEM,
INVALID_OPERATION = -ENOSYS,
BAD_VALUE = -EINVAL,
BAD_TYPE = 0x80000001,
NAME_NOT_FOUND = -ENOENT,
PERMISSION_DENIED = -EPERM,
NO_INIT = -ENODEV,
ALREADY_EXISTS = -EEXIST,
DEAD_OBJECT = -EPIPE,
FAILED_TRANSACTION = 0x80000002,
JPARKS_BROKE_IT = -EPIPE,
#if !defined(HAVE_MS_C_RUNTIME)
BAD_INDEX = -EOVERFLOW,
NOT_ENOUGH_DATA = -ENODATA,
WOULD_BLOCK = -EWOULDBLOCK,
TIMED_OUT = -ETIMEDOUT,
UNKNOWN_TRANSACTION = -EBADMSG,
#else
BAD_INDEX = -E2BIG,
NOT_ENOUGH_DATA = 0x80000003,
WOULD_BLOCK = 0x80000004,
TIMED_OUT = 0x80000005,
UNKNOWN_TRANSACTION = 0x80000006,
#endif
FDS_NOT_ALLOWED = 0x80000007,
};
// Restore define; enumeration is in "android" namespace, so the value defined
// there won't work for Win32 code in a different namespace.
#ifdef _WIN32
# define NO_ERROR 0L
#endif
}; // namespace android
// ---------------------------------------------------------------------------
#endif // ANDROID_ERRORS_H

@ -0,0 +1,217 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "JNIHelp"
#include "JNIHelp.h"
#include "ALog-priv.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
/**
* Equivalent to ScopedLocalRef, but for C_JNIEnv instead. (And slightly more powerful.)
*/
template<typename T>
class scoped_local_ref {
public:
scoped_local_ref(C_JNIEnv* env, T localRef = NULL)
: mEnv(env), mLocalRef(localRef)
{
}
~scoped_local_ref() {
reset();
}
void reset(T localRef = NULL) {
if (mLocalRef != NULL) {
(*mEnv)->DeleteLocalRef(reinterpret_cast<JNIEnv*>(mEnv), mLocalRef);
mLocalRef = localRef;
}
}
T get() const {
return mLocalRef;
}
private:
C_JNIEnv* mEnv;
T mLocalRef;
// Disallow copy and assignment.
scoped_local_ref(const scoped_local_ref&);
void operator=(const scoped_local_ref&);
};
static jclass findClass(C_JNIEnv* env, const char* className) {
JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
return (*env)->FindClass(e, className);
}
extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
ALOGV("Registering %s's %d native methods...", className, numMethods);
scoped_local_ref<jclass> c(env, findClass(env, className));
if (c.get() == NULL) {
char* msg;
asprintf(&msg, "Native registration unable to find class '%s'; aborting...", className);
e->FatalError(msg);
}
if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
char* msg;
asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className);
e->FatalError(msg);
}
return 0;
}
/*
* Returns a human-readable summary of an exception object. The buffer will
* be populated with the "binary" class name and, if present, the
* exception message.
*/
static bool logExceptionSummary(C_JNIEnv *env, jthrowable exception,
const char* exceptionClassName) {
JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
/* get the name of the exception's class */
scoped_local_ref<jclass> exceptionClass(env, (*env)->GetObjectClass(e, exception)); // can't fail
scoped_local_ref<jclass> classClass(env,
(*env)->GetObjectClass(e, exceptionClass.get())); // java.lang.Class, can't fail
jmethodID classGetNameMethod =
(*env)->GetMethodID(e, classClass.get(), "getName", "()Ljava/lang/String;");
scoped_local_ref<jstring> classNameStr(env,
(jstring) (*env)->CallObjectMethod(e, exceptionClass.get(), classGetNameMethod));
if (classNameStr.get() == NULL) {
(*env)->ExceptionClear(e);
ALOGW("Discarding pending exception (%s) to throw %s", "<error getting class name>",
exceptionClassName);
return false;
}
const char* classNameChars = (*env)->GetStringUTFChars(e, classNameStr.get(), NULL);
if (classNameChars == NULL) {
(*env)->ExceptionClear(e);
ALOGW("Discarding pending exception (%s) to throw %s", "<error getting class name UTF-8>",
exceptionClassName);
return false;
}
(*env)->ReleaseStringUTFChars(e, classNameStr.get(), classNameChars);
/* if the exception has a detail message, get that */
jmethodID getMessage =
(*env)->GetMethodID(e, exceptionClass.get(), "getMessage", "()Ljava/lang/String;");
scoped_local_ref<jstring> messageStr(env,
(jstring) (*env)->CallObjectMethod(e, exception, getMessage));
if (messageStr.get() == NULL) {
return true;
}
const char* messageChars = (*env)->GetStringUTFChars(e, messageStr.get(), NULL);
if (messageChars != NULL) {
ALOGW("Discarding pending exception (%s: %s) to throw %s",
classNameChars,
messageChars,
exceptionClassName);
(*env)->ReleaseStringUTFChars(e, messageStr.get(), messageChars);
} else {
ALOGW("Discarding pending exception (%s: <error getting message>) to throw %s",
classNameChars,
exceptionClassName);
(*env)->ExceptionClear(e); // clear OOM
}
return true;
}
extern "C" int jniThrowException(C_JNIEnv* env, const char* className, const char* msg) {
JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
if ((*env)->ExceptionCheck(e)) {
/* TODO: consider creating the new exception with this as "cause" */
scoped_local_ref<jthrowable> exception(env, (*env)->ExceptionOccurred(e));
(*env)->ExceptionClear(e);
if (exception.get() != NULL) {
logExceptionSummary(env, exception.get(), className);
}
}
scoped_local_ref<jclass> exceptionClass(env, findClass(env, className));
if (exceptionClass.get() == NULL) {
ALOGE("Unable to find exception class %s", className);
/* ClassNotFoundException now pending */
return -1;
}
if ((*env)->ThrowNew(e, exceptionClass.get(), msg) != JNI_OK) {
ALOGE("Failed throwing '%s' '%s'", className, msg);
/* an exception, most likely OOM, will now be pending */
return -1;
}
return 0;
}
int jniThrowExceptionFmt(C_JNIEnv* env, const char* className, const char* fmt, va_list args) {
char msgBuf[512];
vsnprintf(msgBuf, sizeof(msgBuf), fmt, args);
return jniThrowException(env, className, msgBuf);
}
int jniThrowNullPointerException(C_JNIEnv* env, const char* msg) {
return jniThrowException(env, "java/lang/NullPointerException", msg);
}
int jniThrowRuntimeException(C_JNIEnv* env, const char* msg) {
return jniThrowException(env, "java/lang/RuntimeException", msg);
}
int jniThrowIOException(C_JNIEnv* env, int errnum) {
char buffer[80];
const char* message = jniStrError(errnum, buffer, sizeof(buffer));
return jniThrowException(env, "java/io/IOException", message);
}
const char* jniStrError(int errnum, char* buf, size_t buflen) {
#if __GLIBC__
// Note: glibc has a nonstandard strerror_r that returns char* rather than POSIX's int.
// char *strerror_r(int errnum, char *buf, size_t n);
return strerror_r(errnum, buf, buflen);
#else
int rc = strerror_r(errnum, buf, buflen);
if (rc != 0) {
// (POSIX only guarantees a value other than 0. The safest
// way to implement this function is to use C++ and overload on the
// type of strerror_r to accurately distinguish GNU from POSIX.)
snprintf(buf, buflen, "errno %d", errnum);
}
return buf;
#endif
}
void* operator new (size_t size) { return malloc(size); }
void* operator new [] (size_t size) { return malloc(size); }
void operator delete (void* pointer) { free(pointer); }
void operator delete [] (void* pointer) { free(pointer); }

@ -0,0 +1,178 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* JNI helper functions.
*
* This file may be included by C or C++ code, which is trouble because jni.h
* uses different typedefs for JNIEnv in each language.
*
* TODO: remove C support.
*/
#ifndef NATIVEHELPER_JNIHELP_H_
#define NATIVEHELPER_JNIHELP_H_
#include "jni.h"
#include <unistd.h>
#ifndef NELEM
# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
#endif
#ifdef __cplusplus
extern "C" {
#endif
/*
* Register one or more native methods with a particular class.
* "className" looks like "java/lang/String". Aborts on failure.
* TODO: fix all callers and change the return type to void.
*/
int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods);
/*
* Throw an exception with the specified class and an optional message.
*
* The "className" argument will be passed directly to FindClass, which
* takes strings with slashes (e.g. "java/lang/Object").
*
* If an exception is currently pending, we log a warning message and
* clear it.
*
* Returns 0 on success, nonzero if something failed (e.g. the exception
* class couldn't be found, so *an* exception will still be pending).
*
* Currently aborts the VM if it can't throw the exception.
*/
int jniThrowException(C_JNIEnv* env, const char* className, const char* msg);
/*
* Throw a java.lang.NullPointerException, with an optional message.
*/
int jniThrowNullPointerException(C_JNIEnv* env, const char* msg);
/*
* Throw a java.lang.RuntimeException, with an optional message.
*/
int jniThrowRuntimeException(C_JNIEnv* env, const char* msg);
/*
* Throw a java.io.IOException, generating the message from errno.
*/
int jniThrowIOException(C_JNIEnv* env, int errnum);
/*
* Return a pointer to a locale-dependent error string explaining errno
* value 'errnum'. The returned pointer may or may not be equal to 'buf'.
* This function is thread-safe (unlike strerror) and portable (unlike
* strerror_r).
*/
const char* jniStrError(int errnum, char* buf, size_t buflen);
/*
* Returns a new java.io.FileDescriptor for the given int fd.
*/
jobject jniCreateFileDescriptor(C_JNIEnv* env, int fd);
/*
* Returns the int fd from a java.io.FileDescriptor.
*/
int jniGetFDFromFileDescriptor(C_JNIEnv* env, jobject fileDescriptor);
/*
* Sets the int fd in a java.io.FileDescriptor.
*/
void jniSetFileDescriptorOfFD(C_JNIEnv* env, jobject fileDescriptor, int value);
/*
* Returns the reference from a java.lang.ref.Reference.
*/
jobject jniGetReferent(C_JNIEnv* env, jobject ref);
#ifdef __cplusplus
}
#endif
/*
* For C++ code, we provide inlines that map to the C functions. g++ always
* inlines these, even on non-optimized builds.
*/
#if defined(__cplusplus)
inline int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) {
return jniRegisterNativeMethods(&env->functions, className, gMethods, numMethods);
}
inline int jniThrowException(JNIEnv* env, const char* className, const char* msg) {
return jniThrowException(&env->functions, className, msg);
}
extern "C" int jniThrowExceptionFmt(C_JNIEnv* env, const char* className, const char* fmt, va_list args);
/*
* Equivalent to jniThrowException but with a printf-like format string and
* variable-length argument list. This is only available in C++.
*/
inline int jniThrowExceptionFmt(JNIEnv* env, const char* className, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
return jniThrowExceptionFmt(&env->functions, className, fmt, args);
va_end(args);
}
inline int jniThrowNullPointerException(JNIEnv* env, const char* msg) {
return jniThrowNullPointerException(&env->functions, msg);
}
inline int jniThrowRuntimeException(JNIEnv* env, const char* msg) {
return jniThrowRuntimeException(&env->functions, msg);
}
inline int jniThrowIOException(JNIEnv* env, int errnum) {
return jniThrowIOException(&env->functions, errnum);
}
inline jobject jniCreateFileDescriptor(JNIEnv* env, int fd) {
return jniCreateFileDescriptor(&env->functions, fd);
}
inline int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor) {
return jniGetFDFromFileDescriptor(&env->functions, fileDescriptor);
}
inline void jniSetFileDescriptorOfFD(JNIEnv* env, jobject fileDescriptor, int value) {
jniSetFileDescriptorOfFD(&env->functions, fileDescriptor, value);
}
inline jobject jniGetReferent(JNIEnv* env, jobject ref) {
return jniGetReferent(&env->functions, ref);
}
#endif
#define FIND_CLASS(var, className) \
var = env->FindClass(className); \
LOG_FATAL_IF(! var, "Unable to find class " className);
#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \
var = env->GetMethodID(clazz, methodName, fieldDescriptor); \
LOG_FATAL_IF(! var, "Unable to find method" methodName);
#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
LOG_FATAL_IF(! var, "Unable to find field " fieldName);
#endif /* NATIVEHELPER_JNIHELP_H_ */

@ -0,0 +1,118 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Note this code is adapted from AOSP implementation of String, now located at
// https://android.googlesource.com/platform/libcore/+/master/libart/src/main/java/java/lang/StringFactory.java
#include <jni.h>
#define REPLACEMENT_CHAR 0xfffd;
namespace android {
jsize utf8ToJavaCharArray(const char* d, jchar v[], jint byteCount) {
jint idx = 0;
jint last = byteCount;
jint s = 0;
outer:
while (idx < last) {
jbyte b0 = d[idx++];
if ((b0 & 0x80) == 0) {
// 0xxxxxxx
// Range: U-00000000 - U-0000007F
jint val = b0 & 0xff;
v[s++] = (jchar) val;
} else if (((b0 & 0xe0) == 0xc0) || ((b0 & 0xf0) == 0xe0) ||
((b0 & 0xf8) == 0xf0) || ((b0 & 0xfc) == 0xf8) || ((b0 & 0xfe) == 0xfc)) {
jint utfCount = 1;
if ((b0 & 0xf0) == 0xe0) utfCount = 2;
else if ((b0 & 0xf8) == 0xf0) utfCount = 3;
else if ((b0 & 0xfc) == 0xf8) utfCount = 4;
else if ((b0 & 0xfe) == 0xfc) utfCount = 5;
// 110xxxxx (10xxxxxx)+
// Range: U-00000080 - U-000007FF (count == 1)
// Range: U-00000800 - U-0000FFFF (count == 2)
// Range: U-00010000 - U-001FFFFF (count == 3)
// Range: U-00200000 - U-03FFFFFF (count == 4)
// Range: U-04000000 - U-7FFFFFFF (count == 5)
if (idx + utfCount > last) {
v[s++] = REPLACEMENT_CHAR;
continue;
}
// Extract usable bits from b0
jint val = b0 & (0x1f >> (utfCount - 1));
for (int i = 0; i < utfCount; ++i) {
jbyte b = d[idx++];
if ((b & 0xc0) != 0x80) {
v[s++] = REPLACEMENT_CHAR;
idx--; // Put the input char back
goto outer;
}
// Push new bits in from the right side
val <<= 6;
val |= b & 0x3f;
}
// Note: Java allows overlong char
// specifications To disallow, check that val
// is greater than or equal to the minimum
// value for each count:
//
// count min value
// ----- ----------
// 1 0x80
// 2 0x800
// 3 0x10000
// 4 0x200000
// 5 0x4000000
// Allow surrogate values (0xD800 - 0xDFFF) to
// be specified using 3-byte UTF values only
if ((utfCount != 2) && (val >= 0xD800) && (val <= 0xDFFF)) {
v[s++] = REPLACEMENT_CHAR;
continue;
}
// Reject chars greater than the Unicode maximum of U+10FFFF.
if (val > 0x10FFFF) {
v[s++] = REPLACEMENT_CHAR;
continue;
}
// Encode chars from U+10000 up as surrogate pairs
if (val < 0x10000) {
v[s++] = (jchar) val;
} else {
int x = val & 0xffff;
int u = (val >> 16) & 0x1f;
int w = (u - 1) & 0xffff;
int hi = 0xd800 | (w << 6) | (x >> 10);
int lo = 0xdc00 | (x & 0x3ff);
v[s++] = (jchar) hi;
v[s++] = (jchar) lo;
}
} else {
// Illegal values 0x8*, 0x9*, 0xa*, 0xb*, 0xfd-0xff
v[s++] = REPLACEMENT_CHAR;
}
}
return s;
}
}

@ -0,0 +1,32 @@
All the files in this directory are copied from stock android. The following
files:
JNIHelp.cpp
ALog-priv.h
are copied in from Android's libnativehelper module (altogether less than 1000
lines of code). The remainder are from the core framework (directory
/frameworks/base/core/jni).
Notes on changes:
The ashmem_XXX() interfaces are used for the various "xxxForBlobDescriptor()"
API functions. The code in libcutils for this seems to be platform
dependent - some platforms have kernel support, others have a user space
implementation. So these functions are not supported for now.
The original SQLiteConnection.cpp uses AndroidRuntime::genJNIEnv() to obtain a
pointer to the current threads environment. Changed to store a pointer to the
process JavaVM (Android allows only one) as a global variable. Then retrieve
the JNIEnv as needed using GetEnv().
Replaced uses of class String8 with std::string in SQLiteConnection.cpp and a
few other places.
The "LOCALIZED" collation and some miscellaneous user-functions added by the
sqlite3_android.cpp module are not included. A collation called LOCALIZED
that is equivalent to BINARY is added instead to keep various things working.
This should not cause serious problems - class SQLiteConnection always
runs "REINDEX LOCALIZED" immediately after opening a connection.

@ -0,0 +1,412 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
#undef LOG_TAG
#define LOG_TAG "CursorWindow"
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#include <jni.h>
#include <JNIHelp.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "CursorWindow.h"
#include "android_database_SQLiteCommon.h"
namespace android {
static struct {
jfieldID data;
jfieldID sizeCopied;
} gCharArrayBufferClassInfo;
static jstring gEmptyString = NULL;
static void throwExceptionWithRowCol(JNIEnv* env, jint row, jint column) {
char buf[64];
snprintf(buf, sizeof(buf), "Couldn't read row %d column %d", row, column);
jniThrowException(env, "java/lang/IllegalStateException", buf);
}
static void throwUnknownTypeException(JNIEnv * env, jint type) {
char buf[32];
snprintf(buf, sizeof(buf), "UNKNOWN type %d", type);
jniThrowException(env, "java/lang/IllegalStateException", buf);
}
static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj, jint cursorWindowSize) {
CursorWindow* window;
const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
status_t status = CursorWindow::create(nameStr, cursorWindowSize, &window);
env->ReleaseStringUTFChars(nameObj, nameStr);
if (status || !window) {
ALOGE("Could not allocate CursorWindow of size %d due to error %d.",
cursorWindowSize, status);
return 0;
}
LOG_WINDOW("nativeInitializeEmpty: window = %p", window);
return reinterpret_cast<jlong>(window);
}
static void nativeDispose(JNIEnv* env, jclass clazz, jlong windowPtr) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
if (window) {
LOG_WINDOW("Closing window %p", window);
delete window;
}
}
static jstring nativeGetName(JNIEnv* env, jclass clazz, jlong windowPtr) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
return env->NewStringUTF(window->name());
}
static void nativeClear(JNIEnv * env, jclass clazz, jlong windowPtr) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
LOG_WINDOW("Clearing window %p", window);
status_t status = window->clear();
if (status) {
LOG_WINDOW("Could not clear window. error=%d", status);
}
}
static jint nativeGetNumRows(JNIEnv* env, jclass clazz, jlong windowPtr) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
return window->getNumRows();
}
static jboolean nativeSetNumColumns(JNIEnv* env, jclass clazz, jlong windowPtr,
jint columnNum) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
status_t status = window->setNumColumns(columnNum);
return status == OK;
}
static jboolean nativeAllocRow(JNIEnv* env, jclass clazz, jlong windowPtr) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
status_t status = window->allocRow();
return status == OK;
}
static void nativeFreeLastRow(JNIEnv* env, jclass clazz, jlong windowPtr) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
window->freeLastRow();
}
static jint nativeGetType(JNIEnv* env, jclass clazz, jlong windowPtr,
jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
LOG_WINDOW("returning column type affinity for %d,%d from %p", row, column, window);
CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
if (!fieldSlot) {
return CursorWindow::FIELD_TYPE_NULL;
}
return window->getFieldSlotType(fieldSlot);
}
static jbyteArray nativeGetBlob(JNIEnv* env, jclass clazz, jlong windowPtr,
jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
//LOG_WINDOW("Getting blob for %d,%d from %p", row, column, window);
CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
if (!fieldSlot) {
throwExceptionWithRowCol(env, row, column);
return NULL;
}
int32_t type = window->getFieldSlotType(fieldSlot);
if (type == CursorWindow::FIELD_TYPE_BLOB || type == CursorWindow::FIELD_TYPE_STRING) {
size_t size;
const void* value = window->getFieldSlotValueBlob(fieldSlot, &size);
jbyteArray byteArray = env->NewByteArray(size);
if (!byteArray) {
env->ExceptionClear();
throw_sqlite3_exception(env, "Native could not create new byte[]");
return NULL;
}
env->SetByteArrayRegion(byteArray, 0, size, static_cast<const jbyte*>(value));
return byteArray;
} else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
throw_sqlite3_exception(env, "INTEGER data in nativeGetBlob ");
} else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
throw_sqlite3_exception(env, "FLOAT data in nativeGetBlob ");
} else if (type == CursorWindow::FIELD_TYPE_NULL) {
// do nothing
} else {
throwUnknownTypeException(env, type);
}
return NULL;
}
extern int utf8ToJavaCharArray(const char* d, jchar v[], jint byteCount);
static jstring nativeGetString(JNIEnv* env, jclass clazz, jlong windowPtr,
jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
//LOG_WINDOW("Getting string for %d,%d from %p", row, column, window);
CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
if (!fieldSlot) {
throwExceptionWithRowCol(env, row, column);
return NULL;
}
int32_t type = window->getFieldSlotType(fieldSlot);
if (type == CursorWindow::FIELD_TYPE_STRING) {
size_t sizeIncludingNull;
const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
if (sizeIncludingNull <= 1) {
return gEmptyString;
}
const size_t MaxStackStringSize = 65536; // max size for a stack char array
if (sizeIncludingNull > MaxStackStringSize) {
jchar* chars = new jchar[sizeIncludingNull - 1];
jint size = utf8ToJavaCharArray(value, chars, sizeIncludingNull - 1);
jstring string = env->NewString(chars, size);
delete[] chars;
return string;
} else {
jchar chars[sizeIncludingNull - 1];
jint size = utf8ToJavaCharArray(value, chars, sizeIncludingNull - 1);
return env->NewString(chars, size);
}
} else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
int64_t value = window->getFieldSlotValueLong(fieldSlot);
char buf[32];
snprintf(buf, sizeof(buf), "%" PRId64, value);
return env->NewStringUTF(buf);
} else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
double value = window->getFieldSlotValueDouble(fieldSlot);
char buf[32];
snprintf(buf, sizeof(buf), "%g", value);
return env->NewStringUTF(buf);
} else if (type == CursorWindow::FIELD_TYPE_NULL) {
return NULL;
} else if (type == CursorWindow::FIELD_TYPE_BLOB) {
throw_sqlite3_exception(env, "Unable to convert BLOB to string");
return NULL;
} else {
throwUnknownTypeException(env, type);
return NULL;
}
}
static jlong nativeGetLong(JNIEnv* env, jclass clazz, jlong windowPtr,
jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
//LOG_WINDOW("Getting long for %d,%d from %p", row, column, window);
CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
if (!fieldSlot) {
throwExceptionWithRowCol(env, row, column);
return 0;
}
int32_t type = window->getFieldSlotType(fieldSlot);
if (type == CursorWindow::FIELD_TYPE_INTEGER) {
return window->getFieldSlotValueLong(fieldSlot);
} else if (type == CursorWindow::FIELD_TYPE_STRING) {
size_t sizeIncludingNull;
const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
return sizeIncludingNull > 1 ? strtoll(value, NULL, 0) : 0L;
} else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
return jlong(window->getFieldSlotValueDouble(fieldSlot));
} else if (type == CursorWindow::FIELD_TYPE_NULL) {
return 0;
} else if (type == CursorWindow::FIELD_TYPE_BLOB) {
throw_sqlite3_exception(env, "Unable to convert BLOB to long");
return 0;
} else {
throwUnknownTypeException(env, type);
return 0;
}
}
static jdouble nativeGetDouble(JNIEnv* env, jclass clazz, jlong windowPtr,
jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
//LOG_WINDOW("Getting double for %d,%d from %p", row, column, window);
CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
if (!fieldSlot) {
throwExceptionWithRowCol(env, row, column);
return 0.0;
}
int32_t type = window->getFieldSlotType(fieldSlot);
if (type == CursorWindow::FIELD_TYPE_FLOAT) {
return window->getFieldSlotValueDouble(fieldSlot);
} else if (type == CursorWindow::FIELD_TYPE_STRING) {
size_t sizeIncludingNull;
const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
return sizeIncludingNull > 1 ? strtod(value, NULL) : 0.0;
} else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
return jdouble(window->getFieldSlotValueLong(fieldSlot));
} else if (type == CursorWindow::FIELD_TYPE_NULL) {
return 0.0;
} else if (type == CursorWindow::FIELD_TYPE_BLOB) {
throw_sqlite3_exception(env, "Unable to convert BLOB to double");
return 0.0;
} else {
throwUnknownTypeException(env, type);
return 0.0;
}
}
static jboolean nativePutBlob(JNIEnv* env, jclass clazz, jlong windowPtr,
jbyteArray valueObj, jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
jsize len = env->GetArrayLength(valueObj);
void* value = env->GetPrimitiveArrayCritical(valueObj, NULL);
status_t status = window->putBlob(row, column, value, len);
env->ReleasePrimitiveArrayCritical(valueObj, value, JNI_ABORT);
if (status) {
LOG_WINDOW("Failed to put blob. error=%d", status);
return false;
}
LOG_WINDOW("%d,%d is BLOB with %u bytes", row, column, len);
return true;
}
static jboolean nativePutString(JNIEnv* env, jclass clazz, jlong windowPtr,
jstring valueObj, jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
size_t sizeIncludingNull = env->GetStringUTFLength(valueObj) + 1;
const char* valueStr = env->GetStringUTFChars(valueObj, NULL);
if (!valueStr) {
LOG_WINDOW("value can't be transferred to UTFChars");
return false;
}
status_t status = window->putString(row, column, valueStr, sizeIncludingNull);
env->ReleaseStringUTFChars(valueObj, valueStr);
if (status) {
LOG_WINDOW("Failed to put string. error=%d", status);
return false;
}
LOG_WINDOW("%d,%d is TEXT with %u bytes", row, column, sizeIncludingNull);
return true;
}
static jboolean nativePutLong(JNIEnv* env, jclass clazz, jlong windowPtr,
jlong value, jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
status_t status = window->putLong(row, column, value);
if (status) {
LOG_WINDOW("Failed to put long. error=%d", status);
return false;
}
LOG_WINDOW("%d,%d is INTEGER 0x%016llx", row, column, value);
return true;
}
static jboolean nativePutDouble(JNIEnv* env, jclass clazz, jlong windowPtr,
jdouble value, jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
status_t status = window->putDouble(row, column, value);
if (status) {
LOG_WINDOW("Failed to put double. error=%d", status);
return false;
}
LOG_WINDOW("%d,%d is FLOAT %lf", row, column, value);
return true;
}
static jboolean nativePutNull(JNIEnv* env, jclass clazz, jlong windowPtr,
jint row, jint column) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
status_t status = window->putNull(row, column);
if (status) {
LOG_WINDOW("Failed to put null. error=%d", status);
return false;
}
LOG_WINDOW("%d,%d is NULL", row, column);
return true;
}
static const JNINativeMethod sMethods[] =
{
/* name, signature, funcPtr */
{ "nativeCreate", "(Ljava/lang/String;I)J",
(void*)nativeCreate },
{ "nativeDispose", "(J)V",
(void*)nativeDispose },
{ "nativeGetName", "(J)Ljava/lang/String;",
(void*)nativeGetName },
{ "nativeClear", "(J)V",
(void*)nativeClear },
{ "nativeGetNumRows", "(J)I",
(void*)nativeGetNumRows },
{ "nativeSetNumColumns", "(JI)Z",
(void*)nativeSetNumColumns },
{ "nativeAllocRow", "(J)Z",
(void*)nativeAllocRow },
{ "nativeFreeLastRow", "(J)V",
(void*)nativeFreeLastRow },
{ "nativeGetType", "(JII)I",
(void*)nativeGetType },
{ "nativeGetBlob", "(JII)[B",
(void*)nativeGetBlob },
{ "nativeGetString", "(JII)Ljava/lang/String;",
(void*)nativeGetString },
{ "nativeGetLong", "(JII)J",
(void*)nativeGetLong },
{ "nativeGetDouble", "(JII)D",
(void*)nativeGetDouble },
{ "nativePutBlob", "(J[BII)Z",
(void*)nativePutBlob },
{ "nativePutString", "(JLjava/lang/String;II)Z",
(void*)nativePutString },
{ "nativePutLong", "(JJII)Z",
(void*)nativePutLong },
{ "nativePutDouble", "(JDII)Z",
(void*)nativePutDouble },
{ "nativePutNull", "(JII)Z",
(void*)nativePutNull },
};
int register_android_database_CursorWindow(JNIEnv* env)
{
jclass clazz;
FIND_CLASS(clazz, "android/database/CharArrayBuffer");
GET_FIELD_ID(gCharArrayBufferClassInfo.data, clazz, "data", "[C");
GET_FIELD_ID(gCharArrayBufferClassInfo.sizeCopied, clazz, "sizeCopied", "I");
gEmptyString = static_cast<jstring>(env->NewGlobalRef(env->NewStringUTF("")));
return jniRegisterNativeMethods(env,
"io/requery/android/database/CursorWindow", sMethods, NELEM(sMethods));
}
} // namespace android

@ -0,0 +1,140 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
#include "android_database_SQLiteCommon.h"
namespace android {
/* throw a SQLiteException with a message appropriate for the error in handle */
void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle) {
throw_sqlite3_exception(env, handle, NULL);
}
/* throw a SQLiteException with the given message */
void throw_sqlite3_exception(JNIEnv* env, const char* message) {
throw_sqlite3_exception(env, NULL, message);
}
/* throw a SQLiteException with a message appropriate for the error in handle
concatenated with the given message
*/
void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle, const char* message) {
if (handle) {
// get the error code and message from the SQLite connection
// the error message may contain more information than the error code
// because it is based on the extended error code rather than the simplified
// error code that SQLite normally returns.
throw_sqlite3_exception(env, sqlite3_extended_errcode(handle),
sqlite3_errmsg(handle), message);
} else {
// we use SQLITE_OK so that a generic SQLiteException is thrown;
// any code not specified in the switch statement below would do.
throw_sqlite3_exception(env, SQLITE_OK, "unknown error", message);
}
}
/* throw a SQLiteException for a given error code
* should only be used when the database connection is not available because the
* error information will not be quite as rich */
void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message) {
throw_sqlite3_exception(env, errcode, "unknown error", message);
}
/* throw a SQLiteException for a given error code, sqlite3message, and
user message
*/
void throw_sqlite3_exception(JNIEnv* env, int errcode,
const char* sqlite3Message, const char* message) {
const char* exceptionClass;
switch (errcode & 0xff) { /* mask off extended error code */
case SQLITE_IOERR:
exceptionClass = "android/database/sqlite/SQLiteDiskIOException";
break;
case SQLITE_CORRUPT:
case SQLITE_NOTADB: // treat "unsupported file format" error as corruption also
exceptionClass = "android/database/sqlite/SQLiteDatabaseCorruptException";
break;
case SQLITE_CONSTRAINT:
exceptionClass = "android/database/sqlite/SQLiteConstraintException";
break;
case SQLITE_ABORT:
exceptionClass = "android/database/sqlite/SQLiteAbortException";
break;
case SQLITE_DONE:
exceptionClass = "android/database/sqlite/SQLiteDoneException";
sqlite3Message = NULL; // SQLite error message is irrelevant in this case
break;
case SQLITE_FULL:
exceptionClass = "android/database/sqlite/SQLiteFullException";
break;
case SQLITE_MISUSE:
exceptionClass = "android/database/sqlite/SQLiteMisuseException";
break;
case SQLITE_PERM:
exceptionClass = "android/database/sqlite/SQLiteAccessPermException";
break;
case SQLITE_BUSY:
exceptionClass = "android/database/sqlite/SQLiteDatabaseLockedException";
break;
case SQLITE_LOCKED:
exceptionClass = "android/database/sqlite/SQLiteTableLockedException";
break;
case SQLITE_READONLY:
exceptionClass = "android/database/sqlite/SQLiteReadOnlyDatabaseException";
break;
case SQLITE_CANTOPEN:
exceptionClass = "android/database/sqlite/SQLiteCantOpenDatabaseException";
break;
case SQLITE_TOOBIG:
exceptionClass = "android/database/sqlite/SQLiteBlobTooBigException";
break;
case SQLITE_RANGE:
exceptionClass = "android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException";
break;
case SQLITE_NOMEM:
exceptionClass = "android/database/sqlite/SQLiteOutOfMemoryException";
break;
case SQLITE_MISMATCH:
exceptionClass = "android/database/sqlite/SQLiteDatatypeMismatchException";
break;
case SQLITE_INTERRUPT:
exceptionClass = "androidx/core/os/OperationCanceledException";
break;
default:
exceptionClass = "android/database/sqlite/SQLiteException";
break;
}
// check this exception class exists otherwise just default to SQLiteException
if (env->FindClass(exceptionClass) == NULL) {
exceptionClass = "android/database/sqlite/SQLiteException";
}
if (sqlite3Message) {
char *zFullmsg = sqlite3_mprintf(
"%s (code %d)%s%s", sqlite3Message, errcode,
(message ? ": " : ""), (message ? message : "")
);
jniThrowException(env, exceptionClass, zFullmsg);
sqlite3_free(zFullmsg);
} else {
jniThrowException(env, exceptionClass, message);
}
}
} // namespace android

@ -0,0 +1,52 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
#ifndef _ANDROID_DATABASE_SQLITE_COMMON_H
#define _ANDROID_DATABASE_SQLITE_COMMON_H
#include <jni.h>
#include <JNIHelp.h>
#include <sqlite3.h>
// Special log tags defined in SQLiteDebug.java.
#define SQLITE_LOG_TAG "SQLiteLog"
#define SQLITE_TRACE_TAG "SQLiteStatements"
#define SQLITE_PROFILE_TAG "SQLiteTime"
namespace android {
/* throw a SQLiteException with a message appropriate for the error in handle */
void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle);
/* throw a SQLiteException with the given message */
void throw_sqlite3_exception(JNIEnv* env, const char* message);
/* throw a SQLiteException with a message appropriate for the error in handle
concatenated with the given message
*/
void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle, const char* message);
/* throw a SQLiteException for a given error code */
void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message);
void throw_sqlite3_exception(JNIEnv* env, int errcode,
const char* sqlite3Message, const char* message);
}
#endif // _ANDROID_DATABASE_SQLITE_COMMON_H

@ -0,0 +1,81 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
#define LOG_TAG "SQLiteDebug"
#include <jni.h>
#include "JNIHelp.h"
#include "ALog-priv.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sqlite3.h>
namespace android {
static struct {
jfieldID memoryUsed;
jfieldID pageCacheOverflow;
jfieldID largestMemAlloc;
} gSQLiteDebugPagerStatsClassInfo;
static void nativeGetPagerStats(JNIEnv *env, jobject clazz, jobject statsObj)
{
int memoryUsed;
int pageCacheOverflow;
int largestMemAlloc;
int unused;
sqlite3_status(SQLITE_STATUS_MEMORY_USED, &memoryUsed, &unused, 0);
sqlite3_status(SQLITE_STATUS_MALLOC_SIZE, &unused, &largestMemAlloc, 0);
sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW, &pageCacheOverflow, &unused, 0);
env->SetIntField(statsObj, gSQLiteDebugPagerStatsClassInfo.memoryUsed, memoryUsed);
env->SetIntField(statsObj, gSQLiteDebugPagerStatsClassInfo.pageCacheOverflow,
pageCacheOverflow);
env->SetIntField(statsObj, gSQLiteDebugPagerStatsClassInfo.largestMemAlloc, largestMemAlloc);
}
/*
* JNI registration.
*/
static JNINativeMethod gMethods[] =
{
{ "nativeGetPagerStats", "(Lio/requery/android/database/sqlite/SQLiteDebug$PagerStats;)V",
(void*) nativeGetPagerStats },
};
int register_android_database_SQLiteDebug(JNIEnv *env)
{
jclass clazz;
FIND_CLASS(clazz, "io/requery/android/database/sqlite/SQLiteDebug$PagerStats");
GET_FIELD_ID(gSQLiteDebugPagerStatsClassInfo.memoryUsed, clazz,
"memoryUsed", "I");
GET_FIELD_ID(gSQLiteDebugPagerStatsClassInfo.largestMemAlloc, clazz,
"largestMemAlloc", "I");
GET_FIELD_ID(gSQLiteDebugPagerStatsClassInfo.pageCacheOverflow, clazz,
"pageCacheOverflow", "I");
return jniRegisterNativeMethods(env, "io/requery/android/database/sqlite/SQLiteDebug",
gMethods, NELEM(gMethods));
}
} // namespace android

@ -0,0 +1,229 @@
#define LOG_TAG "SQLiteFunction"
#include <jni.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include "sqlite3.h"
#include "JNIHelp.h"
#include "ALog-priv.h"
#include "android_database_SQLiteCommon.h"
namespace android {
/* Returns the sqlite3_value for the given arg of the given function.
* If 0 is returned, an exception has been thrown to report the reason. */
static sqlite3_value *tovalue(JNIEnv *env, jlong argsPtr, jint arg) {
if (arg < 0) {
throw_sqlite3_exception(env, "Invalid arg index");
return 0;
}
if (!argsPtr) {
throw_sqlite3_exception(env, "Invalid argsPtr");
return 0;
}
sqlite3_value **args = reinterpret_cast<sqlite3_value**>(argsPtr);
return args[arg];
}
static sqlite3_context *tocontext(JNIEnv *env, jlong contextPtr) {
if (!contextPtr) {
throw_sqlite3_exception(env, "Invalid contextPtr");
return 0;
}
return reinterpret_cast<sqlite3_context*>(contextPtr);
}
/*
* Getters
*/
static jbyteArray nativeGetArgBlob(JNIEnv* env, jclass clazz, jlong argsPtr,
jint arg) {
int length;
jbyteArray byteArray;
const void *blob;
sqlite3_value *value = tovalue(env, argsPtr, arg);
if (!value) return NULL;
blob = sqlite3_value_blob(value);
if (!blob) return NULL;
length = sqlite3_value_bytes(value);
byteArray = env->NewByteArray(length);
if (!byteArray) {
env->ExceptionClear();
throw_sqlite3_exception(env, "Native could not create new byte[]");
return NULL;
}
env->SetByteArrayRegion(byteArray, 0, length, static_cast<const jbyte*>(blob));
return byteArray;
}
static jstring nativeGetArgString(JNIEnv* env, jclass clazz, jlong argsPtr,
jint arg) {
sqlite3_value *value = tovalue(env, argsPtr, arg);
if (!value) return NULL;
const jchar* chars = static_cast<const jchar*>(sqlite3_value_text16(value));
if (!chars) return NULL;
size_t len = sqlite3_value_bytes16(value) / sizeof(jchar);
jstring str = env->NewString(chars, len);
if (!str) {
env->ExceptionClear();
throw_sqlite3_exception(env, "Native could not allocate string");
return NULL;
}
return str;
}
static jlong nativeGetArgLong(JNIEnv* env, jclass clazz, jlong argsPtr,
jint arg) {
sqlite3_value *value = tovalue(env, argsPtr, arg);
return value ? sqlite3_value_int64(value) : 0;
}
static jdouble nativeGetArgDouble(JNIEnv* env, jclass clazz, jlong argsPtr,
jint arg) {
sqlite3_value *value = tovalue(env, argsPtr, arg);
return value ? sqlite3_value_double(value) : 0;
}
static jint nativeGetArgInt(JNIEnv* env, jclass clazz, jlong argsPtr,
jint arg) {
sqlite3_value *value = tovalue(env, argsPtr, arg);
return value ? sqlite3_value_int(value) : 0;
}
/*
* Setters
*/
static void nativeSetResultBlob(JNIEnv* env, jclass clazz,
jlong contextPtr, jbyteArray result) {
sqlite3_context *context = tocontext(env, contextPtr);
if (!context) return;
if (result == NULL) {
sqlite3_result_null(context);
return;
}
jsize len = env->GetArrayLength(result);
void *bytes = env->GetPrimitiveArrayCritical(result, NULL);
if (!bytes) {
env->ExceptionClear();
throw_sqlite3_exception(env, "Out of memory accepting blob");
return;
}
sqlite3_result_blob(context, bytes, len, SQLITE_TRANSIENT);
env->ReleasePrimitiveArrayCritical(result, bytes, JNI_ABORT);
}
static void nativeSetResultString(JNIEnv* env, jclass clazz,
jlong contextPtr, jstring result) {
sqlite3_context *context = tocontext(env, contextPtr);
if (result == NULL) {
sqlite3_result_null(context);
return;
}
const char* chars = env->GetStringUTFChars(result, NULL);
if (!chars) {
ALOGE("result value can't be transferred to UTFChars");
sqlite3_result_error_nomem(context);
return;
}
sqlite3_result_text(context, chars, -1, SQLITE_TRANSIENT);
env->ReleaseStringUTFChars(result, chars);
}
static void nativeSetResultLong(JNIEnv* env, jclass clazz,
jlong contextPtr, jlong result) {
sqlite3_context *context = tocontext(env, contextPtr);
if (context) sqlite3_result_int64(context, result);
}
static void nativeSetResultDouble(JNIEnv* env, jclass clazz,
jlong contextPtr, jdouble result) {
sqlite3_context *context = tocontext(env, contextPtr);
if (context) sqlite3_result_double(context, result);
}
static void nativeSetResultInt(JNIEnv* env, jclass clazz,
jlong contextPtr, jint result) {
sqlite3_context *context = tocontext(env, contextPtr);
if (context) sqlite3_result_int(context, result);
}
static void nativeSetResultError(JNIEnv* env, jclass clazz,
jlong contextPtr, jstring error) {
sqlite3_context *context = tocontext(env, contextPtr);
if (error == NULL) {
sqlite3_result_null(context);
return;
}
const char* chars = env->GetStringUTFChars(error, NULL);
if (!chars) {
ALOGE("result value can't be transferred to UTFChars");
sqlite3_result_error_nomem(context);
return;
}
sqlite3_result_error(context, chars, -1);
env->ReleaseStringUTFChars(error, chars);
}
static void nativeSetResultNull(JNIEnv* env, jclass clazz, jlong contextPtr) {
sqlite3_context *context = tocontext(env, contextPtr);
if (context) sqlite3_result_null(context);
}
static const JNINativeMethod sMethods[] =
{
/* name, signature, funcPtr */
{ "nativeGetArgBlob", "(JI)[B",
(void*)nativeGetArgBlob },
{ "nativeGetArgString", "(JI)Ljava/lang/String;",
(void*)nativeGetArgString },
{ "nativeGetArgLong", "(JI)J",
(void*)nativeGetArgLong },
{ "nativeGetArgDouble", "(JI)D",
(void*)nativeGetArgDouble },
{ "nativeGetArgInt", "(JI)I",
(void*)nativeGetArgInt },
{ "nativeSetResultBlob", "(J[B)V",
(void*)nativeSetResultBlob },
{ "nativeSetResultString", "(JLjava/lang/String;)V",
(void*)nativeSetResultString },
{ "nativeSetResultLong", "(JJ)V",
(void*)nativeSetResultLong },
{ "nativeSetResultDouble", "(JD)V",
(void*)nativeSetResultDouble },
{ "nativeSetResultInt", "(JI)V",
(void*)nativeSetResultInt },
{ "nativeSetResultError", "(JLjava/lang/String;)V",
(void*)nativeSetResultError },
{ "nativeSetResultNull", "(J)V",
(void*)nativeSetResultNull },
};
int register_android_database_SQLiteFunction(JNIEnv* env)
{
return jniRegisterNativeMethods(env,
"io/requery/android/database/sqlite/SQLiteFunction", sMethods, NELEM(sMethods));
}
} // namespace android

@ -0,0 +1,92 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// modified from original source see README at the top level of this project
#define LOG_TAG "SQLiteGlobal"
#include <jni.h>
#include <JNIHelp.h>
#include "ALog-priv.h"
#include <sqlite3.h>
//#include <sqlite3_android.h>
#include "android_database_SQLiteCommon.h"
namespace android {
// Limit heap to 8MB for now. This is 4 times the maximum cursor window
// size, as has been used by the original code in SQLiteDatabase for
// a long time.
static const int SOFT_HEAP_LIMIT = 8 * 1024 * 1024;
// Called each time a message is logged.
static void sqliteLogCallback(void* data, int iErrCode, const char* zMsg) {
bool verboseLog = !!data;
if (iErrCode == 0 || iErrCode == SQLITE_CONSTRAINT || iErrCode == SQLITE_SCHEMA) {
if (verboseLog) {
ALOG(LOG_VERBOSE, SQLITE_LOG_TAG, "(%d) %s\n", iErrCode, zMsg);
}
} else {
ALOG(LOG_ERROR, SQLITE_LOG_TAG, "(%d) %s\n", iErrCode, zMsg);
}
}
// Sets the global SQLite configuration.
// This must be called before any other SQLite functions are called.
static void sqliteInitialize() {
// Enable multi-threaded mode. In this mode, SQLite is safe to use by multiple
// threads as long as no two threads use the same database connection at the same
// time (which we guarantee in the SQLite database wrappers).
sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
// Redirect SQLite log messages to the Android log.
#if 0
bool verboseLog = android_util_Log_isVerboseLogEnabled(SQLITE_LOG_TAG);
#endif
bool verboseLog = false;
sqlite3_config(SQLITE_CONFIG_LOG, &sqliteLogCallback, verboseLog ? (void*)1 : NULL);
// The soft heap limit prevents the page cache allocations from growing
// beyond the given limit, no matter what the max page cache sizes are
// set to. The limit does not, as of 3.5.0, affect any other allocations.
sqlite3_soft_heap_limit(SOFT_HEAP_LIMIT);
// Initialize SQLite.
sqlite3_initialize();
}
static jint nativeReleaseMemory(JNIEnv* env, jclass clazz) {
return sqlite3_release_memory(SOFT_HEAP_LIMIT);
}
static JNINativeMethod sMethods[] =
{
/* name, signature, funcPtr */
{ "nativeReleaseMemory", "()I",
(void*)nativeReleaseMemory },
};
int register_android_database_SQLiteGlobal(JNIEnv *env)
{
sqliteInitialize();
return jniRegisterNativeMethods(env, "io/requery/android/database/sqlite/SQLiteGlobal",
sMethods, NELEM(sMethods));
}
} // namespace android
Loading…
Cancel
Save