Skip to main content

drizzle_core/builder/
insert_select.rs

1use core::marker::PhantomData;
2
3use crate::{
4    Cons, HasSelectModel, IntoSelectTarget, Nil, SQL, SQLChunk, SQLColumnInfo, SQLParam, Scoped,
5    SelectAs, SelectCols, SelectStar, SelectedExpressionList, Token, TypeEq, TypeSet, expr::Expr,
6    scope::ListIncludes, types::Assignable,
7};
8
9/// INSERT builder state after `.columns(...)`, waiting for its SELECT
10/// source. `Columns` is the type-level list of target columns.
11#[derive(Debug, Clone, Copy, Default)]
12pub struct InsertColumnsSet<Columns>(PhantomData<Columns>);
13
14/// A generated table column that may appear in an INSERT target list.
15#[diagnostic::on_unimplemented(
16    message = "`{Self}` is not a column of `{Table}`",
17    label = "the target columns belong to the table being inserted into"
18)]
19#[doc(hidden)]
20pub trait InsertColumn<Table> {
21    /// The column type itself (the same type for a bare column).
22    type Column;
23}
24
25impl<T, Table> InsertColumn<Table> for &T
26where
27    T: InsertColumn<Table>,
28{
29    type Column = T::Column;
30}
31
32/// Generated INSERT SELECT metadata for a table.
33#[doc(hidden)]
34pub trait InsertSelectTable {
35    /// Insertable columns, in order.
36    type Columns: TypeSet;
37    /// Columns an INSERT must set: `NOT NULL` without a database default.
38    type RequiredColumns: TypeSet;
39
40    /// Names of the insertable columns, in order.
41    const INSERT_COLUMNS: &'static [&'static str];
42
43    /// Renders `("col1", "col2", ...)` from [`Self::INSERT_COLUMNS`].
44    fn insert_columns_sql<'a, V: SQLParam>() -> SQL<'a, V> {
45        let mut sql = SQL::empty();
46        for (index, column) in Self::INSERT_COLUMNS.iter().enumerate() {
47            if index > 0 {
48                sql.push_mut(Token::COMMA);
49            }
50            sql.append_mut(SQL::ident(*column));
51        }
52        sql.parens()
53    }
54}
55
56impl<T> InsertSelectTable for &T
57where
58    T: InsertSelectTable,
59{
60    type Columns = T::Columns;
61    type RequiredColumns = T::RequiredColumns;
62
63    const INSERT_COLUMNS: &'static [&'static str] = T::INSERT_COLUMNS;
64}
65
66/// A table whose SELECT model has exactly its insertable columns.
67#[doc(hidden)]
68#[diagnostic::on_unimplemented(
69    message = "select-all cannot feed this INSERT target",
70    label = "select the insertable source columns explicitly"
71)]
72pub trait InsertSelectAllColumns: InsertSelectTable {}
73
74impl<T> InsertSelectAllColumns for &T where T: InsertSelectAllColumns {}
75
76/// Pairwise compatibility between target columns and SELECT expressions.
77#[diagnostic::on_unimplemented(
78    message = "the SELECT does not provide one value of a matching type for each INSERT column",
79    label = "its columns do not line up with the target columns",
80    note = "select one value per target column, in the same order, each assignable to its column"
81)]
82#[doc(hidden)]
83pub trait InsertSelectColumns<'a, V: SQLParam, Source> {}
84
85impl<'a, V: SQLParam> InsertSelectColumns<'a, V, Nil> for Nil {}
86
87impl<'a, V, TargetExpr, TargetTail, SourceExpr, SourceTail>
88    InsertSelectColumns<'a, V, Cons<SourceExpr, SourceTail>> for Cons<TargetExpr, TargetTail>
89where
90    V: SQLParam,
91    TargetExpr: Expr<'a, V>,
92    SourceExpr: Expr<'a, V>,
93    TargetExpr::SQLType: Assignable<SourceExpr::SQLType>,
94    TargetExpr::Nullable: crate::expr::AcceptsNullability<SourceExpr::Nullable>,
95    TargetTail: InsertSelectColumns<'a, V, SourceTail>,
96{
97}
98
99/// An explicit column selection that is valid as an INSERT target list.
100#[doc(hidden)]
101pub trait InsertTargetColumns<'a, V: SQLParam, Table> {
102    /// The target columns as a type-level list.
103    type Columns: TypeSet;
104
105    /// Renders `("col1", "col2", ...)`.
106    ///
107    /// # Panics
108    ///
109    /// Panics if the list is empty or names a column twice.
110    fn into_target_columns_sql(self) -> SQL<'a, V>;
111}
112
113/// Target columns of an explicit column selection.
114#[doc(hidden)]
115pub trait InsertTargetMarker<Table> {
116    type Columns: TypeSet;
117}
118
119impl<Table, Selected> InsertTargetMarker<Table> for SelectCols<Selected>
120where
121    Selected: SelectedExpressionList,
122    Selected::Expressions: InsertTargetColumnList<Table>,
123{
124    type Columns = <Selected::Expressions as InsertTargetColumnList<Table>>::Columns;
125}
126
127/// A type-level list of target columns that all belong to `Table`.
128#[doc(hidden)]
129pub trait InsertTargetColumnList<Table> {
130    /// The list with each element resolved to its column type.
131    type Columns: TypeSet;
132
133    /// Appends the quoted column names, comma-separated, to `sql`.
134    ///
135    /// # Panics
136    ///
137    /// Panics if a column already appears in `sql`.
138    fn append_columns<'a, V: SQLParam>(sql: &mut SQL<'a, V>);
139}
140
141impl<Table> InsertTargetColumnList<Table> for Nil {
142    type Columns = Nil;
143
144    fn append_columns<'a, V: SQLParam>(_sql: &mut SQL<'a, V>) {}
145}
146
147impl<Table, Head, Tail> InsertTargetColumnList<Table> for Cons<Head, Tail>
148where
149    Head: InsertColumn<Table>,
150    Head::Column: SQLColumnInfo + Default,
151    Tail: InsertTargetColumnList<Table>,
152{
153    type Columns = Cons<Head::Column, Tail::Columns>;
154
155    fn append_columns<'a, V: SQLParam>(sql: &mut SQL<'a, V>) {
156        let name = Head::Column::default().name();
157        assert!(
158            !sql.chunks.iter().any(
159                |chunk| matches!(chunk, SQLChunk::Ident(identifier) if identifier.as_ref() == name),
160            ),
161            "an INSERT target column cannot appear more than once",
162        );
163        if !sql.chunks.is_empty() {
164            sql.push_mut(Token::COMMA);
165        }
166        sql.append_mut(SQL::ident(name));
167        Tail::append_columns(sql);
168    }
169}
170
171impl<'a, V, Table, Columns> InsertTargetColumns<'a, V, Table> for Columns
172where
173    V: SQLParam,
174    Columns: IntoSelectTarget,
175    Columns::Marker: InsertTargetMarker<Table>,
176    <Columns::Marker as InsertTargetMarker<Table>>::Columns: InsertTargetColumnList<Table>,
177{
178    type Columns = <Columns::Marker as InsertTargetMarker<Table>>::Columns;
179
180    fn into_target_columns_sql(self) -> SQL<'a, V> {
181        let mut columns = SQL::empty();
182        <<Columns::Marker as InsertTargetMarker<Table>>::Columns as InsertTargetColumnList<
183            Table,
184        >>::append_columns(&mut columns);
185        assert!(
186            !columns.chunks.is_empty(),
187            "an INSERT target list must contain at least one column",
188        );
189        columns.parens()
190    }
191}
192
193/// Proof that an explicit target list contains every database-required column.
194#[doc(hidden)]
195#[diagnostic::on_unimplemented(
196    message = "the INSERT target list omits one or more required columns",
197    label = "add every non-null column without a database default to `.columns(...)`"
198)]
199pub trait IncludesRequired<Required, Proof> {}
200
201impl<Targets, Required, Proof> IncludesRequired<Required, Proof> for Targets where
202    Targets: ListIncludes<Required, Proof>
203{
204}
205
206/// A checked SELECT projection for an explicit INSERT target list.
207#[doc(hidden)]
208pub trait PartialInsertSelectCompatible<'a, V: SQLParam, Targets> {}
209
210impl<'a, V, M, Scope, Used, Targets> PartialInsertSelectCompatible<'a, V, Targets>
211    for Scoped<M, Scope, Used>
212where
213    V: SQLParam,
214    M: PartialInsertSelectCompatible<'a, V, Targets>,
215{
216}
217
218impl<'a, V, Targets, Selected> PartialInsertSelectCompatible<'a, V, Targets>
219    for SelectCols<Selected>
220where
221    V: SQLParam,
222    Selected: SelectedExpressionList,
223    Targets: InsertSelectColumns<'a, V, Selected::Expressions>,
224{
225}
226
227/// A checked SELECT projection for every insertable target column.
228#[diagnostic::on_unimplemented(
229    message = "this query cannot be the source of an INSERT into `{Target}`",
230    label = "the source must be a SELECT of the insertable columns",
231    note = "an UPDATE or DELETE, even with RETURNING, cannot feed an INSERT"
232)]
233#[doc(hidden)]
234pub trait InsertSelectCompatible<'a, V: SQLParam, Target, Row> {}
235
236impl<'a, V, M, Scope, Used, Target, Row> InsertSelectCompatible<'a, V, Target, Row>
237    for Scoped<M, Scope, Used>
238where
239    V: SQLParam,
240    M: InsertSelectCompatible<'a, V, Target, Row>,
241{
242}
243
244impl<'a, V, Target, Row> InsertSelectCompatible<'a, V, Target, Row> for SelectStar
245where
246    V: SQLParam,
247    Target: HasSelectModel + InsertSelectAllColumns,
248    Row: TypeEq<Target::SelectModel>,
249{
250}
251
252impl<'a, V, Target, Row, Selected> InsertSelectCompatible<'a, V, Target, Row>
253    for SelectCols<Selected>
254where
255    V: SQLParam,
256    Target: InsertSelectTable,
257    Selected: SelectedExpressionList,
258    Target::Columns: InsertSelectColumns<'a, V, Selected::Expressions>,
259{
260}
261
262impl<'a, V, Target, Row, Selected> InsertSelectCompatible<'a, V, Target, Row> for SelectAs<Selected>
263where
264    V: SQLParam,
265    Target: HasSelectModel + InsertSelectAllColumns,
266    Row: TypeEq<Target::SelectModel>,
267{
268}