summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Ahmed Ibrahim <aibra@google.com> 2023-06-09 16:07:45 +0100
committer Ahmed Ibrahim <aibra@google.com> 2023-06-20 14:43:17 +0100
commit9db459b49b1fa19a795b3d8a4250e15d6da1f4e5 (patch)
treeebb4ab3ff149eae40f85a49785f664a691728095
parent1c450b404d19edca3794a62bcc7d50a6f6081502 (diff)
Add API support for sqlite3_set_authorizer().
Authorizers can be consulted during compilation of a SQL statement to determine if each action requested by the SQL statement is allowed. This can be useful to dynamically block interaction with private, internal, or otherwise sensitive columns or tables inside a database, such as when compiling an untrusted SQL statement. This change adds the ability for developers to provide a custom authorizer on a per-statement basis. Since statements using a custom authorizer are typically untrusted SQL, they're likely to have low cache hit ratios, so we don't attempt to cache compiled statements. Upstream SQLite is likely to continue adding new actions over time, so our API design is a simple mirror of the underlying callback. Mapping of this underlying callback into higher-level concepts is left to a Jetpack library. Bug: 231636192 Test: atest CtsDatabaseTestCases Change-Id: I06fa30e30c58961c9b05628e576a6f5f80fdf550
-rw-r--r--core/api/current.txt47
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/database/sqlite/SQLiteAuthorizer.java178
-rw-r--r--core/java/android/database/sqlite/SQLiteConnection.java155
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java273
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java9
-rw-r--r--core/java/android/database/sqlite/SQLiteDirectCursorDriver.java13
-rw-r--r--core/java/android/database/sqlite/SQLiteProgram.java15
-rw-r--r--core/java/android/database/sqlite/SQLiteQuery.java13
-rw-r--r--core/java/android/database/sqlite/SQLiteRawStatement.java7
-rw-r--r--core/java/android/database/sqlite/SQLiteSession.java117
-rw-r--r--core/java/android/database/sqlite/SQLiteStatement.java22
-rw-r--r--core/jni/android_database_SQLiteConnection.cpp189
13 files changed, 811 insertions, 228 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index a2c1b1b61f7a..20f3c964139a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -14204,6 +14204,46 @@ package android.database.sqlite {
ctor public SQLiteAccessPermException(String);
}
+ public interface SQLiteAuthorizer {
+ method public int onAuthorize(int, @Nullable String, @Nullable String, @Nullable String, @Nullable String);
+ field public static final int SQLITE_ACTION_ALTER_TABLE = 26; // 0x1a
+ field public static final int SQLITE_ACTION_ANALYZE = 28; // 0x1c
+ field public static final int SQLITE_ACTION_ATTACH = 24; // 0x18
+ field public static final int SQLITE_ACTION_CREATE_INDEX = 1; // 0x1
+ field public static final int SQLITE_ACTION_CREATE_TABLE = 2; // 0x2
+ field public static final int SQLITE_ACTION_CREATE_TEMP_INDEX = 3; // 0x3
+ field public static final int SQLITE_ACTION_CREATE_TEMP_TABLE = 4; // 0x4
+ field public static final int SQLITE_ACTION_CREATE_TEMP_TRIGGER = 5; // 0x5
+ field public static final int SQLITE_ACTION_CREATE_TEMP_VIEW = 6; // 0x6
+ field public static final int SQLITE_ACTION_CREATE_TRIGGER = 7; // 0x7
+ field public static final int SQLITE_ACTION_CREATE_VIEW = 8; // 0x8
+ field public static final int SQLITE_ACTION_CREATE_VTABLE = 29; // 0x1d
+ field public static final int SQLITE_ACTION_DELETE = 9; // 0x9
+ field public static final int SQLITE_ACTION_DETACH = 25; // 0x19
+ field public static final int SQLITE_ACTION_DROP_INDEX = 10; // 0xa
+ field public static final int SQLITE_ACTION_DROP_TABLE = 11; // 0xb
+ field public static final int SQLITE_ACTION_DROP_TEMP_INDEX = 12; // 0xc
+ field public static final int SQLITE_ACTION_DROP_TEMP_TABLE = 13; // 0xd
+ field public static final int SQLITE_ACTION_DROP_TEMP_TRIGGER = 14; // 0xe
+ field public static final int SQLITE_ACTION_DROP_TEMP_VIEW = 15; // 0xf
+ field public static final int SQLITE_ACTION_DROP_TRIGGER = 16; // 0x10
+ field public static final int SQLITE_ACTION_DROP_VIEW = 17; // 0x11
+ field public static final int SQLITE_ACTION_DROP_VTABLE = 30; // 0x1e
+ field public static final int SQLITE_ACTION_FUNCTION = 31; // 0x1f
+ field public static final int SQLITE_ACTION_INSERT = 18; // 0x12
+ field public static final int SQLITE_ACTION_PRAGMA = 19; // 0x13
+ field public static final int SQLITE_ACTION_READ = 20; // 0x14
+ field public static final int SQLITE_ACTION_RECURSIVE = 33; // 0x21
+ field public static final int SQLITE_ACTION_REINDEX = 27; // 0x1b
+ field public static final int SQLITE_ACTION_SAVEPOINT = 32; // 0x20
+ field public static final int SQLITE_ACTION_SELECT = 21; // 0x15
+ field public static final int SQLITE_ACTION_TRANSACTION = 22; // 0x16
+ field public static final int SQLITE_ACTION_UPDATE = 23; // 0x17
+ field public static final int SQLITE_AUTHORIZER_RESULT_DENY = 1; // 0x1
+ field public static final int SQLITE_AUTHORIZER_RESULT_IGNORE = 2; // 0x2
+ field public static final int SQLITE_AUTHORIZER_RESULT_OK = 0; // 0x0
+ }
+
public class SQLiteBindOrColumnIndexOutOfRangeException extends android.database.sqlite.SQLiteException {
ctor public SQLiteBindOrColumnIndexOutOfRangeException();
ctor public SQLiteBindOrColumnIndexOutOfRangeException(String);
@@ -14260,6 +14300,7 @@ package android.database.sqlite {
method public void beginTransactionWithListenerNonExclusive(@Nullable android.database.sqlite.SQLiteTransactionListener);
method public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
method public android.database.sqlite.SQLiteStatement compileStatement(String) throws android.database.SQLException;
+ method @NonNull public android.database.sqlite.SQLiteStatement compileStatement(@NonNull String, @NonNull android.database.sqlite.SQLiteAuthorizer) throws android.database.SQLException;
method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams);
method public int delete(String, String, String[]);
@@ -14270,6 +14311,7 @@ package android.database.sqlite {
method public void execPerConnectionSQL(@NonNull String, @Nullable Object[]) throws android.database.SQLException;
method public void execSQL(String) throws android.database.SQLException;
method public void execSQL(String, Object[]) throws android.database.SQLException;
+ method public void execSQL(@NonNull String, @Nullable Object[], @NonNull android.database.sqlite.SQLiteAuthorizer) throws android.database.SQLException;
method public static String findEditTable(String);
method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs();
method public long getMaximumSize();
@@ -14281,6 +14323,7 @@ package android.database.sqlite {
method public long insert(String, String, android.content.ContentValues);
method public long insertOrThrow(String, String, android.content.ContentValues) throws android.database.SQLException;
method public long insertWithOnConflict(String, String, android.content.ContentValues, int);
+ method public boolean isAuthorizerSupportEnabled();
method public boolean isDatabaseIntegrityOk();
method public boolean isDbLockedByCurrentThread();
method @Deprecated public boolean isDbLockedByOtherThreads();
@@ -14307,6 +14350,7 @@ package android.database.sqlite {
method public android.database.Cursor rawQuery(String, String[], android.os.CancellationSignal);
method public android.database.Cursor rawQueryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, String, String[], String);
method public android.database.Cursor rawQueryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, String, String[], String, android.os.CancellationSignal);
+ method @NonNull public android.database.Cursor rawQueryWithFactory(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory, @NonNull String, @Nullable String[], @Nullable String, @Nullable android.os.CancellationSignal, @NonNull android.database.sqlite.SQLiteAuthorizer);
method public static int releaseMemory();
method public long replace(String, String, android.content.ContentValues);
method public long replaceOrThrow(String, String, android.content.ContentValues) throws android.database.SQLException;
@@ -14323,6 +14367,7 @@ package android.database.sqlite {
method public int update(String, android.content.ContentValues, String, String[]);
method public int updateWithOnConflict(String, android.content.ContentValues, String, String[], int);
method public void validateSql(@NonNull String, @Nullable android.os.CancellationSignal);
+ method public void validateSql(@NonNull String, @Nullable android.os.CancellationSignal, @NonNull android.database.sqlite.SQLiteAuthorizer);
method @Deprecated public boolean yieldIfContended();
method public boolean yieldIfContendedSafely();
method public boolean yieldIfContendedSafely(long);
@@ -14371,7 +14416,9 @@ package android.database.sqlite {
ctor public SQLiteDatabase.OpenParams.Builder(android.database.sqlite.SQLiteDatabase.OpenParams);
method @NonNull public android.database.sqlite.SQLiteDatabase.OpenParams.Builder addOpenFlags(int);
method @NonNull public android.database.sqlite.SQLiteDatabase.OpenParams build();
+ method public boolean isAuthorizerSupportEnabled();
method @NonNull public android.database.sqlite.SQLiteDatabase.OpenParams.Builder removeOpenFlags(int);
+ method @NonNull public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setAuthorizerSupportEnabled(boolean);
method @NonNull public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setCursorFactory(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
method @NonNull public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setErrorHandler(@Nullable android.database.DatabaseErrorHandler);
method @Deprecated @NonNull public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setIdleConnectionTimeout(@IntRange(from=0) long);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 529604d12bb3..62b268c07b10 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1303,6 +1303,7 @@ package android.database.sqlite {
public final class SQLiteDirectCursorDriver implements android.database.sqlite.SQLiteCursorDriver {
ctor public SQLiteDirectCursorDriver(android.database.sqlite.SQLiteDatabase, String, String, android.os.CancellationSignal);
+ ctor public SQLiteDirectCursorDriver(@NonNull android.database.sqlite.SQLiteDatabase, @Nullable android.database.sqlite.SQLiteAuthorizer, @NonNull String, @Nullable String, @Nullable android.os.CancellationSignal);
method public void cursorClosed();
method public void cursorDeactivated();
method public void cursorRequeried(android.database.Cursor);
diff --git a/core/java/android/database/sqlite/SQLiteAuthorizer.java b/core/java/android/database/sqlite/SQLiteAuthorizer.java
new file mode 100644
index 000000000000..a2bf8980e9af
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteAuthorizer.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2023 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 android.database.sqlite;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Authorizer which is consulted during compilation of a SQL statement.
+ * <p>
+ * During compilation, this callback will be invoked to determine if each action
+ * requested by the SQL statement is allowed.
+ * <p>
+ * This can be useful to dynamically block interaction with private, internal,
+ * or otherwise sensitive columns or tables inside a database, such as when
+ * compiling an untrusted SQL statement.
+ */
+public interface SQLiteAuthorizer {
+ /** @hide */
+ @IntDef(prefix = { "SQLITE_AUTHORIZER_RESULT_" }, value = {
+ SQLITE_AUTHORIZER_RESULT_OK,
+ SQLITE_AUTHORIZER_RESULT_DENY,
+ SQLITE_AUTHORIZER_RESULT_IGNORE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface AuthorizerResult {}
+
+ /** @hide */
+ @IntDef(prefix = { "SQLITE_ACTION_" }, value = {
+ SQLITE_ACTION_CREATE_INDEX,
+ SQLITE_ACTION_CREATE_TABLE,
+ SQLITE_ACTION_CREATE_TEMP_INDEX,
+ SQLITE_ACTION_CREATE_TEMP_TABLE,
+ SQLITE_ACTION_CREATE_TEMP_TRIGGER,
+ SQLITE_ACTION_CREATE_TEMP_VIEW,
+ SQLITE_ACTION_CREATE_TRIGGER,
+ SQLITE_ACTION_CREATE_VIEW,
+ SQLITE_ACTION_DELETE,
+ SQLITE_ACTION_DROP_INDEX,
+ SQLITE_ACTION_DROP_TABLE,
+ SQLITE_ACTION_DROP_TEMP_INDEX,
+ SQLITE_ACTION_DROP_TEMP_TABLE,
+ SQLITE_ACTION_DROP_TEMP_TRIGGER,
+ SQLITE_ACTION_DROP_TEMP_VIEW,
+ SQLITE_ACTION_DROP_TRIGGER,
+ SQLITE_ACTION_DROP_VIEW,
+ SQLITE_ACTION_INSERT,
+ SQLITE_ACTION_PRAGMA,
+ SQLITE_ACTION_READ,
+ SQLITE_ACTION_SELECT,
+ SQLITE_ACTION_TRANSACTION,
+ SQLITE_ACTION_UPDATE,
+ SQLITE_ACTION_ATTACH,
+ SQLITE_ACTION_DETACH,
+ SQLITE_ACTION_ALTER_TABLE,
+ SQLITE_ACTION_REINDEX,
+ SQLITE_ACTION_ANALYZE,
+ SQLITE_ACTION_CREATE_VTABLE,
+ SQLITE_ACTION_DROP_VTABLE,
+ SQLITE_ACTION_FUNCTION,
+ SQLITE_ACTION_SAVEPOINT,
+ SQLITE_ACTION_RECURSIVE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface AuthorizerAction {}
+
+ /** Successful result */
+ int SQLITE_AUTHORIZER_RESULT_OK = 0;
+ /** Abort the SQL statement with an error */
+ int SQLITE_AUTHORIZER_RESULT_DENY = 1;
+ /** Don't allow access, but don't generate an error */
+ int SQLITE_AUTHORIZER_RESULT_IGNORE = 2;
+
+ /** Authorizer action for {@code CREATE INDEX} */
+ int SQLITE_ACTION_CREATE_INDEX = 1;
+ /** Authorizer action for {@code CREATE TABLE} */
+ int SQLITE_ACTION_CREATE_TABLE = 2;
+ /** Authorizer action for {@code CREATE TEMP INDEX} */
+ int SQLITE_ACTION_CREATE_TEMP_INDEX = 3;
+ /** Authorizer action for {@code CREATE TEMP TABLE} */
+ int SQLITE_ACTION_CREATE_TEMP_TABLE = 4;
+ /** Authorizer action for {@code CREATE TEMP TRIGGER} */
+ int SQLITE_ACTION_CREATE_TEMP_TRIGGER = 5;
+ /** Authorizer action for {@code CREATE TEMP VIEW} */
+ int SQLITE_ACTION_CREATE_TEMP_VIEW = 6;
+ /** Authorizer action for {@code CREATE TRIGGER} */
+ int SQLITE_ACTION_CREATE_TRIGGER = 7;
+ /** Authorizer action for {@code CREATE VIEW} */
+ int SQLITE_ACTION_CREATE_VIEW = 8;
+ /** Authorizer action for {@code DELETE} */
+ int SQLITE_ACTION_DELETE = 9;
+ /** Authorizer action for {@code DROP INDEX} */
+ int SQLITE_ACTION_DROP_INDEX = 10;
+ /** Authorizer action for {@code DROP TABLE} */
+ int SQLITE_ACTION_DROP_TABLE = 11;
+ /** Authorizer action for {@code DROP TEMP INDEX} */
+ int SQLITE_ACTION_DROP_TEMP_INDEX = 12;
+ /** Authorizer action for {@code DROP TEMP TABLE} */
+ int SQLITE_ACTION_DROP_TEMP_TABLE = 13;
+ /** Authorizer action for {@code DROP TEMP TRIGGER} */
+ int SQLITE_ACTION_DROP_TEMP_TRIGGER = 14;
+ /** Authorizer action for {@code DROP TEMP VIEW} */
+ int SQLITE_ACTION_DROP_TEMP_VIEW = 15;
+ /** Authorizer action for {@code DROP TRIGGER} */
+ int SQLITE_ACTION_DROP_TRIGGER = 16;
+ /** Authorizer action for {@code DROP VIEW} */
+ int SQLITE_ACTION_DROP_VIEW = 17;
+ /** Authorizer action for {@code INSERT} */
+ int SQLITE_ACTION_INSERT = 18;
+ /** Authorizer action for {@code PRAGMA} */
+ int SQLITE_ACTION_PRAGMA = 19;
+ /** Authorizer action for read access on a specific table and column */
+ int SQLITE_ACTION_READ = 20;
+ /** Authorizer action for {@code SELECT} */
+ int SQLITE_ACTION_SELECT = 21;
+ /** Authorizer action for transaction operations */
+ int SQLITE_ACTION_TRANSACTION = 22;
+ /** Authorizer action for {@code UPDATE} */
+ int SQLITE_ACTION_UPDATE = 23;
+ /** Authorizer action for {@code ATTACH} */
+ int SQLITE_ACTION_ATTACH = 24;
+ /** Authorizer action for {@code DETACH} */
+ int SQLITE_ACTION_DETACH = 25;
+ /** Authorizer action for {@code ALTER TABLE} */
+ int SQLITE_ACTION_ALTER_TABLE = 26;
+ /** Authorizer action for {@code REINDEX} */
+ int SQLITE_ACTION_REINDEX = 27;
+ /** Authorizer action for {@code ANALYZE} */
+ int SQLITE_ACTION_ANALYZE = 28;
+ /** Authorizer action for {@code CREATE VIRTUAL TABLE} */
+ int SQLITE_ACTION_CREATE_VTABLE = 29;
+ /** Authorizer action for {@code DROP VIRTUAL TABLE} */
+ int SQLITE_ACTION_DROP_VTABLE = 30;
+ /** Authorizer action for invocation of a function */
+ int SQLITE_ACTION_FUNCTION = 31;
+ /** Authorizer action for savepoint operations */
+ int SQLITE_ACTION_SAVEPOINT = 32;
+ /** Authorizer action for recursive operations */
+ int SQLITE_ACTION_RECURSIVE = 33;
+
+ /**
+ * Test if the given action should be allowed.
+ *
+ * @param action The action requested by the SQL statement currently being
+ * compiled.
+ * @param arg3 Optional argument relevant to the given action.
+ * @param arg4 Optional argument relevant to the given action.
+ * @param arg5 Optional argument relevant to the given action.
+ * @param arg6 Optional argument relevant to the given action.
+ * @return {@link SQLiteConstants#SQLITE_AUTHORIZER_RESULT_OK} to allow the action,
+ * {@link SQLiteConstants#SQLITE_AUTHORIZER_RESULT_IGNORE} to disallow the specific
+ * action but allow the SQL statement to continue to be compiled, or
+ * {@link SQLiteConstants#SQLITE_AUTHORIZER_RESULT_DENY} to cause the entire SQL
+ * statement to be rejected with an error.
+ * @see <a href="https://www.sqlite.org/c3ref/c_alter_table.html">Upstream
+ * SQLite documentation</a> that describes possible actions and their
+ * arguments.
+ */
+ @AuthorizerResult int onAuthorize(@AuthorizerAction int action, @Nullable String arg3,
+ @Nullable String arg4, @Nullable String arg5, @Nullable String arg6);
+}
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 23d4d5634ac1..39ee567b746a 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -17,7 +17,7 @@
package android.database.sqlite;
import android.annotation.NonNull;
-
+import android.annotation.Nullable;
import android.database.Cursor;
import android.database.CursorWindow;
import android.database.DatabaseUtils;
@@ -33,8 +33,10 @@ import android.util.Log;
import android.util.LruCache;
import android.util.Pair;
import android.util.Printer;
+
import dalvik.system.BlockGuard;
import dalvik.system.CloseGuard;
+
import java.io.File;
import java.io.IOException;
import java.lang.ref.Reference;
@@ -128,9 +130,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
private int mCancellationSignalAttachCount;
private static native long nativeOpen(String path, int openFlags, String label,
- boolean enableTrace, boolean enableProfile, int lookasideSlotSize,
- int lookasideSlotCount);
+ int lookasideSlotSize, int lookasideSlotCount);
private static native void nativeClose(long connectionPtr);
+ private static native void nativeSetAuthorizer(long connectionPtr,
+ SQLiteAuthorizer authorizer);
private static native void nativeRegisterCustomScalarFunction(long connectionPtr,
String name, UnaryOperator<String> function);
private static native void nativeRegisterCustomAggregateFunction(long connectionPtr,
@@ -225,9 +228,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
final String file = mConfiguration.path;
final int cookie = mRecentOperations.beginOperation("open", null, null);
try {
- mConnectionPtr = nativeOpen(file, mConfiguration.openFlags,
- mConfiguration.label,
- NoPreloadHolder.DEBUG_SQL_STATEMENTS, NoPreloadHolder.DEBUG_SQL_TIME,
+ mConnectionPtr = nativeOpen(file, mConfiguration.openFlags, mConfiguration.label,
mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount);
} catch (SQLiteCantOpenDatabaseException e) {
final StringBuilder message = new StringBuilder("Cannot open database '")
@@ -301,9 +302,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
private void setPageSize() {
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
final long newValue = SQLiteGlobal.getDefaultPageSize();
- long value = executeForLong("PRAGMA page_size", null, null);
+ long value = executeForLong(null, "PRAGMA page_size", null, null);
if (value != newValue) {
- execute("PRAGMA page_size=" + newValue, null, null);
+ execute(null, "PRAGMA page_size=" + newValue, null, null);
}
}
}
@@ -311,9 +312,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
private void setAutoCheckpointInterval() {
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
final long newValue = SQLiteGlobal.getWALAutoCheckpoint();
- long value = executeForLong("PRAGMA wal_autocheckpoint", null, null);
+ long value = executeForLong(null, "PRAGMA wal_autocheckpoint", null, null);
if (value != newValue) {
- executeForLong("PRAGMA wal_autocheckpoint=" + newValue, null, null);
+ executeForLong(null, "PRAGMA wal_autocheckpoint=" + newValue, null, null);
}
}
}
@@ -321,9 +322,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
private void setJournalSizeLimit() {
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
final long newValue = SQLiteGlobal.getJournalSizeLimit();
- long value = executeForLong("PRAGMA journal_size_limit", null, null);
+ long value = executeForLong(null, "PRAGMA journal_size_limit", null, null);
if (value != newValue) {
- executeForLong("PRAGMA journal_size_limit=" + newValue, null, null);
+ executeForLong(null, "PRAGMA journal_size_limit=" + newValue, null, null);
}
}
}
@@ -331,9 +332,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
private void setForeignKeyModeFromConfiguration() {
if (!mIsReadOnlyConnection) {
final long newValue = mConfiguration.foreignKeyConstraintsEnabled ? 1 : 0;
- long value = executeForLong("PRAGMA foreign_keys", null, null);
+ long value = executeForLong(null, "PRAGMA foreign_keys", null, null);
if (value != newValue) {
- execute("PRAGMA foreign_keys=" + newValue, null, null);
+ execute(null, "PRAGMA foreign_keys=" + newValue, null, null);
}
}
}
@@ -386,7 +387,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
Log.i(TAG, walFile.getAbsolutePath() + " " + size + " bytes: Bigger than "
+ threshold + "; truncating");
try {
- executeForString("PRAGMA wal_checkpoint(TRUNCATE)", null, null);
+ executeForString(null, "PRAGMA wal_checkpoint(TRUNCATE)", null, null);
mConfiguration.shouldTruncateWalFile = false;
} catch (SQLiteException e) {
Log.w(TAG, "Failed to truncate the -wal file", e);
@@ -398,10 +399,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
// No change to the sync mode is intended
return;
}
- String value = executeForString("PRAGMA synchronous", null, null);
+ String value = executeForString(null, "PRAGMA synchronous", null, null);
if (!canonicalizeSyncMode(value).equalsIgnoreCase(
canonicalizeSyncMode(newValue))) {
- execute("PRAGMA synchronous=" + newValue, null, null);
+ execute(null, "PRAGMA synchronous=" + newValue, null, null);
}
}
@@ -420,10 +421,11 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
// No change to the journal mode is intended
return;
}
- String value = executeForString("PRAGMA journal_mode", null, null);
+ String value = executeForString(null, "PRAGMA journal_mode", null, null);
if (!value.equalsIgnoreCase(newValue)) {
try {
- String result = executeForString("PRAGMA journal_mode=" + newValue, null, null);
+ String result = executeForString(null, "PRAGMA journal_mode=" + newValue,
+ null, null);
if (result.equalsIgnoreCase(newValue)) {
return;
}
@@ -475,26 +477,26 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
try {
// Ensure the android metadata table exists.
- execute("CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)", null, null);
+ execute(null, "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 "
+ final String oldLocale = executeForString(null, "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);
+ execute(null, "BEGIN", null, null);
boolean success = false;
try {
- execute("DELETE FROM android_metadata", null, null);
- execute("INSERT INTO android_metadata (locale) VALUES(?)",
+ execute(null, "DELETE FROM android_metadata", null, null);
+ execute(null, "INSERT INTO android_metadata (locale) VALUES(?)",
new Object[] { newLocale }, null);
- execute("REINDEX LOCALIZED", null, null);
+ execute(null, "REINDEX LOCALIZED", null, null);
success = true;
} finally {
- execute(success ? "COMMIT" : "ROLLBACK", null, null);
+ execute(null, success ? "COMMIT" : "ROLLBACK", null, null);
}
} catch (SQLiteException ex) {
throw ex;
@@ -523,10 +525,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
final int type = DatabaseUtils.getSqlStatementType(statement.first);
switch (type) {
case DatabaseUtils.STATEMENT_SELECT:
- executeForString(statement.first, statement.second, null);
+ executeForString(null, statement.first, statement.second, null);
break;
case DatabaseUtils.STATEMENT_PRAGMA:
- execute(statement.first, statement.second, null);
+ execute(null, statement.first, statement.second, null);
break;
default:
throw new IllegalArgumentException(
@@ -543,9 +545,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
final File checkFile = new File(mConfiguration.path
+ SQLiteGlobal.WIPE_CHECK_FILE_SUFFIX);
- final boolean hasMetadataTable = executeForLong(
+ final boolean hasMetadataTable = executeForLong(null,
"SELECT count(*) FROM sqlite_master"
- + " WHERE type='table' AND name='android_metadata'", null, null) > 0;
+ + " WHERE type='table' AND name='android_metadata'",
+ null, null) > 0;
final boolean hasCheckFile = checkFile.exists();
if (!mIsReadOnlyConnection && !hasCheckFile) {
@@ -667,14 +670,15 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
*
* @throws SQLiteException if an error occurs, such as a syntax error.
*/
- public void prepare(String sql, SQLiteStatementInfo outStatementInfo) {
+ public void prepare(@Nullable SQLiteAuthorizer authorizer, @NonNull String sql,
+ @Nullable 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);
+ final PreparedStatement statement = acquirePreparedStatement(authorizer, sql);
try {
if (outStatementInfo != null) {
outStatementInfo.numParameters = statement.mNumParameters;
@@ -714,8 +718,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
- public void execute(String sql, Object[] bindArgs,
- CancellationSignal cancellationSignal) {
+ public void execute(@Nullable SQLiteAuthorizer authorizer,
+ @NonNull String sql, @Nullable Object[] bindArgs,
+ @Nullable CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
@@ -724,7 +729,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
try {
final boolean isPragmaStmt =
DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_PRAGMA;
- final PreparedStatement statement = acquirePreparedStatement(sql);
+ final PreparedStatement statement = acquirePreparedStatement(authorizer, sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
@@ -759,15 +764,15 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
- public long executeForLong(String sql, Object[] bindArgs,
- CancellationSignal cancellationSignal) {
+ public long executeForLong(@Nullable SQLiteAuthorizer authorizer, @NonNull String sql,
+ @Nullable Object[] bindArgs, @Nullable 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);
+ final PreparedStatement statement = acquirePreparedStatement(authorizer, sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
@@ -804,15 +809,15 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
- public String executeForString(String sql, Object[] bindArgs,
- CancellationSignal cancellationSignal) {
+ public String executeForString(@Nullable SQLiteAuthorizer authorizer, @NonNull String sql,
+ @Nullable Object[] bindArgs, @Nullable 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);
+ final PreparedStatement statement = acquirePreparedStatement(authorizer, sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
@@ -851,8 +856,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
- public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs,
- CancellationSignal cancellationSignal) {
+ public ParcelFileDescriptor executeForBlobFileDescriptor(@Nullable SQLiteAuthorizer authorizer,
+ @NonNull String sql, @Nullable Object[] bindArgs,
+ @Nullable CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
@@ -860,7 +866,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
final int cookie = mRecentOperations.beginOperation("executeForBlobFileDescriptor",
sql, bindArgs);
try {
- final PreparedStatement statement = acquirePreparedStatement(sql);
+ final PreparedStatement statement = acquirePreparedStatement(authorizer, sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
@@ -897,8 +903,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
- public int executeForChangedRowCount(String sql, Object[] bindArgs,
- CancellationSignal cancellationSignal) {
+ public int executeForChangedRowCount(@Nullable SQLiteAuthorizer authorizer,
+ @NonNull String sql, @Nullable Object[] bindArgs,
+ @Nullable CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
@@ -907,7 +914,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
final int cookie = mRecentOperations.beginOperation("executeForChangedRowCount",
sql, bindArgs);
try {
- final PreparedStatement statement = acquirePreparedStatement(sql);
+ final PreparedStatement statement = acquirePreparedStatement(authorizer, sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
@@ -946,8 +953,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
- public long executeForLastInsertedRowId(String sql, Object[] bindArgs,
- CancellationSignal cancellationSignal) {
+ public long executeForLastInsertedRowId(@Nullable SQLiteAuthorizer authorizer,
+ @NonNull String sql, @Nullable Object[] bindArgs,
+ @Nullable CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
@@ -955,7 +963,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId",
sql, bindArgs);
try {
- final PreparedStatement statement = acquirePreparedStatement(sql);
+ final PreparedStatement statement = acquirePreparedStatement(authorizer, sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
@@ -1000,9 +1008,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
* 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) {
+ public int executeForCursorWindow(@Nullable SQLiteAuthorizer authorizer, @NonNull String sql,
+ @Nullable Object[] bindArgs, @NonNull CursorWindow window,
+ int startPos, int requiredPos, boolean countAllRows,
+ @Nullable CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
@@ -1018,7 +1027,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
final int cookie = mRecentOperations.beginOperation("executeForCursorWindow",
sql, bindArgs);
try {
- final PreparedStatement statement = acquirePreparedStatement(sql);
+ final PreparedStatement statement = acquirePreparedStatement(authorizer, sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
@@ -1056,13 +1065,19 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
}
}
+
/**
* Return a {@link #PreparedStatement}, possibly from the cache.
*/
- PreparedStatement acquirePreparedStatement(String sql) {
+ private PreparedStatement acquirePreparedStatement(@Nullable SQLiteAuthorizer authorizer,
+ @NonNull String sql) {
++mPool.mTotalPrepareStatements;
- PreparedStatement statement = mPreparedStatementCache.get(sql);
- boolean skipCache = false;
+ // Custom per-statement authorizers are typically used for incoming
+ // untrusted custom SQL, which is likely to have low cache hit ratios,
+ // so we compile them every time. This leaves the prepared statement
+ // cache for statements using the default authorizer.
+ boolean skipCache = (authorizer != null);
+ PreparedStatement statement = skipCache ? null : mPreparedStatementCache.get(sql);
if (statement != null) {
if (!statement.mInUse) {
return statement;
@@ -1073,8 +1088,12 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
skipCache = true;
}
++mPool.mTotalPrepareStatementCacheMiss;
- final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
+ if (authorizer != null) {
+ nativeSetAuthorizer(mConnectionPtr, authorizer);
+ }
+ long statementPtr = 0;
try {
+ statementPtr = nativePrepareStatement(mConnectionPtr, sql);
final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
final int type = DatabaseUtils.getSqlStatementType(sql);
final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
@@ -1086,10 +1105,15 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
} 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.
+ // It is safe to call finalize on a NULL, if nativePrepare failed.
if (statement == null || !statement.mInCache) {
nativeFinalizeStatement(mConnectionPtr, statementPtr);
}
throw ex;
+ } finally {
+ if (authorizer != null) {
+ nativeSetAuthorizer(mConnectionPtr, null);
+ }
}
statement.mInUse = true;
return statement;
@@ -1130,10 +1154,11 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
* Return a prepared statement for use by {@link SQLiteRawStatement}. This throws if the
* prepared statement is incompatible with this connection.
*/
- PreparedStatement acquirePersistentStatement(@NonNull String sql) {
+ PreparedStatement acquirePersistentStatement(@Nullable SQLiteAuthorizer authorizer,
+ @NonNull String sql) {
final int cookie = mRecentOperations.beginOperation("prepare", sql, null);
try {
- final PreparedStatement statement = acquirePreparedStatement(sql);
+ final PreparedStatement statement = acquirePreparedStatement(authorizer, sql);
throwIfStatementForbidden(statement);
return statement;
} catch (RuntimeException e) {
@@ -1328,8 +1353,8 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
long pageCount = 0;
long pageSize = 0;
try {
- pageCount = executeForLong("PRAGMA page_count;", null, null);
- pageSize = executeForLong("PRAGMA page_size;", null, null);
+ pageCount = executeForLong(null, "PRAGMA page_count;", null, null);
+ pageSize = executeForLong(null, "PRAGMA page_size;", null, null);
} catch (SQLiteException ex) {
// Ignore.
}
@@ -1340,15 +1365,15 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
// the main database which we have already described.
CursorWindow window = new CursorWindow("collectDbStats");
try {
- executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false, null);
+ executeForCursorWindow(null, "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);
+ pageCount = executeForLong(null, "PRAGMA " + name + ".page_count;", null, null);
+ pageSize = executeForLong(null, "PRAGMA " + name + ".page_size;", null, null);
} catch (SQLiteException ex) {
// Ignore.
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 839186548e9a..06d4f2840ccd 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -44,10 +44,12 @@ import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
import android.util.Printer;
+
import com.android.internal.util.Preconditions;
import dalvik.annotation.optimization.NeverCompile;
import dalvik.system.CloseGuard;
+
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
@@ -253,6 +255,13 @@ public final class SQLiteDatabase extends SQLiteClosable {
*/
public static final int NO_LOCALIZED_COLLATORS = 0x00000010; // update native code if changing
+ /** @hide */
+ public static final int ENABLE_TRACE = 0x00000100;
+ /** @hide */
+ public static final int ENABLE_PROFILE = 0x00000200;
+ /** @hide */
+ public static final int ENABLE_AUTHORIZER = 0x00000400;
+
/**
* Open flag: Flag for {@link #openDatabase} to create the database file if it does not
* already exist.
@@ -1453,9 +1462,38 @@ public final class SQLiteDatabase extends SQLiteClosable {
* {@link SQLiteStatement}s are not synchronized, see the documentation for more details.
*/
public SQLiteStatement compileStatement(String sql) throws SQLException {
+ return compileStatementInternal(sql, null);
+ }
+
+ /**
+ * 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.
+ *<p>
+ * No two threads should be using the same {@link SQLiteStatement} at the same time.
+ * Must configure: {@link OpenParams.Builder#setAuthorizerSupportEnabled(boolean)}.
+ *
+ * @param sql The raw SQL statement, may contain ? for unknown values to be
+ * bound later.
+ * @param authorizer The sql authorizer attached to the statement.
+ * @return A pre-compiled {@link SQLiteStatement} object. Note that
+ * {@link SQLiteStatement}s are not synchronized, see the documentation for more details.
+ */
+ public @NonNull SQLiteStatement compileStatement(@NonNull String sql,
+ @NonNull SQLiteAuthorizer authorizer) throws SQLException {
+ return compileStatementInternal(sql, authorizer);
+ }
+
+ private @NonNull SQLiteStatement compileStatementInternal(@NonNull String sql,
+ @Nullable SQLiteAuthorizer authorizer) throws SQLException {
+ if (authorizer != null) {
+ throwIfNotAuthorizerEnabled();
+ }
acquireReference();
try {
- return new SQLiteStatement(this, sql, null);
+ return new SQLiteStatement(this, authorizer, sql, null);
} finally {
releaseReference();
}
@@ -1752,7 +1790,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
public Cursor rawQueryWithFactory(
CursorFactory cursorFactory, String sql, String[] selectionArgs,
String editTable) {
- return rawQueryWithFactory(cursorFactory, sql, selectionArgs, editTable, null);
+ return rawQueryWithFactoryInternal(cursorFactory, sql, selectionArgs,
+ editTable, null, null);
}
/**
@@ -1773,17 +1812,52 @@ public final class SQLiteDatabase extends SQLiteClosable {
public Cursor rawQueryWithFactory(
CursorFactory cursorFactory, String sql, String[] selectionArgs,
String editTable, CancellationSignal cancellationSignal) {
+ return rawQueryWithFactoryInternal(cursorFactory, sql, selectionArgs, editTable,
+ cancellationSignal, null);
+ }
+
+ /**
+ * Runs the provided SQL and returns a cursor over the result set.
+ * Must configure: {@link OpenParams.Builder#setAuthorizerSupportEnabled(boolean)}.
+ *
+ * @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. The
+ * values will be bound as Strings.
+ * @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.
+ * @param authorizer The sql authorizer attached to the 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.
+ */
+ public @NonNull Cursor rawQueryWithFactory(@Nullable CursorFactory cursorFactory,
+ @NonNull String sql, @SuppressLint("ArrayReturn") @Nullable String[] selectionArgs,
+ @Nullable String editTable, @Nullable CancellationSignal cancellationSignal,
+ @NonNull SQLiteAuthorizer authorizer) {
+ return rawQueryWithFactoryInternal(cursorFactory, sql, selectionArgs, editTable,
+ cancellationSignal, authorizer);
+ }
+
+ private @NonNull Cursor rawQueryWithFactoryInternal(@Nullable CursorFactory cursorFactory,
+ @NonNull String sql, @SuppressLint("ArrayReturn") @Nullable String[] selectionArgs,
+ @Nullable String editTable, @Nullable CancellationSignal cancellationSignal,
+ @Nullable SQLiteAuthorizer authorizer) {
+ if (authorizer != null) {
+ throwIfNotAuthorizerEnabled();
+ }
acquireReference();
try {
- SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable,
- cancellationSignal);
+ SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, authorizer, sql,
+ editTable, cancellationSignal);
return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory,
selectionArgs);
} finally {
releaseReference();
}
}
-
/**
* Convenience method for inserting a row into the database.
*
@@ -1931,7 +2005,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
}
sql.append(')');
- SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
+ // Custom authorizers can be applied through SQLiteQueryBuilder
+ SQLiteStatement statement = new SQLiteStatement(this, null, sql.toString(), bindArgs);
try {
return statement.executeInsert();
} finally {
@@ -1958,8 +2033,9 @@ public final class SQLiteDatabase extends SQLiteClosable {
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);
+ // Custom authorizers can be applied through SQLiteQueryBuilder
+ SQLiteStatement statement = new SQLiteStatement(this, null, "DELETE FROM " + table
+ + (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs);
try {
return statement.executeUpdateDelete();
} finally {
@@ -2036,7 +2112,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
sql.append(whereClause);
}
- SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
+ // Custom authorizers can be applied through SQLiteQueryBuilder
+ SQLiteStatement statement = new SQLiteStatement(this, null, sql.toString(), bindArgs);
try {
return statement.executeUpdateDelete();
} finally {
@@ -2073,7 +2150,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
* @throws SQLException if the SQL string is invalid
*/
public void execSQL(String sql) throws SQLException {
- executeSql(sql, null);
+ executeSql(null, sql, null);
}
/**
@@ -2126,14 +2203,72 @@ public final class SQLiteDatabase extends SQLiteClosable {
* @throws SQLException if the SQL string is invalid
*/
public void execSQL(String sql, Object[] bindArgs) throws SQLException {
- if (bindArgs == null) {
- throw new IllegalArgumentException("Empty bindArgs");
+ executeSql(null, sql, bindArgs);
+ }
+
+ /**
+ * Execute a single SQL statement that is NOT a SELECT/INSERT/UPDATE/DELETE.
+ * Must configure: {@link OpenParams.Builder#setAuthorizerSupportEnabled(boolean)}.
+ * <p>
+ * For INSERT statements, use any of the following instead.
+ * <ul>
+ * <li>{@link #insert(String, String, ContentValues)}</li>
+ * <li>{@link #insertOrThrow(String, String, ContentValues)}</li>
+ * <li>{@link #insertWithOnConflict(String, String, ContentValues, int)}</li>
+ * </ul>
+ * <p>
+ * For UPDATE statements, use any of the following instead.
+ * <ul>
+ * <li>{@link #update(String, ContentValues, String, String[])}</li>
+ * <li>{@link #updateWithOnConflict(String, ContentValues, String, String[], int)}</li>
+ * </ul>
+ * <p>
+ * For DELETE statements, use any of the following instead.
+ * <ul>
+ * <li>{@link #delete(String, String, String[])}</li>
+ * </ul>
+ * <p>
+ * For example, the following are good candidates for using this method:
+ * <ul>
+ * <li>ALTER TABLE</li>
+ * <li>CREATE or DROP table / trigger / view / index / virtual table</li>
+ * <li>REINDEX</li>
+ * <li>RELEASE</li>
+ * <li>SAVEPOINT</li>
+ * <li>PRAGMA that returns no data</li>
+ * </ul>
+ * </p>
+ * <p>
+ * When using {@link #enableWriteAheadLogging()}, journal_mode is
+ * automatically managed by this class. So, do not set journal_mode
+ * using "PRAGMA journal_mode'<value>" statement if your app is using
+ * {@link #enableWriteAheadLogging()}
+ * </p>
+ * <p>
+ * Note that {@code PRAGMA} values which apply on a per-connection basis
+ * should <em>not</em> be configured using this method; you should instead
+ * use {@link #execPerConnectionSQL} to ensure that they are uniformly
+ * applied to all current and future connections.
+ * </p>
+ *
+ * @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.
+ * @param authorizer The sql authorizer attached to the query.
+ * @throws SQLException if the SQL string is invalid
+ */
+ public void execSQL(@NonNull String sql,
+ @SuppressLint("ArrayReturn") @Nullable Object[] bindArgs,
+ @NonNull SQLiteAuthorizer authorizer) throws SQLException {
+ if (authorizer != null) {
+ throwIfNotAuthorizerEnabled();
}
- executeSql(sql, bindArgs);
+ executeSql(authorizer, sql, bindArgs);
}
/** {@hide} */
- public int executeSql(String sql, Object[] bindArgs) throws SQLException {
+ public int executeSql(@Nullable SQLiteAuthorizer authorizer, @NonNull String sql,
+ @Nullable Object[] bindArgs) throws SQLException {
acquireReference();
try {
final int statementType = DatabaseUtils.getSqlStatementType(sql);
@@ -2151,7 +2286,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
}
}
- try (SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs)) {
+ try (SQLiteStatement statement = new SQLiteStatement(this, authorizer, sql, bindArgs)) {
return statement.executeUpdateDelete();
} finally {
// If schema was updated, close non-primary connections, otherwise they might
@@ -2169,19 +2304,37 @@ public final class SQLiteDatabase extends SQLiteClosable {
* Return a {@link SQLiteRawStatement} connected to the database. A transaction must be in
* progress or an exception will be thrown. The resulting object will be closed automatically
* when the current transaction closes.
+ * Must configure: {@link OpenParams.Builder#setAuthorizerSupportEnabled(boolean)}.
* @param sql The SQL string to be compiled into a prepared statement.
+ * @param authorizer The sql authorizer attached to the statement.
* @return A {@link SQLiteRawStatement} holding the compiled SQL.
* @throws IllegalStateException if a transaction is not in progress.
* @throws SQLiteException if the SQL cannot be compiled.
* @hide
*/
@NonNull
- public SQLiteRawStatement createRawStatement(@NonNull String sql) {
+ public SQLiteRawStatement createRawStatement(@NonNull String sql,
+ @NonNull SQLiteAuthorizer authorizer) {
Objects.requireNonNull(sql);
- return new SQLiteRawStatement(this, sql);
+ return new SQLiteRawStatement(this, sql, authorizer);
}
/**
+ * Return a {@link SQLiteRawStatement} connected to the database. A transaction must be in
+ * progress or an exception will be thrown. The resulting object will be closed automatically
+ * when the current transaction closes.
+ * @param sql The SQL string to be compiled into a prepared statement.
+ * @return A {@link SQLiteRawStatement} holding the compiled SQL.
+ * @throws IllegalStateException if a transaction is not in progress.
+ * @throws SQLiteException if the SQL cannot be compiled.
+ * @hide
+ */
+ @NonNull
+ public SQLiteRawStatement createRawStatement(@NonNull String sql) {
+ Objects.requireNonNull(sql);
+ return createRawStatement(sql, null);
+ }
+ /**
* Return the "rowid" of the last row to be inserted on the current connection. See the
* SQLite documentation for the specific details. This method must only be called when inside
* a transaction. {@link IllegalStateException} is thrown if the method is called outside a
@@ -2206,7 +2359,33 @@ public final class SQLiteDatabase extends SQLiteClosable {
* @throws SQLiteException if {@code sql} is invalid
*/
public void validateSql(@NonNull String sql, @Nullable CancellationSignal cancellationSignal) {
- getThreadSession().prepare(sql,
+ validateSqlInternal(sql, cancellationSignal, null);
+ }
+
+ /**
+ * 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}.
+ * Must configure: {@link OpenParams.Builder#setAuthorizerSupportEnabled(boolean)}.
+ *
+ * @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.
+ * @param authorizer The sql authorizer attached to the query.
+ * @throws SQLiteException if {@code sql} is invalid
+ */
+ public void validateSql(@NonNull String sql, @Nullable CancellationSignal cancellationSignal,
+ @NonNull SQLiteAuthorizer authorizer) {
+ validateSqlInternal(sql, cancellationSignal, authorizer);
+ }
+
+ private void validateSqlInternal(@NonNull String sql,
+ @Nullable CancellationSignal cancellationSignal,
+ @Nullable SQLiteAuthorizer authorizer) {
+ if (authorizer != null) {
+ throwIfNotAuthorizerEnabled();
+ }
+ getThreadSession().prepare(authorizer, sql,
getThreadDefaultConnectionFlags(/* readOnly =*/ true), cancellationSignal, null);
}
@@ -2226,6 +2405,21 @@ public final class SQLiteDatabase extends SQLiteClosable {
}
/**
+ * Returns true if the database is opened with authorizer support enabled.
+ *
+ * @return True if authorizer support is enabled.
+ */
+ public boolean isAuthorizerSupportEnabled() {
+ synchronized (mLock) {
+ return isAuthorizerSupportEnabledLocked();
+ }
+ }
+
+ private boolean isAuthorizerSupportEnabledLocked() {
+ return (mConfigurationLocked.openFlags & ENABLE_AUTHORIZER) != 0;
+ }
+
+ /**
* Returns true if the database is in-memory db.
*
* @return True if the database is in-memory.
@@ -2810,6 +3004,13 @@ public final class SQLiteDatabase extends SQLiteClosable {
return "SQLiteDatabase: " + getPath();
}
+ private void throwIfNotAuthorizerEnabled() {
+ if ((mConfigurationLocked.openFlags & ENABLE_AUTHORIZER) == 0) {
+ throw new IllegalStateException("The database '" + mConfigurationLocked.label
+ + "' does not have authorizer support enabled.");
+ }
+ }
+
private void throwIfNotOpenLocked() {
if (mConnectionPoolLocked == null) {
throw new IllegalStateException("The database '" + mConfigurationLocked.label
@@ -3074,6 +3275,39 @@ public final class SQLiteDatabase extends SQLiteClosable {
}
/**
+ * Returns if support for {@link SQLiteAuthorizer} is enabled.
+ *
+ * @see SQLiteDatabase#compileStatement(String, SQLiteAuthorizer)
+ * @see SQLiteDatabase#execSQL(String, Object[], SQLiteAuthorizer)
+ * @see SQLiteDatabase#validateSql(String, CancellationSignal,
+ * SQLiteAuthorizer)
+ * @see SQLiteDatabase#rawQueryWithFactory(CursorFactory, String,
+ * String[], String, CancellationSignal, SQLiteAuthorizer)
+ */
+ public boolean isAuthorizerSupportEnabled() {
+ return (mOpenFlags & ENABLE_AUTHORIZER) != 0;
+ }
+
+ /**
+ * Enables or disables support for {@link SQLiteAuthorizer}.
+ *
+ * @see SQLiteDatabase#compileStatement(String, SQLiteAuthorizer)
+ * @see SQLiteDatabase#execSQL(String, Object[], SQLiteAuthorizer)
+ * @see SQLiteDatabase#validateSql(String, CancellationSignal,
+ * SQLiteAuthorizer)
+ * @see SQLiteDatabase#rawQueryWithFactory(CursorFactory, String,
+ * String[], String, CancellationSignal, SQLiteAuthorizer)
+ */
+ public @NonNull Builder setAuthorizerSupportEnabled(boolean enabled) {
+ if (enabled) {
+ addOpenFlags(SQLiteDatabase.ENABLE_AUTHORIZER);
+ } else {
+ removeOpenFlags(SQLiteDatabase.ENABLE_AUTHORIZER);
+ }
+ return this;
+ }
+
+ /**
* Set an optional factory class that is called to instantiate a cursor when query
* is called.
*
@@ -3174,7 +3408,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
OPEN_READONLY,
CREATE_IF_NECESSARY,
NO_LOCALIZED_COLLATORS,
- ENABLE_WRITE_AHEAD_LOGGING
+ ENABLE_WRITE_AHEAD_LOGGING,
+ ENABLE_AUTHORIZER
})
@Retention(RetentionPolicy.SOURCE)
public @interface DatabaseOpenFlags {}
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
index bc6368639baa..63b59f77845d 100644
--- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -17,9 +17,11 @@
package android.database.sqlite;
import android.compat.annotation.UnsupportedAppUsage;
+import android.database.sqlite.SQLiteDebug.NoPreloadHolder;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Pair;
+
import java.util.ArrayList;
import java.util.Locale;
import java.util.function.BinaryOperator;
@@ -155,6 +157,13 @@ public final class SQLiteDatabaseConfiguration {
throw new IllegalArgumentException("path must not be null.");
}
+ if (NoPreloadHolder.DEBUG_SQL_STATEMENTS) {
+ openFlags |= SQLiteDatabase.ENABLE_PROFILE;
+ }
+ if (NoPreloadHolder.DEBUG_SQL_TIME) {
+ openFlags |= SQLiteDatabase.ENABLE_TRACE;
+ }
+
this.path = path;
label = stripPathForLogs(path);
this.openFlags = openFlags;
diff --git a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
index 1721e0c69dc3..8be11d042fe9 100644
--- a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
+++ b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
@@ -16,6 +16,8 @@
package android.database.sqlite;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
@@ -29,6 +31,7 @@ import android.os.CancellationSignal;
@TestApi
public final class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
private final SQLiteDatabase mDatabase;
+ private final SQLiteAuthorizer mAuthorizer;
private final String mEditTable;
private final String mSql;
private final CancellationSignal mCancellationSignal;
@@ -36,14 +39,22 @@ public final class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable,
CancellationSignal cancellationSignal) {
+ this(db, null, sql, editTable, cancellationSignal);
+ }
+
+ public SQLiteDirectCursorDriver(@NonNull SQLiteDatabase db,
+ @Nullable SQLiteAuthorizer authorizer, @NonNull String sql,
+ @Nullable String editTable, @Nullable CancellationSignal cancellationSignal) {
mDatabase = db;
+ mAuthorizer = authorizer;
mEditTable = editTable;
mSql = sql;
mCancellationSignal = cancellationSignal;
}
public Cursor query(CursorFactory factory, String[] selectionArgs) {
- final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancellationSignal);
+ final SQLiteQuery query = new SQLiteQuery(mDatabase,
+ mAuthorizer, mSql, mCancellationSignal);
final Cursor cursor;
try {
query.bindAllArgsAsStrings(selectionArgs);
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index cd4131ce2abb..84b2a8a1b8aa 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -16,6 +16,8 @@
package android.database.sqlite;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.database.DatabaseUtils;
import android.os.Build;
@@ -33,6 +35,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private final SQLiteDatabase mDatabase;
+ private final SQLiteAuthorizer mAuthorizer;
@UnsupportedAppUsage
private final String mSql;
private final boolean mReadOnly;
@@ -41,9 +44,11 @@ public abstract class SQLiteProgram extends SQLiteClosable {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private final Object[] mBindArgs;
- SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs,
- CancellationSignal cancellationSignalForPrepare) {
+ SQLiteProgram(@NonNull SQLiteDatabase db, @Nullable SQLiteAuthorizer authorizer,
+ @NonNull String sql, @Nullable Object[] bindArgs,
+ @Nullable CancellationSignal cancellationSignalForPrepare) {
mDatabase = db;
+ mAuthorizer = authorizer;
mSql = sql.trim();
int n = DatabaseUtils.getSqlStatementType(mSql);
@@ -59,7 +64,7 @@ public abstract class SQLiteProgram extends SQLiteClosable {
default:
boolean assumeReadOnly = (n == DatabaseUtils.STATEMENT_SELECT);
SQLiteStatementInfo info = new SQLiteStatementInfo();
- db.getThreadSession().prepare(mSql,
+ db.getThreadSession().prepare(mAuthorizer, mSql,
db.getThreadDefaultConnectionFlags(assumeReadOnly),
cancellationSignalForPrepare, info);
mReadOnly = info.readOnly;
@@ -88,6 +93,10 @@ public abstract class SQLiteProgram extends SQLiteClosable {
return mDatabase;
}
+ final SQLiteAuthorizer getAuthorizer() {
+ return mAuthorizer;
+ }
+
final String getSql() {
return mSql;
}
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index 62bcc2060d2a..1d23193c68cf 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -16,6 +16,8 @@
package android.database.sqlite;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.database.CursorWindow;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
@@ -33,8 +35,9 @@ public final class SQLiteQuery extends SQLiteProgram {
private final CancellationSignal mCancellationSignal;
- SQLiteQuery(SQLiteDatabase db, String query, CancellationSignal cancellationSignal) {
- super(db, query, null, cancellationSignal);
+ SQLiteQuery(@NonNull SQLiteDatabase db, @Nullable SQLiteAuthorizer authorizer,
+ @NonNull String query, @Nullable CancellationSignal cancellationSignal) {
+ super(db, authorizer, query, null, cancellationSignal);
mCancellationSignal = cancellationSignal;
}
@@ -59,9 +62,9 @@ public final class SQLiteQuery extends SQLiteProgram {
try {
window.acquireReference();
try {
- int numRows = getSession().executeForCursorWindow(getSql(), getBindArgs(),
- window, startPos, requiredPos, countAllRows, getConnectionFlags(),
- mCancellationSignal);
+ int numRows = getSession().executeForCursorWindow(getAuthorizer(), getSql(),
+ getBindArgs(), window, startPos, requiredPos, countAllRows,
+ getConnectionFlags(), mCancellationSignal);
return numRows;
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java
index 6b43788cd479..6c1ec39fab9b 100644
--- a/core/java/android/database/sqlite/SQLiteRawStatement.java
+++ b/core/java/android/database/sqlite/SQLiteRawStatement.java
@@ -20,8 +20,6 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import com.android.internal.annotations.VisibleForTesting;
-
import dalvik.annotation.optimization.FastNative;
import java.io.Closeable;
@@ -138,14 +136,15 @@ public final class SQLiteRawStatement implements Closeable {
* {@link IllegalStateException} if a transaction is not in progress. Clients should call
* {@link SQLiteDatabase.createRawStatement} to create a new instance.
*/
- SQLiteRawStatement(@NonNull SQLiteDatabase db, @NonNull String sql) throws SQLiteException {
+ SQLiteRawStatement(@NonNull SQLiteDatabase db, @NonNull String sql,
+ @Nullable SQLiteAuthorizer authorizer) throws SQLiteException {
mThread = Thread.currentThread();
mDatabase = db;
mSession = mDatabase.getThreadSession();
mSession.throwIfNoTransaction();
mSql = sql;
// Acquire a connection and prepare the statement.
- mPreparedStatement = mSession.acquirePersistentStatement(mSql, this);
+ mPreparedStatement = mSession.acquirePersistentStatement(authorizer, mSql, this);
mStatement = mPreparedStatement.mStatementPtr;
}
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index 2379c849e534..936ff343a1f8 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -17,7 +17,7 @@
package android.database.sqlite;
import android.annotation.NonNull;
-
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.database.CursorWindow;
import android.database.DatabaseUtils;
@@ -80,7 +80,7 @@ import java.util.ArrayDeque;
* 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 #end}. If the transaction was
+ * transaction was successful, then call {@link #endTransaction}. If the transaction was
* marked successful, its changes will be committed, otherwise they will be rolled back.
* </p><p>
* Explicit transactions can also be nested. A nested explicit transaction is
@@ -309,12 +309,12 @@ public final class SQLiteSession {
CancellationSignal cancellationSignal) {
throwIfTransactionMarkedSuccessful();
beginTransactionUnchecked(transactionMode, transactionListener, connectionFlags,
- cancellationSignal);
+ cancellationSignal);
}
private void beginTransactionUnchecked(int transactionMode,
SQLiteTransactionListener transactionListener, int connectionFlags,
- CancellationSignal cancellationSignal) {
+ @Nullable CancellationSignal cancellationSignal) {
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
}
@@ -329,20 +329,21 @@ public final class SQLiteSession {
// Execute SQL might throw a runtime exception.
switch (transactionMode) {
case TRANSACTION_MODE_IMMEDIATE:
- mConnection.execute("BEGIN IMMEDIATE;", null,
+ mConnection.execute(null, "BEGIN IMMEDIATE;", null,
cancellationSignal); // might throw
break;
case TRANSACTION_MODE_EXCLUSIVE:
- mConnection.execute("BEGIN EXCLUSIVE;", null,
+ mConnection.execute(null, "BEGIN EXCLUSIVE;", null,
cancellationSignal); // might throw
break;
case TRANSACTION_MODE_DEFERRED:
- mConnection.execute("BEGIN DEFERRED;", null,
+ mConnection.execute(null, "BEGIN DEFERRED;", null,
cancellationSignal); // might throw
break;
default:
// Per SQLite documentation, this executes in DEFERRED mode.
- mConnection.execute("BEGIN;", null, cancellationSignal); // might throw
+ mConnection.execute(null, "BEGIN;",
+ null, cancellationSignal); // might throw
break;
}
}
@@ -353,7 +354,8 @@ public final class SQLiteSession {
transactionListener.onBegin(); // might throw
} catch (RuntimeException ex) {
if (mTransactionStack == null) {
- mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
+ mConnection.execute(null, "ROLLBACK;", null,
+ cancellationSignal); // might throw
}
throw ex;
}
@@ -415,14 +417,14 @@ public final class SQLiteSession {
* @see #setTransactionSuccessful
* @see #yieldTransaction
*/
- public void endTransaction(CancellationSignal cancellationSignal) {
+ public void endTransaction(@Nullable CancellationSignal cancellationSignal) {
throwIfNoTransaction();
assert mConnection != null;
-
endTransactionUnchecked(cancellationSignal, false);
}
- private void endTransactionUnchecked(CancellationSignal cancellationSignal, boolean yielding) {
+ private void endTransactionUnchecked(@Nullable CancellationSignal cancellationSignal,
+ boolean yielding) {
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
}
@@ -460,9 +462,11 @@ public final class SQLiteSession {
try {
if (successful) {
- mConnection.execute("COMMIT;", null, cancellationSignal); // might throw
+ mConnection.execute(null, "COMMIT;", null,
+ cancellationSignal); // might throw
} else {
- mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
+ mConnection.execute(null, "ROLLBACK;", null,
+ cancellationSignal); // might throw
}
} finally {
releaseConnection(); // might throw
@@ -599,8 +603,9 @@ public final class SQLiteSession {
* @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) {
+ public void prepare(@Nullable SQLiteAuthorizer authorizer, @NonNull String sql,
+ int connectionFlags, @Nullable CancellationSignal cancellationSignal,
+ @Nullable SQLiteStatementInfo outStatementInfo) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
@@ -611,7 +616,7 @@ public final class SQLiteSession {
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
- mConnection.prepare(sql, outStatementInfo); // might throw
+ mConnection.prepare(authorizer, sql, outStatementInfo); // might throw
} finally {
releaseConnection(); // might throw
}
@@ -630,19 +635,20 @@ public final class SQLiteSession {
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
- public void execute(String sql, Object[] bindArgs, int connectionFlags,
- CancellationSignal cancellationSignal) {
+ public void execute(@Nullable SQLiteAuthorizer authorizer,
+ @NonNull String sql, @Nullable Object[] bindArgs, int connectionFlags,
+ @Nullable CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
- if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
+ if (executeSpecial(sql, connectionFlags, cancellationSignal)) {
return;
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
- mConnection.execute(sql, bindArgs, cancellationSignal); // might throw
+ mConnection.execute(authorizer, sql, bindArgs, cancellationSignal); // might throw
} finally {
releaseConnection(); // might throw
}
@@ -663,19 +669,21 @@ public final class SQLiteSession {
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
- public long executeForLong(String sql, Object[] bindArgs, int connectionFlags,
- CancellationSignal cancellationSignal) {
+ public long executeForLong(@Nullable SQLiteAuthorizer authorizer,
+ @NonNull String sql, @Nullable Object[] bindArgs, int connectionFlags,
+ @Nullable CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
- if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
+ if (executeSpecial(sql, connectionFlags, cancellationSignal)) {
return 0;
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
- return mConnection.executeForLong(sql, bindArgs, cancellationSignal); // might throw
+ return mConnection.executeForLong(authorizer, sql, bindArgs,
+ cancellationSignal); // might throw
} finally {
releaseConnection(); // might throw
}
@@ -696,19 +704,21 @@ public final class SQLiteSession {
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
- public String executeForString(String sql, Object[] bindArgs, int connectionFlags,
- CancellationSignal cancellationSignal) {
+ public String executeForString(@Nullable SQLiteAuthorizer authorizer,
+ @NonNull String sql, @Nullable Object[] bindArgs, int connectionFlags,
+ @Nullable CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
- if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
+ if (executeSpecial(sql, connectionFlags, cancellationSignal)) {
return null;
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
- return mConnection.executeForString(sql, bindArgs, cancellationSignal); // might throw
+ return mConnection.executeForString(authorizer, sql, bindArgs,
+ cancellationSignal); // might throw
} finally {
releaseConnection(); // might throw
}
@@ -731,19 +741,20 @@ public final class SQLiteSession {
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
- public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs,
- int connectionFlags, CancellationSignal cancellationSignal) {
+ public ParcelFileDescriptor executeForBlobFileDescriptor(@Nullable SQLiteAuthorizer authorizer,
+ @NonNull String sql, @Nullable Object[] bindArgs, int connectionFlags,
+ @Nullable CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
- if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
+ if (executeSpecial(sql, connectionFlags, cancellationSignal)) {
return null;
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
- return mConnection.executeForBlobFileDescriptor(sql, bindArgs,
+ return mConnection.executeForBlobFileDescriptor(authorizer, sql, bindArgs,
cancellationSignal); // might throw
} finally {
releaseConnection(); // might throw
@@ -765,19 +776,20 @@ public final class SQLiteSession {
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
- public int executeForChangedRowCount(String sql, Object[] bindArgs, int connectionFlags,
- CancellationSignal cancellationSignal) {
+ public int executeForChangedRowCount(@Nullable SQLiteAuthorizer authorizer,
+ @NonNull String sql, @Nullable Object[] bindArgs, int connectionFlags,
+ @Nullable CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
- if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
+ if (executeSpecial(sql, connectionFlags, cancellationSignal)) {
return 0;
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
- return mConnection.executeForChangedRowCount(sql, bindArgs,
+ return mConnection.executeForChangedRowCount(authorizer, sql, bindArgs,
cancellationSignal); // might throw
} finally {
releaseConnection(); // might throw
@@ -799,19 +811,20 @@ public final class SQLiteSession {
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
- public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags,
- CancellationSignal cancellationSignal) {
+ public long executeForLastInsertedRowId(@Nullable SQLiteAuthorizer authorizer,
+ @NonNull String sql, @Nullable Object[] bindArgs, int connectionFlags,
+ @Nullable CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
- if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
+ if (executeSpecial(sql, connectionFlags, cancellationSignal)) {
return 0;
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
- return mConnection.executeForLastInsertedRowId(sql, bindArgs,
+ return mConnection.executeForLastInsertedRowId(authorizer, sql, bindArgs,
cancellationSignal); // might throw
} finally {
releaseConnection(); // might throw
@@ -842,9 +855,10 @@ public final class SQLiteSession {
* 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) {
+ public int executeForCursorWindow(@Nullable SQLiteAuthorizer authorizer, @NonNull String sql,
+ @Nullable Object[] bindArgs, @NonNull CursorWindow window, int startPos,
+ int requiredPos, boolean countAllRows, int connectionFlags,
+ @Nullable CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
@@ -852,14 +866,14 @@ public final class SQLiteSession {
throw new IllegalArgumentException("window must not be null.");
}
- if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) {
+ if (executeSpecial(sql, connectionFlags, cancellationSignal)) {
window.clear();
return 0;
}
acquireConnection(sql, connectionFlags, cancellationSignal); // might throw
try {
- return mConnection.executeForCursorWindow(sql, bindArgs,
+ return mConnection.executeForCursorWindow(authorizer, sql, bindArgs,
window, startPos, requiredPos, countAllRows,
cancellationSignal); // might throw
} finally {
@@ -888,8 +902,8 @@ public final class SQLiteSession {
* or invalid number of bind arguments.
* @throws OperationCanceledException if the operation was canceled.
*/
- private boolean executeSpecial(String sql, Object[] bindArgs, int connectionFlags,
- CancellationSignal cancellationSignal) {
+ private boolean executeSpecial(@NonNull String sql, int connectionFlags,
+ @Nullable CancellationSignal cancellationSignal) {
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
}
@@ -942,13 +956,14 @@ public final class SQLiteSession {
* method is called when the transaction is closed.
*/
@NonNull
- SQLiteConnection.PreparedStatement acquirePersistentStatement(@NonNull String query,
- @NonNull Closeable dependent) {
+ SQLiteConnection.PreparedStatement acquirePersistentStatement(
+ @Nullable SQLiteAuthorizer authorizer,
+ @NonNull String query, @NonNull Closeable dependent) {
throwIfNoTransaction();
throwIfTransactionMarkedSuccessful();
mOpenDependents.addFirst(dependent);
try {
- return mConnection.acquirePersistentStatement(query);
+ return mConnection.acquirePersistentStatement(authorizer, query);
} catch (Throwable e) {
mOpenDependents.remove(dependent);
throw e;
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index acdc0fac0e98..82023c79270f 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -16,6 +16,8 @@
package android.database.sqlite;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.ParcelFileDescriptor;
@@ -32,7 +34,12 @@ import android.os.ParcelFileDescriptor;
public final class SQLiteStatement extends SQLiteProgram {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) {
- super(db, sql, bindArgs, null);
+ super(db, null, sql, bindArgs, null);
+ }
+
+ SQLiteStatement(@NonNull SQLiteDatabase db, @Nullable SQLiteAuthorizer authorizer,
+ @NonNull String sql, @Nullable Object[] bindArgs) {
+ super(db, authorizer, sql, bindArgs, null);
}
/**
@@ -45,7 +52,8 @@ public final class SQLiteStatement extends SQLiteProgram {
public void execute() {
acquireReference();
try {
- getSession().execute(getSql(), getBindArgs(), getConnectionFlags(), null);
+ getSession().execute(
+ getAuthorizer(), getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
@@ -66,7 +74,7 @@ public final class SQLiteStatement extends SQLiteProgram {
acquireReference();
try {
return getSession().executeForChangedRowCount(
- getSql(), getBindArgs(), getConnectionFlags(), null);
+ getAuthorizer(), getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
@@ -88,7 +96,7 @@ public final class SQLiteStatement extends SQLiteProgram {
acquireReference();
try {
return getSession().executeForLastInsertedRowId(
- getSql(), getBindArgs(), getConnectionFlags(), null);
+ getAuthorizer(), getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
@@ -109,7 +117,7 @@ public final class SQLiteStatement extends SQLiteProgram {
acquireReference();
try {
return getSession().executeForLong(
- getSql(), getBindArgs(), getConnectionFlags(), null);
+ getAuthorizer(), getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
@@ -130,7 +138,7 @@ public final class SQLiteStatement extends SQLiteProgram {
acquireReference();
try {
return getSession().executeForString(
- getSql(), getBindArgs(), getConnectionFlags(), null);
+ getAuthorizer(), getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
@@ -151,7 +159,7 @@ public final class SQLiteStatement extends SQLiteProgram {
acquireReference();
try {
return getSession().executeForBlobFileDescriptor(
- getSql(), getBindArgs(), getConnectionFlags(), null);
+ getAuthorizer(), getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index 7e827a837dce..729c1c453388 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -59,6 +59,10 @@ namespace android {
static const int BUSY_TIMEOUT_MS = 2500;
static struct {
+ jmethodID onAuthorize;
+} gAuthorizer;
+
+static struct {
jmethodID apply;
} gUnaryOperator;
@@ -70,11 +74,14 @@ struct SQLiteConnection {
// Open flags.
// Must be kept in sync with the constants defined in SQLiteDatabase.java.
enum {
- OPEN_READWRITE = 0x00000000,
- OPEN_READONLY = 0x00000001,
- OPEN_READ_MASK = 0x00000001,
- NO_LOCALIZED_COLLATORS = 0x00000010,
- CREATE_IF_NECESSARY = 0x10000000,
+ OPEN_READWRITE = 0x00000000,
+ OPEN_READONLY = 0x00000001,
+ OPEN_READ_MASK = 0x00000001,
+ NO_LOCALIZED_COLLATORS = 0x00000010,
+ ENABLE_TRACE = 0x00000100,
+ ENABLE_PROFILE = 0x00000200,
+ ENABLE_AUTHORIZER = 0x00000400,
+ CREATE_IF_NECESSARY = 0x10000000,
};
sqlite3* const db;
@@ -83,9 +90,15 @@ struct SQLiteConnection {
const String8 label;
volatile bool canceled;
-
- SQLiteConnection(sqlite3* db, int openFlags, const String8& path, const String8& label) :
- db(db), openFlags(openFlags), path(path), label(label), canceled(false) { }
+ volatile jobject authorizer;
+
+ SQLiteConnection(sqlite3* db, int openFlags, const String8& path, const String8& label)
+ : db(db),
+ openFlags(openFlags),
+ path(path),
+ label(label),
+ canceled(false),
+ authorizer(NULL) {}
};
// Called each time a statement begins execution, when tracing is enabled.
@@ -108,10 +121,34 @@ static int sqliteProgressHandlerCallback(void* data) {
return connection->canceled;
}
+static int sqliteAuthorizerCallback(void* data, int action, const char* arg3, const char* arg4,
+ const char* arg5, const char* arg6) {
+ SQLiteConnection* connection = static_cast<SQLiteConnection*>(data);
+ if (connection->authorizer) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ ScopedLocalRef<jobject> authorizerObj(env, env->NewLocalRef(connection->authorizer));
+ ScopedLocalRef<jstring> arg3String(env, env->NewStringUTF(arg3));
+ ScopedLocalRef<jstring> arg4String(env, env->NewStringUTF(arg4));
+ ScopedLocalRef<jstring> arg5String(env, env->NewStringUTF(arg5));
+ ScopedLocalRef<jstring> arg6String(env, env->NewStringUTF(arg6));
+ int res = env->CallIntMethod(authorizerObj.get(), gAuthorizer.onAuthorize, action,
+ arg3String.get(), arg4String.get(), arg5String.get(),
+ arg6String.get());
+ if (env->ExceptionCheck()) {
+ ALOGE("Exception thrown by authorizer");
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return SQLITE_DENY;
+ } else {
+ return res;
+ }
+ } else {
+ return SQLITE_OK;
+ }
+}
static jlong nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFlags,
- jstring labelStr, jboolean enableTrace, jboolean enableProfile, jint lookasideSz,
- jint lookasideCnt) {
+ jstring labelStr, jint lookasideSz, jint lookasideCnt) {
int sqliteFlags;
if (openFlags & SQLiteConnection::CREATE_IF_NECESSARY) {
sqliteFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
@@ -172,13 +209,16 @@ static jlong nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFla
// Create wrapper object.
SQLiteConnection* connection = new SQLiteConnection(db, openFlags, path, label);
- // Enable tracing and profiling if requested.
- if (enableTrace) {
+ // Enable optional features if requested.
+ if (openFlags & SQLiteConnection::ENABLE_TRACE) {
sqlite3_trace(db, &sqliteTraceCallback, connection);
}
- if (enableProfile) {
+ if (openFlags & SQLiteConnection::ENABLE_PROFILE) {
sqlite3_profile(db, &sqliteProfileCallback, connection);
}
+ if (openFlags & SQLiteConnection::ENABLE_AUTHORIZER) {
+ sqlite3_set_authorizer(db, &sqliteAuthorizerCallback, connection);
+ }
ALOGV("Opened connection %p with label '%s'", db, label.string());
return reinterpret_cast<jlong>(connection);
@@ -188,6 +228,11 @@ static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) {
SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
if (connection) {
+ if (connection->authorizer) {
+ env->DeleteGlobalRef(connection->authorizer);
+ connection->authorizer = NULL;
+ }
+
ALOGV("Closing connection %p", connection->db);
int err = sqlite3_close(connection->db);
if (err != SQLITE_OK) {
@@ -201,6 +246,20 @@ static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) {
}
}
+static void nativeSetAuthorizer(JNIEnv* env, jclass clazz, jlong connectionPtr,
+ jobject authorizerObj) {
+ SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+
+ if (connection->authorizer) {
+ env->DeleteGlobalRef(connection->authorizer);
+ connection->authorizer = NULL;
+ }
+ if (authorizerObj) {
+ jobject authorizerObjGlobal = env->NewGlobalRef(authorizerObj);
+ connection->authorizer = authorizerObjGlobal;
+ }
+}
+
static void sqliteCustomScalarFunctionCallback(sqlite3_context *context,
int argc, sqlite3_value **argv) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -885,69 +944,53 @@ static jint nativeLastInsertRowId(JNIEnv* env, jclass, jlong connectionPtr) {
return sqlite3_last_insert_rowid(connection->db);
}
-static const JNINativeMethod sMethods[] =
-{
- /* name, signature, funcPtr */
- { "nativeOpen", "(Ljava/lang/String;ILjava/lang/String;ZZII)J",
- (void*)nativeOpen },
- { "nativeClose", "(J)V",
- (void*)nativeClose },
- { "nativeRegisterCustomScalarFunction", "(JLjava/lang/String;Ljava/util/function/UnaryOperator;)V",
- (void*)nativeRegisterCustomScalarFunction },
- { "nativeRegisterCustomAggregateFunction", "(JLjava/lang/String;Ljava/util/function/BinaryOperator;)V",
- (void*)nativeRegisterCustomAggregateFunction },
- { "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", "(JJZ)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 },
-
- { "nativeLastInsertRowId", "(J)I", (void*) nativeLastInsertRowId }
-};
+static const JNINativeMethod sMethods[] = {
+ /* name, signature, funcPtr */
+ {"nativeOpen", "(Ljava/lang/String;ILjava/lang/String;II)J", (void*)nativeOpen},
+ {"nativeClose", "(J)V", (void*)nativeClose},
+ {"nativeSetAuthorizer", "(JLandroid/database/sqlite/SQLiteAuthorizer;)V",
+ (void*)nativeSetAuthorizer},
+ {"nativeRegisterCustomScalarFunction",
+ "(JLjava/lang/String;Ljava/util/function/UnaryOperator;)V",
+ (void*)nativeRegisterCustomScalarFunction},
+ {"nativeRegisterCustomAggregateFunction",
+ "(JLjava/lang/String;Ljava/util/function/BinaryOperator;)V",
+ (void*)nativeRegisterCustomAggregateFunction},
+ {"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", "(JJZ)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},
+
+ {"nativeLastInsertRowId", "(J)I", (void*)nativeLastInsertRowId}};
int register_android_database_SQLiteConnection(JNIEnv *env)
{
+ jclass authorizerClazz = FindClassOrDie(env, "android/database/sqlite/SQLiteAuthorizer");
+ gAuthorizer.onAuthorize = GetMethodIDOrDie(env, authorizerClazz, "onAuthorize",
+ "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/"
+ "String;Ljava/lang/String;)I");
+
jclass unaryClazz = FindClassOrDie(env, "java/util/function/UnaryOperator");
gUnaryOperator.apply = GetMethodIDOrDie(env, unaryClazz,
"apply", "(Ljava/lang/Object;)Ljava/lang/Object;");