risingwavelabs/risingwave · error

bad result from sqlite

Error message

bad result from sqlite

What it means

The SQLite-backed meta election implementation runs an INSERT ... ON CONFLICT style campaign query and expects exactly one row back identifying the leader. When the query succeeds but returns no row, try_campaign rejects with 'bad result from sqlite'. This indicates the election query's contract (a guaranteed row) was violated by the store.

Solutions

  1. Verify the sqlite meta store file is intact and not shared by multiple meta processes; use a single meta node with sqlite.
  2. Check meta logs for preceding sqlite errors (lock, corruption) and restore the meta store from backup if corrupted.
  3. Restart the meta node to re-run election and repopulate the row.
  4. If reproducible, file a bug: the campaign query should always return a row, so this signals a driver or query defect.
Defensive patterns

Strategy: retry

Try / catch

// detect the empty election row and retry campaign with backoff
match client.try_campaign(service, lock_id).await {
    Err(e) if e.to_string().contains("bad result from sqlite") => {
        tokio::time::sleep(Duration::from_secs(1)).await;
        // verify store integrity before retrying
    }
    other => other?,
}

Prevention

When it happens

Trigger: try_campaign on SqliteElectionClient when the campaign upsert query returns an empty result set, i.e. the row inserted/updated cannot be read back.

Common situations: Corrupted or concurrently wiped sqlite meta store file; running multiple meta nodes against a sqlite store not intended for HA; sqlite driver/behavior changes returning no rows for the upsert.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/3fdc0b35dd9460bb. Report an issue: GitHub.

Appendix: source

Thrown at src/meta/src/rpc/election/sql.rs:191

                END,
                last_heartbeat = CASE
                                     WHEN DATETIME({table}.last_heartbeat, '+' || $3 || ' seconds') < CURRENT_TIMESTAMP THEN EXCLUDED.last_heartbeat
                                     WHEN {table}.id = EXCLUDED.id THEN EXCLUDED.last_heartbeat
                                     ELSE {table}.last_heartbeat
                    END
        RETURNING service, id, last_heartbeat;
        "#,
                    table = Self::election_table_name()
                ),
                vec![Value::from(service_name), Value::from(id), Value::from(ttl)],
            ))
            .await?;

        let row = query_result
            .map(|query_result| ElectionRow::from_query_result(&query_result, ""))
            .transpose()?;

        let row = row.ok_or_else(|| anyhow!("bad result from sqlite"))?;

        Ok(row)
    }

    async fn leader(&self, service_name: &str) -> MetaResult<Option<ElectionRow>> {
        let query_result = self
            .conn
            .query_one(Statement::from_sql_and_values(
                DatabaseBackend::Sqlite,
                format!(
                    r#"SELECT service, id, last_heartbeat FROM {table} WHERE service = $1;"#,
                    table = Self::election_table_name()
                ),
                vec![Value::from(service_name)],
            ))
            .await?;

        let row = query_result

View on GitHub (pinned to 6469eb736d)