neondatabase/neon · error

expected 1 query results, but got {}

Error message

expected 1 query results, but got {}

What it means

The availability checker runs a single INSERT ... ON CONFLICT DO UPDATE via simple_query and asserts that exactly one result set comes back. PostgreSQL/tokio-postgres returned zero or more than one result, so the invariant 'one statement, one result' was violated and the check fails. It is an internal sanity check on the driver/protocol behavior rather than a database fault.

Source

Thrown at compute_tools/src/checker.rs:34

    }

    // The connection object performs the actual communication with the database,
    // so spawn it off to run on its own.
    tokio::spawn(async move {
        if let Err(e) = connection.await {
            error!("connection error: {}", e);
        }
    });

    let query = "
    INSERT INTO public.health_check VALUES (1, pg_catalog.now())
        ON CONFLICT (id) DO UPDATE
         SET updated_at = pg_catalog.now();";

    match client.simple_query(query).await {
        Result::Ok(result) => {
            if result.len() != 1 {
                return Err(anyhow::anyhow!(
                    "expected 1 query results, but got {}",
                    result.len()
                ));
            }
        }
        Err(err) => {
            if let Some(state) = err.code() {
                if state == &tokio_postgres::error::SqlState::DISK_FULL {
                    warn!("Tenant disk is full");
                    return Ok(());
                }
            }
            return Err(err.into());
        }
    }

    Ok(())
}

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Log and inspect result.len() and the returned command tags to see which side of 1 you got
  2. Confirm the query string is still exactly one statement (no semicolons appended)
  3. Pin/check the tokio-postgres version in Cargo.lock against the one this invariant was written for
  4. If driver behavior legitimately changed, relax the check to len() >= 1 or check that at least one entry is a result row

Example fix

// before
if result.len() != 1 {
    return Err(anyhow!("expected 1 query results, but got {}", result.len()));
}
// after
if !result.iter().any(|r| matches!(r, SimpleQueryMessage::CommandComplete(_))) {
    return Err(anyhow!("health_check INSERT produced no completed command, got {} messages", result.len()));
}
Defensive patterns

Strategy: validation

Validate before calling

// Assert single-statement, single-result shape before trusting the check
static EXPECTED_STMTS: usize = 1;
if query.matches(';').count() > EXPECTED_STMTS { anyhow::bail!("checker query must stay a single statement"); }

Try / catch

match client.simple_query(query).await {
    Ok(results) if results.len() == 1 => Ok(()),
    Ok(results) => { warn!("unexpected {} results", results.len()); Ok(()) } // degrade to warning, do not fail availability
    Err(err) => { /* existing SqlState::DISK_FULL special case, else propagate */ }
}

Prevention

When it happens

Trigger: client.simple_query("INSERT INTO public.health_check VALUES (1, now()) ON CONFLICT (id) DO UPDATE SET updated_at = now()") returns a Vec whose len() != 1: an empty response (degraded connection, mid-protocol failure) or multiple responses (query string grew to several statements, driver behavior change across tokio-postgres versions).

Common situations: tokio-postgres upgraded to a version that batches/splits simple query results differently; someone edits the query into a multi-statement string; a proxy (pgbouncer in statement mode) splitting or rewriting the statement; flaky connection returning an empty result set.

Related errors


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/f6d9b81a041d9730. Report an issue: GitHub.