Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moveable binds and rows #3977

Merged
merged 3 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions diesel/src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::backend::Backend;
use crate::expression::QueryMetadata;
use crate::query_builder::{Query, QueryFragment, QueryId};
use crate::result::*;
use crate::sql_types::TypeMetadata;
use std::fmt::Debug;

#[doc(inline)]
Expand Down Expand Up @@ -444,6 +445,15 @@ pub trait LoadConnection<B = DefaultLoadingMode>: Connection {
Self::Backend: QueryMetadata<T::SqlType>;
}

/// Describes a connection with an underlying [`crate::sql_types::TypeMetadata::MetadataLookup`]
#[diesel_derives::__diesel_public_if(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
)]
pub trait WithMetadataLookup: Connection {
/// Retrieves the underlying metadata lookup
fn metadata_lookup(&mut self) -> &mut <Self::Backend as TypeMetadata>::MetadataLookup;
}

/// A variant of the [`Connection`](trait.Connection.html) trait that is
/// usable with dynamic dispatch
///
Expand Down
31 changes: 30 additions & 1 deletion diesel/src/query_builder/ast_pass.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::fmt;

use crate::backend::Backend;
use crate::query_builder::{BindCollector, QueryBuilder};
use crate::query_builder::{BindCollector, MoveableBindCollector, QueryBuilder};
use crate::result::QueryResult;
use crate::serialize::ToSql;
use crate::sql_types::HasSqlType;
Expand Down Expand Up @@ -252,6 +252,35 @@ where
Ok(())
}

/// Push bind collector values from its data onto the query
///
/// This method works with [MoveableBindCollector] data [MoveableBindCollector::BindData]
/// and is used with already collected query meaning its SQL is already built and its
/// bind data already collected.
#[diesel_derives::__diesel_public_if(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
)]
pub(crate) fn push_bind_collector_data<MD>(
&mut self,
bind_collector_data: &MD,
) -> QueryResult<()>
where
DB: Backend,
for<'bc> DB::BindCollector<'bc>: MoveableBindCollector<DB, BindData = MD>,
{
match self.internals {
AstPassInternals::CollectBinds {
ref mut collector,
metadata_lookup: _,
} => collector.append_bind_data(bind_collector_data),
AstPassInternals::DebugBinds(ref mut f) => {
f.push(Box::new("Opaque bind collector data"))
}
_ => {}
}
Ok(())
}

/// Get information about the backend that will consume this query
#[cfg_attr(
not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"),
Expand Down
37 changes: 37 additions & 0 deletions diesel/src/query_builder/bind_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,23 @@ pub trait BindCollector<'a, DB: TypeMetadata>: Sized {
}
}

/// A movable version of the bind collector which allows it to be extracted, moved and refilled.
///
/// This is mostly useful in async context where bind data needs to be moved across threads.
#[diesel_derives::__diesel_public_if(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
)]
pub trait MoveableBindCollector<DB: TypeMetadata> {
momobel marked this conversation as resolved.
Show resolved Hide resolved
/// The movable bind data of this bind collector
type BindData: Send + 'static;

/// Builds a movable version of the bind collector
fn moveable(&self) -> Self::BindData;

/// Refill the bind collector with its bind data
fn append_bind_data(&mut self, from: &Self::BindData);
}

#[derive(Debug)]
/// A bind collector used by backends which transmit bind parameters as an
/// opaque blob of bytes.
Expand Down Expand Up @@ -125,6 +142,26 @@ where
}
}

impl<DB> MoveableBindCollector<DB> for RawBytesBindCollector<DB>
where
for<'a> DB: Backend<BindCollector<'a> = Self> + TypeMetadata + 'static,
<DB as TypeMetadata>::TypeMetadata: Clone + Send,
{
type BindData = Self;

fn moveable(&self) -> Self::BindData {
RawBytesBindCollector {
binds: self.binds.clone(),
metadata: self.metadata.clone(),
}
}

fn append_bind_data(&mut self, from: &Self::BindData) {
self.binds.extend(from.binds.iter().cloned());
self.metadata.extend(from.metadata.clone());
}
}

// This is private for now as we may want to add `Into` impls for the wrapper type
// later on
mod private {
Expand Down
51 changes: 51 additions & 0 deletions diesel/src/query_builder/collected_query.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use super::{AstPass, MoveableBindCollector, Query, QueryFragment, QueryId};
use crate::backend::{Backend, DieselReserveSpecialization};
use crate::result::QueryResult;
use crate::sql_types::Untyped;

#[derive(Debug)]
#[must_use = "Queries are only executed when calling `load`, `get_result` or similar."]
/// A SQL query variant with already collected bind data which can be moved
#[diesel_derives::__diesel_public_if(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
)]
pub struct CollectedQuery<T> {
momobel marked this conversation as resolved.
Show resolved Hide resolved
sql: String,
safe_to_cache_prepared: bool,
bind_data: T,
}

impl<T> CollectedQuery<T> {
/// Builds a [CollectedQuery] with movable bind data
pub fn new(sql: String, safe_to_cache_prepared: bool, bind_data: T) -> Self {
Self {
sql,
safe_to_cache_prepared,
bind_data,
}
}
}

impl<DB, T> QueryFragment<DB> for CollectedQuery<T>
where
DB: Backend + DieselReserveSpecialization,
for<'a> <DB as Backend>::BindCollector<'a>: MoveableBindCollector<DB, BindData = T>,
{
fn walk_ast<'b>(&'b self, mut pass: AstPass<'_, 'b, DB>) -> QueryResult<()> {
if !self.safe_to_cache_prepared {
pass.unsafe_to_cache_prepared();
}
pass.push_sql(&self.sql);
pass.push_bind_collector_data::<T>(&self.bind_data)
}
}

impl<T> QueryId for CollectedQuery<T> {
type QueryId = ();

const HAS_STATIC_QUERY_ID: bool = false;
}

impl<T> Query for CollectedQuery<T> {
type SqlType = Untyped;
}
5 changes: 4 additions & 1 deletion diesel/src/query_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod clause_macro;

pub(crate) mod ast_pass;
pub mod bind_collector;
mod collected_query;
pub(crate) mod combination_clause;
mod debug_query;
mod delete_statement;
Expand All @@ -37,7 +38,9 @@ pub(crate) mod where_clause;
#[doc(inline)]
pub use self::ast_pass::AstPass;
#[doc(inline)]
pub use self::bind_collector::BindCollector;
pub use self::bind_collector::{BindCollector, MoveableBindCollector};
#[doc(inline)]
pub use self::collected_query::CollectedQuery;
#[doc(inline)]
pub use self::debug_query::DebugQuery;
#[doc(inline)]
Expand Down
12 changes: 12 additions & 0 deletions diesel/src/row.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,18 @@ where
}
}

/// A row that can be turned into an owned version
#[diesel_derives::__diesel_public_if(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
)]
pub trait IntoOwnedRow<'a, DB: Backend>: Row<'a, DB> {
/// The owned version of the row
type OwnedRow: Row<'a, DB> + Send + 'static;

/// Turn the row into its owned version
fn into_owned(self) -> Self::OwnedRow;
}

// These traits are not part of the public API
// because:
// * we want to control who can implement `Row` (for `RowSealed`)
Expand Down
75 changes: 74 additions & 1 deletion diesel/src/sqlite/connection/bind_collector.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::query_builder::BindCollector;
use crate::query_builder::{BindCollector, MoveableBindCollector};
use crate::serialize::{IsNull, Output};
use crate::sql_types::HasSqlType;
use crate::sqlite::{Sqlite, SqliteType};
Expand Down Expand Up @@ -200,3 +200,76 @@ impl<'a> BindCollector<'a, Sqlite> for SqliteBindCollector<'a> {
Ok(())
}
}

#[derive(Debug)]
enum OwnedSqliteBindValue {
String(Box<str>),
Binary(Box<[u8]>),
I32(i32),
I64(i64),
F64(f64),
Null,
}

impl<'a> std::convert::From<&InternalSqliteBindValue<'a>> for OwnedSqliteBindValue {
fn from(value: &InternalSqliteBindValue<'a>) -> Self {
match value {
InternalSqliteBindValue::String(s) => Self::String(s.clone()),
InternalSqliteBindValue::BorrowedString(s) => {
Self::String(String::from(*s).into_boxed_str())
}
InternalSqliteBindValue::Binary(b) => Self::Binary(b.clone()),
InternalSqliteBindValue::BorrowedBinary(s) => {
Self::Binary(Vec::from(*s).into_boxed_slice())
}
InternalSqliteBindValue::I32(val) => Self::I32(*val),
InternalSqliteBindValue::I64(val) => Self::I64(*val),
InternalSqliteBindValue::F64(val) => Self::F64(*val),
InternalSqliteBindValue::Null => Self::Null,
}
}
}

impl<'a> std::convert::From<&OwnedSqliteBindValue> for InternalSqliteBindValue<'a> {
fn from(value: &OwnedSqliteBindValue) -> Self {
match value {
OwnedSqliteBindValue::String(s) => Self::String(s.clone()),
OwnedSqliteBindValue::Binary(b) => Self::Binary(b.clone()),
OwnedSqliteBindValue::I32(val) => Self::I32(*val),
OwnedSqliteBindValue::I64(val) => Self::I64(*val),
OwnedSqliteBindValue::F64(val) => Self::F64(*val),
OwnedSqliteBindValue::Null => Self::Null,
}
}
}

#[derive(Debug)]
/// Sqlite bind collector data that is movable across threads
pub struct SqliteBindCollectorData {
binds: Vec<(OwnedSqliteBindValue, SqliteType)>,
}

impl MoveableBindCollector<Sqlite> for SqliteBindCollector<'_> {
type BindData = SqliteBindCollectorData;

fn moveable(&self) -> Self::BindData {
let mut binds = Vec::with_capacity(self.binds.len());
for b in self
.binds
.iter()
.map(|(bind, tpe)| (OwnedSqliteBindValue::from(bind), *tpe))
{
binds.push(b);
}
SqliteBindCollectorData { binds }
}

fn append_bind_data(&mut self, from: &Self::BindData) {
self.binds.reserve_exact(from.binds.len());
self.binds.extend(
from.binds
.iter()
.map(|(bind, tpe)| (InternalSqliteBindValue::from(bind), *tpe)),
);
}
}
13 changes: 12 additions & 1 deletion diesel/src/sqlite/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ extern crate libsqlite3_sys as ffi;

mod bind_collector;
mod functions;
mod owned_row;
mod raw;
mod row;
mod serialized_database;
Expand All @@ -28,7 +29,7 @@ use crate::expression::QueryMetadata;
use crate::query_builder::*;
use crate::result::*;
use crate::serialize::ToSql;
use crate::sql_types::HasSqlType;
use crate::sql_types::{HasSqlType, TypeMetadata};
use crate::sqlite::Sqlite;

/// Connections for the SQLite backend. Unlike other backends, SQLite supported
Expand Down Expand Up @@ -123,6 +124,9 @@ pub struct SqliteConnection {
statement_cache: StatementCache<Sqlite, Statement>,
raw_connection: RawConnection,
transaction_state: AnsiTransactionManager,
// this exists for the sole purpose of implementing `WithMetadataLookup` trait
// and avoiding static mut which will be deprecated in 2024 edition
metadata_lookup: (),
instrumentation: Option<Box<dyn Instrumentation>>,
}

Expand Down Expand Up @@ -220,6 +224,12 @@ impl LoadConnection<DefaultLoadingMode> for SqliteConnection {
}
}

impl WithMetadataLookup for SqliteConnection {
fn metadata_lookup(&mut self) -> &mut <Sqlite as TypeMetadata>::MetadataLookup {
&mut self.metadata_lookup
}
}

#[cfg(feature = "r2d2")]
impl crate::r2d2::R2D2Connection for crate::sqlite::SqliteConnection {
fn ping(&mut self) -> QueryResult<()> {
Expand Down Expand Up @@ -529,6 +539,7 @@ impl SqliteConnection {
statement_cache: StatementCache::new(),
raw_connection,
transaction_state: AnsiTransactionManager::default(),
metadata_lookup: (),
instrumentation: None,
};
conn.register_diesel_sql_functions()
Expand Down
Loading
Loading