mirror of https://github.com/M66B/FairEmail.git
parent
f576d6c8c1
commit
41e8c3b950
@ -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();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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 (<append chunk 1><append chunk2>) AND (<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 (<append chunk 1><append chunk2>) AND (<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
|
File diff suppressed because it is too large
Load Diff
@ -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…
Reference in new issue