Skip to content

Commit

Permalink
Merge pull request #3977 from WattSense/moveable_binds_and_row
Browse files Browse the repository at this point in the history
Moveable binds and rows
  • Loading branch information
weiznich authored Apr 12, 2024
2 parents 5045fdb + e2c2c11 commit 793de72
Show file tree
Hide file tree
Showing 11 changed files with 389 additions and 7 deletions.
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> {
/// 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> {
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

0 comments on commit 793de72

Please sign in to comment.