From 78fb56faf8662ad80fedeba94461efd8419162bb Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Mon, 17 Mar 2025 12:01:36 +0100 Subject: [PATCH] Make it possible to use copy_from/copy_to with dynamic tables Previously, copy_from and copy_to would only be possible with tables known at compile time. It is now possible to also use them with tables that are only known at runtime. That is achieved by changing the signature of `CopyTarget::walk_target` to take a `self` argument. --- diesel/src/pg/query_builder/copy/copy_from.rs | 36 +++++++++++-------- diesel/src/pg/query_builder/copy/copy_to.rs | 9 +++-- diesel/src/pg/query_builder/copy/mod.rs | 12 +++++-- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/diesel/src/pg/query_builder/copy/copy_from.rs b/diesel/src/pg/query_builder/copy/copy_from.rs index baa3e19ac03c..775d5a3c63b2 100644 --- a/diesel/src/pg/query_builder/copy/copy_from.rs +++ b/diesel/src/pg/query_builder/copy/copy_from.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; use std::marker::PhantomData; +use std::rc::Rc; use byteorder::NetworkEndian; use byteorder::WriteBytesExt; @@ -87,7 +88,7 @@ impl CopyFromOptions { pub struct CopyFrom { options: CopyFromOptions, copy_callback: F, - p: PhantomData, + target: S, } pub(crate) struct InternalCopyFromQuery { @@ -163,7 +164,7 @@ where &'b self, pass: crate::query_builder::AstPass<'_, 'b, Pg>, ) -> crate::QueryResult<()> { - S::walk_target(pass) + self.target.walk_target(pass) } } @@ -269,12 +270,16 @@ macro_rules! impl_copy_from_insertable_helper_for_values_clause { diesel_derives::__diesel_for_each_tuple!(impl_copy_from_insertable_helper_for_values_clause); #[derive(Debug)] -pub struct InsertableWrapper(Option); +pub struct InsertableWrapper { + table: Rc, + insertable: Option, +} -impl CopyFromExpression for InsertableWrapper +impl CopyFromExpression for InsertableWrapper where I: Insertable, T, QId, STATIC_QUERY_ID>>, V: CopyFromInsertableHelper, + T: CopyTarget, { type Error = crate::result::Error; @@ -298,7 +303,7 @@ where // this skips reallocating let mut buffer = Vec::::new(); let values = self - .0 + .insertable .take() .expect("We only call this callback once") .values(); @@ -354,7 +359,7 @@ where &'b self, pass: crate::query_builder::AstPass<'_, 'b, Pg>, ) -> crate::QueryResult<()> { - ::Target::walk_target(pass) + self.table.walk_target(pass) } } @@ -372,7 +377,7 @@ where #[must_use = "`COPY FROM` statements are only executed when calling `.execute()`."] #[cfg(feature = "postgres_backend")] pub struct CopyFromQuery { - table: T, + table: Rc, action: Action, } @@ -386,7 +391,7 @@ where /// `action` expects a callback which accepts a [`std::io::Write`] argument. The necessary format /// accepted by this writer sink depends on the options provided via the `with_*` methods #[allow(clippy::wrong_self_convention)] // the sql struct is named that way - pub fn from_raw_data(self, _target: C, action: F) -> CopyFromQuery> + pub fn from_raw_data(self, target: C, action: F) -> CopyFromQuery> where C: CopyTarget, F: Fn(&mut dyn std::io::Write) -> Result<(), E>, @@ -394,7 +399,7 @@ where CopyFromQuery { table: self.table, action: CopyFrom { - p: PhantomData, + target, options: Default::default(), copy_callback: action, }, @@ -411,13 +416,16 @@ where /// This uses the binary format. It internally configures the correct /// set of settings and does not allow to set other options #[allow(clippy::wrong_self_convention)] // the sql struct is named that way - pub fn from_insertable(self, insertable: I) -> CopyFromQuery> + pub fn from_insertable(self, insertable: I) -> CopyFromQuery> where - InsertableWrapper: CopyFromExpression, + InsertableWrapper: CopyFromExpression, { CopyFromQuery { - table: self.table, - action: InsertableWrapper(Some(insertable)), + table: self.table.clone(), + action: InsertableWrapper { + table: self.table, + insertable: Some(insertable), + }, } } } @@ -650,7 +658,7 @@ where T: Table, { CopyFromQuery { - table, + table: Rc::new(table), action: NotSet, } } diff --git a/diesel/src/pg/query_builder/copy/copy_to.rs b/diesel/src/pg/query_builder/copy/copy_to.rs index 4b346b176883..4d2e733f456f 100644 --- a/diesel/src/pg/query_builder/copy/copy_to.rs +++ b/diesel/src/pg/query_builder/copy/copy_to.rs @@ -1,5 +1,4 @@ use std::io::BufRead; -use std::marker::PhantomData; use super::CommonOptions; use super::CopyFormat; @@ -57,7 +56,7 @@ impl QueryFragment for CopyToOptions { #[derive(Debug)] pub struct CopyToCommand { options: CopyToOptions, - p: PhantomData, + target: S, } impl QueryId for CopyToCommand @@ -79,7 +78,7 @@ where ) -> crate::QueryResult<()> { pass.unsafe_to_cache_prepared(); pass.push_sql("COPY "); - S::walk_target(pass.reborrow())?; + self.target.walk_target(pass.reborrow())?; pass.push_sql(" TO STDOUT"); self.options.walk_ast(pass.reborrow())?; Ok(()) @@ -301,7 +300,7 @@ where let io_result_mapper = |e| crate::result::Error::DeserializationError(Box::new(e)); let command = CopyToCommand { - p: PhantomData::, + target: self.target, options: CopyToOptions { header: None, common: CommonOptions { @@ -410,7 +409,7 @@ where { let q = O::setup_options(self); let command = CopyToCommand { - p: PhantomData::, + target: q.target, options: q.options, }; ExecuteCopyToConnection::execute(conn, command) diff --git a/diesel/src/pg/query_builder/copy/mod.rs b/diesel/src/pg/query_builder/copy/mod.rs index 07f9efe77528..7465457feb5f 100644 --- a/diesel/src/pg/query_builder/copy/mod.rs +++ b/diesel/src/pg/query_builder/copy/mod.rs @@ -117,7 +117,10 @@ pub trait CopyTarget { type SqlType: SqlType; #[doc(hidden)] - fn walk_target(pass: crate::query_builder::AstPass<'_, '_, Pg>) -> crate::QueryResult<()>; + fn walk_target( + &self, + pass: crate::query_builder::AstPass<'_, '_, Pg>, + ) -> crate::QueryResult<()>; } impl CopyTarget for T @@ -130,7 +133,10 @@ where type Table = Self; type SqlType = T::SqlType; - fn walk_target(mut pass: crate::query_builder::AstPass<'_, '_, Pg>) -> crate::QueryResult<()> { + fn walk_target( + &self, + mut pass: crate::query_builder::AstPass<'_, '_, Pg>, + ) -> crate::QueryResult<()> { T::STATIC_COMPONENT.walk_ast(pass.reborrow())?; pass.push_sql("("); T::all_columns().walk_ast(pass.reborrow())?; @@ -157,7 +163,7 @@ macro_rules! copy_target_for_columns { type Table = T; type SqlType = crate::dsl::SqlTypeOf; - fn walk_target( + fn walk_target(&self, mut pass: crate::query_builder::AstPass<'_, '_, Pg>, ) -> crate::QueryResult<()> { T::STATIC_COMPONENT.walk_ast(pass.reborrow())?;