Loading core/java/android/database/sqlite/SQLiteConnection.java +55 −4 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package android.database.sqlite; import android.annotation.NonNull; import android.database.Cursor; import android.database.CursorWindow; import android.database.DatabaseUtils; Loading @@ -35,6 +37,7 @@ import dalvik.system.BlockGuard; import dalvik.system.CloseGuard; import java.io.File; import java.io.IOException; import java.lang.ref.Reference; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; Loading Loading @@ -167,6 +170,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private static native int nativeGetDbLookaside(long connectionPtr); private static native void nativeCancel(long connectionPtr); private static native void nativeResetCancel(long connectionPtr, boolean cancelable); private static native int nativeLastInsertRowId(long connectionPtr); private SQLiteConnection(SQLiteConnectionPool pool, SQLiteDatabaseConfiguration configuration, Loading Loading @@ -1052,7 +1056,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } } private PreparedStatement acquirePreparedStatement(String sql) { /** * Return a {@link #PreparedStatement}, possibly from the cache. */ PreparedStatement acquirePreparedStatement(String sql) { ++mPool.mTotalPrepareStatements; PreparedStatement statement = mPreparedStatementCache.get(sql); boolean skipCache = false; Loading Loading @@ -1088,7 +1095,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen return statement; } private void releasePreparedStatement(PreparedStatement statement) { /** * Release a {@link #PreparedStatement} that was originally supplied by this connection. */ void releasePreparedStatement(PreparedStatement statement) { statement.mInUse = false; if (statement.mInCache) { try { Loading Loading @@ -1116,6 +1126,24 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen recyclePreparedStatement(statement); } /** * 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) { final int cookie = mRecentOperations.beginOperation("prepare", sql, null); try { final PreparedStatement statement = acquirePreparedStatement(sql); throwIfStatementForbidden(statement); return statement; } catch (RuntimeException e) { mRecentOperations.failOperation(cookie, e); throw e; } finally { mRecentOperations.endOperation(cookie); } } private void attachCancellationSignal(CancellationSignal cancellationSignal) { if (cancellationSignal != null) { cancellationSignal.throwIfCanceled(); Loading Loading @@ -1200,7 +1228,14 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } } private void throwIfStatementForbidden(PreparedStatement statement) { /** * Verify that the statement is read-only, if the connection only allows read-only * operations. * @param statement The statement to check. * @throws SQLiteException if the statement could update the database inside a read-only * transaction. */ void throwIfStatementForbidden(PreparedStatement statement) { if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) { throw new SQLiteException("Cannot execute this statement because it " + "might modify the database but the connection is read-only."); Loading Loading @@ -1401,8 +1436,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen * In particular, closing the connection requires a guarantee of deterministic * resource disposal because all native statement objects must be freed before * the native database object can be closed. So no finalizers here. * * The class is package-visible so that {@link SQLiteRawStatement} can use it. */ private static final class PreparedStatement { static final class PreparedStatement { // Next item in pool. public PreparedStatement mPoolNext; Loading Loading @@ -1742,4 +1779,18 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } } /** * Return the ROWID of the last row to be inserted under this connection. Returns 0 if there * has never been an insert on this connection. * @return The ROWID of the last row to be inserted under this connection. * @hide */ long lastInsertRowId() { try { return nativeLastInsertRowId(mConnectionPtr); } finally { Reference.reachabilityFence(this); } } } core/java/android/database/sqlite/SQLiteDatabase.java +25 −0 Original line number Diff line number Diff line Loading @@ -2165,6 +2165,31 @@ 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. * @param sql The SQL string to be compiled into a prepared statement. * @return A raw statement holding the compiled sql. * @throws IllegalStateException if a transaction is not in progress. * @throws SQLiteException if the sql cannot be compiled. * @hide */ public SQLiteRawStatement createRawStatement(@NonNull String sql) { return new SQLiteRawStatement(this, sql); } /** * 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 * transaction. * @hide */ public long lastInsertRowId() { return getThreadSession().lastInsertRowId(); } /** * 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}. Loading core/java/android/database/sqlite/SQLiteRawStatement.java 0 → 100644 +741 −0 File added.Preview size limit exceeded, changes collapsed. Show changes core/java/android/database/sqlite/SQLiteSession.java +80 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package android.database.sqlite; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.database.CursorWindow; import android.database.DatabaseUtils; Loading @@ -23,6 +25,10 @@ import android.os.CancellationSignal; import android.os.OperationCanceledException; import android.os.ParcelFileDescriptor; import java.io.Closeable; import java.io.IOException; import java.util.ArrayDeque; /** * Provides a single client the ability to use a database. * Loading Loading @@ -170,6 +176,11 @@ public final class SQLiteSession { private Transaction mTransactionPool; private Transaction mTransactionStack; /** * A list of dependents that should be closed when the transaction completes. */ private final ArrayDeque<Closeable> mOpenDependents = new ArrayDeque<>(); /** * Transaction mode: Deferred. * <p> Loading Loading @@ -379,6 +390,9 @@ public final class SQLiteSession { throwIfTransactionMarkedSuccessful(); mTransactionStack.mMarkedSuccessful = true; // Close open dependents, since the next thing that is supposed to happen is the transaction // ends. closeOpenDependents(); } /** Loading Loading @@ -439,6 +453,11 @@ public final class SQLiteSession { mTransactionStack.mChildFailed = true; } } else { // Close all dependents before anything that might throw. The list should have been // cleared when the transaction was marked successful or unsuccessful. The call here // does nothing if the list is empty but is provided for insurance. closeOpenDependents(); try { if (successful) { mConnection.execute("COMMIT;", null, cancellationSignal); // might throw Loading Loading @@ -917,7 +936,67 @@ public final class SQLiteSession { } } private void throwIfNoTransaction() { /** * Acquire a prepared statement for external use. A current transaction is required and that * transaction may not have been marked successful. The dependent is registered its close() * method is called when the transaction is closed. */ @NonNull SQLiteConnection.PreparedStatement acquirePersistentStatement(@NonNull String query, @NonNull Closeable dependent) { throwIfNoTransaction(); throwIfTransactionMarkedSuccessful(); mOpenDependents.addFirst(dependent); try { return mConnection.acquirePersistentStatement(query); } catch (Throwable e) { mOpenDependents.remove(dependent); throw e; } } /** * Release a prepared statement. The dependent should be in list of open dependents. */ void releasePersistentStatement(@NonNull SQLiteConnection.PreparedStatement statement, @NonNull Closeable dependent) { mConnection.releasePreparedStatement(statement); mOpenDependents.remove(dependent); } /** * Close any open dependents. This may be called multiple times without harm. It never * throws. */ void closeOpenDependents() { while (mOpenDependents.size() > 0) { final Closeable dependent = mOpenDependents.pollFirst(); if (dependent != null) try { dependent.close(); } catch (IOException e) { // Swallow the exception. } } } /** * Return the row ID of the last row to be inserted on this connection. Note that the last row * might not have been inserted on this particular statement, but the return value is the last * row inserted on the same connection as that used by this statement. The function checks that * it is currently in a transaction before executing. Because of this check, it is not * necessary to acquire and release the connection: the connection has already been acquired. * @hide */ long lastInsertRowId() { throwIfNoTransaction(); return mConnection.lastInsertRowId(); } /** * Like it says on the tin: throw if there is no current transaction. */ void throwIfNoTransaction() { if (mTransactionStack == null) { throw new IllegalStateException("Cannot perform this operation because " + "there is no current transaction."); Loading core/java/android/database/sqlite/SQLiteStatement.java +1 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.os.ParcelFileDescriptor; * <p> * This class is not thread-safe. * </p> * Note that this class is unrelated to {@link SQLiteRawStatement}. */ public final class SQLiteStatement extends SQLiteProgram { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) Loading Loading
core/java/android/database/sqlite/SQLiteConnection.java +55 −4 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package android.database.sqlite; import android.annotation.NonNull; import android.database.Cursor; import android.database.CursorWindow; import android.database.DatabaseUtils; Loading @@ -35,6 +37,7 @@ import dalvik.system.BlockGuard; import dalvik.system.CloseGuard; import java.io.File; import java.io.IOException; import java.lang.ref.Reference; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; Loading Loading @@ -167,6 +170,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private static native int nativeGetDbLookaside(long connectionPtr); private static native void nativeCancel(long connectionPtr); private static native void nativeResetCancel(long connectionPtr, boolean cancelable); private static native int nativeLastInsertRowId(long connectionPtr); private SQLiteConnection(SQLiteConnectionPool pool, SQLiteDatabaseConfiguration configuration, Loading Loading @@ -1052,7 +1056,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } } private PreparedStatement acquirePreparedStatement(String sql) { /** * Return a {@link #PreparedStatement}, possibly from the cache. */ PreparedStatement acquirePreparedStatement(String sql) { ++mPool.mTotalPrepareStatements; PreparedStatement statement = mPreparedStatementCache.get(sql); boolean skipCache = false; Loading Loading @@ -1088,7 +1095,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen return statement; } private void releasePreparedStatement(PreparedStatement statement) { /** * Release a {@link #PreparedStatement} that was originally supplied by this connection. */ void releasePreparedStatement(PreparedStatement statement) { statement.mInUse = false; if (statement.mInCache) { try { Loading Loading @@ -1116,6 +1126,24 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen recyclePreparedStatement(statement); } /** * 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) { final int cookie = mRecentOperations.beginOperation("prepare", sql, null); try { final PreparedStatement statement = acquirePreparedStatement(sql); throwIfStatementForbidden(statement); return statement; } catch (RuntimeException e) { mRecentOperations.failOperation(cookie, e); throw e; } finally { mRecentOperations.endOperation(cookie); } } private void attachCancellationSignal(CancellationSignal cancellationSignal) { if (cancellationSignal != null) { cancellationSignal.throwIfCanceled(); Loading Loading @@ -1200,7 +1228,14 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } } private void throwIfStatementForbidden(PreparedStatement statement) { /** * Verify that the statement is read-only, if the connection only allows read-only * operations. * @param statement The statement to check. * @throws SQLiteException if the statement could update the database inside a read-only * transaction. */ void throwIfStatementForbidden(PreparedStatement statement) { if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) { throw new SQLiteException("Cannot execute this statement because it " + "might modify the database but the connection is read-only."); Loading Loading @@ -1401,8 +1436,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen * In particular, closing the connection requires a guarantee of deterministic * resource disposal because all native statement objects must be freed before * the native database object can be closed. So no finalizers here. * * The class is package-visible so that {@link SQLiteRawStatement} can use it. */ private static final class PreparedStatement { static final class PreparedStatement { // Next item in pool. public PreparedStatement mPoolNext; Loading Loading @@ -1742,4 +1779,18 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } } /** * Return the ROWID of the last row to be inserted under this connection. Returns 0 if there * has never been an insert on this connection. * @return The ROWID of the last row to be inserted under this connection. * @hide */ long lastInsertRowId() { try { return nativeLastInsertRowId(mConnectionPtr); } finally { Reference.reachabilityFence(this); } } }
core/java/android/database/sqlite/SQLiteDatabase.java +25 −0 Original line number Diff line number Diff line Loading @@ -2165,6 +2165,31 @@ 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. * @param sql The SQL string to be compiled into a prepared statement. * @return A raw statement holding the compiled sql. * @throws IllegalStateException if a transaction is not in progress. * @throws SQLiteException if the sql cannot be compiled. * @hide */ public SQLiteRawStatement createRawStatement(@NonNull String sql) { return new SQLiteRawStatement(this, sql); } /** * 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 * transaction. * @hide */ public long lastInsertRowId() { return getThreadSession().lastInsertRowId(); } /** * 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}. Loading
core/java/android/database/sqlite/SQLiteRawStatement.java 0 → 100644 +741 −0 File added.Preview size limit exceeded, changes collapsed. Show changes
core/java/android/database/sqlite/SQLiteSession.java +80 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package android.database.sqlite; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.database.CursorWindow; import android.database.DatabaseUtils; Loading @@ -23,6 +25,10 @@ import android.os.CancellationSignal; import android.os.OperationCanceledException; import android.os.ParcelFileDescriptor; import java.io.Closeable; import java.io.IOException; import java.util.ArrayDeque; /** * Provides a single client the ability to use a database. * Loading Loading @@ -170,6 +176,11 @@ public final class SQLiteSession { private Transaction mTransactionPool; private Transaction mTransactionStack; /** * A list of dependents that should be closed when the transaction completes. */ private final ArrayDeque<Closeable> mOpenDependents = new ArrayDeque<>(); /** * Transaction mode: Deferred. * <p> Loading Loading @@ -379,6 +390,9 @@ public final class SQLiteSession { throwIfTransactionMarkedSuccessful(); mTransactionStack.mMarkedSuccessful = true; // Close open dependents, since the next thing that is supposed to happen is the transaction // ends. closeOpenDependents(); } /** Loading Loading @@ -439,6 +453,11 @@ public final class SQLiteSession { mTransactionStack.mChildFailed = true; } } else { // Close all dependents before anything that might throw. The list should have been // cleared when the transaction was marked successful or unsuccessful. The call here // does nothing if the list is empty but is provided for insurance. closeOpenDependents(); try { if (successful) { mConnection.execute("COMMIT;", null, cancellationSignal); // might throw Loading Loading @@ -917,7 +936,67 @@ public final class SQLiteSession { } } private void throwIfNoTransaction() { /** * Acquire a prepared statement for external use. A current transaction is required and that * transaction may not have been marked successful. The dependent is registered its close() * method is called when the transaction is closed. */ @NonNull SQLiteConnection.PreparedStatement acquirePersistentStatement(@NonNull String query, @NonNull Closeable dependent) { throwIfNoTransaction(); throwIfTransactionMarkedSuccessful(); mOpenDependents.addFirst(dependent); try { return mConnection.acquirePersistentStatement(query); } catch (Throwable e) { mOpenDependents.remove(dependent); throw e; } } /** * Release a prepared statement. The dependent should be in list of open dependents. */ void releasePersistentStatement(@NonNull SQLiteConnection.PreparedStatement statement, @NonNull Closeable dependent) { mConnection.releasePreparedStatement(statement); mOpenDependents.remove(dependent); } /** * Close any open dependents. This may be called multiple times without harm. It never * throws. */ void closeOpenDependents() { while (mOpenDependents.size() > 0) { final Closeable dependent = mOpenDependents.pollFirst(); if (dependent != null) try { dependent.close(); } catch (IOException e) { // Swallow the exception. } } } /** * Return the row ID of the last row to be inserted on this connection. Note that the last row * might not have been inserted on this particular statement, but the return value is the last * row inserted on the same connection as that used by this statement. The function checks that * it is currently in a transaction before executing. Because of this check, it is not * necessary to acquire and release the connection: the connection has already been acquired. * @hide */ long lastInsertRowId() { throwIfNoTransaction(); return mConnection.lastInsertRowId(); } /** * Like it says on the tin: throw if there is no current transaction. */ void throwIfNoTransaction() { if (mTransactionStack == null) { throw new IllegalStateException("Cannot perform this operation because " + "there is no current transaction."); Loading
core/java/android/database/sqlite/SQLiteStatement.java +1 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.os.ParcelFileDescriptor; * <p> * This class is not thread-safe. * </p> * Note that this class is unrelated to {@link SQLiteRawStatement}. */ public final class SQLiteStatement extends SQLiteProgram { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) Loading