transact-rs/sqlx · error

PgBindIter is only used once

Error message

PgBindIter is only used once

What it means

`PgBindIter` wraps an iterator for one-time binding as a SQL parameter, backed by an `Option`/`Take`-like inner field. Because encoding consumes the iterator, encoding the same value twice is invalid; the library panics via `.expect("PgBindIter is only used once")` in `encode_by_ref` when the inner value was already taken. This guards against silently re-using a half-consumed iterator, which would encode wrong data.

Source

Thrown at sqlx-postgres/src/bind_iter.rs:140

        if iter.next().is_some() {
            let iter_size = std::cmp::max(lower_size_hint, OVERFLOW);
            return Err(format!("encoded iterator is too large for Postgres: {iter_size}").into());
        }

        // set the length now that we know what it is.
        buf[len_start..(len_start + 4)].copy_from_slice(&count.to_be_bytes());

        Ok(IsNull::No)
    }
}

impl<'q, I> Encode<'q, Postgres> for PgBindIter<I>
where
    I: Iterator,
    <I as Iterator>::Item: Type<Postgres> + Encode<'q, Postgres>,
{
    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
        Self::encode_inner(self.0.take().expect("PgBindIter is only used once"), buf)
    }
    fn encode(self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError>
    where
        Self: Sized,
    {
        Self::encode_inner(
            self.0.into_inner().expect("PgBindIter is only used once"),
            buf,
        )
    }
}

View on GitHub (pinned to 03af8bcc57)

Solutions

  1. Create a fresh `PgBindIter` (from a new/cloned iterator or factory closure) for each query execution
  2. If the source collection is reusable, wrap a `Vec`/slice rather than a one-shot iterator, or clone the underlying collection per execution
  3. Avoid passing the same PgBindIter by reference into multiple `.bind()` calls
  4. If retrying queries, rebuild all bound arguments inside the retry loop

Example fix

// before
let iter = PgBindIter::new(rows.into_iter());
let q1 = query!().bind(&iter); // ok
let q2 = query!().bind(&iter); // panics: only used once
// after
let q1 = query!().bind(PgBindIter::new(rows.iter().cloned()));
let q2 = query!().bind(PgBindIter::new(rows.iter().cloned()));
Defensive patterns

Strategy: validation

Validate before calling

fn fresh_bind<'q, I>(src: &[I::Item]) -> PgBindIter<std::vec::IntoIter<I::Item>>
where I: Iterator, I::Item: Clone {
    PgBindIter::new(src.to_vec().into_iter())
}

Try / catch

std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| value.encode_by_ref(&mut buf)))
    .unwrap_or_else(|_| panic!("PgBindIter already consumed; create a new one"));

Prevention

When it happens

Trigger: Encoding a `PgBindIter` value by reference more than once — e.g. passing the same `&PgBindIter` to two query executions, or a driver code path that calls `encode_by_ref` twice on the same argument (such as in prepared-statement re-execution or retry logic that re-encodes arguments).

Common situations: Re-running a query in a loop with the same bound value; caching an encoded argument and re-encoding; sqlx internal paths that encode arguments once at prepare time and again at execute time.

Related errors


AI-assisted analysis of transact-rs/sqlx@03af8bcc57 (2026-09-03). Data as JSON: /api/errors/bad698bdb5b6c206. Report an issue: GitHub.