tursodatabase/turso · error

No index cursor found for table {table_ref_id}

Error message

No index cursor found for table {table_ref_id}

What it means

Raised in the SQL-to-bytecode builder when resolve_any_index_cursor_id_for_table() finds no index cursor allocated for a table reference. The method exists because a correlated subquery that references an outer query table cannot know whether the outer plan opened a table cursor, an index cursor, or both, so translation resolves whichever exists. The panic means translation demanded an index cursor the planner never allocated — an internal codegen/plan inconsistency, not bad user data.

Source

Thrown at core/vdbe/builder.rs:1868

        self.cursor_ref
            .iter()
            .position(|(k, _)| k.as_ref().is_some_and(|k| k.equals(key)))
    }

    pub fn resolve_cursor_id(&self, key: &CursorKey) -> CursorID {
        self.resolve_cursor_id_safe(key)
            .unwrap_or_else(|| panic!("Cursor not found: {key:?}"))
    }

    /// Resolve the first allocated index cursor for a given table reference.
    /// This method exists due to a limitation of our translation system where
    /// a subquery that references an outer query table cannot know whether a
    /// table cursor, index cursor, or both were opened for that table reference.
    /// Hence: currently we first try to resolve a table cursor, and if that fails,
    /// we resolve an index cursor via this method.
    pub fn resolve_any_index_cursor_id_for_table(&self, table_ref_id: TableInternalId) -> CursorID {
        self.resolve_any_index_cursor_id_for_table_safe(table_ref_id)
            .unwrap_or_else(|| panic!("No index cursor found for table {table_ref_id}"))
    }

    pub fn resolve_any_index_cursor_id_for_table_safe(
        &self,
        table_ref_id: TableInternalId,
    ) -> Option<CursorID> {
        self.cursor_ref.iter().position(|(k, _)| {
            k.as_ref()
                .is_some_and(|k| k.table_reference_id == table_ref_id && k.index.is_some())
        })
    }

    /// Resolve the [Index] that a given cursor is associated with.
    pub fn resolve_index_for_cursor_id(&self, cursor_id: CursorID) -> Arc<Index> {
        let cursor_ref = &self
            .cursor_ref
            .get(cursor_id)
            .unwrap_or_else(|| panic!("Cursor not found: {cursor_id}"))

View on GitHub (pinned to 492c4a71cd)

Solutions

  1. File a bug with the exact SQL — the resolver must not assume an index cursor exists; prepare-time reproduction makes it cheap to fix
  2. Rewrite the correlated subquery as a JOIN or grouped derived table so no outer-reference cursor resolution is needed
  3. Change the plan: drop/disable the relevant index or use +col / NOT INDEXED so the index-cursor code path is not taken
  4. Upgrade to a release where subquery cursor resolution is fixed

Example fix

-- before: correlated subquery resolves an outer index cursor
SELECT a, (SELECT SUM(b) FROM t2 WHERE t2.k = t1.indexed_k) FROM t1;

-- after: join form avoids outer-reference cursor resolution
SELECT t1.a, agg.s FROM t1
LEFT JOIN (SELECT k, SUM(b) AS s FROM t2 GROUP BY k) AS agg
  ON agg.k = t1.indexed_k;
Defensive patterns

Strategy: try-catch

Try / catch

// Treat statement preparation as fallible: a builder panic is a codegen bug,
// not something to unwind through your embedding layer.
let prepared = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    conn.prepare(sql)
}))
.map_err(|_| ApiError::QueryUnsupported { sql: sql.to_string() })??;

Prevention

When it happens

Trigger: Preparing a query with a correlated subquery (SELECT-list scalar subquery, EXISTS, IN) where the outer table reference has only a table cursor (full scan chosen, NOT INDEXED, or no matching index) while the subquery translation path unconditionally resolves an index cursor via resolve_any_index_cursor_id_for_table. Surfaces at statement preparation time, often after optimizer or subquery-decorrelation changes.

Common situations: Version upgrades that changed cursor allocation for correlated references; queries mixing indexed and non-indexed outer columns; development branches touching core/translate/ subquery flattening or co-routines; reproduces deterministically for a given SQL string.

Related errors


AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20). Data as JSON: /api/errors/aad9e9d4fecc9778. Report an issue: GitHub.