1mod chunk;
2mod comment;
3mod cte;
4mod owned;
5mod tokens;
6
7use crate::prelude::*;
8use crate::{
9 param::{Param, ParamBind},
10 placeholder::Placeholder,
11 traits::{SQLParam, ToSQL},
12};
13pub use chunk::*;
14pub use comment::{comment, comment_tags};
15use core::fmt::{Display, Write};
16pub use owned::*;
17use smallvec::SmallVec;
18pub use tokens::*;
19
20#[cfg(feature = "profiling")]
21use crate::profile_sql;
22
23#[derive(Debug, Clone)]
62pub struct SQL<'a, V: SQLParam> {
63 pub chunks: SmallVec<[SQLChunk<'a, V>; 8]>,
65}
66
67impl<'a, V: SQLParam> SQL<'a, V> {
68 const POSITIONAL_PLACEHOLDER: Placeholder = Placeholder::anonymous();
69
70 #[inline]
74 #[must_use]
75 pub const fn empty() -> Self {
76 Self {
77 chunks: SmallVec::new_const(),
78 }
79 }
80
81 #[inline]
83 #[must_use]
84 pub fn token(t: Token) -> Self {
85 Self {
86 chunks: smallvec::smallvec![SQLChunk::Token(t)],
87 }
88 }
89
90 #[inline]
92 #[must_use]
93 pub fn with_capacity_chunks(capacity: usize) -> Self {
94 Self {
95 chunks: SmallVec::with_capacity(capacity),
96 }
97 }
98
99 #[inline]
123 pub fn ident(name: impl Into<Cow<'a, str>>) -> Self {
124 Self {
125 chunks: smallvec::smallvec![SQLChunk::Ident(name.into())],
126 }
127 }
128
129 #[must_use]
132 pub fn columns(columns: &[ColumnRef]) -> Self {
133 let mut sql = Self::with_capacity_chunks(columns.len().saturating_mul(2));
134 for (index, column) in columns.iter().enumerate() {
135 if index > 0 {
136 sql.push_mut(Token::COMMA);
137 }
138 sql.append_mut(Self::ident(column.name));
139 }
140 sql
141 }
142
143 #[inline]
167 pub fn raw(text: impl Into<Cow<'a, str>>) -> Self {
168 Self {
169 chunks: smallvec::smallvec![SQLChunk::Raw(text.into())],
170 }
171 }
172
173 #[inline]
176 #[must_use]
177 pub fn number(value: usize) -> Self {
178 Self {
179 chunks: smallvec::smallvec![SQLChunk::Number(value)],
180 }
181 }
182
183 #[inline]
209 pub fn param(value: impl Into<Cow<'a, V>>) -> Self {
210 Self {
211 chunks: smallvec::smallvec![SQLChunk::Param(Param {
212 value: Some(value.into()),
213 placeholder: Self::POSITIONAL_PLACEHOLDER,
214 })],
215 }
216 }
217
218 #[inline]
223 pub fn bytes(bytes: impl Into<Cow<'a, [u8]>>) -> Self
224 where
225 V: From<&'a [u8]> + From<Vec<u8>> + Into<Cow<'a, V>>,
226 {
227 match bytes.into() {
228 Cow::Borrowed(value) => Self::param(V::from(value)),
229 Cow::Owned(value) => Self::param(V::from(value)),
230 }
231 }
232
233 #[inline]
238 #[must_use]
239 pub fn table(table: TableRef) -> Self {
240 Self {
241 chunks: smallvec::smallvec![SQLChunk::Table(TableSqlRef::from_table_ref(table))],
242 }
243 }
244
245 #[inline]
247 #[must_use]
248 pub fn column(column: ColumnRef) -> Self {
249 Self {
250 chunks: smallvec::smallvec![SQLChunk::Column(ColumnSqlRef::from_column_ref(column))],
251 }
252 }
253
254 #[inline]
279 pub fn func(name: &'static str, args: Self) -> Self {
280 let args = args.parens_if_subquery();
281 SQL::raw(name)
282 .push(Token::LPAREN)
283 .append(args)
284 .push(Token::RPAREN)
285 }
286
287 #[inline]
291 #[must_use]
292 pub fn append(mut self, other: impl Into<Self>) -> Self {
293 #[cfg(feature = "profiling")]
294 profile_sql!("append");
295 let other = other.into();
296
297 if self.chunks.is_empty() {
298 return other;
299 }
300 if other.chunks.is_empty() {
301 return self;
302 }
303
304 self.chunks.extend(other.chunks);
305 self
306 }
307
308 #[inline]
310 pub fn append_mut(&mut self, other: impl Into<Self>) {
311 #[cfg(feature = "profiling")]
312 profile_sql!("append_mut");
313 let other = other.into();
314
315 if self.chunks.is_empty() {
316 self.chunks = other.chunks;
317 return;
318 }
319 if other.chunks.is_empty() {
320 return;
321 }
322
323 self.chunks.extend(other.chunks);
324 }
325
326 #[inline]
328 #[must_use]
329 pub fn push(mut self, chunk: impl Into<SQLChunk<'a, V>>) -> Self {
330 self.chunks.push(chunk.into());
331 self
332 }
333
334 #[inline]
336 pub fn push_mut(&mut self, chunk: impl Into<SQLChunk<'a, V>>) {
337 self.chunks.push(chunk.into());
338 }
339
340 #[inline]
342 #[must_use]
343 pub fn with_capacity(mut self, additional: usize) -> Self {
344 self.chunks.reserve(additional);
345 self
346 }
347
348 pub fn join<T>(sqls: T, separator: Token) -> Self
373 where
374 T: IntoIterator,
375 T::Item: ToSQL<'a, V>,
376 {
377 #[cfg(feature = "profiling")]
378 profile_sql!("join");
379
380 let mut iter = sqls.into_iter();
381 let Some(first) = iter.next() else {
382 return SQL::empty();
383 };
384
385 let mut result = first.into_sql();
386 let (lower, upper) = iter.size_hint();
387 if let Some(upper) = upper {
388 result.chunks.reserve(upper.saturating_mul(2));
389 } else if lower > 0 {
390 result.chunks.reserve(lower * 2);
391 }
392
393 for item in iter {
394 result.chunks.push(SQLChunk::Token(separator));
395 let other = item.into_sql();
396 if !other.chunks.is_empty() {
397 result.chunks.extend(other.chunks);
398 }
399 }
400 result
401 }
402
403 #[inline]
405 #[must_use]
406 pub fn parens(self) -> Self {
407 SQL::token(Token::LPAREN).append(self).push(Token::RPAREN)
408 }
409
410 #[inline]
413 #[must_use]
414 pub fn parens_if_subquery(self) -> Self {
415 if self.is_subquery() {
416 self.parens()
417 } else {
418 self
419 }
420 }
421
422 #[inline]
424 pub fn is_subquery(&self) -> bool {
425 matches!(
426 self.chunks.first(),
427 Some(SQLChunk::Token(Token::SELECT | Token::WITH))
428 )
429 }
430
431 #[inline]
433 #[must_use]
434 pub fn alias(self, name: impl Into<Cow<'a, str>>) -> Self {
435 self.push(Token::AS).push(SQLChunk::Ident(name.into()))
436 }
437
438 #[inline]
440 pub fn param_list<I>(values: I) -> Self
441 where
442 I: IntoIterator,
443 I::Item: Into<Cow<'a, V>>,
444 {
445 let iter = values.into_iter();
446 let (lower, upper) = iter.size_hint();
447 let count = upper.unwrap_or(lower);
448 let mut chunks = SmallVec::with_capacity(count.saturating_mul(2).saturating_sub(1));
449 for (i, v) in iter.enumerate() {
450 if i > 0 {
451 chunks.push(SQLChunk::Token(Token::COMMA));
452 }
453 chunks.push(SQLChunk::Param(Param {
454 value: Some(v.into()),
455 placeholder: Self::POSITIONAL_PLACEHOLDER,
456 }));
457 }
458 SQL { chunks }
459 }
460
461 #[inline]
464 pub fn assignments<I, T>(pairs: I) -> Self
465 where
466 I: IntoIterator<Item = (&'static str, T)>,
467 T: Into<Cow<'a, V>>,
468 {
469 let iter = pairs.into_iter();
470 let (lower, upper) = iter.size_hint();
471 let count = upper.unwrap_or(lower);
472 let mut chunks = SmallVec::with_capacity(count.saturating_mul(4).saturating_sub(1));
474 for (i, (col, val)) in iter.enumerate() {
475 if i > 0 {
476 chunks.push(SQLChunk::Token(Token::COMMA));
477 }
478 chunks.push(SQLChunk::Ident(Cow::Borrowed(col)));
479 chunks.push(SQLChunk::Token(Token::EQ));
480 chunks.push(SQLChunk::Param(Param {
481 value: Some(val.into()),
482 placeholder: Self::POSITIONAL_PLACEHOLDER,
483 }));
484 }
485 SQL { chunks }
486 }
487
488 #[inline]
495 pub fn assignments_sql<I>(pairs: I) -> Self
496 where
497 I: IntoIterator<Item = (&'static str, Self)>,
498 {
499 let iter = pairs.into_iter();
500 let (lower, upper) = iter.size_hint();
501 let count = upper.unwrap_or(lower);
502 let mut chunks = SmallVec::with_capacity(count.saturating_mul(4).saturating_sub(1));
503 for (i, (col, sql)) in iter.enumerate() {
504 if i > 0 {
505 chunks.push(SQLChunk::Token(Token::COMMA));
506 }
507 chunks.push(SQLChunk::Ident(Cow::Borrowed(col)));
508 chunks.push(SQLChunk::Token(Token::EQ));
509 chunks.extend(sql.chunks);
510 }
511 SQL { chunks }
512 }
513
514 pub fn map_params<U: SQLParam>(self, mut f: impl FnMut(V) -> U) -> SQL<'a, U> {
522 let chunks = self
523 .chunks
524 .into_iter()
525 .map(|chunk| match chunk {
526 SQLChunk::Token(t) => SQLChunk::Token(t),
527 SQLChunk::Ident(s) => SQLChunk::Ident(s),
528 SQLChunk::Raw(s) => SQLChunk::Raw(s),
529 SQLChunk::Number(n) => SQLChunk::Number(n),
530 SQLChunk::Param(param) => SQLChunk::Param(Param::new(
531 param.placeholder,
532 param.value.map(|cow| Cow::Owned(f(cow.into_owned()))),
533 )),
534 SQLChunk::Table(t) => SQLChunk::Table(t),
535 SQLChunk::Column(c) => SQLChunk::Column(c),
536 })
537 .collect();
538 SQL { chunks }
539 }
540
541 pub fn into_owned_with<U: SQLParam>(self, mut f: impl FnMut(V) -> U) -> SQL<'static, U> {
547 let chunks = self
548 .chunks
549 .into_iter()
550 .map(|chunk| match chunk {
551 SQLChunk::Token(token) => SQLChunk::Token(token),
552 SQLChunk::Ident(value) => SQLChunk::Ident(Cow::Owned(value.into_owned())),
553 SQLChunk::Raw(value) => SQLChunk::Raw(Cow::Owned(value.into_owned())),
554 SQLChunk::Number(value) => SQLChunk::Number(value),
555 SQLChunk::Param(param) => SQLChunk::Param(Param::new(
556 param.placeholder,
557 param.value.map(|value| Cow::Owned(f(value.into_owned()))),
558 )),
559 SQLChunk::Table(table) => SQLChunk::Table(table),
560 SQLChunk::Column(column) => SQLChunk::Column(column),
561 })
562 .collect();
563 SQL { chunks }
564 }
565
566 #[inline]
568 pub fn into_owned(self) -> OwnedSQL<V> {
569 OwnedSQL::from(self)
570 }
571
572 pub fn sql(&self) -> String {
578 #[cfg(feature = "profiling")]
579 profile_sql!("sql");
580 #[cfg(feature = "profiling")]
581 crate::drizzle_profile_scope!("sql_render", "sql.estimate");
582 let (sql_cap, _) = self.render_capacity_estimate();
583 let mut buf = String::with_capacity(sql_cap);
584 self.write_to(&mut buf);
585 buf
586 }
587
588 #[must_use]
591 pub fn has_returning(&self) -> bool {
592 let mut depth = 0usize;
593 for chunk in &self.chunks {
594 match chunk {
595 SQLChunk::Token(Token::LPAREN) => depth += 1,
596 SQLChunk::Token(Token::RPAREN) => depth = depth.saturating_sub(1),
597 SQLChunk::Token(Token::RETURNING) if depth == 0 => return true,
598 _ => {}
599 }
600 }
601 false
602 }
603
604 #[must_use]
612 pub fn inline_sql(&self) -> Option<String> {
613 let (sql_cap, _) = self.render_capacity_estimate();
614 let mut buf = String::with_capacity(sql_cap);
615 for (i, chunk) in self.chunks.iter().enumerate() {
616 match chunk {
617 SQLChunk::Param(param) => {
618 let value = param.value.as_ref()?;
619 if !value.as_ref().write_literal(&mut buf) {
620 return None;
621 }
622 }
623 _ => chunk.write(&mut buf),
624 }
625
626 if self.ends_select_head(i) {
627 self.write_select_columns(&mut buf, i);
628 }
629
630 if self.needs_space(i) {
631 buf.push(' ');
632 }
633 }
634 Some(buf)
635 }
636
637 pub fn build(&self) -> (String, SmallVec<[&V; 8]>) {
666 self.build_with(crate::dialect::ParamStyle::for_dialect(V::DIALECT))
667 }
668
669 pub fn build_with(&self, style: crate::dialect::ParamStyle) -> (String, SmallVec<[&V; 8]>) {
676 use crate::dialect::Dialect;
677
678 #[cfg(feature = "profiling")]
679 crate::drizzle_profile_scope!("sql_render", "build");
680 #[cfg(feature = "profiling")]
681 crate::drizzle_profile_scope!("sql_render", "build.estimate");
682 let (sql_cap, param_cap) = self.render_capacity_estimate();
683 let mut buf = String::with_capacity(sql_cap);
684 let mut params: SmallVec<[&V; 8]> = SmallVec::with_capacity(param_cap);
685 let mut param_index = 1usize;
686 let mut sqlite_names = SQLiteNamedParams::default();
687
688 #[cfg(feature = "profiling")]
689 crate::drizzle_profile_scope!("sql_render", "build.render");
690 for (i, chunk) in self.chunks.iter().enumerate() {
691 match chunk {
692 SQLChunk::Param(param) => {
693 let mut repeated_name = false;
694 if let Some(name) = param.placeholder.name
695 && V::DIALECT == Dialect::SQLite
696 {
697 let _ = buf.write_char(':');
698 let _ = buf.write_str(name);
699 repeated_name = sqlite_names.is_repeat(name);
700 } else {
701 style.write(param_index, &mut buf);
702 }
703 param_index += 1;
704 if !repeated_name && let Some(value) = ¶m.value {
707 params.push(value.as_ref());
708 }
709 }
710 _ => chunk.write(&mut buf),
711 }
712
713 if self.ends_select_head(i) {
714 self.write_select_columns(&mut buf, i);
715 }
716
717 if self.needs_space(i) {
718 let _ = buf.write_char(' ');
719 }
720 }
721
722 (buf, params)
723 }
724
725 #[inline]
728 pub fn write_to(&self, buf: &mut impl core::fmt::Write) {
729 self.write_to_with(buf, crate::dialect::ParamStyle::for_dialect(V::DIALECT));
730 }
731
732 pub fn write_to_with(
735 &self,
736 buf: &mut impl core::fmt::Write,
737 style: crate::dialect::ParamStyle,
738 ) {
739 use crate::dialect::Dialect;
740
741 #[cfg(feature = "profiling")]
742 crate::drizzle_profile_scope!("sql_render", "write_to");
743 let mut param_index = 1usize;
744 for (i, chunk) in self.chunks.iter().enumerate() {
745 match chunk {
746 SQLChunk::Param(param) => {
747 if let Some(name) = param.placeholder.name
748 && V::DIALECT == Dialect::SQLite
749 {
750 let _ = buf.write_char(':');
751 let _ = buf.write_str(name);
752 } else {
753 style.write(param_index, buf);
754 }
755 param_index += 1;
756 }
757 _ => chunk.write(buf),
758 }
759
760 if self.ends_select_head(i) {
761 self.write_select_columns(buf, i);
762 }
763
764 if self.needs_space(i) {
765 let _ = buf.write_char(' ');
766 }
767 }
768 }
769
770 #[inline]
775 pub fn write_chunk_to(
776 &self,
777 buf: &mut impl core::fmt::Write,
778 chunk: &SQLChunk<'a, V>,
779 index: usize,
780 ) {
781 chunk.write(buf);
782 if self.ends_select_head(index) {
783 self.write_select_columns(buf, index);
784 }
785 }
786
787 fn ends_select_head(&self, index: usize) -> bool {
793 if !matches!(
794 self.chunks.get(index + 1),
795 Some(SQLChunk::Token(Token::FROM))
796 ) {
797 return false;
798 }
799 let token_at = |position: Option<usize>| match position.and_then(|p| self.chunks.get(p)) {
800 Some(SQLChunk::Token(token)) => Some(*token),
801 _ => None,
802 };
803
804 match self.chunks[index] {
805 SQLChunk::Token(Token::SELECT) => true,
806 SQLChunk::Token(Token::DISTINCT) => {
807 matches!(token_at(index.checked_sub(1)), Some(Token::SELECT))
808 }
809 SQLChunk::Token(Token::RPAREN) => {
810 let mut depth = 0usize;
812 let mut open = None;
813 for position in (0..index).rev() {
814 match self.chunks[position] {
815 SQLChunk::Token(Token::RPAREN) => depth += 1,
816 SQLChunk::Token(Token::LPAREN) if depth == 0 => {
817 open = Some(position);
818 break;
819 }
820 SQLChunk::Token(Token::LPAREN) => depth -= 1,
821 _ => {}
822 }
823 }
824 open.is_some_and(|open| {
825 matches!(token_at(open.checked_sub(1)), Some(Token::ON))
826 && matches!(token_at(open.checked_sub(2)), Some(Token::DISTINCT))
827 && matches!(token_at(open.checked_sub(3)), Some(Token::SELECT))
828 })
829 }
830 _ => false,
831 }
832 }
833
834 #[inline]
838 pub(crate) fn write_select_columns(&self, buf: &mut impl core::fmt::Write, head_end: usize) {
839 let chunks = self.chunks.get(head_end + 1..head_end + 3);
840 match chunks {
841 Some([SQLChunk::Token(Token::FROM), SQLChunk::Table(_)]) => {
842 let _ = buf.write_char(' ');
843 let mut first = true;
844 let mut depth = 0usize;
845
846 for (index, chunk) in self.chunks.iter().enumerate().skip(head_end + 2) {
847 match chunk {
848 SQLChunk::Token(Token::LPAREN) => depth += 1,
849 SQLChunk::Token(Token::RPAREN) if depth == 0 => break,
850 SQLChunk::Token(Token::RPAREN) => depth -= 1,
851 SQLChunk::Token(
852 Token::WHERE
853 | Token::GROUP
854 | Token::HAVING
855 | Token::ORDER
856 | Token::LIMIT
857 | Token::OFFSET
858 | Token::WINDOW
859 | Token::FOR
860 | Token::UNION
861 | Token::INTERSECT
862 | Token::EXCEPT
863 | Token::SELECT,
864 ) if depth == 0 => break,
865 SQLChunk::Table(table) if depth == 0 => {
866 if !first {
867 let _ = buf.write_str(", ");
868 }
869 let alias = match self.chunks.get(index + 1..index + 3) {
870 Some([SQLChunk::Token(Token::AS), SQLChunk::Ident(alias)]) => {
871 Some(alias.as_ref())
872 }
873 _ => None,
874 };
875 Self::write_qualified_columns_as(buf, table, alias);
876 first = false;
877 }
878 _ => {}
879 }
880 }
881 }
882 Some([SQLChunk::Token(Token::FROM), _]) => {
883 let _ = buf.write_char(' ');
884 let _ = buf.write_str(Token::STAR.as_str());
885 }
886 _ => {}
887 }
888 }
889
890 #[inline]
893 pub fn write_qualified_columns(buf: &mut impl core::fmt::Write, table: &TableSqlRef) {
894 Self::write_qualified_columns_as(buf, table, None);
895 }
896
897 #[inline]
898 fn write_qualified_columns_as(
899 buf: &mut impl core::fmt::Write,
900 table: &TableSqlRef,
901 alias: Option<&str>,
902 ) {
903 if table.column_names.is_empty() {
904 if let Some(alias) = alias {
905 chunk::write_dialect_quoted_ident(V::DIALECT, buf, alias);
906 } else {
907 if let Some(schema) = table.schema {
908 chunk::write_dialect_quoted_ident(V::DIALECT, buf, schema);
909 let _ = buf.write_char('.');
910 }
911 chunk::write_dialect_quoted_ident(V::DIALECT, buf, table.name);
912 }
913 let _ = buf.write_str(".*");
914 return;
915 }
916
917 for (i, col_name) in table.column_names.iter().enumerate() {
918 if i > 0 {
919 let _ = buf.write_str(", ");
920 }
921 if let Some(alias) = alias {
922 chunk::write_dialect_quoted_ident(V::DIALECT, buf, alias);
923 } else {
924 if let Some(schema) = table.schema {
925 chunk::write_dialect_quoted_ident(V::DIALECT, buf, schema);
926 let _ = buf.write_char('.');
927 }
928 chunk::write_dialect_quoted_ident(V::DIALECT, buf, table.name);
929 }
930 let _ = buf.write_char('.');
931 chunk::write_dialect_quoted_ident(V::DIALECT, buf, col_name);
932 }
933 }
934
935 #[inline]
937 fn needs_space(&self, index: usize) -> bool {
938 let Some(next) = self.chunks.get(index + 1) else {
939 return false;
940 };
941
942 let current = &self.chunks[index];
943 chunk_needs_space(current, next)
944 }
945
946 #[inline]
947 fn render_capacity_estimate(&self) -> (usize, usize) {
948 let mut sql_cap = 0usize;
949 let mut param_cap = 0usize;
950
951 for chunk in &self.chunks {
952 sql_cap = sql_cap.saturating_add(match chunk {
953 SQLChunk::Ident(_) | SQLChunk::Raw(_) => 20,
954 SQLChunk::Column(_) => 30,
955 SQLChunk::Table(_) => 15,
956 SQLChunk::Token(_) => 8,
957 SQLChunk::Number(_) | SQLChunk::Param(_) => 4,
958 });
959 if matches!(chunk, SQLChunk::Param(_)) {
960 param_cap = param_cap.saturating_add(1);
961 }
962 }
963
964 (sql_cap.max(128), param_cap)
965 }
966
967 #[inline]
970 pub fn params(&self) -> impl Iterator<Item = &V> + use<'_, V> {
971 self.chunks.iter().filter_map(|chunk| {
972 if let SQLChunk::Param(Param {
973 value: Some(value), ..
974 }) = chunk
975 {
976 Some(value.as_ref())
977 } else {
978 None
979 }
980 })
981 }
982
983 #[must_use]
1015 pub fn bind<T: SQLParam + Into<V>>(
1016 self,
1017 params: impl IntoIterator<Item: Into<ParamBind<'a, T>>>,
1018 ) -> Self {
1019 #[cfg(feature = "profiling")]
1020 profile_sql!("bind");
1021
1022 let binds: SmallVec<[(&str, V); 4]> = params
1023 .into_iter()
1024 .map(Into::into)
1025 .map(|p| (p.name, p.value.into()))
1026 .collect();
1027
1028 if binds.len() <= 4 {
1029 let bound_chunks: SmallVec<[SQLChunk<'a, V>; 8]> = self
1030 .chunks
1031 .into_iter()
1032 .map(|chunk| match chunk {
1033 SQLChunk::Param(mut param) => {
1034 if let Some(name) = param.placeholder.name
1035 && let Some((_, value)) =
1036 binds.iter().find(|(param_name, _)| *param_name == name)
1037 {
1038 param.value = Some(Cow::Owned(value.clone()));
1039 }
1040 SQLChunk::Param(param)
1041 }
1042 other => other,
1043 })
1044 .collect();
1045
1046 return SQL {
1047 chunks: bound_chunks,
1048 };
1049 }
1050
1051 let param_map: HashMap<&str, V> = binds.into_iter().collect();
1052 let bound_chunks: SmallVec<[SQLChunk<'a, V>; 8]> = self
1053 .chunks
1054 .into_iter()
1055 .map(|chunk| match chunk {
1056 SQLChunk::Param(mut param) => {
1057 if let Some(name) = param.placeholder.name
1058 && let Some(value) = param_map.get(name)
1059 {
1060 param.value = Some(Cow::Owned(value.clone()));
1061 }
1062 SQLChunk::Param(param)
1063 }
1064 other => other,
1065 })
1066 .collect();
1067
1068 SQL {
1069 chunks: bound_chunks,
1070 }
1071 }
1072}
1073
1074#[derive(Default)]
1081pub(crate) struct SQLiteNamedParams<'n> {
1082 seen: SmallVec<[&'n str; 4]>,
1083}
1084
1085impl<'n> SQLiteNamedParams<'n> {
1086 pub(crate) fn is_repeat(&mut self, name: &'n str) -> bool {
1089 if name.is_empty() {
1090 return false;
1091 }
1092 if self.seen.contains(&name) {
1093 true
1094 } else {
1095 self.seen.push(name);
1096 false
1097 }
1098 }
1099}
1100
1101#[inline]
1104pub(crate) fn chunk_needs_space<V: SQLParam>(
1105 current: &SQLChunk<'_, V>,
1106 next: &SQLChunk<'_, V>,
1107) -> bool {
1108 if let SQLChunk::Raw(text) = current
1110 && text.ends_with(' ')
1111 {
1112 return false;
1113 }
1114
1115 if let SQLChunk::Raw(text) = next
1117 && text.starts_with(' ')
1118 {
1119 return false;
1120 }
1121
1122 match (current, next) {
1123 (_, SQLChunk::Token(Token::RPAREN | Token::COMMA | Token::SEMI | Token::DOT))
1126 | (SQLChunk::Token(Token::LPAREN | Token::DOT), _) => false,
1127 (SQLChunk::Token(Token::COMMA), _) => true,
1129 (SQLChunk::Token(Token::RPAREN), next) => next.is_word_like(),
1131 (SQLChunk::Raw(_), SQLChunk::Token(Token::LPAREN))
1135 if V::DIALECT == crate::Dialect::MySQL =>
1136 {
1137 false
1138 }
1139 (current, SQLChunk::Token(Token::LPAREN)) => current.is_word_like(),
1141 (SQLChunk::Token(t), _) if t.is_operator() => true,
1143 (_, SQLChunk::Token(t)) if t.is_operator() => true,
1144 _ => current.is_word_like() && next.is_word_like(),
1146 }
1147}
1148
1149impl<V: SQLParam> Default for SQL<'_, V> {
1152 #[inline]
1153 fn default() -> Self {
1154 Self::empty()
1155 }
1156}
1157
1158impl<'a, V: SQLParam + 'a> From<&'a str> for SQL<'a, V> {
1159 #[inline]
1160 fn from(s: &'a str) -> Self {
1161 SQL::raw(s)
1162 }
1163}
1164
1165impl<V: SQLParam> From<Token> for SQL<'_, V> {
1166 #[inline]
1167 fn from(value: Token) -> Self {
1168 SQL::token(value)
1169 }
1170}
1171
1172impl<'a, V: SQLParam + 'a> AsRef<Self> for SQL<'a, V> {
1173 #[inline]
1174 fn as_ref(&self) -> &Self {
1175 self
1176 }
1177}
1178
1179impl<V: SQLParam + core::fmt::Display> Display for SQL<'_, V> {
1180 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1181 let params: Vec<_> = self.params().collect();
1183 write!(f, r#"sql: "{}", params: {:?}"#, self.sql(), params)
1184 }
1185}
1186
1187impl<'a, V: SQLParam + 'a> ToSQL<'a, V> for SQL<'a, V> {
1188 fn to_sql(&self) -> Self {
1189 self.clone()
1190 }
1191
1192 fn into_sql(self) -> Self {
1193 self
1194 }
1195}
1196
1197impl<'a, V: SQLParam, T> FromIterator<T> for SQL<'a, V>
1198where
1199 SQLChunk<'a, V>: From<T>,
1200{
1201 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
1202 let chunks = iter
1203 .into_iter()
1204 .map(SQLChunk::from)
1205 .collect::<SmallVec<_>>();
1206 Self { chunks }
1207 }
1208}
1209
1210impl<'a, V: SQLParam> IntoIterator for SQL<'a, V> {
1211 type Item = SQLChunk<'a, V>;
1212 type IntoIter = smallvec::IntoIter<[SQLChunk<'a, V>; 8]>;
1213
1214 fn into_iter(self) -> Self::IntoIter {
1215 self.chunks.into_iter()
1216 }
1217}
1218
1219#[cfg(test)]
1220mod tests {
1221 use super::*;
1222 use crate::{Dialect, MySQLDialect};
1223
1224 #[derive(Clone, Debug)]
1225 struct TestParam;
1226
1227 impl SQLParam for TestParam {
1228 const DIALECT: Dialect = Dialect::MySQL;
1229 type DialectMarker = MySQLDialect;
1230 }
1231
1232 impl From<TestParam> for Cow<'_, TestParam> {
1233 fn from(value: TestParam) -> Self {
1234 Cow::Owned(value)
1235 }
1236 }
1237
1238 #[test]
1239 fn owning_mapped_params_preserves_every_chunk_kind() {
1240 let raw = String::from("COALESCE(");
1241 let identifier = String::from("display_name");
1242 let sql = SQL::raw(raw.as_str())
1243 .append(SQL::ident(identifier.as_str()))
1244 .push(Token::COMMA)
1245 .append(SQL::param(TestParam))
1246 .push(Token::COMMA)
1247 .push(Param::<TestParam>::from(Placeholder::named("fallback")))
1248 .push(Token::RPAREN);
1249
1250 let owned = sql.into_owned_with(|value| value);
1251 drop(raw);
1252 drop(identifier);
1253
1254 assert_eq!(owned.sql(), "COALESCE( `display_name`, ?, ?)");
1255 assert_eq!(owned.params().count(), 1);
1256 assert_eq!(
1257 owned
1258 .chunks
1259 .iter()
1260 .filter(|chunk| matches!(chunk, SQLChunk::Param(param) if param.value.is_none()))
1261 .count(),
1262 1
1263 );
1264 }
1265
1266 #[test]
1267 fn columns_renders_an_identifier_list() {
1268 let columns = [
1269 ColumnRef::sql("users", "first"),
1270 ColumnRef::sql("users", "last`name"),
1271 ];
1272
1273 assert_eq!(
1274 SQL::<TestParam>::columns(&columns).sql(),
1275 "`first`, `last``name`"
1276 );
1277 }
1278
1279 #[test]
1280 fn select_star_uses_the_table_alias_to_qualify_columns() {
1281 let table = TableRef::sql("users", &["id", "name"]);
1282 let query = SQL::<TestParam>::from(Token::SELECT)
1283 .push(Token::FROM)
1284 .append(SQL::table(table).alias("u"));
1285
1286 assert_eq!(
1287 query.sql(),
1288 "SELECT `u`.`id`, `u`.`name` FROM `users` AS `u`"
1289 );
1290 }
1291
1292 #[test]
1293 fn nested_select_star_keeps_derived_alias_in_outer_projection() {
1294 let source = SQL::<TestParam>::from(Token::SELECT)
1295 .push(Token::FROM)
1296 .append(SQL::table(TableRef::sql("posts", &["id", "name"])))
1297 .parens()
1298 .push(Token::AS)
1299 .append(SQL::table(TableRef::sql("post_rows", &[])));
1300 let query = SQL::<TestParam>::from(Token::SELECT)
1301 .push(Token::FROM)
1302 .append(SQL::table(TableRef::sql("users", &["id"])))
1303 .append(SQL::raw(" INNER JOIN LATERAL "))
1304 .append(source)
1305 .push(Token::ON)
1306 .append(SQL::raw("TRUE"));
1307
1308 assert_eq!(
1309 query.sql(),
1310 "SELECT `users`.`id`, `post_rows`.* FROM `users` INNER JOIN LATERAL (SELECT `posts`.`id`, `posts`.`name` FROM `posts`) AS `post_rows` ON TRUE"
1311 );
1312 }
1313}