Skip to main content

drizzle_sqlite/
helpers.rs

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