tursodatabase/turso · error · SQLException
SQLite only supports CONCUR_READ_ONLY cursors
Error message
SQLite only supports CONCUR_READ_ONLY cursors
What it means
The second guard in TursoConnection.checkCursor(): Turso result sets are read-only streams over native step() results, so updating rows through the ResultSet (CONCUR_UPDATABLE) is not implemented. Requesting anything other than ResultSet.CONCUR_READ_ONLY throws immediately at statement creation.
Source
Thrown at bindings/java/src/main/java/tech/turso/core/TursoConnection.java:163
* supported by the SQLite interface. Supported settings are:
*
* <ul>
* <li>type: {@link ResultSet#TYPE_FORWARD_ONLY}
* <li>concurrency: {@link ResultSet#CONCUR_READ_ONLY})
* <li>holdability: {@link ResultSet#CLOSE_CURSORS_AT_COMMIT}
* </ul>
*
* @param resultSetType the type setting.
* @param resultSetConcurrency the concurrency setting.
* @param resultSetHoldability the holdability setting.
*/
public void checkCursor(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
if (resultSetType != ResultSet.TYPE_FORWARD_ONLY) {
throw new SQLException("SQLite only supports TYPE_FORWARD_ONLY cursors");
}
if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY) {
throw new SQLException("SQLite only supports CONCUR_READ_ONLY cursors");
}
if (resultSetHoldability != ResultSet.CLOSE_CURSORS_AT_COMMIT) {
throw new SQLException("SQLite only supports closing cursors at commit");
}
}
/**
* Sets the auto-commit mode for this connection.
*
* <p>When auto-commit is enabled (the default), each SQL statement is committed automatically
* upon completion. When auto-commit is disabled, statements are grouped into transactions that
* must be explicitly committed or rolled back.
*
* <p>If this method is called to enable auto-commit while a transaction is active, the current
* transaction is committed first.
*
* @param autoCommit true to enable auto-commit mode; false to disable it
* @throws SQLException if a database access error occurs or the connection is closedView on GitHub (pinned to bad083fafb)
Solutions
- Pass ResultSet.CONCUR_READ_ONLY
- Perform writes with separate INSERT/UPDATE/DELETE statements or a second Statement on the same connection
- Replace rs.updateX()/updateRow() flows with an UPDATE ... WHERE key = ? prepared statement
- Use bind parameters on a PreparedStatement instead of editing rows in place
Example fix
// before
Statement st = conn.createStatement(
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
rs.updateString(1, "v"); rs.updateRow(); // unsupported flow
// after
Statement st = conn.createStatement(
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
try (PreparedStatement up = conn.prepareStatement(
"UPDATE t SET col = ? WHERE id = ?")) {
up.setString(1, "v"); up.setLong(2, id); up.executeUpdate();
} Defensive patterns
Strategy: validation
Validate before calling
int concurrency = ResultSet.CONCUR_READ_ONLY;
if (concurrency != ResultSet.CONCUR_READ_ONLY) {
concurrency = ResultSet.CONCUR_READ_ONLY; // writes must go through UPDATE statements
}
Statement st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, concurrency); Type guard
static boolean isSupportedConcurrency(int resultSetConcurrency) {
return resultSetConcurrency == ResultSet.CONCUR_READ_ONLY;
} Try / catch
try {
st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, reqConcurrency);
} catch (SQLException e) {
if (e.getMessage() != null && e.getMessage().contains("CONCUR_READ_ONLY")) {
st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
} else {
throw e;
}
} Prevention
- Model all writes as separate INSERT/UPDATE/DELETE statements — never as ResultSet edits
- Search code for updateRow()/updateXxx( calls when adopting the driver
- Treat the ResultSet as a read-only stream of stepped rows
When it happens
Trigger: Calling conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE) or prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE, ...). Any concurrency constant other than CONCUR_READ_ONLY (1007) is rejected.
Common situations: Code ported from drivers that support updatable cursors (SQL Server, Oracle); editable-table components; patterns like rs.updateString(...)/rs.updateRow() copied from other JDBC code; tools that default to CONCUR_UPDATABLE.
Related errors
- SQLite only supports TYPE_FORWARD_ONLY cursors
- SQLite only supports closing cursors at commit
- The result set is not open
- The resultSet is not open
- step() returned invalid result: " + errorMessage
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/515821b17d6c7244.
Report an issue: GitHub.