1#[cfg(not(feature = "std"))]
9use crate::prelude::*;
10use crate::{traits::MySQLTable, values::MySQLValue};
11use drizzle_core::{
12 SQL, SQLChunk, SQLIndex, SQLIndexInfo, SQLTableInfo, ToSQL, Token, helpers, traits::SQLModel,
13};
14
15pub use drizzle_core::Join;
16pub(crate) use helpers::{
17 delete, from, group_by_expr, having, limit, order_by, select, select_distinct, set, update,
18 r#where,
19};
20
21#[doc(hidden)]
23pub trait JoinCondition<'a>:
24 join_condition_private::Sealed<'a> + ToSQL<'a, MySQLValue<'a>>
25{
26}
27
28mod join_condition_private {
29 pub trait Sealed<'a> {}
30
31 impl<'a, T> Sealed<'a> for T
32 where
33 T: drizzle_core::expr::Expr<'a, crate::values::MySQLValue<'a>>,
34 T::SQLType: drizzle_core::types::BooleanLike,
35 {
36 }
37}
38
39impl<'a, T> JoinCondition<'a> for T
40where
41 T: drizzle_core::expr::Expr<'a, MySQLValue<'a>>,
42 T::SQLType: drizzle_core::types::BooleanLike,
43{
44}
45
46#[doc(hidden)]
49#[diagnostic::on_unimplemented(
50 message = "`{Self}` cannot follow JOIN",
51 label = "join a table, a view, or an aliased subquery"
52)]
53pub trait JoinSource<'a>: join_source_private::Sealed {
54 type JoinedTable;
55
56 fn into_join_source_sql(self) -> SQL<'a, MySQLValue<'a>>;
57}
58
59mod join_source_private {
60 pub trait Sealed {}
61}
62
63impl<'a, Table> join_source_private::Sealed for Table where Table: MySQLTable<'a> {}
64
65impl<'a, Name, Projection, Query> join_source_private::Sealed
66 for drizzle_core::Derived<'a, MySQLValue<'a>, Name, Projection, Query>
67where
68 Name: drizzle_core::Tag,
69 Projection: drizzle_core::DerivedProjection<Name>,
70 Query: ToSQL<'a, MySQLValue<'a>>,
71{
72}
73
74impl<'a, Table> JoinSource<'a> for Table
75where
76 Table: MySQLTable<'a>,
77{
78 type JoinedTable = Table;
79
80 fn into_join_source_sql(self) -> SQL<'a, MySQLValue<'a>> {
81 self.into_sql()
82 }
83}
84
85impl<'a, Name, Projection, Query> JoinSource<'a>
86 for drizzle_core::Derived<'a, MySQLValue<'a>, Name, Projection, Query>
87where
88 Name: drizzle_core::Tag,
89 Projection: drizzle_core::DerivedProjection<Name>,
90 Query: ToSQL<'a, MySQLValue<'a>>,
91{
92 type JoinedTable = Self;
93
94 fn into_join_source_sql(self) -> SQL<'a, MySQLValue<'a>> {
95 self.into_sql()
96 }
97}
98
99#[doc(hidden)]
105pub trait CrossJoinArg<'a, FromTable>: cross_join_arg_private::Sealed {
106 type JoinedTable;
107 type OnSources;
109
110 fn into_cross_join_sql(self) -> SQL<'a, MySQLValue<'a>>;
111}
112
113mod cross_join_arg_private {
114 pub trait Sealed {}
115
116 impl<'a, Source> Sealed for Source where Source: super::JoinSource<'a> {}
117
118 impl<'a, Source, Condition> Sealed for (Source, Condition)
119 where
120 Source: super::JoinSource<'a>,
121 Condition: super::JoinCondition<'a>,
122 {
123 }
124}
125
126impl<'a, Source, FromTable> CrossJoinArg<'a, FromTable> for Source
127where
128 Source: JoinSource<'a>,
129{
130 type JoinedTable = Source::JoinedTable;
131 type OnSources = ();
132
133 fn into_cross_join_sql(self) -> SQL<'a, MySQLValue<'a>> {
134 Join::new()
135 .cross()
136 .into_sql()
137 .append(self.into_join_source_sql())
138 }
139}
140
141impl<'a, Source, Condition, FromTable> CrossJoinArg<'a, FromTable> for (Source, Condition)
142where
143 Source: JoinSource<'a>,
144 Condition: JoinCondition<'a> + drizzle_core::expr::ExprSources,
145{
146 type JoinedTable = Source::JoinedTable;
147 type OnSources = Condition::Sources;
148
149 fn into_cross_join_sql(self) -> SQL<'a, MySQLValue<'a>> {
150 let (source, condition) = self;
151 Join::new()
152 .inner()
153 .into_sql()
154 .append(source.into_join_source_sql())
155 .push(Token::ON)
156 .append(condition.into_sql())
157 }
158}
159
160drizzle_core::impl_join_arg_trait!(
161 table_trait: MySQLTable<'a>,
162 table_info_trait: SQLTableInfo,
163 condition_trait: JoinCondition<'a>,
164 join_source_trait: JoinSource<'a>,
165 value_type: MySQLValue<'a>,
166);
167
168mod index_hint_private {
169 pub trait Kind {}
170 pub trait List<'a, Table> {}
171}
172
173#[doc(hidden)]
175#[derive(Debug, Clone, Copy, Default)]
176pub struct UseIndex;
177
178#[doc(hidden)]
180#[derive(Debug, Clone, Copy, Default)]
181pub struct ForceIndex;
182
183#[doc(hidden)]
185#[derive(Debug, Clone, Copy, Default)]
186pub struct IgnoreIndex;
187
188impl index_hint_private::Kind for UseIndex {}
189impl index_hint_private::Kind for ForceIndex {}
190impl index_hint_private::Kind for IgnoreIndex {}
191
192#[doc(hidden)]
193pub trait IndexHintKind: index_hint_private::Kind {
194 const SQL: &'static str;
195}
196
197impl IndexHintKind for UseIndex {
198 const SQL: &'static str = "USE INDEX ";
199}
200
201impl IndexHintKind for ForceIndex {
202 const SQL: &'static str = "FORCE INDEX ";
203}
204
205impl IndexHintKind for IgnoreIndex {
206 const SQL: &'static str = "IGNORE INDEX ";
207}
208
209#[diagnostic::on_unimplemented(
211 message = "`{Self}` is not a list of `{Table}` indexes",
212 label = "an index hint names one or more indexes of the hinted table",
213 note = "pass one generated index of `{Table}`, or a tuple of them"
214)]
215#[doc(hidden)]
216pub trait IndexHintList<'a, Table>: index_hint_private::List<'a, Table> {
217 fn names(&self) -> SQL<'a, MySQLValue<'a>>;
218}
219
220impl<'a, Table, Index> index_hint_private::List<'a, Table> for Index where
221 Index: SQLIndex<'a, crate::common::MySQLSchemaType, MySQLValue<'a>, Table = Table>
222{
223}
224
225impl<'a, Table, Index> IndexHintList<'a, Table> for Index
226where
227 Index: SQLIndex<'a, crate::common::MySQLSchemaType, MySQLValue<'a>, Table = Table>,
228{
229 fn names(&self) -> SQL<'a, MySQLValue<'a>> {
230 SQL::ident(SQLIndexInfo::name(self))
231 }
232}
233
234macro_rules! index_hint_tuple {
235 ($($index:ident: $field:tt),+) => {
236 impl<'a, Table, $($index),+> index_hint_private::List<'a, Table> for ($($index,)+)
237 where
238 $($index: SQLIndex<'a, crate::common::MySQLSchemaType, MySQLValue<'a>, Table = Table>,)+
239 {
240 }
241
242 impl<'a, Table, $($index),+> IndexHintList<'a, Table> for ($($index,)+)
243 where
244 $($index: SQLIndex<'a, crate::common::MySQLSchemaType, MySQLValue<'a>, Table = Table>,)+
245 {
246 fn names(&self) -> SQL<'a, MySQLValue<'a>> {
247 SQL::join(
248 [$(SQL::ident(SQLIndexInfo::name(&self.$field)),)+],
249 Token::COMMA,
250 )
251 }
252 }
253 };
254}
255
256index_hint_tuple!(A: 0, B: 1);
257index_hint_tuple!(A: 0, B: 1, C: 2);
258index_hint_tuple!(A: 0, B: 1, C: 2, D: 3);
259index_hint_tuple!(A: 0, B: 1, C: 2, D: 3, E: 4);
260index_hint_tuple!(A: 0, B: 1, C: 2, D: 3, E: 4, F: 5);
261index_hint_tuple!(A: 0, B: 1, C: 2, D: 3, E: 4, F: 5, G: 6);
262index_hint_tuple!(A: 0, B: 1, C: 2, D: 3, E: 4, F: 5, G: 6, H: 7);
263
264#[derive(Debug, Clone, Copy)]
268pub struct IndexHintedTable<Table, Indexes, Kind> {
269 table: Table,
270 indexes: Indexes,
271 kind: core::marker::PhantomData<Kind>,
272}
273
274impl<Table, Indexes, Kind> join_source_private::Sealed for IndexHintedTable<Table, Indexes, Kind> {}
275
276impl<Table, Indexes, Kind> IndexHintedTable<Table, Indexes, Kind> {
277 fn into_sql<'a>(self) -> SQL<'a, MySQLValue<'a>>
278 where
279 Table: MySQLTable<'a>,
280 Indexes: IndexHintList<'a, Table>,
281 Kind: IndexHintKind,
282 {
283 self.table
284 .into_sql()
285 .append(SQL::raw(Kind::SQL))
286 .append(self.indexes.names().parens())
287 }
288}
289
290impl<'a, Table, Indexes, Kind> JoinSource<'a> for IndexHintedTable<Table, Indexes, Kind>
291where
292 Table: MySQLTable<'a>,
293 Indexes: IndexHintList<'a, Table>,
294 Kind: IndexHintKind,
295{
296 type JoinedTable = Table;
297
298 fn into_join_source_sql(self) -> SQL<'a, MySQLValue<'a>> {
299 self.into_sql()
300 }
301}
302
303pub trait MySQLIndexHintExt: Sized {
344 fn use_index<Indexes>(self, indexes: Indexes) -> IndexHintedTable<Self, Indexes, UseIndex>
346 where
347 Indexes: for<'a> IndexHintList<'a, Self>,
348 {
349 IndexHintedTable {
350 table: self,
351 indexes,
352 kind: core::marker::PhantomData,
353 }
354 }
355
356 fn force_index<Indexes>(self, indexes: Indexes) -> IndexHintedTable<Self, Indexes, ForceIndex>
359 where
360 Indexes: for<'a> IndexHintList<'a, Self>,
361 {
362 IndexHintedTable {
363 table: self,
364 indexes,
365 kind: core::marker::PhantomData,
366 }
367 }
368
369 fn ignore_index<Indexes>(self, indexes: Indexes) -> IndexHintedTable<Self, Indexes, IgnoreIndex>
371 where
372 Indexes: for<'a> IndexHintList<'a, Self>,
373 {
374 IndexHintedTable {
375 table: self,
376 indexes,
377 kind: core::marker::PhantomData,
378 }
379 }
380}
381
382impl<Table> MySQLIndexHintExt for Table where Table: for<'a> MySQLTable<'a> {}
383
384pub(crate) fn index_hint<'a, Table, Indexes, Kind>(indexes: &Indexes) -> SQL<'a, MySQLValue<'a>>
385where
386 Table: MySQLTable<'a>,
387 Indexes: IndexHintList<'a, Table>,
388 Kind: IndexHintKind,
389{
390 SQL::raw(Kind::SQL).append(indexes.names().parens())
391}
392
393fn auto_join_condition<'a, Joined, From, Via>() -> SQL<'a, MySQLValue<'a>>
394where
395 Joined: MySQLTable<'a> + drizzle_core::JoinKey<From, Via> + Default,
396 From: SQLTableInfo + Default,
397{
398 let joined = Joined::default();
399 let from = From::default();
400 let columns = <Joined as drizzle_core::JoinKey<From, Via>>::pairs();
401 let mut condition = SQL::with_capacity_chunks(columns.len().saturating_mul(7));
402 for (index, pair) in columns.iter().enumerate() {
403 let (joined_column, from_column) = <Joined as drizzle_core::JoinKey<From, Via>>::pair(pair);
404 if index > 0 {
405 condition.push_mut(Token::AND);
406 }
407 condition.append_mut(
408 SQL::ident(joined.name())
409 .push(Token::DOT)
410 .append(SQL::ident(joined_column)),
411 );
412 condition.push_mut(Token::EQ);
413 condition.append_mut(
414 SQL::ident(from.name())
415 .push(Token::DOT)
416 .append(SQL::ident(from_column)),
417 );
418 }
419 condition
420}
421
422impl<'a, Joined, Indexes, Kind, From, Via> JoinArg<'a, From, Via>
423 for IndexHintedTable<Joined, Indexes, Kind>
424where
425 Joined: MySQLTable<'a> + drizzle_core::JoinKey<From, Via> + Default,
426 From: SQLTableInfo + Default,
427 Indexes: IndexHintList<'a, Joined>,
428 Kind: IndexHintKind,
429{
430 type JoinedTable = Joined;
431 type OnSources = ();
432
433 fn into_join_sql(self, join: Join) -> SQL<'a, MySQLValue<'a>> {
434 join.into_sql()
435 .append(self.into_sql())
436 .push(Token::ON)
437 .append(auto_join_condition::<Joined, From, Via>())
438 }
439}
440
441pub(crate) fn insert_ignore<'a>(mut insert: SQL<'a, MySQLValue<'a>>) -> SQL<'a, MySQLValue<'a>> {
442 debug_assert!(matches!(
443 insert.chunks.first(),
444 Some(SQLChunk::Token(Token::INSERT))
445 ));
446 insert.chunks.insert(1, SQLChunk::Token(Token::IGNORE));
447 insert
448}
449
450pub(crate) fn on_duplicate_key_update<'a, Table>(
451 assignments: &Table::Update,
452) -> SQL<'a, MySQLValue<'a>>
453where
454 Table: MySQLTable<'a>,
455{
456 let assignment_sql = assignments.to_sql();
457 assert!(
458 !assignment_sql.chunks.is_empty(),
459 "on_duplicate_key_update requires at least one assignment"
460 );
461 SQL::from_iter([Token::ON, Token::DUPLICATE, Token::KEY, Token::UPDATE]).append(assignment_sql)
462}
463
464#[track_caller]
465pub(crate) fn values<'a, Table, T>(
466 rows: impl IntoIterator<Item = Table::Insert<T>>,
467) -> SQL<'a, MySQLValue<'a>>
468where
469 Table: MySQLTable<'a>,
470{
471 let rows: Vec<_> = rows.into_iter().collect();
472 if rows.is_empty() {
473 return match <Table as drizzle_core::SQLSchema<
474 'a,
475 crate::common::MySQLSchemaType,
476 MySQLValue<'a>,
477 >>::TYPE
478 {
479 crate::common::MySQLSchemaType::Table(table) => {
480 drizzle_core::helpers::insert_no_rows(table, "SELECT NULL FROM DUAL WHERE 1 = 0")
481 }
482 _ => SQL::from(Token::VALUES),
483 };
484 }
485
486 let columns = rows[0].columns();
487 if rows[1..]
492 .iter()
493 .any(|row| row.columns().as_ref() != columns.as_ref())
494 && let Some(rows_sql) = drizzle_core::helpers::insert_values_with_defaults(
495 rows.iter()
496 .map(|row| (row.columns(), row.values()))
497 .collect(),
498 )
499 {
500 return rows_sql;
501 }
502
503 if columns.is_empty() {
504 let mut row_sql = SQL::with_capacity_chunks(rows.len().saturating_mul(3));
505 for index in 0..rows.len() {
506 if index > 0 {
507 row_sql.push_mut(Token::COMMA);
508 }
509 row_sql.append_mut(SQL::empty().parens());
510 }
511 return SQL::empty().parens().push(Token::VALUES).append(row_sql);
512 }
513
514 let mut row_sql = SQL::with_capacity_chunks(rows.len().saturating_mul(4));
515 for (index, row) in rows.iter().enumerate() {
516 if index > 0 {
517 row_sql.push_mut(Token::COMMA);
518 }
519 row_sql.push_mut(Token::LPAREN);
520 row_sql.append_mut(row.values());
521 row_sql.push_mut(Token::RPAREN);
522 }
523
524 SQL::columns(columns.as_ref())
525 .parens()
526 .push(Token::VALUES)
527 .append(row_sql)
528}
529
530pub(crate) fn standalone_offset<'a, P>(offset: P) -> SQL<'a, MySQLValue<'a>>
531where
532 P: drizzle_core::PaginationArg<'a, MySQLValue<'a>>,
533{
534 SQL::from(Token::LIMIT)
535 .append(SQL::raw(drizzle_core::helpers::MYSQL_UNBOUNDED_LIMIT))
536 .append(helpers::offset(offset))
537}
538
539pub(crate) fn unqualified_columns<'a>(
540 mut columns: SQL<'a, MySQLValue<'a>>,
541) -> SQL<'a, MySQLValue<'a>> {
542 for chunk in &mut columns.chunks {
543 if let SQLChunk::Column(column) = chunk {
544 *chunk = SQLChunk::ident_static(column.name);
545 }
546 }
547 columns
548}
549
550#[derive(Debug, Clone, Copy)]
555pub struct OrderExpr<T> {
556 value: T,
557 direction: drizzle_core::OrderBy,
558}
559
560impl<T: drizzle_core::expr::ExprSources> drizzle_core::expr::ExprSources for OrderExpr<T> {
561 type Sources = T::Sources;
562}
563
564impl<T: drizzle_core::expr::ExprSources> drizzle_core::OrderTerm for OrderExpr<T> {}
565
566impl drizzle_core::expr::ExprSources for OutputAlias {
568 type Sources = ();
569}
570
571impl<'a, T> ToSQL<'a, MySQLValue<'a>> for OrderExpr<T>
572where
573 T: ToSQL<'a, MySQLValue<'a>>,
574{
575 fn to_sql(&self) -> SQL<'a, MySQLValue<'a>> {
576 self.value.to_sql().append(&self.direction)
577 }
578}
579
580pub const fn asc<T>(value: T) -> OrderExpr<T> {
582 OrderExpr {
583 value,
584 direction: drizzle_core::OrderBy::Asc,
585 }
586}
587
588pub const fn desc<T>(value: T) -> OrderExpr<T> {
590 OrderExpr {
591 value,
592 direction: drizzle_core::OrderBy::Desc,
593 }
594}
595
596#[derive(Debug, Clone, Copy)]
600pub struct OutputAlias(&'static str);
601
602impl<'a> ToSQL<'a, MySQLValue<'a>> for OutputAlias {
603 fn to_sql(&self) -> SQL<'a, MySQLValue<'a>> {
604 SQL::ident(self.0)
605 }
606}
607
608#[must_use]
629pub const fn output_alias(name: &'static str) -> OutputAlias {
630 OutputAlias(name)
631}
632
633#[doc(hidden)]
634pub trait SetOrderBy<'a, Projection, Table, Proof>:
635 set_order_private::Sealed<'a, Projection, Table, Proof>
636{
637 fn into_set_order_sql(self) -> SQL<'a, MySQLValue<'a>>;
638}
639
640mod set_order_private {
641 use super::{MySQLValue, OrderExpr, OutputAlias, ToSQL};
642
643 pub trait ProjectionAllows<'a, Item, Table, Proof> {}
644
645 impl<'a, Cols, Scope, Used, Item, Table, Proof> ProjectionAllows<'a, Item, Table, Proof>
646 for drizzle_core::Scoped<drizzle_core::SelectCols<Cols>, Scope, Used>
647 where
648 Cols: drizzle_core::row::SelectedExpressionList,
649 <Cols as drizzle_core::row::SelectedExpressionList>::Expressions:
650 drizzle_core::scope::ListContains<Item, Proof>,
651 {
652 }
653
654 impl<'a, Scope, Used, Item, Table> ProjectionAllows<'a, Item, Table, ()>
655 for drizzle_core::Scoped<drizzle_core::SelectStar, Scope, Used>
656 where
657 Item: drizzle_core::traits::SQLColumn<'a, MySQLValue<'a>>
658 + drizzle_core::traits::ColumnOf<Table>,
659 {
660 }
661
662 pub trait ProjectionListAllowed<'a, Projection, Table, Proof> {}
663
664 impl<'a, Projection, Table> ProjectionListAllowed<'a, Projection, Table, ()> for drizzle_core::Nil {}
665
666 impl<'a, Projection, Table, Head, Tail, HeadProof, TailProof>
667 ProjectionListAllowed<'a, Projection, Table, (HeadProof, TailProof)>
668 for drizzle_core::Cons<Head, Tail>
669 where
670 Head: drizzle_core::traits::SQLColumn<'a, MySQLValue<'a>>,
671 Projection: ProjectionAllows<'a, Head, Table, HeadProof>,
672 Tail: ProjectionListAllowed<'a, Projection, Table, TailProof>,
673 {
674 }
675
676 pub trait Sealed<'a, Projection, Table, Proof> {}
677
678 impl<'a, Projection, Table, Columns, Cols, Proof> Sealed<'a, Projection, Table, Proof> for Columns
679 where
680 Columns: ToSQL<'a, MySQLValue<'a>>
681 + drizzle_core::IntoSelectTarget<Marker = drizzle_core::SelectCols<Cols>>,
682 Cols: drizzle_core::row::SelectedExpressionList,
683 <Cols as drizzle_core::row::SelectedExpressionList>::Expressions:
684 ProjectionListAllowed<'a, Projection, Table, Proof>,
685 {
686 }
687
688 impl<'a, Projection, Table, Column, Proof> Sealed<'a, Projection, Table, Proof>
689 for OrderExpr<Column>
690 where
691 Column: drizzle_core::traits::SQLColumn<'a, MySQLValue<'a>>,
692 Projection: ProjectionAllows<'a, Column, Table, Proof>,
693 {
694 }
695
696 impl<'a, Projection, Table> Sealed<'a, Projection, Table, ()> for OutputAlias {}
697 impl<'a, Projection, Table> Sealed<'a, Projection, Table, ()> for OrderExpr<OutputAlias> {}
698}
699
700impl<'a, Projection, Table, Columns, Cols, Proof> SetOrderBy<'a, Projection, Table, Proof>
701 for Columns
702where
703 Columns: ToSQL<'a, MySQLValue<'a>>
704 + drizzle_core::IntoSelectTarget<Marker = drizzle_core::SelectCols<Cols>>,
705 Cols: drizzle_core::row::SelectedExpressionList,
706 <Cols as drizzle_core::row::SelectedExpressionList>::Expressions:
707 set_order_private::ProjectionListAllowed<'a, Projection, Table, Proof>,
708{
709 fn into_set_order_sql(self) -> SQL<'a, MySQLValue<'a>> {
710 unqualified_columns(self.into_sql())
711 }
712}
713
714impl<'a, Projection, Table, Column, Proof> SetOrderBy<'a, Projection, Table, Proof>
715 for OrderExpr<Column>
716where
717 Column: drizzle_core::traits::SQLColumn<'a, MySQLValue<'a>>,
718 Projection: set_order_private::ProjectionAllows<'a, Column, Table, Proof>,
719{
720 fn into_set_order_sql(self) -> SQL<'a, MySQLValue<'a>> {
721 unqualified_columns(self.value.into_sql()).append(&self.direction)
722 }
723}
724
725impl<'a, Projection, Table> SetOrderBy<'a, Projection, Table, ()> for OutputAlias {
726 fn into_set_order_sql(self) -> SQL<'a, MySQLValue<'a>> {
727 self.to_sql()
728 }
729}
730
731impl<'a, Projection, Table> SetOrderBy<'a, Projection, Table, ()> for OrderExpr<OutputAlias> {
732 fn into_set_order_sql(self) -> SQL<'a, MySQLValue<'a>> {
733 self.value.to_sql().append(&self.direction)
734 }
735}
736
737pub(crate) fn set_op<'a>(
738 left: SQL<'a, MySQLValue<'a>>,
739 operator: Token,
740 all: bool,
741 right: SQL<'a, MySQLValue<'a>>,
742) -> SQL<'a, MySQLValue<'a>> {
743 let sql = left.parens().push(operator);
744 let sql = if all { sql.push(Token::ALL) } else { sql };
745 sql.append(right.parens())
746}