From 8ee88255ac2d21045da9ac2368bff9264285ac67 Mon Sep 17 00:00:00 2001
From: M66B
Date: Mon, 26 Sep 2022 09:01:55 +0200
Subject: [PATCH] Switched to native sqlite
---
app/build.gradle | 2 +-
.../email/BoundaryCallbackMessages.java | 3 +-
app/src/main/java/eu/faircode/email/DB.java | 15 +-
.../faircode/email/FragmentDialogSearch.java | 3 +-
.../faircode/email/FragmentOptionsMisc.java | 14 +-
.../java/eu/faircode/email/Fts4DbHelper.java | 15 +-
.../java/eu/faircode/email/Fts5DbHelper.java | 15 +-
app/src/main/java/eu/faircode/email/Log.java | 3 +-
.../java/eu/faircode/email/WorkerCleanup.java | 3 +-
.../java/eu/faircode/email/WorkerFts.java | 3 +-
settings.gradle | 3 +-
sqlite-android/.gitignore | 7 -
sqlite-android/build.gradle | 150 -
sqlite-android/proguard-rules.pro | 14 -
sqlite-android/src/main/AndroidManifest.xml | 2 -
.../android/database/AbstractCursor.java | 421 ---
.../database/AbstractWindowedCursor.java | 177 --
.../android/database/CursorWindow.java | 507 ----
.../CursorWindowAllocationException.java | 29 -
.../database/DatabaseErrorHandler.java | 33 -
.../database/DefaultDatabaseErrorHandler.java | 106 -
.../android/database/sqlite/CloseGuard.java | 234 --
.../RequerySQLiteOpenHelperFactory.java | 95 -
.../database/sqlite/SQLiteClosable.java | 80 -
.../database/sqlite/SQLiteConnection.java | 1585 ----------
.../database/sqlite/SQLiteConnectionPool.java | 1084 -------
.../android/database/sqlite/SQLiteCursor.java | 260 --
.../database/sqlite/SQLiteCursorDriver.java | 56 -
.../sqlite/SQLiteCustomExtension.java | 42 -
.../database/sqlite/SQLiteCustomFunction.java | 54 -
.../database/sqlite/SQLiteDatabase.java | 2603 -----------------
.../sqlite/SQLiteDatabaseConfiguration.java | 203 --
.../android/database/sqlite/SQLiteDebug.java | 173 --
.../sqlite/SQLiteDirectCursorDriver.java | 85 -
.../database/sqlite/SQLiteFunction.java | 185 --
.../android/database/sqlite/SQLiteGlobal.java | 116 -
.../database/sqlite/SQLiteOpenHelper.java | 410 ---
.../database/sqlite/SQLiteProgram.java | 246 --
.../android/database/sqlite/SQLiteQuery.java | 86 -
.../database/sqlite/SQLiteQueryBuilder.java | 612 ----
.../database/sqlite/SQLiteSession.java | 975 ------
.../database/sqlite/SQLiteStatement.java | 172 --
.../database/sqlite/SQLiteStatementInfo.java | 40 -
.../database/sqlite/SQLiteStatementType.java | 107 -
sqlite-android/src/main/jni/Android.mk | 4 -
sqlite-android/src/main/jni/Application.mk | 5 -
.../src/main/jni/sqlite/ALog-priv.h | 72 -
sqlite-android/src/main/jni/sqlite/Android.mk | 67 -
.../src/main/jni/sqlite/CursorWindow.cpp | 283 --
.../src/main/jni/sqlite/CursorWindow.h | 188 --
sqlite-android/src/main/jni/sqlite/Errors.h | 88 -
.../src/main/jni/sqlite/JNIHelp.cpp | 217 --
sqlite-android/src/main/jni/sqlite/JNIHelp.h | 178 --
.../src/main/jni/sqlite/JNIString.cpp | 118 -
sqlite-android/src/main/jni/sqlite/README | 32 -
.../sqlite/android_database_CursorWindow.cpp | 412 ---
.../sqlite/android_database_SQLiteCommon.cpp | 140 -
.../sqlite/android_database_SQLiteCommon.h | 52 -
.../android_database_SQLiteConnection.cpp | 1048 -------
.../sqlite/android_database_SQLiteDebug.cpp | 81 -
.../android_database_SQLiteFunction.cpp | 229 --
.../sqlite/android_database_SQLiteGlobal.cpp | 92 -
62 files changed, 38 insertions(+), 14296 deletions(-)
delete mode 100644 sqlite-android/.gitignore
delete mode 100644 sqlite-android/build.gradle
delete mode 100644 sqlite-android/proguard-rules.pro
delete mode 100644 sqlite-android/src/main/AndroidManifest.xml
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/AbstractCursor.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/AbstractWindowedCursor.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/CursorWindow.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/CursorWindowAllocationException.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/DatabaseErrorHandler.java
delete mode 100755 sqlite-android/src/main/java/io/requery/android/database/DefaultDatabaseErrorHandler.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/CloseGuard.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/RequerySQLiteOpenHelperFactory.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteClosable.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteConnection.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteConnectionPool.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteCursor.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteCursorDriver.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteCustomExtension.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteCustomFunction.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDatabase.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDatabaseConfiguration.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDebug.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDirectCursorDriver.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteFunction.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteGlobal.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteOpenHelper.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteProgram.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteQuery.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteQueryBuilder.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteSession.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteStatement.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteStatementInfo.java
delete mode 100644 sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteStatementType.java
delete mode 100644 sqlite-android/src/main/jni/Android.mk
delete mode 100644 sqlite-android/src/main/jni/Application.mk
delete mode 100644 sqlite-android/src/main/jni/sqlite/ALog-priv.h
delete mode 100644 sqlite-android/src/main/jni/sqlite/Android.mk
delete mode 100644 sqlite-android/src/main/jni/sqlite/CursorWindow.cpp
delete mode 100644 sqlite-android/src/main/jni/sqlite/CursorWindow.h
delete mode 100644 sqlite-android/src/main/jni/sqlite/Errors.h
delete mode 100644 sqlite-android/src/main/jni/sqlite/JNIHelp.cpp
delete mode 100644 sqlite-android/src/main/jni/sqlite/JNIHelp.h
delete mode 100644 sqlite-android/src/main/jni/sqlite/JNIString.cpp
delete mode 100644 sqlite-android/src/main/jni/sqlite/README
delete mode 100644 sqlite-android/src/main/jni/sqlite/android_database_CursorWindow.cpp
delete mode 100644 sqlite-android/src/main/jni/sqlite/android_database_SQLiteCommon.cpp
delete mode 100644 sqlite-android/src/main/jni/sqlite/android_database_SQLiteCommon.h
delete mode 100644 sqlite-android/src/main/jni/sqlite/android_database_SQLiteConnection.cpp
delete mode 100644 sqlite-android/src/main/jni/sqlite/android_database_SQLiteDebug.cpp
delete mode 100644 sqlite-android/src/main/jni/sqlite/android_database_SQLiteFunction.cpp
delete mode 100644 sqlite-android/src/main/jni/sqlite/android_database_SQLiteGlobal.cpp
diff --git a/app/build.gradle b/app/build.gradle
index 4f0350c03f..33c9e49def 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -469,7 +469,7 @@ dependencies {
// https://github.com/requery/sqlite-android/
// https://jitpack.io/#requery/sqlite-android
//implementation "com.github.requery:sqlite-android:$requery_version"
- implementation project(':sqlite-android')
+ //implementation project(':sqlite-android')
// https://mvnrepository.com/artifact/androidx.paging/paging-runtime
// https://developer.android.com/jetpack/androidx/releases/paging
diff --git a/app/src/main/java/eu/faircode/email/BoundaryCallbackMessages.java b/app/src/main/java/eu/faircode/email/BoundaryCallbackMessages.java
index 853dfc7c4b..f218258124 100644
--- a/app/src/main/java/eu/faircode/email/BoundaryCallbackMessages.java
+++ b/app/src/main/java/eu/faircode/email/BoundaryCallbackMessages.java
@@ -21,6 +21,7 @@ package eu.faircode.email;
import android.content.Context;
import android.content.SharedPreferences;
+import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import androidx.annotation.NonNull;
@@ -83,8 +84,6 @@ import javax.mail.search.SearchTerm;
import javax.mail.search.SizeTerm;
import javax.mail.search.SubjectTerm;
-import io.requery.android.database.sqlite.SQLiteDatabase;
-
public class BoundaryCallbackMessages extends PagedList.BoundaryCallback {
private Context context;
private AdapterMessage.ViewType viewType;
diff --git a/app/src/main/java/eu/faircode/email/DB.java b/app/src/main/java/eu/faircode/email/DB.java
index 65fd360fd7..602e0326b6 100644
--- a/app/src/main/java/eu/faircode/email/DB.java
+++ b/app/src/main/java/eu/faircode/email/DB.java
@@ -6,6 +6,7 @@ import android.app.ActivityManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabaseCorruptException;
import android.net.Uri;
import android.os.Build;
@@ -47,9 +48,6 @@ import java.util.concurrent.ExecutorService;
import javax.mail.Address;
import javax.mail.internet.InternetAddress;
-import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
-import io.requery.android.database.sqlite.SQLiteDatabase;
-
/*
This file is part of FairEmail.
@@ -422,7 +420,7 @@ public abstract class DB extends RoomDatabase {
return Room
.databaseBuilder(context, DB.class, DB_NAME)
- .openHelperFactory(new RequerySQLiteOpenHelperFactory())
+ //.openHelperFactory(new RequerySQLiteOpenHelperFactory())
.setQueryExecutor(executorQuery)
.setTransactionExecutor(executorTransaction)
.setJournalMode(wal ? JournalMode.WRITE_AHEAD_LOGGING : JournalMode.TRUNCATE) // using the latest sqlite
@@ -2561,7 +2559,14 @@ public abstract class DB extends RoomDatabase {
@Override
@SuppressWarnings("deprecation")
public void endTransaction() {
- super.endTransaction();
+ try {
+ super.endTransaction();
+ } catch (IllegalStateException ex) {
+ if ("Cannot perform this operation because there is no current transaction.".equals(ex.getMessage()))
+ Log.w(ex);
+ else
+ throw ex;
+ }
}
public static class Converters {
diff --git a/app/src/main/java/eu/faircode/email/FragmentDialogSearch.java b/app/src/main/java/eu/faircode/email/FragmentDialogSearch.java
index 5bbf91a029..2ec95e78b9 100644
--- a/app/src/main/java/eu/faircode/email/FragmentDialogSearch.java
+++ b/app/src/main/java/eu/faircode/email/FragmentDialogSearch.java
@@ -27,6 +27,7 @@ import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.database.Cursor;
import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteDatabase;
import android.graphics.Typeface;
import android.os.Bundle;
import android.text.TextUtils;
@@ -58,8 +59,6 @@ import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
-import io.requery.android.database.sqlite.SQLiteDatabase;
-
public class FragmentDialogSearch extends FragmentDialogBase {
private ImageButton ibMore;
private TextView tvMore;
diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java b/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java
index 2dc25d2de8..c52a28ecf9 100644
--- a/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java
+++ b/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java
@@ -34,6 +34,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.res.Resources;
+import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabaseCorruptException;
import android.graphics.Paint;
import android.graphics.Typeface;
@@ -86,6 +87,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
+import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.text.NumberFormat;
import java.util.ArrayList;
@@ -98,8 +100,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.SortedMap;
-import io.requery.android.database.sqlite.SQLiteDatabase;
-
public class FragmentOptionsMisc extends FragmentBase implements SharedPreferences.OnSharedPreferenceChangeListener {
private boolean resumed = false;
private List> languages = new ArrayList<>();
@@ -2115,16 +2115,16 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
tvFingerprint.setText(Helper.getFingerprint(getContext()));
- int cursorWindowSize = -1;
+ Integer cursorWindowSize = null;
try {
- Field fCursorWindowSize = io.requery.android.database.CursorWindow.class.getDeclaredField("sDefaultCursorWindowSize");
- fCursorWindowSize.setAccessible(true);
- cursorWindowSize = fCursorWindowSize.getInt(null);
+ //Field fCursorWindowSize = android.database.CursorWindow.class.getDeclaredField("sDefaultCursorWindowSize");
+ //fCursorWindowSize.setAccessible(true);
+ //cursorWindowSize = fCursorWindowSize.getInt(null);
} catch (Throwable ex) {
Log.w(ex);
}
tvCursorWindow.setText(getString(R.string.title_advanced_cursor_window,
- Helper.humanReadableByteCount(cursorWindowSize, false)));
+ cursorWindowSize == null ? "?" : Helper.humanReadableByteCount(cursorWindowSize, false)));
cardDebug.setVisibility(swDebug.isChecked() || BuildConfig.DEBUG ? View.VISIBLE : View.GONE);
}
diff --git a/app/src/main/java/eu/faircode/email/Fts4DbHelper.java b/app/src/main/java/eu/faircode/email/Fts4DbHelper.java
index b98aed42aa..a3e54957c7 100644
--- a/app/src/main/java/eu/faircode/email/Fts4DbHelper.java
+++ b/app/src/main/java/eu/faircode/email/Fts4DbHelper.java
@@ -23,6 +23,8 @@ import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
import android.text.TextUtils;
import java.util.ArrayList;
@@ -31,9 +33,6 @@ import java.util.List;
import javax.mail.Address;
-import io.requery.android.database.sqlite.SQLiteDatabase;
-import io.requery.android.database.sqlite.SQLiteOpenHelper;
-
// https://www.sqlite.org/fts3.html
// fts4 requires sqlite version 3.7.4, API 21 Android
public class Fts4DbHelper extends SQLiteOpenHelper {
@@ -121,7 +120,7 @@ public class Fts4DbHelper extends SQLiteOpenHelper {
cv.put("keyword", TextUtils.join(", ", message.keywords));
cv.put("text", text);
cv.put("notes", message.notes);
- db.insert("message", SQLiteDatabase.CONFLICT_FAIL, cv);
+ db.insertWithOnConflict("message", null, cv, SQLiteDatabase.CONFLICT_FAIL);
}
static void delete(SQLiteDatabase db) {
@@ -129,18 +128,18 @@ public class Fts4DbHelper extends SQLiteOpenHelper {
}
static void delete(SQLiteDatabase db, long id) {
- db.delete("message", "rowid = ?", new Object[]{id});
+ db.delete("message", "rowid = ?", new String[]{Long.toString(id)});
}
static List getSuggestions(SQLiteDatabase db, String query, int max) {
List result = new ArrayList<>();
- try (Cursor cursor = db.query(
+ try (Cursor cursor = db.rawQuery(
"SELECT term FROM message_terms" +
" WHERE term LIKE ?" +
" ORDER BY occurrences DESC" +
" LIMIT " + max,
- new Object[]{query})) {
+ new String[]{query})) {
while (cursor != null && cursor.moveToNext())
result.add(cursor.getString(0));
}
@@ -232,7 +231,7 @@ public class Fts4DbHelper extends SQLiteOpenHelper {
try (Cursor cursor = db.query(
"message", new String[]{"rowid"},
select + "message MATCH ?",
- new Object[]{search},
+ new String[]{search},
null, null, "time DESC", null)) {
while (cursor != null && cursor.moveToNext())
result.add(cursor.getLong(0));
diff --git a/app/src/main/java/eu/faircode/email/Fts5DbHelper.java b/app/src/main/java/eu/faircode/email/Fts5DbHelper.java
index 16f8b76c50..7b13e9ed9b 100644
--- a/app/src/main/java/eu/faircode/email/Fts5DbHelper.java
+++ b/app/src/main/java/eu/faircode/email/Fts5DbHelper.java
@@ -23,6 +23,8 @@ import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
import android.text.TextUtils;
import java.util.ArrayList;
@@ -31,9 +33,6 @@ import java.util.List;
import javax.mail.Address;
-import io.requery.android.database.sqlite.SQLiteDatabase;
-import io.requery.android.database.sqlite.SQLiteOpenHelper;
-
// https://www.sqlite.org/fts5.html
public class Fts5DbHelper extends SQLiteOpenHelper {
private Context context;
@@ -112,7 +111,7 @@ public class Fts5DbHelper extends SQLiteOpenHelper {
cv.put("keyword", TextUtils.join(", ", message.keywords));
cv.put("text", text);
cv.put("notes", message.notes);
- db.insert("message", SQLiteDatabase.CONFLICT_FAIL, cv);
+ db.insertWithOnConflict("message", null, cv, SQLiteDatabase.CONFLICT_FAIL);
}
static void delete(SQLiteDatabase db) {
@@ -120,18 +119,18 @@ public class Fts5DbHelper extends SQLiteOpenHelper {
}
static void delete(SQLiteDatabase db, long id) {
- db.delete("message", "rowid = ?", new Object[]{id});
+ db.delete("message", "rowid = ?", new String[]{Long.toString(id)});
}
static List getSuggestions(SQLiteDatabase db, String query, int max) {
List result = new ArrayList<>();
- try (Cursor cursor = db.query(
+ try (Cursor cursor = db.rawQuery(
"SELECT term FROM message_terms" +
" WHERE term LIKE ?" +
" ORDER BY cnt" +
" LIMIT " + max,
- new Object[]{query})) {
+ new String[]{query})) {
while (cursor != null && cursor.moveToNext())
result.add(cursor.getString(0));
}
@@ -223,7 +222,7 @@ public class Fts5DbHelper extends SQLiteOpenHelper {
try (Cursor cursor = db.query(
"message", new String[]{"rowid"},
select + "message MATCH ?",
- new Object[]{search},
+ new String[]{search},
null, null, "time DESC", null)) {
while (cursor != null && cursor.moveToNext())
result.add(cursor.getLong(0));
diff --git a/app/src/main/java/eu/faircode/email/Log.java b/app/src/main/java/eu/faircode/email/Log.java
index 3fdc1da0c5..542c7eec9c 100644
--- a/app/src/main/java/eu/faircode/email/Log.java
+++ b/app/src/main/java/eu/faircode/email/Log.java
@@ -39,6 +39,7 @@ import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.database.CursorWindowAllocationException;
import android.database.sqlite.SQLiteFullException;
import android.graphics.Point;
import android.graphics.Typeface;
@@ -164,8 +165,6 @@ import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
-import io.requery.android.database.CursorWindowAllocationException;
-
public class Log {
private static Context ctx;
diff --git a/app/src/main/java/eu/faircode/email/WorkerCleanup.java b/app/src/main/java/eu/faircode/email/WorkerCleanup.java
index 4deb2b5f9f..b27750461a 100644
--- a/app/src/main/java/eu/faircode/email/WorkerCleanup.java
+++ b/app/src/main/java/eu/faircode/email/WorkerCleanup.java
@@ -26,6 +26,7 @@ import android.app.NotificationManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.text.TextUtils;
@@ -45,8 +46,6 @@ import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
-import io.requery.android.database.sqlite.SQLiteDatabase;
-
public class WorkerCleanup extends Worker {
private static final int CLEANUP_INTERVAL = 4; // hours
private static final long KEEP_FILES_DURATION = 3600 * 1000L; // milliseconds
diff --git a/app/src/main/java/eu/faircode/email/WorkerFts.java b/app/src/main/java/eu/faircode/email/WorkerFts.java
index 3020841c89..398df2318a 100644
--- a/app/src/main/java/eu/faircode/email/WorkerFts.java
+++ b/app/src/main/java/eu/faircode/email/WorkerFts.java
@@ -24,6 +24,7 @@ import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
@@ -38,8 +39,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
-import io.requery.android.database.sqlite.SQLiteDatabase;
-
public class WorkerFts extends Worker {
private static final int INDEX_DELAY = 30; // seconds
private static final int INDEX_BATCH_SIZE = 100;
diff --git a/settings.gradle b/settings.gradle
index ab1f6a7344..fd78ac7fec 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,2 @@
-include ':app', ':openpgp-api', ':sqlite-android'
+include ':app', ':openpgp-api'
project(':openpgp-api').projectDir = new File('openpgp-api')
-project(':sqlite-android').projectDir = new File('sqlite-android')
diff --git a/sqlite-android/.gitignore b/sqlite-android/.gitignore
deleted file mode 100644
index 054fce48a8..0000000000
--- a/sqlite-android/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-/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/
diff --git a/sqlite-android/build.gradle b/sqlite-android/build.gradle
deleted file mode 100644
index 5357b8d23d..0000000000
--- a/sqlite-android/build.gradle
+++ /dev/null
@@ -1,150 +0,0 @@
-plugins {
- id 'de.undercouch.download'
- id 'com.android.library'
- id 'maven-publish'
-}
-
-group = 'io.requery'
-version = '3.39.3'
-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-3390300.zip'
-}
-
-task downloadSqlite(type: Download) {
- src project.sqliteDistributionUrl
- dest 'src/main/jni/sqlite.zip'
- overwrite false
-}
-
-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
- }
- }
- }
-}
diff --git a/sqlite-android/proguard-rules.pro b/sqlite-android/proguard-rules.pro
deleted file mode 100644
index 02c5d5af92..0000000000
--- a/sqlite-android/proguard-rules.pro
+++ /dev/null
@@ -1,14 +0,0 @@
--keepclasseswithmembers class io.requery.android.database.** {
- native ;
- public (...);
-}
--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
diff --git a/sqlite-android/src/main/AndroidManifest.xml b/sqlite-android/src/main/AndroidManifest.xml
deleted file mode 100644
index 795975fcb4..0000000000
--- a/sqlite-android/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
diff --git a/sqlite-android/src/main/java/io/requery/android/database/AbstractCursor.java b/sqlite-android/src/main/java/io/requery/android/database/AbstractCursor.java
deleted file mode 100644
index 27a5e1c3c7..0000000000
--- a/sqlite-android/src/main/java/io/requery/android/database/AbstractCursor.java
+++ /dev/null
@@ -1,421 +0,0 @@
-/*
- * 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.
- *
- * 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.
- *
- *
- * @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 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);
- }
- }
- }
-}
diff --git a/sqlite-android/src/main/java/io/requery/android/database/AbstractWindowedCursor.java b/sqlite-android/src/main/java/io/requery/android/database/AbstractWindowedCursor.java
deleted file mode 100644
index 1a850138c6..0000000000
--- a/sqlite-android/src/main/java/io/requery/android/database/AbstractWindowedCursor.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * 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.
- *
- * 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.
- *
- * 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.
- *
- * 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.
- *
- */
-@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.
- *
- * 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.
- *
- * If the cursor previously had a cursor window, then it is closed when the
- * new cursor window is assigned.
- *
- *
- * @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();
- }
-}
diff --git a/sqlite-android/src/main/java/io/requery/android/database/CursorWindow.java b/sqlite-android/src/main/java/io/requery/android/database/CursorWindow.java
deleted file mode 100644
index b7af4238fc..0000000000
--- a/sqlite-android/src/main/java/io/requery/android/database/CursorWindow.java
+++ /dev/null
@@ -1,507 +0,0 @@
-/*
- * 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.
- *
- * 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.
- *
- *
- * @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 : "";
- 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.
- *
- * The start position ({@link #getStartPosition()}), number of rows ({@link #getNumRows()}),
- * and number of columns in the cursor are all reset to zero.
- *
- */
- public void clear() {
- mStartPos = 0;
- nativeClear(mWindowPtr);
- }
-
- /**
- * Gets the start position of this cursor window.
- *
- * 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}.
- *
- *
- * @return The zero-based start position.
- */
- public int getStartPosition() {
- return mStartPos;
- }
-
- /**
- * Sets the start position of this cursor window.
- *
- * 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}.
- *
- *
- * @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.
- *
- * 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.
- *
- *
- * @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.
- *
- * The returned field types are:
- *
- *
{@link Cursor#FIELD_TYPE_NULL}
- *
{@link Cursor#FIELD_TYPE_INTEGER}
- *
{@link Cursor#FIELD_TYPE_FLOAT}
- *
{@link Cursor#FIELD_TYPE_STRING}
- *
{@link Cursor#FIELD_TYPE_BLOB}
- *
- *
- *
- * @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.
- *
- * The result is determined as follows:
- *
- *
If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
- * is null.
- *
If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then the result
- * is the blob value.
- *
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.
- *
If the field is of type {@link Cursor#FIELD_TYPE_INTEGER} or
- * {@link Cursor#FIELD_TYPE_FLOAT}, then a {@link SQLiteException} is thrown.
- *
- *
- *
- * @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.
- *
- * The result is determined as follows:
- *
- *
If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
- * is null.
- *
If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
- * is the string value.
- *
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 printf family of functions using
- * format specifier %lld.
- *
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 printf family of functions using
- * format specifier %g.
- *
If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
- * {@link SQLiteException} is thrown.
- *
- *
- *
- * @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}.
- *
- * The buffer is populated as follows:
- *
- *
If the buffer is too small for the value to be copied, then it is
- * automatically resized.
- *
If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the buffer
- * is set to an empty string.
- *
If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the buffer
- * is set to the contents of the string.
- *
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 printf family of functions using
- * format specifier %lld.
- *
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 printf family of functions using
- * format specifier %g.
- *
If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
- * {@link SQLiteException} is thrown.
- *
- *
- *
- * @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 long.
- *
- * The result is determined as follows:
- *
- *
If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
- * is 0L.
- *
If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
- * is the value obtained by parsing the string value with strtoll.
- *
If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
- * is the long value.
- *
If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
- * is the floating-point value converted to a long.
- *
If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
- * {@link SQLiteException} is thrown.
- *
- *
- *
- * @param row The zero-based row index.
- * @param column The zero-based column index.
- * @return The value of the field as a long.
- */
- 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
- * double.
- *
- * The result is determined as follows:
- *
- *
If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
- * is 0.0.
- *
If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
- * is the value obtained by parsing the string value with strtod.
- *
If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
- * is the integer value converted to a double.
- *
If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
- * is the double value.
- *
If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
- * {@link SQLiteException} is thrown.
- *
- *
- *
- * @param row The zero-based row index.
- * @param column The zero-based column index.
- * @return The value of the field as a double.
- */
- 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
- * short.
- *
- * The result is determined by invoking {@link #getLong} and converting the
- * result to short.
- *
- *
- * @param row The zero-based row index.
- * @param column The zero-based column index.
- * @return The value of the field as a short.
- */
- 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
- * int.
- *
- * The result is determined by invoking {@link #getLong} and converting the
- * result to int.
- *
- *
- * @param row The zero-based row index.
- * @param column The zero-based column index.
- * @return The value of the field as an int.
- */
- 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
- * float.
- *
- * The result is determined by invoking {@link #getDouble} and converting the
- * result to float.
- *
- *
- * @param row The zero-based row index.
- * @param column The zero-based column index.
- * @return The value of the field as an float.
- */
- 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;
- }
-}
diff --git a/sqlite-android/src/main/java/io/requery/android/database/CursorWindowAllocationException.java b/sqlite-android/src/main/java/io/requery/android/database/CursorWindowAllocationException.java
deleted file mode 100644
index 3ac59bac02..0000000000
--- a/sqlite-android/src/main/java/io/requery/android/database/CursorWindowAllocationException.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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);
- }
-}
diff --git a/sqlite-android/src/main/java/io/requery/android/database/DatabaseErrorHandler.java b/sqlite-android/src/main/java/io/requery/android/database/DatabaseErrorHandler.java
deleted file mode 100644
index e27fd9badd..0000000000
--- a/sqlite-android/src/main/java/io/requery/android/database/DatabaseErrorHandler.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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);
-}
diff --git a/sqlite-android/src/main/java/io/requery/android/database/DefaultDatabaseErrorHandler.java b/sqlite-android/src/main/java/io/requery/android/database/DefaultDatabaseErrorHandler.java
deleted file mode 100755
index 5225d15ff2..0000000000
--- a/sqlite-android/src/main/java/io/requery/android/database/DefaultDatabaseErrorHandler.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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.
- *
- * An application can specify an implementation of {@link DatabaseErrorHandler} on the
- * following:
- *
- * The specified {@link DatabaseErrorHandler} is used to handle database corruption errors, if they
- * occur.
- *
- * 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> 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 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());
- }
- }
-}
diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/CloseGuard.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/CloseGuard.java
deleted file mode 100644
index 790ca594da..0000000000
--- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/CloseGuard.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * 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).
- *
- *
- * In usage where the resource to be explicitly cleaned up are
- * allocated after object construction, CloseGuard protection can
- * be deferred. For example:
- *
- * 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.
- *
- *
- *
- * 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);
- }
- }
-}
diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/RequerySQLiteOpenHelperFactory.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/RequerySQLiteOpenHelperFactory.java
deleted file mode 100644
index 408a5da5ce..0000000000
--- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/RequerySQLiteOpenHelperFactory.java
+++ /dev/null
@@ -1,95 +0,0 @@
-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;
-
- @SuppressWarnings("WeakerAccess")
- public RequerySQLiteOpenHelperFactory(Iterable configurationOptions) {
- this.configurationOptions = configurationOptions;
- }
-
- public RequerySQLiteOpenHelperFactory() {
- this(Collections.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;
-
- CallbackSQLiteOpenHelper(Context context, String name, SupportSQLiteOpenHelper.Callback cb, Iterable 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);
- }
-}
diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteClosable.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteClosable.java
deleted file mode 100644
index 0edc17d4e0..0000000000
--- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteClosable.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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();
- }
-}
diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteConnection.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteConnection.java
deleted file mode 100644
index c408b86522..0000000000
--- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteConnection.java
+++ /dev/null
@@ -1,1585 +0,0 @@
-/*
- * 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.annotation.TargetApi;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteBindOrColumnIndexOutOfRangeException;
-import android.database.sqlite.SQLiteDatabaseLockedException;
-import android.database.sqlite.SQLiteException;
-import android.os.Build;
-import android.os.Looper;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-import android.util.Printer;
-import androidx.collection.LruCache;
-import androidx.core.os.CancellationSignal;
-import androidx.core.os.OperationCanceledException;
-import io.requery.android.database.CursorWindow;
-
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.Map;
-import java.util.regex.Pattern;
-
-/**
- * Represents a SQLite database connection.
- * Each connection wraps an instance of a native sqlite3 object.
- *
- * When database connection pooling is enabled, there can be multiple active
- * connections to the same database. Otherwise there is typically only one
- * connection per database.
- *
- * When the SQLite WAL feature is enabled, multiple readers and one writer
- * can concurrently access the database. Without WAL, readers and writers
- * are mutually exclusive.
- *
- *
- *
Ownership and concurrency guarantees
- *
- * Connection objects are not thread-safe. They are acquired as needed to
- * perform a database operation and are then returned to the pool. At any
- * given time, a connection is either owned and used by a {@link SQLiteSession}
- * object or the {@link SQLiteConnectionPool}. Those classes are
- * responsible for serializing operations to guard against concurrent
- * use of a connection.
- *
- * The guarantee of having a single owner allows this class to be implemented
- * without locks and greatly simplifies resource management.
- *
- *
- *
Encapsulation guarantees
- *
- * The connection object object owns *all* of the SQLite related native
- * objects that are associated with the connection. What's more, there are
- * no other objects in the system that are capable of obtaining handles to
- * those native objects. Consequently, when the connection is closed, we do
- * not have to worry about what other components might have references to
- * its associated SQLite state -- there are none.
- *
- * Encapsulation is what ensures that the connection object's
- * lifecycle does not become a tortured mess of finalizers and reference
- * queues.
- *
- *
- *
Reentrance
- *
- * This class must tolerate reentrant execution of SQLite operations because
- * triggers may call custom SQLite functions that perform additional queries.
- *
- *
- * @hide
- */
-@SuppressWarnings("TryFinallyCanBeTryWithResources")
-public final class SQLiteConnection implements CancellationSignal.OnCancelListener {
- private static final String TAG = "SQLiteConnection";
- private static final boolean DEBUG = false;
-
- private static final String[] EMPTY_STRING_ARRAY = new String[0];
- private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
-
- private static final Pattern TRIM_SQL_PATTERN = Pattern.compile("[\\s]*\\n+[\\s]*");
-
- private final CloseGuard mCloseGuard = CloseGuard.get();
-
- private final SQLiteConnectionPool mPool;
- private final SQLiteDatabaseConfiguration mConfiguration;
- private final int mConnectionId;
- private final boolean mIsPrimaryConnection;
- private final boolean mIsReadOnlyConnection;
- private final PreparedStatementCache mPreparedStatementCache;
- private PreparedStatement mPreparedStatementPool;
-
- // The recent operations log.
- private final OperationLog mRecentOperations = new OperationLog();
-
- // The native SQLiteConnection pointer. (FOR INTERNAL USE ONLY)
- private long mConnectionPtr;
-
- private boolean mOnlyAllowReadOnlyOperations;
-
- // The number of times attachCancellationSignal has been called.
- // Because SQLite statement execution can be reentrant, we keep track of how many
- // times we have attempted to attach a cancellation signal to the connection so that
- // we can ensure that we detach the signal at the right time.
- private int mCancellationSignalAttachCount;
-
- private static native long nativeOpen(String path, int openFlags, String label,
- boolean enableTrace, boolean enableProfile);
- private static native void nativeClose(long connectionPtr);
- private static native void nativeRegisterCustomFunction(long connectionPtr,
- SQLiteCustomFunction function);
- private static native void nativeRegisterFunction(long connectionPtr,
- SQLiteFunction function);
- private static native void nativeRegisterLocalizedCollators(long connectionPtr, String locale);
- private static native long nativePrepareStatement(long connectionPtr, String sql);
- private static native void nativeFinalizeStatement(long connectionPtr, long statementPtr);
- private static native int nativeGetParameterCount(long connectionPtr, long statementPtr);
- private static native boolean nativeIsReadOnly(long connectionPtr, long statementPtr);
- private static native int nativeGetColumnCount(long connectionPtr, long statementPtr);
- private static native String nativeGetColumnName(long connectionPtr, long statementPtr,
- int index);
- private static native void nativeBindNull(long connectionPtr, long statementPtr,
- int index);
- private static native void nativeBindLong(long connectionPtr, long statementPtr,
- int index, long value);
- private static native void nativeBindDouble(long connectionPtr, long statementPtr,
- int index, double value);
- private static native void nativeBindString(long connectionPtr, long statementPtr,
- int index, String value);
- private static native void nativeBindBlob(long connectionPtr, long statementPtr,
- int index, byte[] value);
- private static native void nativeResetStatementAndClearBindings(
- long connectionPtr, long statementPtr);
- private static native void nativeExecute(long connectionPtr, long statementPtr);
- private static native long nativeExecuteForLong(long connectionPtr, long statementPtr);
- private static native String nativeExecuteForString(long connectionPtr, long statementPtr);
- private static native int nativeExecuteForBlobFileDescriptor(
- long connectionPtr, long statementPtr);
- private static native int nativeExecuteForChangedRowCount(long connectionPtr, long statementPtr);
- private static native long nativeExecuteForLastInsertedRowId(
- long connectionPtr, long statementPtr);
- private static native long nativeExecuteForCursorWindow(
- long connectionPtr, long statementPtr, long winPtr,
- int startPos, int requiredPos, boolean countAllRows);
- private static native int nativeGetDbLookaside(long connectionPtr);
- private static native void nativeCancel(long connectionPtr);
- private static native void nativeResetCancel(long connectionPtr, boolean cancelable);
-
- private static native boolean nativeHasCodec();
- private static native void nativeLoadExtension(long connectionPtr, String file, String proc);
-
- public static boolean hasCodec(){ return nativeHasCodec(); }
-
- private SQLiteConnection(SQLiteConnectionPool pool,
- SQLiteDatabaseConfiguration configuration,
- int connectionId, boolean primaryConnection) {
- mPool = pool;
- mConfiguration = new SQLiteDatabaseConfiguration(configuration);
- mConnectionId = connectionId;
- mIsPrimaryConnection = primaryConnection;
- mIsReadOnlyConnection = (configuration.openFlags & SQLiteDatabase.OPEN_READONLY) != 0;
- mPreparedStatementCache = new PreparedStatementCache(
- mConfiguration.maxSqlCacheSize);
- mCloseGuard.open("close");
- }
-
- @SuppressWarnings("ThrowFromFinallyBlock")
- @Override
- protected void finalize() throws Throwable {
- try {
- if (mPool != null && mConnectionPtr != 0) {
- mPool.onConnectionLeaked();
- }
-
- dispose(true);
- } finally {
- super.finalize();
- }
- }
-
- // Called by SQLiteConnectionPool only.
- static SQLiteConnection open(SQLiteConnectionPool pool,
- SQLiteDatabaseConfiguration configuration,
- int connectionId, boolean primaryConnection) {
- SQLiteConnection connection = new SQLiteConnection(pool, configuration,
- connectionId, primaryConnection);
- try {
- connection.open();
- return connection;
- } catch (SQLiteException ex) {
- connection.dispose(false);
- throw ex;
- }
- }
-
- // Called by SQLiteConnectionPool only.
- // Closes the database closes and releases all of its associated resources.
- // Do not call methods on the connection after it is closed. It will probably crash.
- void close() {
- dispose(false);
- }
-
- private void open() {
- mConnectionPtr = nativeOpen(mConfiguration.path,
- // remove the wal flag as its a custom flag not supported by sqlite3_open_v2
- mConfiguration.openFlags & ~SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING,
- mConfiguration.label,
- SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME);
-
- setPageSize();
- setForeignKeyModeFromConfiguration();
- setJournalSizeLimit();
- setAutoCheckpointInterval();
- if (!nativeHasCodec()) {
- setWalModeFromConfiguration();
- setLocaleFromConfiguration();
- }
-
- // Register (deprecated) custom functions.
- final int customFunctionCount = mConfiguration.customFunctions.size();
- for (int i = 0; i < customFunctionCount; i++) {
- SQLiteCustomFunction function = mConfiguration.customFunctions.get(i);
- nativeRegisterCustomFunction(mConnectionPtr, function);
- }
-
- // Register functions
- final int functionCount = mConfiguration.functions.size();
- for (int i = 0; i < functionCount; i++) {
- SQLiteFunction function = mConfiguration.functions.get(i);
- nativeRegisterFunction(mConnectionPtr, function);
- }
-
- // Register custom extensions
- for (SQLiteCustomExtension extension : mConfiguration.customExtensions) {
- nativeLoadExtension(mConnectionPtr, extension.path, extension.entryPoint);
- }
- }
-
- private void dispose(boolean finalized) {
- if (mCloseGuard != null) {
- if (finalized) {
- mCloseGuard.warnIfOpen();
- }
- mCloseGuard.close();
- }
-
- if (mConnectionPtr != 0) {
- final int cookie = mRecentOperations.beginOperation("close", null, null);
- try {
- mPreparedStatementCache.evictAll();
- nativeClose(mConnectionPtr);
- mConnectionPtr = 0;
- } finally {
- mRecentOperations.endOperation(cookie);
- }
- }
- }
-
- private void setPageSize() {
- if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
- final long newValue = SQLiteGlobal.getDefaultPageSize();
- long value = executeForLong("PRAGMA page_size", null, null);
- if (value != newValue) {
- execute("PRAGMA page_size=" + newValue, null, null);
- }
- }
- }
-
- private void setAutoCheckpointInterval() {
- if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
- final long newValue = SQLiteGlobal.getWALAutoCheckpoint();
- long value = executeForLong("PRAGMA wal_autocheckpoint", null, null);
- if (value != newValue) {
- executeForLong("PRAGMA wal_autocheckpoint=" + newValue, null, null);
- }
- }
- }
-
- private void setJournalSizeLimit() {
- if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
- final long newValue = SQLiteGlobal.getJournalSizeLimit();
- long value = executeForLong("PRAGMA journal_size_limit", null, null);
- if (value != newValue) {
- executeForLong("PRAGMA journal_size_limit=" + newValue, null, null);
- }
- }
- }
-
- private void setForeignKeyModeFromConfiguration() {
- if (!mIsReadOnlyConnection) {
- final long newValue = mConfiguration.foreignKeyConstraintsEnabled ? 1 : 0;
- long value = executeForLong("PRAGMA foreign_keys", null, null);
- if (value != newValue) {
- execute("PRAGMA foreign_keys=" + newValue, null, null);
- }
- }
- }
-
- private void setWalModeFromConfiguration() {
- if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
- if ((mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) {
- setJournalMode("WAL");
- setSyncMode(SQLiteGlobal.getWALSyncMode());
- } else {
- setJournalMode(SQLiteGlobal.getDefaultJournalMode());
- setSyncMode(SQLiteGlobal.getDefaultSyncMode());
- }
- }
- }
-
- private void setSyncMode(String newValue) {
- String value = executeForString("PRAGMA synchronous", null, null);
- if (!canonicalizeSyncMode(value).equalsIgnoreCase(
- canonicalizeSyncMode(newValue))) {
- execute("PRAGMA synchronous=" + newValue, null, null);
- }
- }
-
- private static String canonicalizeSyncMode(String value) {
- switch (value) {
- case "0":
- return "OFF";
- case "1":
- return "NORMAL";
- case "2":
- return "FULL";
- }
- return value;
- }
-
- private void setJournalMode(String newValue) {
- String value = executeForString("PRAGMA journal_mode", null, null);
- if (!value.equalsIgnoreCase(newValue)) {
- try {
- String result = executeForString("PRAGMA journal_mode=" + newValue, null, null);
- if (result.equalsIgnoreCase(newValue)) {
- return;
- }
- // PRAGMA journal_mode silently fails and returns the original journal
- // mode in some cases if the journal mode could not be changed.
- } catch (SQLiteException ex) {
- // This error (SQLITE_BUSY) occurs if one connection has the database
- // open in WAL mode and another tries to change it to non-WAL.
- if (!(ex instanceof SQLiteDatabaseLockedException)) {
- throw ex;
- }
- }
-
- // Because we always disable WAL mode when a database is first opened
- // (even if we intend to re-enable it), we can encounter problems if
- // there is another open connection to the database somewhere.
- // This can happen for a variety of reasons such as an application opening
- // the same database in multiple processes at the same time or if there is a
- // crashing content provider service that the ActivityManager has
- // removed from its registry but whose process hasn't quite died yet
- // by the time it is restarted in a new process.
- //
- // If we don't change the journal mode, nothing really bad happens.
- // In the worst case, an application that enables WAL might not actually
- // get it, although it can still use connection pooling.
- Log.w(TAG, "Could not change the database journal mode of '"
- + mConfiguration.label + "' from '" + value + "' to '" + newValue
- + "' because the database is locked. This usually means that "
- + "there are other open connections to the database which prevents "
- + "the database from enabling or disabling write-ahead logging mode. "
- + "Proceeding without changing the journal mode.");
- }
- }
-
- private void setLocaleFromConfiguration() {
- // Register the localized collators.
- final String newLocale = mConfiguration.locale.toString();
- nativeRegisterLocalizedCollators(mConnectionPtr, newLocale);
-
- // If the database is read-only, we cannot modify the android metadata table
- // or existing indexes.
- if (mIsReadOnlyConnection) {
- return;
- }
-
- try {
- // Ensure the android metadata table exists.
- execute("CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)", null, null);
-
- // Check whether the locale was actually changed.
- final String oldLocale = executeForString("SELECT locale FROM android_metadata "
- + "UNION SELECT NULL ORDER BY locale DESC LIMIT 1", null, null);
- if (oldLocale != null && oldLocale.equals(newLocale)) {
- return;
- }
-
- // Go ahead and update the indexes using the new locale.
- execute("BEGIN", null, null);
- boolean success = false;
- try {
- execute("DELETE FROM android_metadata", null, null);
- execute("INSERT INTO android_metadata (locale) VALUES(?)",
- new Object[] { newLocale }, null);
- execute("REINDEX LOCALIZED", null, null);
- success = true;
- } finally {
- execute(success ? "COMMIT" : "ROLLBACK", null, null);
- }
- } catch (RuntimeException ex) {
- throw new SQLiteException("Failed to change locale for db '" + mConfiguration.label
- + "' to '" + newLocale + "'.");
- }
- }
-
- public void enableLocalizedCollators() {
- if (nativeHasCodec()) {
- setLocaleFromConfiguration();
- }
- }
-
- // Called by SQLiteConnectionPool only.
- void reconfigure(SQLiteDatabaseConfiguration configuration) {
- mOnlyAllowReadOnlyOperations = false;
-
- // Register (deprecated) custom functions.
- final int customFunctionCount = configuration.customFunctions.size();
- for (int i = 0; i < customFunctionCount; i++) {
- SQLiteCustomFunction function = configuration.customFunctions.get(i);
- if (!mConfiguration.customFunctions.contains(function)) {
- nativeRegisterCustomFunction(mConnectionPtr, function);
- }
- }
-
- // Register Functions
- final int functionCount = configuration.functions.size();
- for (int i = 0; i < functionCount; i++) {
- SQLiteFunction function = configuration.functions.get(i);
- if (!mConfiguration.functions.contains(function)) {
- nativeRegisterFunction(mConnectionPtr, function);
- }
- }
-
- // Remember what changed.
- boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
- != mConfiguration.foreignKeyConstraintsEnabled;
- boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
- & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
- boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
-
- // Update configuration parameters.
- mConfiguration.updateParametersFrom(configuration);
-
- // Update prepared statement cache size.
- /* mPreparedStatementCache.resize(configuration.maxSqlCacheSize); */
-
- // Update foreign key mode.
- if (foreignKeyModeChanged) {
- setForeignKeyModeFromConfiguration();
- }
-
- // Update WAL.
- if (walModeChanged) {
- setWalModeFromConfiguration();
- }
-
- // Update locale.
- if (localeChanged) {
- setLocaleFromConfiguration();
- }
- }
-
- // Called by SQLiteConnectionPool only.
- // When set to true, executing write operations will throw SQLiteException.
- // Preparing statements that might write is ok, just don't execute them.
- void setOnlyAllowReadOnlyOperations(boolean readOnly) {
- mOnlyAllowReadOnlyOperations = readOnly;
- }
-
- // Called by SQLiteConnectionPool only.
- // Returns true if the prepared statement cache contains the specified SQL.
- boolean isPreparedStatementInCache(String sql) {
- return mPreparedStatementCache.get(sql) != null;
- }
-
- /**
- * Returns true if this is the primary database connection.
- * @return True if this is the primary database connection.
- */
- public boolean isPrimaryConnection() {
- return mIsPrimaryConnection;
- }
-
- /**
- * Prepares a statement for execution but does not bind its parameters or execute it.
- *
- * 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.
- *
- * 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.
- *
- * To take advantage of this behavior as an optimization, the connection pool
- * provides a method to acquire a connection that already has a given SQL statement
- * in its prepared statement cache so that it is ready for execution.
- *
- *
- * @param sql The SQL statement to prepare.
- * @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.
- */
- public void prepare(String sql, SQLiteStatementInfo outStatementInfo) {
- if (sql == null) {
- throw new IllegalArgumentException("sql must not be null.");
- }
-
- final int cookie = mRecentOperations.beginOperation("prepare", sql, null);
- try {
- final PreparedStatement statement = acquirePreparedStatement(sql);
- try {
- if (outStatementInfo != null) {
- outStatementInfo.numParameters = statement.mNumParameters;
- outStatementInfo.readOnly = statement.mReadOnly;
-
- final int columnCount = nativeGetColumnCount(
- mConnectionPtr, statement.mStatementPtr);
- if (columnCount == 0) {
- outStatementInfo.columnNames = EMPTY_STRING_ARRAY;
- } else {
- outStatementInfo.columnNames = new String[columnCount];
- for (int i = 0; i < columnCount; i++) {
- outStatementInfo.columnNames[i] = nativeGetColumnName(
- mConnectionPtr, statement.mStatementPtr, i);
- }
- }
- }
- } finally {
- releasePreparedStatement(statement);
- }
- } catch (RuntimeException ex) {
- mRecentOperations.failOperation(cookie, ex);
- throw ex;
- } finally {
- mRecentOperations.endOperation(cookie);
- }
- }
-
- /**
- * 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 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,
- CancellationSignal cancellationSignal) {
- if (sql == null) {
- throw new IllegalArgumentException("sql must not be null.");
- }
-
- final int cookie = mRecentOperations.beginOperation("execute", sql, bindArgs);
- try {
- final PreparedStatement statement = acquirePreparedStatement(sql);
- try {
- throwIfStatementForbidden(statement);
- bindArguments(statement, bindArgs);
- applyBlockGuardPolicy(statement);
- attachCancellationSignal(cancellationSignal);
- try {
- nativeExecute(mConnectionPtr, statement.mStatementPtr);
- } finally {
- detachCancellationSignal(cancellationSignal);
- }
- } finally {
- releasePreparedStatement(statement);
- }
- } catch (RuntimeException ex) {
- mRecentOperations.failOperation(cookie, ex);
- throw ex;
- } finally {
- mRecentOperations.endOperation(cookie);
- }
- }
-
- /**
- * Executes a statement that returns a single long result.
- *
- * @param sql The SQL statement to execute.
- * @param bindArgs The arguments to bind, or null if none.
- * @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 long, 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,
- CancellationSignal cancellationSignal) {
- if (sql == null) {
- throw new IllegalArgumentException("sql must not be null.");
- }
-
- final int cookie = mRecentOperations.beginOperation("executeForLong", sql, bindArgs);
- try {
- final PreparedStatement statement = acquirePreparedStatement(sql);
- try {
- throwIfStatementForbidden(statement);
- bindArguments(statement, bindArgs);
- applyBlockGuardPolicy(statement);
- attachCancellationSignal(cancellationSignal);
- try {
- return nativeExecuteForLong(mConnectionPtr, statement.mStatementPtr);
- } finally {
- detachCancellationSignal(cancellationSignal);
- }
- } finally {
- releasePreparedStatement(statement);
- }
- } catch (RuntimeException ex) {
- mRecentOperations.failOperation(cookie, ex);
- throw ex;
- } finally {
- mRecentOperations.endOperation(cookie);
- }
- }
-
- /**
- * 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 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 String, 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,
- CancellationSignal cancellationSignal) {
- if (sql == null) {
- throw new IllegalArgumentException("sql must not be null.");
- }
-
- final int cookie = mRecentOperations.beginOperation("executeForString", sql, bindArgs);
- try {
- final PreparedStatement statement = acquirePreparedStatement(sql);
- try {
- throwIfStatementForbidden(statement);
- bindArguments(statement, bindArgs);
- applyBlockGuardPolicy(statement);
- attachCancellationSignal(cancellationSignal);
- try {
- return nativeExecuteForString(mConnectionPtr, statement.mStatementPtr);
- } finally {
- detachCancellationSignal(cancellationSignal);
- }
- } finally {
- releasePreparedStatement(statement);
- }
- } catch (RuntimeException ex) {
- mRecentOperations.failOperation(cookie, ex);
- throw ex;
- } finally {
- mRecentOperations.endOperation(cookie);
- }
- }
-
- /**
- * 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 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,
- CancellationSignal cancellationSignal) {
- if (sql == null) {
- throw new IllegalArgumentException("sql must not be null.");
- }
-
- final int cookie = mRecentOperations.beginOperation("executeForBlobFileDescriptor",
- sql, bindArgs);
- try {
- final PreparedStatement statement = acquirePreparedStatement(sql);
- try {
- throwIfStatementForbidden(statement);
- bindArguments(statement, bindArgs);
- applyBlockGuardPolicy(statement);
- attachCancellationSignal(cancellationSignal);
- try {
- int fd = nativeExecuteForBlobFileDescriptor(
- mConnectionPtr, statement.mStatementPtr);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
- return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null;
- } else {
- throw new UnsupportedOperationException();
- }
- } finally {
- detachCancellationSignal(cancellationSignal);
- }
- } finally {
- releasePreparedStatement(statement);
- }
- } catch (RuntimeException ex) {
- mRecentOperations.failOperation(cookie, ex);
- throw ex;
- } finally {
- mRecentOperations.endOperation(cookie);
- }
- }
-
- /**
- * 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 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,
- CancellationSignal cancellationSignal) {
- if (sql == null) {
- throw new IllegalArgumentException("sql must not be null.");
- }
-
- int changedRows = 0;
- final int cookie = mRecentOperations.beginOperation("executeForChangedRowCount",
- sql, bindArgs);
- try {
- final PreparedStatement statement = acquirePreparedStatement(sql);
- try {
- throwIfStatementForbidden(statement);
- bindArguments(statement, bindArgs);
- applyBlockGuardPolicy(statement);
- attachCancellationSignal(cancellationSignal);
- try {
- changedRows = nativeExecuteForChangedRowCount(
- mConnectionPtr, statement.mStatementPtr);
- return changedRows;
- } finally {
- detachCancellationSignal(cancellationSignal);
- }
- } finally {
- releasePreparedStatement(statement);
- }
- } catch (RuntimeException ex) {
- mRecentOperations.failOperation(cookie, ex);
- throw ex;
- } finally {
- if (mRecentOperations.endOperationDeferLog(cookie)) {
- mRecentOperations.logOperation(cookie, "changedRows=" + changedRows);
- }
- }
- }
-
- /**
- * 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 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,
- CancellationSignal cancellationSignal) {
- if (sql == null) {
- throw new IllegalArgumentException("sql must not be null.");
- }
-
- final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId",
- sql, bindArgs);
- try {
- final PreparedStatement statement = acquirePreparedStatement(sql);
- try {
- throwIfStatementForbidden(statement);
- bindArguments(statement, bindArgs);
- applyBlockGuardPolicy(statement);
- attachCancellationSignal(cancellationSignal);
- try {
- return nativeExecuteForLastInsertedRowId(
- mConnectionPtr, statement.mStatementPtr);
- } finally {
- detachCancellationSignal(cancellationSignal);
- }
- } finally {
- releasePreparedStatement(statement);
- }
- } catch (RuntimeException ex) {
- mRecentOperations.failOperation(cookie, ex);
- throw ex;
- } finally {
- mRecentOperations.endOperation(cookie);
- }
- }
-
- /**
- * 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 startPos.
- * @param countAllRows True to count all rows that the query would return
- * regagless of whether they fit in the window.
- * @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 countAllRows 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,
- CancellationSignal cancellationSignal) {
- if (sql == null) {
- throw new IllegalArgumentException("sql must not be null.");
- }
- if (window == null) {
- throw new IllegalArgumentException("window must not be null.");
- }
-
- window.acquireReference();
- try {
- int actualPos = -1;
- int countedRows = -1;
- int filledRows = -1;
- final int cookie = mRecentOperations.beginOperation("executeForCursorWindow",
- sql, bindArgs);
- try {
- final PreparedStatement statement = acquirePreparedStatement(sql);
- try {
- throwIfStatementForbidden(statement);
- bindArguments(statement, bindArgs);
- applyBlockGuardPolicy(statement);
- attachCancellationSignal(cancellationSignal);
- try {
- final long result = nativeExecuteForCursorWindow(
- mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
- startPos, requiredPos, countAllRows);
- actualPos = (int)(result >> 32);
- countedRows = (int)result;
- filledRows = window.getNumRows();
- window.setStartPosition(actualPos);
- return countedRows;
- } finally {
- detachCancellationSignal(cancellationSignal);
- }
- } finally {
- releasePreparedStatement(statement);
- }
- } catch (RuntimeException ex) {
- mRecentOperations.failOperation(cookie, ex);
- throw ex;
- } finally {
- if (mRecentOperations.endOperationDeferLog(cookie)) {
- mRecentOperations.logOperation(cookie, "window='" + window
- + "', startPos=" + startPos
- + ", actualPos=" + actualPos
- + ", filledRows=" + filledRows
- + ", countedRows=" + countedRows);
- }
- }
- } finally {
- window.releaseReference();
- }
- }
-
- private PreparedStatement acquirePreparedStatement(String sql) {
- PreparedStatement statement = mPreparedStatementCache.get(sql);
- boolean skipCache = false;
- if (statement != null) {
- if (!statement.mInUse) {
- return statement;
- }
- // The statement is already in the cache but is in use (this statement appears
- // to be not only re-entrant but recursive!). So prepare a new copy of the
- // statement but do not cache it.
- skipCache = true;
- }
-
- final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
- try {
- final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
- final int type = SQLiteStatementType.getSqlStatementType(sql);
- final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
- statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly);
- if (!skipCache && isCacheable(type)) {
- mPreparedStatementCache.put(sql, statement);
- statement.mInCache = true;
- }
- } catch (RuntimeException ex) {
- // Finalize the statement if an exception occurred and we did not add
- // it to the cache. If it is already in the cache, then leave it there.
- if (statement == null || !statement.mInCache) {
- nativeFinalizeStatement(mConnectionPtr, statementPtr);
- }
- throw ex;
- }
- statement.mInUse = true;
- return statement;
- }
-
- private void releasePreparedStatement(PreparedStatement statement) {
- statement.mInUse = false;
- if (statement.mInCache) {
- try {
- nativeResetStatementAndClearBindings(mConnectionPtr, statement.mStatementPtr);
- } catch (SQLiteException ex) {
- // The statement could not be reset due to an error. Remove it from the cache.
- // When remove() is called, the cache will invoke its entryRemoved() callback,
- // which will in turn call finalizePreparedStatement() to finalize and
- // recycle the statement.
- if (DEBUG) {
- Log.d(TAG, "Could not reset prepared statement due to an exception. "
- + "Removing it from the cache. SQL: "
- + trimSqlForDisplay(statement.mSql), ex);
- }
-
- mPreparedStatementCache.remove(statement.mSql);
- }
- } else {
- finalizePreparedStatement(statement);
- }
- }
-
- private void finalizePreparedStatement(PreparedStatement statement) {
- nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr);
- recyclePreparedStatement(statement);
- }
-
- private void attachCancellationSignal(CancellationSignal cancellationSignal) {
- if (cancellationSignal != null) {
- cancellationSignal.throwIfCanceled();
-
- mCancellationSignalAttachCount += 1;
- if (mCancellationSignalAttachCount == 1) {
- // Reset cancellation flag before executing the statement.
- nativeResetCancel(mConnectionPtr, true /*cancelable*/);
-
- // After this point, onCancel() may be called concurrently.
- cancellationSignal.setOnCancelListener(this);
- }
- }
- }
-
- @SuppressLint("Assert")
- private void detachCancellationSignal(CancellationSignal cancellationSignal) {
- if (cancellationSignal != null) {
- assert mCancellationSignalAttachCount > 0;
-
- mCancellationSignalAttachCount -= 1;
- if (mCancellationSignalAttachCount == 0) {
- // After this point, onCancel() cannot be called concurrently.
- cancellationSignal.setOnCancelListener(null);
-
- // Reset cancellation flag after executing the statement.
- nativeResetCancel(mConnectionPtr, false /*cancelable*/);
- }
- }
- }
-
- // CancellationSignal.OnCancelListener callback.
- // This method may be called on a different thread than the executing statement.
- // However, it will only be called between calls to attachCancellationSignal and
- // detachCancellationSignal, while a statement is executing. We can safely assume
- // that the SQLite connection is still alive.
- @Override
- public void onCancel() {
- nativeCancel(mConnectionPtr);
- }
-
- private void bindArguments(PreparedStatement statement, Object[] bindArgs) {
- final int count = bindArgs != null ? bindArgs.length : 0;
- if (count != statement.mNumParameters) {
- String message = "Expected " + statement.mNumParameters + " bind arguments but "
- + count + " were provided.";
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- throw new SQLiteBindOrColumnIndexOutOfRangeException(message);
- } else {
- throw new SQLiteException(message);
- }
- }
- if (count == 0) {
- return;
- }
-
- final long statementPtr = statement.mStatementPtr;
- for (int i = 0; i < count; i++) {
- final Object arg = bindArgs[i];
- switch (getTypeOfObject(arg)) {
- case Cursor.FIELD_TYPE_NULL:
- nativeBindNull(mConnectionPtr, statementPtr, i + 1);
- break;
- case Cursor.FIELD_TYPE_INTEGER:
- nativeBindLong(mConnectionPtr, statementPtr, i + 1,
- ((Number)arg).longValue());
- break;
- case Cursor.FIELD_TYPE_FLOAT:
- nativeBindDouble(mConnectionPtr, statementPtr, i + 1,
- ((Number)arg).doubleValue());
- break;
- case Cursor.FIELD_TYPE_BLOB:
- nativeBindBlob(mConnectionPtr, statementPtr, i + 1, (byte[])arg);
- break;
- case Cursor.FIELD_TYPE_STRING:
- default:
- if (arg instanceof Boolean) {
- // Provide compatibility with legacy applications which may pass
- // Boolean values in bind args.
- nativeBindLong(mConnectionPtr, statementPtr, i + 1, (Boolean) arg ? 1 : 0);
- } else {
- nativeBindString(mConnectionPtr, statementPtr, i + 1, arg.toString());
- }
- break;
- }
- }
- }
-
- /**
- * Returns data type of the given object's value.
- *
- * Returned values are
- *
- *
{@link Cursor#FIELD_TYPE_NULL}
- *
{@link Cursor#FIELD_TYPE_INTEGER}
- *
{@link Cursor#FIELD_TYPE_FLOAT}
- *
{@link Cursor#FIELD_TYPE_STRING}
- *
{@link Cursor#FIELD_TYPE_BLOB}
- *
- *
- *
- * @param obj the object whose value type is to be returned
- * @return object value type
- */
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- private static int getTypeOfObject(Object obj) {
- if (obj == null) {
- return Cursor.FIELD_TYPE_NULL;
- } else if (obj instanceof byte[]) {
- return Cursor.FIELD_TYPE_BLOB;
- } else if (obj instanceof Float || obj instanceof Double) {
- return Cursor.FIELD_TYPE_FLOAT;
- } else if (obj instanceof Long || obj instanceof Integer
- || obj instanceof Short || obj instanceof Byte) {
- return Cursor.FIELD_TYPE_INTEGER;
- } else {
- return Cursor.FIELD_TYPE_STRING;
- }
- }
-
- private void throwIfStatementForbidden(PreparedStatement statement) {
- if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) {
- throw new SQLiteException("Cannot execute this statement because it "
- + "might modify the database but the connection is read-only.");
- }
- }
-
- private static boolean isCacheable(int statementType) {
- return statementType == SQLiteStatementType.STATEMENT_UPDATE
- || statementType == SQLiteStatementType.STATEMENT_SELECT;
- }
-
- private void applyBlockGuardPolicy(PreparedStatement statement) {
- if (!mConfiguration.isInMemoryDb() && SQLiteDebug.DEBUG_SQL_LOG) {
- // don't have access to the policy, so just log
- if (Looper.myLooper() == Looper.getMainLooper()) {
- if (statement.mReadOnly) {
- Log.w(TAG, "Reading from disk on main thread");
- } else {
- Log.w(TAG, "Writing to disk on main thread");
- }
- }
- }
- }
-
- /**
- * Dumps debugging information about this connection.
- *
- * @param printer The printer to receive the dump, not null.
- * @param verbose True to dump more verbose information.
- */
- public void dump(Printer printer, boolean verbose) {
- dumpUnsafe(printer, verbose);
- }
-
- /**
- * Dumps debugging information about this connection, in the case where the
- * caller might not actually own the connection.
- *
- * This function is written so that it may be called by a thread that does not
- * own the connection. We need to be very careful because the connection state is
- * not synchronized.
- *
- * At worst, the method may return stale or slightly wrong data, however
- * it should not crash. This is ok as it is only used for diagnostic purposes.
- *
- * @param printer The printer to receive the dump, not null.
- * @param verbose True to dump more verbose information.
- */
- void dumpUnsafe(Printer printer, boolean verbose) {
- printer.println("Connection #" + mConnectionId + ":");
- if (verbose) {
- printer.println(" connectionPtr: 0x" + Long.toHexString(mConnectionPtr));
- }
- printer.println(" isPrimaryConnection: " + mIsPrimaryConnection);
- printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
-
- mRecentOperations.dump(printer, verbose);
-
- if (verbose) {
- mPreparedStatementCache.dump(printer);
- }
- }
-
- /**
- * Describes the currently executing operation, in the case where the
- * caller might not actually own the connection.
- *
- * This function is written so that it may be called by a thread that does not
- * own the connection. We need to be very careful because the connection state is
- * not synchronized.
- *
- * At worst, the method may return stale or slightly wrong data, however
- * it should not crash. This is ok as it is only used for diagnostic purposes.
- *
- * @return A description of the current operation including how long it has been running,
- * or null if none.
- */
- String describeCurrentOperationUnsafe() {
- return mRecentOperations.describeCurrentOperation();
- }
-
- /**
- * Collects statistics about database connection memory usage.
- *
- * @param dbStatsList The list to populate.
- */
- void collectDbStats(ArrayList dbStatsList) {
- // Get information about the main database.
- int lookaside = nativeGetDbLookaside(mConnectionPtr);
- long pageCount = 0;
- long pageSize = 0;
- try {
- pageCount = executeForLong("PRAGMA page_count;", null, null);
- pageSize = executeForLong("PRAGMA page_size;", null, null);
- } catch (SQLiteException ex) {
- // Ignore.
- }
- dbStatsList.add(getMainDbStatsUnsafe(lookaside, pageCount, pageSize));
-
- // Get information about attached databases.
- // We ignore the first row in the database list because it corresponds to
- // the main database which we have already described.
- CursorWindow window = new CursorWindow("collectDbStats");
- try {
- executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false, null);
- for (int i = 1; i < window.getNumRows(); i++) {
- String name = window.getString(i, 1);
- String path = window.getString(i, 2);
- pageCount = 0;
- pageSize = 0;
- try {
- pageCount = executeForLong("PRAGMA " + name + ".page_count;", null, null);
- pageSize = executeForLong("PRAGMA " + name + ".page_size;", null, null);
- } catch (SQLiteException ex) {
- // Ignore.
- }
- String label = " (attached) " + name;
- if (!path.isEmpty()) {
- label += ": " + path;
- }
- dbStatsList.add(new SQLiteDebug.DbStats(label, pageCount, pageSize, 0, 0, 0, 0));
- }
- } catch (SQLiteException ex) {
- // Ignore.
- } finally {
- window.close();
- }
- }
-
- /**
- * Collects statistics about database connection memory usage, in the case where the
- * caller might not actually own the connection.
- */
- void collectDbStatsUnsafe(ArrayList dbStatsList) {
- dbStatsList.add(getMainDbStatsUnsafe(0, 0, 0));
- }
-
- private SQLiteDebug.DbStats getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize) {
- // The prepared statement cache is thread-safe so we can access its statistics
- // even if we do not own the database connection.
- String label = mConfiguration.path;
- if (!mIsPrimaryConnection) {
- label += " (" + mConnectionId + ")";
- }
- return new SQLiteDebug.DbStats(label, pageCount, pageSize, lookaside,
- mPreparedStatementCache.hitCount(),
- mPreparedStatementCache.missCount(),
- mPreparedStatementCache.size());
- }
-
- @Override
- public String toString() {
- return "SQLiteConnection: " + mConfiguration.path + " (" + mConnectionId + ")";
- }
-
- private PreparedStatement obtainPreparedStatement(String sql, long statementPtr,
- int numParameters, int type, boolean readOnly) {
- PreparedStatement statement = mPreparedStatementPool;
- if (statement != null) {
- mPreparedStatementPool = statement.mPoolNext;
- statement.mPoolNext = null;
- statement.mInCache = false;
- } else {
- statement = new PreparedStatement();
- }
- statement.mSql = sql;
- statement.mStatementPtr = statementPtr;
- statement.mNumParameters = numParameters;
- statement.mType = type;
- statement.mReadOnly = readOnly;
- return statement;
- }
-
- private void recyclePreparedStatement(PreparedStatement statement) {
- statement.mSql = null;
- statement.mPoolNext = mPreparedStatementPool;
- mPreparedStatementPool = statement;
- }
-
- private static String trimSqlForDisplay(String sql) {
- return TRIM_SQL_PATTERN.matcher(sql).replaceAll(" ");
- }
-
- /**
- * Holder type for a prepared statement.
- *
- * Although this object holds a pointer to a native statement object, it
- * does not have a finalizer. This is deliberate. The {@link SQLiteConnection}
- * owns the statement object and will take care of freeing it when needed.
- * In particular, closing the connection requires a guarantee of deterministic
- * resource disposal because all native statement objects must be freed before
- * the native database object can be closed. So no finalizers here.
- */
- private static final class PreparedStatement {
- // Next item in pool.
- public PreparedStatement mPoolNext;
-
- // The SQL from which the statement was prepared.
- public String mSql;
-
- // The native sqlite3_stmt object pointer.
- // Lifetime is managed explicitly by the connection.
- public long mStatementPtr;
-
- // The number of parameters that the prepared statement has.
- public int mNumParameters;
-
- // The statement type.
- public int mType;
-
- // True if the statement is read-only.
- public boolean mReadOnly;
-
- // True if the statement is in the cache.
- public boolean mInCache;
-
- // True if the statement is in use (currently executing).
- // We need this flag because due to the use of custom functions in triggers, it's
- // possible for SQLite calls to be re-entrant. Consequently we need to prevent
- // in use statements from being finalized until they are no longer in use.
- public boolean mInUse;
- }
-
- private final class PreparedStatementCache
- extends LruCache {
- public PreparedStatementCache(int size) {
- super(size);
- }
-
- @Override
- protected void entryRemoved(boolean evicted, String key,
- PreparedStatement oldValue, PreparedStatement newValue) {
- oldValue.mInCache = false;
- if (!oldValue.mInUse) {
- finalizePreparedStatement(oldValue);
- }
- }
-
- public void dump(Printer printer) {
- printer.println(" Prepared statement cache:");
- Map cache = snapshot();
- if (!cache.isEmpty()) {
- int i = 0;
- for (Map.Entry entry : cache.entrySet()) {
- PreparedStatement statement = entry.getValue();
- if (statement.mInCache) { // might be false due to a race with entryRemoved
- String sql = entry.getKey();
- printer.println(" " + i + ": statementPtr=0x"
- + Long.toHexString(statement.mStatementPtr)
- + ", numParameters=" + statement.mNumParameters
- + ", type=" + statement.mType
- + ", readOnly=" + statement.mReadOnly
- + ", sql=\"" + trimSqlForDisplay(sql) + "\"");
- }
- i += 1;
- }
- } else {
- printer.println(" ");
- }
- }
- }
-
- private static final class OperationLog {
- private static final int MAX_RECENT_OPERATIONS = 20;
- private static final int COOKIE_GENERATION_SHIFT = 8;
- private static final int COOKIE_INDEX_MASK = 0xff;
-
- private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
- private int mIndex;
- private int mGeneration;
-
- public int beginOperation(String kind, String sql, Object[] bindArgs) {
- synchronized (mOperations) {
- final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
- Operation operation = mOperations[index];
- if (operation == null) {
- operation = new Operation();
- mOperations[index] = operation;
- } else {
- operation.mFinished = false;
- operation.mException = null;
- if (operation.mBindArgs != null) {
- operation.mBindArgs.clear();
- }
- }
- operation.mStartTime = System.currentTimeMillis();
- operation.mKind = kind;
- operation.mSql = sql;
- if (bindArgs != null) {
- if (operation.mBindArgs == null) {
- operation.mBindArgs = new ArrayList<>();
- } else {
- operation.mBindArgs.clear();
- }
- for (final Object arg : bindArgs) {
- if (arg != null && arg instanceof byte[]) {
- // Don't hold onto the real byte array longer than necessary.
- operation.mBindArgs.add(EMPTY_BYTE_ARRAY);
- } else {
- operation.mBindArgs.add(arg);
- }
- }
- }
- operation.mCookie = newOperationCookieLocked(index);
- mIndex = index;
- return operation.mCookie;
- }
- }
-
- public void failOperation(int cookie, Exception ex) {
- synchronized (mOperations) {
- final Operation operation = getOperationLocked(cookie);
- if (operation != null) {
- operation.mException = ex;
- }
- }
- }
-
- public void endOperation(int cookie) {
- synchronized (mOperations) {
- if (endOperationDeferLogLocked(cookie)) {
- logOperationLocked(cookie, null);
- }
- }
- }
-
- public boolean endOperationDeferLog(int cookie) {
- synchronized (mOperations) {
- return endOperationDeferLogLocked(cookie);
- }
- }
-
- public void logOperation(int cookie, String detail) {
- synchronized (mOperations) {
- logOperationLocked(cookie, detail);
- }
- }
-
- private boolean endOperationDeferLogLocked(int cookie) {
- final Operation operation = getOperationLocked(cookie);
- if (operation != null) {
- operation.mEndTime = System.currentTimeMillis();
- operation.mFinished = true;
- return SQLiteDebug.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
- operation.mEndTime - operation.mStartTime);
- }
- return false;
- }
-
- private void logOperationLocked(int cookie, String detail) {
- final Operation operation = getOperationLocked(cookie);
- if (operation == null) {
- return;
- }
- StringBuilder msg = new StringBuilder();
- operation.describe(msg, false);
- if (detail != null) {
- msg.append(", ").append(detail);
- }
- Log.d(TAG, msg.toString());
- }
-
- private int newOperationCookieLocked(int index) {
- final int generation = mGeneration++;
- return generation << COOKIE_GENERATION_SHIFT | index;
- }
-
- private Operation getOperationLocked(int cookie) {
- final int index = cookie & COOKIE_INDEX_MASK;
- final Operation operation = mOperations[index];
- return operation.mCookie == cookie ? operation : null;
- }
-
- public String describeCurrentOperation() {
- synchronized (mOperations) {
- final Operation operation = mOperations[mIndex];
- if (operation != null && !operation.mFinished) {
- StringBuilder msg = new StringBuilder();
- operation.describe(msg, false);
- return msg.toString();
- }
- return null;
- }
- }
-
- public void dump(Printer printer, boolean verbose) {
- synchronized (mOperations) {
- printer.println(" Most recently executed operations:");
- int index = mIndex;
- Operation operation = mOperations[index];
- if (operation != null) {
- int n = 0;
- do {
- StringBuilder msg = new StringBuilder();
- msg.append(" ").append(n).append(": [");
- msg.append(operation.getFormattedStartTime());
- msg.append("] ");
- operation.describe(msg, verbose);
- printer.println(msg.toString());
-
- if (index > 0) {
- index -= 1;
- } else {
- index = MAX_RECENT_OPERATIONS - 1;
- }
- n += 1;
- operation = mOperations[index];
- } while (operation != null && n < MAX_RECENT_OPERATIONS);
- } else {
- printer.println(" ");
- }
- }
- }
- }
-
- private static final class Operation {
- @SuppressLint("SimpleDateFormat")
- private static final SimpleDateFormat sDateFormat =
- new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
-
- public long mStartTime;
- public long mEndTime;
- public String mKind;
- public String mSql;
- public ArrayList