From 7f01cfb889843c156260e0a0dbc86803479ed362 Mon Sep 17 00:00:00 2001 From: Tyler Hawkes Date: Sat, 21 Dec 2024 19:57:48 -0700 Subject: [PATCH 1/7] Add PgBindIter for encoding and use it as the implementation encoding &[T] --- sqlx-postgres/src/bind_iter.rs | 114 +++++++++++++++++++++++++++++++ sqlx-postgres/src/lib.rs | 2 + sqlx-postgres/src/types/array.rs | 32 +-------- 3 files changed, 119 insertions(+), 29 deletions(-) create mode 100644 sqlx-postgres/src/bind_iter.rs diff --git a/sqlx-postgres/src/bind_iter.rs b/sqlx-postgres/src/bind_iter.rs new file mode 100644 index 0000000000..2985cf1772 --- /dev/null +++ b/sqlx-postgres/src/bind_iter.rs @@ -0,0 +1,114 @@ +use sqlx_core::{ + database::Database, + encode::{Encode, IsNull}, + types::Type, +}; + +use crate::{type_info::PgType, PgArgumentBuffer, PgHasArrayType, PgTypeInfo, Postgres}; + +pub struct PgBindIter(I); + +impl PgBindIter { + pub fn new(inner: I) -> Self { + Self(inner) + } +} + +impl From for PgBindIter { + fn from(inner: I) -> Self { + Self::new(inner) + } +} + +impl Type for PgBindIter +where + T: Type + PgHasArrayType, + I: Iterator, +{ + fn type_info() -> ::TypeInfo { + T::array_type_info() + } + fn compatible(ty: &PgTypeInfo) -> bool { + T::array_compatible(ty) + } +} + +impl<'q, T, I> PgBindIter +where + I: Iterator, + T: Type + Encode<'q, Postgres>, +{ + fn encode_inner( + // need ownership to iterate + mut iter: I, + buf: &mut PgArgumentBuffer, + ) -> Result> { + let first = iter.next(); + let type_info = first + .as_ref() + .and_then(Encode::produces) + .unwrap_or_else(T::type_info); + + buf.extend(&1_i32.to_be_bytes()); // number of dimensions + buf.extend(&0_i32.to_be_bytes()); // flags + + match type_info.0 { + PgType::DeclareWithName(name) => buf.patch_type_by_name(&name), + PgType::DeclareArrayOf(array) => buf.patch_array_type(array), + + ty => { + buf.extend(&ty.oid().0.to_be_bytes()); + } + } + + let len_start = buf.len(); + buf.extend(0_i32.to_be_bytes()); // len (unknown so far) + buf.extend(1_i32.to_be_bytes()); // lower bound + + match first { + Some(first) => buf.encode(first)?, + None => return Ok(IsNull::No), + } + + let mut count = 1_i32; + const MAX: usize = i32::MAX as usize; + + for value in (&mut iter).take(MAX) { + buf.encode(value)?; + count += 1; + } + + const OVERFLOW: usize = MAX + 1; + if iter.next().is_some() { + return Err(format!("encoded iterator is too large for Postgres: {OVERFLOW}").into()); + } + + // set the length now that we know what it is. + buf[len_start..(len_start + 4)].copy_from_slice(count.to_be_bytes().as_slice()); + + Ok(IsNull::No) + } +} + +impl<'q, T, I> Encode<'q, Postgres> for PgBindIter +where + T: Type + Encode<'q, Postgres>, + // Clone is required for the encode_by_ref call since we can't iterate with a shared reference + I: Iterator + Clone, +{ + fn encode_by_ref( + &self, + buf: &mut PgArgumentBuffer, + ) -> Result> { + Self::encode_inner(self.0.clone(), buf) + } + fn encode( + self, + buf: &mut PgArgumentBuffer, + ) -> Result> + where + Self: Sized, + { + Self::encode_inner(self.0, buf) + } +} diff --git a/sqlx-postgres/src/lib.rs b/sqlx-postgres/src/lib.rs index c50f53067e..afb44ea6bf 100644 --- a/sqlx-postgres/src/lib.rs +++ b/sqlx-postgres/src/lib.rs @@ -7,6 +7,7 @@ use crate::executor::Executor; mod advisory_lock; mod arguments; +mod bind_iter; mod column; mod connection; mod copy; @@ -44,6 +45,7 @@ pub(crate) use sqlx_core::driver_prelude::*; pub use advisory_lock::{PgAdvisoryLock, PgAdvisoryLockGuard, PgAdvisoryLockKey}; pub use arguments::{PgArgumentBuffer, PgArguments}; +pub use bind_iter::PgBindIter; pub use column::PgColumn; pub use connection::PgConnection; pub use copy::{PgCopyIn, PgPoolCopyExt}; diff --git a/sqlx-postgres/src/types/array.rs b/sqlx-postgres/src/types/array.rs index 9b8be63412..b5f475c6b4 100644 --- a/sqlx-postgres/src/types/array.rs +++ b/sqlx-postgres/src/types/array.rs @@ -5,7 +5,6 @@ use std::borrow::Cow; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; -use crate::type_info::PgType; use crate::types::Oid; use crate::types::Type; use crate::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; @@ -156,39 +155,14 @@ where T: Encode<'q, Postgres> + Type, { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { - let type_info = self - .first() - .and_then(Encode::produces) - .unwrap_or_else(T::type_info); - - buf.extend(&1_i32.to_be_bytes()); // number of dimensions - buf.extend(&0_i32.to_be_bytes()); // flags - - // element type - match type_info.0 { - PgType::DeclareWithName(name) => buf.patch_type_by_name(&name), - PgType::DeclareArrayOf(array) => buf.patch_array_type(array), - - ty => { - buf.extend(&ty.oid().0.to_be_bytes()); - } - } - - let array_len = i32::try_from(self.len()).map_err(|_| { + // do the length check early to avoid doing unnecessary work + i32::try_from(self.len()).map_err(|_| { format!( "encoded array length is too large for Postgres: {}", self.len() ) })?; - - buf.extend(array_len.to_be_bytes()); // len - buf.extend(&1_i32.to_be_bytes()); // lower bound - - for element in self.iter() { - buf.encode(element)?; - } - - Ok(IsNull::No) + crate::bind_iter::PgBindIter::new(self.iter()).encode(buf) } } From ba7525f8c991d9badff0b581087fdaca1d775d63 Mon Sep 17 00:00:00 2001 From: Tyler Hawkes Date: Sun, 22 Dec 2024 22:10:54 -0700 Subject: [PATCH 2/7] Implement suggestions from review --- sqlx-postgres/src/bind_iter.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/sqlx-postgres/src/bind_iter.rs b/sqlx-postgres/src/bind_iter.rs index 2985cf1772..0ae1879d7b 100644 --- a/sqlx-postgres/src/bind_iter.rs +++ b/sqlx-postgres/src/bind_iter.rs @@ -1,6 +1,7 @@ use sqlx_core::{ database::Database, encode::{Encode, IsNull}, + error::BoxDynError, types::Type, }; @@ -42,7 +43,8 @@ where // need ownership to iterate mut iter: I, buf: &mut PgArgumentBuffer, - ) -> Result> { + ) -> Result { + let lower_size_hint = iter.size_hint().0; let first = iter.next(); let type_info = first .as_ref() @@ -71,20 +73,21 @@ where } let mut count = 1_i32; - const MAX: usize = i32::MAX as usize; + const MAX: usize = i32::MAX as usize - 1; for value in (&mut iter).take(MAX) { buf.encode(value)?; count += 1; } - const OVERFLOW: usize = MAX + 1; + const OVERFLOW: usize = i32::MAX as usize + 1; if iter.next().is_some() { - return Err(format!("encoded iterator is too large for Postgres: {OVERFLOW}").into()); + 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().as_slice()); + buf[len_start..(len_start + 4)].copy_from_slice(&count.to_be_bytes()); Ok(IsNull::No) } @@ -96,16 +99,10 @@ where // Clone is required for the encode_by_ref call since we can't iterate with a shared reference I: Iterator + Clone, { - fn encode_by_ref( - &self, - buf: &mut PgArgumentBuffer, - ) -> Result> { + fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { Self::encode_inner(self.0.clone(), buf) } - fn encode( - self, - buf: &mut PgArgumentBuffer, - ) -> Result> + fn encode(self, buf: &mut PgArgumentBuffer) -> Result where Self: Sized, { From afe6b198b7b1a2472a95d68eaaf24506518879f2 Mon Sep 17 00:00:00 2001 From: Tyler Hawkes Date: Sun, 22 Dec 2024 22:59:43 -0700 Subject: [PATCH 3/7] Add docs to PgBindIter and test to ensure it works for owned and borrowed types --- sqlx-postgres/src/bind_iter.rs | 36 +++++++++++++++++++++ tests/postgres/postgres.rs | 58 ++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/sqlx-postgres/src/bind_iter.rs b/sqlx-postgres/src/bind_iter.rs index 0ae1879d7b..a3942bc973 100644 --- a/sqlx-postgres/src/bind_iter.rs +++ b/sqlx-postgres/src/bind_iter.rs @@ -7,6 +7,42 @@ use sqlx_core::{ use crate::{type_info::PgType, PgArgumentBuffer, PgHasArrayType, PgTypeInfo, Postgres}; +/// A wrapper enabling iterators to encode arrays in Postgres. +/// +/// Because of the blanket impl of `PgHasArrayType` for all references +/// we can borrow instead of needing to clone or copy in the iterators +/// and it still works +/// +/// Previously, 3 separate arrays would be needed in this example which +/// requires iterating 3 times to collect items into the array and then +/// iterating over them again to encode. +/// +/// This now requires only iterating over the array once for each field +/// while using less memory giving both speed and memory usage improvements. +/// +/// ```rust,ignore +/// # use sqlx::types::chrono::{DateTime, Utc} +/// # fn people() -> &'static [Person] { +/// # &[] +/// # } +/// #[derive(sqlx::FromRow)] +/// struct Person { +/// id: i64, +/// name: String, +/// birthdate: DateTime, +/// } +/// +/// let people: &[Person] = people(); +/// +/// sqlx::query( +/// "insert into person(id, name, birthdate) select * from unnest($1, $2, $3)" +/// ) +/// .bind(PgBindIter::from(people.iter().map(|p|p.id))) +/// .bind(PgBindIter::from(people.iter().map(|p|&p.name))) +/// .bind(PgBindIter::from(people.iter().map(|p|&p.birthdate))) +/// .execute(pool) +/// .await?; +/// ``` pub struct PgBindIter(I); impl PgBindIter { diff --git a/tests/postgres/postgres.rs b/tests/postgres/postgres.rs index 87a18db510..dc5d3ec0e4 100644 --- a/tests/postgres/postgres.rs +++ b/tests/postgres/postgres.rs @@ -2042,3 +2042,61 @@ async fn test_issue_3052() { "expected encode error, got {too_large_error:?}", ); } + +#[sqlx_macros::test] +async fn test_bind_iter() -> anyhow::Result<()> { + use sqlx::postgres::PgBindIter; + use sqlx::types::chrono::{DateTime, Utc}; + + let mut conn = new::().await?; + + #[derive(sqlx::FromRow, PartialEq, Debug)] + struct Person { + id: i64, + name: String, + birthdate: DateTime, + } + + let people: Vec = vec![ + Person { + id: 1, + name: "Alice".into(), + birthdate: "1984-01-01T00:00:00Z".parse().unwrap(), + }, + Person { + id: 2, + name: "Bob".into(), + birthdate: "2000-01-01T00:00:00Z".parse().unwrap(), + }, + ]; + + sqlx::query( + r#" +create temporary table person( + id int8 primary key, + name text not null, + birthdate timestamptz not null +)"#, + ) + .execute(&mut conn) + .await?; + + let rows_affected = + sqlx::query("insert into person(id, name, birthdate) select * from unnest($1, $2, $3)") + // owned value + .bind(PgBindIter::from(people.iter().map(|p| p.id))) + // borrowed value + .bind(PgBindIter::from(people.iter().map(|p| &p.name))) + .bind(PgBindIter::from(people.iter().map(|p| &p.birthdate))) + .execute(&mut conn) + .await? + .rows_affected(); + assert_eq!(rows_affected, 2); + + let p_query = sqlx::query_as::<_, Person>("select * from person order by id") + .fetch_all(&mut conn) + .await?; + + assert_eq!(people, p_query); + Ok(()) +} From 4028058f8cde1395f02675e92c314511e2aa046c Mon Sep 17 00:00:00 2001 From: Tyler Hawkes Date: Tue, 24 Dec 2024 00:35:32 -0700 Subject: [PATCH 4/7] Use extension trait for iterators to allow code to flow better. Make struct private. Don't reference unneeded generic T. Make doc tests compile. --- sqlx-postgres/src/bind_iter.rs | 70 ++++++++++++++++---------------- sqlx-postgres/src/lib.rs | 2 +- sqlx-postgres/src/types/array.rs | 2 +- tests/postgres/postgres.rs | 8 ++-- 4 files changed, 42 insertions(+), 40 deletions(-) diff --git a/sqlx-postgres/src/bind_iter.rs b/sqlx-postgres/src/bind_iter.rs index a3942bc973..d2682c25cd 100644 --- a/sqlx-postgres/src/bind_iter.rs +++ b/sqlx-postgres/src/bind_iter.rs @@ -7,7 +7,10 @@ use sqlx_core::{ use crate::{type_info::PgType, PgArgumentBuffer, PgHasArrayType, PgTypeInfo, Postgres}; -/// A wrapper enabling iterators to encode arrays in Postgres. +// not exported but pub because it is used in the extension trait +pub struct PgBindIter(I); + +/// Iterator extension trait enabling iterators to encode arrays in Postgres. /// /// Because of the blanket impl of `PgHasArrayType` for all references /// we can borrow instead of needing to clone or copy in the iterators @@ -18,13 +21,18 @@ use crate::{type_info::PgType, PgArgumentBuffer, PgHasArrayType, PgTypeInfo, Pos /// iterating over them again to encode. /// /// This now requires only iterating over the array once for each field -/// while using less memory giving both speed and memory usage improvements. +/// while using less memory giving both speed and memory usage improvements +/// along with allowing much more flexibility in the underlying collection. /// -/// ```rust,ignore +/// ```rust,no_run +/// # async fn test_bind_iter() { /// # use sqlx::types::chrono::{DateTime, Utc} /// # fn people() -> &'static [Person] { /// # &[] /// # } +/// # let mut conn = sqlx::Postgres::Connection::connect("dummyurl").await; +/// use sqlx::postgres::PgBindIterExt; +/// /// #[derive(sqlx::FromRow)] /// struct Person { /// id: i64, @@ -32,48 +40,42 @@ use crate::{type_info::PgType, PgArgumentBuffer, PgHasArrayType, PgTypeInfo, Pos /// birthdate: DateTime, /// } /// -/// let people: &[Person] = people(); -/// -/// sqlx::query( -/// "insert into person(id, name, birthdate) select * from unnest($1, $2, $3)" -/// ) -/// .bind(PgBindIter::from(people.iter().map(|p|p.id))) -/// .bind(PgBindIter::from(people.iter().map(|p|&p.name))) -/// .bind(PgBindIter::from(people.iter().map(|p|&p.birthdate))) -/// .execute(pool) -/// .await?; +/// # let people: &[Person] = people(); +/// sqlx::query("insert into person(id, name, birthdate) select * from unnest($1, $2, $3)") +/// .bind(people.iter().map(|p| p.id).bind_iter()) +/// .bind(people.iter().map(|p| &p.name).bind_iter()) +/// .bind(people.iter().map(|p| &p.birthdate).bind_iter()) +/// .execute(&mut conn) +/// .await?; +/// # } /// ``` -pub struct PgBindIter(I); - -impl PgBindIter { - pub fn new(inner: I) -> Self { - Self(inner) - } +pub trait PgBindIterExt: Iterator + Sized { + fn bind_iter(self) -> PgBindIter; } -impl From for PgBindIter { - fn from(inner: I) -> Self { - Self::new(inner) +impl PgBindIterExt for I { + fn bind_iter(self) -> PgBindIter { + PgBindIter(self) } } -impl Type for PgBindIter +impl Type for PgBindIter where - T: Type + PgHasArrayType, - I: Iterator, + I: Iterator, + ::Item: Type + PgHasArrayType, { fn type_info() -> ::TypeInfo { - T::array_type_info() + ::Item::array_type_info() } fn compatible(ty: &PgTypeInfo) -> bool { - T::array_compatible(ty) + ::Item::array_compatible(ty) } } -impl<'q, T, I> PgBindIter +impl<'q, I> PgBindIter where - I: Iterator, - T: Type + Encode<'q, Postgres>, + I: Iterator, + ::Item: Type + Encode<'q, Postgres>, { fn encode_inner( // need ownership to iterate @@ -85,7 +87,7 @@ where let type_info = first .as_ref() .and_then(Encode::produces) - .unwrap_or_else(T::type_info); + .unwrap_or_else(::Item::type_info); buf.extend(&1_i32.to_be_bytes()); // number of dimensions buf.extend(&0_i32.to_be_bytes()); // flags @@ -129,11 +131,11 @@ where } } -impl<'q, T, I> Encode<'q, Postgres> for PgBindIter +impl<'q, I> Encode<'q, Postgres> for PgBindIter where - T: Type + Encode<'q, Postgres>, // Clone is required for the encode_by_ref call since we can't iterate with a shared reference - I: Iterator + Clone, + I: Iterator + Clone, + ::Item: Type + Encode<'q, Postgres>, { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { Self::encode_inner(self.0.clone(), buf) diff --git a/sqlx-postgres/src/lib.rs b/sqlx-postgres/src/lib.rs index afb44ea6bf..76b0eb3206 100644 --- a/sqlx-postgres/src/lib.rs +++ b/sqlx-postgres/src/lib.rs @@ -45,7 +45,7 @@ pub(crate) use sqlx_core::driver_prelude::*; pub use advisory_lock::{PgAdvisoryLock, PgAdvisoryLockGuard, PgAdvisoryLockKey}; pub use arguments::{PgArgumentBuffer, PgArguments}; -pub use bind_iter::PgBindIter; +pub use bind_iter::PgBindIterExt; pub use column::PgColumn; pub use connection::PgConnection; pub use copy::{PgCopyIn, PgPoolCopyExt}; diff --git a/sqlx-postgres/src/types/array.rs b/sqlx-postgres/src/types/array.rs index b5f475c6b4..372c2891a8 100644 --- a/sqlx-postgres/src/types/array.rs +++ b/sqlx-postgres/src/types/array.rs @@ -162,7 +162,7 @@ where self.len() ) })?; - crate::bind_iter::PgBindIter::new(self.iter()).encode(buf) + crate::PgBindIterExt::bind_iter(self.iter()).encode(buf) } } diff --git a/tests/postgres/postgres.rs b/tests/postgres/postgres.rs index dc5d3ec0e4..e1c2f086d5 100644 --- a/tests/postgres/postgres.rs +++ b/tests/postgres/postgres.rs @@ -2045,7 +2045,7 @@ async fn test_issue_3052() { #[sqlx_macros::test] async fn test_bind_iter() -> anyhow::Result<()> { - use sqlx::postgres::PgBindIter; + use sqlx::postgres::PgBindIterExt; use sqlx::types::chrono::{DateTime, Utc}; let mut conn = new::().await?; @@ -2084,10 +2084,10 @@ create temporary table person( let rows_affected = sqlx::query("insert into person(id, name, birthdate) select * from unnest($1, $2, $3)") // owned value - .bind(PgBindIter::from(people.iter().map(|p| p.id))) + .bind(people.iter().map(|p| p.id).bind_iter()) // borrowed value - .bind(PgBindIter::from(people.iter().map(|p| &p.name))) - .bind(PgBindIter::from(people.iter().map(|p| &p.birthdate))) + .bind(people.iter().map(|p| &p.name).bind_iter()) + .bind(people.iter().map(|p| &p.birthdate).bind_iter()) .execute(&mut conn) .await? .rows_affected(); From 7768e24b6c362c8d4e85b0b334c8913057de0e4a Mon Sep 17 00:00:00 2001 From: Tyler Hawkes Date: Fri, 27 Dec 2024 02:45:57 -0700 Subject: [PATCH 5/7] Fix doc function --- sqlx-postgres/src/bind_iter.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sqlx-postgres/src/bind_iter.rs b/sqlx-postgres/src/bind_iter.rs index d2682c25cd..4311fc452a 100644 --- a/sqlx-postgres/src/bind_iter.rs +++ b/sqlx-postgres/src/bind_iter.rs @@ -25,7 +25,7 @@ pub struct PgBindIter(I); /// along with allowing much more flexibility in the underlying collection. /// /// ```rust,no_run -/// # async fn test_bind_iter() { +/// # async fn test_bind_iter() -> Result<(), sqlx::error::BoxDynError> { /// # use sqlx::types::chrono::{DateTime, Utc} /// # fn people() -> &'static [Person] { /// # &[] @@ -47,6 +47,8 @@ pub struct PgBindIter(I); /// .bind(people.iter().map(|p| &p.birthdate).bind_iter()) /// .execute(&mut conn) /// .await?; +/// +/// # Ok(()) /// # } /// ``` pub trait PgBindIterExt: Iterator + Sized { From 9f8e03a57e77cbed96931a7e9c39451828d9a895 Mon Sep 17 00:00:00 2001 From: Tyler Hawkes Date: Fri, 27 Dec 2024 16:45:27 -0700 Subject: [PATCH 6/7] Fix doc test to actually compile --- sqlx-postgres/src/bind_iter.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sqlx-postgres/src/bind_iter.rs b/sqlx-postgres/src/bind_iter.rs index 4311fc452a..3831d1f245 100644 --- a/sqlx-postgres/src/bind_iter.rs +++ b/sqlx-postgres/src/bind_iter.rs @@ -26,11 +26,12 @@ pub struct PgBindIter(I); /// /// ```rust,no_run /// # async fn test_bind_iter() -> Result<(), sqlx::error::BoxDynError> { -/// # use sqlx::types::chrono::{DateTime, Utc} +/// # use sqlx::types::chrono::{DateTime, Utc}; +/// # use sqlx::Connection; /// # fn people() -> &'static [Person] { /// # &[] /// # } -/// # let mut conn = sqlx::Postgres::Connection::connect("dummyurl").await; +/// # let mut conn = ::Connection::connect("dummyurl").await?; /// use sqlx::postgres::PgBindIterExt; /// /// #[derive(sqlx::FromRow)] From 6ae35059d019f802624bac66c0194feb188fafbc Mon Sep 17 00:00:00 2001 From: Tyler Hawkes Date: Sat, 4 Jan 2025 12:48:59 -0700 Subject: [PATCH 7/7] Use Cell> instead of Clone bound --- sqlx-postgres/src/bind_iter.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/sqlx-postgres/src/bind_iter.rs b/sqlx-postgres/src/bind_iter.rs index 3831d1f245..0f44f19e3d 100644 --- a/sqlx-postgres/src/bind_iter.rs +++ b/sqlx-postgres/src/bind_iter.rs @@ -1,3 +1,5 @@ +use crate::{type_info::PgType, PgArgumentBuffer, PgHasArrayType, PgTypeInfo, Postgres}; +use core::cell::Cell; use sqlx_core::{ database::Database, encode::{Encode, IsNull}, @@ -5,10 +7,8 @@ use sqlx_core::{ types::Type, }; -use crate::{type_info::PgType, PgArgumentBuffer, PgHasArrayType, PgTypeInfo, Postgres}; - // not exported but pub because it is used in the extension trait -pub struct PgBindIter(I); +pub struct PgBindIter(Cell>); /// Iterator extension trait enabling iterators to encode arrays in Postgres. /// @@ -58,7 +58,7 @@ pub trait PgBindIterExt: Iterator + Sized { impl PgBindIterExt for I { fn bind_iter(self) -> PgBindIter { - PgBindIter(self) + PgBindIter(Cell::new(Some(self))) } } @@ -136,17 +136,19 @@ where impl<'q, I> Encode<'q, Postgres> for PgBindIter where - // Clone is required for the encode_by_ref call since we can't iterate with a shared reference - I: Iterator + Clone, + I: Iterator, ::Item: Type + Encode<'q, Postgres>, { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { - Self::encode_inner(self.0.clone(), buf) + Self::encode_inner(self.0.take().expect("PgBindIter is only used once"), buf) } fn encode(self, buf: &mut PgArgumentBuffer) -> Result where Self: Sized, { - Self::encode_inner(self.0, buf) + Self::encode_inner( + self.0.into_inner().expect("PgBindIter is only used once"), + buf, + ) } }