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: - *

    - *
  • {@link SQLiteDatabase#openOrCreateDatabase(String, - * SQLiteDatabase.CursorFactory, DatabaseErrorHandler)}
  • - *
  • {@link SQLiteDatabase#openDatabase(String, - * SQLiteDatabase.CursorFactory, int, DatabaseErrorHandler)}
  • - *
- * 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). - *

- * A simple example:

   {@code
- *   class Foo {
- *
- *       private final CloseGuard guard = CloseGuard.get();
- *
- *       ...
- *
- *       public Foo() {
- *           ...;
- *           guard.open("cleanup");
- *       }
- *
- *       public void cleanup() {
- *          guard.close();
- *          ...;
- *       }
- *
- *       protected void finalize() throws Throwable {
- *           try {
- *               if (guard != null) {
- *                   guard.warnIfOpen();
- *               }
- *               cleanup();
- *           } finally {
- *               super.finalize();
- *           }
- *       }
- *   }
- * }
- * - * In usage where the resource to be explicitly cleaned up are - * allocated after object construction, CloseGuard protection can - * be deferred. For example:
   {@code
- *   class Bar {
- *
- *       private final CloseGuard guard = CloseGuard.get();
- *
- *       ...
- *
- *       public Bar() {
- *           ...;
- *       }
- *
- *       public void connect() {
- *          ...;
- *          guard.open("cleanup");
- *       }
- *
- *       public void cleanup() {
- *          guard.close();
- *          ...;
- *       }
- *
- *       protected void finalize() throws Throwable {
- *           try {
- *               if (guard != null) {
- *                   guard.warnIfOpen();
- *               }
- *               cleanup();
- *           } finally {
- *               super.finalize();
- *           }
- *       }
- *   }
- * }
- * - * 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 mBindArgs; - public boolean mFinished; - public Exception mException; - public int mCookie; - - public void describe(StringBuilder msg, boolean verbose) { - msg.append(mKind); - if (mFinished) { - msg.append(" took ").append(mEndTime - mStartTime).append("ms"); - } else { - msg.append(" started ").append(System.currentTimeMillis() - mStartTime) - .append("ms ago"); - } - msg.append(" - ").append(getStatus()); - if (mSql != null) { - msg.append(", sql=\"").append(trimSqlForDisplay(mSql)).append("\""); - } - if (verbose && mBindArgs != null && mBindArgs.size() != 0) { - msg.append(", bindArgs=["); - final int count = mBindArgs.size(); - for (int i = 0; i < count; i++) { - final Object arg = mBindArgs.get(i); - if (i != 0) { - msg.append(", "); - } - if (arg == null) { - msg.append("null"); - } else if (arg instanceof byte[]) { - msg.append(""); - } else if (arg instanceof String) { - msg.append("\"").append((String)arg).append("\""); - } else { - msg.append(arg); - } - } - msg.append("]"); - } - if (mException != null) { - msg.append(", exception=\"").append(mException.getMessage()).append("\""); - } - } - - private String getStatus() { - if (!mFinished) { - return "running"; - } - return mException != null ? "failed" : "succeeded"; - } - - private String getFormattedStartTime() { - return sDateFormat.format(new Date(mStartTime)); - } - } -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteConnectionPool.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteConnectionPool.java deleted file mode 100644 index fdfe5b455f..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteConnectionPool.java +++ /dev/null @@ -1,1084 +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.database.sqlite.SQLiteException; -import android.os.SystemClock; -import android.util.Log; -import android.util.Printer; -import androidx.core.os.CancellationSignal; -import androidx.core.os.OperationCanceledException; - -import java.io.Closeable; -import java.util.ArrayList; -import java.util.Map; -import java.util.WeakHashMap; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.LockSupport; - -/** - * Maintains a pool of active SQLite database connections. - *

- * At any given time, a connection is either owned by the pool, or it has been - * acquired by a {@link SQLiteSession}. When the {@link SQLiteSession} is - * finished with the connection it is using, it must return the connection - * back to the pool. - *

- * The pool holds strong references to the connections it owns. However, - * it only holds weak references to the connections that sessions - * have acquired from it. Using weak references in the latter case ensures - * that the connection pool can detect when connections have been improperly - * abandoned so that it can create new connections to replace them if needed. - *

- * The connection pool is thread-safe (but the connections themselves are not). - *

- * - *

Exception safety

- *

- * This code attempts to maintain the invariant that opened connections are - * always owned. Unfortunately that means it needs to handle exceptions - * all over to ensure that broken connections get cleaned up. Most - * operations invokving SQLite can throw {@link SQLiteException} or other - * runtime exceptions. This is a bit of a pain to deal with because the compiler - * cannot help us catch missing exception handling code. - *

- * The general rule for this file: If we are making calls out to - * {@link SQLiteConnection} then we must be prepared to handle any - * runtime exceptions it might throw at us. Note that out-of-memory - * is an {@link Error}, not a {@link RuntimeException}. We don't trouble ourselves - * handling out of memory because it is hard to do anything at all sensible then - * and most likely the VM is about to crash. - *

- * - * @hide - */ -public final class SQLiteConnectionPool implements Closeable { - private static final String TAG = "SQLiteConnectionPool"; - - // Amount of time to wait in milliseconds before unblocking acquireConnection - // and logging a message about the connection pool being busy. - private static final long CONNECTION_POOL_BUSY_MILLIS = 30 * 1000; // 30 seconds - - private final CloseGuard mCloseGuard = CloseGuard.get(); - - private final Object mLock = new Object(); - private final AtomicBoolean mConnectionLeaked = new AtomicBoolean(); - private final SQLiteDatabaseConfiguration mConfiguration; - private int mMaxConnectionPoolSize; - private boolean mIsOpen; - private int mNextConnectionId; - - private ConnectionWaiter mConnectionWaiterPool; - private ConnectionWaiter mConnectionWaiterQueue; - - // Strong references to all available connections. - private final ArrayList mAvailableNonPrimaryConnections = new ArrayList<>(); - private SQLiteConnection mAvailablePrimaryConnection; - - // Describes what should happen to an acquired connection when it is returned to the pool. - enum AcquiredConnectionStatus { - // The connection should be returned to the pool as usual. - NORMAL, - - // The connection must be reconfigured before being returned. - RECONFIGURE, - - // The connection must be closed and discarded. - DISCARD, - } - - // Weak references to all acquired connections. The associated value - // indicates whether the connection must be reconfigured before being - // returned to the available connection list or discarded. - // For example, the prepared statement cache size may have changed and - // need to be updated in preparation for the next client. - private final WeakHashMap mAcquiredConnections = - new WeakHashMap<>(); - - /** - * Connection flag: Read-only. - *

- * This flag indicates that the connection will only be used to - * perform read-only operations. - *

- */ - public static final int CONNECTION_FLAG_READ_ONLY = 1; - - /** - * Connection flag: Primary connection affinity. - *

- * This flag indicates that the primary connection is required. - * This flag helps support legacy applications that expect most data modifying - * operations to be serialized by locking the primary database connection. - * Setting this flag essentially implements the old "db lock" concept by preventing - * an operation from being performed until it can obtain exclusive access to - * the primary connection. - *

- */ - public static final int CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY = 1 << 1; - - /** - * Connection flag: Connection is being used interactively. - *

- * This flag indicates that the connection is needed by the UI thread. - * The connection pool can use this flag to elevate the priority - * of the database connection request. - *

- */ - public static final int CONNECTION_FLAG_INTERACTIVE = 1 << 2; - - private SQLiteConnectionPool(SQLiteDatabaseConfiguration configuration) { - mConfiguration = new SQLiteDatabaseConfiguration(configuration); - setMaxConnectionPoolSizeLocked(); - } - - @SuppressWarnings("ThrowFromFinallyBlock") - @Override - protected void finalize() throws Throwable { - try { - dispose(true); - } finally { - super.finalize(); - } - } - - /** - * Opens a connection pool for the specified database. - * - * @param configuration The database configuration. - * @return The connection pool. - * - * @throws SQLiteException if a database error occurs. - */ - public static SQLiteConnectionPool open(SQLiteDatabaseConfiguration configuration) { - if (configuration == null) { - throw new IllegalArgumentException("configuration must not be null."); - } - - // Create the pool. - SQLiteConnectionPool pool = new SQLiteConnectionPool(configuration); - pool.open(); // might throw - return pool; - } - - // Might throw - private void open() { - // Open the primary connection. - // This might throw if the database is corrupt. - mAvailablePrimaryConnection = openConnectionLocked(mConfiguration, - true /*primaryConnection*/); // might throw - - // Mark the pool as being open for business. - mIsOpen = true; - mCloseGuard.open("close"); - } - - /** - * Closes the connection pool. - *

- * When the connection pool is closed, it will refuse all further requests - * to acquire connections. All connections that are currently available in - * the pool are closed immediately. Any connections that are still in use - * will be closed as soon as they are returned to the pool. - *

- * - * @throws IllegalStateException if the pool has been closed. - */ - public void close() { - dispose(false); - } - - private void dispose(boolean finalized) { - if (mCloseGuard != null) { - if (finalized) { - mCloseGuard.warnIfOpen(); - } - mCloseGuard.close(); - } - - if (!finalized) { - // Close all connections. We don't need (or want) to do this - // when finalized because we don't know what state the connections - // themselves will be in. The finalizer is really just here for CloseGuard. - // The connections will take care of themselves when their own finalizers run. - synchronized (mLock) { - throwIfClosedLocked(); - - mIsOpen = false; - - closeAvailableConnectionsAndLogExceptionsLocked(); - - final int pendingCount = mAcquiredConnections.size(); - if (pendingCount != 0) { - Log.i(TAG, "The connection pool for " + mConfiguration.label - + " has been closed but there are still " - + pendingCount + " connections in use. They will be closed " - + "as they are released back to the pool."); - } - - wakeConnectionWaitersLocked(); - } - } - } - - /** - * Reconfigures the database configuration of the connection pool and all of its - * connections. - *

- * Configuration changes are propagated down to connections immediately if - * they are available or as soon as they are released. This includes changes - * that affect the size of the pool. - *

- * - * @param configuration The new configuration. - * - * @throws IllegalStateException if the pool has been closed. - */ - @SuppressLint("Assert") - public void reconfigure(SQLiteDatabaseConfiguration configuration) { - if (configuration == null) { - throw new IllegalArgumentException("configuration must not be null."); - } - - synchronized (mLock) { - throwIfClosedLocked(); - - boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags) - & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0; - if (walModeChanged) { - // WAL mode can only be changed if there are no acquired connections - // because we need to close all but the primary connection first. - if (!mAcquiredConnections.isEmpty()) { - throw new IllegalStateException("Write Ahead Logging (WAL) mode cannot " - + "be enabled or disabled while there are transactions in " - + "progress. Finish all transactions and release all active " - + "database connections first."); - } - - // Close all non-primary connections. This should happen immediately - // because none of them are in use. - closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked(); - assert mAvailableNonPrimaryConnections.isEmpty(); - } - - boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled - != mConfiguration.foreignKeyConstraintsEnabled; - if (foreignKeyModeChanged) { - // Foreign key constraints can only be changed if there are no transactions - // in progress. To make this clear, we throw an exception if there are - // any acquired connections. - if (!mAcquiredConnections.isEmpty()) { - throw new IllegalStateException("Foreign Key Constraints cannot " - + "be enabled or disabled while there are transactions in " - + "progress. Finish all transactions and release all active " - + "database connections first."); - } - } - - if (mConfiguration.openFlags != configuration.openFlags) { - // If we are changing open flags and WAL mode at the same time, then - // we have no choice but to close the primary connection beforehand - // because there can only be one connection open when we change WAL mode. - if (walModeChanged) { - closeAvailableConnectionsAndLogExceptionsLocked(); - } - - // Try to reopen the primary connection using the new open flags then - // close and discard all existing connections. - // This might throw if the database is corrupt or cannot be opened in - // the new mode in which case existing connections will remain untouched. - SQLiteConnection newPrimaryConnection = openConnectionLocked(configuration, - true /*primaryConnection*/); // might throw - - closeAvailableConnectionsAndLogExceptionsLocked(); - discardAcquiredConnectionsLocked(); - - mAvailablePrimaryConnection = newPrimaryConnection; - mConfiguration.updateParametersFrom(configuration); - setMaxConnectionPoolSizeLocked(); - } else { - // Reconfigure the database connections in place. - mConfiguration.updateParametersFrom(configuration); - setMaxConnectionPoolSizeLocked(); - - closeExcessConnectionsAndLogExceptionsLocked(); - reconfigureAllConnectionsLocked(); - } - - wakeConnectionWaitersLocked(); - } - } - - /** - * Acquires a connection from the pool. - *

- * The caller must call {@link #releaseConnection} to release the connection - * back to the pool when it is finished. Failure to do so will result - * in much unpleasantness. - *

- * - * @param sql If not null, try to find a connection that already has - * the specified SQL statement in its prepared statement cache. - * @param connectionFlags The connection request flags. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * @return The connection that was acquired, never null. - * - * @throws IllegalStateException if the pool has been closed. - * @throws SQLiteException if a database error occurs. - * @throws OperationCanceledException if the operation was canceled. - */ - public SQLiteConnection acquireConnection(String sql, int connectionFlags, - CancellationSignal cancellationSignal) { - return waitForConnection(sql, connectionFlags, cancellationSignal); - } - - /** - * Releases a connection back to the pool. - *

- * It is ok to call this method after the pool has closed, to release - * connections that were still in use at the time of closure. - *

- * - * @param connection The connection to release. Must not be null. - * - * @throws IllegalStateException if the connection was not acquired - * from this pool or if it has already been released. - */ - public void releaseConnection(SQLiteConnection connection) { - synchronized (mLock) { - AcquiredConnectionStatus status = mAcquiredConnections.remove(connection); - if (status == null) { - throw new IllegalStateException("Cannot perform this operation " - + "because the specified connection was not acquired " - + "from this pool or has already been released."); - } - - if (!mIsOpen) { - closeConnectionAndLogExceptionsLocked(connection); - } else if (connection.isPrimaryConnection()) { - if (recycleConnectionLocked(connection, status)) { - assert mAvailablePrimaryConnection == null; - mAvailablePrimaryConnection = connection; - } - wakeConnectionWaitersLocked(); - } else if (mAvailableNonPrimaryConnections.size() >= mMaxConnectionPoolSize - 1) { - closeConnectionAndLogExceptionsLocked(connection); - } else { - if (recycleConnectionLocked(connection, status)) { - mAvailableNonPrimaryConnections.add(connection); - } - wakeConnectionWaitersLocked(); - } - } - } - - // Can't throw. - private boolean recycleConnectionLocked(SQLiteConnection connection, - AcquiredConnectionStatus status) { - if (status == AcquiredConnectionStatus.RECONFIGURE) { - try { - connection.reconfigure(mConfiguration); // might throw - } catch (RuntimeException ex) { - Log.e(TAG, "Failed to reconfigure released connection, closing it: " - + connection, ex); - status = AcquiredConnectionStatus.DISCARD; - } - } - if (status == AcquiredConnectionStatus.DISCARD) { - closeConnectionAndLogExceptionsLocked(connection); - return false; - } - return true; - } - - /** - * Returns true if the session should yield the connection due to - * contention over available database connections. - * - * @param connection The connection owned by the session. - * @param connectionFlags The connection request flags. - * @return True if the session should yield its connection. - * - * @throws IllegalStateException if the connection was not acquired - * from this pool or if it has already been released. - */ - public boolean shouldYieldConnection(SQLiteConnection connection, int connectionFlags) { - synchronized (mLock) { - if (!mAcquiredConnections.containsKey(connection)) { - throw new IllegalStateException("Cannot perform this operation " - + "because the specified connection was not acquired " - + "from this pool or has already been released."); - } - - if (!mIsOpen) { - return false; - } - - return isSessionBlockingImportantConnectionWaitersLocked( - connection.isPrimaryConnection(), connectionFlags); - } - } - - /** - * Collects statistics about database connection memory usage. - * - * @param dbStatsList The list to populate. - */ - public void collectDbStats(ArrayList dbStatsList) { - synchronized (mLock) { - if (mAvailablePrimaryConnection != null) { - mAvailablePrimaryConnection.collectDbStats(dbStatsList); - } - - for (SQLiteConnection connection : mAvailableNonPrimaryConnections) { - connection.collectDbStats(dbStatsList); - } - - for (SQLiteConnection connection : mAcquiredConnections.keySet()) { - connection.collectDbStatsUnsafe(dbStatsList); - } - } - } - - // Might throw. - private SQLiteConnection openConnectionLocked(SQLiteDatabaseConfiguration configuration, - boolean primaryConnection) { - final int connectionId = mNextConnectionId++; - return SQLiteConnection.open(this, configuration, - connectionId, primaryConnection); // might throw - } - - void onConnectionLeaked() { - // This code is running inside of the SQLiteConnection finalizer. - // - // We don't know whether it is just the connection that has been finalized (and leaked) - // or whether the connection pool has also been or is about to be finalized. - // Consequently, it would be a bad idea to try to grab any locks or to - // do any significant work here. So we do the simplest possible thing and - // set a flag. waitForConnection() periodically checks this flag (when it - // times out) so that it can recover from leaked connections and wake - // itself or other threads up if necessary. - // - // You might still wonder why we don't try to do more to wake up the waiters - // immediately. First, as explained above, it would be hard to do safely - // unless we started an extra Thread to function as a reference queue. Second, - // this is never supposed to happen in normal operation. Third, there is no - // guarantee that the GC will actually detect the leak in a timely manner so - // it's not all that important that we recover from the leak in a timely manner - // either. Fourth, if a badly behaved application finds itself hung waiting for - // several seconds while waiting for a leaked connection to be detected and recreated, - // then perhaps its authors will have added incentive to fix the problem! - - Log.w(TAG, "A SQLiteConnection object for database '" - + mConfiguration.label + "' was leaked! Please fix your application " - + "to end transactions in progress properly and to close the database " - + "when it is no longer needed."); - - mConnectionLeaked.set(true); - } - - // Can't throw. - private void closeAvailableConnectionsAndLogExceptionsLocked() { - closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked(); - - if (mAvailablePrimaryConnection != null) { - closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection); - mAvailablePrimaryConnection = null; - } - } - - // Can't throw. - private void closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked() { - for (SQLiteConnection connection : mAvailableNonPrimaryConnections) { - closeConnectionAndLogExceptionsLocked(connection); - } - mAvailableNonPrimaryConnections.clear(); - } - - // Can't throw. - private void closeExcessConnectionsAndLogExceptionsLocked() { - int availableCount = mAvailableNonPrimaryConnections.size(); - while (availableCount-- > mMaxConnectionPoolSize - 1) { - SQLiteConnection connection = - mAvailableNonPrimaryConnections.remove(availableCount); - closeConnectionAndLogExceptionsLocked(connection); - } - } - - // Can't throw. - private void closeConnectionAndLogExceptionsLocked(SQLiteConnection connection) { - try { - connection.close(); // might throw - } catch (RuntimeException ex) { - Log.e(TAG, "Failed to close connection, its fate is now in the hands " - + "of the merciful GC: " + connection, ex); - } - } - - // Can't throw. - private void discardAcquiredConnectionsLocked() { - markAcquiredConnectionsLocked(AcquiredConnectionStatus.DISCARD); - } - - // Can't throw. - private void reconfigureAllConnectionsLocked() { - if (mAvailablePrimaryConnection != null) { - try { - mAvailablePrimaryConnection.reconfigure(mConfiguration); // might throw - } catch (RuntimeException ex) { - Log.e(TAG, "Failed to reconfigure available primary connection, closing it: " - + mAvailablePrimaryConnection, ex); - closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection); - mAvailablePrimaryConnection = null; - } - } - - int count = mAvailableNonPrimaryConnections.size(); - for (int i = 0; i < count; i++) { - final SQLiteConnection connection = mAvailableNonPrimaryConnections.get(i); - try { - connection.reconfigure(mConfiguration); // might throw - } catch (RuntimeException ex) { - Log.e(TAG, "Failed to reconfigure available non-primary connection, closing it: " - + connection, ex); - closeConnectionAndLogExceptionsLocked(connection); - mAvailableNonPrimaryConnections.remove(i--); - count -= 1; - } - } - - markAcquiredConnectionsLocked(AcquiredConnectionStatus.RECONFIGURE); - } - - // Can't throw. - private void markAcquiredConnectionsLocked(AcquiredConnectionStatus status) { - if (!mAcquiredConnections.isEmpty()) { - ArrayList keysToUpdate = new ArrayList<>( - mAcquiredConnections.size()); - for (Map.Entry entry - : mAcquiredConnections.entrySet()) { - AcquiredConnectionStatus oldStatus = entry.getValue(); - if (status != oldStatus - && oldStatus != AcquiredConnectionStatus.DISCARD) { - keysToUpdate.add(entry.getKey()); - } - } - for (SQLiteConnection key : keysToUpdate) { - mAcquiredConnections.put(key, status); - } - } - } - - // Might throw. - private SQLiteConnection waitForConnection(String sql, int connectionFlags, - CancellationSignal cancellationSignal) { - final boolean wantPrimaryConnection = - (connectionFlags & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0; - - final ConnectionWaiter waiter; - final int nonce; - synchronized (mLock) { - throwIfClosedLocked(); - - // Abort if canceled. - if (cancellationSignal != null) { - cancellationSignal.throwIfCanceled(); - } - - // Try to acquire a connection. - SQLiteConnection connection = null; - if (!wantPrimaryConnection) { - connection = tryAcquireNonPrimaryConnectionLocked( - sql, connectionFlags); // might throw - } - if (connection == null) { - connection = tryAcquirePrimaryConnectionLocked(connectionFlags); // might throw - } - if (connection != null) { - return connection; - } - - // No connections available. Enqueue a waiter in priority order. - final int priority = getPriority(connectionFlags); - final long startTime = SystemClock.uptimeMillis(); - waiter = obtainConnectionWaiterLocked(Thread.currentThread(), startTime, - priority, wantPrimaryConnection, sql, connectionFlags); - ConnectionWaiter predecessor = null; - ConnectionWaiter successor = mConnectionWaiterQueue; - while (successor != null) { - if (priority > successor.mPriority) { - waiter.mNext = successor; - break; - } - predecessor = successor; - successor = successor.mNext; - } - if (predecessor != null) { - predecessor.mNext = waiter; - } else { - mConnectionWaiterQueue = waiter; - } - - nonce = waiter.mNonce; - } - - // Set up the cancellation listener. - if (cancellationSignal != null) { - cancellationSignal.setOnCancelListener(new CancellationSignal.OnCancelListener() { - @Override - public void onCancel() { - synchronized (mLock) { - if (waiter.mNonce == nonce) { - cancelConnectionWaiterLocked(waiter); - } - } - } - }); - } - try { - // Park the thread until a connection is assigned or the pool is closed. - // Rethrow an exception from the wait, if we got one. - long busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS; - long nextBusyTimeoutTime = waiter.mStartTime + busyTimeoutMillis; - for (;;) { - // Detect and recover from connection leaks. - if (mConnectionLeaked.compareAndSet(true, false)) { - synchronized (mLock) { - wakeConnectionWaitersLocked(); - } - } - - // Wait to be unparked (may already have happened), a timeout, or interruption. - LockSupport.parkNanos(this, busyTimeoutMillis * 1000000L); - - // Clear the interrupted flag, just in case. - Thread.interrupted(); - - // Check whether we are done waiting yet. - synchronized (mLock) { - throwIfClosedLocked(); - - final SQLiteConnection connection = waiter.mAssignedConnection; - final RuntimeException ex = waiter.mException; - if (connection != null || ex != null) { - recycleConnectionWaiterLocked(waiter); - if (connection != null) { - return connection; - } - throw ex; // rethrow! - } - - final long now = SystemClock.uptimeMillis(); - if (now < nextBusyTimeoutTime) { - busyTimeoutMillis = now - nextBusyTimeoutTime; - } else { - logConnectionPoolBusyLocked(now - waiter.mStartTime, connectionFlags); - busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS; - nextBusyTimeoutTime = now + busyTimeoutMillis; - } - } - } - } finally { - // Remove the cancellation listener. - if (cancellationSignal != null) { - cancellationSignal.setOnCancelListener(null); - } - } - } - - // Can't throw. - private void cancelConnectionWaiterLocked(ConnectionWaiter waiter) { - if (waiter.mAssignedConnection != null || waiter.mException != null) { - // Waiter is done waiting but has not woken up yet. - return; - } - - // Waiter must still be waiting. Dequeue it. - ConnectionWaiter predecessor = null; - ConnectionWaiter current = mConnectionWaiterQueue; - while (current != waiter) { - assert current != null; - predecessor = current; - current = current.mNext; - } - if (predecessor != null) { - predecessor.mNext = waiter.mNext; - } else { - mConnectionWaiterQueue = waiter.mNext; - } - - // Send the waiter an exception and unpark it. - waiter.mException = new OperationCanceledException(); - LockSupport.unpark(waiter.mThread); - - // Check whether removing this waiter will enable other waiters to make progress. - wakeConnectionWaitersLocked(); - } - - // Can't throw. - private void logConnectionPoolBusyLocked(long waitMillis, int connectionFlags) { - final Thread thread = Thread.currentThread(); - StringBuilder msg = new StringBuilder(); - msg.append("The connection pool for database '").append(mConfiguration.label); - msg.append("' has been unable to grant a connection to thread "); - msg.append(thread.getId()).append(" (").append(thread.getName()).append(") "); - msg.append("with flags 0x").append(Integer.toHexString(connectionFlags)); - msg.append(" for ").append(waitMillis * 0.001f).append(" seconds.\n"); - - ArrayList requests = new ArrayList<>(); - int activeConnections = 0; - int idleConnections = 0; - if (!mAcquiredConnections.isEmpty()) { - for (SQLiteConnection connection : mAcquiredConnections.keySet()) { - String description = connection.describeCurrentOperationUnsafe(); - if (description != null) { - requests.add(description); - activeConnections += 1; - } else { - idleConnections += 1; - } - } - } - int availableConnections = mAvailableNonPrimaryConnections.size(); - if (mAvailablePrimaryConnection != null) { - availableConnections += 1; - } - - msg.append("Connections: ").append(activeConnections).append(" active, "); - msg.append(idleConnections).append(" idle, "); - msg.append(availableConnections).append(" available.\n"); - - if (!requests.isEmpty()) { - msg.append("\nRequests in progress:\n"); - for (String request : requests) { - msg.append(" ").append(request).append("\n"); - } - } - - Log.w(TAG, msg.toString()); - } - - // Can't throw. - private void wakeConnectionWaitersLocked() { - // Unpark all waiters that have requests that we can fulfill. - // This method is designed to not throw runtime exceptions, although we might send - // a waiter an exception for it to rethrow. - ConnectionWaiter predecessor = null; - ConnectionWaiter waiter = mConnectionWaiterQueue; - boolean primaryConnectionNotAvailable = false; - boolean nonPrimaryConnectionNotAvailable = false; - while (waiter != null) { - boolean unpark = false; - if (!mIsOpen) { - unpark = true; - } else { - try { - SQLiteConnection connection = null; - if (!waiter.mWantPrimaryConnection && !nonPrimaryConnectionNotAvailable) { - connection = tryAcquireNonPrimaryConnectionLocked( - waiter.mSql, waiter.mConnectionFlags); // might throw - if (connection == null) { - nonPrimaryConnectionNotAvailable = true; - } - } - if (connection == null && !primaryConnectionNotAvailable) { - connection = tryAcquirePrimaryConnectionLocked( - waiter.mConnectionFlags); // might throw - if (connection == null) { - primaryConnectionNotAvailable = true; - } - } - if (connection != null) { - waiter.mAssignedConnection = connection; - unpark = true; - } else if (nonPrimaryConnectionNotAvailable && primaryConnectionNotAvailable) { - // There are no connections available and the pool is still open. - // We cannot fulfill any more connection requests, so stop here. - break; - } - } catch (RuntimeException ex) { - // Let the waiter handle the exception from acquiring a connection. - waiter.mException = ex; - unpark = true; - } - } - - final ConnectionWaiter successor = waiter.mNext; - if (unpark) { - if (predecessor != null) { - predecessor.mNext = successor; - } else { - mConnectionWaiterQueue = successor; - } - waiter.mNext = null; - - LockSupport.unpark(waiter.mThread); - } else { - predecessor = waiter; - } - waiter = successor; - } - } - - // Might throw. - private SQLiteConnection tryAcquirePrimaryConnectionLocked(int connectionFlags) { - // If the primary connection is available, acquire it now. - SQLiteConnection connection = mAvailablePrimaryConnection; - if (connection != null) { - mAvailablePrimaryConnection = null; - finishAcquireConnectionLocked(connection, connectionFlags); // might throw - return connection; - } - - // Make sure that the primary connection actually exists and has just been acquired. - for (SQLiteConnection acquiredConnection : mAcquiredConnections.keySet()) { - if (acquiredConnection.isPrimaryConnection()) { - return null; - } - } - - // Uhoh. No primary connection! Either this is the first time we asked - // for it, or maybe it leaked? - connection = openConnectionLocked(mConfiguration, - true /*primaryConnection*/); // might throw - finishAcquireConnectionLocked(connection, connectionFlags); // might throw - return connection; - } - - // Might throw. - private SQLiteConnection tryAcquireNonPrimaryConnectionLocked( - String sql, int connectionFlags) { - // Try to acquire the next connection in the queue. - SQLiteConnection connection; - final int availableCount = mAvailableNonPrimaryConnections.size(); - if (availableCount > 1 && sql != null) { - // If we have a choice, then prefer a connection that has the - // prepared statement in its cache. - for (int i = 0; i < availableCount; i++) { - connection = mAvailableNonPrimaryConnections.get(i); - if (connection.isPreparedStatementInCache(sql)) { - mAvailableNonPrimaryConnections.remove(i); - finishAcquireConnectionLocked(connection, connectionFlags); // might throw - return connection; - } - } - } - if (availableCount > 0) { - // Otherwise, just grab the next one. - connection = mAvailableNonPrimaryConnections.remove(availableCount - 1); - finishAcquireConnectionLocked(connection, connectionFlags); // might throw - return connection; - } - - // Expand the pool if needed. - int openConnections = mAcquiredConnections.size(); - if (mAvailablePrimaryConnection != null) { - openConnections += 1; - } - if (openConnections >= mMaxConnectionPoolSize) { - return null; - } - connection = openConnectionLocked(mConfiguration, - false /*primaryConnection*/); // might throw - finishAcquireConnectionLocked(connection, connectionFlags); // might throw - return connection; - } - - // Might throw. - private void finishAcquireConnectionLocked(SQLiteConnection connection, int connectionFlags) { - try { - final boolean readOnly = (connectionFlags & CONNECTION_FLAG_READ_ONLY) != 0; - connection.setOnlyAllowReadOnlyOperations(readOnly); - - mAcquiredConnections.put(connection, AcquiredConnectionStatus.NORMAL); - } catch (RuntimeException ex) { - Log.e(TAG, "Failed to prepare acquired connection for session, closing it: " - + connection +", connectionFlags=" + connectionFlags); - closeConnectionAndLogExceptionsLocked(connection); - throw ex; // rethrow! - } - } - - private boolean isSessionBlockingImportantConnectionWaitersLocked( - boolean holdingPrimaryConnection, int connectionFlags) { - ConnectionWaiter waiter = mConnectionWaiterQueue; - if (waiter != null) { - final int priority = getPriority(connectionFlags); - do { - // Only worry about blocked connections that have same or lower priority. - if (priority > waiter.mPriority) { - break; - } - - // If we are holding the primary connection then we are blocking the waiter. - // Likewise, if we are holding a non-primary connection and the waiter - // would accept a non-primary connection, then we are blocking the waier. - if (holdingPrimaryConnection || !waiter.mWantPrimaryConnection) { - return true; - } - - waiter = waiter.mNext; - } while (waiter != null); - } - return false; - } - - private static int getPriority(int connectionFlags) { - return (connectionFlags & CONNECTION_FLAG_INTERACTIVE) != 0 ? 1 : 0; - } - - private void setMaxConnectionPoolSizeLocked() { - if (!SQLiteDatabase.hasCodec() - && (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) { - mMaxConnectionPoolSize = SQLiteGlobal.getWALConnectionPoolSize(); - } else { - // TODO: We don't actually need to restrict the connection pool size to 1 - // for non-WAL databases. There might be reasons to use connection pooling - // with other journal modes. For now, enabling connection pooling and - // using WAL are the same thing in the API. - mMaxConnectionPoolSize = 1; - } - } - - private void throwIfClosedLocked() { - if (!mIsOpen) { - throw new IllegalStateException("Cannot perform this operation " - + "because the connection pool has been closed."); - } - } - - private ConnectionWaiter obtainConnectionWaiterLocked(Thread thread, long startTime, - int priority, boolean wantPrimaryConnection, String sql, int connectionFlags) { - ConnectionWaiter waiter = mConnectionWaiterPool; - if (waiter != null) { - mConnectionWaiterPool = waiter.mNext; - waiter.mNext = null; - } else { - waiter = new ConnectionWaiter(); - } - waiter.mThread = thread; - waiter.mStartTime = startTime; - waiter.mPriority = priority; - waiter.mWantPrimaryConnection = wantPrimaryConnection; - waiter.mSql = sql; - waiter.mConnectionFlags = connectionFlags; - return waiter; - } - - private void recycleConnectionWaiterLocked(ConnectionWaiter waiter) { - waiter.mNext = mConnectionWaiterPool; - waiter.mThread = null; - waiter.mSql = null; - waiter.mAssignedConnection = null; - waiter.mException = null; - waiter.mNonce += 1; - mConnectionWaiterPool = waiter; - } - - public void enableLocalizedCollators() { - synchronized (mLock) { - if (!mAcquiredConnections.isEmpty() || mAvailablePrimaryConnection == null) { - throw new IllegalStateException( - "Cannot enable localized collators while database is in use" - ); - } - mAvailablePrimaryConnection.enableLocalizedCollators(); - } - } - - /** - * Dumps debugging information about this connection pool. - * - * @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) { - synchronized (mLock) { - printer.println("Connection pool for " + mConfiguration.path + ":"); - printer.println(" Open: " + mIsOpen); - printer.println(" Max connections: " + mMaxConnectionPoolSize); - - printer.println(" Available primary connection:"); - if (mAvailablePrimaryConnection != null) { - mAvailablePrimaryConnection.dump(printer, verbose); - } else { - printer.println(""); - } - - printer.println(" Available non-primary connections:"); - if (!mAvailableNonPrimaryConnections.isEmpty()) { - for (SQLiteConnection connection : mAvailableNonPrimaryConnections) { - connection.dump(printer, verbose); - } - } else { - printer.println(""); - } - - printer.println(" Acquired connections:"); - if (!mAcquiredConnections.isEmpty()) { - for (Map.Entry entry : - mAcquiredConnections.entrySet()) { - final SQLiteConnection connection = entry.getKey(); - connection.dumpUnsafe(printer, verbose); - printer.println(" Status: " + entry.getValue()); - } - } else { - printer.println(""); - } - - printer.println(" Connection waiters:"); - if (mConnectionWaiterQueue != null) { - int i = 0; - final long now = SystemClock.uptimeMillis(); - for (ConnectionWaiter waiter = mConnectionWaiterQueue; waiter != null; - waiter = waiter.mNext, i++) { - printer.println(i + ": waited for " - + ((now - waiter.mStartTime) * 0.001f) - + " ms - thread=" + waiter.mThread - + ", priority=" + waiter.mPriority - + ", sql='" + waiter.mSql + "'"); - } - } else { - printer.println(""); - } - } - } - - @Override - public String toString() { - return "SQLiteConnectionPool: " + mConfiguration.path; - } - - private static final class ConnectionWaiter { - public ConnectionWaiter mNext; - public Thread mThread; - public long mStartTime; - public int mPriority; - public boolean mWantPrimaryConnection; - public String mSql; - public int mConnectionFlags; - public SQLiteConnection mAssignedConnection; - public RuntimeException mException; - public int mNonce; - } -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteCursor.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteCursor.java deleted file mode 100644 index 226689eae5..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteCursor.java +++ /dev/null @@ -1,260 +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.sqlite; - -import android.util.Log; -import android.util.SparseIntArray; -import io.requery.android.database.AbstractWindowedCursor; -import io.requery.android.database.CursorWindow; - -import java.util.HashMap; - -/** - * A Cursor implementation that exposes results from a query on a {@link SQLiteDatabase}. - * - * SQLiteCursor is not internally synchronized so code using a SQLiteCursor from multiple - * threads should perform its own synchronization when using the SQLiteCursor. - */ -public class SQLiteCursor extends AbstractWindowedCursor { - static final String TAG = "SQLiteCursor"; - static final int NO_COUNT = -1; - - /** The names of the columns in the rows */ - private final String[] mColumns; - - /** The query object for the cursor */ - private final SQLiteQuery mQuery; - - /** The compiled query this cursor came from */ - private final SQLiteCursorDriver mDriver; - - /** The number of rows in the cursor */ - private int mCount = NO_COUNT; - - /** The number of rows that can fit in the cursor window, 0 if unknown */ - private int mCursorWindowCapacity; - - /** A mapping of column names to column indices, to speed up lookups */ - private SparseIntArray mColumnNameArray; - private HashMap mColumnNameMap; - - /** Used to find out where a cursor was allocated in case it never got released. */ - private final CloseGuard mCloseGuard; - - /** - * Execute a query and provide access to its result set through a Cursor - * interface. For a query such as: {@code SELECT name, birth, phone FROM - * myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth, - * phone) would be in the projection argument and everything from - * {@code FROM} onward would be in the params argument. - * - * @param editTable not used, present only for compatibility with - * {@link android.database.sqlite.SQLiteCursor} - * @param query the {@link SQLiteQuery} object associated with this cursor object. - */ - @SuppressWarnings("unused") - public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) { - if (query == null) { - throw new IllegalArgumentException("query object cannot be null"); - } - mDriver = driver; - mQuery = query; - mCloseGuard = CloseGuard.get(); - mColumns = query.getColumnNames(); - } - - /** - * Get the database that this cursor is associated with. - * @return the SQLiteDatabase that this cursor is associated with. - */ - public SQLiteDatabase getDatabase() { - return mQuery.getDatabase(); - } - - @Override - public boolean onMove(int oldPosition, int newPosition) { - // Make sure the row at newPosition is present in the window - if (mWindow == null || newPosition < mWindow.getStartPosition() || - newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) { - fillWindow(newPosition); - } - - return true; - } - - @Override - public int getCount() { - if (mCount == NO_COUNT) { - fillWindow(0); - } - return mCount; - } - - public static int cursorPickFillWindowStartPosition( - int cursorPosition, int cursorWindowCapacity) { - return Math.max(cursorPosition - cursorWindowCapacity / 3, 0); - } - - private void fillWindow(int requiredPos) { - clearOrCreateWindow(getDatabase().getPath()); - - try { - if (mCount == NO_COUNT) { - int startPos = cursorPickFillWindowStartPosition(requiredPos, 0); - mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true); - mCursorWindowCapacity = mWindow.getNumRows(); - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "received count(*) from native_fill_window: " + mCount); - } - } else { - int startPos = cursorPickFillWindowStartPosition(requiredPos, - mCursorWindowCapacity); - mQuery.fillWindow(mWindow, startPos, requiredPos, false); - } - } catch (RuntimeException ex) { - // Close the cursor window if the query failed and therefore will - // not produce any results. This helps to avoid accidentally leaking - // the cursor window if the client does not correctly handle exceptions - // and fails to close the cursor. - setWindow(null); - throw ex; - } - } - - @Override - public int getColumnIndex(String columnName) { - // Create mColumnNameMap on demand - if (mColumnNameArray == null && mColumnNameMap == null) { - String[] columns = mColumns; - int columnCount = columns.length; - SparseIntArray map = new SparseIntArray(columnCount); - boolean collision = false; - for (int i = 0; i < columnCount; i++) { - int key = columns[i].hashCode(); - // check for hashCode collision - if (map.get(key, -1) != -1) { - collision = true; - break; - } - map.put(key, i); - } - - if (collision) { - mColumnNameMap = new HashMap<>(); - for (int i = 0; i < columnCount; i++) { - mColumnNameMap.put(columns[i], i); - } - } else { - mColumnNameArray = map; - } - } - - // Hack according to bug 903852 - final int periodIndex = columnName.lastIndexOf('.'); - if (periodIndex != -1) { - Exception e = new Exception(); - Log.e(TAG, "requesting column name with table name -- " + columnName, e); - columnName = columnName.substring(periodIndex + 1); - } - - if (mColumnNameMap != null) { - Integer i = mColumnNameMap.get(columnName); - return i == null ? -1 : i; - } else { - return mColumnNameArray.get(columnName.hashCode(), -1); - } - } - - @Override - public String[] getColumnNames() { - return mColumns; - } - - @Override - public void deactivate() { - super.deactivate(); - mDriver.cursorDeactivated(); - } - - @Override - public void close() { - super.close(); - synchronized (this) { - mQuery.close(); - mDriver.cursorClosed(); - } - } - - @Override - public boolean requery() { - if (isClosed()) { - return false; - } - - synchronized (this) { - if (!mQuery.getDatabase().isOpen()) { - return false; - } - - if (mWindow != null) { - mWindow.clear(); - } - mPos = -1; - mCount = NO_COUNT; - - mDriver.cursorRequeried(this); - } - - try { - return super.requery(); - } catch (IllegalStateException e) { - // for backwards compatibility, just return false - Log.w(TAG, "requery() failed " + e.getMessage(), e); - return false; - } - } - - @Override - public void setWindow(CursorWindow window) { - super.setWindow(window); - mCount = NO_COUNT; - } - - /** - * Changes the selection arguments. The new values take effect after a call to requery(). - */ - public void setSelectionArguments(String[] selectionArgs) { - mDriver.setBindArguments(selectionArgs); - } - - /** - * Release the native resources, if they haven't been released yet. - */ - @Override - protected void finalize() { - try { - // if the cursor hasn't been closed yet, close it first - if (mWindow != null) { - mCloseGuard.warnIfOpen(); - close(); - } - } finally { - super.finalize(); - } - } -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteCursorDriver.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteCursorDriver.java deleted file mode 100644 index 0a3c653cb3..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteCursorDriver.java +++ /dev/null @@ -1,56 +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 android.database.Cursor; - -/** - * A driver for SQLiteCursors that is used to create them and gets notified - * by the cursors it creates on significant events in their lifetimes. - */ -public interface SQLiteCursorDriver { - /** - * Executes the query returning a Cursor over the result set. - * - * @param factory The CursorFactory to use when creating the Cursors, or - * null if standard SQLiteCursors should be returned. - * @return a Cursor over the result set - */ - Cursor query(SQLiteDatabase.CursorFactory factory, Object[] bindArgs); - - /** - * Called by a SQLiteCursor when it is released. - */ - void cursorDeactivated(); - - /** - * Called by a SQLiteCursor when it is requeried. - */ - void cursorRequeried(Cursor cursor); - - /** - * Called by a SQLiteCursor when it it closed to destroy this object as well. - */ - void cursorClosed(); - - /** - * Set new bind arguments. These will take effect in cursorRequeried(). - * @param bindArgs the new arguments - */ - void setBindArguments(String[] bindArgs); -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteCustomExtension.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteCustomExtension.java deleted file mode 100644 index 86d8efaa77..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteCustomExtension.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2016 requery.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.requery.android.database.sqlite; - -/** - * Describes a SQLite extension entry point. - */ -public final class SQLiteCustomExtension { - - public final String path; - public final String entryPoint; - - /** - * Creates a SQLite extension description. - * - * @param path path to the loadable extension shared library - * e.g. "/data/data/(package)/lib/libSqliteICU.so" - * @param entryPoint extension entry point (optional) - * e.g. "sqlite3_icu_init" - */ - public SQLiteCustomExtension(String path, String entryPoint) { - if (path == null) { - throw new IllegalArgumentException("null path"); - } - this.path = path; - this.entryPoint = entryPoint; - } -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteCustomFunction.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteCustomFunction.java deleted file mode 100644 index 6814875b49..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteCustomFunction.java +++ /dev/null @@ -1,54 +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 - -package io.requery.android.database.sqlite; - -/** - * Describes a custom SQL function. - * - * @hide - */ -public final class SQLiteCustomFunction { - public final String name; - public final int numArgs; - public final SQLiteDatabase.CustomFunction callback; - - /** - * Create custom function. - * - * @param name The name of the sqlite3 function. - * @param numArgs The number of arguments for the function, or -1 to - * support any number of arguments. - * @param callback The callback to invoke when the function is executed. - */ - public SQLiteCustomFunction(String name, int numArgs, - SQLiteDatabase.CustomFunction callback) { - if (name == null) { - throw new IllegalArgumentException("name must not be null."); - } - - this.name = name; - this.numArgs = numArgs; - this.callback = callback; - } - - // Called from native. - @SuppressWarnings("unused") - private String dispatchCallback(String[] args) { - return callback.callback(args); - } -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDatabase.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDatabase.java deleted file mode 100644 index aca107f4a6..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDatabase.java +++ /dev/null @@ -1,2603 +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 -/* -** 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.content.ContentValues; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabaseCorruptException; -import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteTransactionListener; -import android.os.Build; -import android.os.Looper; -import android.os.ParcelFileDescriptor; -import android.text.TextUtils; -import android.util.EventLog; -import android.util.Log; -import android.util.Pair; -import android.util.Printer; -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.core.os.CancellationSignal; -import androidx.core.os.OperationCanceledException; -import androidx.sqlite.db.SupportSQLiteDatabase; -import androidx.sqlite.db.SupportSQLiteQuery; -import io.requery.android.database.DatabaseErrorHandler; -import io.requery.android.database.DefaultDatabaseErrorHandler; - -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.WeakHashMap; - -/** - * Exposes methods to manage a SQLite database. - * - *

- * SQLiteDatabase has methods to create, delete, execute SQL commands, and - * perform other common database management tasks. - *

- * See the Notepad sample application in the SDK for an example of creating - * and managing a database. - *

- * Database names must be unique within an application, not across all applications. - *

- * - *

Localized Collation - ORDER BY

- *

- * In addition to SQLite's default BINARY collator, Android supplies - * two more, LOCALIZED, which changes with the system's current locale, - * and UNICODE, which is the Unicode Collation Algorithm and not tailored - * to the current locale. - *

- */ -@SuppressWarnings({"unused", "JavaDoc", "TryFinallyCanBeTryWithResources"}) -@SuppressLint("ShiftFlags") // suppressed for readability with native code -public final class SQLiteDatabase extends SQLiteClosable implements SupportSQLiteDatabase { - - /** - * Name of the compiled native library. - */ - public static final String LIBRARY_NAME = "sqlite3x"; - static { - System.loadLibrary(LIBRARY_NAME); - } - - private static final String TAG = "SQLiteDatabase"; - - private static final int EVENT_DB_CORRUPT = 75004; - - // Stores reference to all databases opened in the current process. - // (The referent Object is not used at this time.) - // INVARIANT: Guarded by sActiveDatabases. - private static final WeakHashMap sActiveDatabases = new WeakHashMap<>(); - - // Thread-local for database sessions that belong to this database. - // Each thread has its own database session. - // INVARIANT: Immutable. - private final ThreadLocal mThreadSession = new ThreadLocal() { - @Override - protected SQLiteSession initialValue() { - return createSession(); - } - }; - - // The optional factory to use when creating new Cursors. May be null. - // INVARIANT: Immutable. - private final CursorFactory mCursorFactory; - - // Error handler to be used when SQLite returns corruption errors. - // INVARIANT: Immutable. - private final DatabaseErrorHandler mErrorHandler; - - // Shared database state lock. - // This lock guards all of the shared state of the database, such as its - // configuration, whether it is open or closed, and so on. This lock should - // be held for as little time as possible. - // - // The lock MUST NOT be held while attempting to acquire database connections or - // while executing SQL statements on behalf of the client as it can lead to deadlock. - // - // It is ok to hold the lock while reconfiguring the connection pool or dumping - // statistics because those operations are non-reentrant and do not try to acquire - // connections that might be held by other threads. - // - // Basic rule: grab the lock, access or modify global state, release the lock, then - // do the required SQL work. - private final Object mLock = new Object(); - - // Warns if the database is finalized without being closed properly. - // INVARIANT: Guarded by mLock. - private final CloseGuard mCloseGuardLocked = CloseGuard.get(); - - // The database configuration. - // INVARIANT: Guarded by mLock. - private final SQLiteDatabaseConfiguration mConfigurationLocked; - - // The connection pool for the database, null when closed. - // The pool itself is thread-safe, but the reference to it can only be acquired - // when the lock is held. - // INVARIANT: Guarded by mLock. - private SQLiteConnectionPool mConnectionPoolLocked; - - /** - * When a constraint violation occurs, an immediate ROLLBACK occurs, - * thus ending the current transaction, and the command aborts with a - * return code of SQLITE_CONSTRAINT. If no transaction is active - * (other than the implied transaction that is created on every command) - * then this algorithm works the same as ABORT. - */ - public static final int CONFLICT_ROLLBACK = 1; - - /** - * When a constraint violation occurs,no ROLLBACK is executed - * so changes from prior commands within the same transaction - * are preserved. This is the default behavior. - */ - public static final int CONFLICT_ABORT = 2; - - /** - * When a constraint violation occurs, the command aborts with a return - * code SQLITE_CONSTRAINT. But any changes to the database that - * the command made prior to encountering the constraint violation - * are preserved and are not backed out. - */ - public static final int CONFLICT_FAIL = 3; - - /** - * When a constraint violation occurs, the one row that contains - * the constraint violation is not inserted or changed. - * But the command continues executing normally. Other rows before and - * after the row that contained the constraint violation continue to be - * inserted or updated normally. No error is returned. - */ - public static final int CONFLICT_IGNORE = 4; - - /** - * When a UNIQUE constraint violation occurs, the pre-existing rows that - * are causing the constraint violation are removed prior to inserting - * or updating the current row. Thus the insert or update always occurs. - * The command continues executing normally. No error is returned. - * If a NOT NULL constraint violation occurs, the NULL value is replaced - * by the default value for that column. If the column has no default - * value, then the ABORT algorithm is used. If a CHECK constraint - * violation occurs then the IGNORE algorithm is used. When this conflict - * resolution strategy deletes rows in order to satisfy a constraint, - * it does not invoke delete triggers on those rows. - * This behavior might change in a future release. - */ - public static final int CONFLICT_REPLACE = 5; - - /** - * Use the following when no conflict action is specified. - */ - public static final int CONFLICT_NONE = 0; - - /** Conflict options integer enumeration definition */ - @IntDef({ - CONFLICT_ABORT, - CONFLICT_FAIL, - CONFLICT_IGNORE, - CONFLICT_NONE, - CONFLICT_REPLACE, - CONFLICT_ROLLBACK}) - @Retention(RetentionPolicy.SOURCE) - public @interface ConflictAlgorithm { - } - - private static final String[] CONFLICT_VALUES = new String[] - {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "}; - - /** Open flag to open in the database in read only mode */ - public static final int OPEN_READONLY = 0x00000001; - - /** Open flag to open in the database in read/write mode */ - public static final int OPEN_READWRITE = 0x00000002; - - /** Open flag to create the database if it does not exist */ - public static final int OPEN_CREATE = 0x00000004; - - /** Open flag to support URI filenames */ - public static final int OPEN_URI = 0x00000040; - - /** Open flag opens the database in multi-thread threading mode */ - public static final int OPEN_NOMUTEX = 0x00008000; - - /** Open flag opens the database in serialized threading mode */ - public static final int OPEN_FULLMUTEX = 0x00010000; - - /** Open flag opens the database in shared cache mode */ - public static final int OPEN_SHAREDCACHE = 0x00020000; - - /** Open flag opens the database in private cache mode */ - public static final int OPEN_PRIVATECACHE = 0x00040000; - - /** Open flag equivalent to {@link #OPEN_READWRITE} | {@link #OPEN_CREATE} */ - public static final int CREATE_IF_NECESSARY = OPEN_READWRITE | OPEN_CREATE; - - /** Open flag to enable write-ahead logging */ // custom flag remove for sqlite3_open_v2 - public static final int ENABLE_WRITE_AHEAD_LOGGING = 0x20000000; - - /** Integer flag definition for the database open options */ - @SuppressLint("UniqueConstants") // duplicate values provided for compatibility - @IntDef(flag = true, value = { - OPEN_READONLY, - OPEN_READWRITE, - OPEN_CREATE, - OPEN_URI, - OPEN_NOMUTEX, - OPEN_FULLMUTEX, - OPEN_SHAREDCACHE, - OPEN_PRIVATECACHE, - CREATE_IF_NECESSARY, - ENABLE_WRITE_AHEAD_LOGGING}) - @Retention(RetentionPolicy.SOURCE) - public @interface OpenFlags { - } - - /** - * Absolute max value that can be set by {@link #setMaxSqlCacheSize(int)}. - * - * Each prepared-statement is between 1K - 6K, depending on the complexity of the - * SQL statement & schema. A large SQL cache may use a significant amount of memory. - */ - public static final int MAX_SQL_CACHE_SIZE = 100; - - private SQLiteDatabase(SQLiteDatabaseConfiguration configuration, - CursorFactory cursorFactory, - DatabaseErrorHandler errorHandler) { - mCursorFactory = cursorFactory; - mErrorHandler = errorHandler != null ? errorHandler : new DefaultDatabaseErrorHandler(); - mConfigurationLocked = configuration; - } - - @SuppressWarnings("ThrowFromFinallyBlock") - @Override - protected void finalize() throws Throwable { - try { - dispose(true); - } finally { - super.finalize(); - } - } - - @Override - protected void onAllReferencesReleased() { - dispose(false); - } - - private void dispose(boolean finalized) { - final SQLiteConnectionPool pool; - synchronized (mLock) { - if (mCloseGuardLocked != null) { - if (finalized) { - mCloseGuardLocked.warnIfOpen(); - } - mCloseGuardLocked.close(); - } - - pool = mConnectionPoolLocked; - mConnectionPoolLocked = null; - } - - if (!finalized) { - synchronized (sActiveDatabases) { - sActiveDatabases.remove(this); - } - - if (pool != null) { - pool.close(); - } - } - } - - /** - * Attempts to release memory that SQLite holds but does not require to - * operate properly. Typically this memory will come from the page cache. - * - * @return the number of bytes actually released - */ - public static int releaseMemory() { - return SQLiteGlobal.releaseMemory(); - } - - /** - * Gets a label to use when describing the database in log messages. - * @return The label. - */ - String getLabel() { - synchronized (mLock) { - return mConfigurationLocked.label; - } - } - - /** - * Sends a corruption message to the database error handler. - */ - void onCorruption() { - EventLog.writeEvent(EVENT_DB_CORRUPT, getLabel()); - mErrorHandler.onCorruption(this); - } - - /** - * Gets the {@link SQLiteSession} that belongs to this thread for this database. - * Once a thread has obtained a session, it will continue to obtain the same - * session even after the database has been closed (although the session will not - * be usable). However, a thread that does not already have a session cannot - * obtain one after the database has been closed. - * - * The idea is that threads that have active connections to the database may still - * have work to complete even after the call to {@link #close}. Active database - * connections are not actually disposed until they are released by the threads - * that own them. - * - * @return The session, never null. - * - * @throws IllegalStateException if the thread does not yet have a session and - * the database is not open. - */ - SQLiteSession getThreadSession() { - return mThreadSession.get(); // initialValue() throws if database closed - } - - SQLiteSession createSession() { - final SQLiteConnectionPool pool; - synchronized (mLock) { - throwIfNotOpenLocked(); - pool = mConnectionPoolLocked; - } - return new SQLiteSession(pool); - } - - /** - * Gets default connection flags that are appropriate for this thread, taking into - * account whether the thread is acting on behalf of the UI. - * - * @param readOnly True if the connection should be read-only. - * @return The connection flags. - */ - int getThreadDefaultConnectionFlags(boolean readOnly) { - int flags = readOnly ? SQLiteConnectionPool.CONNECTION_FLAG_READ_ONLY : - SQLiteConnectionPool.CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY; - if (isMainThread()) { - flags |= SQLiteConnectionPool.CONNECTION_FLAG_INTERACTIVE; - } - return flags; - } - - private static boolean isMainThread() { - // FIXME: There should be a better way to do this. - // Would also be nice to have something that would work across Binder calls. - Looper looper = Looper.myLooper(); - return looper != null && looper == Looper.getMainLooper(); - } - - /** - * Begins a transaction in EXCLUSIVE mode. - *

- * Transactions can be nested. - * When the outer transaction is ended all of - * the work done in that transaction and all of the nested transactions will be committed or - * rolled back. The changes will be rolled back if any transaction is ended without being - * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed. - *

- *

Here is the standard idiom for transactions: - * - *

-     *   db.beginTransaction();
-     *   try {
-     *     ...
-     *     db.setTransactionSuccessful();
-     *   } finally {
-     *     db.endTransaction();
-     *   }
-     * 
- */ - @Override - public void beginTransaction() { - beginTransaction(null, SQLiteSession.TRANSACTION_MODE_EXCLUSIVE); - } - - /** - * Begins a transaction in IMMEDIATE mode. Transactions can be nested. When - * the outer transaction is ended all of the work done in that transaction - * and all of the nested transactions will be committed or rolled back. The - * changes will be rolled back if any transaction is ended without being - * marked as clean (by calling setTransactionSuccessful). Otherwise they - * will be committed. - *

- * Here is the standard idiom for transactions: - * - *

-     *   db.beginTransactionNonExclusive();
-     *   try {
-     *     ...
-     *     db.setTransactionSuccessful();
-     *   } finally {
-     *     db.endTransaction();
-     *   }
-     * 
- */ - @Override - public void beginTransactionNonExclusive() { - beginTransaction(null, SQLiteSession.TRANSACTION_MODE_IMMEDIATE); - } - - /** - * Begins a transaction in DEFERRED mode. - */ - public void beginTransactionDeferred() { - beginTransaction(null, SQLiteSession.TRANSACTION_MODE_DEFERRED); - } - - /** - * Begins a transaction in DEFERRED mode. - * - * @param transactionListener listener that should be notified when the transaction begins, - * commits, or is rolled back, either explicitly or by a call to - * {@link #yieldIfContendedSafely}. - */ - public void beginTransactionWithListenerDeferred( - SQLiteTransactionListener transactionListener) { - beginTransaction(transactionListener, SQLiteSession.TRANSACTION_MODE_DEFERRED); - } - - /** - * Begins a transaction in EXCLUSIVE mode. - *

- * Transactions can be nested. - * When the outer transaction is ended all of - * the work done in that transaction and all of the nested transactions will be committed or - * rolled back. The changes will be rolled back if any transaction is ended without being - * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed. - *

- *

Here is the standard idiom for transactions: - * - *

-     *   db.beginTransactionWithListener(listener);
-     *   try {
-     *     ...
-     *     db.setTransactionSuccessful();
-     *   } finally {
-     *     db.endTransaction();
-     *   }
-     * 
- * - * @param transactionListener listener that should be notified when the transaction begins, - * commits, or is rolled back, either explicitly or by a call to - * {@link #yieldIfContendedSafely}. - */ - @Override - public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) { - beginTransaction(transactionListener, SQLiteSession.TRANSACTION_MODE_EXCLUSIVE); - } - - /** - * Begins a transaction in IMMEDIATE mode. Transactions can be nested. When - * the outer transaction is ended all of the work done in that transaction - * and all of the nested transactions will be committed or rolled back. The - * changes will be rolled back if any transaction is ended without being - * marked as clean (by calling setTransactionSuccessful). Otherwise they - * will be committed. - *

- * Here is the standard idiom for transactions: - * - *

-     *   db.beginTransactionWithListenerNonExclusive(listener);
-     *   try {
-     *     ...
-     *     db.setTransactionSuccessful();
-     *   } finally {
-     *     db.endTransaction();
-     *   }
-     * 
- * - * @param transactionListener listener that should be notified when the - * transaction begins, commits, or is rolled back, either - * explicitly or by a call to {@link #yieldIfContendedSafely}. - */ - @Override - public void beginTransactionWithListenerNonExclusive( - SQLiteTransactionListener transactionListener) { - beginTransaction(transactionListener, SQLiteSession.TRANSACTION_MODE_IMMEDIATE); - } - - private void beginTransaction(SQLiteTransactionListener transactionListener, int mode) { - acquireReference(); - try { - getThreadSession().beginTransaction(mode, transactionListener, - getThreadDefaultConnectionFlags(false /*readOnly*/), null); - } finally { - releaseReference(); - } - } - - /** - * End a transaction. See beginTransaction for notes about how to use this and when transactions - * are committed and rolled back. - */ - @Override - public void endTransaction() { - acquireReference(); - try { - getThreadSession().endTransaction(null); - } finally { - releaseReference(); - } - } - - /** - * Marks the current transaction as successful. Do not do any more database work between - * calling this and calling endTransaction. Do as little non-database work as possible in that - * situation too. If any errors are encountered between this and endTransaction the transaction - * will still be committed. - * - * @throws IllegalStateException if the current thread is not in a transaction or the - * transaction is already marked as successful. - */ - @Override - public void setTransactionSuccessful() { - acquireReference(); - try { - getThreadSession().setTransactionSuccessful(); - } finally { - releaseReference(); - } - } - - /** - * Returns true if the current thread has a transaction pending. - * - * @return True if the current thread is in a transaction. - */ - @Override - public boolean inTransaction() { - acquireReference(); - try { - return getThreadSession().hasTransaction(); - } finally { - releaseReference(); - } - } - - /** - * Returns true if the current thread is holding an active connection to the database. - *

- * The name of this method comes from a time when having an active connection - * to the database meant that the thread was holding an actual lock on the - * database. Nowadays, there is no longer a true "database lock" although threads - * may block if they cannot acquire a database connection to perform a - * particular operation. - *

- * - * @return True if the current thread is holding an active connection to the database. - */ - @Override - public boolean isDbLockedByCurrentThread() { - acquireReference(); - try { - return getThreadSession().hasConnection(); - } finally { - releaseReference(); - } - } - - /** - * Temporarily end the transaction to let other threads run. The transaction is assumed to be - * successful so far. Do not call setTransactionSuccessful before calling this. When this - * returns a new transaction will have been created but not marked as successful. This assumes - * that there are no nested transactions (beginTransaction has only been called once) and will - * throw an exception if that is not the case. - * @return true if the transaction was yielded - */ - @Override - public boolean yieldIfContendedSafely() { - return yieldIfContendedHelper(true /* check yielding */, -1 /* sleepAfterYieldDelay*/); - } - - /** - * Temporarily end the transaction to let other threads run. The transaction is assumed to be - * successful so far. Do not call setTransactionSuccessful before calling this. When this - * returns a new transaction will have been created but not marked as successful. This assumes - * that there are no nested transactions (beginTransaction has only been called once) and will - * throw an exception if that is not the case. - * @param sleepAfterYieldDelay if > 0, sleep this long before starting a new transaction if - * the lock was actually yielded. This will allow other background threads to make some - * more progress than they would if we started the transaction immediately. - * @return true if the transaction was yielded - */ - @Override - public boolean yieldIfContendedSafely(long sleepAfterYieldDelay) { - return yieldIfContendedHelper(true /* check yielding */, sleepAfterYieldDelay); - } - - private boolean yieldIfContendedHelper(boolean throwIfUnsafe, long sleepAfterYieldDelay) { - acquireReference(); - try { - return getThreadSession().yieldTransaction(sleepAfterYieldDelay, throwIfUnsafe, null); - } finally { - releaseReference(); - } - } - - /** - * Open the database according to the flags {@link OpenFlags} - * - *

Sets the locale of the database to the the system's current locale. - * Call {@link #setLocale} if you would like something else.

- * - * @param path to database file to open and/or create - * @param factory an optional factory class that is called to instantiate a - * cursor when query is called, or null for default - * @param flags to control database access mode - * @return the newly opened database - * @throws SQLiteException if the database cannot be opened - */ - public static SQLiteDatabase openDatabase(String path, - CursorFactory factory, - @OpenFlags int flags) { - return openDatabase(path, factory, flags, null); - } - - /** - * Open the database according to the flags {@link OpenFlags} - * - *

Sets the locale of the database to the the system's current locale. - * Call {@link #setLocale} if you would like something else.

- * - *

Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be - * used to handle corruption when sqlite reports database corruption.

- * - * @param path to database file to open and/or create - * @param factory an optional factory class that is called to instantiate a - * cursor when query is called, or null for default - * @param flags to control database access mode - * @param errorHandler the {@link DatabaseErrorHandler} obj to be used to handle corruption - * when sqlite reports database corruption - * @return the newly opened database - * @throws SQLiteException if the database cannot be opened - */ - public static SQLiteDatabase openDatabase(String path, - CursorFactory factory, - @OpenFlags int flags, - DatabaseErrorHandler errorHandler) { - SQLiteDatabaseConfiguration configuration = new SQLiteDatabaseConfiguration(path, flags); - SQLiteDatabase db = new SQLiteDatabase(configuration, factory, errorHandler); - db.open(); - return db; - } - - /** - * Open the database according to the given configuration. - * - *

Sets the locale of the database to the the system's current locale. - * Call {@link #setLocale} if you would like something else.

- * - *

Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be - * used to handle corruption when sqlite reports database corruption.

- * - * @param configuration to database configuration to use - * @param factory an optional factory class that is called to instantiate a - * cursor when query is called, or null for default - * @param errorHandler the {@link DatabaseErrorHandler} obj to be used to handle corruption - * when sqlite reports database corruption - * @return the newly opened database - * @throws SQLiteException if the database cannot be opened - */ - public static SQLiteDatabase openDatabase(SQLiteDatabaseConfiguration configuration, - CursorFactory factory, - DatabaseErrorHandler errorHandler) { - SQLiteDatabase db = new SQLiteDatabase(configuration, factory, errorHandler); - db.open(); - return db; - } - - /** - * Equivalent to openDatabase(file.getPath(), factory, CREATE_IF_NECESSARY). - */ - public static SQLiteDatabase openOrCreateDatabase(File file, CursorFactory factory) { - return openOrCreateDatabase(file.getPath(), factory); - } - - /** - * Equivalent to openDatabase(path, factory, CREATE_IF_NECESSARY). - */ - public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory) { - return openDatabase(path, factory, CREATE_IF_NECESSARY, null); - } - - /** - * Equivalent to openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler). - */ - public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory, - DatabaseErrorHandler errorHandler) { - return openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler); - } - - /** - * Deletes a database including its journal file and other auxiliary files - * that may have been created by the database engine. - * - * @param file The database file path. - * @return True if the database was successfully deleted. - */ - public static boolean deleteDatabase(File file) { - if (file == null) { - throw new IllegalArgumentException("file must not be null"); - } - - boolean deleted; - deleted = file.delete(); - deleted |= new File(file.getPath() + "-journal").delete(); - deleted |= new File(file.getPath() + "-shm").delete(); - deleted |= new File(file.getPath() + "-wal").delete(); - - File dir = file.getParentFile(); - if (dir != null) { - final String prefix = file.getName() + "-mj"; - final FileFilter filter = new FileFilter() { - @Override - public boolean accept(File candidate) { - return candidate.getName().startsWith(prefix); - } - }; - for (File masterJournal : dir.listFiles(filter)) { - deleted |= masterJournal.delete(); - } - } - return deleted; - } - - /** - * Reopens the database in read-write mode. - * If the database is already read-write, does nothing. - * - * @throws SQLiteException if the database could not be reopened as requested, in which - * case it remains open in read only mode. - * @throws IllegalStateException if the database is not open. - * - * @see #isReadOnly() - * @hide - */ - public void reopenReadWrite() { - synchronized (mLock) { - throwIfNotOpenLocked(); - - if (!isReadOnlyLocked()) { - return; // nothing to do - } - - // Reopen the database in read-write mode. - final int oldOpenFlags = mConfigurationLocked.openFlags; - mConfigurationLocked.openFlags = (mConfigurationLocked.openFlags & ~OPEN_READONLY); - try { - mConnectionPoolLocked.reconfigure(mConfigurationLocked); - } catch (RuntimeException ex) { - mConfigurationLocked.openFlags = oldOpenFlags; - throw ex; - } - } - } - - private void open() { - try { - if (!mConfigurationLocked.isInMemoryDb() - && (mConfigurationLocked.openFlags & OPEN_CREATE) != 0) { - ensureFile(mConfigurationLocked.path); - } - try { - openInner(); - } catch (SQLiteDatabaseCorruptException ex) { - onCorruption(); - openInner(); - } - } catch (SQLiteException ex) { - Log.e(TAG, "Failed to open database '" + getLabel() + "'.", ex); - close(); - throw ex; - } - } - - private static void ensureFile(String path) { - File file = new File(path); - if (!file.exists()) { - try { - if (!file.getParentFile().exists() && !file.getParentFile().mkdirs()) { - // Fixes #103: Check parent directory's existence before - // attempting to create. - Log.e(TAG, "Couldn't mkdirs " + file); - } - if (!file.createNewFile()) { - Log.e(TAG, "Couldn't create " + file); - } - } catch (IOException e) { - Log.e(TAG, "Couldn't ensure file " + file, e); - } - } - } - - private void openInner() { - synchronized (mLock) { - assert mConnectionPoolLocked == null; - mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked); - mCloseGuardLocked.open("close"); - } - - synchronized (sActiveDatabases) { - sActiveDatabases.put(this, null); - } - } - - /** - * Create a memory backed SQLite database. Its contents will be destroyed - * when the database is closed. - * - *

Sets the locale of the database to the the system's current locale. - * Call {@link #setLocale} if you would like something else.

- * - * @param factory an optional factory class that is called to instantiate a - * cursor when query is called - * @return a SQLiteDatabase object, or null if the database can't be created - */ - public static SQLiteDatabase create(CursorFactory factory) { - // This is a magic string with special meaning for SQLite. - return openDatabase(SQLiteDatabaseConfiguration.MEMORY_DB_PATH, - factory, CREATE_IF_NECESSARY); - } - - /** - * Registers a CustomFunction callback as a function that can be called from - * SQLite database triggers. - * - * @param name the name of the sqlite3 function - * @param numArgs the number of arguments for the function - * @param function callback to call when the function is executed - * @hide - */ - @Deprecated - public void addCustomFunction(String name, int numArgs, CustomFunction function) { - // Create wrapper (also validates arguments). - SQLiteCustomFunction wrapper = new SQLiteCustomFunction(name, numArgs, function); - - synchronized (mLock) { - throwIfNotOpenLocked(); - - mConfigurationLocked.customFunctions.add(wrapper); - try { - mConnectionPoolLocked.reconfigure(mConfigurationLocked); - } catch (RuntimeException ex) { - mConfigurationLocked.customFunctions.remove(wrapper); - throw ex; - } - } - } - - /** - * Registers a Function callback as a function that can be called from - * SQLite database triggers. - * - * @param name the name of the sqlite3 function - * @param numArgs the number of arguments for the function - * @param function callback to call when the function is executed - * @hide - */ - public void addFunction(String name, int numArgs, Function function) { - addFunction(name, numArgs, function, 0); - } - - /** - * Registers a Function callback as a function that can be called from - * SQLite database triggers. - * - * @param name the name of the sqlite3 function - * @param numArgs the number of arguments for the function - * @param function callback to call when the function is executed - * @param flags - * @hide - */ - public void addFunction(String name, int numArgs, Function function, int flags) { - // Create wrapper (also validates arguments). - SQLiteFunction wrapper = new SQLiteFunction(name, numArgs, function, flags); - - synchronized (mLock) { - throwIfNotOpenLocked(); - - mConfigurationLocked.functions.add(wrapper); - try { - mConnectionPoolLocked.reconfigure(mConfigurationLocked); - } catch (RuntimeException ex) { - mConfigurationLocked.functions.remove(wrapper); - throw ex; - } - } - } - - /** - * Gets the database version. - * - * @return the database version - */ - @Override - public int getVersion() { - return ((Long) longForQuery("PRAGMA user_version;", null)).intValue(); - } - - /** - * Sets the database version. - * - * @param version the new database version - */ - @Override - public void setVersion(int version) { - execSQL("PRAGMA user_version = " + version); - } - - /** - * Returns the maximum size the database may grow to. - * - * @return the new maximum database size - */ - @Override - public long getMaximumSize() { - long pageCount = longForQuery("PRAGMA max_page_count;", null); - return pageCount * getPageSize(); - } - - /** - * Sets the maximum size the database will grow to. The maximum size cannot - * be set below the current size. - * - * @param numBytes the maximum database size, in bytes - * @return the new maximum database size - */ - @Override - public long setMaximumSize(long numBytes) { - long pageSize = getPageSize(); - long numPages = numBytes / pageSize; - // If numBytes isn't a multiple of pageSize, bump up a page - if ((numBytes % pageSize) != 0) { - numPages++; - } - long newPageCount = longForQuery("PRAGMA max_page_count = " + numPages, null); - return newPageCount * pageSize; - } - - /** - * Returns the current database page size, in bytes. - * - * @return the database page size, in bytes - */ - @Override - public long getPageSize() { - return longForQuery("PRAGMA page_size;", null); - } - - /** - * Sets the database page size. The page size must be a power of two. This - * method does not work if any data has been written to the database file, - * and must be called right after the database has been created. - * - * @param numBytes the database page size, in bytes - */ - @Override - public void setPageSize(long numBytes) { - execSQL("PRAGMA page_size = " + numBytes); - } - - /** - * Finds the name of the first table, which is editable. - * - * @param tables a list of tables - * @return the first table listed - */ - public static String findEditTable(String tables) { - if (!TextUtils.isEmpty(tables)) { - // find the first word terminated by either a space or a comma - int spacepos = tables.indexOf(' '); - int commapos = tables.indexOf(','); - - if (spacepos > 0 && (spacepos < commapos || commapos < 0)) { - return tables.substring(0, spacepos); - } else if (commapos > 0 && (commapos < spacepos || spacepos < 0) ) { - return tables.substring(0, commapos); - } - return tables; - } else { - throw new IllegalStateException("Invalid tables"); - } - } - - /** - * Compiles an SQL statement into a reusable pre-compiled statement object. - * The parameters are identical to {@link #execSQL(String)}. You may put ?s in the - * statement and fill in those values with {@link SQLiteProgram#bindString} - * and {@link SQLiteProgram#bindLong} each time you want to run the - * statement. Statements may not return result sets larger than 1x1. - *

- * No two threads should be using the same {@link SQLiteStatement} at the same time. - * - * @param sql The raw SQL statement, may contain ? for unknown values to be - * bound later. - * @return A pre-compiled {@link SQLiteStatement} object. Note that - * {@link SQLiteStatement}s are not synchronized, see the documentation for more details. - */ - @Override - public SQLiteStatement compileStatement(String sql) throws SQLException { - acquireReference(); - try { - return new SQLiteStatement(this, sql, null); - } finally { - releaseReference(); - } - } - - /** - * Query the given URL, returning a {@link Cursor} over the result set. - * - * @param distinct true if you want each row to be unique, false otherwise. - * @param table The table name to compile the query against. - * @param columns A list of which columns to return. Passing null will - * return all columns, which is discouraged to prevent reading - * data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, formatted as an - * SQL WHERE clause (excluding the WHERE itself). Passing null - * will return all rows for the given table. - * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in order that they - * appear in the selection. - * @param groupBy A filter declaring how to group rows, formatted as an SQL - * GROUP BY clause (excluding the GROUP BY itself). Passing null - * will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in the cursor, - * if row grouping is being used, formatted as an SQL HAVING - * clause (excluding the HAVING itself). Passing null will cause - * all row groups to be included, and is required when row - * grouping is not being used. - * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause - * (excluding the ORDER BY itself). Passing null will use the - * default sort order, which may be unordered. - * @param limit Limits the number of rows returned by the query, - * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - * @see Cursor - */ - public Cursor query(boolean distinct, String table, String[] columns, - String selection, Object[] selectionArgs, String groupBy, - String having, String orderBy, String limit) { - return queryWithFactory(null, distinct, table, columns, selection, selectionArgs, - groupBy, having, orderBy, limit, null); - } - - /** - * Query the given URL, returning a {@link Cursor} over the result set. - * - * @param distinct true if you want each row to be unique, false otherwise. - * @param table The table name to compile the query against. - * @param columns A list of which columns to return. Passing null will - * return all columns, which is discouraged to prevent reading - * data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, formatted as an - * SQL WHERE clause (excluding the WHERE itself). Passing null - * will return all rows for the given table. - * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in order that they - * appear in the selection. - * @param groupBy A filter declaring how to group rows, formatted as an SQL - * GROUP BY clause (excluding the GROUP BY itself). Passing null - * will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in the cursor, - * if row grouping is being used, formatted as an SQL HAVING - * clause (excluding the HAVING itself). Passing null will cause - * all row groups to be included, and is required when row - * grouping is not being used. - * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause - * (excluding the ORDER BY itself). Passing null will use the - * default sort order, which may be unordered. - * @param limit Limits the number of rows returned by the query, - * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * If the operation is canceled, then {@link OperationCanceledException} will be thrown - * when the query is executed. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - * @see Cursor - */ - public Cursor query(boolean distinct, String table, String[] columns, - String selection, Object[] selectionArgs, String groupBy, - String having, String orderBy, String limit, CancellationSignal cancellationSignal) { - return queryWithFactory(null, distinct, table, columns, selection, selectionArgs, - groupBy, having, orderBy, limit, cancellationSignal); - } - - /** - * Query the given URL, returning a {@link Cursor} over the result set. - * - * @param cursorFactory the cursor factory to use, or null for the default factory - * @param distinct true if you want each row to be unique, false otherwise. - * @param table The table name to compile the query against. - * @param columns A list of which columns to return. Passing null will - * return all columns, which is discouraged to prevent reading - * data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, formatted as an - * SQL WHERE clause (excluding the WHERE itself). Passing null - * will return all rows for the given table. - * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in order that they - * appear in the selection. - * @param groupBy A filter declaring how to group rows, formatted as an SQL - * GROUP BY clause (excluding the GROUP BY itself). Passing null - * will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in the cursor, - * if row grouping is being used, formatted as an SQL HAVING - * clause (excluding the HAVING itself). Passing null will cause - * all row groups to be included, and is required when row - * grouping is not being used. - * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause - * (excluding the ORDER BY itself). Passing null will use the - * default sort order, which may be unordered. - * @param limit Limits the number of rows returned by the query, - * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - * @see Cursor - */ - public Cursor queryWithFactory(CursorFactory cursorFactory, - boolean distinct, String table, String[] columns, - String selection, Object[] selectionArgs, String groupBy, - String having, String orderBy, String limit) { - return queryWithFactory(cursorFactory, distinct, table, columns, selection, - selectionArgs, groupBy, having, orderBy, limit, null); - } - - /** - * Query the given URL, returning a {@link Cursor} over the result set. - * - * @param cursorFactory the cursor factory to use, or null for the default factory - * @param distinct true if you want each row to be unique, false otherwise. - * @param table The table name to compile the query against. - * @param columns A list of which columns to return. Passing null will - * return all columns, which is discouraged to prevent reading - * data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, formatted as an - * SQL WHERE clause (excluding the WHERE itself). Passing null - * will return all rows for the given table. - * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in order that they - * appear in the selection. - * @param groupBy A filter declaring how to group rows, formatted as an SQL - * GROUP BY clause (excluding the GROUP BY itself). Passing null - * will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in the cursor, - * if row grouping is being used, formatted as an SQL HAVING - * clause (excluding the HAVING itself). Passing null will cause - * all row groups to be included, and is required when row - * grouping is not being used. - * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause - * (excluding the ORDER BY itself). Passing null will use the - * default sort order, which may be unordered. - * @param limit Limits the number of rows returned by the query, - * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * If the operation is canceled, then {@link OperationCanceledException} will be thrown - * when the query is executed. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - * @see Cursor - */ - public Cursor queryWithFactory(CursorFactory cursorFactory, - boolean distinct, String table, String[] columns, - String selection, Object[] selectionArgs, String groupBy, - String having, String orderBy, String limit, CancellationSignal cancellationSignal) { - acquireReference(); - try { - String sql = SQLiteQueryBuilder.buildQueryString( - distinct, table, columns, selection, groupBy, having, orderBy, limit); - - return rawQueryWithFactory(cursorFactory, sql, selectionArgs, - findEditTable(table), cancellationSignal); - } finally { - releaseReference(); - } - } - - /** - * Query the given table, returning a {@link Cursor} over the result set. - * - * @param table The table name to compile the query against. - * @param columns A list of which columns to return. Passing null will - * return all columns, which is discouraged to prevent reading - * data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, formatted as an - * SQL WHERE clause (excluding the WHERE itself). Passing null - * will return all rows for the given table. - * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in order that they - * appear in the selection. - * @param groupBy A filter declaring how to group rows, formatted as an SQL - * GROUP BY clause (excluding the GROUP BY itself). Passing null - * will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in the cursor, - * if row grouping is being used, formatted as an SQL HAVING - * clause (excluding the HAVING itself). Passing null will cause - * all row groups to be included, and is required when row - * grouping is not being used. - * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause - * (excluding the ORDER BY itself). Passing null will use the - * default sort order, which may be unordered. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - * @see Cursor - */ - public Cursor query(String table, String[] columns, String selection, - Object[] selectionArgs, String groupBy, String having, - String orderBy) { - - return query(false, table, columns, selection, selectionArgs, groupBy, - having, orderBy, null /* limit */); - } - - /** - * Query the given table, returning a {@link Cursor} over the result set. - * - * @param table The table name to compile the query against. - * @param columns A list of which columns to return. Passing null will - * return all columns, which is discouraged to prevent reading - * data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, formatted as an - * SQL WHERE clause (excluding the WHERE itself). Passing null - * will return all rows for the given table. - * @param selectionArgs You may include ?s in selection, which will be - * replaced by the values from selectionArgs, in order that they - * appear in the selection. - * @param groupBy A filter declaring how to group rows, formatted as an SQL - * GROUP BY clause (excluding the GROUP BY itself). Passing null - * will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in the cursor, - * if row grouping is being used, formatted as an SQL HAVING - * clause (excluding the HAVING itself). Passing null will cause - * all row groups to be included, and is required when row - * grouping is not being used. - * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause - * (excluding the ORDER BY itself). Passing null will use the - * default sort order, which may be unordered. - * @param limit Limits the number of rows returned by the query, - * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - * @see Cursor - */ - public Cursor query(String table, String[] columns, String selection, - Object[] selectionArgs, String groupBy, String having, - String orderBy, String limit) { - - return query(false, table, columns, selection, selectionArgs, groupBy, - having, orderBy, limit); - } - - /** - * Runs the provided SQL and returns a {@link Cursor} over the result set. - * - * @param query the SQL query. The SQL string must not be ; terminated - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - */ - @Override - public Cursor query(String query) { - return rawQueryWithFactory(null, query, null, null, null); - } - - /** - * Runs the provided SQL and returns a {@link Cursor} over the result set. - * - * @param query the SQL query. The SQL string must not be ; terminated - * @param selectionArgs You may include ?s in where clause in the query, - * which will be replaced by the values from selectionArgs. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - */ - @Override - public Cursor query(String query, Object[] selectionArgs) { - return rawQueryWithFactory(null, query, selectionArgs, null, null); - } - - /** - * Runs the provided SQL and returns a {@link Cursor} over the result set. - * - * @param supportQuery the SQL query. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - */ - @Override - public Cursor query(final SupportSQLiteQuery supportQuery) { - return query(supportQuery, (CancellationSignal) null); - } - - /** - * Runs the provided SQL and returns a {@link Cursor} over the result set. - * - * @param supportQuery the SQL query. The SQL string must not be ; terminated - * @param signal A signal to cancel the operation in progress, or null if none. - * If the operation is canceled, then {@link OperationCanceledException} will be thrown - * when the query is executed. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - */ - @Override - @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) - public Cursor query(SupportSQLiteQuery supportQuery, android.os.CancellationSignal signal) { - if (signal != null) { - final CancellationSignal supportCancellationSignal = new CancellationSignal(); - signal.setOnCancelListener(new android.os.CancellationSignal.OnCancelListener() { - @Override - public void onCancel() { - supportCancellationSignal.cancel(); - } - }); - return query(supportQuery, supportCancellationSignal); - } else { - return query(supportQuery, (CancellationSignal) null); - } - } - - /** - * Runs the provided SQL and returns a {@link Cursor} over the result set. - * - * @param supportQuery the SQL query. The SQL string must not be ; terminated - * @param signal A signal to cancel the operation in progress, or null if none. - * If the operation is canceled, then {@link OperationCanceledException} will be thrown - * when the query is executed. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - */ - public Cursor query(final SupportSQLiteQuery supportQuery, CancellationSignal signal) { - return rawQueryWithFactory(new CursorFactory() { - @Override - public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, - String editTable, SQLiteQuery query) { - supportQuery.bindTo(query); - if (mCursorFactory == null) { - return new SQLiteCursor(masterQuery, editTable, query); - } else { - return mCursorFactory.newCursor(db, masterQuery, editTable, query); - } - } - }, supportQuery.getSql(), new String[0], null, signal); - } - - /** - * Runs the provided SQL and returns a {@link Cursor} over the result set. - * - * @param sql the SQL query. The SQL string must not be ; terminated - * @param selectionArgs You may include ?s in where clause in the query, - * which will be replaced by the values from selectionArgs. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - */ - public Cursor rawQuery(String sql, Object[] selectionArgs) { - return rawQueryWithFactory(null, sql, selectionArgs, null, null); - } - - /** - * Runs the provided SQL and returns a {@link Cursor} over the result set. - * - * @param sql the SQL query. The SQL string must not be ; terminated - * @param selectionArgs You may include ?s in where clause in the query, - * which will be replaced by the values from selectionArgs. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * If the operation is canceled, then {@link OperationCanceledException} will be thrown - * when the query is executed. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - */ - public Cursor rawQuery(String sql, Object[] selectionArgs, - CancellationSignal cancellationSignal) { - return rawQueryWithFactory(null, sql, selectionArgs, null, cancellationSignal); - } - - /** - * Runs the provided SQL and returns a cursor over the result set. - * - * @param cursorFactory the cursor factory to use, or null for the default factory - * @param sql the SQL query. The SQL string must not be ; terminated - * @param selectionArgs You may include ?s in where clause in the query, - * which will be replaced by the values from selectionArgs. - * @param editTable the name of the first table, which is editable - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - */ - public Cursor rawQueryWithFactory( - CursorFactory cursorFactory, String sql, Object[] selectionArgs, - String editTable) { - return rawQueryWithFactory(cursorFactory, sql, selectionArgs, editTable, null); - } - - /** - * Runs the provided SQL and returns a cursor over the result set. - * - * @param cursorFactory the cursor factory to use, or null for the default factory - * @param sql the SQL query. The SQL string must not be ; terminated - * @param selectionArgs You may include ?s in where clause in the query, - * which will be replaced by the values from selectionArgs. - * @param editTable the name of the first table, which is editable - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * If the operation is canceled, then {@link OperationCanceledException} will be thrown - * when the query is executed. - * @return A {@link Cursor} object, which is positioned before the first entry. Note that - * {@link Cursor}s are not synchronized, see the documentation for more details. - */ - public Cursor rawQueryWithFactory( - CursorFactory cursorFactory, String sql, Object[] selectionArgs, - String editTable, CancellationSignal cancellationSignal) { - acquireReference(); - try { - SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable, - cancellationSignal); - return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory, - selectionArgs); - } finally { - releaseReference(); - } - } - - /** - * Convenience method for inserting a row into the database. - * - * @param table the table to insert the row into - * @param nullColumnHack optional; may be null. - * SQL doesn't allow inserting a completely empty row without - * naming at least one column name. If your provided values is - * empty, no column names are known and an empty row can't be inserted. - * If not set to null, the nullColumnHack parameter - * provides the name of nullable column name to explicitly insert a NULL into - * in the case where your values is empty. - * @param values this map contains the initial column values for the - * row. The keys should be the column names and the values the - * column values - * @return the row ID of the newly inserted row, or -1 if an error occurred - */ - public long insert(String table, String nullColumnHack, ContentValues values) { - try { - return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); - } catch (SQLException e) { - Log.e(TAG, "Error inserting " + values, e); - return -1; - } - } - - /** - * Convenience method for inserting a row into the database. - * - * @param table the table to insert the row into - * @param nullColumnHack optional; may be null. - * SQL doesn't allow inserting a completely empty row without - * naming at least one column name. If your provided values is - * empty, no column names are known and an empty row can't be inserted. - * If not set to null, the nullColumnHack parameter - * provides the name of nullable column name to explicitly insert a NULL into - * in the case where your values is empty. - * @param values this map contains the initial column values for the - * row. The keys should be the column names and the values the - * column values - * @throws SQLException - * @return the row ID of the newly inserted row, or -1 if an error occurred - */ - public long insertOrThrow(String table, String nullColumnHack, ContentValues values) - throws SQLException { - return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); - } - - /** - * Convenience method for replacing a row in the database. - * - * @param table the table in which to replace the row - * @param nullColumnHack optional; may be null. - * SQL doesn't allow inserting a completely empty row without - * naming at least one column name. If your provided initialValues is - * empty, no column names are known and an empty row can't be inserted. - * If not set to null, the nullColumnHack parameter - * provides the name of nullable column name to explicitly insert a NULL into - * in the case where your initialValues is empty. - * @param initialValues this map contains the initial column values for - * the row. - * @return the row ID of the newly inserted row, or -1 if an error occurred - */ - public long replace(String table, String nullColumnHack, ContentValues initialValues) { - try { - return insertWithOnConflict(table, nullColumnHack, initialValues, - CONFLICT_REPLACE); - } catch (SQLException e) { - Log.e(TAG, "Error inserting " + initialValues, e); - return -1; - } - } - - /** - * Convenience method for replacing a row in the database. - * - * @param table the table in which to replace the row - * @param nullColumnHack optional; may be null. - * SQL doesn't allow inserting a completely empty row without - * naming at least one column name. If your provided initialValues is - * empty, no column names are known and an empty row can't be inserted. - * If not set to null, the nullColumnHack parameter - * provides the name of nullable column name to explicitly insert a NULL into - * in the case where your initialValues is empty. - * @param initialValues this map contains the initial column values for - * the row. The key - * @throws SQLException - * @return the row ID of the newly inserted row, or -1 if an error occurred - */ - public long replaceOrThrow(String table, String nullColumnHack, - ContentValues initialValues) throws SQLException { - return insertWithOnConflict(table, nullColumnHack, initialValues, - CONFLICT_REPLACE); - } - - /** - * General method for inserting a row into the database. - * - * @param table the table to insert the row into - * @param conflictAlgorithm for insert conflict resolver - * @param values this map contains the initial column values for the - * row. The keys should be the column names and the values the - * column values - * @return the row ID of the newly inserted row - * OR the primary key of the existing row if the input param 'conflictAlgorithm' = - * {@link #CONFLICT_IGNORE} - * OR -1 if any error - */ - @Override - public long insert(String table, @ConflictAlgorithm int conflictAlgorithm, - ContentValues values) throws SQLException { - return insertWithOnConflict(table, null, values, conflictAlgorithm); - } - - /** - * General method for inserting a row into the database. - * - * @param table the table to insert the row into - * @param nullColumnHack optional; may be null. - * SQL doesn't allow inserting a completely empty row without - * naming at least one column name. If your provided initialValues is - * empty, no column names are known and an empty row can't be inserted. - * If not set to null, the nullColumnHack parameter - * provides the name of nullable column name to explicitly insert a NULL into - * in the case where your initialValues is empty. - * @param initialValues this map contains the initial column values for the - * row. The keys should be the column names and the values the - * column values - * @param conflictAlgorithm for insert conflict resolver - * @return the row ID of the newly inserted row - * OR the primary key of the existing row if the input param 'conflictAlgorithm' = - * {@link #CONFLICT_IGNORE} - * OR -1 if any error - */ - @SuppressWarnings("StringConcatenationInsideStringBufferAppend") - public long insertWithOnConflict(String table, String nullColumnHack, - ContentValues initialValues, @ConflictAlgorithm int conflictAlgorithm) { - acquireReference(); - try { - StringBuilder sql = new StringBuilder(); - sql.append("INSERT"); - sql.append(CONFLICT_VALUES[conflictAlgorithm]); - sql.append(" INTO "); - sql.append(table); - sql.append('('); - - Object[] bindArgs = null; - int size = (initialValues != null && initialValues.size() > 0) - ? initialValues.size() : 0; - if (size > 0) { - bindArgs = new Object[size]; - int i = 0; - for (Map.Entry entry : initialValues.valueSet()) { - sql.append((i > 0) ? "," : ""); - sql.append(entry.getKey()); - bindArgs[i++] = entry.getValue(); - } - sql.append(')'); - sql.append(" VALUES ("); - for (i = 0; i < size; i++) { - sql.append((i > 0) ? ",?" : "?"); - } - } else { - sql.append(nullColumnHack + ") VALUES (NULL"); - } - sql.append(')'); - - SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); - try { - return statement.executeInsert(); - } finally { - statement.close(); - } - } finally { - releaseReference(); - } - } - - /** - * Convenience method for deleting rows in the database. - * - * @param table the table to delete from - * @param whereClause the optional WHERE clause to apply when deleting. - * Passing null will delete all rows. - * @param whereArgs You may include ?s in the where clause, which - * will be replaced by the values from whereArgs. The values - * will be bound as Strings. - * @return the number of rows affected if a whereClause is passed in, 0 - * otherwise. To remove all rows and get a count pass "1" as the - * whereClause. - */ - public int delete(String table, String whereClause, String[] whereArgs) { - acquireReference(); - try { - SQLiteStatement statement = new SQLiteStatement(this, "DELETE FROM " + table + - (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs); - try { - return statement.executeUpdateDelete(); - } finally { - statement.close(); - } - } finally { - releaseReference(); - } - } - - /** - * Convenience method for deleting rows in the database. - * - * @param table the table to delete from - * @param whereClause the optional WHERE clause to apply when deleting. - * Passing null will delete all rows. - * @param whereArgs You may include ?s in the where clause, which - * will be replaced by the values from whereArgs. The values - * will be bound as Strings. - * @return the number of rows affected if a whereClause is passed in, 0 - * otherwise. To remove all rows and get a count pass "1" as the - * whereClause. - */ - @Override - public int delete(String table, String whereClause, Object[] whereArgs) { - acquireReference(); - try { - SQLiteStatement statement = new SQLiteStatement(this, "DELETE FROM " + table + - (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs); - try { - return statement.executeUpdateDelete(); - } finally { - statement.close(); - } - } finally { - releaseReference(); - } - } - - /** - * Convenience method for updating rows in the database. - * - * @param table the table to update in - * @param values a map from column names to new column values. null is a - * valid value that will be translated to NULL. - * @param whereClause the optional WHERE clause to apply when updating. - * Passing null will update all rows. - * @param whereArgs You may include ?s in the where clause, which - * will be replaced by the values from whereArgs. The values - * will be bound as Strings. - * @return the number of rows affected - */ - public int update(String table, ContentValues values, String whereClause, String[] whereArgs) { - return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE); - } - - /** - * Convenience method for updating rows in the database. - * - * @param table the table to update in - * @param values a map from column names to new column values. null is a - * valid value that will be translated to NULL. - * @param whereClause the optional WHERE clause to apply when updating. - * Passing null will update all rows. - * @param whereArgs You may include ?s in the where clause, which - * will be replaced by the values from whereArgs. The values - * will be bound as Strings. - * @param conflictAlgorithm for update conflict resolver - * @return the number of rows affected - */ - @Override - public int update(String table, @ConflictAlgorithm int conflictAlgorithm, ContentValues values, - String whereClause, Object[] whereArgs) { - if (values == null || values.size() == 0) { - throw new IllegalArgumentException("Empty values"); - } - - acquireReference(); - try { - StringBuilder sql = new StringBuilder(120); - sql.append("UPDATE "); - sql.append(CONFLICT_VALUES[conflictAlgorithm]); - sql.append(table); - sql.append(" SET "); - - // move all bind args to one array - int setValuesSize = values.size(); - int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length); - Object[] bindArgs = new Object[bindArgsSize]; - int i = 0; - for (Map.Entry entry : values.valueSet()) { - sql.append((i > 0) ? "," : ""); - sql.append(entry.getKey()); - bindArgs[i++] = entry.getValue(); - sql.append("=?"); - } - if (whereArgs != null) { - for (i = setValuesSize; i < bindArgsSize; i++) { - bindArgs[i] = whereArgs[i - setValuesSize]; - } - } - if (!TextUtils.isEmpty(whereClause)) { - sql.append(" WHERE "); - sql.append(whereClause); - } - - SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); - try { - return statement.executeUpdateDelete(); - } finally { - statement.close(); - } - } finally { - releaseReference(); - } - } - - /** - * Convenience method for updating rows in the database. - * - * @param table the table to update in - * @param values a map from column names to new column values. null is a - * valid value that will be translated to NULL. - * @param whereClause the optional WHERE clause to apply when updating. - * Passing null will update all rows. - * @param whereArgs You may include ?s in the where clause, which - * will be replaced by the values from whereArgs. The values - * will be bound as Strings. - * @param conflictAlgorithm for update conflict resolver - * @return the number of rows affected - */ - public int updateWithOnConflict(String table, ContentValues values, - String whereClause, String[] whereArgs, @ConflictAlgorithm int conflictAlgorithm) { - if (values == null || values.size() == 0) { - throw new IllegalArgumentException("Empty values"); - } - - acquireReference(); - try { - StringBuilder sql = new StringBuilder(120); - sql.append("UPDATE "); - sql.append(CONFLICT_VALUES[conflictAlgorithm]); - sql.append(table); - sql.append(" SET "); - - // move all bind args to one array - int setValuesSize = values.size(); - int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length); - Object[] bindArgs = new Object[bindArgsSize]; - int i = 0; - for (Map.Entry entry : values.valueSet()) { - sql.append((i > 0) ? "," : ""); - sql.append(entry.getKey()); - bindArgs[i++] = entry.getValue(); - sql.append("=?"); - } - if (whereArgs != null) { - for (i = setValuesSize; i < bindArgsSize; i++) { - bindArgs[i] = whereArgs[i - setValuesSize]; - } - } - if (!TextUtils.isEmpty(whereClause)) { - sql.append(" WHERE "); - sql.append(whereClause); - } - - SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); - try { - return statement.executeUpdateDelete(); - } finally { - statement.close(); - } - } finally { - releaseReference(); - } - } - - /** - * Execute a single SQL statement that is NOT a SELECT - * or any other SQL statement that returns data. - *

- * It has no means to return any data (such as the number of affected rows). - * Instead, you're encouraged to use {@link #insert(String, String, ContentValues)}, - * {@link #update(String, ContentValues, String, String[])}, et al, when possible. - *

- *

- * When using {@link #enableWriteAheadLogging()}, journal_mode is - * automatically managed by this class. So, do not set journal_mode - * using "PRAGMA journal_mode'" statement if your app is using - * {@link #enableWriteAheadLogging()} - *

- * - * @param sql the SQL statement to be executed. Multiple statements separated by semicolons are - * not supported. - * @throws SQLException if the SQL string is invalid - */ - @Override - public void execSQL(String sql) throws SQLException { - executeSql(sql, null); - } - - /** - * Execute a single SQL statement that is NOT a SELECT/INSERT/UPDATE/DELETE. - *

- * For INSERT statements, use any of the following instead. - *

    - *
  • {@link #insert(String, String, ContentValues)}
  • - *
  • {@link #insertOrThrow(String, String, ContentValues)}
  • - *
  • {@link #insertWithOnConflict(String, String, ContentValues, int)}
  • - *
- *

- * For UPDATE statements, use any of the following instead. - *

    - *
  • {@link #update(String, ContentValues, String, String[])}
  • - *
  • {@link #updateWithOnConflict(String, ContentValues, String, String[], int)}
  • - *
- *

- * For DELETE statements, use any of the following instead. - *

    - *
  • {@link #delete(String, String, String[])}
  • - *
- *

- * For example, the following are good candidates for using this method: - *

    - *
  • ALTER TABLE
  • - *
  • CREATE or DROP table / trigger / view / index / virtual table
  • - *
  • REINDEX
  • - *
  • RELEASE
  • - *
  • SAVEPOINT
  • - *
  • PRAGMA that returns no data
  • - *
- *

- *

- * When using {@link #enableWriteAheadLogging()}, journal_mode is - * automatically managed by this class. So, do not set journal_mode - * using "PRAGMA journal_mode'" statement if your app is using - * {@link #enableWriteAheadLogging()} - *

- * - * @param sql the SQL statement to be executed. Multiple statements separated by semicolons are - * not supported. - * @param bindArgs only byte[], String, Long and Double are supported in bindArgs. - * @throws SQLException if the SQL string is invalid - */ - @Override - public void execSQL(String sql, Object[] bindArgs) throws SQLException { - if (bindArgs == null) { - throw new IllegalArgumentException("Empty bindArgs"); - } - executeSql(sql, bindArgs); - } - - private int executeSql(String sql, Object[] bindArgs) throws SQLException { - acquireReference(); - try { - SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs); - try { - return statement.executeUpdateDelete(); - } finally { - statement.close(); - } - } finally { - releaseReference(); - } - } - - /** - * Verifies that a SQL SELECT statement is valid by compiling it. - * If the SQL statement is not valid, this method will throw a {@link SQLiteException}. - * - * @param sql SQL to be validated - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * If the operation is canceled, then {@link OperationCanceledException} will be thrown - * when the query is executed. - * @throws SQLiteException if {@code sql} is invalid - */ - public void validateSql(@NonNull String sql, @Nullable CancellationSignal cancellationSignal) { - getThreadSession().prepare(sql, - getThreadDefaultConnectionFlags(true), cancellationSignal, null); - } - - /** - * Returns true if the database is opened as read only. - * - * @return True if database is opened as read only. - */ - @Override - public boolean isReadOnly() { - synchronized (mLock) { - return isReadOnlyLocked(); - } - } - - private boolean isReadOnlyLocked() { - return (mConfigurationLocked.openFlags & OPEN_READONLY) == OPEN_READONLY; - } - - /** - * Returns true if the database is in-memory db. - * - * @return True if the database is in-memory. - * @hide - */ - public boolean isInMemoryDatabase() { - synchronized (mLock) { - return mConfigurationLocked.isInMemoryDb(); - } - } - - /** - * Returns true if the database is currently open. - * - * @return True if the database is currently open (has not been closed). - */ - @Override - public boolean isOpen() { - synchronized (mLock) { - return mConnectionPoolLocked != null; - } - } - - /** - * Returns true if the new version code is greater than the current database version. - * - * @param newVersion The new version code. - * @return True if the new version code is greater than the current database version. - */ - @Override - public boolean needUpgrade(int newVersion) { - return newVersion > getVersion(); - } - - /** - * Gets the path to the database file. - * - * @return The path to the database file. - */ - @Override - public final String getPath() { - synchronized (mLock) { - return mConfigurationLocked.path; - } - } - - /** - * Sets the locale for this database. - * - * @param locale The new locale. - * - * @throws SQLException if the locale could not be set. The most common reason - * for this is that there is no collator available for the locale you requested. - * In this case the database remains unchanged. - */ - @Override - public void setLocale(Locale locale) { - if (locale == null) { - throw new IllegalArgumentException("locale must not be null."); - } - - synchronized (mLock) { - throwIfNotOpenLocked(); - - final Locale oldLocale = mConfigurationLocked.locale; - mConfigurationLocked.locale = locale; - try { - mConnectionPoolLocked.reconfigure(mConfigurationLocked); - } catch (RuntimeException ex) { - mConfigurationLocked.locale = oldLocale; - throw ex; - } - } - } - - /** - * Sets the maximum size of the prepared-statement cache for this database. - * (size of the cache = number of compiled-sql-statements stored in the cache). - *

- * Maximum cache size can ONLY be increased from its current size (default = 10). - * If this method is called with smaller size than the current maximum value, - * then IllegalStateException is thrown. - *

- * This method is thread-safe. - * - * @param cacheSize the size of the cache. can be (0 to {@link #MAX_SQL_CACHE_SIZE}) - * @throws IllegalStateException if input cacheSize > {@link #MAX_SQL_CACHE_SIZE}. - */ - @Override - public void setMaxSqlCacheSize(int cacheSize) { - if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) { - throw new IllegalStateException( - "expected value between 0 and " + MAX_SQL_CACHE_SIZE); - } - - synchronized (mLock) { - throwIfNotOpenLocked(); - - final int oldMaxSqlCacheSize = mConfigurationLocked.maxSqlCacheSize; - mConfigurationLocked.maxSqlCacheSize = cacheSize; - try { - mConnectionPoolLocked.reconfigure(mConfigurationLocked); - } catch (RuntimeException ex) { - mConfigurationLocked.maxSqlCacheSize = oldMaxSqlCacheSize; - throw ex; - } - } - } - - /** - * Sets whether foreign key constraints are enabled for the database. - *

- * By default, foreign key constraints are not enforced by the database. - * This method allows an application to enable foreign key constraints. - * It must be called each time the database is opened to ensure that foreign - * key constraints are enabled for the session. - *

- * A good time to call this method is right after calling {@link #openOrCreateDatabase} - * or in the {@link SQLiteOpenHelper#onConfigure} callback. - *

- * When foreign key constraints are disabled, the database does not check whether - * changes to the database will violate foreign key constraints. Likewise, when - * foreign key constraints are disabled, the database will not execute cascade - * delete or update triggers. As a result, it is possible for the database - * state to become inconsistent. To perform a database integrity check, - * call {@link #isDatabaseIntegrityOk}. - *

- * This method must not be called while a transaction is in progress. - *

- * See also SQLite Foreign Key Constraints - * for more details about foreign key constraint support. - *

- * - * @param enable True to enable foreign key constraints, false to disable them. - * - * @throws IllegalStateException if the are transactions is in progress - * when this method is called. - */ - @Override - public void setForeignKeyConstraintsEnabled(boolean enable) { - synchronized (mLock) { - throwIfNotOpenLocked(); - - if (mConfigurationLocked.foreignKeyConstraintsEnabled == enable) { - return; - } - - mConfigurationLocked.foreignKeyConstraintsEnabled = enable; - try { - mConnectionPoolLocked.reconfigure(mConfigurationLocked); - } catch (RuntimeException ex) { - mConfigurationLocked.foreignKeyConstraintsEnabled = !enable; - throw ex; - } - } - } - - /** - * This method enables parallel execution of queries from multiple threads on the - * same database. It does this by opening multiple connections to the database - * and using a different database connection for each query. The database - * journal mode is also changed to enable writes to proceed concurrently with reads. - *

- * When write-ahead logging is not enabled (the default), it is not possible for - * reads and writes to occur on the database at the same time. Before modifying the - * database, the writer implicitly acquires an exclusive lock on the database which - * prevents readers from accessing the database until the write is completed. - *

- * In contrast, when write-ahead logging is enabled (by calling this method), write - * operations occur in a separate log file which allows reads to proceed concurrently. - * While a write is in progress, readers on other threads will perceive the state - * of the database as it was before the write began. When the write completes, readers - * on other threads will then perceive the new state of the database. - *

- * It is a good idea to enable write-ahead logging whenever a database will be - * concurrently accessed and modified by multiple threads at the same time. - * However, write-ahead logging uses significantly more memory than ordinary - * journaling because there are multiple connections to the same database. - * So if a database will only be used by a single thread, or if optimizing - * concurrency is not very important, then write-ahead logging should be disabled. - *

- * After calling this method, execution of queries in parallel is enabled as long as - * the database remains open. To disable execution of queries in parallel, either - * call {@link #disableWriteAheadLogging} or close the database and reopen it. - *

- * The maximum number of connections used to execute queries in parallel is - * dependent upon the device memory and possibly other properties. - *

- * If a query is part of a transaction, then it is executed on the same database handle the - * transaction was begun. - *

- * Writers should use {@link #beginTransactionNonExclusive()} or - * {@link #beginTransactionWithListenerNonExclusive(SQLiteTransactionListener)} - * to start a transaction. Non-exclusive mode allows database file to be in readable - * by other threads executing queries. - *

- * If the database has any attached databases, then execution of queries in parallel is NOT - * possible. Likewise, write-ahead logging is not supported for read-only databases - * or memory databases. In such cases, {@link #enableWriteAheadLogging} returns false. - *

- * The best way to enable write-ahead logging is to pass the - * {@link #ENABLE_WRITE_AHEAD_LOGGING} flag to {@link #openDatabase}. This is - * more efficient than calling {@link #enableWriteAheadLogging}. - *

-     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
-     *             SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING,
-     *             myDatabaseErrorHandler);
-     *     db.enableWriteAheadLogging();
-     * 
- *

- * Another way to enable write-ahead logging is to call {@link #enableWriteAheadLogging} - * after opening the database. - *

-     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
-     *             SQLiteDatabase.CREATE_IF_NECESSARY, myDatabaseErrorHandler);
-     *     db.enableWriteAheadLogging();
-     * 
- *

- * See also SQLite Write-Ahead Logging for - * more details about how write-ahead logging works. - *

- * - * @return True if write-ahead logging is enabled. - * - * @throws IllegalStateException if there are transactions in progress at the - * time this method is called. WAL mode can only be changed when there are no - * transactions in progress. - * - * @see #ENABLE_WRITE_AHEAD_LOGGING - * @see #disableWriteAheadLogging - */ - @Override - public boolean enableWriteAheadLogging() { - synchronized (mLock) { - throwIfNotOpenLocked(); - - if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0) { - return true; - } - - if (isReadOnlyLocked()) { - // WAL doesn't make sense for readonly-databases. - // TODO: True, but connection pooling does still make sense... - return false; - } - - if (mConfigurationLocked.isInMemoryDb()) { - Log.i(TAG, "can't enable WAL for memory databases."); - return false; - } - - mConfigurationLocked.openFlags |= ENABLE_WRITE_AHEAD_LOGGING; - try { - mConnectionPoolLocked.reconfigure(mConfigurationLocked); - } catch (RuntimeException ex) { - mConfigurationLocked.openFlags &= ~ENABLE_WRITE_AHEAD_LOGGING; - throw ex; - } - } - return true; - } - - /** - * This method disables the features enabled by {@link #enableWriteAheadLogging()}. - * - * @throws IllegalStateException if there are transactions in progress at the - * time this method is called. WAL mode can only be changed when there are no - * transactions in progress. - * - * @see #enableWriteAheadLogging - */ - @Override - public void disableWriteAheadLogging() { - synchronized (mLock) { - throwIfNotOpenLocked(); - - if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) == 0) { - return; - } - - mConfigurationLocked.openFlags &= ~ENABLE_WRITE_AHEAD_LOGGING; - try { - mConnectionPoolLocked.reconfigure(mConfigurationLocked); - } catch (RuntimeException ex) { - mConfigurationLocked.openFlags |= ENABLE_WRITE_AHEAD_LOGGING; - throw ex; - } - } - } - - /** - * Returns true if write-ahead logging has been enabled for this database. - * - * @return True if write-ahead logging has been enabled for this database. - * - * @see #enableWriteAheadLogging - * @see #ENABLE_WRITE_AHEAD_LOGGING - */ - @Override - public boolean isWriteAheadLoggingEnabled() { - synchronized (mLock) { - throwIfNotOpenLocked(); - - return (mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0; - } - } - - /** - * Collect statistics about all open databases in the current process. - * Used by bug report. - */ - static ArrayList getDbStats() { - ArrayList dbStatsList = new ArrayList<>(); - for (SQLiteDatabase db : getActiveDatabases()) { - db.collectDbStats(dbStatsList); - } - return dbStatsList; - } - - private void collectDbStats(ArrayList dbStatsList) { - synchronized (mLock) { - if (mConnectionPoolLocked != null) { - mConnectionPoolLocked.collectDbStats(dbStatsList); - } - } - } - - private static ArrayList getActiveDatabases() { - ArrayList databases = new ArrayList<>(); - synchronized (sActiveDatabases) { - databases.addAll(sActiveDatabases.keySet()); - } - return databases; - } - - /** - * Dump detailed information about all open databases in the current process. - * Used by bug report. - */ - static void dumpAll(Printer printer, boolean verbose) { - for (SQLiteDatabase db : getActiveDatabases()) { - db.dump(printer, verbose); - } - } - - private void dump(Printer printer, boolean verbose) { - synchronized (mLock) { - if (mConnectionPoolLocked != null) { - printer.println(""); - mConnectionPoolLocked.dump(printer, verbose); - } - } - } - - /** - * Returns list of full pathnames of all attached databases including the main database - * by executing 'pragma database_list' on the database. - * - * @return ArrayList of pairs of (database name, database file path) or null if the database - * is not open. - */ - @Override - public List> getAttachedDbs() { - ArrayList> attachedDbs = new ArrayList<>(); - synchronized (mLock) { - if (mConnectionPoolLocked == null) { - return null; // not open - } - - acquireReference(); - } - - try { - // has attached databases. query sqlite to get the list of attached databases. - Cursor c = null; - try { - c = rawQuery("pragma database_list;", null); - while (c.moveToNext()) { - // sqlite returns a row for each database in the returned list of databases. - // in each row, - // 1st column is the database name such as main, or the database - // name specified on the "ATTACH" command - // 2nd column is the database file path. - attachedDbs.add(new Pair<>(c.getString(1), c.getString(2))); - } - } finally { - if (c != null) { - c.close(); - } - } - return attachedDbs; - } finally { - releaseReference(); - } - } - - /** - * Runs 'pragma integrity_check' on the given database (and all the attached databases) - * and returns true if the given database (and all its attached databases) pass integrity_check, - * false otherwise. - *

- * If the result is false, then this method logs the errors reported by the integrity_check - * command execution. - *

- * Note that 'pragma integrity_check' on a database can take a long time. - * - * @return true if the given database (and all its attached databases) pass integrity_check, - * false otherwise. - */ - @Override - public boolean isDatabaseIntegrityOk() { - acquireReference(); - try { - List> attachedDbs; - try { - attachedDbs = getAttachedDbs(); - if (attachedDbs == null) { - throw new IllegalStateException("databaselist for: " + getPath() + " couldn't " + - "be retrieved. probably because the database is closed"); - } - } catch (SQLiteException e) { - // can't get attachedDb list. do integrity check on the main database - attachedDbs = new ArrayList<>(); - attachedDbs.add(new Pair<>("main", getPath())); - } - - for (Pair p : attachedDbs) { - SQLiteStatement prog = null; - try { - prog = compileStatement("PRAGMA " + p.first + ".integrity_check(1);"); - String rslt = prog.simpleQueryForString(); - if (!rslt.equalsIgnoreCase("ok")) { - // integrity_checker failed on main or attached databases - Log.e(TAG, "PRAGMA integrity_check on " + p.second + " returned: " + rslt); - return false; - } - } finally { - if (prog != null) prog.close(); - } - } - } finally { - releaseReference(); - } - return true; - } - - @Override - public String toString() { - return "SQLiteDatabase: " + getPath(); - } - - private void throwIfNotOpenLocked() { - if (mConnectionPoolLocked == null) { - throw new IllegalStateException("The database '" + mConfigurationLocked.label - + "' is not open."); - } - } - - /** - * Used to allow returning sub-classes of {@link Cursor} when calling query. - */ - public interface CursorFactory { - /** - * See {@link SQLiteCursor#SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)}. - */ - Cursor newCursor(SQLiteDatabase db, - SQLiteCursorDriver masterQuery, String editTable, - SQLiteQuery query); - } - - /** - * A callback interface for a custom sqlite3 function. This can be used to create a function - * that can be called from sqlite3 database triggers. - * - * This interface is deprecated; new code should prefer {@link Function} - */ - @Deprecated - public interface CustomFunction { - /** - * Invoked whenever the function is called. - * @param args function arguments - * @return String value of the result or null - */ - String callback(String[] args); - } - - /** - * A callback interface for a custom sqlite3 function. This can be used to create a function - * that can be called from sqlite3 database triggers, or used in queries. - */ - public interface Function { - /** - * Flag that declares this function to be "deterministic," - * which means it may be used with Indexes on Expressions. - */ - public static final int FLAG_DETERMINISTIC = 0x800; - - interface Args { - byte[] getBlob(int arg); - String getString(int arg); - double getDouble(int arg); - int getInt(int arg); - long getLong(int arg); - } - - interface Result { - void set(byte[] value); - void set(double value); - void set(int value); - void set(long value); - void set(String value); - void setError(String error); - void setNull(); - } - - /** - * Invoked whenever the function is called. - * @param args function arguments - * @return String value of the result or null - */ - void callback(Args args, Result result); - } - - static boolean hasCodec() { - return SQLiteConnection.hasCodec(); - } - - void enableLocalizedCollators() { - mConnectionPoolLocked.enableLocalizedCollators(); - } - - /** - * Query the table for the number of rows in the table. - * @param table the name of the table to query - * @return the number of rows in the table - */ - public long queryNumEntries(String table) { - return queryNumEntries(table, null, null); - } - - /** - * Query the table for the number of rows in the table. - * @param table the name of the table to query - * @param selection A filter declaring which rows to return, - * formatted as an SQL WHERE clause (excluding the WHERE itself). - * Passing null will count all rows for the given table - * @return the number of rows in the table filtered by the selection - */ - public long queryNumEntries(String table, String selection) { - return queryNumEntries(table, selection, null); - } - - /** - * Query the table for the number of rows in the table. - * @param table the name of the table to query - * @param selection A filter declaring which rows to return, - * formatted as an SQL WHERE clause (excluding the WHERE itself). - * Passing null will count all rows for the given table - * @param selectionArgs You may include ?s in selection, - * which will be replaced by the values from selectionArgs, - * in order that they appear in the selection. - * The values will be bound as Strings. - * @return the number of rows in the table filtered by the selection - */ - public long queryNumEntries(String table, String selection, String[] selectionArgs) { - String s = (!TextUtils.isEmpty(selection)) ? " where " + selection : ""; - return longForQuery("select count(*) from " + table + s, selectionArgs); - } - - /** - * Utility method to run the query on the db and return the value in the - * first column of the first row. - */ - public long longForQuery(String query, String[] selectionArgs) { - SQLiteStatement prog = compileStatement(query); - try { - return longForQuery(prog, selectionArgs); - } finally { - prog.close(); - } - } - - /** - * Utility method to run the pre-compiled query and return the value in the - * first column of the first row. - */ - private static long longForQuery(SQLiteStatement prog, String[] selectionArgs) { - prog.bindAllArgsAsStrings(selectionArgs); - return prog.simpleQueryForLong(); - } - - /** - * Utility method to run the query on the db and return the value in the - * first column of the first row. - */ - public String stringForQuery(String query, String[] selectionArgs) { - SQLiteStatement prog = compileStatement(query); - try { - return stringForQuery(prog, selectionArgs); - } finally { - prog.close(); - } - } - - /** - * Utility method to run the pre-compiled query and return the value in the - * first column of the first row. - */ - public static String stringForQuery(SQLiteStatement prog, String[] selectionArgs) { - prog.bindAllArgsAsStrings(selectionArgs); - return prog.simpleQueryForString(); - } - - /** - * Utility method to run the query on the db and return the blob value in the - * first column of the first row. - * - * @return A read-only file descriptor for a copy of the blob value. - */ - public ParcelFileDescriptor blobFileDescriptorForQuery(String query, String[] selectionArgs) { - SQLiteStatement prog = compileStatement(query); - try { - return blobFileDescriptorForQuery(prog, selectionArgs); - } finally { - prog.close(); - } - } - - /** - * Utility method to run the pre-compiled query and return the blob value in the - * first column of the first row. - * - * @return A read-only file descriptor for a copy of the blob value. - */ - public static ParcelFileDescriptor blobFileDescriptorForQuery(SQLiteStatement prog, - String[] selectionArgs) { - prog.bindAllArgsAsStrings(selectionArgs); - return prog.simpleQueryForBlobFileDescriptor(); - } -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDatabaseConfiguration.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDatabaseConfiguration.java deleted file mode 100644 index 2087f2bb8e..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDatabaseConfiguration.java +++ /dev/null @@ -1,203 +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 - -package io.requery.android.database.sqlite; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.regex.Pattern; - -/** - * Describes how to configure a database. - *

- * The purpose of this object is to keep track of all of the little - * configuration settings that are applied to a database after it - * is opened so that they can be applied to all connections in the - * connection pool uniformly. - *

- * Each connection maintains its own copy of this object so it can - * keep track of which settings have already been applied. - *

- * - * @hide - */ -public final class SQLiteDatabaseConfiguration { - // The pattern we use to strip email addresses from database paths - // when constructing a label to use in log messages. - private static final Pattern EMAIL_IN_DB_PATTERN = - Pattern.compile("[\\w\\.\\-]+@[\\w\\.\\-]+"); - - /** - * Special path used by in-memory databases. - */ - public static final String MEMORY_DB_PATH = ":memory:"; - - /** - * The database path. - */ - public final String path; - - /** - * The label to use to describe the database when it appears in logs. - * This is derived from the path but is stripped to remove PII. - */ - public final String label; - - /** - * The flags used to open the database. - */ - public @SQLiteDatabase.OpenFlags int openFlags; - - /** - * The maximum size of the prepared statement cache for each database connection. - * Must be non-negative. - * - * Default is 25. - */ - public int maxSqlCacheSize; - - /** - * The database locale. - * - * Default is the value returned by {@link Locale#getDefault()}. - */ - public Locale locale; - - /** - * True if foreign key constraints are enabled. - * - * Default is false. - */ - public boolean foreignKeyConstraintsEnabled; - - /** - * The custom functions to register. - * - * This interface is deprecated; see {@link SQLiteFunction} - */ - @Deprecated - public final List customFunctions = new ArrayList<>(); - - /** - * The {@link SQLiteFunction}s to register. - */ - public final List functions = new ArrayList<>(); - - /** - * The custom extensions to register. - */ - public final List customExtensions = new ArrayList<>(); - - /** - * Creates a database configuration with the required parameters for opening a - * database and default values for all other parameters. - * - * @param path The database path. - * @param openFlags Open flags for the database, such as {@link SQLiteDatabase#OPEN_READWRITE}. - */ - public SQLiteDatabaseConfiguration(String path, @SQLiteDatabase.OpenFlags int openFlags) { - if (path == null) { - throw new IllegalArgumentException("path must not be null."); - } - - this.path = path; - label = stripPathForLogs(path); - this.openFlags = openFlags; - - // Set default values for optional parameters. - maxSqlCacheSize = 25; - locale = Locale.getDefault(); - } - - /** - * Creates a database configuration with the required parameters for opening a - * database and default values for all other parameters. - * - * @param path The database path. - * @param openFlags Open flags for the database, such as {@link SQLiteDatabase#OPEN_READWRITE}. - * @param functions custom functions to use. - * @param extensions custom extensions to use. - */ - public SQLiteDatabaseConfiguration(String path, - @SQLiteDatabase.OpenFlags int openFlags, - List customFunctions, - List functions, - List extensions) { - this(path, openFlags); - this.customFunctions.addAll(customFunctions); - this.customExtensions.addAll(extensions); - this.functions.addAll(functions); - } - - /** - * Creates a database configuration as a copy of another configuration. - * - * @param other The other configuration. - */ - SQLiteDatabaseConfiguration(SQLiteDatabaseConfiguration other) { - if (other == null) { - throw new IllegalArgumentException("other must not be null."); - } - - this.path = other.path; - this.label = other.label; - updateParametersFrom(other); - } - - /** - * Updates the non-immutable parameters of this configuration object - * from the other configuration object. - * - * @param other The object from which to copy the parameters. - */ - void updateParametersFrom(SQLiteDatabaseConfiguration other) { - if (other == null) { - throw new IllegalArgumentException("other must not be null."); - } - if (!path.equals(other.path)) { - throw new IllegalArgumentException("other configuration must refer to " - + "the same database."); - } - - openFlags = other.openFlags; - maxSqlCacheSize = other.maxSqlCacheSize; - locale = other.locale; - foreignKeyConstraintsEnabled = other.foreignKeyConstraintsEnabled; - customFunctions.clear(); - customFunctions.addAll(other.customFunctions); - customExtensions.clear(); - customExtensions.addAll(other.customExtensions); - functions.clear(); - functions.addAll(other.functions); - } - - /** - * Returns true if the database is in-memory. - * @return True if the database is in-memory. - */ - public boolean isInMemoryDb() { - return path.equalsIgnoreCase(MEMORY_DB_PATH); - } - - private static String stripPathForLogs(String path) { - if (path.indexOf('@') == -1) { - return path; - } - return EMAIL_IN_DB_PATTERN.matcher(path).replaceAll("XX@YY"); - } -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDebug.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDebug.java deleted file mode 100644 index 7a398894bb..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDebug.java +++ /dev/null @@ -1,173 +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 android.util.Log; -import android.util.Printer; - -import java.util.ArrayList; - -/** - * Provides debugging info about all SQLite databases running in the current process. - * - * {@hide} - */ -@SuppressWarnings("unused") -public final class SQLiteDebug { - private static native void nativeGetPagerStats(PagerStats stats); - - /** - * Controls the printing of informational SQL log messages. - * - * Enable using "adb shell setprop log.tag.SQLiteLog VERBOSE". - */ - public static final boolean DEBUG_SQL_LOG = - Log.isLoggable("SQLiteLog", Log.VERBOSE); - - /** - * Controls the printing of SQL statements as they are executed. - * - * Enable using "adb shell setprop log.tag.SQLiteStatements VERBOSE". - */ - public static final boolean DEBUG_SQL_STATEMENTS = - Log.isLoggable("SQLiteStatements", Log.VERBOSE); - - /** - * Controls the printing of wall-clock time taken to execute SQL statements - * as they are executed. - * - * Enable using "adb shell setprop log.tag.SQLiteTime VERBOSE". - */ - public static final boolean DEBUG_SQL_TIME = - Log.isLoggable("SQLiteTime", Log.VERBOSE); - - /** - * True to enable database performance testing instrumentation. - * @hide - */ - public static final boolean DEBUG_LOG_SLOW_QUERIES = false; - - private SQLiteDebug() { - } - - /** - * Determines whether a query should be logged. - * - * Reads the "db.log.slow_query_threshold" system property, which can be changed - * by the user at any time. If the value is zero, then all queries will - * be considered slow. If the value does not exist or is negative, then no queries will - * be considered slow. - * - * This value can be changed dynamically while the system is running. - * For example, "adb shell setprop db.log.slow_query_threshold 200" will - * log all queries that take 200ms or longer to run. - * @hide - */ - public static boolean shouldLogSlowQuery(long elapsedTimeMillis) { - int slowQueryMillis = Integer.parseInt( - System.getProperty("db.log.slow_query_threshold", "-1")); - return slowQueryMillis >= 0 && elapsedTimeMillis >= slowQueryMillis; - } - - /** - * Contains statistics about the active pagers in the current process. - * - * @see #nativeGetPagerStats(PagerStats) - */ - public static class PagerStats { - /** the current amount of memory checked out by sqlite using sqlite3_malloc(). - * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html - */ - public int memoryUsed; - - /** the number of bytes of page cache allocation which could not be sattisfied by the - * SQLITE_CONFIG_PAGECACHE buffer and where forced to overflow to sqlite3_malloc(). - * The returned value includes allocations that overflowed because they where too large - * (they were larger than the "sz" parameter to SQLITE_CONFIG_PAGECACHE) and allocations - * that overflowed because no space was left in the page cache. - * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html - */ - public int pageCacheOverflow; - - /** records the largest memory allocation request handed to sqlite3. - * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html - */ - public int largestMemAlloc; - - /** a list of {@link DbStats} - one for each main database opened by the applications - * running on the android device - */ - public ArrayList dbStats; - } - - /** - * contains statistics about a database - */ - public static class DbStats { - /** name of the database */ - public String dbName; - - /** the page size for the database */ - public long pageSize; - - /** the database size */ - public long dbSize; - - /** documented here http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html */ - public int lookaside; - - /** statement cache stats: hits/misses/cachesize */ - public String cache; - - public DbStats(String dbName, long pageCount, long pageSize, int lookaside, - int hits, int misses, int cachesize) { - this.dbName = dbName; - this.pageSize = pageSize / 1024; - dbSize = (pageCount * pageSize) / 1024; - this.lookaside = lookaside; - this.cache = hits + "/" + misses + "/" + cachesize; - } - } - - /** - * return all pager and database stats for the current process. - * @return {@link PagerStats} - */ - public static PagerStats getDatabaseInfo() { - PagerStats stats = new PagerStats(); - nativeGetPagerStats(stats); - stats.dbStats = SQLiteDatabase.getDbStats(); - return stats; - } - - /** - * Dumps detailed information about all databases used by the process. - * @param printer The printer for dumping database state. - * @param args Command-line arguments supplied to dumpsys dbinfo - */ - public static void dump(Printer printer, String[] args) { - boolean verbose = false; - for (String arg : args) { - if (arg.equals("-v")) { - verbose = true; - } - } - - SQLiteDatabase.dumpAll(printer, verbose); - } -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDirectCursorDriver.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDirectCursorDriver.java deleted file mode 100644 index 1b69858c10..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteDirectCursorDriver.java +++ /dev/null @@ -1,85 +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 android.database.Cursor; -import androidx.core.os.CancellationSignal; - -/** - * A cursor driver that uses the given query directly. - * - * @hide - */ -public final class SQLiteDirectCursorDriver implements SQLiteCursorDriver { - private final SQLiteDatabase mDatabase; - private final String mEditTable; - private final String mSql; - private final CancellationSignal mCancellationSignal; - private SQLiteQuery mQuery; - - public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable, - CancellationSignal cancellationSignal) { - mDatabase = db; - mEditTable = editTable; - mSql = sql; - mCancellationSignal = cancellationSignal; - } - - public Cursor query(SQLiteDatabase.CursorFactory factory, Object[] selectionArgs) { - SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, selectionArgs, mCancellationSignal); - final Cursor cursor; - try { - if (factory == null) { - cursor = new SQLiteCursor(this, mEditTable, query); - } else { - cursor = factory.newCursor(mDatabase, this, mEditTable, query); - } - } catch (RuntimeException ex) { - query.close(); - throw ex; - } - - mQuery = query; - return cursor; - } - - @Override - public void cursorClosed() { - // Do nothing - } - - @Override - public void setBindArguments(String[] bindArgs) { - mQuery.bindAllArgsAsStrings(bindArgs); - } - - @Override - public void cursorDeactivated() { - // Do nothing - } - - @Override - public void cursorRequeried(Cursor cursor) { - // Do nothing - } - - @Override - public String toString() { - return "SQLiteDirectCursorDriver: " + mSql; - } -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteFunction.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteFunction.java deleted file mode 100644 index d9fbcb6b18..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteFunction.java +++ /dev/null @@ -1,185 +0,0 @@ -package io.requery.android.database.sqlite; - -/** - * @author dhleong - */ -public class SQLiteFunction { - public final String name; - public final int numArgs; - public final SQLiteDatabase.Function callback; - - // accessed from native code - final int flags; - - // NOTE: from a single database connection, all calls to - // functions are serialized by SQLITE-internal mutexes, - // so we save on GC churn by reusing a single, shared instance - private final MyArgs args = new MyArgs(); - private final MyResult result = new MyResult(); - - /** - * Create custom function. - * - * @param name The name of the sqlite3 function. - * @param numArgs The number of arguments for the function, or -1 to - * support any number of arguments. - * @param callback The callback to invoke when the function is executed. - * @param flags Extra SQLITE flags to pass when creating the function - * in native code. - */ - public SQLiteFunction(String name, int numArgs, - SQLiteDatabase.Function callback) { - this(name, numArgs, callback, 0); - } - - /** - * Create custom function. - * - * @param name The name of the sqlite3 function. - * @param numArgs The number of arguments for the function, or -1 to - * support any number of arguments. - * @param callback The callback to invoke when the function is executed. - * @param flags Extra SQLITE flags to pass when creating the function - * in native code. - */ - public SQLiteFunction(String name, int numArgs, - SQLiteDatabase.Function callback, - int flags) { - if (name == null) { - throw new IllegalArgumentException("name must not be null."); - } - - this.name = name; - this.numArgs = numArgs; - this.callback = callback; - this.flags = flags; - } - - // Called from native. - @SuppressWarnings("unused") - private void dispatchCallback(long contextPtr, long argsPtr, int argsCount) { - result.contextPtr = contextPtr; - args.argsPtr = argsPtr; - args.argsCount = argsCount; - - try { - callback.callback(args, result); - - if (!result.isSet) { - result.setNull(); - } - - } finally { - result.contextPtr = 0; - result.isSet = false; - args.argsPtr = 0; - args.argsCount = 0; - } - } - - static native byte[] nativeGetArgBlob(long argsPtr, int arg); - static native String nativeGetArgString(long argsPtr, int arg); - static native double nativeGetArgDouble(long argsPtr, int arg); - static native int nativeGetArgInt(long argsPtr, int arg); - static native long nativeGetArgLong(long argsPtr, int arg); - - static native void nativeSetResultBlob(long contextPtr, byte[] result); - static native void nativeSetResultString(long contextPtr, String result); - static native void nativeSetResultDouble(long contextPtr, double result); - static native void nativeSetResultInt(long contextPtr, int result); - static native void nativeSetResultLong(long contextPtr, long result); - static native void nativeSetResultError(long contextPtr, String error); - static native void nativeSetResultNull(long contextPtr); - - private static class MyArgs implements SQLiteDatabase.Function.Args { - long argsPtr; - int argsCount; - - @Override - public byte[] getBlob(int arg) { - return nativeGetArgBlob(argsPtr, checkArg(arg)); - } - - @Override - public String getString(int arg) { - return nativeGetArgString(argsPtr, checkArg(arg)); - } - - @Override - public double getDouble(int arg) { - return nativeGetArgDouble(argsPtr, checkArg(arg)); - } - - @Override - public int getInt(int arg) { - return nativeGetArgInt(argsPtr, checkArg(arg)); - } - - @Override - public long getLong(int arg) { - return nativeGetArgLong(argsPtr, checkArg(arg)); - } - - private int checkArg(int arg) { - if (arg < 0 || arg >= argsCount) { - throw new IllegalArgumentException( - "Requested arg " + arg + " but had " + argsCount - ); - } - - return arg; - } - } - - private static class MyResult implements SQLiteDatabase.Function.Result { - long contextPtr; - boolean isSet; - - @Override - public void set(byte[] value) { - checkSet(); - nativeSetResultBlob(contextPtr, value); - } - - @Override - public void set(double value) { - checkSet(); - nativeSetResultDouble(contextPtr, value); - } - - @Override - public void set(int value) { - checkSet(); - nativeSetResultInt(contextPtr, value); - } - - @Override - public void set(long value) { - checkSet(); - nativeSetResultLong(contextPtr, value); - } - - @Override - public void set(String value) { - checkSet(); - nativeSetResultString(contextPtr, value); - } - - @Override - public void setError(String error) { - checkSet(); - nativeSetResultError(contextPtr, error); - } - - @Override - public void setNull() { - checkSet(); - nativeSetResultNull(contextPtr); - } - - private void checkSet() { - if (isSet) throw new IllegalStateException("Result is already set"); - isSet = true; - } - } -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteGlobal.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteGlobal.java deleted file mode 100644 index 6e1281affe..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteGlobal.java +++ /dev/null @@ -1,116 +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.os.StatFs; - -/** - * Provides access to SQLite functions that affect all database connection, - * such as memory management. - * - * The native code associated with SQLiteGlobal is also sets global configuration options - * using sqlite3_config() then calls sqlite3_initialize() to ensure that the SQLite - * library is properly initialized exactly once before any other framework or application - * code has a chance to run. - * - * Verbose SQLite logging is enabled if the "log.tag.SQLiteLog" property is set to "V". - * (per {@link SQLiteDebug#DEBUG_SQL_LOG}). - * - * @hide - */ -public final class SQLiteGlobal { - private static final Object sLock = new Object(); - private static int sDefaultPageSize; - - private static native int nativeReleaseMemory(); - - private SQLiteGlobal() { - } - - /** - * Attempts to release memory by pruning the SQLite page cache and other - * internal data structures. - * - * @return The number of bytes that were freed. - */ - public static int releaseMemory() { - return nativeReleaseMemory(); - } - - // values derived from: - // https://android.googlesource.com/platform/frameworks/base.git/+/master/core/res/res/values/config.xml - - /** - * Gets the default page size to use when creating a database. - */ - @SuppressWarnings("deprecation") - public static int getDefaultPageSize() { - synchronized (sLock) { - if (sDefaultPageSize == 0) { - sDefaultPageSize = new StatFs("/data").getBlockSize(); - } - return 1024; - } - } - - /** - * Gets the default journal mode when WAL is not in use. - */ - public static String getDefaultJournalMode() { - return "TRUNCATE"; - } - - /** - * Gets the journal size limit in bytes. - */ - public static int getJournalSizeLimit() { - return 524288; - } - - /** - * Gets the default database synchronization mode when WAL is not in use. - */ - public static String getDefaultSyncMode() { - return "FULL"; - } - - /** - * Gets the database synchronization mode when in WAL mode. - */ - public static String getWALSyncMode() { - return "normal"; - } - - /** - * Gets the WAL auto-checkpoint integer in database pages. - */ - public static int getWALAutoCheckpoint() { - return 1000; - } - - /** - * Gets the connection pool size when in WAL mode. - */ - public static int getWALConnectionPoolSize() { - return 10; - } -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteOpenHelper.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteOpenHelper.java deleted file mode 100644 index dd5e4d0fcb..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteOpenHelper.java +++ /dev/null @@ -1,410 +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 android.content.Context; -import android.database.sqlite.SQLiteException; -import android.util.Log; -import androidx.sqlite.db.SupportSQLiteOpenHelper; -import io.requery.android.database.DatabaseErrorHandler; - -/** - * A helper class to manage database creation and version management. - * - *

You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and - * optionally {@link #onOpen}, and this class takes care of opening the database - * if it exists, creating it if it does not, and upgrading it as necessary. - * Transactions are used to make sure the database is always in a sensible state. - * - *

This class makes it easy for {@link android.content.ContentProvider} - * implementations to defer opening and upgrading the database until first use, - * to avoid blocking application startup with long-running database upgrades. - * - *

For an example, see the NotePadProvider class in the NotePad sample application, - * in the samples/ directory of the SDK.

- * - *

Note: this class assumes - * monotonically increasing version numbers for upgrades.

- */ -@SuppressWarnings("unused") -public abstract class SQLiteOpenHelper implements SupportSQLiteOpenHelper { - private static final String TAG = SQLiteOpenHelper.class.getSimpleName(); - - // When true, getReadableDatabase returns a read-only database if it is just being opened. - // The database handle is reopened in read/write mode when getWritableDatabase is called. - // We leave this behavior disabled in production because it is inefficient and breaks - // many applications. For debugging purposes it can be useful to turn on strict - // read-only semantics to catch applications that call getReadableDatabase when they really - // wanted getWritableDatabase. - private static final boolean DEBUG_STRICT_READONLY = false; - - private final Context mContext; - private final String mName; - private final SQLiteDatabase.CursorFactory mFactory; - private final int mNewVersion; - - private SQLiteDatabase mDatabase; - private boolean mIsInitializing; - private boolean mEnableWriteAheadLogging; - private final DatabaseErrorHandler mErrorHandler; - - /** - * Create a helper object to create, open, and/or manage a database. - * This method always returns very quickly. The database is not actually - * created or opened until one of {@link #getWritableDatabase} or - * {@link #getReadableDatabase} is called. - * - * @param context to use to open or create the database - * @param name of the database file, or null for an in-memory database - * @param factory to use for creating cursor objects, or null for the default - * @param version number of the database (starting at 1); if the database is older, - * {@link #onUpgrade} will be used to upgrade the database; if the database is - * newer, {@link #onDowngrade} will be used to downgrade the database - */ - public SQLiteOpenHelper(Context context, - String name, - SQLiteDatabase.CursorFactory factory, - int version) { - this(context, name, factory, version, null); - } - - /** - * Create a helper object to create, open, and/or manage a database. - * The database is not actually created or opened until one of - * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called. - * - *

Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be - * used to handle corruption when sqlite reports database corruption.

- * - * @param context to use to open or create the database - * @param name of the database file, or null for an in-memory database - * @param factory to use for creating cursor objects, or null for the default - * @param version number of the database (starting at 1); if the database is older, - * {@link #onUpgrade} will be used to upgrade the database; if the database is - * newer, {@link #onDowngrade} will be used to downgrade the database - * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database - * corruption, or null to use the default error handler. - */ - public SQLiteOpenHelper(Context context, String name, - SQLiteDatabase.CursorFactory factory, - int version, - DatabaseErrorHandler errorHandler) { - if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version); - - mContext = context; - mName = name; - mFactory = factory; - mNewVersion = version; - mErrorHandler = errorHandler; - } - - /** - * Return the name of the SQLite database being opened, as given to - * the constructor. - */ - @Override - public String getDatabaseName() { - return mName; - } - - /** - * Enables or disables the use of write-ahead logging for the database. - * - * Write-ahead logging cannot be used with read-only databases so the value of - * this flag is ignored if the database is opened read-only. - * - * @param enabled True if write-ahead logging should be enabled, false if it - * should be disabled. - * - * @see SQLiteDatabase#enableWriteAheadLogging() - */ - @Override - public void setWriteAheadLoggingEnabled(boolean enabled) { - synchronized (this) { - if (mEnableWriteAheadLogging != enabled) { - if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) { - if (enabled) { - mDatabase.enableWriteAheadLogging(); - } else { - mDatabase.disableWriteAheadLogging(); - } - } - mEnableWriteAheadLogging = enabled; - } - } - } - - /** - * Create and/or open a database that will be used for reading and writing. - * The first time this is called, the database will be opened and - * {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be - * called. - * - *

Once opened successfully, the database is cached, so you can - * call this method every time you need to write to the database. - * (Make sure to call {@link #close} when you no longer need the database.) - * Errors such as bad permissions or a full disk may cause this method - * to fail, but future attempts may succeed if the problem is fixed.

- * - *

Database upgrade may take a long time, you - * should not call this method from the application main thread, including - * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}. - * - * @throws SQLiteException if the database cannot be opened for writing - * @return a read/write database object valid until {@link #close} is called - */ - @Override - public SQLiteDatabase getWritableDatabase() { - synchronized (this) { - return getDatabaseLocked(true); - } - } - - /** - * Create and/or open a database. This will be the same object returned by - * {@link #getWritableDatabase} unless some problem, such as a full disk, - * requires the database to be opened read-only. In that case, a read-only - * database object will be returned. If the problem is fixed, a future call - * to {@link #getWritableDatabase} may succeed, in which case the read-only - * database object will be closed and the read/write object will be returned - * in the future. - * - *

Like {@link #getWritableDatabase}, this method may - * take a long time to return, so you should not call it from the - * application main thread, including from - * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}. - * - * @throws SQLiteException if the database cannot be opened - * @return a database object valid until {@link #getWritableDatabase} - * or {@link #close} is called. - */ - @Override - public SQLiteDatabase getReadableDatabase() { - synchronized (this) { - return getDatabaseLocked(false); - } - } - - private SQLiteDatabase getDatabaseLocked(boolean writable) { - if (mDatabase != null) { - if (!mDatabase.isOpen()) { - // Darn! The user closed the database by calling mDatabase.close(). - mDatabase = null; - } else if (!writable || !mDatabase.isReadOnly()) { - // The database is already open for business. - return mDatabase; - } - } - - if (mIsInitializing) { - throw new IllegalStateException("getDatabase called recursively"); - } - - SQLiteDatabase db = mDatabase; - try { - mIsInitializing = true; - - if (db != null) { - if (db.isReadOnly()) { - db.reopenReadWrite(); - } - } else if (mName == null) { - db = SQLiteDatabase.create(null); - } else { - try { - final String path = mContext.getDatabasePath(mName).getPath(); - if (DEBUG_STRICT_READONLY && !writable) { - SQLiteDatabaseConfiguration configuration = - createConfiguration(path, SQLiteDatabase.OPEN_READONLY); - db = SQLiteDatabase.openDatabase(configuration, mFactory, mErrorHandler); - } else { - int flags = mEnableWriteAheadLogging ? - SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING : 0; - flags |= SQLiteDatabase.CREATE_IF_NECESSARY; - SQLiteDatabaseConfiguration configuration = - createConfiguration(path, flags); - db = SQLiteDatabase.openDatabase(configuration, mFactory, mErrorHandler); - } - } catch (SQLiteException ex) { - if (writable) { - throw ex; - } - Log.e(TAG, "Couldn't open " + mName - + " for writing (will try read-only):", ex); - final String path = mContext.getDatabasePath(mName).getPath(); - SQLiteDatabaseConfiguration configuration = - createConfiguration(path, SQLiteDatabase.OPEN_READONLY); - db = SQLiteDatabase.openDatabase(configuration, mFactory, mErrorHandler); - } - } - - onConfigure(db); - - final int version = db.getVersion(); - if (version != mNewVersion) { - if (db.isReadOnly()) { - throw new SQLiteException("Can't upgrade read-only database from version " + - db.getVersion() + " to " + mNewVersion + ": " + mName); - } - - db.beginTransaction(); - try { - if (version == 0) { - onCreate(db); - } else { - if (version > mNewVersion) { - onDowngrade(db, version, mNewVersion); - } else { - onUpgrade(db, version, mNewVersion); - } - } - db.setVersion(mNewVersion); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - onOpen(db); - - if (db.isReadOnly()) { - Log.w(TAG, "Opened " + mName + " in read-only mode"); - } - - mDatabase = db; - return db; - } finally { - mIsInitializing = false; - if (db != null && db != mDatabase) { - db.close(); - } - } - } - - /** - * Close any open database object. - */ - @Override - public synchronized void close() { - if (mIsInitializing) throw new IllegalStateException("Closed during initialization"); - - if (mDatabase != null && mDatabase.isOpen()) { - mDatabase.close(); - mDatabase = null; - } - } - - /** - * Called when the database connection is being configured, to enable features - * such as write-ahead logging or foreign key support. - *

- * This method is called before {@link #onCreate}, {@link #onUpgrade}, - * {@link #onDowngrade}, or {@link #onOpen} are called. It should not modify - * the database except to configure the database connection as required. - *

- * This method should only call methods that configure the parameters of the - * database connection, such as {@link SQLiteDatabase#enableWriteAheadLogging} - * {@link SQLiteDatabase#setForeignKeyConstraintsEnabled}, - * {@link SQLiteDatabase#setLocale}, {@link SQLiteDatabase#setMaximumSize}, - * or executing PRAGMA statements. - *

- * - * @param db The database. - */ - public void onConfigure(SQLiteDatabase db) {} - - /** - * Called when the database is created for the first time. This is where the - * creation of tables and the initial population of the tables should happen. - * - * @param db The database. - */ - public abstract void onCreate(SQLiteDatabase db); - - /** - * Called when the database needs to be upgraded. The implementation - * should use this method to drop tables, add tables, or do anything else it - * needs to upgrade to the new schema version. - * - *

- * The SQLite ALTER TABLE documentation can be found - * here. If you add new columns - * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns - * you can use ALTER TABLE to rename the old table, then create the new table and then - * populate the new table with the contents of the old table. - *

- * This method executes within a transaction. If an exception is thrown, all changes - * will automatically be rolled back. - *

- * - * @param db The database. - * @param oldVersion The old database version. - * @param newVersion The new database version. - */ - public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion); - - /** - * Called when the database needs to be downgraded. This is strictly similar to - * {@link #onUpgrade} method, but is called whenever current version is newer than requested one. - * However, this method is not abstract, so it is not mandatory for a customer to - * implement it. If not overridden, default implementation will reject downgrade and - * throws SQLiteException - * - *

- * This method executes within a transaction. If an exception is thrown, all changes - * will automatically be rolled back. - *

- * - * @param db The database. - * @param oldVersion The old database version. - * @param newVersion The new database version. - */ - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { - throw new SQLiteException("Can't downgrade database from version " + - oldVersion + " to " + newVersion); - } - - /** - * Called when the database has been opened. The implementation - * should check {@link SQLiteDatabase#isReadOnly} before updating the - * database. - *

- * This method is called after the database connection has been configured - * and after the database schema has been created, upgraded or downgraded as necessary. - * If the database connection must be configured in some way before the schema - * is created, upgraded, or downgraded, do it in {@link #onConfigure} instead. - *

- * - * @param db The database. - */ - public void onOpen(SQLiteDatabase db) {} - - /** - * Called before the database is opened. Provides the {@link SQLiteDatabaseConfiguration} - * instance that is used to initialize the database. Override this to create a configuration - * that has custom functions or extensions. - * - * @param path to database file to open and/or create - * @param openFlags to control database access mode - * @return {@link SQLiteDatabaseConfiguration} instance, cannot be null. - */ - protected SQLiteDatabaseConfiguration createConfiguration(String path, - @SQLiteDatabase.OpenFlags int openFlags) { - return new SQLiteDatabaseConfiguration(path, openFlags); - } -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteProgram.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteProgram.java deleted file mode 100644 index 2399023ed5..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteProgram.java +++ /dev/null @@ -1,246 +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.sqlite; - -import androidx.core.os.CancellationSignal; -import androidx.sqlite.db.SupportSQLiteProgram; - -import java.util.Arrays; - -/** - * A base class for compiled SQLite programs. - *

- * This class is not thread-safe. - *

- */ -@SuppressWarnings("unused") -public abstract class SQLiteProgram extends SQLiteClosable implements SupportSQLiteProgram { - private static final String[] EMPTY_STRING_ARRAY = new String[0]; - - private final SQLiteDatabase mDatabase; - private final String mSql; - private final boolean mReadOnly; - private final String[] mColumnNames; - private final int mNumParameters; - private final Object[] mBindArgs; - - SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs, - CancellationSignal cancellationSignalForPrepare) { - mDatabase = db; - mSql = sql.trim(); - - int n = SQLiteStatementType.getSqlStatementType(mSql); - switch (n) { - case SQLiteStatementType.STATEMENT_BEGIN: - case SQLiteStatementType.STATEMENT_COMMIT: - case SQLiteStatementType.STATEMENT_ABORT: - mReadOnly = false; - mColumnNames = EMPTY_STRING_ARRAY; - mNumParameters = 0; - break; - - default: - boolean assumeReadOnly = (n == SQLiteStatementType.STATEMENT_SELECT); - SQLiteStatementInfo info = new SQLiteStatementInfo(); - db.getThreadSession().prepare(mSql, - db.getThreadDefaultConnectionFlags(assumeReadOnly), - cancellationSignalForPrepare, info); - mReadOnly = info.readOnly; - mColumnNames = info.columnNames; - mNumParameters = info.numParameters; - break; - } - - if (bindArgs != null && bindArgs.length > mNumParameters) { - throw new IllegalArgumentException("Too many bind arguments. " - + bindArgs.length + " arguments were provided but the statement needs " - + mNumParameters + " arguments."); - } - - if (mNumParameters != 0) { - mBindArgs = new Object[mNumParameters]; - if (bindArgs != null) { - System.arraycopy(bindArgs, 0, mBindArgs, 0, bindArgs.length); - } - } else { - mBindArgs = null; - } - } - - final SQLiteDatabase getDatabase() { - return mDatabase; - } - - final String getSql() { - return mSql; - } - - final Object[] getBindArgs() { - return mBindArgs; - } - - final String[] getColumnNames() { - return mColumnNames; - } - - /** @hide */ - protected final SQLiteSession getSession() { - return mDatabase.getThreadSession(); - } - - /** @hide */ - protected final int getConnectionFlags() { - return mDatabase.getThreadDefaultConnectionFlags(mReadOnly); - } - - /** @hide */ - protected final void onCorruption() { - mDatabase.onCorruption(); - } - - /** - * Bind a NULL value to this statement. The value remains bound until - * {@link #clearBindings} is called. - * - * @param index The 1-based index to the parameter to bind null to - */ - @Override - public void bindNull(int index) { - bind(index, null); - } - - /** - * Bind a long value to this statement. The value remains bound until - * {@link #clearBindings} is called. - *addToBindArgs - * @param index The 1-based index to the parameter to bind - * @param value The value to bind - */ - @Override - public void bindLong(int index, long value) { - bind(index, value); - } - - /** - * Bind a double value to this statement. The value remains bound until - * {@link #clearBindings} is called. - * - * @param index The 1-based index to the parameter to bind - * @param value The value to bind - */ - @Override - public void bindDouble(int index, double value) { - bind(index, value); - } - - /** - * Bind a String value to this statement. The value remains bound until - * {@link #clearBindings} is called. - * - * @param index The 1-based index to the parameter to bind - * @param value The value to bind, must not be null - */ - @Override - public void bindString(int index, String value) { - if (value == null) { - throw new IllegalArgumentException("the bind value at index " + index + " is null"); - } - bind(index, value); - } - - /** - * Bind a byte array value to this statement. The value remains bound until - * {@link #clearBindings} is called. - * - * @param index The 1-based index to the parameter to bind - * @param value The value to bind, must not be null - */ - @Override - public void bindBlob(int index, byte[] value) { - if (value == null) { - throw new IllegalArgumentException("the bind value at index " + index + " is null"); - } - bind(index, value); - } - - /** - * Binds the given Object to the given SQLiteProgram using the proper - * typing. For example, bind numbers as longs/doubles, and everything else - * as a string by call toString() on it. - * - * @param index the 1-based index to bind at - * @param value the value to bind - */ - public void bindObject(int index, Object value) { - if (value == null) { - bindNull(index); - } else if (value instanceof Double || value instanceof Float) { - bindDouble(index, ((Number)value).doubleValue()); - } else if (value instanceof Number) { - bindLong(index, ((Number)value).longValue()); - } else if (value instanceof Boolean) { - Boolean bool = (Boolean)value; - if (bool) { - bindLong(index, 1); - } else { - bindLong(index, 0); - } - } else if (value instanceof byte[]){ - bindBlob(index, (byte[]) value); - } else { - bindString(index, value.toString()); - } - } - - /** - * Clears all existing bindings. Unset bindings are treated as NULL. - */ - @Override - public void clearBindings() { - if (mBindArgs != null) { - Arrays.fill(mBindArgs, null); - } - } - - /** - * Given an array of String bindArgs, this method binds all of them in one single call. - * - * @param bindArgs the String array of bind args, none of which must be null. - */ - public void bindAllArgsAsStrings(String[] bindArgs) { - if (bindArgs != null) { - for (int i = bindArgs.length; i != 0; i--) { - bindString(i, bindArgs[i - 1]); - } - } - } - - @Override - protected void onAllReferencesReleased() { - clearBindings(); - } - - private void bind(int index, Object value) { - if (index < 1 || index > mNumParameters) { - throw new IllegalArgumentException("Cannot bind argument at index " - + index + " because the index is out of range. " - + "The statement has " + mNumParameters + " parameters."); - } - mBindArgs[index - 1] = value; - } -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteQuery.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteQuery.java deleted file mode 100644 index 3632ad10c0..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteQuery.java +++ /dev/null @@ -1,86 +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.sqlite; - -import android.database.sqlite.SQLiteDatabaseCorruptException; -import android.database.sqlite.SQLiteException; -import android.util.Log; -import androidx.core.os.CancellationSignal; -import androidx.core.os.OperationCanceledException; -import io.requery.android.database.CursorWindow; - -/** - * Represents a query that reads the resulting rows into a {@link SQLiteQuery}. - * This class is used by {@link SQLiteCursor} and isn't useful itself. - *

- * This class is not thread-safe. - *

- */ -public final class SQLiteQuery extends SQLiteProgram { - private static final String TAG = "SQLiteQuery"; - - private final CancellationSignal mCancellationSignal; - - SQLiteQuery(SQLiteDatabase db, String query, Object[] bindArgs, - CancellationSignal cancellationSignal) { - super(db, query, bindArgs, cancellationSignal); - mCancellationSignal = cancellationSignal; - } - - /** - * Reads rows into a buffer. - * - * @param window The window to fill into - * @param startPos The start position for filling the window. - * @param requiredPos The position of a row that MUST be in the window. - * If it won't fit, then the query should discard part of what it filled. - * @param countAllRows True to count all rows that the query would - * return regardless of whether they fit in the window. - * @return Number of rows that were enumerated. Might not be all rows - * unless countAllRows is true. - * - * @throws SQLiteException if an error occurs. - * @throws OperationCanceledException if the operation was canceled. - */ - int fillWindow(CursorWindow window, int startPos, int requiredPos, boolean countAllRows) { - acquireReference(); - try { - window.acquireReference(); - try { - return getSession().executeForCursorWindow(getSql(), getBindArgs(), - window, startPos, requiredPos, countAllRows, getConnectionFlags(), - mCancellationSignal); - } catch (SQLiteDatabaseCorruptException ex) { - onCorruption(); - throw ex; - } catch (SQLiteException ex) { - Log.e(TAG, "exception: " + ex.getMessage() + "; query: " + getSql()); - throw ex; - } finally { - window.releaseReference(); - } - } finally { - releaseReference(); - } - } - - @Override - public String toString() { - return "SQLiteQuery: " + getSql(); - } -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteQueryBuilder.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteQueryBuilder.java deleted file mode 100644 index a22c413262..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteQueryBuilder.java +++ /dev/null @@ -1,612 +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.sqlite; - -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.os.OperationCanceledException; -import android.provider.BaseColumns; -import android.text.TextUtils; -import android.util.Log; -import androidx.core.os.CancellationSignal; - -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.regex.Pattern; - -/** - * This is a convience class that helps build SQL queries to be sent to - * {@link SQLiteDatabase} objects. - */ -@SuppressWarnings("unused") -public class SQLiteQueryBuilder { - private static final String TAG = "SQLiteQueryBuilder"; - private static final Pattern sLimitPattern = - Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?"); - - private Map mProjectionMap = null; - private String mTables = ""; - private StringBuilder mWhereClause = null; // lazily created - private boolean mDistinct; - private SQLiteDatabase.CursorFactory mFactory; - private boolean mStrict; - - public SQLiteQueryBuilder() { - mDistinct = false; - mFactory = null; - } - - /** - * Mark the query as DISTINCT. - * - * @param distinct if true the query is DISTINCT, otherwise it isn't - */ - public void setDistinct(boolean distinct) { - mDistinct = distinct; - } - - /** - * Returns the list of tables being queried - * - * @return the list of tables being queried - */ - public String getTables() { - return mTables; - } - - /** - * Sets the list of tables to query. Multiple tables can be specified to perform a join. - * For example: - * setTables("foo, bar") - * setTables("foo LEFT OUTER JOIN bar ON (foo.id = bar.foo_id)") - * - * @param inTables the list of tables to query on - */ - public void setTables(String inTables) { - mTables = inTables; - } - - /** - * Append a chunk to the WHERE clause of the query. All chunks appended are surrounded - * by parenthesis and ANDed with the selection passed to {@link #query}. The final - * WHERE clause looks like: - * - * WHERE (<append chunk 1><append chunk2>) AND (<query() selection parameter>) - * - * @param inWhere the chunk of text to append to the WHERE clause. - */ - public void appendWhere(CharSequence inWhere) { - if (mWhereClause == null) { - mWhereClause = new StringBuilder(inWhere.length() + 16); - } - if (mWhereClause.length() == 0) { - mWhereClause.append('('); - } - mWhereClause.append(inWhere); - } - - /** - * Append a chunk to the WHERE clause of the query. All chunks appended are surrounded - * by parenthesis and ANDed with the selection passed to {@link #query}. The final - * WHERE clause looks like: - * - * WHERE (<append chunk 1><append chunk2>) AND (<query() selection parameter>) - * - * @param inWhere the chunk of text to append to the WHERE clause. it will be escaped - * to avoid SQL injection attacks - */ - public void appendWhereEscapeString(String inWhere) { - if (mWhereClause == null) { - mWhereClause = new StringBuilder(inWhere.length() + 16); - } - if (mWhereClause.length() == 0) { - mWhereClause.append('('); - } - DatabaseUtils.appendEscapedSQLString(mWhereClause, inWhere); - } - - /** - * Sets the projection map for the query. The projection map maps - * from column names that the caller passes into query to database - * column names. This is useful for renaming columns as well as - * disambiguating column names when doing joins. For example you - * could map "name" to "people.name". If a projection map is set - * it must contain all column names the user may request, even if - * the key and value are the same. - * - * @param columnMap maps from the user column names to the database column names - */ - public void setProjectionMap(Map columnMap) { - mProjectionMap = columnMap; - } - - /** - * Sets the cursor factory to be used for the query. You can use - * one factory for all queries on a database but it is normally - * easier to specify the factory when doing this query. - * - * @param factory the factory to use. - */ - public void setCursorFactory(SQLiteDatabase.CursorFactory factory) { - mFactory = factory; - } - - /** - * When set, the selection is verified against malicious arguments. - * When using this class to create a statement using - * {@link #buildQueryString(boolean, String, String[], String, String, String, String, String)}, - * non-numeric limits will raise an exception. If a projection map is specified, fields - * not in that map will be ignored. - * If this class is used to execute the statement directly using - * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String)} - * or - * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String, String)}, - * additionally also parenthesis escaping selection are caught. - * - * To summarize: To get maximum protection against malicious third party apps (for example - * content provider consumers), make sure to do the following: - *
    - *
  • Set this value to true
  • - *
  • Use a projection map
  • - *
  • Use one of the query overloads instead of getting the statement as a sql string
  • - *
- * By default, this value is false. - */ - public void setStrict(boolean flag) { - mStrict = flag; - } - - /** - * Build an SQL query string from the given clauses. - * - * @param distinct true if you want each row to be unique, false otherwise. - * @param tables The table names to compile the query against. - * @param columns A list of which columns to return. Passing null will - * return all columns, which is discouraged to prevent reading - * data from storage that isn't going to be used. - * @param where A filter declaring which rows to return, formatted as an SQL - * WHERE clause (excluding the WHERE itself). Passing null will - * return all rows for the given URL. - * @param groupBy A filter declaring how to group rows, formatted as an SQL - * GROUP BY clause (excluding the GROUP BY itself). Passing null - * will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in the cursor, - * if row grouping is being used, formatted as an SQL HAVING - * clause (excluding the HAVING itself). Passing null will cause - * all row groups to be included, and is required when row - * grouping is not being used. - * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause - * (excluding the ORDER BY itself). Passing null will use the - * default sort order, which may be unordered. - * @param limit Limits the number of rows returned by the query, - * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return the SQL query string - */ - public static String buildQueryString( - boolean distinct, String tables, String[] columns, String where, - String groupBy, String having, String orderBy, String limit) { - if (TextUtils.isEmpty(groupBy) && !TextUtils.isEmpty(having)) { - throw new IllegalArgumentException( - "HAVING clauses are only permitted when using a groupBy clause"); - } - if (!TextUtils.isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) { - throw new IllegalArgumentException("invalid LIMIT clauses:" + limit); - } - - StringBuilder query = new StringBuilder(120); - - query.append("SELECT "); - if (distinct) { - query.append("DISTINCT "); - } - if (columns != null && columns.length != 0) { - appendColumns(query, columns); - } else { - query.append("* "); - } - query.append("FROM "); - query.append(tables); - appendClause(query, " WHERE ", where); - appendClause(query, " GROUP BY ", groupBy); - appendClause(query, " HAVING ", having); - appendClause(query, " ORDER BY ", orderBy); - appendClause(query, " LIMIT ", limit); - - return query.toString(); - } - - private static void appendClause(StringBuilder s, String name, String clause) { - if (!TextUtils.isEmpty(clause)) { - s.append(name); - s.append(clause); - } - } - - /** - * Add the names that are non-null in columns to s, separating - * them with commas. - */ - public static void appendColumns(StringBuilder s, String[] columns) { - int n = columns.length; - - for (int i = 0; i < n; i++) { - String column = columns[i]; - - if (column != null) { - if (i > 0) { - s.append(", "); - } - s.append(column); - } - } - s.append(' '); - } - - /** - * Perform a query by combining all current settings and the - * information passed into this method. - * - * @param db the database to query on - * @param projectionIn A list of which columns to return. Passing - * null will return all columns, which is discouraged to prevent - * reading data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, - * formatted as an SQL WHERE clause (excluding the WHERE - * itself). Passing null will return all rows for the given URL. - * @param selectionArgs You may include ?s in selection, which - * will be replaced by the values from selectionArgs, in order - * that they appear in the selection. The values will be bound - * as Strings. - * @param groupBy A filter declaring how to group rows, formatted - * as an SQL GROUP BY clause (excluding the GROUP BY - * itself). Passing null will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in - * the cursor, if row grouping is being used, formatted as an - * SQL HAVING clause (excluding the HAVING itself). Passing - * null will cause all row groups to be included, and is - * required when row grouping is not being used. - * @param sortOrder How to order the rows, formatted as an SQL - * ORDER BY clause (excluding the ORDER BY itself). Passing null - * will use the default sort order, which may be unordered. - * @return a cursor over the result set - * @see android.content.ContentResolver#query(android.net.Uri, String[], - * String, String[], String) - */ - public Cursor query(SQLiteDatabase db, String[] projectionIn, - String selection, String[] selectionArgs, String groupBy, - String having, String sortOrder) { - return query(db, projectionIn, selection, selectionArgs, groupBy, having, sortOrder, - null /* limit */, null /* cancellationSignal */); - } - - /** - * Perform a query by combining all current settings and the - * information passed into this method. - * - * @param db the database to query on - * @param projectionIn A list of which columns to return. Passing - * null will return all columns, which is discouraged to prevent - * reading data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, - * formatted as an SQL WHERE clause (excluding the WHERE - * itself). Passing null will return all rows for the given URL. - * @param selectionArgs You may include ?s in selection, which - * will be replaced by the values from selectionArgs, in order - * that they appear in the selection. The values will be bound - * as Strings. - * @param groupBy A filter declaring how to group rows, formatted - * as an SQL GROUP BY clause (excluding the GROUP BY - * itself). Passing null will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in - * the cursor, if row grouping is being used, formatted as an - * SQL HAVING clause (excluding the HAVING itself). Passing - * null will cause all row groups to be included, and is - * required when row grouping is not being used. - * @param sortOrder How to order the rows, formatted as an SQL - * ORDER BY clause (excluding the ORDER BY itself). Passing null - * will use the default sort order, which may be unordered. - * @param limit Limits the number of rows returned by the query, - * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return a cursor over the result set - * @see android.content.ContentResolver#query(android.net.Uri, String[], - * String, String[], String) - */ - public Cursor query(SQLiteDatabase db, String[] projectionIn, - String selection, String[] selectionArgs, String groupBy, - String having, String sortOrder, String limit) { - return query(db, projectionIn, selection, selectionArgs, - groupBy, having, sortOrder, limit, null); - } - - /** - * Perform a query by combining all current settings and the - * information passed into this method. - * - * @param db the database to query on - * @param projectionIn A list of which columns to return. Passing - * null will return all columns, which is discouraged to prevent - * reading data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, - * formatted as an SQL WHERE clause (excluding the WHERE - * itself). Passing null will return all rows for the given URL. - * @param selectionArgs You may include ?s in selection, which - * will be replaced by the values from selectionArgs, in order - * that they appear in the selection. The values will be bound - * as Strings. - * @param groupBy A filter declaring how to group rows, formatted - * as an SQL GROUP BY clause (excluding the GROUP BY - * itself). Passing null will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in - * the cursor, if row grouping is being used, formatted as an - * SQL HAVING clause (excluding the HAVING itself). Passing - * null will cause all row groups to be included, and is - * required when row grouping is not being used. - * @param sortOrder How to order the rows, formatted as an SQL - * ORDER BY clause (excluding the ORDER BY itself). Passing null - * will use the default sort order, which may be unordered. - * @param limit Limits the number of rows returned by the query, - * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * If the operation is canceled, then {@link OperationCanceledException} will be thrown - * when the query is executed. - * @return a cursor over the result set - * @see android.content.ContentResolver#query(android.net.Uri, String[], - * String, String[], String) - */ - public Cursor query(SQLiteDatabase db, String[] projectionIn, - String selection, String[] selectionArgs, String groupBy, - String having, String sortOrder, String limit, CancellationSignal cancellationSignal) { - if (mTables == null) { - return null; - } - - if (mStrict && selection != null && selection.length() > 0) { - // Validate the user-supplied selection to detect syntactic anomalies - // in the selection string that could indicate a SQL injection attempt. - // The idea is to ensure that the selection clause is a valid SQL expression - // by compiling it twice: once wrapped in parentheses and once as - // originally specified. An attacker cannot create an expression that - // would escape the SQL expression while maintaining balanced parentheses - // in both the wrapped and original forms. - String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy, - having, sortOrder, limit); - db.validateSql(sqlForValidation, cancellationSignal); // will throw if query is invalid - } - - String sql = buildQuery( - projectionIn, selection, groupBy, having, - sortOrder, limit); - - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Performing query: " + sql); - } - return db.rawQueryWithFactory( - mFactory, sql, selectionArgs, - SQLiteDatabase.findEditTable(mTables), - cancellationSignal); // will throw if query is invalid - } - - /** - * Construct a SELECT statement suitable for use in a group of - * SELECT statements that will be joined through UNION operators - * in buildUnionQuery. - * - * @param projectionIn A list of which columns to return. Passing - * null will return all columns, which is discouraged to - * prevent reading data from storage that isn't going to be - * used. - * @param selection A filter declaring which rows to return, - * formatted as an SQL WHERE clause (excluding the WHERE - * itself). Passing null will return all rows for the given - * URL. - * @param groupBy A filter declaring how to group rows, formatted - * as an SQL GROUP BY clause (excluding the GROUP BY itself). - * Passing null will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in - * the cursor, if row grouping is being used, formatted as an - * SQL HAVING clause (excluding the HAVING itself). Passing - * null will cause all row groups to be included, and is - * required when row grouping is not being used. - * @param sortOrder How to order the rows, formatted as an SQL - * ORDER BY clause (excluding the ORDER BY itself). Passing null - * will use the default sort order, which may be unordered. - * @param limit Limits the number of rows returned by the query, - * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return the resulting SQL SELECT statement - */ - public String buildQuery( - String[] projectionIn, String selection, String groupBy, - String having, String sortOrder, String limit) { - String[] projection = computeProjection(projectionIn); - - StringBuilder where = new StringBuilder(); - boolean hasBaseWhereClause = mWhereClause != null && mWhereClause.length() > 0; - - if (hasBaseWhereClause) { - where.append(mWhereClause.toString()); - where.append(')'); - } - - // Tack on the user's selection, if present. - if (selection != null && selection.length() > 0) { - if (hasBaseWhereClause) { - where.append(" AND "); - } - - where.append('('); - where.append(selection); - where.append(')'); - } - - return buildQueryString( - mDistinct, mTables, projection, where.toString(), - groupBy, having, sortOrder, limit); - } - - /** - * Construct a SELECT statement suitable for use in a group of - * SELECT statements that will be joined through UNION operators - * in buildUnionQuery. - * - * @param typeDiscriminatorColumn the name of the result column - * whose cells will contain the name of the table from which - * each row was drawn. - * @param unionColumns the names of the columns to appear in the - * result. This may include columns that do not appear in the - * table this SELECT is querying (i.e. mTables), but that do - * appear in one of the other tables in the UNION query that we - * are constructing. - * @param columnsPresentInTable a Set of the names of the columns - * that appear in this table (i.e. in the table whose name is - * mTables). Since columns in unionColumns include columns that - * appear only in other tables, we use this array to distinguish - * which ones actually are present. Other columns will have - * NULL values for results from this subquery. - * @param computedColumnsOffset all columns in unionColumns before - * this index are included under the assumption that they're - * computed and therefore won't appear in columnsPresentInTable, - * e.g. "date * 1000 as normalized_date" - * @param typeDiscriminatorValue the value used for the - * type-discriminator column in this subquery - * @param selection A filter declaring which rows to return, - * formatted as an SQL WHERE clause (excluding the WHERE - * itself). Passing null will return all rows for the given - * URL. - * @param groupBy A filter declaring how to group rows, formatted - * as an SQL GROUP BY clause (excluding the GROUP BY itself). - * Passing null will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in - * the cursor, if row grouping is being used, formatted as an - * SQL HAVING clause (excluding the HAVING itself). Passing - * null will cause all row groups to be included, and is - * required when row grouping is not being used. - * @return the resulting SQL SELECT statement - */ - public String buildUnionSubQuery( - String typeDiscriminatorColumn, - String[] unionColumns, - Set columnsPresentInTable, - int computedColumnsOffset, - String typeDiscriminatorValue, - String selection, - String groupBy, - String having) { - int unionColumnsCount = unionColumns.length; - String[] projectionIn = new String[unionColumnsCount]; - - for (int i = 0; i < unionColumnsCount; i++) { - String unionColumn = unionColumns[i]; - - if (unionColumn.equals(typeDiscriminatorColumn)) { - projectionIn[i] = "'" + typeDiscriminatorValue + "' AS " - + typeDiscriminatorColumn; - } else if (i <= computedColumnsOffset - || columnsPresentInTable.contains(unionColumn)) { - projectionIn[i] = unionColumn; - } else { - projectionIn[i] = "NULL AS " + unionColumn; - } - } - return buildQuery( - projectionIn, selection, groupBy, having, - null /* sortOrder */, - null /* limit */); - } - - /** - * Given a set of subqueries, all of which are SELECT statements, - * construct a query that returns the union of what those - * subqueries return. - * @param subQueries an array of SQL SELECT statements, all of - * which must have the same columns as the same positions in - * their results - * @param sortOrder How to order the rows, formatted as an SQL - * ORDER BY clause (excluding the ORDER BY itself). Passing - * null will use the default sort order, which may be unordered. - * @param limit The limit clause, which applies to the entire union result set - * - * @return the resulting SQL SELECT statement - */ - public String buildUnionQuery(String[] subQueries, String sortOrder, String limit) { - StringBuilder query = new StringBuilder(128); - int subQueryCount = subQueries.length; - String unionOperator = mDistinct ? " UNION " : " UNION ALL "; - - for (int i = 0; i < subQueryCount; i++) { - if (i > 0) { - query.append(unionOperator); - } - query.append(subQueries[i]); - } - appendClause(query, " ORDER BY ", sortOrder); - appendClause(query, " LIMIT ", limit); - return query.toString(); - } - - private String[] computeProjection(String[] projectionIn) { - if (projectionIn != null && projectionIn.length > 0) { - if (mProjectionMap != null) { - String[] projection = new String[projectionIn.length]; - int length = projectionIn.length; - - for (int i = 0; i < length; i++) { - String userColumn = projectionIn[i]; - String column = mProjectionMap.get(userColumn); - - if (column != null) { - projection[i] = column; - continue; - } - - if (!mStrict && - ( userColumn.contains(" AS ") || userColumn.contains(" as "))) { - /* A column alias already exist */ - projection[i] = userColumn; - continue; - } - - throw new IllegalArgumentException("Invalid column " - + projectionIn[i]); - } - return projection; - } else { - return projectionIn; - } - } else if (mProjectionMap != null) { - // Return all columns in projection map. - Set> entrySet = mProjectionMap.entrySet(); - String[] projection = new String[entrySet.size()]; - Iterator> entryIter = entrySet.iterator(); - int i = 0; - - while (entryIter.hasNext()) { - Entry entry = entryIter.next(); - - // Don't include the _count column when people ask for no projection. - if (entry.getKey().equals(BaseColumns._COUNT)) { - continue; - } - projection[i++] = entry.getValue(); - } - return projection; - } - return null; - } -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteSession.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteSession.java deleted file mode 100644 index 1f3201d3a2..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteSession.java +++ /dev/null @@ -1,975 +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.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteTransactionListener; -import android.os.ParcelFileDescriptor; -import androidx.core.os.CancellationSignal; -import androidx.core.os.OperationCanceledException; -import io.requery.android.database.CursorWindow; - -/** - * Provides a single client the ability to use a database. - * - *

About database sessions

- *

- * Database access is always performed using a session. The session - * manages the lifecycle of transactions and database connections. - *

- * Sessions can be used to perform both read-only and read-write operations. - * There is some advantage to knowing when a session is being used for - * read-only purposes because the connection pool can optimize the use - * of the available connections to permit multiple read-only operations - * to execute in parallel whereas read-write operations may need to be serialized. - *

- * When Write Ahead Logging (WAL) is enabled, the database can - * execute simultaneous read-only and read-write transactions, provided that - * at most one read-write transaction is performed at a time. When WAL is not - * enabled, read-only transactions can execute in parallel but read-write - * transactions are mutually exclusive. - *

- * - *

Ownership and concurrency guarantees

- *

- * Session objects are not thread-safe. In fact, session objects are thread-bound. - * The {@link SQLiteDatabase} uses a thread-local variable to associate a session - * with each thread for the use of that thread alone. Consequently, each thread - * has its own session object and therefore its own transaction state independent - * of other threads. - *

- * A thread has at most one session per database. This constraint ensures that - * a thread can never use more than one database connection at a time for a - * given database. As the number of available database connections is limited, - * if a single thread tried to acquire multiple connections for the same database - * at the same time, it might deadlock. Therefore we allow there to be only - * one session (so, at most one connection) per thread per database. - *

- * - *

Transactions

- *

- * There are two kinds of transaction: implicit transactions and explicit - * transactions. - *

- * An implicit transaction is created whenever a database operation is requested - * and there is no explicit transaction currently in progress. An implicit transaction - * only lasts for the duration of the database operation in question and then it - * is ended. If the database operation was successful, then its changes are committed. - *

- * An explicit transaction is started by calling {@link #beginTransaction} and - * specifying the desired transaction mode. Once an explicit transaction has begun, - * all subsequent database operations will be performed as part of that transaction. - * To end an explicit transaction, first call {@link #setTransactionSuccessful} if the - * transaction was successful, then call {@link #endTransaction}. If the transaction was - * marked successful, its changes will be committed, otherwise they will be rolled back. - *

- * Explicit transactions can also be nested. A nested explicit transaction is - * started with {@link #beginTransaction}, marked successful with - * {@link #setTransactionSuccessful}and ended with {@link #endTransaction}. - * If any nested transaction is not marked successful, then the entire transaction - * including all of its nested transactions will be rolled back - * when the outermost transaction is ended. - *

- * To improve concurrency, an explicit transaction can be yielded by calling - * {@link #yieldTransaction}. If there is contention for use of the database, - * then yielding ends the current transaction, commits its changes, releases the - * database connection for use by another session for a little while, and starts a - * new transaction with the same properties as the original one. - * Changes committed by {@link #yieldTransaction} cannot be rolled back. - *

- * When a transaction is started, the client can provide a {@link SQLiteTransactionListener} - * to listen for notifications of transaction-related events. - *

- * Recommended usage: - *

- * // First, begin the transaction.
- * session.beginTransaction(SQLiteSession.TRANSACTION_MODE_DEFERRED, 0);
- * try {
- *     // Then do stuff...
- *     session.execute("INSERT INTO ...", null, 0);
- *
- *     // As the very last step before ending the transaction, mark it successful.
- *     session.setTransactionSuccessful();
- * } finally {
- *     // Finally, end the transaction.
- *     // This statement will commit the transaction if it was marked successful or
- *     // roll it back otherwise.
- *     session.endTransaction();
- * }
- * 
- *

- * - *

Database connections

- *

- * A {@link SQLiteDatabase} can have multiple active sessions at the same - * time. Each session acquires and releases connections to the database - * as needed to perform each requested database transaction. If all connections - * are in use, then database transactions on some sessions will block until a - * connection becomes available. - *

- * The session acquires a single database connection only for the duration - * of a single (implicit or explicit) database transaction, then releases it. - * This characteristic allows a small pool of database connections to be shared - * efficiently by multiple sessions as long as they are not all trying to perform - * database transactions at the same time. - *

- * - *

Responsiveness

- *

- * Because there are a limited number of database connections and the session holds - * a database connection for the entire duration of a database transaction, - * it is important to keep transactions short. This is especially important - * for read-write transactions since they may block other transactions - * from executing. Consider calling {@link #yieldTransaction} periodically - * during long-running transactions. - *

- * Another important consideration is that transactions that take too long to - * run may cause the application UI to become unresponsive. Even if the transaction - * is executed in a background thread, the user will get bored and - * frustrated if the application shows no data for several seconds while - * a transaction runs. - *

- * Guidelines: - *

    - *
  • Do not perform database transactions on the UI thread.
  • - *
  • Keep database transactions as short as possible.
  • - *
  • Simple queries often run faster than complex queries.
  • - *
  • Measure the performance of your database transactions.
  • - *
  • Consider what will happen when the size of the data set grows. - * A query that works well on 100 rows may struggle with 10,000.
  • - *
- * - *

Reentrance

- *

- * This class must tolerate reentrant execution of SQLite operations because - * triggers may call custom SQLite functions that perform additional queries. - *

- * - * @hide - */ -@SuppressWarnings({"unused", "JavaDoc"}) -@SuppressLint("Assert") -public final class SQLiteSession { - private final SQLiteConnectionPool mConnectionPool; - - private SQLiteConnection mConnection; - private int mConnectionFlags; - private int mConnectionUseCount; - private Transaction mTransactionPool; - private Transaction mTransactionStack; - - /** - * Transaction mode: Deferred. - *

- * In a deferred transaction, no locks are acquired on the database - * until the first operation is performed. If the first operation is - * read-only, then a SHARED lock is acquired, otherwise - * a RESERVED lock is acquired. - *

- * While holding a SHARED lock, this session is only allowed to - * read but other sessions are allowed to read or write. - * While holding a RESERVED lock, this session is allowed to read - * or write but other sessions are only allowed to read. - *

- * Because the lock is only acquired when needed in a deferred transaction, - * it is possible for another session to write to the database first before - * this session has a chance to do anything. - *

- * Corresponds to the SQLite BEGIN DEFERRED transaction mode. - *

- */ - public static final int TRANSACTION_MODE_DEFERRED = 0; - - /** - * Transaction mode: Immediate. - *

- * When an immediate transaction begins, the session acquires a - * RESERVED lock. - *

- * While holding a RESERVED lock, this session is allowed to read - * or write but other sessions are only allowed to read. - *

- * Corresponds to the SQLite BEGIN IMMEDIATE transaction mode. - *

- */ - public static final int TRANSACTION_MODE_IMMEDIATE = 1; - - /** - * Transaction mode: Exclusive. - *

- * When an exclusive transaction begins, the session acquires an - * EXCLUSIVE lock. - *

- * While holding an EXCLUSIVE lock, this session is allowed to read - * or write but no other sessions are allowed to access the database. - *

- * Corresponds to the SQLite BEGIN EXCLUSIVE transaction mode. - *

- */ - public static final int TRANSACTION_MODE_EXCLUSIVE = 2; - - /** - * Creates a session bound to the specified connection pool. - * - * @param connectionPool The connection pool. - */ - public SQLiteSession(SQLiteConnectionPool connectionPool) { - if (connectionPool == null) { - throw new IllegalArgumentException("connectionPool must not be null"); - } - - mConnectionPool = connectionPool; - } - - /** - * Returns true if the session has a transaction in progress. - * - * @return True if the session has a transaction in progress. - */ - public boolean hasTransaction() { - return mTransactionStack != null; - } - - /** - * Returns true if the session has a nested transaction in progress. - * - * @return True if the session has a nested transaction in progress. - */ - public boolean hasNestedTransaction() { - return mTransactionStack != null && mTransactionStack.mParent != null; - } - - /** - * Returns true if the session has an active database connection. - * - * @return True if the session has an active database connection. - */ - public boolean hasConnection() { - return mConnection != null; - } - - /** - * Begins a transaction. - *

- * Transactions may nest. If the transaction is not in progress, - * then a database connection is obtained and a new transaction is started. - * Otherwise, a nested transaction is started. - *

- * Each call to {@link #beginTransaction} must be matched exactly by a call - * to {@link #endTransaction}. To mark a transaction as successful, - * call {@link #setTransactionSuccessful} before calling {@link #endTransaction}. - * If the transaction is not successful, or if any of its nested - * transactions were not successful, then the entire transaction will - * be rolled back when the outermost transaction is ended. - *

- * - * @param transactionMode The transaction mode. One of: {@link #TRANSACTION_MODE_DEFERRED}, - * {@link #TRANSACTION_MODE_IMMEDIATE}, or {@link #TRANSACTION_MODE_EXCLUSIVE}. - * Ignored when creating a nested transaction. - * @param transactionListener The transaction listener, or null if none. - * @param connectionFlags The connection flags to use if a connection must be - * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * - * @throws IllegalStateException if {@link #setTransactionSuccessful} has already been - * called for the current transaction. - * @throws SQLiteException if an error occurs. - * @throws OperationCanceledException if the operation was canceled. - * - * @see #setTransactionSuccessful - * @see #yieldTransaction - * @see #endTransaction - */ - public void beginTransaction(int transactionMode, - SQLiteTransactionListener transactionListener, - int connectionFlags, - CancellationSignal cancellationSignal) { - throwIfTransactionMarkedSuccessful(); - beginTransactionUnchecked(transactionMode, transactionListener, connectionFlags, - cancellationSignal); - } - - private void beginTransactionUnchecked(int transactionMode, - SQLiteTransactionListener transactionListener, int connectionFlags, - CancellationSignal cancellationSignal) { - if (cancellationSignal != null) { - cancellationSignal.throwIfCanceled(); - } - - if (mTransactionStack == null) { - acquireConnection(null, connectionFlags, cancellationSignal); // might throw - } - try { - // Set up the transaction such that we can back out safely - // in case we fail part way. - if (mTransactionStack == null) { - // Execute SQL might throw a runtime exception. - switch (transactionMode) { - case TRANSACTION_MODE_IMMEDIATE: - mConnection.execute("BEGIN IMMEDIATE;", null, - cancellationSignal); // might throw - break; - case TRANSACTION_MODE_EXCLUSIVE: - mConnection.execute("BEGIN EXCLUSIVE;", null, - cancellationSignal); // might throw - break; - default: - mConnection.execute("BEGIN;", null, cancellationSignal); // might throw - break; - } - } - - // Listener might throw a runtime exception. - if (transactionListener != null) { - try { - transactionListener.onBegin(); // might throw - } catch (RuntimeException ex) { - if (mTransactionStack == null) { - mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw - } - throw ex; - } - } - - // Bookkeeping can't throw, except an OOM, which is just too bad... - Transaction transaction = obtainTransaction(transactionMode, transactionListener); - transaction.mParent = mTransactionStack; - mTransactionStack = transaction; - } finally { - if (mTransactionStack == null) { - releaseConnection(); // might throw - } - } - } - - /** - * Marks the current transaction as having completed successfully. - *

- * This method can be called at most once between {@link #beginTransaction} and - * {@link #endTransaction} to indicate that the changes made by the transaction should be - * committed. If this method is not called, the changes will be rolled back - * when the transaction is ended. - *

- * - * @throws IllegalStateException if there is no current transaction, or if - * {@link #setTransactionSuccessful} has already been called for the current transaction. - * - * @see #beginTransaction - * @see #endTransaction - */ - public void setTransactionSuccessful() { - throwIfNoTransaction(); - throwIfTransactionMarkedSuccessful(); - - mTransactionStack.mMarkedSuccessful = true; - } - - /** - * Ends the current transaction and commits or rolls back changes. - *

- * If this is the outermost transaction (not nested within any other - * transaction), then the changes are committed if {@link #setTransactionSuccessful} - * was called or rolled back otherwise. - *

- * This method must be called exactly once for each call to {@link #beginTransaction}. - *

- * - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * - * @throws IllegalStateException if there is no current transaction. - * @throws SQLiteException if an error occurs. - * @throws OperationCanceledException if the operation was canceled. - * - * @see #beginTransaction - * @see #setTransactionSuccessful - * @see #yieldTransaction - */ - public void endTransaction(CancellationSignal cancellationSignal) { - throwIfNoTransaction(); - assert mConnection != null; - - endTransactionUnchecked(cancellationSignal, false); - } - - private void endTransactionUnchecked(CancellationSignal cancellationSignal, boolean yielding) { - if (cancellationSignal != null) { - cancellationSignal.throwIfCanceled(); - } - - final Transaction top = mTransactionStack; - boolean successful = (top.mMarkedSuccessful || yielding) && !top.mChildFailed; - - RuntimeException listenerException = null; - final SQLiteTransactionListener listener = top.mListener; - if (listener != null) { - try { - if (successful) { - listener.onCommit(); // might throw - } else { - listener.onRollback(); // might throw - } - } catch (RuntimeException ex) { - listenerException = ex; - successful = false; - } - } - - mTransactionStack = top.mParent; - recycleTransaction(top); - - if (mTransactionStack != null) { - if (!successful) { - mTransactionStack.mChildFailed = true; - } - } else { - try { - if (successful) { - mConnection.execute("COMMIT;", null, cancellationSignal); // might throw - } else { - mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw - } - } finally { - releaseConnection(); // might throw - } - } - - if (listenerException != null) { - throw listenerException; - } - } - - /** - * Temporarily ends a transaction to let other threads have use of - * the database. Begins a new transaction after a specified delay. - *

- * If there are other threads waiting to acquire connections, - * then the current transaction is committed and the database - * connection is released. After a short delay, a new transaction - * is started. - *

- * The transaction is assumed to be successful so far. Do not call - * {@link #setTransactionSuccessful()} before calling this method. - * This method will fail if the transaction has already been marked - * successful. - *

- * The changes that were committed by a yield cannot be rolled back later. - *

- * Before this method was called, there must already have been - * a transaction in progress. When this method returns, there will - * still be a transaction in progress, either the same one as before - * or a new one if the transaction was actually yielded. - *

- * This method should not be called when there is a nested transaction - * in progress because it is not possible to yield a nested transaction. - * If throwIfNested is true, then attempting to yield - * a nested transaction will throw {@link IllegalStateException}, otherwise - * the method will return false in that case. - *

- * If there is no nested transaction in progress but a previous nested - * transaction failed, then the transaction is not yielded (because it - * must be rolled back) and this method returns false. - *

- * - * @param sleepAfterYieldDelayMillis A delay time to wait after yielding - * the database connection to allow other threads some time to run. - * If the value is less than or equal to zero, there will be no additional - * delay beyond the time it will take to begin a new transaction. - * @param throwIfUnsafe If true, then instead of returning false when no - * transaction is in progress, a nested transaction is in progress, or when - * the transaction has already been marked successful, throws {@link IllegalStateException}. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * @return True if the transaction was actually yielded. - * - * @throws IllegalStateException if throwIfNested is true and - * there is no current transaction, there is a nested transaction in progress or - * if {@link #setTransactionSuccessful} has already been called for the current transaction. - * @throws SQLiteException if an error occurs. - * @throws OperationCanceledException if the operation was canceled. - * - * @see #beginTransaction - * @see #endTransaction - */ - public boolean yieldTransaction(long sleepAfterYieldDelayMillis, boolean throwIfUnsafe, - CancellationSignal cancellationSignal) { - if (throwIfUnsafe) { - throwIfNoTransaction(); - throwIfTransactionMarkedSuccessful(); - throwIfNestedTransaction(); - } else { - if (mTransactionStack == null || mTransactionStack.mMarkedSuccessful - || mTransactionStack.mParent != null) { - return false; - } - } - assert mConnection != null; - - if (mTransactionStack.mChildFailed) { - return false; - } - - return yieldTransactionUnchecked(sleepAfterYieldDelayMillis, - cancellationSignal); // might throw - } - - private boolean yieldTransactionUnchecked(long sleepAfterYieldDelayMillis, - CancellationSignal cancellationSignal) { - if (cancellationSignal != null) { - cancellationSignal.throwIfCanceled(); - } - - if (!mConnectionPool.shouldYieldConnection(mConnection, mConnectionFlags)) { - return false; - } - - final int transactionMode = mTransactionStack.mMode; - final SQLiteTransactionListener listener = mTransactionStack.mListener; - final int connectionFlags = mConnectionFlags; - endTransactionUnchecked(cancellationSignal, true); // might throw - - if (sleepAfterYieldDelayMillis > 0) { - try { - Thread.sleep(sleepAfterYieldDelayMillis); - } catch (InterruptedException ex) { - // we have been interrupted, that's all we need to do - } - } - - beginTransactionUnchecked(transactionMode, listener, connectionFlags, - cancellationSignal); // might throw - return true; - } - - /** - * Prepares a statement for execution but does not bind its parameters or execute it. - *

- * 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 and reused if possible. - *

- * - * @param sql The SQL statement to prepare. - * @param connectionFlags The connection flags to use if a connection must be - * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate - * with information about the statement, or null if none. - * - * @throws SQLiteException if an error occurs, such as a syntax error. - * @throws OperationCanceledException if the operation was canceled. - */ - public void prepare(String sql, int connectionFlags, CancellationSignal cancellationSignal, - SQLiteStatementInfo outStatementInfo) { - if (sql == null) { - throw new IllegalArgumentException("sql must not be null."); - } - - if (cancellationSignal != null) { - cancellationSignal.throwIfCanceled(); - } - - acquireConnection(sql, connectionFlags, cancellationSignal); // might throw - try { - mConnection.prepare(sql, outStatementInfo); // might throw - } finally { - releaseConnection(); // might throw - } - } - - /** - * Executes a statement that does not return a result. - * - * @param sql The SQL statement to execute. - * @param bindArgs The arguments to bind, or null if none. - * @param connectionFlags The connection flags to use if a connection must be - * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * - * @throws SQLiteException if an error occurs, such as a syntax error - * or invalid number of bind arguments. - * @throws OperationCanceledException if the operation was canceled. - */ - public void execute(String sql, Object[] bindArgs, int connectionFlags, - CancellationSignal cancellationSignal) { - if (sql == null) { - throw new IllegalArgumentException("sql must not be null."); - } - - if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { - return; - } - - acquireConnection(sql, connectionFlags, cancellationSignal); // might throw - try { - mConnection.execute(sql, bindArgs, cancellationSignal); // might throw - } finally { - releaseConnection(); // might throw - } - } - - /** - * Executes a statement that returns a single long result. - * - * @param sql The SQL statement to execute. - * @param bindArgs The arguments to bind, or null if none. - * @param connectionFlags The connection flags to use if a connection must be - * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * @return The value of the first column in the first row of the result set - * as a 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, int connectionFlags, - CancellationSignal cancellationSignal) { - if (sql == null) { - throw new IllegalArgumentException("sql must not be null."); - } - - if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { - return 0; - } - - acquireConnection(sql, connectionFlags, cancellationSignal); // might throw - try { - return mConnection.executeForLong(sql, bindArgs, cancellationSignal); // might throw - } finally { - releaseConnection(); // might throw - } - } - - /** - * Executes a statement that returns a single {@link String} result. - * - * @param sql The SQL statement to execute. - * @param bindArgs The arguments to bind, or null if none. - * @param connectionFlags The connection flags to use if a connection must be - * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * @return The value of the first column in the first row of the result set - * as a 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, int connectionFlags, - CancellationSignal cancellationSignal) { - if (sql == null) { - throw new IllegalArgumentException("sql must not be null."); - } - - if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { - return null; - } - - acquireConnection(sql, connectionFlags, cancellationSignal); // might throw - try { - return mConnection.executeForString(sql, bindArgs, cancellationSignal); // might throw - } finally { - releaseConnection(); // might throw - } - } - - /** - * Executes a statement that returns a single BLOB result as a - * file descriptor to a shared memory region. - * - * @param sql The SQL statement to execute. - * @param bindArgs The arguments to bind, or null if none. - * @param connectionFlags The connection flags to use if a connection must be - * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * @return The file descriptor for a shared memory region that contains - * the value of the first column in the first row of the result set as a BLOB, - * or null if none. - * - * @throws SQLiteException if an error occurs, such as a syntax error - * or invalid number of bind arguments. - * @throws OperationCanceledException if the operation was canceled. - */ - public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs, - int connectionFlags, CancellationSignal cancellationSignal) { - if (sql == null) { - throw new IllegalArgumentException("sql must not be null."); - } - - if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { - return null; - } - - acquireConnection(sql, connectionFlags, cancellationSignal); // might throw - try { - return mConnection.executeForBlobFileDescriptor(sql, bindArgs, - cancellationSignal); // might throw - } finally { - releaseConnection(); // might throw - } - } - - /** - * Executes a statement that returns a count of the number of rows - * that were changed. Use for UPDATE or DELETE SQL statements. - * - * @param sql The SQL statement to execute. - * @param bindArgs The arguments to bind, or null if none. - * @param connectionFlags The connection flags to use if a connection must be - * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * @return The number of rows that were changed. - * - * @throws SQLiteException if an error occurs, such as a syntax error - * or invalid number of bind arguments. - * @throws OperationCanceledException if the operation was canceled. - */ - public int executeForChangedRowCount(String sql, Object[] bindArgs, int connectionFlags, - CancellationSignal cancellationSignal) { - if (sql == null) { - throw new IllegalArgumentException("sql must not be null."); - } - - if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { - return 0; - } - - acquireConnection(sql, connectionFlags, cancellationSignal); // might throw - try { - return mConnection.executeForChangedRowCount(sql, bindArgs, - cancellationSignal); // might throw - } finally { - releaseConnection(); // might throw - } - } - - /** - * Executes a statement that returns the row id of the last row inserted - * by the statement. Use for INSERT SQL statements. - * - * @param sql The SQL statement to execute. - * @param bindArgs The arguments to bind, or null if none. - * @param connectionFlags The connection flags to use if a connection must be - * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * @return The row id of the last row that was inserted, or 0 if none. - * - * @throws SQLiteException if an error occurs, such as a syntax error - * or invalid number of bind arguments. - * @throws OperationCanceledException if the operation was canceled. - */ - public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags, - CancellationSignal cancellationSignal) { - if (sql == null) { - throw new IllegalArgumentException("sql must not be null."); - } - - if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { - return 0; - } - - acquireConnection(sql, connectionFlags, cancellationSignal); // might throw - try { - return mConnection.executeForLastInsertedRowId(sql, bindArgs, - cancellationSignal); // might throw - } finally { - releaseConnection(); // might throw - } - } - - /** - * Executes a statement and populates the specified {@link CursorWindow} - * with a range of results. Returns the number of rows that were counted - * during query execution. - * - * @param sql The SQL statement to execute. - * @param bindArgs The arguments to bind, or null if none. - * @param window The cursor window to clear and fill. - * @param startPos The start position for filling the window. - * @param requiredPos The position of a row that MUST be in the window. - * If it won't fit, then the query should discard part of what it filled - * so that it does. Must be greater than or equal to startPos. - * @param countAllRows True to count all rows that the query would return - * regagless of whether they fit in the window. - * @param connectionFlags The connection flags to use if a connection must be - * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * @return The number of rows that were counted during query execution. Might - * not be all rows in the result set unless 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, - int connectionFlags, - CancellationSignal cancellationSignal) { - if (sql == null) { - throw new IllegalArgumentException("sql must not be null."); - } - if (window == null) { - throw new IllegalArgumentException("window must not be null."); - } - - if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { - window.clear(); - return 0; - } - - acquireConnection(sql, connectionFlags, cancellationSignal); // might throw - try { - return mConnection.executeForCursorWindow(sql, bindArgs, - window, startPos, requiredPos, countAllRows, - cancellationSignal); // might throw - } finally { - releaseConnection(); // might throw - } - } - - /** - * Performs special reinterpretation of certain SQL statements such as "BEGIN", - * "COMMIT" and "ROLLBACK" to ensure that transaction state invariants are - * maintained. - * - * This function is mainly used to support legacy apps that perform their - * own transactions by executing raw SQL rather than calling {@link #beginTransaction} - * and the like. - * - * @param sql The SQL statement to execute. - * @param bindArgs The arguments to bind, or null if none. - * @param connectionFlags The connection flags to use if a connection must be - * acquired by this operation. Refer to {@link SQLiteConnectionPool}. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * @return True if the statement was of a special form that was handled here, - * false otherwise. - * - * @throws SQLiteException if an error occurs, such as a syntax error - * or invalid number of bind arguments. - * @throws OperationCanceledException if the operation was canceled. - */ - private boolean executeSpecial(String sql, Object[] bindArgs, int connectionFlags, - CancellationSignal cancellationSignal) { - if (cancellationSignal != null) { - cancellationSignal.throwIfCanceled(); - } - - final int type = SQLiteStatementType.getSqlStatementType(sql); - switch (type) { - case SQLiteStatementType.STATEMENT_BEGIN: - beginTransaction(TRANSACTION_MODE_EXCLUSIVE, null, connectionFlags, - cancellationSignal); - return true; - - case SQLiteStatementType.STATEMENT_COMMIT: - setTransactionSuccessful(); - endTransaction(cancellationSignal); - return true; - - case SQLiteStatementType.STATEMENT_ABORT: - endTransaction(cancellationSignal); - return true; - } - return false; - } - - private void acquireConnection(String sql, int connectionFlags, - CancellationSignal cancellationSignal) { - if (mConnection == null) { - assert mConnectionUseCount == 0; - mConnection = mConnectionPool.acquireConnection(sql, connectionFlags, - cancellationSignal); // might throw - mConnectionFlags = connectionFlags; - } - mConnectionUseCount += 1; - } - - private void releaseConnection() { - assert mConnection != null; - assert mConnectionUseCount > 0; - if (--mConnectionUseCount == 0) { - try { - mConnectionPool.releaseConnection(mConnection); // might throw - } finally { - mConnection = null; - } - } - } - - private void throwIfNoTransaction() { - if (mTransactionStack == null) { - throw new IllegalStateException("Cannot perform this operation because " - + "there is no current transaction."); - } - } - - private void throwIfTransactionMarkedSuccessful() { - if (mTransactionStack != null && mTransactionStack.mMarkedSuccessful) { - throw new IllegalStateException("Cannot perform this operation because " - + "the transaction has already been marked successful. The only " - + "thing you can do now is call endTransaction()."); - } - } - - private void throwIfNestedTransaction() { - if (hasNestedTransaction()) { - throw new IllegalStateException("Cannot perform this operation because " - + "a nested transaction is in progress."); - } - } - - private Transaction obtainTransaction(int mode, SQLiteTransactionListener listener) { - Transaction transaction = mTransactionPool; - if (transaction != null) { - mTransactionPool = transaction.mParent; - transaction.mParent = null; - transaction.mMarkedSuccessful = false; - transaction.mChildFailed = false; - } else { - transaction = new Transaction(); - } - transaction.mMode = mode; - transaction.mListener = listener; - return transaction; - } - - private void recycleTransaction(Transaction transaction) { - transaction.mParent = mTransactionPool; - transaction.mListener = null; - mTransactionPool = transaction; - } - - private static final class Transaction { - public Transaction mParent; - public int mMode; - public SQLiteTransactionListener mListener; - public boolean mMarkedSuccessful; - public boolean mChildFailed; - } -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteStatement.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteStatement.java deleted file mode 100644 index 34edd8ec9c..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteStatement.java +++ /dev/null @@ -1,172 +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.sqlite; - -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabaseCorruptException; -import android.database.sqlite.SQLiteDoneException; -import android.os.ParcelFileDescriptor; -import androidx.sqlite.db.SupportSQLiteStatement; - -/** - * Represents a statement that can be executed against a database. The statement - * cannot return multiple rows or columns, but single value (1 x 1) result sets - * are supported. - *

- * This class is not thread-safe. - *

- */ -@SuppressWarnings("unused") -public final class SQLiteStatement extends SQLiteProgram implements SupportSQLiteStatement { - - SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) { - super(db, sql, bindArgs, null); - } - - /** - * Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example - * CREATE / DROP table, view, trigger, index etc. - * - * @throws SQLException If the SQL string is invalid for some reason - */ - @Override - public void execute() { - acquireReference(); - try { - getSession().execute(getSql(), getBindArgs(), getConnectionFlags(), null); - } catch (SQLiteDatabaseCorruptException ex) { - onCorruption(); - throw ex; - } finally { - releaseReference(); - } - } - - /** - * Execute this SQL statement, if the the number of rows affected by execution of this SQL - * statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements. - * - * @return the number of rows affected by this SQL statement execution. - * @throws SQLException If the SQL string is invalid for some reason - */ - @Override - public int executeUpdateDelete() { - acquireReference(); - try { - return getSession().executeForChangedRowCount( - getSql(), getBindArgs(), getConnectionFlags(), null); - } catch (SQLiteDatabaseCorruptException ex) { - onCorruption(); - throw ex; - } finally { - releaseReference(); - } - } - - /** - * Execute this SQL statement and return the ID of the row inserted due to this call. - * The SQL statement should be an INSERT for this to be a useful call. - * - * @return the row ID of the last row inserted, if this insert is successful. -1 otherwise. - * - * @throws SQLException If the SQL string is invalid for some reason - */ - @Override - public long executeInsert() { - acquireReference(); - try { - return getSession().executeForLastInsertedRowId( - getSql(), getBindArgs(), getConnectionFlags(), null); - } catch (SQLiteDatabaseCorruptException ex) { - onCorruption(); - throw ex; - } finally { - releaseReference(); - } - } - - /** - * Execute a statement that returns a 1 by 1 table with a numeric value. - * For example, SELECT COUNT(*) FROM table; - * - * @return The result of the query. - * - * @throws SQLiteDoneException if the query returns zero rows - */ - @Override - public long simpleQueryForLong() { - acquireReference(); - try { - return getSession().executeForLong( - getSql(), getBindArgs(), getConnectionFlags(), null); - } catch (SQLiteDatabaseCorruptException ex) { - onCorruption(); - throw ex; - } finally { - releaseReference(); - } - } - - /** - * Execute a statement that returns a 1 by 1 table with a text value. - * For example, SELECT COUNT(*) FROM table; - * - * @return The result of the query. - * - * @throws SQLiteDoneException if the query returns zero rows - */ - @Override - public String simpleQueryForString() { - acquireReference(); - try { - return getSession().executeForString( - getSql(), getBindArgs(), getConnectionFlags(), null); - } catch (SQLiteDatabaseCorruptException ex) { - onCorruption(); - throw ex; - } finally { - releaseReference(); - } - } - - /** - * Executes a statement that returns a 1 by 1 table with a blob value. - * - * @return A read-only file descriptor for a copy of the blob value, or {@code null} - * if the value is null or could not be read for some reason. - * - * @throws SQLiteDoneException if the query returns zero rows - */ - public ParcelFileDescriptor simpleQueryForBlobFileDescriptor() { - acquireReference(); - try { - return getSession().executeForBlobFileDescriptor( - getSql(), getBindArgs(), getConnectionFlags(), null); - } catch (SQLiteDatabaseCorruptException ex) { - onCorruption(); - throw ex; - } finally { - releaseReference(); - } - } - - @Override - public String toString() { - return "SQLiteProgram: " + getSql(); - } -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteStatementInfo.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteStatementInfo.java deleted file mode 100644 index c8d50bc395..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteStatementInfo.java +++ /dev/null @@ -1,40 +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 - -package io.requery.android.database.sqlite; - -/** - * Describes a SQLite statement. - * - * @hide - */ -public final class SQLiteStatementInfo { - /** - * The number of parameters that the statement has. - */ - public int numParameters; - - /** - * The names of all columns in the result set of the statement. - */ - public String[] columnNames; - - /** - * True if the statement is read-only. - */ - public boolean readOnly; -} diff --git a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteStatementType.java b/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteStatementType.java deleted file mode 100644 index 5cb412e751..0000000000 --- a/sqlite-android/src/main/java/io/requery/android/database/sqlite/SQLiteStatementType.java +++ /dev/null @@ -1,107 +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.sqlite; - -import java.util.Locale; - -class SQLiteStatementType { - - /** One of the values returned by {@link #getSqlStatementType(String)}. */ - public static final int STATEMENT_SELECT = 1; - /** One of the values returned by {@link #getSqlStatementType(String)}. */ - public static final int STATEMENT_UPDATE = 2; - /** One of the values returned by {@link #getSqlStatementType(String)}. */ - public static final int STATEMENT_ATTACH = 3; - /** One of the values returned by {@link #getSqlStatementType(String)}. */ - public static final int STATEMENT_BEGIN = 4; - /** One of the values returned by {@link #getSqlStatementType(String)}. */ - public static final int STATEMENT_COMMIT = 5; - /** One of the values returned by {@link #getSqlStatementType(String)}. */ - public static final int STATEMENT_ABORT = 6; - /** One of the values returned by {@link #getSqlStatementType(String)}. */ - public static final int STATEMENT_PRAGMA = 7; - /** One of the values returned by {@link #getSqlStatementType(String)}. */ - public static final int STATEMENT_DDL = 8; - /** One of the values returned by {@link #getSqlStatementType(String)}. */ - public static final int STATEMENT_UNPREPARED = 9; - /** One of the values returned by {@link #getSqlStatementType(String)}. */ - public static final int STATEMENT_OTHER = 99; - - private SQLiteStatementType() { - } - - /** - * Returns one of the following which represent the type of the given SQL statement. - *
    - *
  1. {@link #STATEMENT_SELECT}
  2. - *
  3. {@link #STATEMENT_UPDATE}
  4. - *
  5. {@link #STATEMENT_ATTACH}
  6. - *
  7. {@link #STATEMENT_BEGIN}
  8. - *
  9. {@link #STATEMENT_COMMIT}
  10. - *
  11. {@link #STATEMENT_ABORT}
  12. - *
  13. {@link #STATEMENT_OTHER}
  14. - *
- * @param sql the SQL statement whose type is returned by this method - * @return one of the values listed above - */ - public static int getSqlStatementType(String sql) { - sql = sql.trim(); - if (sql.length() < 3) { - return STATEMENT_OTHER; - } - String prefixSql = sql.substring(0, 3); - - if (prefixSql.equalsIgnoreCase("SEL") - || prefixSql.equalsIgnoreCase("WIT")) { - return STATEMENT_SELECT; - } - if (prefixSql.equalsIgnoreCase("INS") - || prefixSql.equalsIgnoreCase("UPD") - || prefixSql.equalsIgnoreCase("REP") - || prefixSql.equalsIgnoreCase("DEL")) { - return STATEMENT_UPDATE; - } - if (prefixSql.equalsIgnoreCase("ATT")) { - return STATEMENT_ATTACH; - } - if (prefixSql.equalsIgnoreCase("COM") - || prefixSql.equalsIgnoreCase("END")) { - return STATEMENT_COMMIT; - } - if (prefixSql.equalsIgnoreCase("ROL")) { - return STATEMENT_ABORT; - } - if (prefixSql.equalsIgnoreCase("BEG")) { - return STATEMENT_BEGIN; - } - if (prefixSql.equalsIgnoreCase("PRA")) { - return STATEMENT_PRAGMA; - } - if (prefixSql.equalsIgnoreCase("CRE") - || prefixSql.equalsIgnoreCase("DRO") - || prefixSql.equalsIgnoreCase("ALT")) { - return STATEMENT_DDL; - } - - if (prefixSql.equalsIgnoreCase("ANA") || prefixSql.equalsIgnoreCase("DET")) { - return STATEMENT_UNPREPARED; - } - - return STATEMENT_OTHER; - } -} diff --git a/sqlite-android/src/main/jni/Android.mk b/sqlite-android/src/main/jni/Android.mk deleted file mode 100644 index e372509cbb..0000000000 --- a/sqlite-android/src/main/jni/Android.mk +++ /dev/null @@ -1,4 +0,0 @@ - -LOCAL_PATH:= $(call my-dir) -include $(LOCAL_PATH)/sqlite/Android.mk - diff --git a/sqlite-android/src/main/jni/Application.mk b/sqlite-android/src/main/jni/Application.mk deleted file mode 100644 index 89c88ffbf3..0000000000 --- a/sqlite-android/src/main/jni/Application.mk +++ /dev/null @@ -1,5 +0,0 @@ -APP_STL:=none -APP_OPTIM := release -APP_ABI := armeabi-v7a,arm64-v8a,x86,x86_64 -NDK_TOOLCHAIN_VERSION := clang -NDK_APP_LIBS_OUT=../jniLibs diff --git a/sqlite-android/src/main/jni/sqlite/ALog-priv.h b/sqlite-android/src/main/jni/sqlite/ALog-priv.h deleted file mode 100644 index 04a0abf398..0000000000 --- a/sqlite-android/src/main/jni/sqlite/ALog-priv.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef NATIVEHELPER_ALOGPRIV_H_ -#define NATIVEHELPER_ALOGPRIV_H_ - -#include - -#ifndef LOG_NDEBUG -#ifdef NDEBUG -#define LOG_NDEBUG 1 -#else -#define LOG_NDEBUG 0 -#endif -#endif - - -/* - * Basic log message macros intended to emulate the behavior of log/log.h - * in system core. This should be dependent only on ndk exposed logging - * functionality. - */ - -#ifndef ALOG -#define ALOG(priority, tag, fmt...) \ - __android_log_print(ANDROID_##priority, tag, fmt) -#endif - -#ifndef ALOGV -#if LOG_NDEBUG -#define ALOGV(...) ((void)0) -#else -#define ALOGV(...) ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) -#endif -#endif - -#ifndef ALOGD -#define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) -#endif - -#ifndef ALOGI -#define ALOGI(...) ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) -#endif - -#ifndef ALOGW -#define ALOGW(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) -#endif - -#ifndef ALOGE -#define ALOGE(...) ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) -#endif - -/* -** Not quite the same as the core android LOG_FATAL_IF (which also -** sends a SIGTRAP), but close enough. -*/ -#define LOG_FATAL_IF(bCond, zErr) if( bCond ) ALOGE(zErr); - -#endif diff --git a/sqlite-android/src/main/jni/sqlite/Android.mk b/sqlite-android/src/main/jni/sqlite/Android.mk deleted file mode 100644 index 5d995ff030..0000000000 --- a/sqlite-android/src/main/jni/sqlite/Android.mk +++ /dev/null @@ -1,67 +0,0 @@ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -# NOTE the following flags, -# SQLITE_TEMP_STORE=3 causes all TEMP files to go into RAM. and thats the behavior we want -# SQLITE_ENABLE_FTS3 enables usage of FTS3 - NOT FTS1 or 2. -# SQLITE_DEFAULT_AUTOVACUUM=1 causes the databases to be subject to auto-vacuum -sqlite_flags := \ - -DNDEBUG=1 \ - -DHAVE_USLEEP=1 \ - -DSQLITE_HAVE_ISNAN \ - -DSQLITE_DEFAULT_JOURNAL_SIZE_LIMIT=1048576 \ - -DSQLITE_THREADSAFE=2 \ - -DSQLITE_TEMP_STORE=3 \ - -DSQLITE_POWERSAFE_OVERWRITE=1 \ - -DSQLITE_DEFAULT_FILE_FORMAT=4 \ - -DSQLITE_DEFAULT_AUTOVACUUM=1 \ - -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 \ - -DSQLITE_ENABLE_FTS3 \ - -DSQLITE_ENABLE_FTS3_PARENTHESIS \ - -DSQLITE_ENABLE_FTS4 \ - -DSQLITE_ENABLE_FTS4_PARENTHESIS \ - -DSQLITE_ENABLE_FTS5 \ - -DSQLITE_ENABLE_FTS5_PARENTHESIS \ - -D_SQLITE_ENABLE_JSON1 \ - -D_SQLITE_ENABLE_RTREE=1 \ - -DSQLITE_OMIT_BUILTIN_TEST \ - -D_SQLITE_OMIT_COMPILEOPTION_DIAGS \ - -DSQLITE_DEFAULT_FILE_PERMISSIONS=0600 \ - -DSQLITE_DEFAULT_MEMSTATUS=0 \ - -DSQLITE_MAX_EXPR_DEPTH=0 \ - -D_SQLITE_USE_ALLOCA \ - -DSQLITE_ENABLE_BATCH_ATOMIC_WRITE \ - -O3 - -LOCAL_CFLAGS += $(sqlite_flags) -LOCAL_CFLAGS += -Wno-unused-parameter -Wno-int-to-pointer-cast -LOCAL_CFLAGS += -Wno-uninitialized -Wno-parentheses -LOCAL_CPPFLAGS += -Wno-conversion-null - - -ifeq ($(TARGET_ARCH), arm) - LOCAL_CFLAGS += -DPACKED="__attribute__ ((packed))" -else - LOCAL_CFLAGS += -DPACKED="" -endif - -LOCAL_SRC_FILES:= \ - android_database_SQLiteCommon.cpp \ - android_database_SQLiteConnection.cpp \ - android_database_SQLiteFunction.cpp \ - android_database_SQLiteGlobal.cpp \ - android_database_SQLiteDebug.cpp \ - android_database_CursorWindow.cpp \ - CursorWindow.cpp \ - JNIHelp.cpp \ - JNIString.cpp - -LOCAL_SRC_FILES += sqlite3.c - -LOCAL_C_INCLUDES += $(LOCAL_PATH) - -LOCAL_MODULE:= libsqlite3x -LOCAL_LDLIBS += -ldl -llog -latomic - -include $(BUILD_SHARED_LIBRARY) - diff --git a/sqlite-android/src/main/jni/sqlite/CursorWindow.cpp b/sqlite-android/src/main/jni/sqlite/CursorWindow.cpp deleted file mode 100644 index 3e57543914..0000000000 --- a/sqlite-android/src/main/jni/sqlite/CursorWindow.cpp +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2006-2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - // modified from original source see README at the top level of this project - -#undef LOG_TAG -#define LOG_TAG "CursorWindow" - -#include "CursorWindow.h" -#include "ALog-priv.h" - -#include -#include -#include - -namespace android { - -CursorWindow::CursorWindow(const char* name, void* data, size_t size, bool readOnly) : - mData(data), mSize(size), mReadOnly(readOnly) { - mName = strdup(name); - mHeader = static_cast(mData); -} - -CursorWindow::~CursorWindow() { - free(mName); - free(mData); -} - -status_t CursorWindow::create(const char* name, size_t size, CursorWindow** outWindow) { - status_t result; - void* data = malloc(size); - if (!data) { - return NO_MEMORY; - } - CursorWindow* window = new CursorWindow(name, data, size, false); - result = window->clear(); - if (!result) { - LOG_WINDOW("Created new CursorWindow: freeOffset=%d, " - "numRows=%d, numColumns=%d, mSize=%d, mData=%p", - window->mHeader->freeOffset, - window->mHeader->numRows, - window->mHeader->numColumns, - window->mSize, window->mData); - *outWindow = window; - return OK; - } - delete window; - return result; -} - -status_t CursorWindow::clear() { - if (mReadOnly) { - return INVALID_OPERATION; - } - - mHeader->freeOffset = sizeof(Header) + sizeof(RowSlotChunk); - mHeader->firstChunkOffset = sizeof(Header); - mHeader->numRows = 0; - mHeader->numColumns = 0; - - RowSlotChunk* firstChunk = static_cast(offsetToPtr(mHeader->firstChunkOffset)); - firstChunk->nextChunkOffset = 0; - return OK; -} - -status_t CursorWindow::setNumColumns(uint32_t numColumns) { - if (mReadOnly) { - return INVALID_OPERATION; - } - - uint32_t cur = mHeader->numColumns; - if ((cur > 0 || mHeader->numRows > 0) && cur != numColumns) { - ALOGE("Trying to go from %d columns to %d", cur, numColumns); - return INVALID_OPERATION; - } - mHeader->numColumns = numColumns; - return OK; -} - -status_t CursorWindow::allocRow() { - if (mReadOnly) { - return INVALID_OPERATION; - } - - // Fill in the row slot - RowSlot* rowSlot = allocRowSlot(); - if (rowSlot == NULL) { - return NO_MEMORY; - } - - // Allocate the slots for the field directory - size_t fieldDirSize = mHeader->numColumns * sizeof(FieldSlot); - uint32_t fieldDirOffset = alloc(fieldDirSize, true /*aligned*/); - if (!fieldDirOffset) { - mHeader->numRows--; - LOG_WINDOW("The row failed, so back out the new row accounting " - "from allocRowSlot %d", mHeader->numRows); - return NO_MEMORY; - } - FieldSlot* fieldDir = static_cast(offsetToPtr(fieldDirOffset)); - memset(fieldDir, 0, fieldDirSize); - - //LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %d bytes at offset %u\n", - // mHeader->numRows - 1, offsetFromPtr(rowSlot), fieldDirSize, fieldDirOffset); - rowSlot->offset = fieldDirOffset; - return OK; -} - -status_t CursorWindow::freeLastRow() { - if (mReadOnly) { - return INVALID_OPERATION; - } - - if (mHeader->numRows > 0) { - mHeader->numRows--; - } - return OK; -} - -uint32_t CursorWindow::alloc(size_t size, bool aligned) { - uint32_t padding; - if (aligned) { - // 4 byte alignment - padding = (~mHeader->freeOffset + 1) & 3; - } else { - padding = 0; - } - - uint32_t offset = mHeader->freeOffset + padding; - uint32_t nextFreeOffset = offset + size; - if (nextFreeOffset > mSize) { - ALOGW("Window is full: requested allocation %zu bytes, " - "free space %zu bytes, window size %zu bytes", - size, freeSpace(), mSize); - return 0; - } - - mHeader->freeOffset = nextFreeOffset; - return offset; -} - -CursorWindow::RowSlot* CursorWindow::getRowSlot(uint32_t row) { - uint32_t chunkPos = row; - RowSlotChunk* chunk = static_cast( - offsetToPtr(mHeader->firstChunkOffset)); - while (chunkPos >= ROW_SLOT_CHUNK_NUM_ROWS) { - chunk = static_cast(offsetToPtr(chunk->nextChunkOffset)); - chunkPos -= ROW_SLOT_CHUNK_NUM_ROWS; - } - return &chunk->slots[chunkPos]; -} - -CursorWindow::RowSlot* CursorWindow::allocRowSlot() { - uint32_t chunkPos = mHeader->numRows; - RowSlotChunk* chunk = static_cast( - offsetToPtr(mHeader->firstChunkOffset)); - while (chunkPos > ROW_SLOT_CHUNK_NUM_ROWS) { - chunk = static_cast(offsetToPtr(chunk->nextChunkOffset)); - chunkPos -= ROW_SLOT_CHUNK_NUM_ROWS; - } - if (chunkPos == ROW_SLOT_CHUNK_NUM_ROWS) { - if (!chunk->nextChunkOffset) { - chunk->nextChunkOffset = alloc(sizeof(RowSlotChunk), true /*aligned*/); - if (!chunk->nextChunkOffset) { - return NULL; - } - } - chunk = static_cast(offsetToPtr(chunk->nextChunkOffset)); - chunk->nextChunkOffset = 0; - chunkPos = 0; - } - mHeader->numRows += 1; - return &chunk->slots[chunkPos]; -} - -CursorWindow::FieldSlot* CursorWindow::getFieldSlot(uint32_t row, uint32_t column) { - if (row >= mHeader->numRows || column >= mHeader->numColumns) { - ALOGE("Failed to read row %d, column %d from a CursorWindow which " - "has %d rows, %d columns.", - row, column, mHeader->numRows, mHeader->numColumns); - return NULL; - } - RowSlot* rowSlot = getRowSlot(row); - if (!rowSlot) { - ALOGE("Failed to find rowSlot for row %d.", row); - return NULL; - } - FieldSlot* fieldDir = static_cast(offsetToPtr(rowSlot->offset)); - return &fieldDir[column]; -} - -status_t CursorWindow::putBlob(uint32_t row, uint32_t column, const void* value, size_t size) { - return putBlobOrString(row, column, value, size, FIELD_TYPE_BLOB); -} - -status_t CursorWindow::putString(uint32_t row, uint32_t column, const char* value, - size_t sizeIncludingNull) { - return putBlobOrString(row, column, value, sizeIncludingNull, FIELD_TYPE_STRING); -} - -status_t CursorWindow::putBlobOrString(uint32_t row, uint32_t column, - const void* value, size_t size, int32_t type) { - if (mReadOnly) { - return INVALID_OPERATION; - } - - FieldSlot* fieldSlot = getFieldSlot(row, column); - if (!fieldSlot) { - return BAD_VALUE; - } - - uint32_t offset = alloc(size); - if (!offset) { - return NO_MEMORY; - } - - memcpy(offsetToPtr(offset), value, size); - - fieldSlot->type = type; - fieldSlot->data.buffer.offset = offset; - fieldSlot->data.buffer.size = size; - return OK; -} - -status_t CursorWindow::putLong(uint32_t row, uint32_t column, int64_t value) { - if (mReadOnly) { - return INVALID_OPERATION; - } - - FieldSlot* fieldSlot = getFieldSlot(row, column); - if (!fieldSlot) { - return BAD_VALUE; - } - - fieldSlot->type = FIELD_TYPE_INTEGER; - fieldSlot->data.l = value; - return OK; -} - -status_t CursorWindow::putDouble(uint32_t row, uint32_t column, double value) { - if (mReadOnly) { - return INVALID_OPERATION; - } - - FieldSlot* fieldSlot = getFieldSlot(row, column); - if (!fieldSlot) { - return BAD_VALUE; - } - - fieldSlot->type = FIELD_TYPE_FLOAT; - fieldSlot->data.d = value; - return OK; -} - -status_t CursorWindow::putNull(uint32_t row, uint32_t column) { - if (mReadOnly) { - return INVALID_OPERATION; - } - - FieldSlot* fieldSlot = getFieldSlot(row, column); - if (!fieldSlot) { - return BAD_VALUE; - } - - fieldSlot->type = FIELD_TYPE_NULL; - fieldSlot->data.buffer.offset = 0; - fieldSlot->data.buffer.size = 0; - return OK; -} - -}; // namespace android diff --git a/sqlite-android/src/main/jni/sqlite/CursorWindow.h b/sqlite-android/src/main/jni/sqlite/CursorWindow.h deleted file mode 100644 index aceeb6347e..0000000000 --- a/sqlite-android/src/main/jni/sqlite/CursorWindow.h +++ /dev/null @@ -1,188 +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 - -#ifndef _ANDROID__DATABASE_WINDOW_H -#define _ANDROID__DATABASE_WINDOW_H - -#include "ALog-priv.h" -#include -#include - -#include "Errors.h" - -#if LOG_NDEBUG - -#define IF_LOG_WINDOW() if (false) -#define LOG_WINDOW(...) - -#else - -#define IF_LOG_WINDOW() IF_ALOG(LOG_DEBUG, "CursorWindow") -#define LOG_WINDOW(...) ALOG(LOG_DEBUG, "CursorWindow", __VA_ARGS__) - -#endif - -namespace android { - -/** - * This class stores a set of rows from a database in a buffer. The beginning of the - * window has first chunk of RowSlots, which are offsets to the row directory, followed by - * an offset to the next chunk in a linked-list of additional chunk of RowSlots in case - * the pre-allocated chunk isn't big enough to refer to all rows. Each row directory has a - * FieldSlot per column, which has the size, offset, and type of the data for that field. - * Note that the data types come from sqlite3.h. - * - * Strings are stored in UTF-8. - */ -class CursorWindow { - CursorWindow(const char* name, void* data, size_t size, bool readOnly); - -public: - /* Field types. */ - enum { - FIELD_TYPE_NULL = 0, - FIELD_TYPE_INTEGER = 1, - FIELD_TYPE_FLOAT = 2, - FIELD_TYPE_STRING = 3, - FIELD_TYPE_BLOB = 4, - }; - - /* Opaque type that describes a field slot. */ - struct FieldSlot { - private: - int32_t type; - union { - double d; - int64_t l; - struct { - uint32_t offset; - uint32_t size; - } buffer; - } data; - - friend class CursorWindow; - } __attribute((packed)); - - ~CursorWindow(); - - static status_t create(const char* name, size_t size, CursorWindow** outCursorWindow); - - inline const char* name() { return mName; } - inline size_t size() { return mSize; } - inline size_t freeSpace() { return mSize - mHeader->freeOffset; } - inline uint32_t getNumRows() { return mHeader->numRows; } - inline uint32_t getNumColumns() { return mHeader->numColumns; } - - status_t clear(); - status_t setNumColumns(uint32_t numColumns); - - /** - * Allocate a row slot and its directory. - * The row is initialized will null entries for each field. - */ - status_t allocRow(); - status_t freeLastRow(); - - status_t putBlob(uint32_t row, uint32_t column, const void* value, size_t size); - status_t putString(uint32_t row, uint32_t column, const char* value, size_t sizeIncludingNull); - status_t putLong(uint32_t row, uint32_t column, int64_t value); - status_t putDouble(uint32_t row, uint32_t column, double value); - status_t putNull(uint32_t row, uint32_t column); - - /** - * Gets the field slot at the specified row and column. - * Returns null if the requested row or column is not in the window. - */ - FieldSlot* getFieldSlot(uint32_t row, uint32_t column); - - inline int32_t getFieldSlotType(FieldSlot* fieldSlot) { - return fieldSlot->type; - } - - inline int64_t getFieldSlotValueLong(FieldSlot* fieldSlot) { - return fieldSlot->data.l; - } - - inline double getFieldSlotValueDouble(FieldSlot* fieldSlot) { - return fieldSlot->data.d; - } - - inline const char* getFieldSlotValueString(FieldSlot* fieldSlot, - size_t* outSizeIncludingNull) { - *outSizeIncludingNull = fieldSlot->data.buffer.size; - return static_cast(offsetToPtr(fieldSlot->data.buffer.offset)); - } - - inline const void* getFieldSlotValueBlob(FieldSlot* fieldSlot, size_t* outSize) { - *outSize = fieldSlot->data.buffer.size; - return offsetToPtr(fieldSlot->data.buffer.offset); - } - -private: - static const size_t ROW_SLOT_CHUNK_NUM_ROWS = 100; - - struct Header { - // Offset of the lowest unused byte in the window. - uint32_t freeOffset; - - // Offset of the first row slot chunk. - uint32_t firstChunkOffset; - - uint32_t numRows; - uint32_t numColumns; - }; - - struct RowSlot { - uint32_t offset; - }; - - struct RowSlotChunk { - RowSlot slots[ROW_SLOT_CHUNK_NUM_ROWS]; - uint32_t nextChunkOffset; - }; - - char* mName; - void* mData; - size_t mSize; - bool mReadOnly; - Header* mHeader; - - inline void* offsetToPtr(uint32_t offset) { - return static_cast(mData) + offset; - } - - inline uint32_t offsetFromPtr(void* ptr) { - return static_cast(ptr) - static_cast(mData); - } - - /** - * Allocate a portion of the window. Returns the offset - * of the allocation, or 0 if there isn't enough space. - * If aligned is true, the allocation gets 4 byte alignment. - */ - uint32_t alloc(size_t size, bool aligned = false); - - RowSlot* getRowSlot(uint32_t row); - RowSlot* allocRowSlot(); - - status_t putBlobOrString(uint32_t row, uint32_t column, - const void* value, size_t size, int32_t type); -}; - -}; // namespace android - -#endif diff --git a/sqlite-android/src/main/jni/sqlite/Errors.h b/sqlite-android/src/main/jni/sqlite/Errors.h deleted file mode 100644 index 0b75b1926c..0000000000 --- a/sqlite-android/src/main/jni/sqlite/Errors.h +++ /dev/null @@ -1,88 +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. - */ - -#ifndef ANDROID_ERRORS_H -#define ANDROID_ERRORS_H - -#include -#include - -namespace android { - -// use this type to return error codes -#ifdef HAVE_MS_C_RUNTIME -typedef int status_t; -#else -typedef int32_t status_t; -#endif - -/* the MS C runtime lacks a few error codes */ - -/* - * Error codes. - * All error codes are negative values. - */ - -// Win32 #defines NO_ERROR as well. It has the same value, so there's no -// real conflict, though it's a bit awkward. -#ifdef _WIN32 -# undef NO_ERROR -#endif - -enum { - OK = 0, // Everything's swell. - NO_ERROR = 0, // No errors. - - UNKNOWN_ERROR = 0x80000000, - - NO_MEMORY = -ENOMEM, - INVALID_OPERATION = -ENOSYS, - BAD_VALUE = -EINVAL, - BAD_TYPE = 0x80000001, - NAME_NOT_FOUND = -ENOENT, - PERMISSION_DENIED = -EPERM, - NO_INIT = -ENODEV, - ALREADY_EXISTS = -EEXIST, - DEAD_OBJECT = -EPIPE, - FAILED_TRANSACTION = 0x80000002, - JPARKS_BROKE_IT = -EPIPE, -#if !defined(HAVE_MS_C_RUNTIME) - BAD_INDEX = -EOVERFLOW, - NOT_ENOUGH_DATA = -ENODATA, - WOULD_BLOCK = -EWOULDBLOCK, - TIMED_OUT = -ETIMEDOUT, - UNKNOWN_TRANSACTION = -EBADMSG, -#else - BAD_INDEX = -E2BIG, - NOT_ENOUGH_DATA = 0x80000003, - WOULD_BLOCK = 0x80000004, - TIMED_OUT = 0x80000005, - UNKNOWN_TRANSACTION = 0x80000006, -#endif - FDS_NOT_ALLOWED = 0x80000007, -}; - -// Restore define; enumeration is in "android" namespace, so the value defined -// there won't work for Win32 code in a different namespace. -#ifdef _WIN32 -# define NO_ERROR 0L -#endif - -}; // namespace android - -// --------------------------------------------------------------------------- - -#endif // ANDROID_ERRORS_H diff --git a/sqlite-android/src/main/jni/sqlite/JNIHelp.cpp b/sqlite-android/src/main/jni/sqlite/JNIHelp.cpp deleted file mode 100644 index c153429616..0000000000 --- a/sqlite-android/src/main/jni/sqlite/JNIHelp.cpp +++ /dev/null @@ -1,217 +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. - */ - -#define LOG_TAG "JNIHelp" - -#include "JNIHelp.h" -#include "ALog-priv.h" - -#include -#include -#include -#include - -/** - * Equivalent to ScopedLocalRef, but for C_JNIEnv instead. (And slightly more powerful.) - */ -template -class scoped_local_ref { -public: - scoped_local_ref(C_JNIEnv* env, T localRef = NULL) - : mEnv(env), mLocalRef(localRef) - { - } - - ~scoped_local_ref() { - reset(); - } - - void reset(T localRef = NULL) { - if (mLocalRef != NULL) { - (*mEnv)->DeleteLocalRef(reinterpret_cast(mEnv), mLocalRef); - mLocalRef = localRef; - } - } - - T get() const { - return mLocalRef; - } - -private: - C_JNIEnv* mEnv; - T mLocalRef; - - // Disallow copy and assignment. - scoped_local_ref(const scoped_local_ref&); - void operator=(const scoped_local_ref&); -}; - -static jclass findClass(C_JNIEnv* env, const char* className) { - JNIEnv* e = reinterpret_cast(env); - return (*env)->FindClass(e, className); -} - -extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, - const JNINativeMethod* gMethods, int numMethods) -{ - JNIEnv* e = reinterpret_cast(env); - - ALOGV("Registering %s's %d native methods...", className, numMethods); - - scoped_local_ref c(env, findClass(env, className)); - if (c.get() == NULL) { - char* msg; - asprintf(&msg, "Native registration unable to find class '%s'; aborting...", className); - e->FatalError(msg); - } - - if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) { - char* msg; - asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className); - e->FatalError(msg); - } - - return 0; -} - -/* - * Returns a human-readable summary of an exception object. The buffer will - * be populated with the "binary" class name and, if present, the - * exception message. - */ -static bool logExceptionSummary(C_JNIEnv *env, jthrowable exception, - const char* exceptionClassName) { - JNIEnv* e = reinterpret_cast(env); - - /* get the name of the exception's class */ - scoped_local_ref exceptionClass(env, (*env)->GetObjectClass(e, exception)); // can't fail - scoped_local_ref classClass(env, - (*env)->GetObjectClass(e, exceptionClass.get())); // java.lang.Class, can't fail - jmethodID classGetNameMethod = - (*env)->GetMethodID(e, classClass.get(), "getName", "()Ljava/lang/String;"); - scoped_local_ref classNameStr(env, - (jstring) (*env)->CallObjectMethod(e, exceptionClass.get(), classGetNameMethod)); - if (classNameStr.get() == NULL) { - (*env)->ExceptionClear(e); - ALOGW("Discarding pending exception (%s) to throw %s", "", - exceptionClassName); - return false; - } - const char* classNameChars = (*env)->GetStringUTFChars(e, classNameStr.get(), NULL); - if (classNameChars == NULL) { - (*env)->ExceptionClear(e); - ALOGW("Discarding pending exception (%s) to throw %s", "", - exceptionClassName); - return false; - } - (*env)->ReleaseStringUTFChars(e, classNameStr.get(), classNameChars); - - /* if the exception has a detail message, get that */ - jmethodID getMessage = - (*env)->GetMethodID(e, exceptionClass.get(), "getMessage", "()Ljava/lang/String;"); - scoped_local_ref messageStr(env, - (jstring) (*env)->CallObjectMethod(e, exception, getMessage)); - if (messageStr.get() == NULL) { - return true; - } - - const char* messageChars = (*env)->GetStringUTFChars(e, messageStr.get(), NULL); - if (messageChars != NULL) { - ALOGW("Discarding pending exception (%s: %s) to throw %s", - classNameChars, - messageChars, - exceptionClassName); - (*env)->ReleaseStringUTFChars(e, messageStr.get(), messageChars); - } else { - ALOGW("Discarding pending exception (%s: ) to throw %s", - classNameChars, - exceptionClassName); - (*env)->ExceptionClear(e); // clear OOM - } - - return true; -} - -extern "C" int jniThrowException(C_JNIEnv* env, const char* className, const char* msg) { - JNIEnv* e = reinterpret_cast(env); - - if ((*env)->ExceptionCheck(e)) { - /* TODO: consider creating the new exception with this as "cause" */ - scoped_local_ref exception(env, (*env)->ExceptionOccurred(e)); - (*env)->ExceptionClear(e); - - if (exception.get() != NULL) { - logExceptionSummary(env, exception.get(), className); - } - } - - scoped_local_ref exceptionClass(env, findClass(env, className)); - if (exceptionClass.get() == NULL) { - ALOGE("Unable to find exception class %s", className); - /* ClassNotFoundException now pending */ - return -1; - } - - if ((*env)->ThrowNew(e, exceptionClass.get(), msg) != JNI_OK) { - ALOGE("Failed throwing '%s' '%s'", className, msg); - /* an exception, most likely OOM, will now be pending */ - return -1; - } - - return 0; -} - -int jniThrowExceptionFmt(C_JNIEnv* env, const char* className, const char* fmt, va_list args) { - char msgBuf[512]; - vsnprintf(msgBuf, sizeof(msgBuf), fmt, args); - return jniThrowException(env, className, msgBuf); -} - -int jniThrowNullPointerException(C_JNIEnv* env, const char* msg) { - return jniThrowException(env, "java/lang/NullPointerException", msg); -} - -int jniThrowRuntimeException(C_JNIEnv* env, const char* msg) { - return jniThrowException(env, "java/lang/RuntimeException", msg); -} - -int jniThrowIOException(C_JNIEnv* env, int errnum) { - char buffer[80]; - const char* message = jniStrError(errnum, buffer, sizeof(buffer)); - return jniThrowException(env, "java/io/IOException", message); -} - -const char* jniStrError(int errnum, char* buf, size_t buflen) { -#if __GLIBC__ - // Note: glibc has a nonstandard strerror_r that returns char* rather than POSIX's int. - // char *strerror_r(int errnum, char *buf, size_t n); - return strerror_r(errnum, buf, buflen); -#else - int rc = strerror_r(errnum, buf, buflen); - if (rc != 0) { - // (POSIX only guarantees a value other than 0. The safest - // way to implement this function is to use C++ and overload on the - // type of strerror_r to accurately distinguish GNU from POSIX.) - snprintf(buf, buflen, "errno %d", errnum); - } - return buf; -#endif -} - -void* operator new (size_t size) { return malloc(size); } -void* operator new [] (size_t size) { return malloc(size); } -void operator delete (void* pointer) { free(pointer); } -void operator delete [] (void* pointer) { free(pointer); } diff --git a/sqlite-android/src/main/jni/sqlite/JNIHelp.h b/sqlite-android/src/main/jni/sqlite/JNIHelp.h deleted file mode 100644 index 33a836fe83..0000000000 --- a/sqlite-android/src/main/jni/sqlite/JNIHelp.h +++ /dev/null @@ -1,178 +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. - */ - -/* - * JNI helper functions. - * - * This file may be included by C or C++ code, which is trouble because jni.h - * uses different typedefs for JNIEnv in each language. - * - * TODO: remove C support. - */ -#ifndef NATIVEHELPER_JNIHELP_H_ -#define NATIVEHELPER_JNIHELP_H_ - -#include "jni.h" -#include - -#ifndef NELEM -# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * Register one or more native methods with a particular class. - * "className" looks like "java/lang/String". Aborts on failure. - * TODO: fix all callers and change the return type to void. - */ -int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods); - -/* - * Throw an exception with the specified class and an optional message. - * - * The "className" argument will be passed directly to FindClass, which - * takes strings with slashes (e.g. "java/lang/Object"). - * - * If an exception is currently pending, we log a warning message and - * clear it. - * - * Returns 0 on success, nonzero if something failed (e.g. the exception - * class couldn't be found, so *an* exception will still be pending). - * - * Currently aborts the VM if it can't throw the exception. - */ -int jniThrowException(C_JNIEnv* env, const char* className, const char* msg); - -/* - * Throw a java.lang.NullPointerException, with an optional message. - */ -int jniThrowNullPointerException(C_JNIEnv* env, const char* msg); - -/* - * Throw a java.lang.RuntimeException, with an optional message. - */ -int jniThrowRuntimeException(C_JNIEnv* env, const char* msg); - -/* - * Throw a java.io.IOException, generating the message from errno. - */ -int jniThrowIOException(C_JNIEnv* env, int errnum); - -/* - * Return a pointer to a locale-dependent error string explaining errno - * value 'errnum'. The returned pointer may or may not be equal to 'buf'. - * This function is thread-safe (unlike strerror) and portable (unlike - * strerror_r). - */ -const char* jniStrError(int errnum, char* buf, size_t buflen); - -/* - * Returns a new java.io.FileDescriptor for the given int fd. - */ -jobject jniCreateFileDescriptor(C_JNIEnv* env, int fd); - -/* - * Returns the int fd from a java.io.FileDescriptor. - */ -int jniGetFDFromFileDescriptor(C_JNIEnv* env, jobject fileDescriptor); - -/* - * Sets the int fd in a java.io.FileDescriptor. - */ -void jniSetFileDescriptorOfFD(C_JNIEnv* env, jobject fileDescriptor, int value); - -/* - * Returns the reference from a java.lang.ref.Reference. - */ -jobject jniGetReferent(C_JNIEnv* env, jobject ref); - -#ifdef __cplusplus -} -#endif - - -/* - * For C++ code, we provide inlines that map to the C functions. g++ always - * inlines these, even on non-optimized builds. - */ -#if defined(__cplusplus) -inline int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { - return jniRegisterNativeMethods(&env->functions, className, gMethods, numMethods); -} - -inline int jniThrowException(JNIEnv* env, const char* className, const char* msg) { - return jniThrowException(&env->functions, className, msg); -} - -extern "C" int jniThrowExceptionFmt(C_JNIEnv* env, const char* className, const char* fmt, va_list args); - -/* - * Equivalent to jniThrowException but with a printf-like format string and - * variable-length argument list. This is only available in C++. - */ -inline int jniThrowExceptionFmt(JNIEnv* env, const char* className, const char* fmt, ...) { - va_list args; - va_start(args, fmt); - return jniThrowExceptionFmt(&env->functions, className, fmt, args); - va_end(args); -} - -inline int jniThrowNullPointerException(JNIEnv* env, const char* msg) { - return jniThrowNullPointerException(&env->functions, msg); -} - -inline int jniThrowRuntimeException(JNIEnv* env, const char* msg) { - return jniThrowRuntimeException(&env->functions, msg); -} - -inline int jniThrowIOException(JNIEnv* env, int errnum) { - return jniThrowIOException(&env->functions, errnum); -} - -inline jobject jniCreateFileDescriptor(JNIEnv* env, int fd) { - return jniCreateFileDescriptor(&env->functions, fd); -} - -inline int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor) { - return jniGetFDFromFileDescriptor(&env->functions, fileDescriptor); -} - -inline void jniSetFileDescriptorOfFD(JNIEnv* env, jobject fileDescriptor, int value) { - jniSetFileDescriptorOfFD(&env->functions, fileDescriptor, value); -} - -inline jobject jniGetReferent(JNIEnv* env, jobject ref) { - return jniGetReferent(&env->functions, ref); -} - -#endif - -#define FIND_CLASS(var, className) \ - var = env->FindClass(className); \ - LOG_FATAL_IF(! var, "Unable to find class " className); - -#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \ - var = env->GetMethodID(clazz, methodName, fieldDescriptor); \ - LOG_FATAL_IF(! var, "Unable to find method" methodName); - -#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ - var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ - LOG_FATAL_IF(! var, "Unable to find field " fieldName); - -#endif /* NATIVEHELPER_JNIHELP_H_ */ diff --git a/sqlite-android/src/main/jni/sqlite/JNIString.cpp b/sqlite-android/src/main/jni/sqlite/JNIString.cpp deleted file mode 100644 index ca758b1e96..0000000000 --- a/sqlite-android/src/main/jni/sqlite/JNIString.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Note this code is adapted from AOSP implementation of String, now located at -// https://android.googlesource.com/platform/libcore/+/master/libart/src/main/java/java/lang/StringFactory.java - -#include - -#define REPLACEMENT_CHAR 0xfffd; - -namespace android { - -jsize utf8ToJavaCharArray(const char* d, jchar v[], jint byteCount) { - jint idx = 0; - jint last = byteCount; - jint s = 0; -outer: - while (idx < last) { - jbyte b0 = d[idx++]; - if ((b0 & 0x80) == 0) { - // 0xxxxxxx - // Range: U-00000000 - U-0000007F - jint val = b0 & 0xff; - v[s++] = (jchar) val; - } else if (((b0 & 0xe0) == 0xc0) || ((b0 & 0xf0) == 0xe0) || - ((b0 & 0xf8) == 0xf0) || ((b0 & 0xfc) == 0xf8) || ((b0 & 0xfe) == 0xfc)) { - jint utfCount = 1; - if ((b0 & 0xf0) == 0xe0) utfCount = 2; - else if ((b0 & 0xf8) == 0xf0) utfCount = 3; - else if ((b0 & 0xfc) == 0xf8) utfCount = 4; - else if ((b0 & 0xfe) == 0xfc) utfCount = 5; - - // 110xxxxx (10xxxxxx)+ - // Range: U-00000080 - U-000007FF (count == 1) - // Range: U-00000800 - U-0000FFFF (count == 2) - // Range: U-00010000 - U-001FFFFF (count == 3) - // Range: U-00200000 - U-03FFFFFF (count == 4) - // Range: U-04000000 - U-7FFFFFFF (count == 5) - - if (idx + utfCount > last) { - v[s++] = REPLACEMENT_CHAR; - continue; - } - - // Extract usable bits from b0 - jint val = b0 & (0x1f >> (utfCount - 1)); - for (int i = 0; i < utfCount; ++i) { - jbyte b = d[idx++]; - if ((b & 0xc0) != 0x80) { - v[s++] = REPLACEMENT_CHAR; - idx--; // Put the input char back - goto outer; - } - // Push new bits in from the right side - val <<= 6; - val |= b & 0x3f; - } - - // Note: Java allows overlong char - // specifications To disallow, check that val - // is greater than or equal to the minimum - // value for each count: - // - // count min value - // ----- ---------- - // 1 0x80 - // 2 0x800 - // 3 0x10000 - // 4 0x200000 - // 5 0x4000000 - - // Allow surrogate values (0xD800 - 0xDFFF) to - // be specified using 3-byte UTF values only - if ((utfCount != 2) && (val >= 0xD800) && (val <= 0xDFFF)) { - v[s++] = REPLACEMENT_CHAR; - continue; - } - - // Reject chars greater than the Unicode maximum of U+10FFFF. - if (val > 0x10FFFF) { - v[s++] = REPLACEMENT_CHAR; - continue; - } - - // Encode chars from U+10000 up as surrogate pairs - if (val < 0x10000) { - v[s++] = (jchar) val; - } else { - int x = val & 0xffff; - int u = (val >> 16) & 0x1f; - int w = (u - 1) & 0xffff; - int hi = 0xd800 | (w << 6) | (x >> 10); - int lo = 0xdc00 | (x & 0x3ff); - v[s++] = (jchar) hi; - v[s++] = (jchar) lo; - } - } else { - // Illegal values 0x8*, 0x9*, 0xa*, 0xb*, 0xfd-0xff - v[s++] = REPLACEMENT_CHAR; - } - } - return s; -} -} \ No newline at end of file diff --git a/sqlite-android/src/main/jni/sqlite/README b/sqlite-android/src/main/jni/sqlite/README deleted file mode 100644 index da09588fda..0000000000 --- a/sqlite-android/src/main/jni/sqlite/README +++ /dev/null @@ -1,32 +0,0 @@ - -All the files in this directory are copied from stock android. The following -files: - -JNIHelp.cpp -ALog-priv.h - -are copied in from Android's libnativehelper module (altogether less than 1000 -lines of code). The remainder are from the core framework (directory -/frameworks/base/core/jni). - -Notes on changes: - -The ashmem_XXX() interfaces are used for the various "xxxForBlobDescriptor()" -API functions. The code in libcutils for this seems to be platform -dependent - some platforms have kernel support, others have a user space -implementation. So these functions are not supported for now. - -The original SQLiteConnection.cpp uses AndroidRuntime::genJNIEnv() to obtain a -pointer to the current threads environment. Changed to store a pointer to the -process JavaVM (Android allows only one) as a global variable. Then retrieve -the JNIEnv as needed using GetEnv(). - -Replaced uses of class String8 with std::string in SQLiteConnection.cpp and a -few other places. - -The "LOCALIZED" collation and some miscellaneous user-functions added by the -sqlite3_android.cpp module are not included. A collation called LOCALIZED -that is equivalent to BINARY is added instead to keep various things working. -This should not cause serious problems - class SQLiteConnection always -runs "REINDEX LOCALIZED" immediately after opening a connection. - diff --git a/sqlite-android/src/main/jni/sqlite/android_database_CursorWindow.cpp b/sqlite-android/src/main/jni/sqlite/android_database_CursorWindow.cpp deleted file mode 100644 index cda6093037..0000000000 --- a/sqlite-android/src/main/jni/sqlite/android_database_CursorWindow.cpp +++ /dev/null @@ -1,412 +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 - -#undef LOG_TAG -#define LOG_TAG "CursorWindow" -#define __STDC_FORMAT_MACROS - -#include -#include -#include -#include -#include -#include - -#include "CursorWindow.h" -#include "android_database_SQLiteCommon.h" - -namespace android { - -static struct { - jfieldID data; - jfieldID sizeCopied; -} gCharArrayBufferClassInfo; - -static jstring gEmptyString = NULL; - -static void throwExceptionWithRowCol(JNIEnv* env, jint row, jint column) { - char buf[64]; - snprintf(buf, sizeof(buf), "Couldn't read row %d column %d", row, column); - jniThrowException(env, "java/lang/IllegalStateException", buf); -} - -static void throwUnknownTypeException(JNIEnv * env, jint type) { - char buf[32]; - snprintf(buf, sizeof(buf), "UNKNOWN type %d", type); - jniThrowException(env, "java/lang/IllegalStateException", buf); -} - -static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj, jint cursorWindowSize) { - CursorWindow* window; - const char* nameStr = env->GetStringUTFChars(nameObj, NULL); - status_t status = CursorWindow::create(nameStr, cursorWindowSize, &window); - env->ReleaseStringUTFChars(nameObj, nameStr); - - if (status || !window) { - ALOGE("Could not allocate CursorWindow of size %d due to error %d.", - cursorWindowSize, status); - return 0; - } - - LOG_WINDOW("nativeInitializeEmpty: window = %p", window); - return reinterpret_cast(window); -} - -static void nativeDispose(JNIEnv* env, jclass clazz, jlong windowPtr) { - CursorWindow* window = reinterpret_cast(windowPtr); - if (window) { - LOG_WINDOW("Closing window %p", window); - delete window; - } -} - -static jstring nativeGetName(JNIEnv* env, jclass clazz, jlong windowPtr) { - CursorWindow* window = reinterpret_cast(windowPtr); - return env->NewStringUTF(window->name()); -} - -static void nativeClear(JNIEnv * env, jclass clazz, jlong windowPtr) { - CursorWindow* window = reinterpret_cast(windowPtr); - LOG_WINDOW("Clearing window %p", window); - status_t status = window->clear(); - if (status) { - LOG_WINDOW("Could not clear window. error=%d", status); - } -} - -static jint nativeGetNumRows(JNIEnv* env, jclass clazz, jlong windowPtr) { - CursorWindow* window = reinterpret_cast(windowPtr); - return window->getNumRows(); -} - -static jboolean nativeSetNumColumns(JNIEnv* env, jclass clazz, jlong windowPtr, - jint columnNum) { - CursorWindow* window = reinterpret_cast(windowPtr); - status_t status = window->setNumColumns(columnNum); - return status == OK; -} - -static jboolean nativeAllocRow(JNIEnv* env, jclass clazz, jlong windowPtr) { - CursorWindow* window = reinterpret_cast(windowPtr); - status_t status = window->allocRow(); - return status == OK; -} - -static void nativeFreeLastRow(JNIEnv* env, jclass clazz, jlong windowPtr) { - CursorWindow* window = reinterpret_cast(windowPtr); - window->freeLastRow(); -} - -static jint nativeGetType(JNIEnv* env, jclass clazz, jlong windowPtr, - jint row, jint column) { - CursorWindow* window = reinterpret_cast(windowPtr); - LOG_WINDOW("returning column type affinity for %d,%d from %p", row, column, window); - - CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column); - if (!fieldSlot) { - return CursorWindow::FIELD_TYPE_NULL; - } - return window->getFieldSlotType(fieldSlot); -} - -static jbyteArray nativeGetBlob(JNIEnv* env, jclass clazz, jlong windowPtr, - jint row, jint column) { - CursorWindow* window = reinterpret_cast(windowPtr); - //LOG_WINDOW("Getting blob for %d,%d from %p", row, column, window); - - CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column); - if (!fieldSlot) { - throwExceptionWithRowCol(env, row, column); - return NULL; - } - - int32_t type = window->getFieldSlotType(fieldSlot); - if (type == CursorWindow::FIELD_TYPE_BLOB || type == CursorWindow::FIELD_TYPE_STRING) { - size_t size; - const void* value = window->getFieldSlotValueBlob(fieldSlot, &size); - jbyteArray byteArray = env->NewByteArray(size); - if (!byteArray) { - env->ExceptionClear(); - throw_sqlite3_exception(env, "Native could not create new byte[]"); - return NULL; - } - env->SetByteArrayRegion(byteArray, 0, size, static_cast(value)); - return byteArray; - } else if (type == CursorWindow::FIELD_TYPE_INTEGER) { - throw_sqlite3_exception(env, "INTEGER data in nativeGetBlob "); - } else if (type == CursorWindow::FIELD_TYPE_FLOAT) { - throw_sqlite3_exception(env, "FLOAT data in nativeGetBlob "); - } else if (type == CursorWindow::FIELD_TYPE_NULL) { - // do nothing - } else { - throwUnknownTypeException(env, type); - } - return NULL; -} - -extern int utf8ToJavaCharArray(const char* d, jchar v[], jint byteCount); - -static jstring nativeGetString(JNIEnv* env, jclass clazz, jlong windowPtr, - jint row, jint column) { - CursorWindow* window = reinterpret_cast(windowPtr); - //LOG_WINDOW("Getting string for %d,%d from %p", row, column, window); - - CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column); - if (!fieldSlot) { - throwExceptionWithRowCol(env, row, column); - return NULL; - } - - int32_t type = window->getFieldSlotType(fieldSlot); - if (type == CursorWindow::FIELD_TYPE_STRING) { - size_t sizeIncludingNull; - const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull); - if (sizeIncludingNull <= 1) { - return gEmptyString; - } - const size_t MaxStackStringSize = 65536; // max size for a stack char array - if (sizeIncludingNull > MaxStackStringSize) { - jchar* chars = new jchar[sizeIncludingNull - 1]; - jint size = utf8ToJavaCharArray(value, chars, sizeIncludingNull - 1); - jstring string = env->NewString(chars, size); - delete[] chars; - return string; - } else { - jchar chars[sizeIncludingNull - 1]; - jint size = utf8ToJavaCharArray(value, chars, sizeIncludingNull - 1); - return env->NewString(chars, size); - } - } else if (type == CursorWindow::FIELD_TYPE_INTEGER) { - int64_t value = window->getFieldSlotValueLong(fieldSlot); - char buf[32]; - snprintf(buf, sizeof(buf), "%" PRId64, value); - return env->NewStringUTF(buf); - } else if (type == CursorWindow::FIELD_TYPE_FLOAT) { - double value = window->getFieldSlotValueDouble(fieldSlot); - char buf[32]; - snprintf(buf, sizeof(buf), "%g", value); - return env->NewStringUTF(buf); - } else if (type == CursorWindow::FIELD_TYPE_NULL) { - return NULL; - } else if (type == CursorWindow::FIELD_TYPE_BLOB) { - throw_sqlite3_exception(env, "Unable to convert BLOB to string"); - return NULL; - } else { - throwUnknownTypeException(env, type); - return NULL; - } -} - -static jlong nativeGetLong(JNIEnv* env, jclass clazz, jlong windowPtr, - jint row, jint column) { - CursorWindow* window = reinterpret_cast(windowPtr); - //LOG_WINDOW("Getting long for %d,%d from %p", row, column, window); - - CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column); - if (!fieldSlot) { - throwExceptionWithRowCol(env, row, column); - return 0; - } - - int32_t type = window->getFieldSlotType(fieldSlot); - if (type == CursorWindow::FIELD_TYPE_INTEGER) { - return window->getFieldSlotValueLong(fieldSlot); - } else if (type == CursorWindow::FIELD_TYPE_STRING) { - size_t sizeIncludingNull; - const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull); - return sizeIncludingNull > 1 ? strtoll(value, NULL, 0) : 0L; - } else if (type == CursorWindow::FIELD_TYPE_FLOAT) { - return jlong(window->getFieldSlotValueDouble(fieldSlot)); - } else if (type == CursorWindow::FIELD_TYPE_NULL) { - return 0; - } else if (type == CursorWindow::FIELD_TYPE_BLOB) { - throw_sqlite3_exception(env, "Unable to convert BLOB to long"); - return 0; - } else { - throwUnknownTypeException(env, type); - return 0; - } -} - -static jdouble nativeGetDouble(JNIEnv* env, jclass clazz, jlong windowPtr, - jint row, jint column) { - CursorWindow* window = reinterpret_cast(windowPtr); - //LOG_WINDOW("Getting double for %d,%d from %p", row, column, window); - - CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column); - if (!fieldSlot) { - throwExceptionWithRowCol(env, row, column); - return 0.0; - } - - int32_t type = window->getFieldSlotType(fieldSlot); - if (type == CursorWindow::FIELD_TYPE_FLOAT) { - return window->getFieldSlotValueDouble(fieldSlot); - } else if (type == CursorWindow::FIELD_TYPE_STRING) { - size_t sizeIncludingNull; - const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull); - return sizeIncludingNull > 1 ? strtod(value, NULL) : 0.0; - } else if (type == CursorWindow::FIELD_TYPE_INTEGER) { - return jdouble(window->getFieldSlotValueLong(fieldSlot)); - } else if (type == CursorWindow::FIELD_TYPE_NULL) { - return 0.0; - } else if (type == CursorWindow::FIELD_TYPE_BLOB) { - throw_sqlite3_exception(env, "Unable to convert BLOB to double"); - return 0.0; - } else { - throwUnknownTypeException(env, type); - return 0.0; - } -} - -static jboolean nativePutBlob(JNIEnv* env, jclass clazz, jlong windowPtr, - jbyteArray valueObj, jint row, jint column) { - CursorWindow* window = reinterpret_cast(windowPtr); - jsize len = env->GetArrayLength(valueObj); - - void* value = env->GetPrimitiveArrayCritical(valueObj, NULL); - status_t status = window->putBlob(row, column, value, len); - env->ReleasePrimitiveArrayCritical(valueObj, value, JNI_ABORT); - - if (status) { - LOG_WINDOW("Failed to put blob. error=%d", status); - return false; - } - - LOG_WINDOW("%d,%d is BLOB with %u bytes", row, column, len); - return true; -} - -static jboolean nativePutString(JNIEnv* env, jclass clazz, jlong windowPtr, - jstring valueObj, jint row, jint column) { - CursorWindow* window = reinterpret_cast(windowPtr); - - size_t sizeIncludingNull = env->GetStringUTFLength(valueObj) + 1; - const char* valueStr = env->GetStringUTFChars(valueObj, NULL); - if (!valueStr) { - LOG_WINDOW("value can't be transferred to UTFChars"); - return false; - } - status_t status = window->putString(row, column, valueStr, sizeIncludingNull); - env->ReleaseStringUTFChars(valueObj, valueStr); - - if (status) { - LOG_WINDOW("Failed to put string. error=%d", status); - return false; - } - - LOG_WINDOW("%d,%d is TEXT with %u bytes", row, column, sizeIncludingNull); - return true; -} - -static jboolean nativePutLong(JNIEnv* env, jclass clazz, jlong windowPtr, - jlong value, jint row, jint column) { - CursorWindow* window = reinterpret_cast(windowPtr); - status_t status = window->putLong(row, column, value); - - if (status) { - LOG_WINDOW("Failed to put long. error=%d", status); - return false; - } - - LOG_WINDOW("%d,%d is INTEGER 0x%016llx", row, column, value); - return true; -} - -static jboolean nativePutDouble(JNIEnv* env, jclass clazz, jlong windowPtr, - jdouble value, jint row, jint column) { - CursorWindow* window = reinterpret_cast(windowPtr); - status_t status = window->putDouble(row, column, value); - - if (status) { - LOG_WINDOW("Failed to put double. error=%d", status); - return false; - } - - LOG_WINDOW("%d,%d is FLOAT %lf", row, column, value); - return true; -} - -static jboolean nativePutNull(JNIEnv* env, jclass clazz, jlong windowPtr, - jint row, jint column) { - CursorWindow* window = reinterpret_cast(windowPtr); - status_t status = window->putNull(row, column); - - if (status) { - LOG_WINDOW("Failed to put null. error=%d", status); - return false; - } - - LOG_WINDOW("%d,%d is NULL", row, column); - return true; -} - -static const JNINativeMethod sMethods[] = -{ - /* name, signature, funcPtr */ - { "nativeCreate", "(Ljava/lang/String;I)J", - (void*)nativeCreate }, - { "nativeDispose", "(J)V", - (void*)nativeDispose }, - { "nativeGetName", "(J)Ljava/lang/String;", - (void*)nativeGetName }, - { "nativeClear", "(J)V", - (void*)nativeClear }, - { "nativeGetNumRows", "(J)I", - (void*)nativeGetNumRows }, - { "nativeSetNumColumns", "(JI)Z", - (void*)nativeSetNumColumns }, - { "nativeAllocRow", "(J)Z", - (void*)nativeAllocRow }, - { "nativeFreeLastRow", "(J)V", - (void*)nativeFreeLastRow }, - { "nativeGetType", "(JII)I", - (void*)nativeGetType }, - { "nativeGetBlob", "(JII)[B", - (void*)nativeGetBlob }, - { "nativeGetString", "(JII)Ljava/lang/String;", - (void*)nativeGetString }, - { "nativeGetLong", "(JII)J", - (void*)nativeGetLong }, - { "nativeGetDouble", "(JII)D", - (void*)nativeGetDouble }, - { "nativePutBlob", "(J[BII)Z", - (void*)nativePutBlob }, - { "nativePutString", "(JLjava/lang/String;II)Z", - (void*)nativePutString }, - { "nativePutLong", "(JJII)Z", - (void*)nativePutLong }, - { "nativePutDouble", "(JDII)Z", - (void*)nativePutDouble }, - { "nativePutNull", "(JII)Z", - (void*)nativePutNull }, -}; - -int register_android_database_CursorWindow(JNIEnv* env) -{ - jclass clazz; - FIND_CLASS(clazz, "android/database/CharArrayBuffer"); - - GET_FIELD_ID(gCharArrayBufferClassInfo.data, clazz, "data", "[C"); - GET_FIELD_ID(gCharArrayBufferClassInfo.sizeCopied, clazz, "sizeCopied", "I"); - - gEmptyString = static_cast(env->NewGlobalRef(env->NewStringUTF(""))); - return jniRegisterNativeMethods(env, - "io/requery/android/database/CursorWindow", sMethods, NELEM(sMethods)); -} - -} // namespace android diff --git a/sqlite-android/src/main/jni/sqlite/android_database_SQLiteCommon.cpp b/sqlite-android/src/main/jni/sqlite/android_database_SQLiteCommon.cpp deleted file mode 100644 index 705343efb8..0000000000 --- a/sqlite-android/src/main/jni/sqlite/android_database_SQLiteCommon.cpp +++ /dev/null @@ -1,140 +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 - -#include "android_database_SQLiteCommon.h" - -namespace android { - -/* throw a SQLiteException with a message appropriate for the error in handle */ -void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle) { - throw_sqlite3_exception(env, handle, NULL); -} - -/* throw a SQLiteException with the given message */ -void throw_sqlite3_exception(JNIEnv* env, const char* message) { - throw_sqlite3_exception(env, NULL, message); -} - -/* throw a SQLiteException with a message appropriate for the error in handle - concatenated with the given message - */ -void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle, const char* message) { - if (handle) { - // get the error code and message from the SQLite connection - // the error message may contain more information than the error code - // because it is based on the extended error code rather than the simplified - // error code that SQLite normally returns. - throw_sqlite3_exception(env, sqlite3_extended_errcode(handle), - sqlite3_errmsg(handle), message); - } else { - // we use SQLITE_OK so that a generic SQLiteException is thrown; - // any code not specified in the switch statement below would do. - throw_sqlite3_exception(env, SQLITE_OK, "unknown error", message); - } -} - -/* throw a SQLiteException for a given error code - * should only be used when the database connection is not available because the - * error information will not be quite as rich */ -void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message) { - throw_sqlite3_exception(env, errcode, "unknown error", message); -} - -/* throw a SQLiteException for a given error code, sqlite3message, and - user message - */ -void throw_sqlite3_exception(JNIEnv* env, int errcode, - const char* sqlite3Message, const char* message) { - const char* exceptionClass; - switch (errcode & 0xff) { /* mask off extended error code */ - case SQLITE_IOERR: - exceptionClass = "android/database/sqlite/SQLiteDiskIOException"; - break; - case SQLITE_CORRUPT: - case SQLITE_NOTADB: // treat "unsupported file format" error as corruption also - exceptionClass = "android/database/sqlite/SQLiteDatabaseCorruptException"; - break; - case SQLITE_CONSTRAINT: - exceptionClass = "android/database/sqlite/SQLiteConstraintException"; - break; - case SQLITE_ABORT: - exceptionClass = "android/database/sqlite/SQLiteAbortException"; - break; - case SQLITE_DONE: - exceptionClass = "android/database/sqlite/SQLiteDoneException"; - sqlite3Message = NULL; // SQLite error message is irrelevant in this case - break; - case SQLITE_FULL: - exceptionClass = "android/database/sqlite/SQLiteFullException"; - break; - case SQLITE_MISUSE: - exceptionClass = "android/database/sqlite/SQLiteMisuseException"; - break; - case SQLITE_PERM: - exceptionClass = "android/database/sqlite/SQLiteAccessPermException"; - break; - case SQLITE_BUSY: - exceptionClass = "android/database/sqlite/SQLiteDatabaseLockedException"; - break; - case SQLITE_LOCKED: - exceptionClass = "android/database/sqlite/SQLiteTableLockedException"; - break; - case SQLITE_READONLY: - exceptionClass = "android/database/sqlite/SQLiteReadOnlyDatabaseException"; - break; - case SQLITE_CANTOPEN: - exceptionClass = "android/database/sqlite/SQLiteCantOpenDatabaseException"; - break; - case SQLITE_TOOBIG: - exceptionClass = "android/database/sqlite/SQLiteBlobTooBigException"; - break; - case SQLITE_RANGE: - exceptionClass = "android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException"; - break; - case SQLITE_NOMEM: - exceptionClass = "android/database/sqlite/SQLiteOutOfMemoryException"; - break; - case SQLITE_MISMATCH: - exceptionClass = "android/database/sqlite/SQLiteDatatypeMismatchException"; - break; - case SQLITE_INTERRUPT: - exceptionClass = "androidx/core/os/OperationCanceledException"; - break; - default: - exceptionClass = "android/database/sqlite/SQLiteException"; - break; - } - - // check this exception class exists otherwise just default to SQLiteException - if (env->FindClass(exceptionClass) == NULL) { - exceptionClass = "android/database/sqlite/SQLiteException"; - } - - if (sqlite3Message) { - char *zFullmsg = sqlite3_mprintf( - "%s (code %d)%s%s", sqlite3Message, errcode, - (message ? ": " : ""), (message ? message : "") - ); - jniThrowException(env, exceptionClass, zFullmsg); - sqlite3_free(zFullmsg); - } else { - jniThrowException(env, exceptionClass, message); - } -} - - -} // namespace android diff --git a/sqlite-android/src/main/jni/sqlite/android_database_SQLiteCommon.h b/sqlite-android/src/main/jni/sqlite/android_database_SQLiteCommon.h deleted file mode 100644 index 8c684dec49..0000000000 --- a/sqlite-android/src/main/jni/sqlite/android_database_SQLiteCommon.h +++ /dev/null @@ -1,52 +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 - -#ifndef _ANDROID_DATABASE_SQLITE_COMMON_H -#define _ANDROID_DATABASE_SQLITE_COMMON_H - -#include -#include - -#include - -// Special log tags defined in SQLiteDebug.java. -#define SQLITE_LOG_TAG "SQLiteLog" -#define SQLITE_TRACE_TAG "SQLiteStatements" -#define SQLITE_PROFILE_TAG "SQLiteTime" - -namespace android { - -/* throw a SQLiteException with a message appropriate for the error in handle */ -void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle); - -/* throw a SQLiteException with the given message */ -void throw_sqlite3_exception(JNIEnv* env, const char* message); - -/* throw a SQLiteException with a message appropriate for the error in handle - concatenated with the given message - */ -void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle, const char* message); - -/* throw a SQLiteException for a given error code */ -void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message); - -void throw_sqlite3_exception(JNIEnv* env, int errcode, - const char* sqlite3Message, const char* message); - -} - -#endif // _ANDROID_DATABASE_SQLITE_COMMON_H diff --git a/sqlite-android/src/main/jni/sqlite/android_database_SQLiteConnection.cpp b/sqlite-android/src/main/jni/sqlite/android_database_SQLiteConnection.cpp deleted file mode 100644 index bd7ff7b8e9..0000000000 --- a/sqlite-android/src/main/jni/sqlite/android_database_SQLiteConnection.cpp +++ /dev/null @@ -1,1048 +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 - -#define LOG_TAG "SQLiteConnection" - -#include -#include -#include -#include -#include -#include - -#include "sqlite3.h" -#include "JNIHelp.h" -#include "ALog-priv.h" -#include "android_database_SQLiteCommon.h" -#include "CursorWindow.h" - -// Set to 1 to use UTF16 storage for localized indexes. -#define UTF16_STORAGE 0 - -namespace android { - -/* Busy timeout in milliseconds. - * If another connection (possibly in another process) has the database locked for - * longer than this amount of time then SQLite will generate a SQLITE_BUSY error. - * The SQLITE_BUSY error is then raised as a SQLiteDatabaseLockedException. - * - * In ordinary usage, busy timeouts are quite rare. Most databases only ever - * have a single open connection at a time unless they are using WAL. When using - * WAL, a timeout could occur if one connection is busy performing an auto-checkpoint - * operation. The busy timeout needs to be long enough to tolerate slow I/O write - * operations but not so long as to cause the application to hang indefinitely if - * there is a problem acquiring a database lock. - */ -static const int BUSY_TIMEOUT_MS = 2500; - -static JavaVM *gpJavaVM = 0; - -static struct { - jfieldID name; - jfieldID numArgs; - jmethodID dispatchCallback; -} gSQLiteCustomFunctionClassInfo; - -static struct { - jfieldID name; - jfieldID numArgs; - jfieldID flags; - jmethodID dispatchCallback; -} gSQLiteFunctionClassInfo; - -static struct { - jclass clazz; -} gStringClassInfo; - -struct SQLiteConnection { - sqlite3* const db; - const int openFlags; - char* path; - char* label; - - volatile bool canceled; - - SQLiteConnection(sqlite3* db, int openFlags, const char* path_, const char* label_) : - db(db), openFlags(openFlags), canceled(false) { - path = strdup(path_); - label = strdup(label_); - } - - ~SQLiteConnection() { - free(path); - free(label); - } -}; - -// Called each time a statement begins execution, when tracing is enabled. -static void sqliteTraceCallback(void *data, const char *sql) { - SQLiteConnection* connection = static_cast(data); - ALOG(LOG_VERBOSE, SQLITE_TRACE_TAG, "%s: \"%s\"\n", - connection->label, sql); -} - -// Called each time a statement finishes execution, when profiling is enabled. -static void sqliteProfileCallback(void *data, const char *sql, sqlite3_uint64 tm) { - SQLiteConnection* connection = static_cast(data); - ALOG(LOG_VERBOSE, SQLITE_PROFILE_TAG, "%s: \"%s\" took %0.3f ms\n", - connection->label, sql, tm * 0.000001f); -} - -// Called after each SQLite VM instruction when cancelation is enabled. -static int sqliteProgressHandlerCallback(void* data) { - SQLiteConnection* connection = static_cast(data); - return connection->canceled; -} - -/* -** This function is a collation sequence callback equivalent to the built-in -** BINARY sequence. -** -** Stock Android uses a modified version of sqlite3.c that calls out to a module -** named "sqlite3_android" to add extra built-in collations and functions to -** all database handles. Specifically, collation sequence "LOCALIZED". For now, -** this module does not include sqlite3_android (since it is difficult to build -** with the NDK only). Instead, this function is registered as "LOCALIZED" for all -** new database handles. -*/ -static int coll_localized( - void *not_used, - int nKey1, const void *pKey1, - int nKey2, const void *pKey2 -){ - int rc, n; - n = nKey1GetStringUTFChars(pathStr, NULL); - const char* labelChars = env->GetStringUTFChars(labelStr, NULL); - - sqlite3* db; - int err = sqlite3_open_v2(pathChars, &db, openFlags, NULL); - if (err != SQLITE_OK) { - env->ReleaseStringUTFChars(pathStr, pathChars); - env->ReleaseStringUTFChars(labelStr, labelChars); - throw_sqlite3_exception_errcode(env, err, "Could not open database"); - return 0; - } - err = sqlite3_create_collation(db, "localized", SQLITE_UTF8, 0, coll_localized); - if (err != SQLITE_OK) { - env->ReleaseStringUTFChars(pathStr, pathChars); - env->ReleaseStringUTFChars(labelStr, labelChars); - throw_sqlite3_exception_errcode(env, err, "Could not register collation"); - sqlite3_close(db); - return 0; - } - - // Check that the database is really read/write when that is what we asked for. - if ((openFlags & SQLITE_OPEN_READWRITE) && sqlite3_db_readonly(db, NULL)) { - env->ReleaseStringUTFChars(pathStr, pathChars); - env->ReleaseStringUTFChars(labelStr, labelChars); - throw_sqlite3_exception(env, db, "Could not open the database in read/write mode."); - sqlite3_close(db); - return 0; - } - - // Set the default busy handler to retry automatically before returning SQLITE_BUSY. - err = sqlite3_busy_timeout(db, BUSY_TIMEOUT_MS); - if (err != SQLITE_OK) { - env->ReleaseStringUTFChars(pathStr, pathChars); - env->ReleaseStringUTFChars(labelStr, labelChars); - throw_sqlite3_exception(env, db, "Could not set busy timeout"); - sqlite3_close(db); - return 0; - } - - // Register custom Android functions. -#if 0 - err = register_android_functions(db, UTF16_STORAGE); - if (err) { - env->ReleaseStringUTFChars(pathStr, pathChars); - env->ReleaseStringUTFChars(labelStr, labelChars); - throw_sqlite3_exception(env, db, "Could not register Android SQL functions."); - sqlite3_close(db); - return 0; - } -#endif - - // Create wrapper object. - SQLiteConnection* connection = new SQLiteConnection(db, openFlags, pathChars, labelChars); - ALOGV("Opened connection %p with label '%s'", db, labelChars); - env->ReleaseStringUTFChars(pathStr, pathChars); - env->ReleaseStringUTFChars(labelStr, labelChars); - - // Enable tracing and profiling if requested. - if (enableTrace) { - sqlite3_trace(db, &sqliteTraceCallback, connection); - } - if (enableProfile) { - sqlite3_profile(db, &sqliteProfileCallback, connection); - } - - return reinterpret_cast(connection); -} - -static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - - if (connection) { - ALOGV("Closing connection %p", connection->db); - int err = sqlite3_close(connection->db); - if (err != SQLITE_OK) { - // This can happen if sub-objects aren't closed first. Make sure the caller knows. - ALOGE("sqlite3_close(%p) failed: %d", connection->db, err); - throw_sqlite3_exception(env, connection->db, "Count not close db."); - return; - } - - delete connection; - } -} - -// Called each time a custom function is evaluated. -static void sqliteCustomFunctionCallback(sqlite3_context *context, - int argc, sqlite3_value **argv) { - - JNIEnv* env = 0; - gpJavaVM->GetEnv((void**)&env, JNI_VERSION_1_4); - - // Get the callback function object. - // Create a new local reference to it in case the callback tries to do something - // dumb like unregister the function (thereby destroying the global ref) while it is running. - jobject functionObjGlobal = reinterpret_cast(sqlite3_user_data(context)); - jobject functionObj = env->NewLocalRef(functionObjGlobal); - - jobjectArray argsArray = env->NewObjectArray(argc, gStringClassInfo.clazz, NULL); - if (argsArray) { - for (int i = 0; i < argc; i++) { - const jchar* arg = static_cast(sqlite3_value_text16(argv[i])); - if (!arg) { - ALOGW("NULL argument in custom_function_callback. This should not happen."); - } else { - size_t argLen = sqlite3_value_bytes16(argv[i]) / sizeof(jchar); - jstring argStr = env->NewString(arg, argLen); - if (!argStr) { - goto error; // out of memory error - } - env->SetObjectArrayElement(argsArray, i, argStr); - env->DeleteLocalRef(argStr); - } - } - - { - jobject result = env->CallObjectMethod(functionObj, - gSQLiteCustomFunctionClassInfo.dispatchCallback, argsArray); - if (env->ExceptionCheck()) { - sqlite3_result_error(context, "Custom function exception", -1); - } else if (result == NULL) { - sqlite3_result_null(context); - } else { - jstring str = static_cast(result); - const char* chars = env->GetStringUTFChars(str, NULL); - sqlite3_result_text(context, chars, -1, SQLITE_TRANSIENT); - env->ReleaseStringUTFChars(str, chars); - } - env->DeleteLocalRef(result); - } -error: - env->DeleteLocalRef(argsArray); - } - - env->DeleteLocalRef(functionObj); - - if (env->ExceptionCheck()) { - ALOGE("An exception was thrown by custom SQLite function."); - /* LOGE_EX(env); */ - env->ExceptionClear(); - } -} - -// Called each time a Function is evaluated. -static void sqliteFunctionCallback(sqlite3_context *context, - int argc, sqlite3_value **argv) { - - JNIEnv* env = 0; - gpJavaVM->GetEnv((void**)&env, JNI_VERSION_1_4); - - // Get the callback function object. - // Create a new local reference to it in case the callback tries to do something - // dumb like unregister the function (thereby destroying the global ref) while it is running. - jobject functionObjGlobal = reinterpret_cast(sqlite3_user_data(context)); - jobject functionObj = env->NewLocalRef(functionObjGlobal); - - jlong contextPtr = jlong(context); - jlong argsPtr = jlong(argv); - jint argsCount = jint(argc); - - env->CallVoidMethod(functionObj, - gSQLiteFunctionClassInfo.dispatchCallback, - contextPtr, - argsPtr, - argsCount - ); - if (env->ExceptionCheck()) { - sqlite3_result_error(context, "Custom function exception", -1); - } - - env->DeleteLocalRef(functionObj); - - if (env->ExceptionCheck()) { - ALOGE("An exception was thrown by custom SQLite function."); - /* LOGE_EX(env); */ - env->ExceptionClear(); - } -} - -// Called when a custom function is destroyed. -static void sqliteCustomFunctionDestructor(void* data) { - jobject functionObjGlobal = reinterpret_cast(data); - JNIEnv* env = 0; - gpJavaVM->GetEnv((void**)&env, JNI_VERSION_1_4); - env->DeleteGlobalRef(functionObjGlobal); -} - -static void nativeRegisterCustomFunction(JNIEnv* env, jclass clazz, jlong connectionPtr, - jobject functionObj) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - - jstring nameStr = jstring(env->GetObjectField( - functionObj, gSQLiteCustomFunctionClassInfo.name)); - jint numArgs = env->GetIntField(functionObj, gSQLiteCustomFunctionClassInfo.numArgs); - - jobject functionObjGlobal = env->NewGlobalRef(functionObj); - - const char* name = env->GetStringUTFChars(nameStr, NULL); - int err = sqlite3_create_function_v2(connection->db, name, numArgs, SQLITE_UTF16, - reinterpret_cast(functionObjGlobal), - &sqliteCustomFunctionCallback, NULL, NULL, &sqliteCustomFunctionDestructor); - env->ReleaseStringUTFChars(nameStr, name); - - if (err != SQLITE_OK) { - ALOGE("sqlite3_create_function returned %d", err); - env->DeleteGlobalRef(functionObjGlobal); - throw_sqlite3_exception(env, connection->db); - return; - } -} - -static void nativeRegisterFunction(JNIEnv *env, jclass clazz, jlong connectionPtr, - jobject functionObj) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - - jstring nameStr = jstring(env->GetObjectField( - functionObj, gSQLiteFunctionClassInfo.name)); - jint numArgs = env->GetIntField(functionObj, gSQLiteFunctionClassInfo.numArgs); - jint flags = env->GetIntField(functionObj, gSQLiteFunctionClassInfo.flags); - - jobject functionObjGlobal = env->NewGlobalRef(functionObj); - - const char* name = env->GetStringUTFChars(nameStr, NULL); - int err = sqlite3_create_function_v2(connection->db, name, numArgs, - SQLITE_UTF16 | flags, - reinterpret_cast(functionObjGlobal), - &sqliteFunctionCallback, NULL, NULL, &sqliteCustomFunctionDestructor); - env->ReleaseStringUTFChars(nameStr, name); - - if (err != SQLITE_OK) { - ALOGE("sqlite3_create_function returned %d", err); - env->DeleteGlobalRef(functionObjGlobal); - throw_sqlite3_exception(env, connection->db); - return; - } -} - -static void nativeRegisterLocalizedCollators(JNIEnv* env, jclass clazz, jlong connectionPtr, - jstring localeStr) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); -#if 0 - const char* locale = env->GetStringUTFChars(localeStr, NULL); - - int err = register_localized_collators(connection->db, locale, UTF16_STORAGE); - env->ReleaseStringUTFChars(localeStr, locale); - - if (err != SQLITE_OK) { - throw_sqlite3_exception(env, connection->db); - } -#endif -} - -static jlong nativePrepareStatement(JNIEnv* env, jclass clazz, jlong connectionPtr, - jstring sqlString) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - - jsize sqlLength = env->GetStringLength(sqlString); - const jchar* sql = env->GetStringCritical(sqlString, NULL); - sqlite3_stmt* statement; - int err = sqlite3_prepare16_v2(connection->db, - sql, sqlLength * sizeof(jchar), &statement, NULL); - env->ReleaseStringCritical(sqlString, sql); - - if (err != SQLITE_OK) { - // Error messages like 'near ")": syntax error' are not - // always helpful enough, so construct an error string that - // includes the query itself. - const char *query = env->GetStringUTFChars(sqlString, NULL); - char *message = (char*) malloc(strlen(query) + 50); - if (message) { - strcpy(message, ", while compiling: "); // less than 50 chars - strcat(message, query); - } - env->ReleaseStringUTFChars(sqlString, query); - throw_sqlite3_exception(env, connection->db, message); - free(message); - return 0; - } - - ALOGV("Prepared statement %p on connection %p", statement, connection->db); - return reinterpret_cast(statement); -} - -static void nativeFinalizeStatement(JNIEnv* env, jclass clazz, jlong connectionPtr, - jlong statementPtr) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - - // We ignore the result of sqlite3_finalize because it is really telling us about - // whether any errors occurred while executing the statement. The statement itself - // is always finalized regardless. - ALOGV("Finalized statement %p on connection %p", statement, connection->db); - sqlite3_finalize(statement); -} - -static jint nativeGetParameterCount(JNIEnv* env, jclass clazz, jlong connectionPtr, - jlong statementPtr) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - - return sqlite3_bind_parameter_count(statement); -} - -static jboolean nativeIsReadOnly(JNIEnv* env, jclass clazz, jlong connectionPtr, - jlong statementPtr) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - - return sqlite3_stmt_readonly(statement) != 0; -} - -static jint nativeGetColumnCount(JNIEnv* env, jclass clazz, jlong connectionPtr, - jlong statementPtr) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - - return sqlite3_column_count(statement); -} - -static jstring nativeGetColumnName(JNIEnv* env, jclass clazz, jlong connectionPtr, - jlong statementPtr, jint index) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - - const jchar* name = static_cast(sqlite3_column_name16(statement, index)); - if (name) { - size_t length = 0; - while (name[length]) { - length += 1; - } - return env->NewString(name, length); - } - return NULL; -} - -static void nativeBindNull(JNIEnv* env, jclass clazz, jlong connectionPtr, - jlong statementPtr, jint index) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - - int err = sqlite3_bind_null(statement, index); - if (err != SQLITE_OK) { - throw_sqlite3_exception(env, connection->db, NULL); - } -} - -static void nativeBindLong(JNIEnv* env, jclass clazz, jlong connectionPtr, - jlong statementPtr, jint index, jlong value) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - - int err = sqlite3_bind_int64(statement, index, value); - if (err != SQLITE_OK) { - throw_sqlite3_exception(env, connection->db, NULL); - } -} - -static void nativeBindDouble(JNIEnv* env, jclass clazz, jlong connectionPtr, - jlong statementPtr, jint index, jdouble value) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - - int err = sqlite3_bind_double(statement, index, value); - if (err != SQLITE_OK) { - throw_sqlite3_exception(env, connection->db, NULL); - } -} - -static void nativeBindString(JNIEnv* env, jclass clazz, jlong connectionPtr, - jlong statementPtr, jint index, jstring valueString) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - - jsize valueLength = env->GetStringLength(valueString); - const jchar* value = env->GetStringCritical(valueString, NULL); - int err = sqlite3_bind_text16(statement, index, value, valueLength * sizeof(jchar), - SQLITE_TRANSIENT); - env->ReleaseStringCritical(valueString, value); - if (err != SQLITE_OK) { - throw_sqlite3_exception(env, connection->db, NULL); - } -} - -static void nativeBindBlob(JNIEnv* env, jclass clazz, jlong connectionPtr, - jlong statementPtr, jint index, jbyteArray valueArray) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - - jsize valueLength = env->GetArrayLength(valueArray); - jbyte* value = static_cast(env->GetPrimitiveArrayCritical(valueArray, NULL)); - int err = sqlite3_bind_blob(statement, index, value, valueLength, SQLITE_TRANSIENT); - env->ReleasePrimitiveArrayCritical(valueArray, value, JNI_ABORT); - if (err != SQLITE_OK) { - throw_sqlite3_exception(env, connection->db, NULL); - } -} - -static void nativeResetStatementAndClearBindings(JNIEnv* env, jclass clazz, jlong connectionPtr, - jlong statementPtr) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - - int err = sqlite3_reset(statement); - if (err == SQLITE_OK) { - err = sqlite3_clear_bindings(statement); - } - if (err != SQLITE_OK) { - throw_sqlite3_exception(env, connection->db, NULL); - } -} - -static int executeNonQuery(JNIEnv* env, SQLiteConnection* connection, sqlite3_stmt* statement) { - int err = sqlite3_step(statement); - if (err == SQLITE_ROW) { - throw_sqlite3_exception(env, - "Queries can be performed using SQLiteDatabase query or rawQuery methods only."); - } else if (err != SQLITE_DONE) { - throw_sqlite3_exception(env, connection->db); - } - return err; -} - -static void nativeExecute(JNIEnv* env, jclass clazz, jlong connectionPtr, - jlong statementPtr) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - - executeNonQuery(env, connection, statement); -} - -static jint nativeExecuteForChangedRowCount(JNIEnv* env, jclass clazz, - jlong connectionPtr, jlong statementPtr) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - - int err = executeNonQuery(env, connection, statement); - return err == SQLITE_DONE ? sqlite3_changes(connection->db) : -1; -} - -static jlong nativeExecuteForLastInsertedRowId(JNIEnv* env, jclass clazz, - jlong connectionPtr, jlong statementPtr) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - - int err = executeNonQuery(env, connection, statement); - return err == SQLITE_DONE && sqlite3_changes(connection->db) > 0 - ? sqlite3_last_insert_rowid(connection->db) : -1; -} - -static int executeOneRowQuery(JNIEnv* env, SQLiteConnection* connection, sqlite3_stmt* statement) { - int err = sqlite3_step(statement); - if (err != SQLITE_ROW) { - throw_sqlite3_exception(env, connection->db); - } - return err; -} - -static jlong nativeExecuteForLong(JNIEnv* env, jclass clazz, - jlong connectionPtr, jlong statementPtr) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - - int err = executeOneRowQuery(env, connection, statement); - if (err == SQLITE_ROW && sqlite3_column_count(statement) >= 1) { - return sqlite3_column_int64(statement, 0); - } - return -1; -} - -static jstring nativeExecuteForString(JNIEnv* env, jclass clazz, - jlong connectionPtr, jlong statementPtr) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - - int err = executeOneRowQuery(env, connection, statement); - if (err == SQLITE_ROW && sqlite3_column_count(statement) >= 1) { - const jchar* text = static_cast(sqlite3_column_text16(statement, 0)); - if (text) { - size_t length = sqlite3_column_bytes16(statement, 0) / sizeof(jchar); - return env->NewString(text, length); - } - } - return NULL; -} - -static int createAshmemRegionWithData(JNIEnv* env, const void* data, size_t length) { -#if 0 - int error = 0; - int fd = ashmem_create_region(NULL, length); - if (fd < 0) { - error = errno; - ALOGE("ashmem_create_region failed: %s", strerror(error)); - } else { - if (length > 0) { - void* ptr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (ptr == MAP_FAILED) { - error = errno; - ALOGE("mmap failed: %s", strerror(error)); - } else { - memcpy(ptr, data, length); - munmap(ptr, length); - } - } - - if (!error) { - if (ashmem_set_prot_region(fd, PROT_READ) < 0) { - error = errno; - ALOGE("ashmem_set_prot_region failed: %s", strerror(errno)); - } else { - return fd; - } - } - - close(fd); - } - -#endif - jniThrowIOException(env, -1); - return -1; -} - -static jint nativeExecuteForBlobFileDescriptor(JNIEnv* env, jclass clazz, - jlong connectionPtr, jlong statementPtr) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - - int err = executeOneRowQuery(env, connection, statement); - if (err == SQLITE_ROW && sqlite3_column_count(statement) >= 1) { - const void* blob = sqlite3_column_blob(statement, 0); - if (blob) { - int length = sqlite3_column_bytes(statement, 0); - if (length >= 0) { - return createAshmemRegionWithData(env, blob, length); - } - } - } - return -1; -} - -enum CopyRowResult { - CPR_OK, - CPR_FULL, - CPR_ERROR, -}; - -static CopyRowResult copyRow(JNIEnv* env, CursorWindow* window, - sqlite3_stmt* statement, int numColumns, int startPos, int addedRows) { - // Allocate a new field directory for the row. - status_t status = window->allocRow(); - if (status) { - LOG_WINDOW("Failed allocating fieldDir at startPos %d row %d, error=%d", - startPos, addedRows, status); - return CPR_FULL; - } - - // Pack the row into the window. - CopyRowResult result = CPR_OK; - for (int i = 0; i < numColumns; i++) { - int type = sqlite3_column_type(statement, i); - if (type == SQLITE_TEXT) { - // TEXT data - const char* text = reinterpret_cast( - sqlite3_column_text(statement, i)); - // SQLite does not include the NULL terminator in size, but does - // ensure all strings are NULL terminated, so increase size by - // one to make sure we store the terminator. - size_t sizeIncludingNull = sqlite3_column_bytes(statement, i) + 1; - status = window->putString(addedRows, i, text, sizeIncludingNull); - if (status) { - LOG_WINDOW("Failed allocating %u bytes for text at %d,%d, error=%d", - sizeIncludingNull, startPos + addedRows, i, status); - result = CPR_FULL; - break; - } - LOG_WINDOW("%d,%d is TEXT with %u bytes", - startPos + addedRows, i, sizeIncludingNull); - } else if (type == SQLITE_INTEGER) { - // INTEGER data - int64_t value = sqlite3_column_int64(statement, i); - status = window->putLong(addedRows, i, value); - if (status) { - LOG_WINDOW("Failed allocating space for a long in column %d, error=%d", - i, status); - result = CPR_FULL; - break; - } - LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + addedRows, i, value); - } else if (type == SQLITE_FLOAT) { - // FLOAT data - double value = sqlite3_column_double(statement, i); - status = window->putDouble(addedRows, i, value); - if (status) { - LOG_WINDOW("Failed allocating space for a double in column %d, error=%d", - i, status); - result = CPR_FULL; - break; - } - LOG_WINDOW("%d,%d is FLOAT %lf", startPos + addedRows, i, value); - } else if (type == SQLITE_BLOB) { - // BLOB data - const void* blob = sqlite3_column_blob(statement, i); - size_t size = sqlite3_column_bytes(statement, i); - status = window->putBlob(addedRows, i, blob, size); - if (status) { - LOG_WINDOW("Failed allocating %u bytes for blob at %d,%d, error=%d", - size, startPos + addedRows, i, status); - result = CPR_FULL; - break; - } - LOG_WINDOW("%d,%d is Blob with %u bytes", - startPos + addedRows, i, size); - } else if (type == SQLITE_NULL) { - // NULL field - status = window->putNull(addedRows, i); - if (status) { - LOG_WINDOW("Failed allocating space for a null in column %d, error=%d", - i, status); - result = CPR_FULL; - break; - } - - LOG_WINDOW("%d,%d is NULL", startPos + addedRows, i); - } else { - // Unknown data - ALOGE("Unknown column type when filling database window"); - throw_sqlite3_exception(env, "Unknown column type when filling window"); - result = CPR_ERROR; - break; - } - } - - // Free the last row if if was not successfully copied. - if (result != CPR_OK) { - window->freeLastRow(); - } - return result; -} - -static jlong nativeExecuteForCursorWindow(JNIEnv* env, jclass clazz, - jlong connectionPtr, jlong statementPtr, jlong windowPtr, - jint startPos, jint requiredPos, jboolean countAllRows) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - sqlite3_stmt* statement = reinterpret_cast(statementPtr); - CursorWindow* window = reinterpret_cast(windowPtr); - - status_t status = window->clear(); - if (status) { - throw_sqlite3_exception(env, connection->db, "Failed to clear the cursor window"); - return 0; - } - - int numColumns = sqlite3_column_count(statement); - status = window->setNumColumns(numColumns); - if (status) { - throw_sqlite3_exception(env, connection->db, "Failed to set the cursor window column count"); - return 0; - } - - int retryCount = 0; - int totalRows = 0; - int addedRows = 0; - bool windowFull = false; - bool gotException = false; - while (!gotException && (!windowFull || countAllRows)) { - int err = sqlite3_step(statement); - if (err == SQLITE_ROW) { - LOG_WINDOW("Stepped statement %p to row %d", statement, totalRows); - retryCount = 0; - totalRows += 1; - - // Skip the row if the window is full or we haven't reached the start position yet. - if (startPos >= totalRows || windowFull) { - continue; - } - - CopyRowResult cpr = copyRow(env, window, statement, numColumns, startPos, addedRows); - if (cpr == CPR_FULL && addedRows && startPos + addedRows <= requiredPos) { - // We filled the window before we got to the one row that we really wanted. - // Clear the window and start filling it again from here. - // TODO: Would be nicer if we could progressively replace earlier rows. - window->clear(); - window->setNumColumns(numColumns); - startPos += addedRows; - addedRows = 0; - cpr = copyRow(env, window, statement, numColumns, startPos, addedRows); - } - - if (cpr == CPR_OK) { - addedRows += 1; - } else if (cpr == CPR_FULL) { - windowFull = true; - } else { - gotException = true; - } - } else if (err == SQLITE_DONE) { - // All rows processed, bail - LOG_WINDOW("Processed all rows"); - break; - } else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) { - // The table is locked, retry - LOG_WINDOW("Database locked, retrying"); - if (retryCount > 50) { - ALOGE("Bailing on database busy retry"); - throw_sqlite3_exception(env, connection->db, "retrycount exceeded"); - gotException = true; - } else { - // Sleep to give the thread holding the lock a chance to finish - usleep(1000); - retryCount++; - } - } else { - throw_sqlite3_exception(env, connection->db); - gotException = true; - } - } - - LOG_WINDOW("Resetting statement %p after fetching %d rows and adding %d rows" - "to the window in %d bytes", - statement, totalRows, addedRows, window->size() - window->freeSpace()); - sqlite3_reset(statement); - - // Report the total number of rows on request. - if (startPos > totalRows) { - ALOGE("startPos %d > actual rows %d", startPos, totalRows); - } - jlong result = jlong(startPos) << 32 | jlong(totalRows); - return result; -} - -static jint nativeGetDbLookaside(JNIEnv* env, jobject clazz, jlong connectionPtr) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - - int cur = -1; - int unused; - sqlite3_db_status(connection->db, SQLITE_DBSTATUS_LOOKASIDE_USED, &cur, &unused, 0); - return cur; -} - -static void nativeCancel(JNIEnv* env, jobject clazz, jlong connectionPtr) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - connection->canceled = true; -} - -static void nativeResetCancel(JNIEnv* env, jobject clazz, jlong connectionPtr, - jboolean cancelable) { - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - connection->canceled = false; - - if (cancelable) { - sqlite3_progress_handler(connection->db, 4, sqliteProgressHandlerCallback, - connection); - } else { - sqlite3_progress_handler(connection->db, 0, NULL, NULL); - } -} - -static jboolean nativeHasCodec(JNIEnv* env, jobject clazz){ -#ifdef SQLITE_HAS_CODEC - return true; -#else - return false; -#endif -} - -static void nativeLoadExtension(JNIEnv* env, jobject clazz, - jlong connectionPtr, jstring file, jstring proc) { - char* errorMessage; - - SQLiteConnection* connection = reinterpret_cast(connectionPtr); - int result = sqlite3_enable_load_extension(connection->db, 1); - if (result == SQLITE_OK) { - const char* fileChars = env->GetStringUTFChars(file, NULL); - const char* procChars = NULL; - if (proc) { - procChars = env->GetStringUTFChars(proc, NULL); - } - result = sqlite3_load_extension(connection->db, fileChars, procChars, &errorMessage); - env->ReleaseStringUTFChars(file, fileChars); - if (proc) { - env->ReleaseStringUTFChars(proc, procChars); - } - } - if (result != SQLITE_OK) { - char* formattedError = sqlite3_mprintf("Could not register extension: %s", errorMessage); - sqlite3_free(errorMessage); - - throw_sqlite3_exception_errcode(env, result, formattedError); - sqlite3_free(formattedError); - } -} - -static JNINativeMethod sMethods[] = -{ - /* name, signature, funcPtr */ - { "nativeOpen", "(Ljava/lang/String;ILjava/lang/String;ZZ)J", - (void*)nativeOpen }, - { "nativeClose", "(J)V", - (void*)nativeClose }, - { "nativeRegisterCustomFunction", "(JLio/requery/android/database/sqlite/SQLiteCustomFunction;)V", - (void*)nativeRegisterCustomFunction }, - { "nativeRegisterFunction", "(JLio/requery/android/database/sqlite/SQLiteFunction;)V", - (void*)nativeRegisterFunction }, - { "nativeRegisterLocalizedCollators", "(JLjava/lang/String;)V", - (void*)nativeRegisterLocalizedCollators }, - { "nativePrepareStatement", "(JLjava/lang/String;)J", - (void*)nativePrepareStatement }, - { "nativeFinalizeStatement", "(JJ)V", - (void*)nativeFinalizeStatement }, - { "nativeGetParameterCount", "(JJ)I", - (void*)nativeGetParameterCount }, - { "nativeIsReadOnly", "(JJ)Z", - (void*)nativeIsReadOnly }, - { "nativeGetColumnCount", "(JJ)I", - (void*)nativeGetColumnCount }, - { "nativeGetColumnName", "(JJI)Ljava/lang/String;", - (void*)nativeGetColumnName }, - { "nativeBindNull", "(JJI)V", - (void*)nativeBindNull }, - { "nativeBindLong", "(JJIJ)V", - (void*)nativeBindLong }, - { "nativeBindDouble", "(JJID)V", - (void*)nativeBindDouble }, - { "nativeBindString", "(JJILjava/lang/String;)V", - (void*)nativeBindString }, - { "nativeBindBlob", "(JJI[B)V", - (void*)nativeBindBlob }, - { "nativeResetStatementAndClearBindings", "(JJ)V", - (void*)nativeResetStatementAndClearBindings }, - { "nativeExecute", "(JJ)V", - (void*)nativeExecute }, - { "nativeExecuteForLong", "(JJ)J", - (void*)nativeExecuteForLong }, - { "nativeExecuteForString", "(JJ)Ljava/lang/String;", - (void*)nativeExecuteForString }, - { "nativeExecuteForBlobFileDescriptor", "(JJ)I", - (void*)nativeExecuteForBlobFileDescriptor }, - { "nativeExecuteForChangedRowCount", "(JJ)I", - (void*)nativeExecuteForChangedRowCount }, - { "nativeExecuteForLastInsertedRowId", "(JJ)J", - (void*)nativeExecuteForLastInsertedRowId }, - { "nativeExecuteForCursorWindow", "(JJJIIZ)J", - (void*)nativeExecuteForCursorWindow }, - { "nativeGetDbLookaside", "(J)I", - (void*)nativeGetDbLookaside }, - { "nativeCancel", "(J)V", - (void*)nativeCancel }, - { "nativeResetCancel", "(JZ)V", - (void*)nativeResetCancel }, - { "nativeHasCodec", "()Z", - (void*)nativeHasCodec }, - { "nativeLoadExtension", "(JLjava/lang/String;Ljava/lang/String;)V", - (void*)nativeLoadExtension }, -}; - -int register_android_database_SQLiteConnection(JNIEnv *env) -{ - jclass clazz; - FIND_CLASS(clazz, "io/requery/android/database/sqlite/SQLiteCustomFunction"); - - GET_FIELD_ID(gSQLiteCustomFunctionClassInfo.name, clazz, - "name", "Ljava/lang/String;"); - GET_FIELD_ID(gSQLiteCustomFunctionClassInfo.numArgs, clazz, - "numArgs", "I"); - GET_METHOD_ID(gSQLiteCustomFunctionClassInfo.dispatchCallback, - clazz, "dispatchCallback", "([Ljava/lang/String;)Ljava/lang/String;"); - - FIND_CLASS(clazz, "io/requery/android/database/sqlite/SQLiteFunction"); - - GET_FIELD_ID(gSQLiteFunctionClassInfo.name, clazz, - "name", "Ljava/lang/String;"); - GET_FIELD_ID(gSQLiteFunctionClassInfo.numArgs, clazz, - "numArgs", "I"); - GET_FIELD_ID(gSQLiteFunctionClassInfo.flags, clazz, - "flags", "I"); - GET_METHOD_ID(gSQLiteFunctionClassInfo.dispatchCallback, - clazz, "dispatchCallback", "(JJI)V"); - - FIND_CLASS(clazz, "java/lang/String"); - gStringClassInfo.clazz = jclass(env->NewGlobalRef(clazz)); - - return jniRegisterNativeMethods(env, - "io/requery/android/database/sqlite/SQLiteConnection", - sMethods, NELEM(sMethods) - ); -} - -extern int register_android_database_SQLiteGlobal(JNIEnv *env); -extern int register_android_database_SQLiteDebug(JNIEnv *env); -extern int register_android_database_SQLiteFunction(JNIEnv *env); -extern int register_android_database_CursorWindow(JNIEnv *env); - -} // namespace android - -extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { - JNIEnv *env = 0; - - android::gpJavaVM = vm; - vm->GetEnv((void**)&env, JNI_VERSION_1_4); - - android::register_android_database_SQLiteConnection(env); - android::register_android_database_SQLiteDebug(env); - android::register_android_database_SQLiteGlobal(env); - android::register_android_database_CursorWindow(env); - android::register_android_database_SQLiteFunction(env); - - return JNI_VERSION_1_4; -} - - - diff --git a/sqlite-android/src/main/jni/sqlite/android_database_SQLiteDebug.cpp b/sqlite-android/src/main/jni/sqlite/android_database_SQLiteDebug.cpp deleted file mode 100644 index 371b321f02..0000000000 --- a/sqlite-android/src/main/jni/sqlite/android_database_SQLiteDebug.cpp +++ /dev/null @@ -1,81 +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 - -#define LOG_TAG "SQLiteDebug" - -#include -#include "JNIHelp.h" -#include "ALog-priv.h" - -#include -#include -#include -#include - -#include - -namespace android { - -static struct { - jfieldID memoryUsed; - jfieldID pageCacheOverflow; - jfieldID largestMemAlloc; -} gSQLiteDebugPagerStatsClassInfo; - -static void nativeGetPagerStats(JNIEnv *env, jobject clazz, jobject statsObj) -{ - int memoryUsed; - int pageCacheOverflow; - int largestMemAlloc; - int unused; - - sqlite3_status(SQLITE_STATUS_MEMORY_USED, &memoryUsed, &unused, 0); - sqlite3_status(SQLITE_STATUS_MALLOC_SIZE, &unused, &largestMemAlloc, 0); - sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW, &pageCacheOverflow, &unused, 0); - env->SetIntField(statsObj, gSQLiteDebugPagerStatsClassInfo.memoryUsed, memoryUsed); - env->SetIntField(statsObj, gSQLiteDebugPagerStatsClassInfo.pageCacheOverflow, - pageCacheOverflow); - env->SetIntField(statsObj, gSQLiteDebugPagerStatsClassInfo.largestMemAlloc, largestMemAlloc); -} - -/* - * JNI registration. - */ - -static JNINativeMethod gMethods[] = -{ - { "nativeGetPagerStats", "(Lio/requery/android/database/sqlite/SQLiteDebug$PagerStats;)V", - (void*) nativeGetPagerStats }, -}; - -int register_android_database_SQLiteDebug(JNIEnv *env) -{ - jclass clazz; - FIND_CLASS(clazz, "io/requery/android/database/sqlite/SQLiteDebug$PagerStats"); - - GET_FIELD_ID(gSQLiteDebugPagerStatsClassInfo.memoryUsed, clazz, - "memoryUsed", "I"); - GET_FIELD_ID(gSQLiteDebugPagerStatsClassInfo.largestMemAlloc, clazz, - "largestMemAlloc", "I"); - GET_FIELD_ID(gSQLiteDebugPagerStatsClassInfo.pageCacheOverflow, clazz, - "pageCacheOverflow", "I"); - - return jniRegisterNativeMethods(env, "io/requery/android/database/sqlite/SQLiteDebug", - gMethods, NELEM(gMethods)); -} - -} // namespace android diff --git a/sqlite-android/src/main/jni/sqlite/android_database_SQLiteFunction.cpp b/sqlite-android/src/main/jni/sqlite/android_database_SQLiteFunction.cpp deleted file mode 100644 index f376593dda..0000000000 --- a/sqlite-android/src/main/jni/sqlite/android_database_SQLiteFunction.cpp +++ /dev/null @@ -1,229 +0,0 @@ -#define LOG_TAG "SQLiteFunction" - -#include -#include -#include -#include -#include - -#include "sqlite3.h" -#include "JNIHelp.h" -#include "ALog-priv.h" -#include "android_database_SQLiteCommon.h" - -namespace android { - -/* Returns the sqlite3_value for the given arg of the given function. - * If 0 is returned, an exception has been thrown to report the reason. */ -static sqlite3_value *tovalue(JNIEnv *env, jlong argsPtr, jint arg) { - if (arg < 0) { - throw_sqlite3_exception(env, "Invalid arg index"); - return 0; - } - if (!argsPtr) { - throw_sqlite3_exception(env, "Invalid argsPtr"); - return 0; - } - - sqlite3_value **args = reinterpret_cast(argsPtr); - return args[arg]; -} - -static sqlite3_context *tocontext(JNIEnv *env, jlong contextPtr) { - if (!contextPtr) { - throw_sqlite3_exception(env, "Invalid contextPtr"); - return 0; - } - - return reinterpret_cast(contextPtr); -} - -/* - * Getters - */ - -static jbyteArray nativeGetArgBlob(JNIEnv* env, jclass clazz, jlong argsPtr, - jint arg) { - int length; - jbyteArray byteArray; - const void *blob; - - sqlite3_value *value = tovalue(env, argsPtr, arg); - if (!value) return NULL; - - blob = sqlite3_value_blob(value); - if (!blob) return NULL; - - length = sqlite3_value_bytes(value); - byteArray = env->NewByteArray(length); - if (!byteArray) { - env->ExceptionClear(); - throw_sqlite3_exception(env, "Native could not create new byte[]"); - return NULL; - } - - env->SetByteArrayRegion(byteArray, 0, length, static_cast(blob)); - return byteArray; -} - -static jstring nativeGetArgString(JNIEnv* env, jclass clazz, jlong argsPtr, - jint arg) { - sqlite3_value *value = tovalue(env, argsPtr, arg); - if (!value) return NULL; - - const jchar* chars = static_cast(sqlite3_value_text16(value)); - if (!chars) return NULL; - - size_t len = sqlite3_value_bytes16(value) / sizeof(jchar); - jstring str = env->NewString(chars, len); - if (!str) { - env->ExceptionClear(); - throw_sqlite3_exception(env, "Native could not allocate string"); - return NULL; - } - - return str; -} - -static jlong nativeGetArgLong(JNIEnv* env, jclass clazz, jlong argsPtr, - jint arg) { - sqlite3_value *value = tovalue(env, argsPtr, arg); - return value ? sqlite3_value_int64(value) : 0; -} - -static jdouble nativeGetArgDouble(JNIEnv* env, jclass clazz, jlong argsPtr, - jint arg) { - sqlite3_value *value = tovalue(env, argsPtr, arg); - return value ? sqlite3_value_double(value) : 0; -} - -static jint nativeGetArgInt(JNIEnv* env, jclass clazz, jlong argsPtr, - jint arg) { - sqlite3_value *value = tovalue(env, argsPtr, arg); - return value ? sqlite3_value_int(value) : 0; -} - -/* - * Setters - */ - -static void nativeSetResultBlob(JNIEnv* env, jclass clazz, - jlong contextPtr, jbyteArray result) { - sqlite3_context *context = tocontext(env, contextPtr); - if (!context) return; - if (result == NULL) { - sqlite3_result_null(context); - return; - } - - jsize len = env->GetArrayLength(result); - void *bytes = env->GetPrimitiveArrayCritical(result, NULL); - if (!bytes) { - env->ExceptionClear(); - throw_sqlite3_exception(env, "Out of memory accepting blob"); - return; - } - - sqlite3_result_blob(context, bytes, len, SQLITE_TRANSIENT); - env->ReleasePrimitiveArrayCritical(result, bytes, JNI_ABORT); -} - -static void nativeSetResultString(JNIEnv* env, jclass clazz, - jlong contextPtr, jstring result) { - sqlite3_context *context = tocontext(env, contextPtr); - if (result == NULL) { - sqlite3_result_null(context); - return; - } - - const char* chars = env->GetStringUTFChars(result, NULL); - if (!chars) { - ALOGE("result value can't be transferred to UTFChars"); - sqlite3_result_error_nomem(context); - return; - } - - sqlite3_result_text(context, chars, -1, SQLITE_TRANSIENT); - env->ReleaseStringUTFChars(result, chars); -} - -static void nativeSetResultLong(JNIEnv* env, jclass clazz, - jlong contextPtr, jlong result) { - sqlite3_context *context = tocontext(env, contextPtr); - if (context) sqlite3_result_int64(context, result); -} - -static void nativeSetResultDouble(JNIEnv* env, jclass clazz, - jlong contextPtr, jdouble result) { - sqlite3_context *context = tocontext(env, contextPtr); - if (context) sqlite3_result_double(context, result); -} - -static void nativeSetResultInt(JNIEnv* env, jclass clazz, - jlong contextPtr, jint result) { - sqlite3_context *context = tocontext(env, contextPtr); - if (context) sqlite3_result_int(context, result); -} - -static void nativeSetResultError(JNIEnv* env, jclass clazz, - jlong contextPtr, jstring error) { - sqlite3_context *context = tocontext(env, contextPtr); - if (error == NULL) { - sqlite3_result_null(context); - return; - } - - const char* chars = env->GetStringUTFChars(error, NULL); - if (!chars) { - ALOGE("result value can't be transferred to UTFChars"); - sqlite3_result_error_nomem(context); - return; - } - - sqlite3_result_error(context, chars, -1); - env->ReleaseStringUTFChars(error, chars); -} - -static void nativeSetResultNull(JNIEnv* env, jclass clazz, jlong contextPtr) { - sqlite3_context *context = tocontext(env, contextPtr); - if (context) sqlite3_result_null(context); -} - - -static const JNINativeMethod sMethods[] = -{ - /* name, signature, funcPtr */ - { "nativeGetArgBlob", "(JI)[B", - (void*)nativeGetArgBlob }, - { "nativeGetArgString", "(JI)Ljava/lang/String;", - (void*)nativeGetArgString }, - { "nativeGetArgLong", "(JI)J", - (void*)nativeGetArgLong }, - { "nativeGetArgDouble", "(JI)D", - (void*)nativeGetArgDouble }, - { "nativeGetArgInt", "(JI)I", - (void*)nativeGetArgInt }, - - { "nativeSetResultBlob", "(J[B)V", - (void*)nativeSetResultBlob }, - { "nativeSetResultString", "(JLjava/lang/String;)V", - (void*)nativeSetResultString }, - { "nativeSetResultLong", "(JJ)V", - (void*)nativeSetResultLong }, - { "nativeSetResultDouble", "(JD)V", - (void*)nativeSetResultDouble }, - { "nativeSetResultInt", "(JI)V", - (void*)nativeSetResultInt }, - { "nativeSetResultError", "(JLjava/lang/String;)V", - (void*)nativeSetResultError }, - { "nativeSetResultNull", "(J)V", - (void*)nativeSetResultNull }, -}; - -int register_android_database_SQLiteFunction(JNIEnv* env) -{ - return jniRegisterNativeMethods(env, - "io/requery/android/database/sqlite/SQLiteFunction", sMethods, NELEM(sMethods)); -} - -} // namespace android diff --git a/sqlite-android/src/main/jni/sqlite/android_database_SQLiteGlobal.cpp b/sqlite-android/src/main/jni/sqlite/android_database_SQLiteGlobal.cpp deleted file mode 100644 index a07b48e272..0000000000 --- a/sqlite-android/src/main/jni/sqlite/android_database_SQLiteGlobal.cpp +++ /dev/null @@ -1,92 +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 - -#define LOG_TAG "SQLiteGlobal" - -#include -#include -#include "ALog-priv.h" - -#include -//#include - -#include "android_database_SQLiteCommon.h" - -namespace android { - -// Limit heap to 8MB for now. This is 4 times the maximum cursor window -// size, as has been used by the original code in SQLiteDatabase for -// a long time. -static const int SOFT_HEAP_LIMIT = 8 * 1024 * 1024; - - -// Called each time a message is logged. -static void sqliteLogCallback(void* data, int iErrCode, const char* zMsg) { - bool verboseLog = !!data; - if (iErrCode == 0 || iErrCode == SQLITE_CONSTRAINT || iErrCode == SQLITE_SCHEMA) { - if (verboseLog) { - ALOG(LOG_VERBOSE, SQLITE_LOG_TAG, "(%d) %s\n", iErrCode, zMsg); - } - } else { - ALOG(LOG_ERROR, SQLITE_LOG_TAG, "(%d) %s\n", iErrCode, zMsg); - } -} - -// Sets the global SQLite configuration. -// This must be called before any other SQLite functions are called. -static void sqliteInitialize() { - // Enable multi-threaded mode. In this mode, SQLite is safe to use by multiple - // threads as long as no two threads use the same database connection at the same - // time (which we guarantee in the SQLite database wrappers). - sqlite3_config(SQLITE_CONFIG_MULTITHREAD); - - // Redirect SQLite log messages to the Android log. -#if 0 - bool verboseLog = android_util_Log_isVerboseLogEnabled(SQLITE_LOG_TAG); -#endif - bool verboseLog = false; - sqlite3_config(SQLITE_CONFIG_LOG, &sqliteLogCallback, verboseLog ? (void*)1 : NULL); - - // The soft heap limit prevents the page cache allocations from growing - // beyond the given limit, no matter what the max page cache sizes are - // set to. The limit does not, as of 3.5.0, affect any other allocations. - sqlite3_soft_heap_limit(SOFT_HEAP_LIMIT); - - // Initialize SQLite. - sqlite3_initialize(); -} - -static jint nativeReleaseMemory(JNIEnv* env, jclass clazz) { - return sqlite3_release_memory(SOFT_HEAP_LIMIT); -} - -static JNINativeMethod sMethods[] = -{ - /* name, signature, funcPtr */ - { "nativeReleaseMemory", "()I", - (void*)nativeReleaseMemory }, -}; - -int register_android_database_SQLiteGlobal(JNIEnv *env) -{ - sqliteInitialize(); - - return jniRegisterNativeMethods(env, "io/requery/android/database/sqlite/SQLiteGlobal", - sMethods, NELEM(sMethods)); -} - -} // namespace android