{"record":{"id":"515821b17d6c7244","repo":"tursodatabase/turso","slug":"sqlite-only-supports-concur-read-only-cursors","errorCode":null,"errorMessage":"SQLite only supports CONCUR_READ_ONLY cursors","messagePattern":"SQLite only supports CONCUR_READ_ONLY cursors","errorType":"exception","errorClass":"SQLException","httpStatus":null,"severity":"error","filePath":"bindings/java/src/main/java/tech/turso/core/TursoConnection.java","lineNumber":163,"sourceCode":"   * supported by the SQLite interface. Supported settings are:\n   *\n   * <ul>\n   *   <li>type: {@link ResultSet#TYPE_FORWARD_ONLY}\n   *   <li>concurrency: {@link ResultSet#CONCUR_READ_ONLY})\n   *   <li>holdability: {@link ResultSet#CLOSE_CURSORS_AT_COMMIT}\n   * </ul>\n   *\n   * @param resultSetType the type setting.\n   * @param resultSetConcurrency the concurrency setting.\n   * @param resultSetHoldability the holdability setting.\n   */\n  public void checkCursor(int resultSetType, int resultSetConcurrency, int resultSetHoldability)\n      throws SQLException {\n    if (resultSetType != ResultSet.TYPE_FORWARD_ONLY) {\n      throw new SQLException(\"SQLite only supports TYPE_FORWARD_ONLY cursors\");\n    }\n    if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY) {\n      throw new SQLException(\"SQLite only supports CONCUR_READ_ONLY cursors\");\n    }\n    if (resultSetHoldability != ResultSet.CLOSE_CURSORS_AT_COMMIT) {\n      throw new SQLException(\"SQLite only supports closing cursors at commit\");\n    }\n  }\n\n  /**\n   * Sets the auto-commit mode for this connection.\n   *\n   * <p>When auto-commit is enabled (the default), each SQL statement is committed automatically\n   * upon completion. When auto-commit is disabled, statements are grouped into transactions that\n   * must be explicitly committed or rolled back.\n   *\n   * <p>If this method is called to enable auto-commit while a transaction is active, the current\n   * transaction is committed first.\n   *\n   * @param autoCommit true to enable auto-commit mode; false to disable it\n   * @throws SQLException if a database access error occurs or the connection is closed","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/bindings/java/src/main/java/tech/turso/core/TursoConnection.java#L145-L181","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nStatement st = conn.createStatement(\n    ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);\nrs.updateString(1, \"v\"); rs.updateRow(); // unsupported flow\n\n// after\nStatement st = conn.createStatement(\n    ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);\ntry (PreparedStatement up = conn.prepareStatement(\n        \"UPDATE t SET col = ? WHERE id = ?\")) {\n  up.setString(1, \"v\"); up.setLong(2, id); up.executeUpdate();\n}","handlingStrategy":"validation","validationCode":"int concurrency = ResultSet.CONCUR_READ_ONLY;\nif (concurrency != ResultSet.CONCUR_READ_ONLY) {\n  concurrency = ResultSet.CONCUR_READ_ONLY; // writes must go through UPDATE statements\n}\nStatement st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, concurrency);","typeGuard":"static boolean isSupportedConcurrency(int resultSetConcurrency) {\n  return resultSetConcurrency == ResultSet.CONCUR_READ_ONLY;\n}","tryCatchPattern":"try {\n  st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, reqConcurrency);\n} catch (SQLException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"CONCUR_READ_ONLY\")) {\n    st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);\n  } else {\n    throw e;\n  }\n}","preventionTips":["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"],"tags":["java","jdbc","resultset","concurrency","bindings","sqlite"],"backgroundTag":"jdbc-updatable-resultset-unsupported","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}