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)]
28pub struct SQL<'a, V: SQLParam> {
29 pub chunks: SmallVec<[SQLChunk<'a, V>; 8]>,
30}
31
32impl<'a, V: SQLParam> SQL<'a, V> {
33 const POSITIONAL_PLACEHOLDER: Placeholder = Placeholder::anonymous();
34
35 #[inline]
39 #[must_use]
40 pub const fn empty() -> Self {
41 Self {
42 chunks: SmallVec::new_const(),
43 }
44 }
45
46 #[inline]
50 #[must_use]
51 pub fn token(t: Token) -> Self {
52 Self {
53 chunks: smallvec::smallvec![SQLChunk::Token(t)],
54 }
55 }
56
57 #[inline]
59 #[must_use]
60 pub fn with_capacity_chunks(capacity: usize) -> Self {
61 Self {
62 chunks: SmallVec::with_capacity(capacity),
63 }
64 }
65
66 #[inline]
68 pub fn ident(name: impl Into<Cow<'a, str>>) -> Self {
69 Self {
70 chunks: smallvec::smallvec![SQLChunk::Ident(name.into())],
71 }
72 }
73
74 #[must_use]
76 pub fn columns(columns: &[ColumnRef]) -> Self {
77 let mut sql = Self::with_capacity_chunks(columns.len().saturating_mul(2));
78 for (index, column) in columns.iter().enumerate() {
79 if index > 0 {
80 sql.push_mut(Token::COMMA);
81 }
82 sql.append_mut(Self::ident(column.name));
83 }
84 sql
85 }
86
87 #[inline]
89 pub fn raw(text: impl Into<Cow<'a, str>>) -> Self {
90 Self {
91 chunks: smallvec::smallvec![SQLChunk::Raw(text.into())],
92 }
93 }
94
95 #[inline]
97 #[must_use]
98 pub fn number(value: usize) -> Self {
99 Self {
100 chunks: smallvec::smallvec![SQLChunk::Number(value)],
101 }
102 }
103
104 #[inline]
106 pub fn param(value: impl Into<Cow<'a, V>>) -> Self {
107 Self {
108 chunks: smallvec::smallvec![SQLChunk::Param(Param {
109 value: Some(value.into()),
110 placeholder: Self::POSITIONAL_PLACEHOLDER,
111 })],
112 }
113 }
114
115 #[inline]
119 pub fn bytes(bytes: impl Into<Cow<'a, [u8]>>) -> Self
120 where
121 V: From<&'a [u8]> + From<Vec<u8>> + Into<Cow<'a, V>>,
122 {
123 match bytes.into() {
124 Cow::Borrowed(value) => Self::param(V::from(value)),
125 Cow::Owned(value) => Self::param(V::from(value)),
126 }
127 }
128
129 #[inline]
131 #[must_use]
132 pub fn table(table: TableRef) -> Self {
133 Self {
134 chunks: smallvec::smallvec![SQLChunk::Table(TableSqlRef::from_table_ref(table))],
135 }
136 }
137
138 #[inline]
140 #[must_use]
141 pub fn column(column: ColumnRef) -> Self {
142 Self {
143 chunks: smallvec::smallvec![SQLChunk::Column(ColumnSqlRef::from_column_ref(column))],
144 }
145 }
146
147 #[inline]
150 pub fn func(name: &'static str, args: Self) -> Self {
151 let args = args.parens_if_subquery();
152 SQL::raw(name)
153 .push(Token::LPAREN)
154 .append(args)
155 .push(Token::RPAREN)
156 }
157
158 #[inline]
162 #[must_use]
163 pub fn append(mut self, other: impl Into<Self>) -> Self {
164 #[cfg(feature = "profiling")]
165 profile_sql!("append");
166 let other = other.into();
167
168 if self.chunks.is_empty() {
169 return other;
170 }
171 if other.chunks.is_empty() {
172 return self;
173 }
174
175 self.chunks.extend(other.chunks);
176 self
177 }
178
179 #[inline]
180 pub fn append_mut(&mut self, other: impl Into<Self>) {
181 #[cfg(feature = "profiling")]
182 profile_sql!("append_mut");
183 let other = other.into();
184
185 if self.chunks.is_empty() {
186 self.chunks = other.chunks;
187 return;
188 }
189 if other.chunks.is_empty() {
190 return;
191 }
192
193 self.chunks.extend(other.chunks);
194 }
195
196 #[inline]
198 #[must_use]
199 pub fn push(mut self, chunk: impl Into<SQLChunk<'a, V>>) -> Self {
200 self.chunks.push(chunk.into());
201 self
202 }
203
204 #[inline]
205 pub fn push_mut(&mut self, chunk: impl Into<SQLChunk<'a, V>>) {
206 self.chunks.push(chunk.into());
207 }
208
209 #[inline]
211 #[must_use]
212 pub fn with_capacity(mut self, additional: usize) -> Self {
213 self.chunks.reserve(additional);
214 self
215 }
216
217 pub fn join<T>(sqls: T, separator: Token) -> Self
221 where
222 T: IntoIterator,
223 T::Item: ToSQL<'a, V>,
224 {
225 #[cfg(feature = "profiling")]
226 profile_sql!("join");
227
228 let mut iter = sqls.into_iter();
229 let Some(first) = iter.next() else {
230 return SQL::empty();
231 };
232
233 let mut result = first.into_sql();
234 let (lower, upper) = iter.size_hint();
235 if let Some(upper) = upper {
236 result.chunks.reserve(upper.saturating_mul(2));
237 } else if lower > 0 {
238 result.chunks.reserve(lower * 2);
239 }
240
241 for item in iter {
242 result.chunks.push(SQLChunk::Token(separator));
243 let other = item.into_sql();
244 if !other.chunks.is_empty() {
245 result.chunks.extend(other.chunks);
246 }
247 }
248 result
249 }
250
251 #[inline]
253 #[must_use]
254 pub fn parens(self) -> Self {
255 SQL::token(Token::LPAREN).append(self).push(Token::RPAREN)
256 }
257
258 #[inline]
260 #[must_use]
261 pub fn parens_if_subquery(self) -> Self {
262 if self.is_subquery() {
263 self.parens()
264 } else {
265 self
266 }
267 }
268
269 #[inline]
271 pub fn is_subquery(&self) -> bool {
272 matches!(
273 self.chunks.first(),
274 Some(SQLChunk::Token(Token::SELECT | Token::WITH))
275 )
276 }
277
278 #[inline]
280 #[must_use]
281 pub fn alias(self, name: impl Into<Cow<'a, str>>) -> Self {
282 self.push(Token::AS).push(SQLChunk::Ident(name.into()))
283 }
284
285 #[inline]
288 pub fn param_list<I>(values: I) -> Self
289 where
290 I: IntoIterator,
291 I::Item: Into<Cow<'a, V>>,
292 {
293 let iter = values.into_iter();
294 let (lower, upper) = iter.size_hint();
295 let count = upper.unwrap_or(lower);
296 let mut chunks = SmallVec::with_capacity(count.saturating_mul(2).saturating_sub(1));
297 for (i, v) in iter.enumerate() {
298 if i > 0 {
299 chunks.push(SQLChunk::Token(Token::COMMA));
300 }
301 chunks.push(SQLChunk::Param(Param {
302 value: Some(v.into()),
303 placeholder: Self::POSITIONAL_PLACEHOLDER,
304 }));
305 }
306 SQL { chunks }
307 }
308
309 #[inline]
312 pub fn assignments<I, T>(pairs: I) -> Self
313 where
314 I: IntoIterator<Item = (&'static str, T)>,
315 T: Into<Cow<'a, V>>,
316 {
317 let iter = pairs.into_iter();
318 let (lower, upper) = iter.size_hint();
319 let count = upper.unwrap_or(lower);
320 let mut chunks = SmallVec::with_capacity(count.saturating_mul(4).saturating_sub(1));
322 for (i, (col, val)) in iter.enumerate() {
323 if i > 0 {
324 chunks.push(SQLChunk::Token(Token::COMMA));
325 }
326 chunks.push(SQLChunk::Ident(Cow::Borrowed(col)));
327 chunks.push(SQLChunk::Token(Token::EQ));
328 chunks.push(SQLChunk::Param(Param {
329 value: Some(val.into()),
330 placeholder: Self::POSITIONAL_PLACEHOLDER,
331 }));
332 }
333 SQL { chunks }
334 }
335
336 #[inline]
343 pub fn assignments_sql<I>(pairs: I) -> Self
344 where
345 I: IntoIterator<Item = (&'static str, Self)>,
346 {
347 let iter = pairs.into_iter();
348 let (lower, upper) = iter.size_hint();
349 let count = upper.unwrap_or(lower);
350 let mut chunks = SmallVec::with_capacity(count.saturating_mul(4).saturating_sub(1));
351 for (i, (col, sql)) in iter.enumerate() {
352 if i > 0 {
353 chunks.push(SQLChunk::Token(Token::COMMA));
354 }
355 chunks.push(SQLChunk::Ident(Cow::Borrowed(col)));
356 chunks.push(SQLChunk::Token(Token::EQ));
357 chunks.extend(sql.chunks);
358 }
359 SQL { chunks }
360 }
361
362 pub fn map_params<U: SQLParam>(self, mut f: impl FnMut(V) -> U) -> SQL<'a, U> {
370 let chunks = self
371 .chunks
372 .into_iter()
373 .map(|chunk| match chunk {
374 SQLChunk::Token(t) => SQLChunk::Token(t),
375 SQLChunk::Ident(s) => SQLChunk::Ident(s),
376 SQLChunk::Raw(s) => SQLChunk::Raw(s),
377 SQLChunk::Number(n) => SQLChunk::Number(n),
378 SQLChunk::Param(param) => SQLChunk::Param(Param::new(
379 param.placeholder,
380 param.value.map(|cow| Cow::Owned(f(cow.into_owned()))),
381 )),
382 SQLChunk::Table(t) => SQLChunk::Table(t),
383 SQLChunk::Column(c) => SQLChunk::Column(c),
384 })
385 .collect();
386 SQL { chunks }
387 }
388
389 pub fn into_owned_with<U: SQLParam>(self, mut f: impl FnMut(V) -> U) -> SQL<'static, U> {
395 let chunks = self
396 .chunks
397 .into_iter()
398 .map(|chunk| match chunk {
399 SQLChunk::Token(token) => SQLChunk::Token(token),
400 SQLChunk::Ident(value) => SQLChunk::Ident(Cow::Owned(value.into_owned())),
401 SQLChunk::Raw(value) => SQLChunk::Raw(Cow::Owned(value.into_owned())),
402 SQLChunk::Number(value) => SQLChunk::Number(value),
403 SQLChunk::Param(param) => SQLChunk::Param(Param::new(
404 param.placeholder,
405 param.value.map(|value| Cow::Owned(f(value.into_owned()))),
406 )),
407 SQLChunk::Table(table) => SQLChunk::Table(table),
408 SQLChunk::Column(column) => SQLChunk::Column(column),
409 })
410 .collect();
411 SQL { chunks }
412 }
413
414 #[inline]
416 pub fn into_owned(self) -> OwnedSQL<V> {
417 OwnedSQL::from(self)
418 }
419
420 pub fn sql(&self) -> String {
423 #[cfg(feature = "profiling")]
424 profile_sql!("sql");
425 #[cfg(feature = "profiling")]
426 crate::drizzle_profile_scope!("sql_render", "sql.estimate");
427 let (sql_cap, _) = self.render_capacity_estimate();
428 let mut buf = String::with_capacity(sql_cap);
429 self.write_to(&mut buf);
430 buf
431 }
432
433 #[must_use]
436 pub fn has_returning(&self) -> bool {
437 let mut depth = 0usize;
438 for chunk in &self.chunks {
439 match chunk {
440 SQLChunk::Token(Token::LPAREN) => depth += 1,
441 SQLChunk::Token(Token::RPAREN) => depth = depth.saturating_sub(1),
442 SQLChunk::Token(Token::RETURNING) if depth == 0 => return true,
443 _ => {}
444 }
445 }
446 false
447 }
448
449 #[must_use]
456 pub fn inline_sql(&self) -> Option<String> {
457 let (sql_cap, _) = self.render_capacity_estimate();
458 let mut buf = String::with_capacity(sql_cap);
459 for (i, chunk) in self.chunks.iter().enumerate() {
460 match chunk {
461 SQLChunk::Param(param) => {
462 let value = param.value.as_ref()?;
463 if !value.as_ref().write_literal(&mut buf) {
464 return None;
465 }
466 }
467 _ => chunk.write(&mut buf),
468 }
469
470 if self.ends_select_head(i) {
471 self.write_select_columns(&mut buf, i);
472 }
473
474 if self.needs_space(i) {
475 buf.push(' ');
476 }
477 }
478 Some(buf)
479 }
480
481 pub fn build(&self) -> (String, SmallVec<[&V; 8]>) {
486 self.build_with(crate::dialect::ParamStyle::for_dialect(V::DIALECT))
487 }
488
489 pub fn build_with(&self, style: crate::dialect::ParamStyle) -> (String, SmallVec<[&V; 8]>) {
494 use crate::dialect::Dialect;
495
496 #[cfg(feature = "profiling")]
497 crate::drizzle_profile_scope!("sql_render", "build");
498 #[cfg(feature = "profiling")]
499 crate::drizzle_profile_scope!("sql_render", "build.estimate");
500 let (sql_cap, param_cap) = self.render_capacity_estimate();
501 let mut buf = String::with_capacity(sql_cap);
502 let mut params: SmallVec<[&V; 8]> = SmallVec::with_capacity(param_cap);
503 let mut param_index = 1usize;
504 let mut sqlite_names = SQLiteNamedParams::default();
505
506 #[cfg(feature = "profiling")]
507 crate::drizzle_profile_scope!("sql_render", "build.render");
508 for (i, chunk) in self.chunks.iter().enumerate() {
509 match chunk {
510 SQLChunk::Param(param) => {
511 let mut repeated_name = false;
512 if let Some(name) = param.placeholder.name
513 && V::DIALECT == Dialect::SQLite
514 {
515 let _ = buf.write_char(':');
516 let _ = buf.write_str(name);
517 repeated_name = sqlite_names.is_repeat(name);
518 } else {
519 style.write(param_index, &mut buf);
520 }
521 param_index += 1;
522 if !repeated_name && let Some(value) = ¶m.value {
525 params.push(value.as_ref());
526 }
527 }
528 _ => chunk.write(&mut buf),
529 }
530
531 if self.ends_select_head(i) {
532 self.write_select_columns(&mut buf, i);
533 }
534
535 if self.needs_space(i) {
536 let _ = buf.write_char(' ');
537 }
538 }
539
540 (buf, params)
541 }
542
543 #[inline]
546 pub fn write_to(&self, buf: &mut impl core::fmt::Write) {
547 self.write_to_with(buf, crate::dialect::ParamStyle::for_dialect(V::DIALECT));
548 }
549
550 pub fn write_to_with(
553 &self,
554 buf: &mut impl core::fmt::Write,
555 style: crate::dialect::ParamStyle,
556 ) {
557 use crate::dialect::Dialect;
558
559 #[cfg(feature = "profiling")]
560 crate::drizzle_profile_scope!("sql_render", "write_to");
561 let mut param_index = 1usize;
562 for (i, chunk) in self.chunks.iter().enumerate() {
563 match chunk {
564 SQLChunk::Param(param) => {
565 if let Some(name) = param.placeholder.name
566 && V::DIALECT == Dialect::SQLite
567 {
568 let _ = buf.write_char(':');
569 let _ = buf.write_str(name);
570 } else {
571 style.write(param_index, buf);
572 }
573 param_index += 1;
574 }
575 _ => chunk.write(buf),
576 }
577
578 if self.ends_select_head(i) {
579 self.write_select_columns(buf, i);
580 }
581
582 if self.needs_space(i) {
583 let _ = buf.write_char(' ');
584 }
585 }
586 }
587
588 #[inline]
590 pub fn write_chunk_to(
591 &self,
592 buf: &mut impl core::fmt::Write,
593 chunk: &SQLChunk<'a, V>,
594 index: usize,
595 ) {
596 chunk.write(buf);
597 if self.ends_select_head(index) {
598 self.write_select_columns(buf, index);
599 }
600 }
601
602 fn ends_select_head(&self, index: usize) -> bool {
608 if !matches!(
609 self.chunks.get(index + 1),
610 Some(SQLChunk::Token(Token::FROM))
611 ) {
612 return false;
613 }
614 let token_at = |position: Option<usize>| match position.and_then(|p| self.chunks.get(p)) {
615 Some(SQLChunk::Token(token)) => Some(*token),
616 _ => None,
617 };
618
619 match self.chunks[index] {
620 SQLChunk::Token(Token::SELECT) => true,
621 SQLChunk::Token(Token::DISTINCT) => {
622 matches!(token_at(index.checked_sub(1)), Some(Token::SELECT))
623 }
624 SQLChunk::Token(Token::RPAREN) => {
625 let mut depth = 0usize;
627 let mut open = None;
628 for position in (0..index).rev() {
629 match self.chunks[position] {
630 SQLChunk::Token(Token::RPAREN) => depth += 1,
631 SQLChunk::Token(Token::LPAREN) if depth == 0 => {
632 open = Some(position);
633 break;
634 }
635 SQLChunk::Token(Token::LPAREN) => depth -= 1,
636 _ => {}
637 }
638 }
639 open.is_some_and(|open| {
640 matches!(token_at(open.checked_sub(1)), Some(Token::ON))
641 && matches!(token_at(open.checked_sub(2)), Some(Token::DISTINCT))
642 && matches!(token_at(open.checked_sub(3)), Some(Token::SELECT))
643 })
644 }
645 _ => false,
646 }
647 }
648
649 #[inline]
653 pub(crate) fn write_select_columns(&self, buf: &mut impl core::fmt::Write, head_end: usize) {
654 let chunks = self.chunks.get(head_end + 1..head_end + 3);
655 match chunks {
656 Some([SQLChunk::Token(Token::FROM), SQLChunk::Table(_)]) => {
657 let _ = buf.write_char(' ');
658 let mut first = true;
659 let mut depth = 0usize;
660
661 for (index, chunk) in self.chunks.iter().enumerate().skip(head_end + 2) {
662 match chunk {
663 SQLChunk::Token(Token::LPAREN) => depth += 1,
664 SQLChunk::Token(Token::RPAREN) if depth == 0 => break,
665 SQLChunk::Token(Token::RPAREN) => depth -= 1,
666 SQLChunk::Token(
667 Token::WHERE
668 | Token::GROUP
669 | Token::HAVING
670 | Token::ORDER
671 | Token::LIMIT
672 | Token::OFFSET
673 | Token::WINDOW
674 | Token::FOR
675 | Token::UNION
676 | Token::INTERSECT
677 | Token::EXCEPT
678 | Token::SELECT,
679 ) if depth == 0 => break,
680 SQLChunk::Table(table) if depth == 0 => {
681 if !first {
682 let _ = buf.write_str(", ");
683 }
684 let alias = match self.chunks.get(index + 1..index + 3) {
685 Some([SQLChunk::Token(Token::AS), SQLChunk::Ident(alias)]) => {
686 Some(alias.as_ref())
687 }
688 _ => None,
689 };
690 Self::write_qualified_columns_as(buf, table, alias);
691 first = false;
692 }
693 _ => {}
694 }
695 }
696 }
697 Some([SQLChunk::Token(Token::FROM), _]) => {
698 let _ = buf.write_char(' ');
699 let _ = buf.write_str(Token::STAR.as_str());
700 }
701 _ => {}
702 }
703 }
704
705 #[inline]
707 pub fn write_qualified_columns(buf: &mut impl core::fmt::Write, table: &TableSqlRef) {
708 Self::write_qualified_columns_as(buf, table, None);
709 }
710
711 #[inline]
712 fn write_qualified_columns_as(
713 buf: &mut impl core::fmt::Write,
714 table: &TableSqlRef,
715 alias: Option<&str>,
716 ) {
717 if table.column_names.is_empty() {
718 if let Some(alias) = alias {
719 chunk::write_dialect_quoted_ident(V::DIALECT, buf, alias);
720 } else {
721 if let Some(schema) = table.schema {
722 chunk::write_dialect_quoted_ident(V::DIALECT, buf, schema);
723 let _ = buf.write_char('.');
724 }
725 chunk::write_dialect_quoted_ident(V::DIALECT, buf, table.name);
726 }
727 let _ = buf.write_str(".*");
728 return;
729 }
730
731 for (i, col_name) in table.column_names.iter().enumerate() {
732 if i > 0 {
733 let _ = buf.write_str(", ");
734 }
735 if let Some(alias) = alias {
736 chunk::write_dialect_quoted_ident(V::DIALECT, buf, alias);
737 } else {
738 if let Some(schema) = table.schema {
739 chunk::write_dialect_quoted_ident(V::DIALECT, buf, schema);
740 let _ = buf.write_char('.');
741 }
742 chunk::write_dialect_quoted_ident(V::DIALECT, buf, table.name);
743 }
744 let _ = buf.write_char('.');
745 chunk::write_dialect_quoted_ident(V::DIALECT, buf, col_name);
746 }
747 }
748
749 #[inline]
751 fn needs_space(&self, index: usize) -> bool {
752 let Some(next) = self.chunks.get(index + 1) else {
753 return false;
754 };
755
756 let current = &self.chunks[index];
757 chunk_needs_space(current, next)
758 }
759
760 #[inline]
761 fn render_capacity_estimate(&self) -> (usize, usize) {
762 let mut sql_cap = 0usize;
763 let mut param_cap = 0usize;
764
765 for chunk in &self.chunks {
766 sql_cap = sql_cap.saturating_add(match chunk {
767 SQLChunk::Ident(_) | SQLChunk::Raw(_) => 20,
768 SQLChunk::Column(_) => 30,
769 SQLChunk::Table(_) => 15,
770 SQLChunk::Token(_) => 8,
771 SQLChunk::Number(_) | SQLChunk::Param(_) => 4,
772 });
773 if matches!(chunk, SQLChunk::Param(_)) {
774 param_cap = param_cap.saturating_add(1);
775 }
776 }
777
778 (sql_cap.max(128), param_cap)
779 }
780
781 #[inline]
784 pub fn params(&self) -> impl Iterator<Item = &V> + use<'_, V> {
785 self.chunks.iter().filter_map(|chunk| {
786 if let SQLChunk::Param(Param {
787 value: Some(value), ..
788 }) = chunk
789 {
790 Some(value.as_ref())
791 } else {
792 None
793 }
794 })
795 }
796
797 #[must_use]
799 pub fn bind<T: SQLParam + Into<V>>(
800 self,
801 params: impl IntoIterator<Item: Into<ParamBind<'a, T>>>,
802 ) -> Self {
803 #[cfg(feature = "profiling")]
804 profile_sql!("bind");
805
806 let binds: SmallVec<[(&str, V); 4]> = params
807 .into_iter()
808 .map(Into::into)
809 .map(|p| (p.name, p.value.into()))
810 .collect();
811
812 if binds.len() <= 4 {
813 let bound_chunks: SmallVec<[SQLChunk<'a, V>; 8]> = self
814 .chunks
815 .into_iter()
816 .map(|chunk| match chunk {
817 SQLChunk::Param(mut param) => {
818 if let Some(name) = param.placeholder.name
819 && let Some((_, value)) =
820 binds.iter().find(|(param_name, _)| *param_name == name)
821 {
822 param.value = Some(Cow::Owned(value.clone()));
823 }
824 SQLChunk::Param(param)
825 }
826 other => other,
827 })
828 .collect();
829
830 return SQL {
831 chunks: bound_chunks,
832 };
833 }
834
835 let param_map: HashMap<&str, V> = binds.into_iter().collect();
836 let bound_chunks: SmallVec<[SQLChunk<'a, V>; 8]> = self
837 .chunks
838 .into_iter()
839 .map(|chunk| match chunk {
840 SQLChunk::Param(mut param) => {
841 if let Some(name) = param.placeholder.name
842 && let Some(value) = param_map.get(name)
843 {
844 param.value = Some(Cow::Owned(value.clone()));
845 }
846 SQLChunk::Param(param)
847 }
848 other => other,
849 })
850 .collect();
851
852 SQL {
853 chunks: bound_chunks,
854 }
855 }
856}
857
858#[derive(Default)]
865pub(crate) struct SQLiteNamedParams<'n> {
866 seen: SmallVec<[&'n str; 4]>,
867}
868
869impl<'n> SQLiteNamedParams<'n> {
870 pub(crate) fn is_repeat(&mut self, name: &'n str) -> bool {
873 if name.is_empty() {
874 return false;
875 }
876 if self.seen.contains(&name) {
877 true
878 } else {
879 self.seen.push(name);
880 false
881 }
882 }
883}
884
885#[inline]
888pub(crate) fn chunk_needs_space<V: SQLParam>(
889 current: &SQLChunk<'_, V>,
890 next: &SQLChunk<'_, V>,
891) -> bool {
892 if let SQLChunk::Raw(text) = current
894 && text.ends_with(' ')
895 {
896 return false;
897 }
898
899 if let SQLChunk::Raw(text) = next
901 && text.starts_with(' ')
902 {
903 return false;
904 }
905
906 match (current, next) {
907 (_, SQLChunk::Token(Token::RPAREN | Token::COMMA | Token::SEMI | Token::DOT))
910 | (SQLChunk::Token(Token::LPAREN | Token::DOT), _) => false,
911 (SQLChunk::Token(Token::COMMA), _) => true,
913 (SQLChunk::Token(Token::RPAREN), next) => next.is_word_like(),
915 (SQLChunk::Raw(_), SQLChunk::Token(Token::LPAREN))
919 if V::DIALECT == crate::Dialect::MySQL =>
920 {
921 false
922 }
923 (current, SQLChunk::Token(Token::LPAREN)) => current.is_word_like(),
925 (SQLChunk::Token(t), _) if t.is_operator() => true,
927 (_, SQLChunk::Token(t)) if t.is_operator() => true,
928 _ => current.is_word_like() && next.is_word_like(),
930 }
931}
932
933impl<V: SQLParam> Default for SQL<'_, V> {
936 #[inline]
937 fn default() -> Self {
938 Self::empty()
939 }
940}
941
942impl<'a, V: SQLParam + 'a> From<&'a str> for SQL<'a, V> {
943 #[inline]
944 fn from(s: &'a str) -> Self {
945 SQL::raw(s)
946 }
947}
948
949impl<V: SQLParam> From<Token> for SQL<'_, V> {
950 #[inline]
951 fn from(value: Token) -> Self {
952 SQL::token(value)
953 }
954}
955
956impl<'a, V: SQLParam + 'a> AsRef<Self> for SQL<'a, V> {
957 #[inline]
958 fn as_ref(&self) -> &Self {
959 self
960 }
961}
962
963impl<V: SQLParam + core::fmt::Display> Display for SQL<'_, V> {
964 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
965 let params: Vec<_> = self.params().collect();
967 write!(f, r#"sql: "{}", params: {:?}"#, self.sql(), params)
968 }
969}
970
971impl<'a, V: SQLParam + 'a> ToSQL<'a, V> for SQL<'a, V> {
972 fn to_sql(&self) -> Self {
973 self.clone()
974 }
975
976 fn into_sql(self) -> Self {
977 self
978 }
979}
980
981impl<'a, V: SQLParam, T> FromIterator<T> for SQL<'a, V>
982where
983 SQLChunk<'a, V>: From<T>,
984{
985 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
986 let chunks = iter
987 .into_iter()
988 .map(SQLChunk::from)
989 .collect::<SmallVec<_>>();
990 Self { chunks }
991 }
992}
993
994impl<'a, V: SQLParam> IntoIterator for SQL<'a, V> {
995 type Item = SQLChunk<'a, V>;
996 type IntoIter = smallvec::IntoIter<[SQLChunk<'a, V>; 8]>;
997
998 fn into_iter(self) -> Self::IntoIter {
999 self.chunks.into_iter()
1000 }
1001}
1002
1003#[cfg(test)]
1004mod tests {
1005 use super::*;
1006 use crate::{Dialect, MySQLDialect};
1007
1008 #[derive(Clone, Debug)]
1009 struct TestParam;
1010
1011 impl SQLParam for TestParam {
1012 const DIALECT: Dialect = Dialect::MySQL;
1013 type DialectMarker = MySQLDialect;
1014 }
1015
1016 impl From<TestParam> for Cow<'_, TestParam> {
1017 fn from(value: TestParam) -> Self {
1018 Cow::Owned(value)
1019 }
1020 }
1021
1022 #[test]
1023 fn owning_mapped_params_preserves_every_chunk_kind() {
1024 let raw = String::from("COALESCE(");
1025 let identifier = String::from("display_name");
1026 let sql = SQL::raw(raw.as_str())
1027 .append(SQL::ident(identifier.as_str()))
1028 .push(Token::COMMA)
1029 .append(SQL::param(TestParam))
1030 .push(Token::COMMA)
1031 .push(Param::<TestParam>::from(Placeholder::named("fallback")))
1032 .push(Token::RPAREN);
1033
1034 let owned = sql.into_owned_with(|value| value);
1035 drop(raw);
1036 drop(identifier);
1037
1038 assert_eq!(owned.sql(), "COALESCE( `display_name`, ?, ?)");
1039 assert_eq!(owned.params().count(), 1);
1040 assert_eq!(
1041 owned
1042 .chunks
1043 .iter()
1044 .filter(|chunk| matches!(chunk, SQLChunk::Param(param) if param.value.is_none()))
1045 .count(),
1046 1
1047 );
1048 }
1049
1050 #[test]
1051 fn columns_renders_an_identifier_list() {
1052 let columns = [
1053 ColumnRef::sql("users", "first"),
1054 ColumnRef::sql("users", "last`name"),
1055 ];
1056
1057 assert_eq!(
1058 SQL::<TestParam>::columns(&columns).sql(),
1059 "`first`, `last``name`"
1060 );
1061 }
1062
1063 #[test]
1064 fn select_star_uses_the_table_alias_to_qualify_columns() {
1065 let table = TableRef::sql("users", &["id", "name"]);
1066 let query = SQL::<TestParam>::from(Token::SELECT)
1067 .push(Token::FROM)
1068 .append(SQL::table(table).alias("u"));
1069
1070 assert_eq!(
1071 query.sql(),
1072 "SELECT `u`.`id`, `u`.`name` FROM `users` AS `u`"
1073 );
1074 }
1075
1076 #[test]
1077 fn nested_select_star_keeps_derived_alias_in_outer_projection() {
1078 let source = SQL::<TestParam>::from(Token::SELECT)
1079 .push(Token::FROM)
1080 .append(SQL::table(TableRef::sql("posts", &["id", "name"])))
1081 .parens()
1082 .push(Token::AS)
1083 .append(SQL::table(TableRef::sql("post_rows", &[])));
1084 let query = SQL::<TestParam>::from(Token::SELECT)
1085 .push(Token::FROM)
1086 .append(SQL::table(TableRef::sql("users", &["id"])))
1087 .append(SQL::raw(" INNER JOIN LATERAL "))
1088 .append(source)
1089 .push(Token::ON)
1090 .append(SQL::raw("TRUE"));
1091
1092 assert_eq!(
1093 query.sql(),
1094 "SELECT `users`.`id`, `post_rows`.* FROM `users` INNER JOIN LATERAL (SELECT `posts`.`id`, `posts`.`name` FROM `posts`) AS `post_rows` ON TRUE"
1095 );
1096 }
1097}