1mod chunk;
2mod cte;
3mod owned;
4mod tokens;
5
6use crate::prelude::*;
7use crate::{
8 dialect::DialectExt,
9 param::{Param, ParamBind},
10 placeholder::Placeholder,
11 traits::{SQLColumnInfo, SQLParam, SQLTableInfo, ToSQL},
12};
13pub use chunk::*;
14use core::fmt::Display;
15pub use owned::*;
16use smallvec::SmallVec;
17pub use tokens::*;
18
19#[cfg(feature = "profiling")]
20use crate::profile_sql;
21
22#[derive(Debug, Clone)]
27pub struct SQL<'a, V: SQLParam> {
28 pub chunks: SmallVec<[SQLChunk<'a, V>; 8]>,
29}
30
31impl<'a, V: SQLParam> SQL<'a, V> {
32 const POSITIONAL_PLACEHOLDER: Placeholder = Placeholder::anonymous();
33
34 #[inline]
38 pub const fn empty() -> Self {
39 Self {
40 chunks: SmallVec::new_const(),
41 }
42 }
43
44 #[inline]
48 pub fn token(t: Token) -> Self {
49 Self {
50 chunks: smallvec::smallvec![SQLChunk::Token(t)],
51 }
52 }
53
54 #[inline]
56 pub fn with_capacity_chunks(capacity: usize) -> Self {
57 Self {
58 chunks: SmallVec::with_capacity(capacity),
59 }
60 }
61
62 #[inline]
64 pub fn ident(name: impl Into<Cow<'a, str>>) -> Self {
65 Self {
66 chunks: smallvec::smallvec![SQLChunk::Ident(name.into())],
67 }
68 }
69
70 #[inline]
72 pub fn raw(text: impl Into<Cow<'a, str>>) -> Self {
73 Self {
74 chunks: smallvec::smallvec![SQLChunk::Raw(text.into())],
75 }
76 }
77
78 #[inline]
80 pub fn param(value: impl Into<Cow<'a, V>>) -> Self {
81 Self {
82 chunks: smallvec::smallvec![SQLChunk::Param(Param {
83 value: Some(value.into()),
84 placeholder: Self::POSITIONAL_PLACEHOLDER,
85 })],
86 }
87 }
88
89 #[inline]
93 pub fn bytes(bytes: impl Into<Cow<'a, [u8]>>) -> Self
94 where
95 V: From<&'a [u8]>,
96 V: From<Vec<u8>>,
97 V: Into<Cow<'a, V>>,
98 {
99 match bytes.into() {
100 Cow::Borrowed(value) => Self::param(V::from(value)),
101 Cow::Owned(value) => Self::param(V::from(value)),
102 }
103 }
104
105 #[inline]
107 pub fn placeholder(name: &'static str) -> Self {
108 Self {
109 chunks: smallvec::smallvec![SQLChunk::Param(Param {
110 value: None,
111 placeholder: Placeholder::named(name),
112 })],
113 }
114 }
115
116 #[inline]
118 pub fn table(table: &'static dyn SQLTableInfo) -> Self {
119 Self {
120 chunks: smallvec::smallvec![SQLChunk::Table(table)],
121 }
122 }
123
124 #[inline]
126 pub fn column(column: &'static dyn SQLColumnInfo) -> Self {
127 Self {
128 chunks: smallvec::smallvec![SQLChunk::Column(column)],
129 }
130 }
131
132 #[inline]
135 pub fn func(name: &'static str, args: SQL<'a, V>) -> Self {
136 let args = if args.is_subquery() {
137 args.parens()
138 } else {
139 args
140 };
141 SQL::raw(name)
142 .push(Token::LPAREN)
143 .append(args)
144 .push(Token::RPAREN)
145 }
146
147 #[inline]
151 pub fn append(mut self, other: impl Into<SQL<'a, V>>) -> Self {
152 #[cfg(feature = "profiling")]
153 profile_sql!("append");
154 let other = other.into();
155 if !other.chunks.is_empty() {
156 self.chunks.reserve(other.chunks.len());
157 self.chunks.extend(other.chunks);
158 }
159 self
160 }
161
162 #[inline]
164 pub fn push(mut self, chunk: impl Into<SQLChunk<'a, V>>) -> Self {
165 self.chunks.push(chunk.into());
166 self
167 }
168
169 #[inline]
171 pub fn with_capacity(mut self, additional: usize) -> Self {
172 self.chunks.reserve(additional);
173 self
174 }
175
176 pub fn join<T>(sqls: T, separator: Token) -> SQL<'a, V>
180 where
181 T: IntoIterator,
182 T::Item: ToSQL<'a, V>,
183 {
184 #[cfg(feature = "profiling")]
185 profile_sql!("join");
186
187 let mut iter = sqls.into_iter();
188 let Some(first) = iter.next() else {
189 return SQL::empty();
190 };
191
192 let mut result = first.into_sql();
193 let (lower, _) = iter.size_hint();
194 if lower > 0 {
195 result.chunks.reserve(lower * 2);
197 }
198 for item in iter {
199 result = result.push(separator).append(item.into_sql());
200 }
201 result
202 }
203
204 #[inline]
206 pub fn parens(self) -> Self {
207 SQL::token(Token::LPAREN).append(self).push(Token::RPAREN)
208 }
209
210 #[inline]
212 pub fn is_subquery(&self) -> bool {
213 matches!(self.chunks.first(), Some(SQLChunk::Token(Token::SELECT)))
214 }
215
216 pub fn alias(self, name: impl Into<Cow<'a, str>>) -> SQL<'a, V> {
218 self.push(Token::AS).push(SQLChunk::Ident(name.into()))
219 }
220
221 pub fn param_list<I>(values: I) -> Self
224 where
225 I: IntoIterator,
226 I::Item: Into<Cow<'a, V>>,
227 {
228 let iter = values.into_iter();
229 let (lower, _) = iter.size_hint();
230 let mut chunks = SmallVec::with_capacity(lower.saturating_mul(2));
231 for (i, v) in iter.enumerate() {
232 if i > 0 {
233 chunks.push(SQLChunk::Token(Token::COMMA));
234 }
235 chunks.push(SQLChunk::Param(Param {
236 value: Some(v.into()),
237 placeholder: Self::POSITIONAL_PLACEHOLDER,
238 }));
239 }
240 SQL { chunks }
241 }
242
243 pub fn assignments<I, T>(pairs: I) -> Self
246 where
247 I: IntoIterator<Item = (&'static str, T)>,
248 T: Into<Cow<'a, V>>,
249 {
250 let iter = pairs.into_iter();
251 let (lower, _) = iter.size_hint();
252 let mut chunks = SmallVec::with_capacity(lower.saturating_mul(4));
254 for (i, (col, val)) in iter.enumerate() {
255 if i > 0 {
256 chunks.push(SQLChunk::Token(Token::COMMA));
257 }
258 chunks.push(SQLChunk::Ident(Cow::Borrowed(col)));
259 chunks.push(SQLChunk::Token(Token::EQ));
260 chunks.push(SQLChunk::Param(Param {
261 value: Some(val.into()),
262 placeholder: Self::POSITIONAL_PLACEHOLDER,
263 }));
264 }
265 SQL { chunks }
266 }
267
268 pub fn assignments_sql<I>(pairs: I) -> Self
274 where
275 I: IntoIterator<Item = (&'static str, SQL<'a, V>)>,
276 {
277 let iter = pairs.into_iter();
278 let (lower, _) = iter.size_hint();
279 let mut chunks = SmallVec::with_capacity(lower.saturating_mul(4));
280 for (i, (col, sql)) in iter.enumerate() {
281 if i > 0 {
282 chunks.push(SQLChunk::Token(Token::COMMA));
283 }
284 chunks.push(SQLChunk::Ident(Cow::Borrowed(col)));
285 chunks.push(SQLChunk::Token(Token::EQ));
286 chunks.extend(sql.chunks);
287 }
288 SQL { chunks }
289 }
290
291 pub fn into_owned(self) -> OwnedSQL<V> {
295 OwnedSQL::from(self)
296 }
297
298 pub fn sql(&self) -> String {
301 #[cfg(feature = "profiling")]
302 profile_sql!("sql");
303 let capacity = self.estimate_capacity();
304 let mut buf = String::with_capacity(capacity);
305 self.write_to(&mut buf);
306 buf
307 }
308
309 pub fn write_to(&self, buf: &mut impl core::fmt::Write) {
313 use crate::dialect::Dialect;
314 let mut param_index = 1usize;
315 for (i, chunk) in self.chunks.iter().enumerate() {
316 match chunk {
317 SQLChunk::Token(Token::SELECT) => {
318 chunk.write(buf);
319 self.write_select_columns(buf, i);
320 }
321 SQLChunk::Param(param) => {
322 if let Some(name) = param.placeholder.name
325 && V::DIALECT == Dialect::SQLite
326 {
327 let _ = buf.write_char(':');
328 let _ = buf.write_str(name);
329 } else {
330 let _ = buf.write_str(&V::DIALECT.render_placeholder(param_index));
331 }
332 param_index += 1;
333 }
334 _ => chunk.write(buf),
335 }
336
337 if self.needs_space(i) {
338 let _ = buf.write_char(' ');
339 }
340 }
341 }
342
343 pub fn write_chunk_to(
345 &self,
346 buf: &mut impl core::fmt::Write,
347 chunk: &SQLChunk<'a, V>,
348 index: usize,
349 ) {
350 match chunk {
351 SQLChunk::Token(Token::SELECT) => {
352 chunk.write(buf);
353 self.write_select_columns(buf, index);
354 }
355 _ => chunk.write(buf),
356 }
357 }
358
359 fn write_select_columns(&self, buf: &mut impl core::fmt::Write, select_index: usize) {
361 let chunks = self.chunks.get(select_index + 1..select_index + 3);
362 match chunks {
363 Some([SQLChunk::Token(Token::FROM), SQLChunk::Table(table)]) => {
364 let _ = buf.write_char(' ');
365 self.write_qualified_columns(buf, *table);
366 }
367 Some([SQLChunk::Token(Token::FROM), _]) => {
368 let _ = buf.write_char(' ');
369 let _ = buf.write_str(Token::STAR.as_str());
370 }
371 _ => {}
372 }
373 }
374
375 pub fn write_qualified_columns(
377 &self,
378 buf: &mut impl core::fmt::Write,
379 table: &dyn SQLTableInfo,
380 ) {
381 let columns = table.columns();
382 if columns.is_empty() {
383 let _ = buf.write_char('*');
384 return;
385 }
386
387 for (i, col) in columns.iter().enumerate() {
388 if i > 0 {
389 let _ = buf.write_str(", ");
390 }
391 let _ = buf.write_char('"');
392 let _ = buf.write_str(table.name());
393 let _ = buf.write_str("\".\"");
394 let _ = buf.write_str(col.name());
395 let _ = buf.write_char('"');
396 }
397 }
398
399 fn estimate_capacity(&self) -> usize {
400 const PLACEHOLDER_SIZE: usize = 2;
401 const IDENT_OVERHEAD: usize = 2;
402 const COLUMN_OVERHEAD: usize = 5;
403
404 self.chunks
405 .iter()
406 .map(|chunk| match chunk {
407 SQLChunk::Token(t) => t.as_str().len(),
408 SQLChunk::Ident(s) => s.len() + IDENT_OVERHEAD,
409 SQLChunk::Raw(s) => s.len(),
410 SQLChunk::Param { .. } => PLACEHOLDER_SIZE,
411 SQLChunk::Table(t) => t.name().len() + IDENT_OVERHEAD,
412 SQLChunk::Column(c) => c.table().name().len() + c.name().len() + COLUMN_OVERHEAD,
413 })
414 .sum::<usize>()
415 + self.chunks.len()
416 }
417
418 fn needs_space(&self, index: usize) -> bool {
420 let Some(next) = self.chunks.get(index + 1) else {
421 return false;
422 };
423
424 let current = &self.chunks[index];
425 chunk_needs_space(current, next)
426 }
427
428 pub fn params(&self) -> impl Iterator<Item = &V> {
431 self.chunks.iter().filter_map(|chunk| {
432 if let SQLChunk::Param(Param {
433 value: Some(value), ..
434 }) = chunk
435 {
436 Some(value.as_ref())
437 } else {
438 None
439 }
440 })
441 }
442
443 pub fn bind<T: SQLParam + Into<V>>(
445 self,
446 params: impl IntoIterator<Item: Into<ParamBind<'a, T>>>,
447 ) -> SQL<'a, V> {
448 #[cfg(feature = "profiling")]
449 profile_sql!("bind");
450
451 let param_map: HashMap<&str, V> = params
452 .into_iter()
453 .map(Into::into)
454 .map(|p| (p.name, p.value.into()))
455 .collect();
456
457 let bound_chunks: SmallVec<[SQLChunk<'a, V>; 8]> = self
458 .chunks
459 .into_iter()
460 .map(|chunk| match chunk {
461 SQLChunk::Param(mut param) => {
462 if let Some(name) = param.placeholder.name
463 && let Some(value) = param_map.get(name)
464 {
465 param.value = Some(Cow::Owned(value.clone()));
466 }
467 SQLChunk::Param(param)
468 }
469 other => other,
470 })
471 .collect();
472
473 SQL {
474 chunks: bound_chunks,
475 }
476 }
477}
478
479pub(crate) fn chunk_needs_space<V: SQLParam>(
482 current: &SQLChunk<'_, V>,
483 next: &SQLChunk<'_, V>,
484) -> bool {
485 if let SQLChunk::Raw(text) = current
487 && text.ends_with(' ')
488 {
489 return false;
490 }
491
492 if let SQLChunk::Raw(text) = next
494 && text.starts_with(' ')
495 {
496 return false;
497 }
498
499 match (current, next) {
500 (_, SQLChunk::Token(Token::RPAREN | Token::COMMA | Token::SEMI | Token::DOT)) => false,
502 (SQLChunk::Token(Token::LPAREN | Token::DOT), _) => false,
504 (SQLChunk::Token(Token::COMMA), _) => true,
506 (SQLChunk::Token(Token::RPAREN), next) => next.is_word_like(),
508 (current, SQLChunk::Token(Token::LPAREN)) => current.is_word_like(),
510 (SQLChunk::Token(t), _) if t.is_operator() => true,
512 (_, SQLChunk::Token(t)) if t.is_operator() => true,
513 _ => current.is_word_like() && next.is_word_like(),
515 }
516}
517
518impl<'a, V: SQLParam> Default for SQL<'a, V> {
521 fn default() -> Self {
522 Self::empty()
523 }
524}
525
526impl<'a, V: SQLParam + 'a> From<&'a str> for SQL<'a, V> {
527 fn from(s: &'a str) -> Self {
528 SQL::raw(s)
529 }
530}
531
532impl<'a, V: SQLParam> From<Token> for SQL<'a, V> {
533 fn from(value: Token) -> Self {
534 SQL::token(value)
535 }
536}
537
538impl<'a, V: SQLParam + 'a> AsRef<SQL<'a, V>> for SQL<'a, V> {
539 fn as_ref(&self) -> &SQL<'a, V> {
540 self
541 }
542}
543
544impl<'a, V: SQLParam + core::fmt::Display> Display for SQL<'a, V> {
545 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
546 let params: Vec<_> = self.params().collect();
548 write!(f, r#"sql: "{}", params: {:?}"#, self.sql(), params)
549 }
550}
551
552impl<'a, V: SQLParam + 'a> ToSQL<'a, V> for SQL<'a, V> {
553 fn to_sql(&self) -> SQL<'a, V> {
554 self.clone()
555 }
556
557 fn into_sql(self) -> SQL<'a, V> {
558 self
559 }
560}
561
562impl<'a, V: SQLParam, T> FromIterator<T> for SQL<'a, V>
563where
564 SQLChunk<'a, V>: From<T>,
565{
566 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
567 let chunks = SmallVec::from_iter(iter.into_iter().map(SQLChunk::from));
568 Self { chunks }
569 }
570}
571
572impl<'a, V: SQLParam> IntoIterator for SQL<'a, V> {
573 type Item = SQLChunk<'a, V>;
574 type IntoIter = smallvec::IntoIter<[SQLChunk<'a, V>; 8]>;
575
576 fn into_iter(self) -> Self::IntoIter {
577 self.chunks.into_iter()
578 }
579}