risingwavelabs/risingwave · error

bad result from postgres

Error message

bad result from postgres

What it means

The PostgreSQL-backed election client's try_campaign expects the campaign INSERT ... ON CONFLICT ... RETURNING query to return exactly one row. An empty result is rejected with 'bad result from postgres', meaning the upsert did not return the leader row as required.

Solutions

  1. Verify the connection targets the intended Postgres database and schema, and avoid transaction-mode poolers for the election connection.
  2. Check the election table exists with the expected primary key and the Postgres version supports the used upsert syntax.
  3. Look for prior postgres errors in meta logs (deadlock, serialization) and retry election.
  4. If reproducible on a known-good Postgres, file a bug with the campaign query and driver version.
Defensive patterns

Strategy: retry

Try / catch

match client.try_campaign(service, lock_id).await {
    Err(e) if e.to_string().contains("bad result from postgres") => {
        tokio::time::sleep(Duration::from_secs(1)).await;
        // re-check connection target before retrying
    }
    other => other?,
}

Prevention

When it happens

Trigger: try_campaign on PostgresElectionClient when the RETURNING clause yields no row — e.g. the ON CONFLICT branch matched but no row was returned due to driver/query behavior or store anomalies.

Common situations: Postgres version or driver incompatibility changing upsert semantics; concurrent truncation of the election table; connecting through a pooler (e.g. PgBouncer transaction mode) that splits the write/read across sessions or databases.

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/fbff2d6b6b1a91d4. Report an issue: GitHub.

Appendix: source

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

                    END
        RETURNING service, id, last_heartbeat;
        "#,
                    table = Self::election_table_name()
                ),
                vec![
                    Value::from(service_name),
                    Value::from(id),
                    // special handling for interval
                    Value::from(ttl.to_string()),
                ],
            ))
            .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 postgres"))?;

        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::Postgres,
                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)