From 981926c6d89f4042c9b5cf8086c2d4331899c814 Mon Sep 17 00:00:00 2001 From: Lee Shombert Date: Thu, 6 Jul 2023 16:24:16 -0700 Subject: [PATCH] Add sqlite3 changes APIs Bug: 289226544 Add support for sqlite3_changes64() and sqlite3_total_changes64(). The APIs will be made public in a later commit. Unit tests have been added for the APIs. The unit tests are incorporated into the unit test for getLastInsertRowid(), and the entire test has been moved from SQLiteRawStatementTest to SQLiteDatabaseTest. As a code clean-up, the pipeline of "lastInsertRowId()" methods have been renamed to "getLastInsertRowId()", to match the API council's decision on the public API. Test: atest * FrameworksCoreTests:android.database.sqlite Change-Id: I1c167d6d6b8701fae0fd4129e1ae9029556c6d26 --- .../database/sqlite/SQLiteConnection.java | 29 ++++++- .../database/sqlite/SQLiteDatabase.java | 44 ++++++++++- .../database/sqlite/SQLiteSession.java | 24 +++++- .../jni/android_database_SQLiteConnection.cpp | 14 +++- .../database/sqlite/SQLiteDatabaseTest.java | 77 +++++++++++++++++++ .../sqlite/SQLiteRawStatementTest.java | 53 ------------- 6 files changed, 183 insertions(+), 58 deletions(-) diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index 382e4f8b04e97..b7489229a424f 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -176,6 +176,8 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private static native void nativeCancel(long connectionPtr); private static native void nativeResetCancel(long connectionPtr, boolean cancelable); private static native int nativeLastInsertRowId(long connectionPtr); + private static native long nativeChanges(long connectionPtr); + private static native long nativeTotalChanges(long connectionPtr); private SQLiteConnection(SQLiteConnectionPool pool, SQLiteDatabaseConfiguration configuration, @@ -1823,11 +1825,36 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen * @return The ROWID of the last row to be inserted under this connection. * @hide */ - long lastInsertRowId() { + long getLastInsertRowId() { try { return nativeLastInsertRowId(mConnectionPtr); } finally { Reference.reachabilityFence(this); } } + + /** + * Return the number of database changes on the current connection made by the last SQL + * statement + * @hide + */ + long getLastChangedRowsCount() { + try { + return nativeChanges(mConnectionPtr); + } finally { + Reference.reachabilityFence(this); + } + } + + /** + * Return the total number of database changes made on the current connection. + * @hide + */ + long getTotalChangedRowsCount() { + try { + return nativeTotalChanges(mConnectionPtr); + } finally { + Reference.reachabilityFence(this); + } + } } diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index eceec34ba0f4b..a3f8383472582 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -2197,7 +2197,49 @@ public final class SQLiteDatabase extends SQLiteClosable { * @throws IllegalStateException if there is no current transaction. */ public long getLastInsertRowId() { - return getThreadSession().lastInsertRowId(); + return getThreadSession().getLastInsertRowId(); + } + + /** + * Return the number of database rows that were inserted, updated, or deleted by the most recent + * SQL statement within the current transaction. + * + * @see sqlite3_changes64 + * + * @return The number of rows changed by the most recent sql statement + * @throws IllegalStateException if there is no current transaction. + * @hide + */ + public long getLastChangedRowsCount() { + return getThreadSession().getLastChangedRowsCount(); + } + + /** + * Return the total number of database rows that have been inserted, updated, or deleted on + * the current connection since it was created. Due to Android's internal management of + * SQLite connections, the value may, or may not, include changes made in earlier + * transactions. Best practice is to compare values returned within a single transaction. + * + *
+     *    database.beginTransaction();
+     *    try {
+     *        long initialValue = database.getTotalChangedRowsCount();
+     *        // Execute SQL statements
+     *        long changedRows = database.getTotalChangedRowsCount() - initialValue;
+     *        // changedRows counts the total number of rows updated in the transaction.
+     *    } finally {
+     *        database.endTransaction();
+     *    }
+     * 
+ * + * @see sqlite3_total_changes64 + * + * @return The number of rows changed on the current connection. + * @throws IllegalStateException if there is no current transaction. + * @hide + */ + public long getTotalChangedRowsCount() { + return getThreadSession().getTotalChangedRowsCount(); } /** diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java index 2379c849e5341..ef1a9cbe0adf1 100644 --- a/core/java/android/database/sqlite/SQLiteSession.java +++ b/core/java/android/database/sqlite/SQLiteSession.java @@ -988,9 +988,29 @@ public final class SQLiteSession { * necessary to acquire and release the connection: the connection has already been acquired. * @hide */ - long lastInsertRowId() { + long getLastInsertRowId() { throwIfNoTransaction(); - return mConnection.lastInsertRowId(); + return mConnection.getLastInsertRowId(); + } + + /** + * Return the number of database rows that were changed by the most recent SQL statement on + * this connection. + * @hide + */ + long getLastChangedRowsCount() { + throwIfNoTransaction(); + return mConnection.getLastChangedRowsCount(); + } + + /** + * Return the total number of database rows that were changed on the current connection, since + * it was created. + * @hide + */ + long getTotalChangedRowsCount() { + throwIfNoTransaction(); + return mConnection.getTotalChangedRowsCount(); } /** diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp index 7e827a837dcef..29520c24da75c 100644 --- a/core/jni/android_database_SQLiteConnection.cpp +++ b/core/jni/android_database_SQLiteConnection.cpp @@ -885,6 +885,16 @@ static jint nativeLastInsertRowId(JNIEnv* env, jclass, jlong connectionPtr) { return sqlite3_last_insert_rowid(connection->db); } +static jlong nativeChanges(JNIEnv* env, jclass, jlong connectionPtr) { + SQLiteConnection* connection = reinterpret_cast(connectionPtr); + return sqlite3_changes64(connection->db); +} + +static jlong nativeTotalChanges(JNIEnv* env, jclass, jlong connectionPtr) { + SQLiteConnection* connection = reinterpret_cast(connectionPtr); + return sqlite3_total_changes64(connection->db); +} + static const JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ @@ -943,7 +953,9 @@ static const JNINativeMethod sMethods[] = { "nativeResetCancel", "(JZ)V", (void*)nativeResetCancel }, - { "nativeLastInsertRowId", "(J)I", (void*) nativeLastInsertRowId } + { "nativeLastInsertRowId", "(J)I", (void*) nativeLastInsertRowId }, + { "nativeChanges", "(J)J", (void*) nativeChanges }, + { "nativeTotalChanges", "(J)J", (void*) nativeTotalChanges }, }; int register_android_database_SQLiteConnection(JNIEnv *env) diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java index ea5e7b6397314..fc72f611ccc8b 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java @@ -137,4 +137,81 @@ public class SQLiteDatabaseTest { fail("Timed out"); } } + + /** + * Create a database with one table with three columns. + */ + private void createComplexDatabase() { + mDatabase.beginTransaction(); + try { + mDatabase.execSQL("CREATE TABLE t1 (i int, d double, t text);"); + mDatabase.setTransactionSuccessful(); + } finally { + mDatabase.endTransaction(); + } + } + + /** + * A three-value insert for the complex database. + */ + private String createComplexInsert() { + return "INSERT INTO t1 (i, d, t) VALUES (?1, ?2, ?3)"; + } + + @Test + public void testAutomaticCounters() { + final int size = 10; + + createComplexDatabase(); + + // Put 10 lines in the database. + mDatabase.beginTransaction(); + try { + try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) { + for (int i = 0; i < size; i++) { + int vi = i * 3; + double vd = i * 2.5; + String vt = String.format("text%02dvalue", i); + s.bindInt(1, vi); + s.bindDouble(2, vd); + s.bindText(3, vt); + boolean r = s.step(); + // No row is returned by this query. + assertFalse(r); + s.reset(); + assertEquals(i + 1, mDatabase.getLastInsertRowId()); + assertEquals(1, mDatabase.getLastChangedRowsCount()); + assertEquals(i + 2, mDatabase.getTotalChangedRowsCount()); + } + } + mDatabase.setTransactionSuccessful(); + } finally { + mDatabase.endTransaction(); + } + + // Put a second 10 lines in the database. + mDatabase.beginTransaction(); + try { + try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) { + for (int i = 0; i < size; i++) { + int vi = i * 3; + double vd = i * 2.5; + String vt = String.format("text%02dvalue", i); + s.bindInt(1, vi); + s.bindDouble(2, vd); + s.bindText(3, vt); + boolean r = s.step(); + // No row is returned by this query. + assertFalse(r); + s.reset(); + assertEquals(size + i + 1, mDatabase.getLastInsertRowId()); + assertEquals(1, mDatabase.getLastChangedRowsCount()); + assertEquals(size + i + 2, mDatabase.getTotalChangedRowsCount()); + } + } + mDatabase.setTransactionSuccessful(); + } finally { + mDatabase.endTransaction(); + } + } } diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java index 59a1643c061f3..36bb8e5496c94 100644 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java +++ b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java @@ -845,59 +845,6 @@ public class SQLiteRawStatementTest { } } - @Test - public void testLastInsertRowId() { - final int size = 10; - - createComplexDatabase(); - - // Put 10 lines in the database. - mDatabase.beginTransaction(); - try { - try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) { - for (int i = 0; i < size; i++) { - int vi = i * 3; - double vd = i * 2.5; - String vt = String.format("text%02dvalue", i); - s.bindInt(1, vi); - s.bindDouble(2, vd); - s.bindText(3, vt); - boolean r = s.step(); - // No row is returned by this query. - assertFalse(r); - s.reset(); - assertEquals(i + 1, mDatabase.getLastInsertRowId()); - } - } - mDatabase.setTransactionSuccessful(); - } finally { - mDatabase.endTransaction(); - } - - // Put a second 10 lines in the database. - mDatabase.beginTransaction(); - try { - try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) { - for (int i = 0; i < size; i++) { - int vi = i * 3; - double vd = i * 2.5; - String vt = String.format("text%02dvalue", i); - s.bindInt(1, vi); - s.bindDouble(2, vd); - s.bindText(3, vt); - boolean r = s.step(); - // No row is returned by this query. - assertFalse(r); - s.reset(); - assertEquals(size + i + 1, mDatabase.getLastInsertRowId()); - } - } - mDatabase.setTransactionSuccessful(); - } finally { - mDatabase.endTransaction(); - } - } - @Test public void testUnicode() { // Create the t1 table and put some data in it.