Repository: launchbadge/sqlx
File: sqlx-postgres/src/bind_iter.rs (Rust)

Code before:

        let mut count = 1_i32;
        const MAX: usize = i32::MAX as usize - 1;

        for value in (&mut iter).take(MAX) {
            buf.encode(value)?;
            count += 1;
        }

        const OVERFLOW: usize = i32::MAX as usize + 1;
        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
    // Clone is required for the encode_by_ref call since we can't iterate with a shared reference
    I: Iterator + Clone,
    <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.clone(), buf)
    }
    fn encode(self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError>
    where
        Self: Sized,
    {
        Self::encode_inner(self.0, buf)
    }
}


Review comment (suggestion):
The plan I had for this was to use `Cell<Option<I>>` since the `Clone` bound could theoretically be hard to satisfy for some iterators.

Using `Cell` would make it `!Sync` and only able to used once, but those seem like pretty reasonable tradeoffs. I don't expect anyone to need to share this across threads or or need to bind the same iterator more than once (they could just use the same placeholder number to refer to the existing binding or create a new iterator).

It seems like maybe we should split `Encode` into two traits: one that encodes by-value and one by-reference, then a blanket impl of the former over the latter. The by-reference trait would be the one that most types would implement, but the by-value trait would be the one that all the APIs actually accept.



Code after:

        let mut count = 1_i32;
        const MAX: usize = i32::MAX as usize - 1;

        for value in (&mut iter).take(MAX) {
            buf.encode(value)?;
            count += 1;
        }

        const OVERFLOW: usize = i32::MAX as usize + 1;
        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,
        )
    }
}