1use compact_str::{CompactString, ToCompactString};
2use smallvec::{SmallVec, smallvec};
3use std::{borrow::Cow, collections::HashMap, fmt::Display};
4
5use crate::{
6 OwnedParam, Param, ParamBind, Placeholder, PlaceholderStyle,
7 traits::{SQLColumnInfo, SQLParam, SQLTableInfo},
8};
9
10#[derive(Clone)]
12pub enum SQLChunk<'a, V: SQLParam + 'a> {
13 Text(Cow<'a, CompactString>),
14 Param(Param<'a, V>),
15 SQL(Box<SQL<'a, V>>),
16 Table(&'static dyn SQLTableInfo),
18 Column(&'static dyn SQLColumnInfo),
20 Alias {
22 chunk: Box<SQLChunk<'a, V>>,
23 alias: CompactString,
24 },
25 Subquery(Box<SQL<'a, V>>),
27}
28
29pub enum OwnedSQLChunk<V: SQLParam> {
30 Text(CompactString),
31 Param(OwnedParam<V>),
32 SQL(Box<OwnedSQL<V>>),
33 Table(&'static dyn SQLTableInfo),
34 Column(&'static dyn SQLColumnInfo),
35 Alias {
36 chunk: Box<OwnedSQLChunk<V>>,
37 alias: CompactString,
38 },
39 Subquery(Box<OwnedSQL<V>>),
40}
41
42impl<'a, V: SQLParam> From<SQLChunk<'a, V>> for OwnedSQLChunk<V> {
43 fn from(value: SQLChunk<'a, V>) -> Self {
44 match value {
45 SQLChunk::Text(cow) => Self::Text(cow.into_owned()),
46 SQLChunk::Param(param) => Self::Param(param.into()),
47 SQLChunk::SQL(sql) => Self::SQL(Box::new((*sql).into())),
48 SQLChunk::Table(sqltable_info) => Self::Table(sqltable_info),
49 SQLChunk::Column(sqlcolumn_info) => Self::Column(sqlcolumn_info),
50 SQLChunk::Alias { chunk, alias } => Self::Alias {
51 chunk: Box::new((*chunk).into()),
52 alias,
53 },
54 SQLChunk::Subquery(sql) => Self::Subquery(Box::new((*sql).into())),
55 }
56 }
57}
58
59impl<'a, V: SQLParam + 'a> SQLChunk<'a, V> {
60 pub const fn text(text: &'static str) -> Self {
62 Self::Text(Cow::Owned(CompactString::const_new(text)))
63 }
64
65 pub const fn param(value: &'a V, placeholder: Placeholder) -> Self {
67 Self::Param(Param {
68 value: Some(Cow::Borrowed(value)),
69 placeholder,
70 })
71 }
72
73 pub fn sql(sql: SQL<'a, V>) -> Self {
75 Self::SQL(Box::new(sql))
76 }
77
78 pub const fn table(table: &'static dyn SQLTableInfo) -> Self {
80 Self::Table(table)
81 }
82
83 pub const fn column(column: &'static dyn SQLColumnInfo) -> Self {
85 Self::Column(column)
86 }
87
88 pub fn alias(chunk: SQLChunk<'a, V>, alias: impl Into<CompactString>) -> Self {
90 Self::Alias {
91 chunk: Box::new(chunk),
92 alias: alias.into(),
93 }
94 }
95
96 pub fn subquery(sql: SQL<'a, V>) -> Self {
98 Self::Subquery(Box::new(sql))
99 }
100
101 pub(crate) fn write_to_buffer(&self, buf: &mut CompactString) {
103 match self {
104 SQLChunk::Text(text) => buf.push_str(text),
105 SQLChunk::Param(Param { placeholder, .. }) => buf.push_str(&placeholder.to_string()),
106 SQLChunk::SQL(sql) => buf.push_str(&sql.sql()),
107 SQLChunk::Table(table) => {
108 buf.push('"');
109 buf.push_str(table.name());
110 buf.push('"');
111 }
112 SQLChunk::Column(column) => {
113 buf.push('"');
114 buf.push_str(column.table().name());
115 buf.push_str(r#"".""#);
116 buf.push_str(column.name());
117 buf.push('"');
118 }
119 SQLChunk::Alias { chunk, alias } => {
120 chunk.write_to_buffer(buf);
121 buf.push_str(" AS ");
122 buf.push_str(alias);
123 }
124 SQLChunk::Subquery(sql) => {
125 buf.push('(');
126 buf.push_str(&sql.sql());
127 buf.push(')');
128 }
129 }
130 }
131}
132
133impl<'a, V: SQLParam + std::fmt::Debug> std::fmt::Debug for SQLChunk<'a, V> {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 match self {
136 SQLChunk::Text(text) => f.debug_tuple("Text").field(text).finish(),
137 SQLChunk::Param(param) => f.debug_tuple("Param").field(param).finish(),
138 SQLChunk::SQL(_) => f.debug_tuple("SQL").field(&"<nested>").finish(),
139 SQLChunk::Table(table) => f.debug_tuple("Table").field(&table.name()).finish(),
140 SQLChunk::Column(column) => f
141 .debug_tuple("Column")
142 .field(&format!("{}.{}", column.table().name(), column.name()))
143 .finish(),
144 SQLChunk::Alias { alias, .. } => f
145 .debug_struct("Alias")
146 .field("alias", alias)
147 .field("chunk", &"<nested>")
148 .finish(),
149 SQLChunk::Subquery(_) => f.debug_tuple("Subquery").field(&"<nested>").finish(),
150 }
151 }
152}
153
154#[derive(Debug, Clone)]
159pub struct SQL<'a, V: SQLParam> {
160 pub chunks: SmallVec<[SQLChunk<'a, V>; 3]>,
162}
163
164pub struct OwnedSQL<V: SQLParam> {
165 pub chunks: SmallVec<[OwnedSQLChunk<V>; 3]>,
166}
167
168impl<'a, V: SQLParam> From<SQL<'a, V>> for OwnedSQL<V> {
169 fn from(value: SQL<'a, V>) -> Self {
170 Self {
171 chunks: value.chunks.iter().map(|v| v.clone().into()).collect(),
172 }
173 }
174}
175
176impl<'a, V: SQLParam> SQL<'a, V> {
177 const POSITIONAL_PLACEHOLDER: Placeholder = Placeholder::positional();
179
180 pub const fn new<'b>(chunks: [SQLChunk<'b, V>; 3]) -> SQL<'b, V> {
181 SQL {
182 chunks: SmallVec::from_const(chunks),
183 }
184 }
185
186 pub const fn empty<'b>() -> SQL<'b, V> {
188 SQL {
189 chunks: SmallVec::new_const(),
190 }
191 }
192
193 pub fn into_owned(&self) -> OwnedSQL<V> {
194 OwnedSQL::from(self.clone())
195 }
196
197 pub const fn text(text: &'static str) -> Self {
199 let chunks = SmallVec::from_const([
201 SQLChunk::Text(Cow::Owned(CompactString::const_new(text))),
202 SQLChunk::Text(Cow::Owned(CompactString::const_new(""))), SQLChunk::Text(Cow::Owned(CompactString::const_new(""))),
204 ]);
205 Self { chunks }
206 }
207
208 pub const fn wildcard() -> Self {
210 Self::text("*")
211 }
212
213 pub const fn null() -> Self {
215 Self::text("NULL")
216 }
217
218 pub const fn r#true() -> Self {
220 Self::text("TRUE")
221 }
222
223 pub const fn r#false() -> Self {
225 Self::text("FALSE")
226 }
227
228 pub fn raw<T: AsRef<str>>(sql: T) -> Self {
232 let sql = Cow::Owned(sql.as_ref().to_compact_string());
233
234 Self {
235 chunks: smallvec![SQLChunk::Text(sql)],
236 }
237 }
238
239 pub fn parameter(param: impl Into<Cow<'a, V>>) -> Self {
244 Self {
245 chunks: smallvec![SQLChunk::Param(Param {
246 value: Some(param.into()),
247 placeholder: Self::POSITIONAL_PLACEHOLDER,
248 })],
249 }
250 }
251
252 pub fn table(table: &'static dyn SQLTableInfo) -> SQL<'a, V> {
254 SQL {
255 chunks: smallvec![SQLChunk::table(table)],
256 }
257 }
258
259 pub fn column(column: &'static dyn SQLColumnInfo) -> SQL<'a, V> {
260 SQL {
261 chunks: smallvec![SQLChunk::column(column)],
262 }
263 }
264
265 pub fn placeholder(name: &'static str) -> Self
269 where
270 V: Default,
271 {
272 Self {
273 chunks: smallvec![SQLChunk::Param(Param {
274 value: None,
275 placeholder: Placeholder::colon(name),
276 })],
277 }
278 }
279
280 pub fn placeholder_with_style(name: &'static str, style: PlaceholderStyle) -> Self
282 where
283 V: Default,
284 {
285 Self {
286 chunks: smallvec![SQLChunk::Param(Param {
287 value: None, placeholder: Placeholder::with_style(name, style),
289 })],
290 }
291 }
292
293 pub fn from_placeholder(placeholder: Placeholder) -> Self
295 where
296 V: Default,
297 {
298 Self {
299 chunks: smallvec![SQLChunk::Param(Param::from_placeholder(placeholder))],
300 }
301 }
302
303 pub fn append_raw(mut self, sql: impl AsRef<str>) -> Self {
307 let text_chunk = SQLChunk::Text(Cow::Owned(sql.as_ref().to_compact_string()));
308 self.chunks.push(text_chunk);
309 self
310 }
311
312 pub fn append(mut self, other: impl Into<SQL<'a, V>>) -> Self {
316 let other_sql = other.into();
317 self.chunks.extend(other_sql.chunks);
318 self
319 }
320
321 pub fn with_capacity(mut self, additional: usize) -> Self {
323 self.chunks.reserve(additional);
324 self
325 }
326
327 pub fn join<T>(sqls: T, separator: &'static str) -> SQL<'a, V>
331 where
332 T: IntoIterator,
333 T::Item: crate::ToSQL<'a, V>,
334 {
335 let sqls: Vec<_> = sqls.into_iter().map(|sql| sql.to_sql()).collect();
336
337 if sqls.is_empty() {
338 return SQL::empty();
339 }
340
341 if sqls.len() == 1 {
342 return sqls.into_iter().next().unwrap();
343 }
344
345 let total_chunks =
347 sqls.iter().map(|sql| sql.chunks.len()).sum::<usize>() + (sqls.len() - 1);
348 let mut chunks = SmallVec::with_capacity(total_chunks);
349
350 let separator_chunk = SQLChunk::Text(Cow::Owned(CompactString::const_new(separator)));
351
352 for (i, sql) in sqls.into_iter().enumerate() {
353 if i > 0 {
354 chunks.push(separator_chunk.clone());
355 }
356 chunks.extend(sql.chunks);
357 }
358
359 SQL { chunks }
360 }
361
362 fn collect_chunk_params<'b>(chunk: &'b SQLChunk<'a, V>, params_vec: &mut Vec<&'b V>) {
364 match chunk {
365 SQLChunk::Param(Param {
366 value: Some(value), ..
367 }) => params_vec.push(value.as_ref()),
368 SQLChunk::SQL(sql) => params_vec.extend(sql.params()),
369 SQLChunk::Alias { chunk, .. } => Self::collect_chunk_params(chunk, params_vec),
370 SQLChunk::Subquery(sql) => params_vec.extend(sql.params()),
371 _ => {}
372 }
373 }
374
375 pub fn sql(&self) -> String {
377 match self.chunks.len() {
378 0 => String::new(),
379 1 => self.render_single_chunk(0).to_string(),
380 _ => {
381 let capacity = self.estimate_capacity();
382 let mut buf = CompactString::with_capacity(capacity);
383 self.write_sql(&mut buf);
384 buf.into()
385 }
386 }
387 }
388
389 pub fn bind(self, params: impl IntoIterator<Item = ParamBind<'a, V>>) -> SQL<'a, V> {
390 let param_map: HashMap<&str, V> = params.into_iter().map(|p| (p.name, p.value)).collect();
391
392 let bound_chunks = self
393 .into_iter()
394 .map(|chunk| match chunk {
395 SQLChunk::Param(mut param) => {
396 if let Some(name) = param.placeholder.name
398 && let Some(value) = param_map.get(name)
399 {
400 param.value = Some(Cow::Owned(value.clone()));
401 }
402 SQLChunk::Param(param)
403 }
404 other => other,
405 })
406 .collect();
407
408 SQL {
409 chunks: bound_chunks,
410 }
411 }
412
413 pub(crate) fn write_qualified_columns(
415 &self,
416 buf: &mut CompactString,
417 table: &'a dyn SQLTableInfo,
418 ) {
419 let columns = table.columns();
420 if columns.is_empty() {
421 buf.push('*');
422 return;
423 }
424
425 for (i, col) in columns.iter().enumerate() {
426 if i > 0 {
427 buf.push_str(", ");
428 }
429 buf.push('"');
430 buf.push_str(table.name());
431 buf.push_str(r#"".""#);
432 buf.push_str(col.name());
433 buf.push('"');
434 }
435 }
436
437 fn write_sql(&self, buf: &mut CompactString) {
438 for i in 0..self.chunks.len() {
439 self.write_chunk(buf, &self.chunks[i], i);
440
441 if self.needs_space(i) {
442 buf.push(' ');
443 }
444 }
445 }
446
447 pub(crate) fn write_chunk(
449 &self,
450 buf: &mut CompactString,
451 chunk: &SQLChunk<'a, V>,
452 index: usize,
453 ) {
454 match chunk {
455 SQLChunk::Text(text) if text.is_empty() => {
456 if let Some(table) = self.detect_pattern_at(index) {
457 self.write_qualified_columns(buf, table);
458 }
459 }
460 SQLChunk::Text(text) if text.trim().eq_ignore_ascii_case("SELECT") => {
461 if let Some(table) = self.detect_select_from_table_pattern(index) {
462 buf.push_str("SELECT ");
463 self.write_qualified_columns(buf, table);
464 } else if self.detect_select_from_non_table_pattern(index) {
465 buf.push_str("SELECT *");
466 } else {
467 buf.push_str(text);
468 }
469 }
470 SQLChunk::Text(text) => buf.push_str(text),
471 SQLChunk::Param(Param { placeholder, .. }) => buf.push_str(&placeholder.to_string()),
472 SQLChunk::SQL(sql) => buf.push_str(&sql.sql()),
473 SQLChunk::Table(table) => {
474 buf.push('"');
475 buf.push_str(table.name());
476 buf.push('"');
477 }
478 SQLChunk::Column(column) => {
479 buf.push('"');
480 buf.push_str(column.table().name());
481 buf.push_str(r#"".""#);
482 buf.push_str(column.name());
483 buf.push('"');
484 }
485 SQLChunk::Alias { chunk, alias } => {
486 chunk.write_to_buffer(buf);
487 buf.push_str(" AS ");
488 buf.push_str(alias);
489 }
490 SQLChunk::Subquery(sql) => {
491 buf.push('(');
492 buf.push_str(&sql.sql());
493 buf.push(')');
494 }
495 }
496 }
497
498 fn render_single_chunk(&self, index: usize) -> CompactString {
500 let chunk = &self.chunks[index];
501 let capacity = match chunk {
502 SQLChunk::Text(text) if text.is_empty() => self
503 .detect_pattern_at(index)
504 .map(|table| table.columns().len() * 20)
505 .unwrap_or(0),
506 SQLChunk::Text(text) => text.len(),
507 SQLChunk::Table(table) => table.name().len() + 2,
508 SQLChunk::Column(column) => column.table().name().len() + column.name().len() + 5,
509 SQLChunk::Alias { alias, .. } => alias.len() + 10,
510 _ => 32,
511 };
512
513 let mut buf = CompactString::with_capacity(capacity);
514 self.write_chunk(&mut buf, chunk, index);
515 buf
516 }
517
518 pub(crate) fn detect_pattern_at(&self, empty_index: usize) -> Option<&'a dyn SQLTableInfo> {
520 let select_pos = self.find_select_before(empty_index)?;
521 self.find_table_after_from(select_pos)
522 }
523
524 pub(crate) fn detect_select_from_table_pattern(
526 &self,
527 select_index: usize,
528 ) -> Option<&'a dyn SQLTableInfo> {
529 if select_index + 2 < self.chunks.len()
530 && let (SQLChunk::Text(from_text), SQLChunk::Table(table)) = (
531 &self.chunks[select_index + 1],
532 &self.chunks[select_index + 2],
533 )
534 && from_text.trim().eq_ignore_ascii_case("FROM")
535 {
536 return Some(*table);
537 }
538 None
539 }
540
541 fn detect_select_from_non_table_pattern(&self, select_index: usize) -> bool {
543 if select_index + 2 < self.chunks.len() {
544 if let SQLChunk::Text(from_text) = &self.chunks[select_index + 1] {
545 if from_text.trim().eq_ignore_ascii_case("FROM") {
546 match &self.chunks[select_index + 2] {
548 SQLChunk::Table(_) => false, SQLChunk::Text(_) | SQLChunk::SQL(_) | SQLChunk::Subquery(_) => true, _ => true, }
552 } else {
553 false
554 }
555 } else {
556 false
557 }
558 } else {
559 false
560 }
561 }
562
563 fn find_select_before(&self, pos: usize) -> Option<usize> {
565 self.chunks[..pos]
566 .iter()
567 .enumerate()
568 .rev()
569 .find_map(|(i, chunk)| match chunk {
570 SQLChunk::Text(text) if text.trim().eq_ignore_ascii_case("SELECT") => Some(i),
571 _ => None,
572 })
573 }
574
575 fn find_table_after_from(&self, select_pos: usize) -> Option<&'a dyn SQLTableInfo> {
577 let chunks = &self.chunks[select_pos..];
578 let mut found_from = false;
579
580 for chunk in chunks {
581 match chunk {
582 SQLChunk::Text(text) if text.trim().eq_ignore_ascii_case("FROM") => {
583 found_from = true;
584 }
585 SQLChunk::Table(table) if found_from => return Some(*table),
586 _ => {}
587 }
588 }
589 None
590 }
591
592 fn estimate_capacity(&self) -> usize {
594 let chunk_content_size: usize = self
595 .chunks
596 .iter()
597 .map(|chunk| match chunk {
598 SQLChunk::Text(t) => t.len(),
599 SQLChunk::Param { .. } => 1, SQLChunk::SQL(sql) => sql.chunks.len() * 8, SQLChunk::Table(t) => t.name().len() + 2, SQLChunk::Column(c) => c.table().name().len() + c.name().len() + 5, SQLChunk::Alias { alias, .. } => alias.len() + 4, SQLChunk::Subquery(sql) => (sql.chunks.len() * 8) + 2, })
606 .sum();
607
608 chunk_content_size + self.chunks.len()
610 }
611
612 pub(crate) fn needs_space(&self, index: usize) -> bool {
613 if index + 1 >= self.chunks.len() {
614 return false;
615 }
616
617 let current = &self.chunks[index];
618
619 let mut next_index = index + 1;
621 let next = loop {
622 if next_index >= self.chunks.len() {
623 return false;
624 }
625
626 let candidate = &self.chunks[next_index];
627 if let SQLChunk::Text(t) = candidate
628 && t.is_empty()
629 {
630 next_index += 1;
631 continue;
632 }
633 break candidate;
634 };
635
636 let ends_word = chunk_ends_word(current);
637 let starts_word = chunk_starts_word(next);
638
639 ends_word && starts_word
640 }
641
642 pub fn params(&self) -> Vec<&V> {
644 let mut params_vec = Vec::with_capacity(self.chunks.len().min(8));
645 for chunk in &self.chunks {
646 Self::collect_chunk_params(chunk, &mut params_vec);
647 }
648 params_vec
649 }
650
651 pub fn alias(self, alias: impl Into<CompactString>) -> SQL<'a, V> {
653 SQL {
654 chunks: smallvec![SQLChunk::Alias {
655 chunk: Box::new(SQLChunk::SQL(Box::new(self))),
656 alias: alias.into(),
657 }],
658 }
659 }
660
661 pub fn subquery(self) -> SQL<'a, V> {
663 SQL {
664 chunks: smallvec![SQLChunk::subquery(self)],
665 }
666 }
667
668 pub fn parameters<I>(values: I) -> Self
670 where
671 I: IntoIterator,
672 I::Item: Into<Cow<'a, V>>,
673 {
674 let values: Vec<_> = values.into_iter().map(|v| v.into()).collect();
675
676 if values.is_empty() {
677 return Self::empty();
678 }
679
680 if values.len() == 1 {
681 return Self::parameter(values.into_iter().next().unwrap());
682 }
683
684 let mut chunks = SmallVec::with_capacity(values.len() * 2 - 1);
686 let separator_chunk = SQLChunk::Text(Cow::Owned(CompactString::const_new(", ")));
687
688 for (i, value) in values.into_iter().enumerate() {
689 if i > 0 {
690 chunks.push(separator_chunk.clone());
691 }
692 chunks.push(SQLChunk::Param(Param {
693 value: Some(value),
694 placeholder: Self::POSITIONAL_PLACEHOLDER,
695 }));
696 }
697
698 SQL { chunks }
699 }
700
701 pub fn assignments<I, T>(pairs: I) -> Self
703 where
704 I: IntoIterator<Item = (&'a str, T)>,
705 T: Into<Cow<'a, V>>,
706 {
707 let pairs: Vec<_> = pairs
708 .into_iter()
709 .map(|(col, val)| (col, val.into()))
710 .collect();
711
712 if pairs.is_empty() {
713 return Self::empty();
714 }
715
716 if pairs.len() == 1 {
717 let (col, val) = pairs.into_iter().next().unwrap();
718 return Self::raw(col)
719 .append_raw(" = ")
720 .append(Self::parameter(val));
721 }
722
723 let mut chunks = SmallVec::with_capacity(pairs.len() * 4 - 1);
725 let separator_chunk = SQLChunk::Text(Cow::Owned(CompactString::const_new(", ")));
726 let equals_chunk = SQLChunk::Text(Cow::Owned(CompactString::const_new(" = ")));
727
728 for (i, (col, val)) in pairs.into_iter().enumerate() {
729 if i > 0 {
730 chunks.push(separator_chunk.clone());
731 }
732 chunks.push(SQLChunk::Text(Cow::Owned(col.to_compact_string())));
733 chunks.push(equals_chunk.clone());
734 chunks.push(SQLChunk::Param(Param {
735 value: Some(val),
736 placeholder: Self::POSITIONAL_PLACEHOLDER,
737 }));
738 }
739
740 SQL { chunks }
741 }
742}
743
744fn chunk_ends_word<V: SQLParam>(chunk: &SQLChunk<'_, V>) -> bool {
746 match chunk {
747 SQLChunk::Text(t) => {
748 let last = t.chars().last().unwrap_or(' ');
749 !last.is_whitespace() && !['(', ',', '.', ')'].contains(&last)
750 }
751 SQLChunk::Table(_)
752 | SQLChunk::Column(_)
753 | SQLChunk::Param(_)
754 | SQLChunk::Alias { .. }
755 | SQLChunk::Subquery(_) => true,
756 _ => false,
757 }
758}
759
760fn chunk_starts_word<V: SQLParam>(chunk: &SQLChunk<'_, V>) -> bool {
762 match chunk {
763 SQLChunk::Text(t) => {
764 let first = t.chars().next().unwrap_or(' ');
765 !first.is_whitespace() && !['(', ',', ')', ';'].contains(&first)
766 }
767 SQLChunk::Table(_)
768 | SQLChunk::Column(_)
769 | SQLChunk::Param(_)
770 | SQLChunk::Alias { .. }
771 | SQLChunk::Subquery(_) => true,
772 _ => false,
773 }
774}
775
776impl<'a, V: SQLParam> IntoIterator for SQL<'a, V> {
777 type Item = SQLChunk<'a, V>;
778 type IntoIter = std::iter::FlatMap<
779 smallvec::IntoIter<[SQLChunk<'a, V>; 3]>,
780 Box<dyn Iterator<Item = SQLChunk<'a, V>> + 'a>,
781 fn(SQLChunk<'a, V>) -> Box<dyn Iterator<Item = SQLChunk<'a, V>> + 'a>,
782 >;
783
784 fn into_iter(self) -> Self::IntoIter {
785 fn flatten_chunk<'a, V: SQLParam>(
786 chunk: SQLChunk<'a, V>,
787 ) -> Box<dyn Iterator<Item = SQLChunk<'a, V>> + 'a> {
788 match chunk {
789 SQLChunk::SQL(nested_sql) => Box::new(nested_sql.into_iter()),
790 other => Box::new(std::iter::once(other)),
791 }
792 }
793
794 self.chunks
795 .into_iter()
796 .flat_map(flatten_chunk as fn(_) -> _)
797 }
798}
799
800impl<'a, V: SQLParam> Default for SQL<'a, V> {
801 fn default() -> Self {
802 Self::empty()
803 }
804}
805
806impl<'a, V: SQLParam + 'a> From<&'a str> for SQL<'a, V> {
807 fn from(s: &'a str) -> Self {
808 SQL::raw(s)
809 }
810}
811
812impl<'a, V: SQLParam + 'a> AsRef<SQL<'a, V>> for SQL<'a, V> {
813 fn as_ref(&self) -> &SQL<'a, V> {
814 self
815 }
816}
817
818impl<'a, V: SQLParam + std::fmt::Display> Display for SQL<'a, V> {
819 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
820 let params = self.params();
821 write!(f, r#"sql: "{}", params: {:?} "#, self.sql(), params)
822 }
823}
824
825use crate::ToSQL;
826
827impl<V: SQLParam> ToSQL<'static, V> for OwnedSQL<V> {
828 fn to_sql(&self) -> SQL<'static, V> {
829 SQL::from(self)
830 }
831}
832
833impl<'a, V: SQLParam + 'a> ToSQL<'a, V> for SQL<'a, V> {
834 fn to_sql(&self) -> SQL<'a, V> {
835 self.clone()
836 }
837}