use alloc::boxed::Box;
use alloc::string::ToString;
use alloc::vec::Vec;
use diesel::backend::Backend;
use diesel::query_builder::{AstPass, QueryFragment, QueryId};
use diesel::query_dsl::RunQueryDsl;
use diesel::result::{Error as DieselError, QueryResult};
use diesel::serialize::ToSql;
use diesel::sql_types::{BigInt, Binary, Double, HasSqlType, Text};
use super::sql_output::ColumnNames;
use super::view::{ChangesetOp, PatchsetOp};
use crate::encoding::Value;
use crate::{DynTable, SchemaWithPK};
#[derive(Debug, Clone, thiserror::Error)]
enum RenderError {
#[error("missing column name for index {column_index}")]
MissingColumnName { column_index: usize },
#[error("UPDATE has an empty SET clause")]
EmptyUpdateSet,
#[error("UPDATE/DELETE targets a table with no primary key")]
EmptyRowPredicate,
#[error("changeset UPDATE is missing the old value of primary-key column {column_index}")]
MissingPkValue { column_index: usize },
#[error("adapter rejected column {column_index} of table {table_name:?}: {message}")]
AdapterBindFailure {
table_name: alloc::string::String,
column_index: usize,
message: alloc::string::String,
},
}
#[inline]
fn render_err(err: RenderError) -> DieselError {
DieselError::QueryBuilderError(Box::new(err))
}
#[inline]
fn column_name_at<T: ColumnNames>(table: &T, index: usize) -> QueryResult<&str> {
table.column_name(index).ok_or_else(|| {
render_err(RenderError::MissingColumnName {
column_index: index,
})
})
}
pub trait ValueBackend: Backend {
fn bind_value<'b, S, B>(
out: &mut AstPass<'_, 'b, Self>,
value: &'b Value<S, B>,
) -> QueryResult<()>
where
S: AsRef<str>,
B: AsRef<[u8]>;
}
impl<DB> ValueBackend for DB
where
DB: Backend + HasSqlType<BigInt> + HasSqlType<Double> + HasSqlType<Text> + HasSqlType<Binary>,
i64: ToSql<BigInt, DB>,
f64: ToSql<Double, DB>,
str: ToSql<Text, DB>,
[u8]: ToSql<Binary, DB>,
{
fn bind_value<'b, S, B>(
out: &mut AstPass<'_, 'b, Self>,
value: &'b Value<S, B>,
) -> QueryResult<()>
where
S: AsRef<str>,
B: AsRef<[u8]>,
{
match value {
Value::Null => {
out.push_sql("NULL");
Ok(())
}
Value::Integer(i) => out.push_bind_param::<BigInt, i64>(i),
Value::Real(f) => out.push_bind_param::<Double, f64>(f),
Value::Text(s) => out.push_bind_param::<Text, str>(s.as_ref()),
Value::Blob(b) => out.push_bind_param::<Binary, [u8]>(b.as_ref()),
}
}
}
type BoxedBinder<'a, DB> = Box<dyn Binder<DB> + Send + 'a>;
type Assignment<'a, S, B> = (usize, &'a Value<S, B>);
enum RenderPlan<'a, S, B> {
Insert {
values: &'a [Value<S, B>],
},
Update {
set: Vec<Assignment<'a, S, B>>,
predicate: Vec<Assignment<'a, S, B>>,
},
Delete {
predicate: Vec<Assignment<'a, S, B>>,
},
}
fn build_predicate<'a, T, S, B, F>(
table: &T,
mut value_for: F,
) -> Result<Vec<Assignment<'a, S, B>>, RenderError>
where
T: SchemaWithPK,
F: FnMut(usize, usize) -> Result<&'a Value<S, B>, RenderError>,
{
let pk_columns = table.primary_key_columns();
if pk_columns.is_empty() {
return Err(RenderError::EmptyRowPredicate);
}
let mut predicate = Vec::with_capacity(pk_columns.len());
for (pk_ordinal, col_idx) in pk_columns.into_iter().enumerate() {
predicate.push((col_idx, value_for(pk_ordinal, col_idx)?));
}
Ok(predicate)
}
trait DieselRenderable<'a, S, B> {
type Table: SchemaWithPK;
fn table(&self) -> &'a Self::Table;
fn render_plan(&self) -> Result<RenderPlan<'a, S, B>, RenderError>;
}
impl<'a, T, S, B> DieselRenderable<'a, S, B> for PatchsetOp<'a, T, S, B>
where
T: SchemaWithPK,
{
type Table = T;
fn table(&self) -> &'a T {
PatchsetOp::table(self)
}
fn render_plan(&self) -> Result<RenderPlan<'a, S, B>, RenderError> {
match *self {
PatchsetOp::Insert { values, .. } => Ok(RenderPlan::Insert { values }),
PatchsetOp::Update {
table, pk, entries, ..
} => {
let mut set = Vec::new();
for (col_idx, ((), new)) in entries.iter().enumerate() {
if let Some(value) = new.as_ref()
&& table.primary_key_index(col_idx).is_none()
{
set.push((col_idx, value));
}
}
if set.is_empty() {
return Err(RenderError::EmptyUpdateSet);
}
let predicate = build_predicate(table, |pk_ordinal, _col_idx| Ok(&pk[pk_ordinal]))?;
Ok(RenderPlan::Update { set, predicate })
}
PatchsetOp::Delete { table, pk, .. } => {
let predicate = build_predicate(table, |pk_ordinal, _col_idx| Ok(&pk[pk_ordinal]))?;
Ok(RenderPlan::Delete { predicate })
}
}
}
}
impl<'a, T, S, B> DieselRenderable<'a, S, B> for ChangesetOp<'a, T, S, B>
where
T: SchemaWithPK,
S: AsRef<str> + PartialEq,
B: AsRef<[u8]> + PartialEq,
{
type Table = T;
fn table(&self) -> &'a T {
ChangesetOp::table(self)
}
fn render_plan(&self) -> Result<RenderPlan<'a, S, B>, RenderError> {
match *self {
ChangesetOp::Insert { values, .. } => Ok(RenderPlan::Insert { values }),
ChangesetOp::Update { table, values, .. } => {
let mut set = Vec::new();
for (col_idx, (old, new)) in values.iter().enumerate() {
if let Some(new_value) = new.as_ref()
&& old.as_ref() != Some(new_value)
{
set.push((col_idx, new_value));
}
}
if set.is_empty() {
return Err(RenderError::EmptyUpdateSet);
}
let predicate = build_predicate(table, |_pk_ordinal, col_idx| {
values[col_idx]
.0
.as_ref()
.ok_or(RenderError::MissingPkValue {
column_index: col_idx,
})
})?;
Ok(RenderPlan::Update { set, predicate })
}
ChangesetOp::Delete {
table, old_values, ..
} => {
let predicate =
build_predicate(table, |_pk_ordinal, col_idx| Ok(&old_values[col_idx]))?;
Ok(RenderPlan::Delete { predicate })
}
}
}
}
trait ClauseSink<'b, S, B, DB: Backend> {
fn column_name(&self, col_idx: usize) -> QueryResult<&str>;
fn emit_value(
&mut self,
col_idx: usize,
value: &'b Value<S, B>,
out: &mut AstPass<'_, 'b, DB>,
) -> QueryResult<()>;
}
struct NaiveSink<'t, T> {
table: &'t T,
}
impl<'b, T, S, B, DB> ClauseSink<'b, S, B, DB> for NaiveSink<'_, T>
where
T: ColumnNames,
S: AsRef<str>,
B: AsRef<[u8]>,
DB: ValueBackend,
{
fn column_name(&self, col_idx: usize) -> QueryResult<&str> {
column_name_at(self.table, col_idx)
}
fn emit_value(
&mut self,
_col_idx: usize,
value: &'b Value<S, B>,
out: &mut AstPass<'_, 'b, DB>,
) -> QueryResult<()> {
DB::bind_value(out, value)
}
}
fn walk_assignments<'p, 'b, S, B, DB, K>(
items: &[Assignment<'p, S, B>],
separator: &str,
sink: &mut K,
out: &mut AstPass<'_, 'b, DB>,
) -> QueryResult<()>
where
'p: 'b,
DB: Backend,
K: ClauseSink<'b, S, B, DB>,
{
for (position, (col_idx, value)) in items.iter().copied().enumerate() {
if position > 0 {
out.push_sql(separator);
}
out.push_identifier(sink.column_name(col_idx)?)?;
out.push_sql(" = ");
sink.emit_value(col_idx, value, out)?;
}
Ok(())
}
fn walk_plan<'p, 'b, S, B, DB, K>(
plan: RenderPlan<'p, S, B>,
table_name: &str,
sink: &mut K,
out: &mut AstPass<'_, 'b, DB>,
) -> QueryResult<()>
where
'p: 'b,
DB: Backend,
K: ClauseSink<'b, S, B, DB>,
{
match plan {
RenderPlan::Insert { values } => {
out.push_sql("INSERT INTO ");
out.push_identifier(table_name)?;
out.push_sql(" (");
for (index, _value) in values.iter().enumerate() {
if index > 0 {
out.push_sql(", ");
}
out.push_identifier(sink.column_name(index)?)?;
}
out.push_sql(") VALUES (");
for (index, value) in values.iter().enumerate() {
if index > 0 {
out.push_sql(", ");
}
sink.emit_value(index, value, out)?;
}
out.push_sql(")");
Ok(())
}
RenderPlan::Update { set, predicate } => {
out.push_sql("UPDATE ");
out.push_identifier(table_name)?;
out.push_sql(" SET ");
walk_assignments(&set, ", ", sink, out)?;
out.push_sql(" WHERE ");
walk_assignments(&predicate, " AND ", sink, out)
}
RenderPlan::Delete { predicate } => {
out.push_sql("DELETE FROM ");
out.push_identifier(table_name)?;
out.push_sql(" WHERE ");
walk_assignments(&predicate, " AND ", sink, out)
}
}
}
fn walk_naive<'a, 'b, V, T, S, B, DB>(op: &'b V, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()>
where
'a: 'b,
V: DieselRenderable<'a, S, B, Table = T>,
T: ColumnNames + 'a,
S: AsRef<str> + 'a,
B: AsRef<[u8]> + 'a,
DB: ValueBackend,
{
out.unsafe_to_cache_prepared();
let plan = op.render_plan().map_err(render_err)?;
let table = op.table();
let mut sink = NaiveSink { table };
walk_plan(plan, table.name(), &mut sink, &mut out)
}
impl<T, S, B, DB> QueryFragment<DB> for PatchsetOp<'_, T, S, B>
where
T: ColumnNames,
S: AsRef<str>,
B: AsRef<[u8]>,
DB: ValueBackend,
{
fn walk_ast<'b>(&'b self, out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
walk_naive(self, out)
}
}
impl<T, S, B, DB> QueryFragment<DB> for ChangesetOp<'_, T, S, B>
where
T: ColumnNames,
S: AsRef<str> + PartialEq,
B: AsRef<[u8]> + PartialEq,
DB: ValueBackend,
{
fn walk_ast<'b>(&'b self, out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
walk_naive(self, out)
}
}
impl<T, S, B> QueryId for PatchsetOp<'_, T, S, B> {
type QueryId = ();
const HAS_STATIC_QUERY_ID: bool = false;
}
impl<T, S, B> QueryId for ChangesetOp<'_, T, S, B> {
type QueryId = ();
const HAS_STATIC_QUERY_ID: bool = false;
}
impl<T, S, B, Conn> RunQueryDsl<Conn> for PatchsetOp<'_, T, S, B> {}
impl<T, S, B, Conn> RunQueryDsl<Conn> for ChangesetOp<'_, T, S, B> {}
pub trait Binder<DB: Backend> {
fn walk<'b>(&'b self, out: &mut AstPass<'_, 'b, DB>) -> QueryResult<()>;
}
pub struct DefaultBinder<'a, S, B> {
value: &'a Value<S, B>,
}
impl<'a, S, B> DefaultBinder<'a, S, B> {
#[must_use]
pub fn new(value: &'a Value<S, B>) -> Self {
Self { value }
}
}
impl<'a, S, B> From<&'a Value<S, B>> for DefaultBinder<'a, S, B> {
fn from(value: &'a Value<S, B>) -> Self {
Self { value }
}
}
impl<S, B, DB> Binder<DB> for DefaultBinder<'_, S, B>
where
S: AsRef<str>,
B: AsRef<[u8]>,
DB: ValueBackend,
{
fn walk<'b>(&'b self, out: &mut AstPass<'_, 'b, DB>) -> QueryResult<()> {
DB::bind_value(out, self.value)
}
}
pub trait Adapter<DB, S, B> {
fn column_name(&self, table_name: &str, column_index: usize) -> &str;
fn bind<'a>(
&self,
table_name: &str,
column_index: usize,
value: &'a Value<S, B>,
) -> QueryResult<Box<dyn Binder<DB> + Send + 'a>>;
}
fn resolve_binders<'a, S, B, DB, A>(
plan: RenderPlan<'a, S, B>,
table_name: &str,
adapter: &A,
) -> Result<Vec<BoxedBinder<'a, DB>>, RenderError>
where
DB: Backend,
A: Adapter<DB, S, B>,
{
let mut binders: Vec<BoxedBinder<'a, DB>> = Vec::new();
{
let mut bind = |col_idx: usize, value: &'a Value<S, B>| -> Result<(), RenderError> {
let binder = adapter.bind(table_name, col_idx, value).map_err(|err| {
RenderError::AdapterBindFailure {
table_name: table_name.into(),
column_index: col_idx,
message: err.to_string(),
}
})?;
binders.push(binder);
Ok(())
};
match plan {
RenderPlan::Insert { values } => {
for (col_idx, value) in values.iter().enumerate() {
bind(col_idx, value)?;
}
}
RenderPlan::Update { set, predicate } => {
for (col_idx, value) in set {
bind(col_idx, value)?;
}
for (col_idx, value) in predicate {
bind(col_idx, value)?;
}
}
RenderPlan::Delete { predicate } => {
for (col_idx, value) in predicate {
bind(col_idx, value)?;
}
}
}
}
Ok(binders)
}
struct BoundSink<'r, 'a, S, B, DB, A>
where
DB: Backend,
{
binders: core::slice::Iter<'r, BoxedBinder<'a, DB>>,
adapter: &'a A,
table_name: &'a str,
_marker: core::marker::PhantomData<(S, B)>,
}
impl<'b, 'a, S, B, DB, A> ClauseSink<'b, S, B, DB> for BoundSink<'b, 'a, S, B, DB, A>
where
'a: 'b,
DB: Backend,
A: Adapter<DB, S, B>,
{
fn column_name(&self, col_idx: usize) -> QueryResult<&str> {
Ok(self.adapter.column_name(self.table_name, col_idx))
}
fn emit_value(
&mut self,
_col_idx: usize,
_value: &'b Value<S, B>,
out: &mut AstPass<'_, 'b, DB>,
) -> QueryResult<()> {
let binder = self
.binders
.next()
.ok_or_else(|| DieselError::QueryBuilderError(Box::new(BinderResolutionError)))?;
binder.walk(out)
}
}
pub struct BoundOp<'a, V, S, B, DB, A>
where
DB: Backend,
{
op: V,
resolved: Result<Vec<BoxedBinder<'a, DB>>, RenderError>,
adapter: &'a A,
_marker: core::marker::PhantomData<(S, B)>,
}
impl<'a, V, S, B, DB, A> BoundOp<'a, V, S, B, DB, A>
where
V: DieselRenderable<'a, S, B> + 'a,
S: 'a,
B: 'a,
DB: Backend,
A: Adapter<DB, S, B> + Send + Sync,
{
fn resolve(op: V, adapter: &'a A) -> Self {
let table_name = op.table().name();
let resolved = op
.render_plan()
.and_then(|plan| resolve_binders(plan, table_name, adapter));
Self {
op,
resolved,
adapter,
_marker: core::marker::PhantomData,
}
}
}
impl<'a, V, T, S, B, DB, A> QueryFragment<DB> for BoundOp<'a, V, S, B, DB, A>
where
V: DieselRenderable<'a, S, B, Table = T>,
T: SchemaWithPK + 'a,
S: AsRef<str> + 'a,
B: AsRef<[u8]> + 'a,
DB: Backend,
A: Adapter<DB, S, B> + Send + Sync,
{
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
out.unsafe_to_cache_prepared();
let binders = self
.resolved
.as_ref()
.map_err(|err| render_err(err.clone()))?;
let plan = self.op.render_plan().map_err(render_err)?;
let table_name = self.op.table().name();
let mut sink = BoundSink {
binders: binders.iter(),
adapter: self.adapter,
table_name,
_marker: core::marker::PhantomData,
};
walk_plan(plan, table_name, &mut sink, &mut out)
}
}
impl<V, S, B, DB, A> QueryId for BoundOp<'_, V, S, B, DB, A>
where
DB: Backend,
{
type QueryId = ();
const HAS_STATIC_QUERY_ID: bool = false;
}
impl<V, S, B, DB, A, Conn> RunQueryDsl<Conn> for BoundOp<'_, V, S, B, DB, A> where DB: Backend {}
#[derive(Debug, thiserror::Error)]
#[error("internal: binder count mismatch between BoundOp resolve and walk_ast")]
struct BinderResolutionError;
pub type BoundPatchsetOp<'a, T, S, B, DB, A> = BoundOp<'a, PatchsetOp<'a, T, S, B>, S, B, DB, A>;
pub type BoundChangesetOp<'a, T, S, B, DB, A> = BoundOp<'a, ChangesetOp<'a, T, S, B>, S, B, DB, A>;
impl<'a, T, S, B> PatchsetOp<'a, T, S, B>
where
T: SchemaWithPK,
S: AsRef<str>,
B: AsRef<[u8]>,
{
#[must_use]
pub fn with_adapter<DB, A>(self, adapter: &'a A) -> BoundPatchsetOp<'a, T, S, B, DB, A>
where
A: Adapter<DB, S, B> + Send + Sync,
DB: Backend,
{
BoundOp::resolve(self, adapter)
}
}
impl<'a, T, S, B> ChangesetOp<'a, T, S, B>
where
T: SchemaWithPK,
S: AsRef<str> + PartialEq,
B: AsRef<[u8]> + PartialEq,
{
#[must_use]
pub fn with_adapter<DB, A>(self, adapter: &'a A) -> BoundChangesetOp<'a, T, S, B, DB, A>
where
A: Adapter<DB, S, B> + Send + Sync,
DB: Backend,
{
BoundOp::resolve(self, adapter)
}
}
pub trait ApplyOps: Iterator + Sized {
fn apply<Conn>(self, conn: &mut Conn) -> QueryResult<usize>
where
Conn: diesel::Connection,
Self::Item: QueryFragment<Conn::Backend> + QueryId + RunQueryDsl<Conn>;
fn apply_transactional<Conn>(self, conn: &mut Conn) -> QueryResult<usize>
where
Conn: diesel::Connection,
Self::Item: QueryFragment<Conn::Backend> + QueryId + RunQueryDsl<Conn>;
}
impl<I> ApplyOps for I
where
I: Iterator + Sized,
{
fn apply<Conn>(self, conn: &mut Conn) -> QueryResult<usize>
where
Conn: diesel::Connection,
Self::Item: QueryFragment<Conn::Backend> + QueryId + RunQueryDsl<Conn>,
{
let mut total = 0_usize;
for op in self {
total = total.saturating_add(op.execute(conn)?);
}
Ok(total)
}
fn apply_transactional<Conn>(self, conn: &mut Conn) -> QueryResult<usize>
where
Conn: diesel::Connection,
Self::Item: QueryFragment<Conn::Backend> + QueryId + RunQueryDsl<Conn>,
{
conn.transaction(|conn| self.apply(conn))
}
}