Skip to content

fix(android): TiDatabaseProxy track lastInsertRowId/rowsAffected per-execute and null out closed cursor#14517

Open
mbender74 wants to merge 1 commit into
tidev:mainfrom
mbender74:android-database
Open

fix(android): TiDatabaseProxy track lastInsertRowId/rowsAffected per-execute and null out closed cursor#14517
mbender74 wants to merge 1 commit into
tidev:mainfrom
mbender74:android-database

Conversation

@mbender74

Copy link
Copy Markdown
Contributor
  • TiDatabaseProxy now tracks lastInsertRowId and rowsAffected per-execute so they reflect the most recent statement, not a stale value from an earlier execute.
  • TiResultSetProxy nulls out its closed cursor to prevent use-after-close crashes.

@mbender74 mbender74 changed the title fix(android): track lastInsertRowId/rowsAffected per-execute and null out closed cursor fix(android): TiDatabaseProxy track lastInsertRowId/rowsAffected per-execute and null out closed cursor Jul 16, 2026
@m1ga

m1ga commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Test code:

var win = Ti.UI.createWindow({});

win.open();

var db = Ti.Database.open('db_test');

// Clean slate
db.execute('DROP TABLE IF EXISTS people');
db.execute('CREATE TABLE people (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)');

Ti.API.info('=== Testing lastInsertRowId / rowsAffected ===');

// ------------------------------
// INSERT #1
// ------------------------------
db.execute('INSERT INTO people (name) VALUES (?)', 'Alice');
Ti.API.info('Insert #1');
Ti.API.info('lastInsertRowId = ' + db.lastInsertRowId);
Ti.API.info('rowsAffected    = ' + db.rowsAffected);

// Expect:
// lastInsertRowId = 1
// rowsAffected = 1

// ------------------------------
// INSERT #2
// ------------------------------
db.execute('INSERT INTO people (name) VALUES (?)', 'Bob');
Ti.API.info('Insert #2');
Ti.API.info('lastInsertRowId = ' + db.lastInsertRowId);
Ti.API.info('rowsAffected    = ' + db.rowsAffected);

// Expect:
// lastInsertRowId = 2
// rowsAffected = 1

// ------------------------------
// UPDATE
// ------------------------------
db.execute('UPDATE people SET name=? WHERE id=?', 'Bobby', 2);
Ti.API.info('Update');
Ti.API.info('lastInsertRowId = ' + db.lastInsertRowId);
Ti.API.info('rowsAffected    = ' + db.rowsAffected);

// Expect:
// rowsAffected = 1
// lastInsertRowId should still reflect the most recent statement's state
// (typically unchanged for UPDATE depending on platform behavior).

// ------------------------------
// UPDATE affecting no rows
// ------------------------------
db.execute('UPDATE people SET name=? WHERE id=?', 'Nobody', 999);

Ti.API.info('Update (no rows)');
Ti.API.info('rowsAffected = ' + db.rowsAffected);

// Expect:
// rowsAffected = 0
// (Previously this might incorrectly remain 1 if stale.)

// ------------------------------
// DELETE
// ------------------------------
db.execute('DELETE FROM people WHERE id=?', 1);

Ti.API.info('Delete');
Ti.API.info('rowsAffected = ' + db.rowsAffected);

// Expect:
// rowsAffected = 1

// ------------------------------
// SELECT
// ------------------------------
var rs = db.execute('SELECT * FROM people');

Ti.API.info('Reading ResultSet');

while (rs.isValidRow()) {
	Ti.API.info(
		rs.fieldByName('id') + ' : ' +
		rs.fieldByName('name')
	);
	rs.next();
}

Ti.API.info('Closing ResultSet...');
rs.close();

Ti.API.info('=== Testing use-after-close ===');

try {
	Ti.API.info('Calling isValidRow() after close...');
	Ti.API.info(rs.isValidRow());
} catch (e) {
	Ti.API.error('Expected exception: ' + e);
}

try {
	Ti.API.info('Calling fieldByName() after close...');
	Ti.API.info(rs.fieldByName('name'));
} catch (e) {
	Ti.API.error('Expected exception: ' + e);
}

try {
	Ti.API.info('Calling next() after close...');
	rs.next();
	Ti.API.info('next() returned normally');
} catch (e) {
	Ti.API.error('Expected exception: ' + e);
}

db.close();

Ti.API.info('Done.');

will crash with

[ERROR] TiResultSet: (main) [91,91] Exception getting value for column 1: Index 1 requested, with a size of 1
[ERROR] TiResultSet: android.database.CursorIndexOutOfBoundsException: Index 1 requested, with a size of 1
[ERROR] TiResultSet:    at android.database.AbstractCursor.checkPosition(AbstractCursor.java:535)
[ERROR] TiResultSet:    at android.database.AbstractWindowedCursor.checkPosition(AbstractWindowedCursor.java:139)
[ERROR] TiResultSet:    at android.database.AbstractWindowedCursor.getType(AbstractWindowedCursor.java:133)
[ERROR] TiResultSet:    at android.database.AbstractWindowedCursor.isFloat(AbstractWindowedCursor.java:128)
[ERROR] TiResultSet:    at ti.modules.titanium.database.TiResultSetProxy.internalGetField(TiResultSetProxy.java:106)
[ERROR] TiResultSet:    at ti.modules.titanium.database.TiResultSetProxy.internalGetFieldByName(TiResultSetProxy.java:202)
[ERROR] TiResultSet:    at ti.modules.titanium.database.TiResultSetProxy.internalGetFieldByName(TiResultSetProxy.java:192)
[ERROR] TiResultSet:    at ti.modules.titanium.database.TiResultSetProxy.fieldByName(TiResultSetProxy.java:162)

in 13.3.0 and works find with this PR 👍

@m1ga
m1ga self-requested a review July 16, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants