tursodatabase/turso · error · SQLException
turso does not support stored procedures
Error message
turso does not support stored procedures
What it means
Connection.prepareCall(...) - the entry point for CallableStatement used to invoke stored procedures - throws SQLException (not SQLFeatureNotSupportedException) at JDBC4Connection.java:225, because the SQLite engine turso wraps has no stored procedures. All four prepareCall overloads funnel into this one rejection. Note the exception type: catching only SQLFeatureNotSupportedException will miss it.
Solutions
- Replace the stored-procedure call with plain SQL: move the logic into the application, or into SQL statements executed via prepareStatement().
- For MyBatis, change statementType="CALLABLE" to statementType="PREPARED" with an equivalent SELECT/INSERT.
- Catch SQLException (the driver throws SQLException here, not SQLFeatureNotSupportedException) if a shared layer must tolerate it.
- Grep the migrated codebase for prepareCall and '{call' / '{?=' patterns and rewrite every hit.
Example fix
// before
try (CallableStatement cs = conn.prepareCall("{ call get_user(?, ?) }")) {
cs.setLong(1, id); cs.execute();
}
// after
try (PreparedStatement ps = conn.prepareStatement("select name from users where id = ?")) {
ps.setLong(1, id);
try (ResultSet rs = ps.executeQuery()) { ... }
} Defensive patterns
Strategy: try-catch
Validate before calling
// detect up front and avoid CallableStatement entirely // turso/SQLite has no stored procedures: route procedure-style calls to // plain SQL executed via prepareStatement
Try / catch
try {
stmt = conn.prepareCall(sql);
} catch (SQLException e) { // note: SQLException, NOT SQLFeatureNotSupportedException
if (e.getMessage().contains("stored procedures")) {
// rewrite as plain SQL via prepareStatement
} else { throw e; }
} Prevention
- Never call prepareCall on turso - the engine has no stored procedures.
- In MyBatis use statementType=\"PREPARED\", never CALLABLE.
- Grep migrations for '{call' and '{?=' escape-syntax patterns.
- Remember this throws SQLException, so SQLFeatureNotSupportedException-only handlers will miss it.
When it happens
Trigger: conn.prepareCall("{ call compute_totals(?) }"); MyBatis mappers with statementType="CALLABLE"; JPA/Hibernate native queries wrapped in {call ...} syntax; legacy DAO code written against Oracle/SQL Server/MySQL migrated to turso; ORM tooling that probes callable support.
Common situations: Porting an enterprise codebase that relied on stored procedures; Spring StoredProcedure or SimpleJdbcCall helpers; reporting tools that default to CallableStatement for anything resembling a procedure call; schema migrations that left procedure-call statements behind after the procedures themselves were dropped.
Related errors
- Exception while resetting statement
- Exception while retrieving number of changes
- Exception while retrieving parameter count
- createBlob not supported
- createClob not supported
AI-assisted analysis of tursodatabase/turso@244cde92a7 (2026-08-20).
Data as JSON: /api/errors/d62fe7b8ce4e058f.
Report an issue: GitHub.
Appendix: source
Thrown at bindings/java/src/main/java/tech/turso/jdbc4/JDBC4Connection.java:225
public CallableStatement prepareCall(String sql) throws SQLException {
return prepareCall(
sql,
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY,
ResultSet.CLOSE_CURSORS_AT_COMMIT);
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
return prepareCall(sql, resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT);
}
@Override
public CallableStatement prepareCall(
String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
throw new SQLException("turso does not support stored procedures");
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
return prepareStatement(
sql, resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT);
}
@Override
public PreparedStatement prepareStatement(
String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws SQLException {View on GitHub (pinned to 244cde92a7)