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 #[inline]
76 pub fn raw(text: impl Into<Cow<'a, str>>) -> Self {
77 Self {
78 chunks: smallvec::smallvec![SQLChunk::Raw(text.into())],
79 }
80 }
81
82 #[inline]
84 #[must_use]
85 pub fn number(value: usize) -> Self {
86 Self {
87 chunks: smallvec::smallvec![SQLChunk::Number(value)],
88 }
89 }
90
91 #[inline]
93 pub fn param(value: impl Into<Cow<'a, V>>) -> Self {
94 Self {
95 chunks: smallvec::smallvec![SQLChunk::Param(Param {
96 value: Some(value.into()),
97 placeholder: Self::POSITIONAL_PLACEHOLDER,
98 })],
99 }
100 }
101
102 #[inline]
106 pub fn bytes(bytes: impl Into<Cow<'a, [u8]>>) -> Self
107 where
108 V: From<&'a [u8]> + From<Vec<u8>> + Into<Cow<'a, V>>,
109 {
110 match bytes.into() {
111 Cow::Borrowed(value) => Self::param(V::from(value)),
112 Cow::Owned(value) => Self::param(V::from(value)),
113 }
114 }
115
116 #[inline]
118 #[must_use]
119 pub fn table(table: TableRef) -> Self {
120 Self {
121 chunks: smallvec::smallvec![SQLChunk::Table(TableSqlRef::from_table_ref(table))],
122 }
123 }
124
125 #[inline]
127 #[must_use]
128 pub fn column(column: ColumnRef) -> Self {
129 Self {
130 chunks: smallvec::smallvec![SQLChunk::Column(ColumnSqlRef::from_column_ref(column))],
131 }
132 }
133
134 #[inline]
137 pub fn func(name: &'static str, args: Self) -> Self {
138 let args = args.parens_if_subquery();
139 SQL::raw(name)
140 .push(Token::LPAREN)
141 .append(args)
142 .push(Token::RPAREN)
143 }
144
145 #[inline]
149 #[must_use]
150 pub fn append(mut self, other: impl Into<Self>) -> Self {
151 #[cfg(feature = "profiling")]
152 profile_sql!("append");
153 let other = other.into();
154
155 if self.chunks.is_empty() {
156 return other;
157 }
158 if other.chunks.is_empty() {
159 return self;
160 }
161
162 self.chunks.extend(other.chunks);
163 self
164 }
165
166 #[inline]
167 pub fn append_mut(&mut self, other: impl Into<Self>) {
168 #[cfg(feature = "profiling")]
169 profile_sql!("append_mut");
170 let other = other.into();
171
172 if self.chunks.is_empty() {
173 self.chunks = other.chunks;
174 return;
175 }
176 if other.chunks.is_empty() {
177 return;
178 }
179
180 self.chunks.extend(other.chunks);
181 }
182
183 #[inline]
185 #[must_use]
186 pub fn push(mut self, chunk: impl Into<SQLChunk<'a, V>>) -> Self {
187 self.chunks.push(chunk.into());
188 self
189 }
190
191 #[inline]
192 pub fn push_mut(&mut self, chunk: impl Into<SQLChunk<'a, V>>) {
193 self.chunks.push(chunk.into());
194 }
195
196 #[inline]
198 #[must_use]
199 pub fn with_capacity(mut self, additional: usize) -> Self {
200 self.chunks.reserve(additional);
201 self
202 }
203
204 pub fn join<T>(sqls: T, separator: Token) -> Self
208 where
209 T: IntoIterator,
210 T::Item: ToSQL<'a, V>,
211 {
212 #[cfg(feature = "profiling")]
213 profile_sql!("join");
214
215 let mut iter = sqls.into_iter();
216 let Some(first) = iter.next() else {
217 return SQL::empty();
218 };
219
220 let mut result = first.into_sql();
221 let (lower, upper) = iter.size_hint();
222 if let Some(upper) = upper {
223 result.chunks.reserve(upper.saturating_mul(2));
224 } else if lower > 0 {
225 result.chunks.reserve(lower * 2);
226 }
227
228 for item in iter {
229 result.chunks.push(SQLChunk::Token(separator));
230 let other = item.into_sql();
231 if !other.chunks.is_empty() {
232 result.chunks.extend(other.chunks);
233 }
234 }
235 result
236 }
237
238 #[inline]
240 #[must_use]
241 pub fn parens(self) -> Self {
242 SQL::token(Token::LPAREN).append(self).push(Token::RPAREN)
243 }
244
245 #[inline]
247 #[must_use]
248 pub fn parens_if_subquery(self) -> Self {
249 if self.is_subquery() {
250 self.parens()
251 } else {
252 self
253 }
254 }
255
256 #[inline]
258 pub fn is_subquery(&self) -> bool {
259 matches!(
260 self.chunks.first(),
261 Some(SQLChunk::Token(Token::SELECT | Token::WITH))
262 )
263 }
264
265 #[inline]
267 #[must_use]
268 pub fn alias(self, name: impl Into<Cow<'a, str>>) -> Self {
269 self.push(Token::AS).push(SQLChunk::Ident(name.into()))
270 }
271
272 #[inline]
275 pub fn param_list<I>(values: I) -> Self
276 where
277 I: IntoIterator,
278 I::Item: Into<Cow<'a, V>>,
279 {
280 let iter = values.into_iter();
281 let (lower, upper) = iter.size_hint();
282 let count = upper.unwrap_or(lower);
283 let mut chunks = SmallVec::with_capacity(count.saturating_mul(2).saturating_sub(1));
284 for (i, v) in iter.enumerate() {
285 if i > 0 {
286 chunks.push(SQLChunk::Token(Token::COMMA));
287 }
288 chunks.push(SQLChunk::Param(Param {
289 value: Some(v.into()),
290 placeholder: Self::POSITIONAL_PLACEHOLDER,
291 }));
292 }
293 SQL { chunks }
294 }
295
296 #[inline]
299 pub fn assignments<I, T>(pairs: I) -> Self
300 where
301 I: IntoIterator<Item = (&'static str, T)>,
302 T: Into<Cow<'a, V>>,
303 {
304 let iter = pairs.into_iter();
305 let (lower, upper) = iter.size_hint();
306 let count = upper.unwrap_or(lower);
307 let mut chunks = SmallVec::with_capacity(count.saturating_mul(4).saturating_sub(1));
309 for (i, (col, val)) in iter.enumerate() {
310 if i > 0 {
311 chunks.push(SQLChunk::Token(Token::COMMA));
312 }
313 chunks.push(SQLChunk::Ident(Cow::Borrowed(col)));
314 chunks.push(SQLChunk::Token(Token::EQ));
315 chunks.push(SQLChunk::Param(Param {
316 value: Some(val.into()),
317 placeholder: Self::POSITIONAL_PLACEHOLDER,
318 }));
319 }
320 SQL { chunks }
321 }
322
323 #[inline]
329 pub fn assignments_sql<I>(pairs: I) -> Self
330 where
331 I: IntoIterator<Item = (&'static str, Self)>,
332 {
333 let iter = pairs.into_iter();
334 let (lower, upper) = iter.size_hint();
335 let count = upper.unwrap_or(lower);
336 let mut chunks = SmallVec::with_capacity(count.saturating_mul(4).saturating_sub(1));
337 for (i, (col, sql)) in iter.enumerate() {
338 if i > 0 {
339 chunks.push(SQLChunk::Token(Token::COMMA));
340 }
341 chunks.push(SQLChunk::Ident(Cow::Borrowed(col)));
342 chunks.push(SQLChunk::Token(Token::EQ));
343 chunks.extend(sql.chunks);
344 }
345 SQL { chunks }
346 }
347
348 pub fn map_params<U: SQLParam>(self, mut f: impl FnMut(V) -> U) -> SQL<'a, U> {
356 let chunks = self
357 .chunks
358 .into_iter()
359 .map(|chunk| match chunk {
360 SQLChunk::Token(t) => SQLChunk::Token(t),
361 SQLChunk::Ident(s) => SQLChunk::Ident(s),
362 SQLChunk::Raw(s) => SQLChunk::Raw(s),
363 SQLChunk::Number(n) => SQLChunk::Number(n),
364 SQLChunk::Param(param) => SQLChunk::Param(Param::new(
365 param.placeholder,
366 param.value.map(|cow| Cow::Owned(f(cow.into_owned()))),
367 )),
368 SQLChunk::Table(t) => SQLChunk::Table(t),
369 SQLChunk::Column(c) => SQLChunk::Column(c),
370 })
371 .collect();
372 SQL { chunks }
373 }
374
375 #[inline]
377 pub fn into_owned(self) -> OwnedSQL<V> {
378 OwnedSQL::from(self)
379 }
380
381 pub fn sql(&self) -> String {
384 #[cfg(feature = "profiling")]
385 profile_sql!("sql");
386 #[cfg(feature = "profiling")]
387 crate::drizzle_profile_scope!("sql_render", "sql.estimate");
388 let (sql_cap, _) = self.render_capacity_estimate();
389 let mut buf = String::with_capacity(sql_cap);
390 self.write_to(&mut buf);
391 buf
392 }
393
394 pub fn build(&self) -> (String, SmallVec<[&V; 8]>) {
399 self.build_with(crate::dialect::ParamStyle::for_dialect(V::DIALECT))
400 }
401
402 pub fn build_with(&self, style: crate::dialect::ParamStyle) -> (String, SmallVec<[&V; 8]>) {
407 use crate::dialect::Dialect;
408
409 #[cfg(feature = "profiling")]
410 crate::drizzle_profile_scope!("sql_render", "build");
411 #[cfg(feature = "profiling")]
412 crate::drizzle_profile_scope!("sql_render", "build.estimate");
413 let (sql_cap, param_cap) = self.render_capacity_estimate();
414 let mut buf = String::with_capacity(sql_cap);
415 let mut params: SmallVec<[&V; 8]> = SmallVec::with_capacity(param_cap);
416 let mut param_index = 1usize;
417
418 #[cfg(feature = "profiling")]
419 crate::drizzle_profile_scope!("sql_render", "build.render");
420 for (i, chunk) in self.chunks.iter().enumerate() {
421 match chunk {
422 SQLChunk::Token(Token::SELECT) => {
423 chunk.write(&mut buf);
424 self.write_select_columns(&mut buf, i);
425 }
426 SQLChunk::Param(param) => {
427 if let Some(name) = param.placeholder.name
428 && V::DIALECT == Dialect::SQLite
429 {
430 let _ = buf.write_char(':');
431 let _ = buf.write_str(name);
432 } else {
433 style.write(param_index, &mut buf);
434 }
435 param_index += 1;
436 if let Some(value) = ¶m.value {
437 params.push(value.as_ref());
438 }
439 }
440 _ => chunk.write(&mut buf),
441 }
442
443 if self.needs_space(i) {
444 let _ = buf.write_char(' ');
445 }
446 }
447
448 (buf, params)
449 }
450
451 #[inline]
454 pub fn write_to(&self, buf: &mut impl core::fmt::Write) {
455 self.write_to_with(buf, crate::dialect::ParamStyle::for_dialect(V::DIALECT));
456 }
457
458 pub fn write_to_with(
461 &self,
462 buf: &mut impl core::fmt::Write,
463 style: crate::dialect::ParamStyle,
464 ) {
465 use crate::dialect::Dialect;
466
467 #[cfg(feature = "profiling")]
468 crate::drizzle_profile_scope!("sql_render", "write_to");
469 let mut param_index = 1usize;
470 for (i, chunk) in self.chunks.iter().enumerate() {
471 match chunk {
472 SQLChunk::Token(Token::SELECT) => {
473 chunk.write(buf);
474 self.write_select_columns(buf, i);
475 }
476 SQLChunk::Param(param) => {
477 if let Some(name) = param.placeholder.name
478 && V::DIALECT == Dialect::SQLite
479 {
480 let _ = buf.write_char(':');
481 let _ = buf.write_str(name);
482 } else {
483 style.write(param_index, buf);
484 }
485 param_index += 1;
486 }
487 _ => chunk.write(buf),
488 }
489
490 if self.needs_space(i) {
491 let _ = buf.write_char(' ');
492 }
493 }
494 }
495
496 #[inline]
498 pub fn write_chunk_to(
499 &self,
500 buf: &mut impl core::fmt::Write,
501 chunk: &SQLChunk<'a, V>,
502 index: usize,
503 ) {
504 match chunk {
505 SQLChunk::Token(Token::SELECT) => {
506 chunk.write(buf);
507 self.write_select_columns(buf, index);
508 }
509 _ => chunk.write(buf),
510 }
511 }
512
513 #[inline]
515 pub(crate) fn write_select_columns(
516 &self,
517 buf: &mut impl core::fmt::Write,
518 select_index: usize,
519 ) {
520 let chunks = self.chunks.get(select_index + 1..select_index + 3);
521 match chunks {
522 Some([SQLChunk::Token(Token::FROM), SQLChunk::Table(table)]) => {
523 let _ = buf.write_char(' ');
524 Self::write_qualified_columns(buf, table);
525 }
526 Some([SQLChunk::Token(Token::FROM), _]) => {
527 let _ = buf.write_char(' ');
528 let _ = buf.write_str(Token::STAR.as_str());
529 }
530 _ => {}
531 }
532 }
533
534 #[inline]
536 pub fn write_qualified_columns(buf: &mut impl core::fmt::Write, table: &TableSqlRef) {
537 if table.column_names.is_empty() {
538 let _ = buf.write_char('*');
539 return;
540 }
541
542 for (i, col_name) in table.column_names.iter().enumerate() {
543 if i > 0 {
544 let _ = buf.write_str(", ");
545 }
546 chunk::write_quoted_ident(buf, table.name);
547 let _ = buf.write_char('.');
548 chunk::write_quoted_ident(buf, col_name);
549 }
550 }
551
552 #[inline]
554 fn needs_space(&self, index: usize) -> bool {
555 let Some(next) = self.chunks.get(index + 1) else {
556 return false;
557 };
558
559 let current = &self.chunks[index];
560 chunk_needs_space(current, next)
561 }
562
563 #[inline]
564 fn render_capacity_estimate(&self) -> (usize, usize) {
565 let mut sql_cap = 0usize;
566 let mut param_cap = 0usize;
567
568 for chunk in &self.chunks {
569 sql_cap = sql_cap.saturating_add(match chunk {
570 SQLChunk::Ident(_) | SQLChunk::Raw(_) => 20,
571 SQLChunk::Column(_) => 30,
572 SQLChunk::Table(_) => 15,
573 SQLChunk::Token(_) => 8,
574 SQLChunk::Number(_) | SQLChunk::Param(_) => 4,
575 });
576 if matches!(chunk, SQLChunk::Param(_)) {
577 param_cap = param_cap.saturating_add(1);
578 }
579 }
580
581 (sql_cap.max(128), param_cap)
582 }
583
584 #[inline]
587 pub fn params(&self) -> impl Iterator<Item = &V> + use<'_, V> {
588 self.chunks.iter().filter_map(|chunk| {
589 if let SQLChunk::Param(Param {
590 value: Some(value), ..
591 }) = chunk
592 {
593 Some(value.as_ref())
594 } else {
595 None
596 }
597 })
598 }
599
600 #[must_use]
602 pub fn bind<T: SQLParam + Into<V>>(
603 self,
604 params: impl IntoIterator<Item: Into<ParamBind<'a, T>>>,
605 ) -> Self {
606 #[cfg(feature = "profiling")]
607 profile_sql!("bind");
608
609 let binds: SmallVec<[(&str, V); 4]> = params
610 .into_iter()
611 .map(Into::into)
612 .map(|p| (p.name, p.value.into()))
613 .collect();
614
615 if binds.len() <= 4 {
616 let bound_chunks: SmallVec<[SQLChunk<'a, V>; 8]> = self
617 .chunks
618 .into_iter()
619 .map(|chunk| match chunk {
620 SQLChunk::Param(mut param) => {
621 if let Some(name) = param.placeholder.name
622 && let Some((_, value)) =
623 binds.iter().find(|(param_name, _)| *param_name == name)
624 {
625 param.value = Some(Cow::Owned(value.clone()));
626 }
627 SQLChunk::Param(param)
628 }
629 other => other,
630 })
631 .collect();
632
633 return SQL {
634 chunks: bound_chunks,
635 };
636 }
637
638 let param_map: HashMap<&str, V> = binds.into_iter().collect();
639 let bound_chunks: SmallVec<[SQLChunk<'a, V>; 8]> = self
640 .chunks
641 .into_iter()
642 .map(|chunk| match chunk {
643 SQLChunk::Param(mut param) => {
644 if let Some(name) = param.placeholder.name
645 && let Some(value) = param_map.get(name)
646 {
647 param.value = Some(Cow::Owned(value.clone()));
648 }
649 SQLChunk::Param(param)
650 }
651 other => other,
652 })
653 .collect();
654
655 SQL {
656 chunks: bound_chunks,
657 }
658 }
659}
660
661#[inline]
664pub(crate) fn chunk_needs_space<V: SQLParam>(
665 current: &SQLChunk<'_, V>,
666 next: &SQLChunk<'_, V>,
667) -> bool {
668 if let SQLChunk::Raw(text) = current
670 && text.ends_with(' ')
671 {
672 return false;
673 }
674
675 if let SQLChunk::Raw(text) = next
677 && text.starts_with(' ')
678 {
679 return false;
680 }
681
682 match (current, next) {
683 (_, SQLChunk::Token(Token::RPAREN | Token::COMMA | Token::SEMI | Token::DOT))
686 | (SQLChunk::Token(Token::LPAREN | Token::DOT), _) => false,
687 (SQLChunk::Token(Token::COMMA), _) => true,
689 (SQLChunk::Token(Token::RPAREN), next) => next.is_word_like(),
691 (current, SQLChunk::Token(Token::LPAREN)) => current.is_word_like(),
693 (SQLChunk::Token(t), _) if t.is_operator() => true,
695 (_, SQLChunk::Token(t)) if t.is_operator() => true,
696 _ => current.is_word_like() && next.is_word_like(),
698 }
699}
700
701impl<V: SQLParam> Default for SQL<'_, V> {
704 #[inline]
705 fn default() -> Self {
706 Self::empty()
707 }
708}
709
710impl<'a, V: SQLParam + 'a> From<&'a str> for SQL<'a, V> {
711 #[inline]
712 fn from(s: &'a str) -> Self {
713 SQL::raw(s)
714 }
715}
716
717impl<V: SQLParam> From<Token> for SQL<'_, V> {
718 #[inline]
719 fn from(value: Token) -> Self {
720 SQL::token(value)
721 }
722}
723
724impl<'a, V: SQLParam + 'a> AsRef<Self> for SQL<'a, V> {
725 #[inline]
726 fn as_ref(&self) -> &Self {
727 self
728 }
729}
730
731impl<V: SQLParam + core::fmt::Display> Display for SQL<'_, V> {
732 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
733 let params: Vec<_> = self.params().collect();
735 write!(f, r#"sql: "{}", params: {:?}"#, self.sql(), params)
736 }
737}
738
739impl<'a, V: SQLParam + 'a> ToSQL<'a, V> for SQL<'a, V> {
740 fn to_sql(&self) -> Self {
741 self.clone()
742 }
743
744 fn into_sql(self) -> Self {
745 self
746 }
747}
748
749impl<'a, V: SQLParam, T> FromIterator<T> for SQL<'a, V>
750where
751 SQLChunk<'a, V>: From<T>,
752{
753 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
754 let chunks = iter
755 .into_iter()
756 .map(SQLChunk::from)
757 .collect::<SmallVec<_>>();
758 Self { chunks }
759 }
760}
761
762impl<'a, V: SQLParam> IntoIterator for SQL<'a, V> {
763 type Item = SQLChunk<'a, V>;
764 type IntoIter = smallvec::IntoIter<[SQLChunk<'a, V>; 8]>;
765
766 fn into_iter(self) -> Self::IntoIter {
767 self.chunks.into_iter()
768 }
769}