Skip to main content

drizzle_core/sql/
mod.rs

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/// SQL fragment builder with flat chunk storage.
24///
25/// Uses `SmallVec<[SQLChunk; 8]>` for inline storage of typical SQL fragments
26/// without heap allocation.
27#[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    // ==================== constructors ====================
36
37    /// Creates an empty SQL fragment
38    #[inline]
39    #[must_use]
40    pub const fn empty() -> Self {
41        Self {
42            chunks: SmallVec::new_const(),
43        }
44    }
45
46    // ==================== constructors ====================
47
48    /// Creates SQL with a single token
49    #[inline]
50    #[must_use]
51    pub fn token(t: Token) -> Self {
52        Self {
53            chunks: smallvec::smallvec![SQLChunk::Token(t)],
54        }
55    }
56
57    /// Creates an empty SQL fragment with pre-allocated chunk capacity.
58    #[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    /// Creates SQL with a quoted identifier
67    #[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    /// Creates SQL with raw text (unquoted)
75    #[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    /// Creates SQL with a single unsigned integer literal.
83    #[inline]
84    #[must_use]
85    pub fn number(value: usize) -> Self {
86        Self {
87            chunks: smallvec::smallvec![SQLChunk::Number(value)],
88        }
89    }
90
91    /// Creates SQL with a single parameter value
92    #[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    /// Creates SQL with a binary parameter value (BLOB/bytea)
103    ///
104    /// Prefer this over `SQL::param(Vec<u8>)` to avoid list semantics.
105    #[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    /// Creates SQL referencing a table
117    #[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    /// Creates SQL referencing a column
126    #[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    /// Creates SQL for a function call: NAME(args)
135    /// Subqueries are automatically wrapped in parentheses: NAME((SELECT ...))
136    #[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    // ==================== builder methods ====================
146
147    /// Append another SQL fragment (flat extend)
148    #[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    /// Push a single chunk
184    #[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    /// Pre-allocates capacity for additional chunks
197    #[inline]
198    #[must_use]
199    pub fn with_capacity(mut self, additional: usize) -> Self {
200        self.chunks.reserve(additional);
201        self
202    }
203
204    // ==================== combinators ====================
205
206    /// Joins multiple SQL fragments with a separator
207    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    /// Wrap in parentheses: (self)
239    #[inline]
240    #[must_use]
241    pub fn parens(self) -> Self {
242        SQL::token(Token::LPAREN).append(self).push(Token::RPAREN)
243    }
244
245    /// Wrap this SQL fragment in parentheses only when it is a subquery.
246    #[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    /// Check if this SQL fragment is a subquery (starts with SELECT/WITH).
257    #[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    /// Creates an aliased version: self AS "name"
266    #[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    /// Creates a comma-separated list of parameters.
273    /// Builds chunks directly without intermediate SQL allocations.
274    #[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    /// Creates a comma-separated list of column assignments: "col" = ?
297    /// Builds chunks directly without intermediate SQL allocations.
298    #[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        // Each assignment: Ident + EQ + Param = 3 chunks, plus commas
308        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    /// Creates a comma-separated list of column assignments from pre-built SQL fragments: "col" = <sql>
324    ///
325    /// Unlike `assignments()` which wraps each value in `SQL::param()`, this variant
326    /// accepts pre-built `SQL` fragments, preserving placeholders and raw expressions.
327    /// Builds chunks directly without intermediate SQL allocations.
328    #[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    // ==================== output methods ====================
349
350    /// Maps parameter values from type `V` to type `U` using the provided function.
351    ///
352    /// Only `Param` chunks are affected; all other chunks pass through unchanged.
353    /// This is useful for converting between owned and borrowed value types
354    /// (e.g. `OwnedPostgresValue` → `PostgresValue<'a>`).
355    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    /// Converts to owned version (consuming self to avoid clone)
376    #[inline]
377    pub fn into_owned(self) -> OwnedSQL<V> {
378        OwnedSQL::from(self)
379    }
380
381    /// Returns the SQL string with dialect-appropriate placeholders.
382    /// Uses `$1, $2, ...` for `PostgreSQL`, `:name` or `?` for `SQLite`, `?` for `MySQL`.
383    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    /// Generates the SQL string and collects parameter references in a single pass.
395    ///
396    /// This is the preferred method for driver execution paths since it avoids
397    /// iterating the chunk list twice (once for `sql()`, once for `params()`).
398    pub fn build(&self) -> (String, SmallVec<[&V; 8]>) {
399        self.build_with(crate::dialect::ParamStyle::for_dialect(V::DIALECT))
400    }
401
402    /// Same as [`build`](Self::build) but lets the caller override the
403    /// placeholder style. Drivers that speak the dialect but bind parameters
404    /// differently (e.g. AWS Data API on Postgres) use this to emit
405    /// `:1, :2, ...` instead of `$1, $2, ...` without any post-hoc rewriting.
406    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) = &param.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    /// Write SQL to a buffer with dialect-appropriate placeholders.
452    /// Uses `$1, $2, ...` for `PostgreSQL`, `?` or `:name` for `SQLite`, `?` for `MySQL`.
453    #[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    /// Same as [`write_to`](Self::write_to) but with a caller-chosen
459    /// placeholder style.
460    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    /// Write a single chunk with pattern detection
497    #[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    /// Write appropriate columns for SELECT statement
514    #[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    /// Write fully qualified columns for a table
535    #[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    /// Simplified spacing logic
553    #[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    /// Returns an iterator over references to parameter values
585    /// (avoids allocating a Vec - callers can collect if needed)
586    #[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    /// Bind named parameters
601    #[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/// Canonical spacing logic for SQL chunk rendering.
662/// Used by both `SQL::write_to()` and `prepare_render()`.
663#[inline]
664pub(crate) fn chunk_needs_space<V: SQLParam>(
665    current: &SQLChunk<'_, V>,
666    next: &SQLChunk<'_, V>,
667) -> bool {
668    // No space if current raw text ends with space
669    if let SQLChunk::Raw(text) = current
670        && text.ends_with(' ')
671    {
672        return false;
673    }
674
675    // No space if next raw text starts with space
676    if let SQLChunk::Raw(text) = next
677        && text.starts_with(' ')
678    {
679        return false;
680    }
681
682    match (current, next) {
683        // No space before closing/separator punctuation
684        // or after opening punctuation
685        (_, SQLChunk::Token(Token::RPAREN | Token::COMMA | Token::SEMI | Token::DOT))
686        | (SQLChunk::Token(Token::LPAREN | Token::DOT), _) => false,
687        // Space after comma
688        (SQLChunk::Token(Token::COMMA), _) => true,
689        // Space after closing paren if next is word-like (e.g., ") FROM")
690        (SQLChunk::Token(Token::RPAREN), next) => next.is_word_like(),
691        // Space before opening paren if preceded by word-like (e.g., "AS (")
692        (current, SQLChunk::Token(Token::LPAREN)) => current.is_word_like(),
693        // Space around comparison/arithmetic operators
694        (SQLChunk::Token(t), _) if t.is_operator() => true,
695        (_, SQLChunk::Token(t)) if t.is_operator() => true,
696        // Space between all word-like chunks
697        _ => current.is_word_like() && next.is_word_like(),
698    }
699}
700
701// ==================== trait implementations ====================
702
703impl<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        // Collect params for Debug formatting (iterator can't be used with :?)
734        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}