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