drizzle_sqlite/
helpers.rs

1use crate::{
2    common::Join,
3    traits::{SQLiteSQL, SQLiteTable, ToSQLiteSQL},
4};
5use drizzle_core::{
6    SQL, ToSQL, Token, helpers as core_helpers,
7    traits::{SQLColumnInfo, SQLModel},
8};
9
10// Re-export core helpers with SQLiteValue type for convenience
11pub(crate) use core_helpers::{
12    delete, from, group_by, having, insert, limit, offset, order_by, select, set, update, r#where,
13};
14
15/// Helper to convert column info to SQL for joining (column names only for INSERT)
16fn columns_info_to_sql<'a>(columns: &[&'static dyn SQLColumnInfo]) -> SQLiteSQL<'a> {
17    // For INSERT statements, we need just column names, not fully qualified names
18    let joined_names = columns
19        .iter()
20        .map(|col| col.name())
21        .collect::<Vec<_>>()
22        .join(", ");
23    SQL::raw(joined_names)
24}
25
26fn join_internal<'a, Table>(
27    table: Table,
28    join: Join,
29    condition: impl ToSQLiteSQL<'a>,
30) -> SQLiteSQL<'a>
31where
32    Table: SQLiteTable<'a>,
33{
34    join.to_sql()
35        .append(&table)
36        .push(Token::ON)
37        .append(&condition)
38}
39
40/// Helper function to create a JOIN clause using table generic
41pub fn natural_join<'a, Table>(table: Table, condition: impl ToSQLiteSQL<'a>) -> SQLiteSQL<'a>
42where
43    Table: SQLiteTable<'a>,
44{
45    join_internal(table, Join::default().natural(), condition)
46}
47
48/// Helper function to create a JOIN clause using table generic
49pub fn join<'a, Table>(table: Table, condition: impl ToSQLiteSQL<'a>) -> SQLiteSQL<'a>
50where
51    Table: SQLiteTable<'a>,
52{
53    join_internal(table, Join::default(), condition)
54}
55
56pub fn natural_left_join<'a, Table>(table: Table, condition: impl ToSQLiteSQL<'a>) -> SQLiteSQL<'a>
57where
58    Table: SQLiteTable<'a>,
59{
60    join_internal(table, Join::new().natural().left(), condition)
61}
62
63/// Helper function to create a LEFT JOIN clause using table generic
64pub fn left_join<'a, Table>(table: Table, condition: impl ToSQLiteSQL<'a>) -> SQLiteSQL<'a>
65where
66    Table: SQLiteTable<'a>,
67{
68    join_internal(table, Join::new().left(), condition)
69}
70
71pub fn left_outer_join<'a, Table>(table: Table, condition: impl ToSQLiteSQL<'a>) -> SQLiteSQL<'a>
72where
73    Table: SQLiteTable<'a>,
74{
75    join_internal(table, Join::new().left().outer(), condition)
76}
77
78pub fn natural_left_outer_join<'a, Table>(
79    table: Table,
80    condition: impl ToSQLiteSQL<'a>,
81) -> SQLiteSQL<'a>
82where
83    Table: SQLiteTable<'a>,
84{
85    join_internal(table, Join::new().natural().left().outer(), condition)
86}
87
88pub fn natural_right_join<'a, Table>(table: Table, condition: impl ToSQLiteSQL<'a>) -> SQLiteSQL<'a>
89where
90    Table: SQLiteTable<'a>,
91{
92    join_internal(table, Join::new().natural().right(), condition)
93}
94
95/// Helper function to create a RIGHT JOIN clause using table generic
96pub fn right_join<'a, Table>(table: Table, condition: impl ToSQLiteSQL<'a>) -> SQLiteSQL<'a>
97where
98    Table: SQLiteTable<'a>,
99{
100    join_internal(table, Join::new().right(), condition)
101}
102
103pub fn right_outer_join<'a, Table>(table: Table, condition: impl ToSQLiteSQL<'a>) -> SQLiteSQL<'a>
104where
105    Table: SQLiteTable<'a>,
106{
107    join_internal(table, Join::new().right().outer(), condition)
108}
109
110pub fn natural_right_outer_join<'a, Table>(
111    table: Table,
112    condition: impl ToSQLiteSQL<'a>,
113) -> SQLiteSQL<'a>
114where
115    Table: SQLiteTable<'a>,
116{
117    join_internal(table, Join::new().natural().right().outer(), condition)
118}
119
120pub fn natural_full_join<'a, Table>(table: Table, condition: impl ToSQLiteSQL<'a>) -> SQLiteSQL<'a>
121where
122    Table: SQLiteTable<'a>,
123{
124    join_internal(table, Join::new().natural().full(), condition)
125}
126
127/// Helper function to create a FULL JOIN clause using table generic
128pub fn full_join<'a, Table>(table: Table, condition: impl ToSQLiteSQL<'a>) -> SQLiteSQL<'a>
129where
130    Table: SQLiteTable<'a>,
131{
132    join_internal(table, Join::new().full(), condition)
133}
134
135pub fn full_outer_join<'a, Table>(table: Table, condition: impl ToSQLiteSQL<'a>) -> SQLiteSQL<'a>
136where
137    Table: SQLiteTable<'a>,
138{
139    join_internal(table, Join::new().full().outer(), condition)
140}
141
142pub fn natural_full_outer_join<'a, Table>(
143    table: Table,
144    condition: impl ToSQLiteSQL<'a>,
145) -> SQLiteSQL<'a>
146where
147    Table: SQLiteTable<'a>,
148{
149    join_internal(table, Join::new().natural().full().outer(), condition)
150}
151
152pub fn natural_inner_join<'a, Table>(table: Table, condition: impl ToSQLiteSQL<'a>) -> SQLiteSQL<'a>
153where
154    Table: SQLiteTable<'a>,
155{
156    join_internal(table, Join::new().natural().inner(), condition)
157}
158
159/// Helper function to create an INNER JOIN clause using table generic
160pub fn inner_join<'a, Table>(table: Table, condition: impl ToSQLiteSQL<'a>) -> SQLiteSQL<'a>
161where
162    Table: SQLiteTable<'a>,
163{
164    join_internal(table, Join::new().inner(), condition)
165}
166
167/// Helper function to create a CROSS JOIN clause using table generic
168pub fn cross_join<'a, Table>(table: Table, condition: impl ToSQLiteSQL<'a>) -> SQLiteSQL<'a>
169where
170    Table: SQLiteTable<'a>,
171{
172    join_internal(table, Join::new().cross(), condition)
173}
174
175/// Helper function to create VALUES clause for INSERT with pattern validation
176/// All rows must have the same column pattern (enforced by type parameter)
177pub(crate) fn values<'a, Table, T>(
178    rows: impl IntoIterator<Item = Table::Insert<T>>,
179) -> SQLiteSQL<'a>
180where
181    Table: SQLiteTable<'a> + Default,
182{
183    let rows: Vec<_> = rows.into_iter().collect();
184
185    if rows.is_empty() {
186        return SQL::from(Token::VALUES);
187    }
188
189    // Since all rows have the same PATTERN, they all have the same columns
190    // Get column info from the first row (all rows will have the same columns)
191    let columns_info = rows[0].columns();
192
193    // Check if this is a DEFAULT VALUES case (no columns)
194    if columns_info.is_empty() {
195        return SQL::from_iter([Token::DEFAULT, Token::VALUES]);
196    }
197
198    let columns_sql = columns_info_to_sql(&columns_info);
199    let value_clauses = rows.iter().map(|row| {
200        SQL::from(Token::LPAREN)
201            .append(row.values())
202            .push(Token::RPAREN)
203    });
204
205    columns_sql
206        .parens()
207        .push(Token::VALUES)
208        .append(SQL::join(value_clauses, Token::COMMA))
209}
210
211/// Helper function to create a RETURNING clause - SQLite specific
212pub(crate) fn returning<'a, 'b, I>(columns: I) -> SQLiteSQL<'a>
213where
214    I: ToSQLiteSQL<'a>,
215{
216    SQL::from(Token::RETURNING).append(&columns)
217}