{"record":{"id":"c451af4dd2b06f5b","repo":"tursodatabase/turso","slug":"sqlite-only-supports-type-forward-only-cursors","errorCode":null,"errorMessage":"SQLite only supports TYPE_FORWARD_ONLY cursors","messagePattern":"SQLite only supports TYPE_FORWARD_ONLY cursors","errorType":"exception","errorClass":"SQLException","httpStatus":null,"severity":"error","filePath":"bindings/java/src/main/java/tech/turso/core/TursoConnection.java","lineNumber":160,"sourceCode":"  // TODO: check whether this is still valid for turso\n  /**\n   * Checks whether the type, concurrency, and holdability settings for a {@link ResultSet} are\n   * 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.","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/bindings/java/src/main/java/tech/turso/core/TursoConnection.java#L142-L178","documentation":"TursoConnection.checkCursor() validates the ResultSet hints passed to Connection.createStatement(type, concurrency, holdability) or prepareStatement(...) overloads. The native engine streams rows one at a time via step(), so a cursor can only move forward and any resultSetType other than ResultSet.TYPE_FORWARD_ONLY is rejected up front.","triggerScenarios":"Calling conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ...) or conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_SENSITIVE, ...) with any type constant other than ResultSet.TYPE_FORWARD_ONLY (1003). The no-arg createStatement()/prepareStatement() never trigger it.","commonSituations":"Porting JDBC code from MySQL/PostgreSQL drivers that accept scrollable cursors; UI grid or pagination components that call rs.absolute()/rs.previous(); libraries and copy-pasted boilerplate that hardcode TYPE_SCROLL_INSENSITIVE; migration from sqlite-jdbc tests that exercise scroll types.","solutions":["Pass ResultSet.TYPE_FORWARD_ONLY as the resultSetType argument","Remove scrolling calls (previous(), absolute(), relative(), afterLast()) and iterate with next() only","For pagination, re-execute the query with LIMIT/OFFSET instead of scrolling a cursor","If rows must be re-read, buffer them into a List while iterating forward once"],"exampleFix":"// before\nStatement st = conn.createStatement(\n    ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);\n\n// after\nStatement st = conn.createStatement(\n    ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);","handlingStrategy":"validation","validationCode":"int type = ResultSet.TYPE_FORWARD_ONLY;\nint concurrency = ResultSet.CONCUR_READ_ONLY;\nint holdability = ResultSet.CLOSE_CURSORS_AT_COMMIT;\nif (type != ResultSet.TYPE_FORWARD_ONLY) {\n  type = ResultSet.TYPE_FORWARD_ONLY; // downgrade before the driver rejects it\n}\nStatement st = conn.createStatement(type, concurrency, holdability);","typeGuard":"static boolean isSupportedCursorType(int resultSetType) {\n  return resultSetType == ResultSet.TYPE_FORWARD_ONLY;\n}","tryCatchPattern":"try {\n  st = conn.createStatement(reqType, reqConcurrency, reqHoldability);\n} catch (SQLException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"TYPE_FORWARD_ONLY\")) {\n    st = conn.createStatement(); // driver defaults are always supported\n  } else {\n    throw e;\n  }\n}","preventionTips":["Prefer the no-arg createStatement()/prepareStatement(sql) — the driver applies supported defaults and checkCursor never runs","Audit ported JDBC code for TYPE_SCROLL_* constants before switching drivers","Do not use scroll APIs (absolute, previous, relative); design iteration as forward-only"],"tags":["java","jdbc","resultset","cursor","bindings","sqlite"],"backgroundTag":"jdbc-unsupported-resultset-type","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}