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