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(table)],
122 }
123 }
124
125 #[inline]
127 #[must_use]
128 pub fn column(column: ColumnRef) -> Self {
129 Self {
130 chunks: smallvec::smallvec![SQLChunk::Column(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 #[must_use]
267 pub fn alias(self, name: impl Into<Cow<'a, str>>) -> Self {
268 self.push(Token::AS).push(SQLChunk::Ident(name.into()))
269 }
270
271 pub fn param_list<I>(values: I) -> Self
274 where
275 I: IntoIterator,
276 I::Item: Into<Cow<'a, V>>,
277 {
278 let iter = values.into_iter();
279 let (lower, _) = iter.size_hint();
280 let mut chunks = SmallVec::with_capacity(lower.saturating_mul(2));
281 for (i, v) in iter.enumerate() {
282 if i > 0 {
283 chunks.push(SQLChunk::Token(Token::COMMA));
284 }
285 chunks.push(SQLChunk::Param(Param {
286 value: Some(v.into()),
287 placeholder: Self::POSITIONAL_PLACEHOLDER,
288 }));
289 }
290 SQL { chunks }
291 }
292
293 pub fn assignments<I, T>(pairs: I) -> Self
296 where
297 I: IntoIterator<Item = (&'static str, T)>,
298 T: Into<Cow<'a, V>>,
299 {
300 let iter = pairs.into_iter();
301 let (lower, _) = iter.size_hint();
302 let mut chunks = SmallVec::with_capacity(lower.saturating_mul(4));
304 for (i, (col, val)) in iter.enumerate() {
305 if i > 0 {
306 chunks.push(SQLChunk::Token(Token::COMMA));
307 }
308 chunks.push(SQLChunk::Ident(Cow::Borrowed(col)));
309 chunks.push(SQLChunk::Token(Token::EQ));
310 chunks.push(SQLChunk::Param(Param {
311 value: Some(val.into()),
312 placeholder: Self::POSITIONAL_PLACEHOLDER,
313 }));
314 }
315 SQL { chunks }
316 }
317
318 pub fn assignments_sql<I>(pairs: I) -> Self
324 where
325 I: IntoIterator<Item = (&'static str, Self)>,
326 {
327 let iter = pairs.into_iter();
328 let (lower, _) = iter.size_hint();
329 let mut chunks = SmallVec::with_capacity(lower.saturating_mul(4));
330 for (i, (col, sql)) in iter.enumerate() {
331 if i > 0 {
332 chunks.push(SQLChunk::Token(Token::COMMA));
333 }
334 chunks.push(SQLChunk::Ident(Cow::Borrowed(col)));
335 chunks.push(SQLChunk::Token(Token::EQ));
336 chunks.extend(sql.chunks);
337 }
338 SQL { chunks }
339 }
340
341 pub fn map_params<U: SQLParam>(self, mut f: impl FnMut(V) -> U) -> SQL<'a, U> {
349 let chunks = self
350 .chunks
351 .into_iter()
352 .map(|chunk| match chunk {
353 SQLChunk::Token(t) => SQLChunk::Token(t),
354 SQLChunk::Ident(s) => SQLChunk::Ident(s),
355 SQLChunk::Raw(s) => SQLChunk::Raw(s),
356 SQLChunk::Number(n) => SQLChunk::Number(n),
357 SQLChunk::Param(param) => SQLChunk::Param(Param::new(
358 param.placeholder,
359 param.value.map(|cow| Cow::Owned(f(cow.into_owned()))),
360 )),
361 SQLChunk::Table(t) => SQLChunk::Table(t),
362 SQLChunk::Column(c) => SQLChunk::Column(c),
363 })
364 .collect();
365 SQL { chunks }
366 }
367
368 pub fn into_owned(self) -> OwnedSQL<V> {
370 OwnedSQL::from(self)
371 }
372
373 pub fn sql(&self) -> String {
376 #[cfg(feature = "profiling")]
377 profile_sql!("sql");
378 #[cfg(feature = "profiling")]
379 crate::drizzle_profile_scope!("sql_render", "sql.estimate");
380 let sql_cap = self.chunks.len().saturating_mul(8).max(128);
381 let mut buf = String::with_capacity(sql_cap);
382 self.write_to(&mut buf);
383 buf
384 }
385
386 pub fn build(&self) -> (String, SmallVec<[&V; 8]>) {
391 self.build_with(crate::dialect::ParamStyle::for_dialect(V::DIALECT))
392 }
393
394 pub fn build_with(&self, style: crate::dialect::ParamStyle) -> (String, SmallVec<[&V; 8]>) {
399 use crate::dialect::Dialect;
400
401 #[cfg(feature = "profiling")]
402 crate::drizzle_profile_scope!("sql_render", "build");
403 #[cfg(feature = "profiling")]
404 crate::drizzle_profile_scope!("sql_render", "build.estimate");
405 let sql_cap = self.chunks.len().saturating_mul(8).max(128);
406 let param_cap = self.chunks.len().saturating_div(8).max(8);
407 let mut buf = String::with_capacity(sql_cap);
408 let mut params: SmallVec<[&V; 8]> = SmallVec::with_capacity(param_cap);
409 let mut param_index = 1usize;
410
411 #[cfg(feature = "profiling")]
412 crate::drizzle_profile_scope!("sql_render", "build.render");
413 for (i, chunk) in self.chunks.iter().enumerate() {
414 match chunk {
415 SQLChunk::Token(Token::SELECT) => {
416 chunk.write(&mut buf);
417 self.write_select_columns(&mut buf, i);
418 }
419 SQLChunk::Param(param) => {
420 if let Some(name) = param.placeholder.name
421 && V::DIALECT == Dialect::SQLite
422 {
423 let _ = buf.write_char(':');
424 let _ = buf.write_str(name);
425 } else {
426 style.write(param_index, &mut buf);
427 }
428 param_index += 1;
429 if let Some(value) = ¶m.value {
430 params.push(value.as_ref());
431 }
432 }
433 _ => chunk.write(&mut buf),
434 }
435
436 if self.needs_space(i) {
437 let _ = buf.write_char(' ');
438 }
439 }
440
441 (buf, params)
442 }
443
444 pub fn write_to(&self, buf: &mut impl core::fmt::Write) {
447 self.write_to_with(buf, crate::dialect::ParamStyle::for_dialect(V::DIALECT));
448 }
449
450 pub fn write_to_with(
453 &self,
454 buf: &mut impl core::fmt::Write,
455 style: crate::dialect::ParamStyle,
456 ) {
457 use crate::dialect::Dialect;
458
459 #[cfg(feature = "profiling")]
460 crate::drizzle_profile_scope!("sql_render", "write_to");
461 let mut param_index = 1usize;
462 for (i, chunk) in self.chunks.iter().enumerate() {
463 match chunk {
464 SQLChunk::Token(Token::SELECT) => {
465 chunk.write(buf);
466 self.write_select_columns(buf, i);
467 }
468 SQLChunk::Param(param) => {
469 if let Some(name) = param.placeholder.name
470 && V::DIALECT == Dialect::SQLite
471 {
472 let _ = buf.write_char(':');
473 let _ = buf.write_str(name);
474 } else {
475 style.write(param_index, buf);
476 }
477 param_index += 1;
478 }
479 _ => chunk.write(buf),
480 }
481
482 if self.needs_space(i) {
483 let _ = buf.write_char(' ');
484 }
485 }
486 }
487
488 pub fn write_chunk_to(
490 &self,
491 buf: &mut impl core::fmt::Write,
492 chunk: &SQLChunk<'a, V>,
493 index: usize,
494 ) {
495 match chunk {
496 SQLChunk::Token(Token::SELECT) => {
497 chunk.write(buf);
498 self.write_select_columns(buf, index);
499 }
500 _ => chunk.write(buf),
501 }
502 }
503
504 pub(crate) fn write_select_columns(
506 &self,
507 buf: &mut impl core::fmt::Write,
508 select_index: usize,
509 ) {
510 let chunks = self.chunks.get(select_index + 1..select_index + 3);
511 match chunks {
512 Some([SQLChunk::Token(Token::FROM), SQLChunk::Table(table)]) => {
513 let _ = buf.write_char(' ');
514 Self::write_qualified_columns(buf, table);
515 }
516 Some([SQLChunk::Token(Token::FROM), _]) => {
517 let _ = buf.write_char(' ');
518 let _ = buf.write_str(Token::STAR.as_str());
519 }
520 _ => {}
521 }
522 }
523
524 pub fn write_qualified_columns(buf: &mut impl core::fmt::Write, table: &TableRef) {
526 if table.column_names.is_empty() {
527 let _ = buf.write_char('*');
528 return;
529 }
530
531 for (i, col_name) in table.column_names.iter().enumerate() {
532 if i > 0 {
533 let _ = buf.write_str(", ");
534 }
535 chunk::write_quoted_ident(buf, table.name);
536 let _ = buf.write_char('.');
537 chunk::write_quoted_ident(buf, col_name);
538 }
539 }
540
541 fn needs_space(&self, index: usize) -> bool {
543 let Some(next) = self.chunks.get(index + 1) else {
544 return false;
545 };
546
547 let current = &self.chunks[index];
548 chunk_needs_space(current, next)
549 }
550
551 pub fn params(&self) -> impl Iterator<Item = &V> + use<'_, V> {
554 self.chunks.iter().filter_map(|chunk| {
555 if let SQLChunk::Param(Param {
556 value: Some(value), ..
557 }) = chunk
558 {
559 Some(value.as_ref())
560 } else {
561 None
562 }
563 })
564 }
565
566 #[must_use]
568 pub fn bind<T: SQLParam + Into<V>>(
569 self,
570 params: impl IntoIterator<Item: Into<ParamBind<'a, T>>>,
571 ) -> Self {
572 #[cfg(feature = "profiling")]
573 profile_sql!("bind");
574
575 let param_map: HashMap<&str, V> = params
576 .into_iter()
577 .map(Into::into)
578 .map(|p| (p.name, p.value.into()))
579 .collect();
580
581 let bound_chunks: SmallVec<[SQLChunk<'a, V>; 8]> = self
582 .chunks
583 .into_iter()
584 .map(|chunk| match chunk {
585 SQLChunk::Param(mut param) => {
586 if let Some(name) = param.placeholder.name
587 && let Some(value) = param_map.get(name)
588 {
589 param.value = Some(Cow::Owned(value.clone()));
590 }
591 SQLChunk::Param(param)
592 }
593 other => other,
594 })
595 .collect();
596
597 SQL {
598 chunks: bound_chunks,
599 }
600 }
601}
602
603pub(crate) fn chunk_needs_space<V: SQLParam>(
606 current: &SQLChunk<'_, V>,
607 next: &SQLChunk<'_, V>,
608) -> bool {
609 if let SQLChunk::Raw(text) = current
611 && text.ends_with(' ')
612 {
613 return false;
614 }
615
616 if let SQLChunk::Raw(text) = next
618 && text.starts_with(' ')
619 {
620 return false;
621 }
622
623 match (current, next) {
624 (_, SQLChunk::Token(Token::RPAREN | Token::COMMA | Token::SEMI | Token::DOT))
627 | (SQLChunk::Token(Token::LPAREN | Token::DOT), _) => false,
628 (SQLChunk::Token(Token::COMMA), _) => true,
630 (SQLChunk::Token(Token::RPAREN), next) => next.is_word_like(),
632 (current, SQLChunk::Token(Token::LPAREN)) => current.is_word_like(),
634 (SQLChunk::Token(t), _) if t.is_operator() => true,
636 (_, SQLChunk::Token(t)) if t.is_operator() => true,
637 _ => current.is_word_like() && next.is_word_like(),
639 }
640}
641
642impl<V: SQLParam> Default for SQL<'_, V> {
645 fn default() -> Self {
646 Self::empty()
647 }
648}
649
650impl<'a, V: SQLParam + 'a> From<&'a str> for SQL<'a, V> {
651 fn from(s: &'a str) -> Self {
652 SQL::raw(s)
653 }
654}
655
656impl<V: SQLParam> From<Token> for SQL<'_, V> {
657 fn from(value: Token) -> Self {
658 SQL::token(value)
659 }
660}
661
662impl<'a, V: SQLParam + 'a> AsRef<Self> for SQL<'a, V> {
663 fn as_ref(&self) -> &Self {
664 self
665 }
666}
667
668impl<V: SQLParam + core::fmt::Display> Display for SQL<'_, V> {
669 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
670 let params: Vec<_> = self.params().collect();
672 write!(f, r#"sql: "{}", params: {:?}"#, self.sql(), params)
673 }
674}
675
676impl<'a, V: SQLParam + 'a> ToSQL<'a, V> for SQL<'a, V> {
677 fn to_sql(&self) -> Self {
678 self.clone()
679 }
680
681 fn into_sql(self) -> Self {
682 self
683 }
684}
685
686impl<'a, V: SQLParam, T> FromIterator<T> for SQL<'a, V>
687where
688 SQLChunk<'a, V>: From<T>,
689{
690 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
691 let chunks = iter
692 .into_iter()
693 .map(SQLChunk::from)
694 .collect::<SmallVec<_>>();
695 Self { chunks }
696 }
697}
698
699impl<'a, V: SQLParam> IntoIterator for SQL<'a, V> {
700 type Item = SQLChunk<'a, V>;
701 type IntoIter = smallvec::IntoIter<[SQLChunk<'a, V>; 8]>;
702
703 fn into_iter(self) -> Self::IntoIter {
704 self.chunks.into_iter()
705 }
706}