Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3a94cb5d authored by Lee Shombert's avatar Lee Shombert
Browse files

New SQLite APIs

Bug: 279043253

Introducing new and improved SQLite APIs that more closely conform to
the native methods.  The new SQLiteStmt class is a thin wrapper over
the SQLite sqlite3_stmt object.

All new APIs are currently marked @hide.  The APIs will be exposed to
the public in a separate review.  Unit tests have been added; CTS
tests will be added when the APIs are made public.

Test: atest
 * SQLiteRawStatementTest
 * SQLiteDatabaseTest

Change-Id: I4f4b09a77d75adfb6d8eb78e54989f5dc4701472
parent 14b8e32f
Loading
Loading
Loading
Loading
+55 −4
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.database.sqlite;

import android.annotation.NonNull;

import android.database.Cursor;
import android.database.CursorWindow;
import android.database.DatabaseUtils;
@@ -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;
@@ -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,
@@ -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;
@@ -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 {
@@ -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();
@@ -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.");
@@ -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;

@@ -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);
        }
    }
}
+25 −0
Original line number Diff line number Diff line
@@ -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}.
+741 −0

File added.

Preview size limit exceeded, changes collapsed.

+80 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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.
 *
@@ -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>
@@ -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();
    }

    /**
@@ -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
@@ -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.");
+1 −0
Original line number Diff line number Diff line
@@ -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