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 closed

View on GitHub (pinned to bad083fafb)

Solutions

  1. Pass ResultSet.CONCUR_READ_ONLY
  2. Perform writes with separate INSERT/UPDATE/DELETE statements or a second Statement on the same connection
  3. Replace rs.updateX()/updateRow() flows with an UPDATE ... WHERE key = ? prepared statement
  4. 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

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


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/515821b17d6c7244. Report an issue: GitHub.