summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 6aac75087258..d0e32a7db1f0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -14205,6 +14205,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);
@@ -14261,6 +14301,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[]);
@@ -14271,6 +14312,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();
@@ -14282,6 +14324,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();
@@ -14308,6 +14351,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;
@@ -14324,6 +14368,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);
@@ -14372,7 +14417,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 1dfd4f9fa484..255b45e47bf6 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 8323f7c0eeb5..b757cd9fd6d6 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;");