Skip to main content

drizzle_sqlite/
helpers.rs

1#[cfg(not(feature = "std"))]
2use crate::prelude::*;
3use crate::traits::SQLiteTable;
4use crate::values::SQLiteValue;
5use drizzle_core::{
6    SQL, SQLChunk, Token, helpers as core_helpers,
7    traits::{SQLModel, ToSQL},
8};
9
10// Re-export core helpers with SQLiteValue type for convenience
11pub(crate) use core_helpers::{
12    delete, except, from, group_by_expr, having, insert, intersect, limit, offset, order_by,
13    select, select_distinct, set, union, union_all, update, r#where,
14};
15
16// Re-export Join from core
17pub use drizzle_core::Join;
18
19/// A table-like source accepted by an explicit JOIN tuple.
20#[doc(hidden)]
21pub trait JoinSource<'a>: join_source_private::Sealed {
22    type JoinedTable;
23
24    fn into_join_source_sql(self) -> SQL<'a, SQLiteValue<'a>>;
25}
26
27mod join_source_private {
28    pub trait Sealed {}
29}
30
31impl<'a, Table> join_source_private::Sealed for Table where Table: SQLiteTable<'a> {}
32
33impl<'a, Name, Projection, Query> join_source_private::Sealed
34    for drizzle_core::Derived<'a, SQLiteValue<'a>, Name, Projection, Query>
35where
36    Name: drizzle_core::Tag,
37    Projection: drizzle_core::DerivedProjection<Name>,
38    Query: ToSQL<'a, SQLiteValue<'a>>,
39{
40}
41
42impl<'a, Table> JoinSource<'a> for Table
43where
44    Table: SQLiteTable<'a>,
45{
46    type JoinedTable = Table;
47
48    fn into_join_source_sql(self) -> SQL<'a, SQLiteValue<'a>> {
49        self.into_sql()
50    }
51}
52
53impl<'a, Name, Projection, Query> JoinSource<'a>
54    for drizzle_core::Derived<'a, SQLiteValue<'a>, Name, Projection, Query>
55where
56    Name: drizzle_core::Tag,
57    Projection: drizzle_core::DerivedProjection<Name>,
58    Query: ToSQL<'a, SQLiteValue<'a>>,
59{
60    type JoinedTable = Self;
61
62    fn into_join_source_sql(self) -> SQL<'a, SQLiteValue<'a>> {
63        self.into_sql()
64    }
65}
66
67/// A source or legacy tuple accepted by [`crate::builder::SelectBuilder::cross_join`].
68///
69/// A bare source renders `CROSS JOIN`. The legacy `(source, predicate)`
70/// form renders the equivalent portable `INNER JOIN ... ON ...`, because
71/// PostgreSQL does not allow an `ON` clause after `CROSS JOIN`.
72#[doc(hidden)]
73pub trait CrossJoinArg<'a, FromTable>: cross_join_arg_private::Sealed {
74    type JoinedTable;
75
76    fn into_cross_join_sql(self) -> SQL<'a, SQLiteValue<'a>>;
77}
78
79mod cross_join_arg_private {
80    pub trait Sealed {}
81
82    impl<'a, Source> Sealed for Source where Source: super::JoinSource<'a> {}
83
84    impl<'a, Source, Condition> Sealed for (Source, Condition)
85    where
86        Source: super::JoinSource<'a>,
87        Condition: drizzle_core::ToSQL<'a, crate::values::SQLiteValue<'a>>,
88    {
89    }
90}
91
92impl<'a, Source, FromTable> CrossJoinArg<'a, FromTable> for Source
93where
94    Source: JoinSource<'a>,
95{
96    type JoinedTable = Source::JoinedTable;
97
98    fn into_cross_join_sql(self) -> SQL<'a, SQLiteValue<'a>> {
99        Join::new()
100            .cross()
101            .into_sql()
102            .append(self.into_join_source_sql())
103    }
104}
105
106impl<'a, Source, Condition, FromTable> CrossJoinArg<'a, FromTable> for (Source, Condition)
107where
108    Source: JoinSource<'a>,
109    Condition: ToSQL<'a, SQLiteValue<'a>>,
110{
111    type JoinedTable = Source::JoinedTable;
112
113    fn into_cross_join_sql(self) -> SQL<'a, SQLiteValue<'a>> {
114        let (source, condition) = self;
115        Join::new()
116            .inner()
117            .into_sql()
118            .append(source.into_join_source_sql())
119            .push(Token::ON)
120            .append(condition.into_sql())
121    }
122}
123
124drizzle_core::impl_join_arg_trait!(
125    table_trait: SQLiteTable<'a>,
126    table_info_trait: drizzle_core::SQLTableInfo,
127    condition_trait: ToSQL<'a, SQLiteValue<'a>>,
128    join_source_trait: JoinSource<'a>,
129    value_type: SQLiteValue<'a>,
130);
131
132// Generate all join helper functions using the shared macro
133drizzle_core::impl_join_helpers!(
134    table_trait: SQLiteTable<'a>,
135    condition_trait: ToSQL<'a, SQLiteValue<'a>>,
136    sql_type: SQL<'a, SQLiteValue<'a>>,
137);
138
139/// Creates a VALUES clause for INSERT statements.
140/// All rows must declare the same set of columns.
141pub(crate) fn values<'a, Table, T>(
142    rows: impl IntoIterator<Item = Table::Insert<T>>,
143) -> SQL<'a, SQLiteValue<'a>>
144where
145    Table: SQLiteTable<'a> + Default,
146{
147    let rows: Vec<Table::Insert<T>> = rows.into_iter().collect();
148
149    if rows.is_empty() {
150        return SQL::from(Token::VALUES);
151    }
152
153    // Since all rows have the same PATTERN, they all have the same columns
154    // Get column info from the first row (all rows will have the same columns)
155    let columns_info = rows[0].columns();
156    let columns_slice = columns_info.as_ref();
157
158    // Every column takes its default. `DEFAULT VALUES` inserts one row, and
159    // SQLite has no `DEFAULT` keyword inside VALUES, so several such rows
160    // insert NULL into `rowid`, which assigns the next rowid and leaves every
161    // declared column to its default. (A WITHOUT ROWID table rejects this
162    // with "no column named rowid" instead of inserting a single row.)
163    if columns_slice.is_empty() {
164        if rows.len() == 1 {
165            return SQL::from_iter([Token::DEFAULT, Token::VALUES]);
166        }
167        let mut values_sql = SQL::with_capacity_chunks(rows.len().saturating_mul(4));
168        for index in 0..rows.len() {
169            if index > 0 {
170                values_sql.push_mut(Token::COMMA);
171            }
172            values_sql.append_mut(SQL::from(Token::NULL).parens());
173        }
174        return SQL::raw("rowid")
175            .parens()
176            .push(Token::VALUES)
177            .append(values_sql);
178    }
179
180    let columns_sql = SQL::columns(columns_slice);
181    let mut values_sql = SQL::with_capacity_chunks(rows.len().saturating_mul(4));
182    for (idx, row) in rows.iter().enumerate() {
183        if idx > 0 {
184            values_sql.push_mut(Token::COMMA);
185        }
186        values_sql.push_mut(Token::LPAREN);
187        values_sql.append_mut(row.values());
188        values_sql.push_mut(Token::RPAREN);
189    }
190
191    columns_sql.parens().push(Token::VALUES).append(values_sql)
192}
193
194/// An `OFFSET` for a query without a `LIMIT`.
195///
196/// `SQLite` only accepts `OFFSET` as part of a `LIMIT` clause; a negative
197/// limit means "no limit".
198#[track_caller]
199pub(crate) fn standalone_offset<'a, P>(offset: P) -> SQL<'a, SQLiteValue<'a>>
200where
201    P: drizzle_core::PaginationArg<'a, SQLiteValue<'a>>,
202{
203    SQL::from(Token::LIMIT)
204        .append(SQL::raw("-1"))
205        .append(core_helpers::offset(offset))
206}
207
208/// Ends an `INSERT ... SELECT` so an upsert clause can follow it.
209///
210/// When the final `SELECT` ends in its `FROM` clause, SQLite parses the `ON`
211/// of `ON CONFLICT` as a join constraint and rejects the statement. A
212/// trailing `WHERE true` closes the `SELECT`, as SQLite's documentation
213/// recommends. Inserts from VALUES, and `SELECT`s that already end in a
214/// `WHERE`, `GROUP BY`, `HAVING`, `WINDOW`, `ORDER BY` or `LIMIT`, are
215/// returned unchanged.
216pub(crate) fn before_upsert<'a>(sql: SQL<'a, SQLiteValue<'a>>) -> SQL<'a, SQLiteValue<'a>> {
217    let mut depth = 0usize;
218    let mut ends_in_from = false;
219    for chunk in &sql.chunks {
220        match chunk {
221            SQLChunk::Token(Token::LPAREN) => depth += 1,
222            SQLChunk::Token(Token::RPAREN) => depth = depth.saturating_sub(1),
223            SQLChunk::Token(Token::SELECT) if depth == 0 => ends_in_from = false,
224            SQLChunk::Token(Token::FROM) if depth == 0 => ends_in_from = true,
225            SQLChunk::Token(
226                Token::WHERE
227                | Token::GROUP
228                | Token::HAVING
229                | Token::WINDOW
230                | Token::ORDER
231                | Token::LIMIT,
232            ) if depth == 0 => ends_in_from = false,
233            _ => {}
234        }
235    }
236    if ends_in_from {
237        sql.push(Token::WHERE).append(SQL::raw("true"))
238    } else {
239        sql
240    }
241}
242
243/// Helper function to create a RETURNING clause - `SQLite` specific
244pub(crate) fn returning<'a, 'b, I>(columns: I) -> SQL<'a, SQLiteValue<'a>>
245where
246    I: ToSQL<'a, SQLiteValue<'a>>,
247{
248    let columns = columns.into_sql();
249    let columns = if columns.chunks.is_empty() {
250        SQL::from(Token::STAR)
251    } else {
252        columns
253    };
254    SQL::from(Token::RETURNING).append(columns)
255}