1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
//! Bind Module for Atmosphere SQL Framework
//!
//! This module provides functionality to bind values to SQL queries in a type-safe and efficient
//! manner. It includes traits and implementations that facilitate the binding of parameters to
//! various SQL query types, ensuring that the queries are correctly formatted and executed against
//! the database.
//!
//! Key components of this module include the `Bindable` trait, which abstracts over different
//! types of queries, allowing for flexible and dynamic binding of values, and the `Bind` trait,
//! which provides an interface for binding columns to SQL queries in the context of a specific
//! table.
//!
//! # Types
//!
//! - `BindError`: An error related to binding operations, such as unknown column errors.
//! - `Bindable`: A trait for abstracting over different query types, providing a method to dynamically bind values.
//! - `Bind`: A trait for binding columns to SQL queries, specific to table entities.
//!
//! The module plays a crucial role in the framework, enabling developers to write database
//! interactions that are both expressive and resilient to errors like incorrect parameter types or
//! missing values.

use crate::{Column, Result, Table};
use miette::Diagnostic;
use sqlx::database::HasArguments;
use sqlx::query::QueryAs;
use sqlx::{Encode, QueryBuilder, Type};
use thiserror::Error;

/// Enumerates errors that can occur during the binding of values to SQL queries.
///
/// This enum covers various issues that might arise when binding parameters, such as referencing
/// unknown columns.
#[derive(Debug, Diagnostic, Error)]
#[non_exhaustive]
pub enum BindError {
    /// Represents an error where a specified column is unknown or not found.
    #[error("unknown column: {0}")]
    #[diagnostic(code(atmosphere::bind::unknown))]
    Unknown(&'static str),
}

type Query<'q, DB> = sqlx::query::Query<'q, DB, <DB as HasArguments<'q>>::Arguments>;

/// Trait for dynamic binding of values.
///
/// `Bindable` provides an abstraction over different types of SQL queries, such as
/// `sqlx::query::Query` and `sqlx::query::QueryAs`, allowing for flexible and dynamic binding of
/// values. It is designed to work with various query types and enables the binding of values with
/// different types and constraints.
pub trait Bindable<'q> {
    /// Binds a value to the query. The value must be compatible with the `atmosphere::Driver`.
    fn dyn_bind<T: 'q + Send + Encode<'q, crate::Driver> + Type<crate::Driver>>(
        self,
        value: T,
    ) -> Self;
}

impl<'q> Bindable<'q> for Query<'q, crate::Driver> {
    fn dyn_bind<T: 'q + Send + Encode<'q, crate::Driver> + Type<crate::Driver>>(
        self,
        value: T,
    ) -> Self {
        self.bind(value)
    }
}

impl<'q, E> Bindable<'q>
    for QueryAs<'q, crate::Driver, E, <crate::Driver as HasArguments<'q>>::Arguments>
{
    fn dyn_bind<T: 'q + Send + Encode<'q, crate::Driver> + Type<crate::Driver>>(
        self,
        value: T,
    ) -> Self {
        self.bind(value)
    }
}

impl<'q> Bindable<'q> for QueryBuilder<'q, crate::Driver> {
    fn dyn_bind<T: 'q + Send + Encode<'q, crate::Driver> + Type<crate::Driver>>(
        mut self,
        value: T,
    ) -> Self {
        self.push_bind(value);
        self
    }
}

/// Trait for binding columns to SQL queries in the context of a specific table.
///
/// This trait should be implemented by table entities to enable the binding of their columns to
/// SQL queries. It provides a method to bind a single column, ensuring that the query correctly
/// reflects the structure and constraints of the table.
pub trait Bind: Table {
    /// Binds a single column of the implementing table entity to a given query.
    fn bind<'q, Q: Bindable<'q>>(&'q self, c: &'q Column<Self>, query: Q) -> Result<Q>;
}