1use crate::filter::FilterValue;
12use std::borrow::Cow;
13use std::collections::HashMap;
14use std::fmt::Write;
15use std::sync::{Arc, OnceLock, RwLock};
16use tracing::debug;
17
18pub mod keywords {
42 pub const SELECT: &str = "SELECT";
44 pub const INSERT: &str = "INSERT";
45 pub const UPDATE: &str = "UPDATE";
46 pub const DELETE: &str = "DELETE";
47 pub const INTO: &str = "INTO";
48 pub const VALUES: &str = "VALUES";
49 pub const SET: &str = "SET";
50 pub const FROM: &str = "FROM";
51 pub const WHERE: &str = "WHERE";
52 pub const RETURNING: &str = "RETURNING";
53
54 pub const AND: &str = "AND";
56 pub const OR: &str = "OR";
57 pub const NOT: &str = "NOT";
58 pub const IN: &str = "IN";
59 pub const IS: &str = "IS";
60 pub const NULL: &str = "NULL";
61 pub const LIKE: &str = "LIKE";
62 pub const ILIKE: &str = "ILIKE";
63 pub const BETWEEN: &str = "BETWEEN";
64 pub const EXISTS: &str = "EXISTS";
65
66 pub const ORDER_BY: &str = "ORDER BY";
68 pub const ASC: &str = "ASC";
69 pub const DESC: &str = "DESC";
70 pub const NULLS_FIRST: &str = "NULLS FIRST";
71 pub const NULLS_LAST: &str = "NULLS LAST";
72 pub const LIMIT: &str = "LIMIT";
73 pub const OFFSET: &str = "OFFSET";
74
75 pub const GROUP_BY: &str = "GROUP BY";
77 pub const HAVING: &str = "HAVING";
78 pub const DISTINCT: &str = "DISTINCT";
79 pub const DISTINCT_ON: &str = "DISTINCT ON";
80
81 pub const JOIN: &str = "JOIN";
83 pub const INNER_JOIN: &str = "INNER JOIN";
84 pub const LEFT_JOIN: &str = "LEFT JOIN";
85 pub const RIGHT_JOIN: &str = "RIGHT JOIN";
86 pub const FULL_JOIN: &str = "FULL OUTER JOIN";
87 pub const CROSS_JOIN: &str = "CROSS JOIN";
88 pub const LATERAL: &str = "LATERAL";
89 pub const ON: &str = "ON";
90 pub const USING: &str = "USING";
91
92 pub const WITH: &str = "WITH";
94 pub const RECURSIVE: &str = "RECURSIVE";
95 pub const AS: &str = "AS";
96 pub const MATERIALIZED: &str = "MATERIALIZED";
97 pub const NOT_MATERIALIZED: &str = "NOT MATERIALIZED";
98
99 pub const OVER: &str = "OVER";
101 pub const PARTITION_BY: &str = "PARTITION BY";
102 pub const ROWS: &str = "ROWS";
103 pub const RANGE: &str = "RANGE";
104 pub const GROUPS: &str = "GROUPS";
105 pub const UNBOUNDED_PRECEDING: &str = "UNBOUNDED PRECEDING";
106 pub const UNBOUNDED_FOLLOWING: &str = "UNBOUNDED FOLLOWING";
107 pub const CURRENT_ROW: &str = "CURRENT ROW";
108 pub const PRECEDING: &str = "PRECEDING";
109 pub const FOLLOWING: &str = "FOLLOWING";
110
111 pub const COUNT: &str = "COUNT";
113 pub const SUM: &str = "SUM";
114 pub const AVG: &str = "AVG";
115 pub const MIN: &str = "MIN";
116 pub const MAX: &str = "MAX";
117 pub const ROW_NUMBER: &str = "ROW_NUMBER";
118 pub const RANK: &str = "RANK";
119 pub const DENSE_RANK: &str = "DENSE_RANK";
120 pub const LAG: &str = "LAG";
121 pub const LEAD: &str = "LEAD";
122 pub const FIRST_VALUE: &str = "FIRST_VALUE";
123 pub const LAST_VALUE: &str = "LAST_VALUE";
124 pub const NTILE: &str = "NTILE";
125
126 pub const ON_CONFLICT: &str = "ON CONFLICT";
128 pub const DO_NOTHING: &str = "DO NOTHING";
129 pub const DO_UPDATE: &str = "DO UPDATE";
130 pub const EXCLUDED: &str = "excluded";
131 pub const ON_DUPLICATE_KEY: &str = "ON DUPLICATE KEY UPDATE";
132 pub const MERGE: &str = "MERGE";
133 pub const MATCHED: &str = "MATCHED";
134 pub const NOT_MATCHED: &str = "NOT MATCHED";
135
136 pub const FOR_UPDATE: &str = "FOR UPDATE";
138 pub const FOR_SHARE: &str = "FOR SHARE";
139 pub const NOWAIT: &str = "NOWAIT";
140 pub const SKIP_LOCKED: &str = "SKIP LOCKED";
141
142 pub const CREATE: &str = "CREATE";
144 pub const ALTER: &str = "ALTER";
145 pub const DROP: &str = "DROP";
146 pub const TABLE: &str = "TABLE";
147 pub const INDEX: &str = "INDEX";
148 pub const VIEW: &str = "VIEW";
149 pub const TRIGGER: &str = "TRIGGER";
150 pub const FUNCTION: &str = "FUNCTION";
151 pub const PROCEDURE: &str = "PROCEDURE";
152 pub const SEQUENCE: &str = "SEQUENCE";
153 pub const IF_EXISTS: &str = "IF EXISTS";
154 pub const IF_NOT_EXISTS: &str = "IF NOT EXISTS";
155 pub const OR_REPLACE: &str = "OR REPLACE";
156 pub const CASCADE: &str = "CASCADE";
157 pub const RESTRICT: &str = "RESTRICT";
158
159 pub const PRIMARY_KEY: &str = "PRIMARY KEY";
161 pub const FOREIGN_KEY: &str = "FOREIGN KEY";
162 pub const REFERENCES: &str = "REFERENCES";
163 pub const UNIQUE: &str = "UNIQUE";
164 pub const CHECK: &str = "CHECK";
165 pub const DEFAULT: &str = "DEFAULT";
166 pub const NOT_NULL: &str = "NOT NULL";
167
168 pub const SPACE: &str = " ";
170 pub const COMMA_SPACE: &str = ", ";
171 pub const OPEN_PAREN: &str = "(";
172 pub const CLOSE_PAREN: &str = ")";
173 pub const STAR: &str = "*";
174 pub const EQUALS: &str = " = ";
175 pub const NOT_EQUALS: &str = " <> ";
176 pub const LESS_THAN: &str = " < ";
177 pub const GREATER_THAN: &str = " > ";
178 pub const LESS_OR_EQUAL: &str = " <= ";
179 pub const GREATER_OR_EQUAL: &str = " >= ";
180}
181
182pub fn escape_identifier(name: &str) -> String {
184 let escaped = name.replace('"', "\"\"");
186 format!("\"{}\"", escaped)
187}
188
189pub fn needs_quoting(name: &str) -> bool {
191 let reserved = [
193 "user",
194 "order",
195 "group",
196 "select",
197 "from",
198 "where",
199 "table",
200 "index",
201 "key",
202 "primary",
203 "foreign",
204 "check",
205 "default",
206 "null",
207 "not",
208 "and",
209 "or",
210 "in",
211 "is",
212 "like",
213 "between",
214 "case",
215 "when",
216 "then",
217 "else",
218 "end",
219 "as",
220 "on",
221 "join",
222 "left",
223 "right",
224 "inner",
225 "outer",
226 "cross",
227 "natural",
228 "using",
229 "limit",
230 "offset",
231 "union",
232 "intersect",
233 "except",
234 "all",
235 "distinct",
236 "having",
237 "create",
238 "alter",
239 "drop",
240 "insert",
241 "update",
242 "delete",
243 "into",
244 "values",
245 "set",
246 "returning",
247 ];
248
249 if reserved.contains(&name.to_lowercase().as_str()) {
251 return true;
252 }
253
254 !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
256}
257
258pub fn quote_identifier(name: &str) -> String {
260 if needs_quoting(name) {
261 escape_identifier(name)
262 } else {
263 name.to_string()
264 }
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
269pub enum DatabaseType {
270 #[default]
272 PostgreSQL,
273 MySQL,
275 SQLite,
277 MSSQL,
279}
280
281const QUESTION_MARK_PLACEHOLDER: &str = "?";
283
284pub const POSTGRES_PLACEHOLDERS: &[&str] = &[
297 "$0", "$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9", "$10", "$11", "$12", "$13", "$14",
298 "$15", "$16", "$17", "$18", "$19", "$20", "$21", "$22", "$23", "$24", "$25", "$26", "$27",
299 "$28", "$29", "$30", "$31", "$32", "$33", "$34", "$35", "$36", "$37", "$38", "$39", "$40",
300 "$41", "$42", "$43", "$44", "$45", "$46", "$47", "$48", "$49", "$50", "$51", "$52", "$53",
301 "$54", "$55", "$56", "$57", "$58", "$59", "$60", "$61", "$62", "$63", "$64", "$65", "$66",
302 "$67", "$68", "$69", "$70", "$71", "$72", "$73", "$74", "$75", "$76", "$77", "$78", "$79",
303 "$80", "$81", "$82", "$83", "$84", "$85", "$86", "$87", "$88", "$89", "$90", "$91", "$92",
304 "$93", "$94", "$95", "$96", "$97", "$98", "$99", "$100", "$101", "$102", "$103", "$104",
305 "$105", "$106", "$107", "$108", "$109", "$110", "$111", "$112", "$113", "$114", "$115", "$116",
306 "$117", "$118", "$119", "$120", "$121", "$122", "$123", "$124", "$125", "$126", "$127", "$128",
307 "$129", "$130", "$131", "$132", "$133", "$134", "$135", "$136", "$137", "$138", "$139", "$140",
308 "$141", "$142", "$143", "$144", "$145", "$146", "$147", "$148", "$149", "$150", "$151", "$152",
309 "$153", "$154", "$155", "$156", "$157", "$158", "$159", "$160", "$161", "$162", "$163", "$164",
310 "$165", "$166", "$167", "$168", "$169", "$170", "$171", "$172", "$173", "$174", "$175", "$176",
311 "$177", "$178", "$179", "$180", "$181", "$182", "$183", "$184", "$185", "$186", "$187", "$188",
312 "$189", "$190", "$191", "$192", "$193", "$194", "$195", "$196", "$197", "$198", "$199", "$200",
313 "$201", "$202", "$203", "$204", "$205", "$206", "$207", "$208", "$209", "$210", "$211", "$212",
314 "$213", "$214", "$215", "$216", "$217", "$218", "$219", "$220", "$221", "$222", "$223", "$224",
315 "$225", "$226", "$227", "$228", "$229", "$230", "$231", "$232", "$233", "$234", "$235", "$236",
316 "$237", "$238", "$239", "$240", "$241", "$242", "$243", "$244", "$245", "$246", "$247", "$248",
317 "$249", "$250", "$251", "$252", "$253", "$254", "$255", "$256",
318];
319
320pub const MYSQL_IN_PATTERNS: &[&str] = &[
323 "", "?",
325 "?, ?",
326 "?, ?, ?",
327 "?, ?, ?, ?",
328 "?, ?, ?, ?, ?",
329 "?, ?, ?, ?, ?, ?",
330 "?, ?, ?, ?, ?, ?, ?",
331 "?, ?, ?, ?, ?, ?, ?, ?",
332 "?, ?, ?, ?, ?, ?, ?, ?, ?",
333 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?", "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
335 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
336 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
337 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
338 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
339 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?", "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
341 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
342 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
343 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?", "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
345 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
346 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
347 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
348 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?", "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
350 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
351 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
352 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
353 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?", "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
355 "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?", ];
357
358#[inline]
368pub fn postgres_in_pattern(start_idx: usize, count: usize) -> String {
369 if start_idx == 1 && count <= 10 {
371 static POSTGRES_IN_1: &[&str] = &[
372 "",
373 "$1",
374 "$1, $2",
375 "$1, $2, $3",
376 "$1, $2, $3, $4",
377 "$1, $2, $3, $4, $5",
378 "$1, $2, $3, $4, $5, $6",
379 "$1, $2, $3, $4, $5, $6, $7",
380 "$1, $2, $3, $4, $5, $6, $7, $8",
381 "$1, $2, $3, $4, $5, $6, $7, $8, $9",
382 "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10",
383 ];
384 return POSTGRES_IN_1[count].to_string();
385 }
386
387 let mut result = String::with_capacity(count * 5);
389 for i in 0..count {
390 if i > 0 {
391 result.push_str(", ");
392 }
393 let idx = start_idx + i;
394 if idx < POSTGRES_PLACEHOLDERS.len() {
395 result.push_str(POSTGRES_PLACEHOLDERS[idx]);
396 } else {
397 use std::fmt::Write;
398 let _ = write!(result, "${}", idx);
399 }
400 }
401 result
402}
403
404const POSTGRES_IN_FROM_1: &[&str] = &[
407 "", "$1", "$1, $2", "$1, $2, $3", "$1, $2, $3, $4", "$1, $2, $3, $4, $5", "$1, $2, $3, $4, $5, $6", "$1, $2, $3, $4, $5, $6, $7", "$1, $2, $3, $4, $5, $6, $7, $8", "$1, $2, $3, $4, $5, $6, $7, $8, $9", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31", "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32", ];
441
442#[inline]
449#[allow(clippy::needless_range_loop)]
450pub fn write_postgres_in_pattern(buf: &mut String, start_idx: usize, count: usize) {
451 if count == 0 {
452 return;
453 }
454
455 if start_idx == 1 && count < POSTGRES_IN_FROM_1.len() {
457 buf.push_str(POSTGRES_IN_FROM_1[count]);
458 return;
459 }
460
461 buf.reserve(count * 6);
464
465 let end_idx = start_idx + count;
467 let table_len = POSTGRES_PLACEHOLDERS.len();
468
469 if end_idx <= table_len {
470 buf.push_str(POSTGRES_PLACEHOLDERS[start_idx]);
472 for idx in (start_idx + 1)..end_idx {
473 buf.push_str(", ");
474 buf.push_str(POSTGRES_PLACEHOLDERS[idx]);
475 }
476 } else if start_idx >= table_len {
477 let _ = write!(buf, "${}", start_idx);
479 for idx in (start_idx + 1)..end_idx {
480 let _ = write!(buf, ", ${}", idx);
481 }
482 } else {
483 buf.push_str(POSTGRES_PLACEHOLDERS[start_idx]);
485 for idx in (start_idx + 1)..table_len.min(end_idx) {
486 buf.push_str(", ");
487 buf.push_str(POSTGRES_PLACEHOLDERS[idx]);
488 }
489 for idx in table_len..end_idx {
490 let _ = write!(buf, ", ${}", idx);
491 }
492 }
493}
494
495impl DatabaseType {
496 #[inline]
517 pub fn placeholder(&self, index: usize) -> Cow<'static, str> {
518 match self {
519 Self::PostgreSQL => {
520 if index > 0 && index < POSTGRES_PLACEHOLDERS.len() {
522 Cow::Borrowed(POSTGRES_PLACEHOLDERS[index])
523 } else {
524 Cow::Owned(format!("${}", index))
526 }
527 }
528 Self::MySQL | Self::SQLite => Cow::Borrowed(QUESTION_MARK_PLACEHOLDER),
529 Self::MSSQL => Cow::Owned(format!("@P{}", index)),
530 }
531 }
532
533 #[inline]
538 pub fn placeholder_string(&self, index: usize) -> String {
539 self.placeholder(index).into_owned()
540 }
541}
542
543#[derive(Debug, Clone)]
545pub struct SqlBuilder {
546 db_type: DatabaseType,
547 parts: Vec<String>,
548 params: Vec<FilterValue>,
549}
550
551impl SqlBuilder {
552 pub fn new(db_type: DatabaseType) -> Self {
554 Self {
555 db_type,
556 parts: Vec::new(),
557 params: Vec::new(),
558 }
559 }
560
561 pub fn postgres() -> Self {
563 Self::new(DatabaseType::PostgreSQL)
564 }
565
566 pub fn mysql() -> Self {
568 Self::new(DatabaseType::MySQL)
569 }
570
571 pub fn sqlite() -> Self {
573 Self::new(DatabaseType::SQLite)
574 }
575
576 pub fn push(&mut self, sql: impl AsRef<str>) -> &mut Self {
578 self.parts.push(sql.as_ref().to_string());
579 self
580 }
581
582 pub fn push_param(&mut self, value: impl Into<FilterValue>) -> &mut Self {
584 let index = self.params.len() + 1;
585 self.parts
588 .push(self.db_type.placeholder(index).into_owned());
589 self.params.push(value.into());
590 self
591 }
592
593 pub fn push_identifier(&mut self, name: &str) -> &mut Self {
595 self.parts.push(quote_identifier(name));
596 self
597 }
598
599 pub fn push_sep(&mut self, sep: &str) -> &mut Self {
601 self.parts.push(sep.to_string());
602 self
603 }
604
605 pub fn build(self) -> (String, Vec<FilterValue>) {
607 (self.parts.join(""), self.params)
608 }
609
610 pub fn sql(&self) -> String {
612 self.parts.join("")
613 }
614
615 pub fn params(&self) -> &[FilterValue] {
617 &self.params
618 }
619
620 pub fn next_param_index(&self) -> usize {
622 self.params.len() + 1
623 }
624}
625
626impl Default for SqlBuilder {
627 fn default() -> Self {
628 Self::postgres()
629 }
630}
631
632#[derive(Debug, Clone, Copy)]
638pub enum QueryCapacity {
639 SimpleSelect,
641 SelectWithFilters(usize),
643 Insert(usize),
645 Update(usize),
647 Delete,
649 Custom(usize),
651}
652
653impl QueryCapacity {
654 #[inline]
656 pub const fn estimate(&self) -> usize {
657 match self {
658 Self::SimpleSelect => 64,
659 Self::SelectWithFilters(n) => 64 + *n * 32,
660 Self::Insert(cols) => 32 + *cols * 16,
661 Self::Update(cols) => 32 + *cols * 20,
662 Self::Delete => 48,
663 Self::Custom(cap) => *cap,
664 }
665 }
666}
667
668#[derive(Debug, Clone)]
705pub struct FastSqlBuilder {
706 buffer: String,
708 params: Vec<FilterValue>,
710 db_type: DatabaseType,
712}
713
714impl FastSqlBuilder {
715 #[inline]
717 pub fn new(db_type: DatabaseType) -> Self {
718 Self {
719 buffer: String::new(),
720 params: Vec::new(),
721 db_type,
722 }
723 }
724
725 #[inline]
727 pub fn with_capacity(db_type: DatabaseType, capacity: QueryCapacity) -> Self {
728 Self {
729 buffer: String::with_capacity(capacity.estimate()),
730 params: Vec::with_capacity(match capacity {
731 QueryCapacity::SimpleSelect => 2,
732 QueryCapacity::SelectWithFilters(n) => n,
733 QueryCapacity::Insert(n) => n,
734 QueryCapacity::Update(n) => n + 1,
735 QueryCapacity::Delete => 2,
736 QueryCapacity::Custom(n) => n / 16,
737 }),
738 db_type,
739 }
740 }
741
742 #[inline]
744 pub fn postgres(capacity: QueryCapacity) -> Self {
745 Self::with_capacity(DatabaseType::PostgreSQL, capacity)
746 }
747
748 #[inline]
750 pub fn mysql(capacity: QueryCapacity) -> Self {
751 Self::with_capacity(DatabaseType::MySQL, capacity)
752 }
753
754 #[inline]
756 pub fn sqlite(capacity: QueryCapacity) -> Self {
757 Self::with_capacity(DatabaseType::SQLite, capacity)
758 }
759
760 #[inline]
762 pub fn push_str(&mut self, s: &str) -> &mut Self {
763 self.buffer.push_str(s);
764 self
765 }
766
767 #[inline]
769 pub fn push_char(&mut self, c: char) -> &mut Self {
770 self.buffer.push(c);
771 self
772 }
773
774 #[inline]
776 pub fn bind(&mut self, value: impl Into<FilterValue>) -> &mut Self {
777 let index = self.params.len() + 1;
778 let placeholder = self.db_type.placeholder(index);
779 self.buffer.push_str(&placeholder);
780 self.params.push(value.into());
781 self
782 }
783
784 #[inline]
786 pub fn push_bind(&mut self, s: &str, value: impl Into<FilterValue>) -> &mut Self {
787 self.push_str(s);
788 self.bind(value)
789 }
790
791 pub fn bind_in_clause(&mut self, values: impl IntoIterator<Item = FilterValue>) -> &mut Self {
818 let values: Vec<FilterValue> = values.into_iter().collect();
819 if values.is_empty() {
820 return self;
821 }
822
823 let start_index = self.params.len() + 1;
824 let count = values.len();
825
826 match self.db_type {
828 DatabaseType::PostgreSQL => {
829 let estimated_len = count * 5;
831 self.buffer.reserve(estimated_len);
832
833 for (i, _) in values.iter().enumerate() {
834 if i > 0 {
835 self.buffer.push_str(", ");
836 }
837 let idx = start_index + i;
838 if idx < POSTGRES_PLACEHOLDERS.len() {
839 self.buffer.push_str(POSTGRES_PLACEHOLDERS[idx]);
840 } else {
841 let _ = write!(self.buffer, "${}", idx);
842 }
843 }
844 }
845 DatabaseType::MySQL | DatabaseType::SQLite => {
846 if start_index == 1 && count < MYSQL_IN_PATTERNS.len() {
848 self.buffer.push_str(MYSQL_IN_PATTERNS[count]);
849 } else {
850 let estimated_len = count * 3; self.buffer.reserve(estimated_len);
853 for i in 0..count {
854 if i > 0 {
855 self.buffer.push_str(", ");
856 }
857 self.buffer.push('?');
858 }
859 }
860 }
861 DatabaseType::MSSQL => {
862 let estimated_len = count * 6; self.buffer.reserve(estimated_len);
865
866 for (i, _) in values.iter().enumerate() {
867 if i > 0 {
868 self.buffer.push_str(", ");
869 }
870 let idx = start_index + i;
871 let _ = write!(self.buffer, "@P{}", idx);
872 }
873 }
874 }
875
876 self.params.extend(values);
877 self
878 }
879
880 pub fn bind_in_slice<T: Into<FilterValue> + Clone>(&mut self, values: &[T]) -> &mut Self {
902 if values.is_empty() {
903 return self;
904 }
905
906 let start_index = self.params.len() + 1;
907 let count = values.len();
908
909 match self.db_type {
911 DatabaseType::PostgreSQL => {
912 let estimated_len = count * 5;
913 self.buffer.reserve(estimated_len);
914
915 for i in 0..count {
916 if i > 0 {
917 self.buffer.push_str(", ");
918 }
919 let idx = start_index + i;
920 if idx < POSTGRES_PLACEHOLDERS.len() {
921 self.buffer.push_str(POSTGRES_PLACEHOLDERS[idx]);
922 } else {
923 let _ = write!(self.buffer, "${}", idx);
924 }
925 }
926 }
927 DatabaseType::MySQL | DatabaseType::SQLite => {
928 if start_index == 1 && count < MYSQL_IN_PATTERNS.len() {
929 self.buffer.push_str(MYSQL_IN_PATTERNS[count]);
930 } else {
931 let estimated_len = count * 3;
932 self.buffer.reserve(estimated_len);
933 for i in 0..count {
934 if i > 0 {
935 self.buffer.push_str(", ");
936 }
937 self.buffer.push('?');
938 }
939 }
940 }
941 DatabaseType::MSSQL => {
942 let estimated_len = count * 6;
943 self.buffer.reserve(estimated_len);
944
945 for i in 0..count {
946 if i > 0 {
947 self.buffer.push_str(", ");
948 }
949 let idx = start_index + i;
950 let _ = write!(self.buffer, "@P{}", idx);
951 }
952 }
953 }
954
955 self.params.reserve(count);
957 for v in values {
958 self.params.push(v.clone().into());
959 }
960 self
961 }
962
963 #[inline]
968 pub fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> &mut Self {
969 let _ = self.buffer.write_fmt(args);
970 self
971 }
972
973 #[inline]
975 pub fn push_identifier(&mut self, name: &str) -> &mut Self {
976 if needs_quoting(name) {
977 self.buffer.push('"');
978 for c in name.chars() {
980 if c == '"' {
981 self.buffer.push_str("\"\"");
982 } else {
983 self.buffer.push(c);
984 }
985 }
986 self.buffer.push('"');
987 } else {
988 self.buffer.push_str(name);
989 }
990 self
991 }
992
993 #[inline]
995 pub fn push_if(&mut self, condition: bool, s: &str) -> &mut Self {
996 if condition {
997 self.push_str(s);
998 }
999 self
1000 }
1001
1002 #[inline]
1004 pub fn bind_if(&mut self, condition: bool, value: impl Into<FilterValue>) -> &mut Self {
1005 if condition {
1006 self.bind(value);
1007 }
1008 self
1009 }
1010
1011 #[inline]
1013 pub fn sql(&self) -> &str {
1014 &self.buffer
1015 }
1016
1017 #[inline]
1019 pub fn params(&self) -> &[FilterValue] {
1020 &self.params
1021 }
1022
1023 #[inline]
1025 pub fn param_count(&self) -> usize {
1026 self.params.len()
1027 }
1028
1029 #[inline]
1031 pub fn build(self) -> (String, Vec<FilterValue>) {
1032 let sql_len = self.buffer.len();
1033 let param_count = self.params.len();
1034 debug!(sql_len, param_count, db_type = ?self.db_type, "FastSqlBuilder::build()");
1035 (self.buffer, self.params)
1036 }
1037
1038 #[inline]
1040 pub fn build_sql(self) -> String {
1041 self.buffer
1042 }
1043}
1044
1045pub mod templates {
1053 use super::*;
1054
1055 pub fn select_by_id(table: &str, columns: &[&str]) -> String {
1068 let cols = if columns.is_empty() {
1069 "*".to_string()
1070 } else {
1071 columns.join(", ")
1072 };
1073 format!("SELECT {} FROM {} WHERE id = $1", cols, table)
1074 }
1075
1076 pub fn insert_returning(table: &str, columns: &[&str]) -> String {
1088 let cols = columns.join(", ");
1089 let placeholders: Vec<String> = (1..=columns.len())
1090 .map(|i| {
1091 if i < POSTGRES_PLACEHOLDERS.len() {
1092 POSTGRES_PLACEHOLDERS[i].to_string()
1093 } else {
1094 format!("${}", i)
1095 }
1096 })
1097 .collect();
1098 format!(
1099 "INSERT INTO {} ({}) VALUES ({}) RETURNING *",
1100 table,
1101 cols,
1102 placeholders.join(", ")
1103 )
1104 }
1105
1106 pub fn update_by_id(table: &str, columns: &[&str]) -> String {
1118 let sets: Vec<String> = columns
1119 .iter()
1120 .enumerate()
1121 .map(|(i, col)| {
1122 let idx = i + 1;
1123 if idx < POSTGRES_PLACEHOLDERS.len() {
1124 format!("{} = {}", col, POSTGRES_PLACEHOLDERS[idx])
1125 } else {
1126 format!("{} = ${}", col, idx)
1127 }
1128 })
1129 .collect();
1130 let id_idx = columns.len() + 1;
1131 let id_placeholder = if id_idx < POSTGRES_PLACEHOLDERS.len() {
1132 POSTGRES_PLACEHOLDERS[id_idx]
1133 } else {
1134 "$?"
1135 };
1136 format!(
1137 "UPDATE {} SET {} WHERE id = {}",
1138 table,
1139 sets.join(", "),
1140 id_placeholder
1141 )
1142 }
1143
1144 pub fn delete_by_id(table: &str) -> String {
1155 format!("DELETE FROM {} WHERE id = $1", table)
1156 }
1157
1158 pub fn batch_placeholders(db_type: DatabaseType, columns: usize, rows: usize) -> String {
1170 let mut result = String::with_capacity(rows * columns * 4);
1171 let mut param_idx = 1;
1172
1173 for row in 0..rows {
1174 if row > 0 {
1175 result.push_str(", ");
1176 }
1177 result.push('(');
1178 for col in 0..columns {
1179 if col > 0 {
1180 result.push_str(", ");
1181 }
1182 match db_type {
1183 DatabaseType::PostgreSQL => {
1184 if param_idx < POSTGRES_PLACEHOLDERS.len() {
1185 result.push_str(POSTGRES_PLACEHOLDERS[param_idx]);
1186 } else {
1187 let _ = write!(result, "${}", param_idx);
1188 }
1189 param_idx += 1;
1190 }
1191 DatabaseType::MySQL | DatabaseType::SQLite => {
1192 result.push('?');
1193 }
1194 DatabaseType::MSSQL => {
1195 let _ = write!(result, "@P{}", param_idx);
1196 param_idx += 1;
1197 }
1198 }
1199 }
1200 result.push(')');
1201 }
1202
1203 result
1204 }
1205}
1206
1207pub struct LazySql<F>
1231where
1232 F: Fn(DatabaseType) -> String,
1233{
1234 generator: F,
1235}
1236
1237impl<F> LazySql<F>
1238where
1239 F: Fn(DatabaseType) -> String,
1240{
1241 #[inline]
1243 pub const fn new(generator: F) -> Self {
1244 Self { generator }
1245 }
1246
1247 #[inline]
1249 pub fn get(&self, db_type: DatabaseType) -> String {
1250 (self.generator)(db_type)
1251 }
1252}
1253
1254pub struct CachedSql<F>
1277where
1278 F: Fn(DatabaseType) -> String,
1279{
1280 generator: F,
1281 postgres: OnceLock<String>,
1282 mysql: OnceLock<String>,
1283 sqlite: OnceLock<String>,
1284 mssql: OnceLock<String>,
1285}
1286
1287impl<F> CachedSql<F>
1288where
1289 F: Fn(DatabaseType) -> String,
1290{
1291 pub const fn new(generator: F) -> Self {
1293 Self {
1294 generator,
1295 postgres: OnceLock::new(),
1296 mysql: OnceLock::new(),
1297 sqlite: OnceLock::new(),
1298 mssql: OnceLock::new(),
1299 }
1300 }
1301
1302 pub fn get(&self, db_type: DatabaseType) -> &str {
1307 match db_type {
1308 DatabaseType::PostgreSQL => self.postgres.get_or_init(|| (self.generator)(db_type)),
1309 DatabaseType::MySQL => self.mysql.get_or_init(|| (self.generator)(db_type)),
1310 DatabaseType::SQLite => self.sqlite.get_or_init(|| (self.generator)(db_type)),
1311 DatabaseType::MSSQL => self.mssql.get_or_init(|| (self.generator)(db_type)),
1312 }
1313 }
1314}
1315
1316pub struct SqlTemplateCache {
1345 cache: RwLock<HashMap<(String, DatabaseType), Arc<String>>>,
1346}
1347
1348impl Default for SqlTemplateCache {
1349 fn default() -> Self {
1350 Self::new()
1351 }
1352}
1353
1354impl SqlTemplateCache {
1355 pub fn new() -> Self {
1357 Self {
1358 cache: RwLock::new(HashMap::new()),
1359 }
1360 }
1361
1362 pub fn with_capacity(capacity: usize) -> Self {
1364 Self {
1365 cache: RwLock::new(HashMap::with_capacity(capacity)),
1366 }
1367 }
1368
1369 pub fn get_or_insert<F>(&self, key: &str, db_type: DatabaseType, generator: F) -> Arc<String>
1374 where
1375 F: FnOnce() -> String,
1376 {
1377 let cache_key = (key.to_string(), db_type);
1378
1379 {
1381 let cache = self.cache.read().unwrap();
1382 if let Some(sql) = cache.get(&cache_key) {
1383 return Arc::clone(sql);
1384 }
1385 }
1386
1387 let mut cache = self.cache.write().unwrap();
1389
1390 if let Some(sql) = cache.get(&cache_key) {
1392 return Arc::clone(sql);
1393 }
1394
1395 let sql = Arc::new(generator());
1396 cache.insert(cache_key, Arc::clone(&sql));
1397 sql
1398 }
1399
1400 pub fn contains(&self, key: &str, db_type: DatabaseType) -> bool {
1402 let cache_key = (key.to_string(), db_type);
1403 self.cache.read().unwrap().contains_key(&cache_key)
1404 }
1405
1406 pub fn clear(&self) {
1408 self.cache.write().unwrap().clear();
1409 }
1410
1411 pub fn len(&self) -> usize {
1413 self.cache.read().unwrap().len()
1414 }
1415
1416 pub fn is_empty(&self) -> bool {
1418 self.cache.read().unwrap().is_empty()
1419 }
1420}
1421
1422pub fn global_sql_cache() -> &'static SqlTemplateCache {
1437 static CACHE: OnceLock<SqlTemplateCache> = OnceLock::new();
1438 CACHE.get_or_init(|| SqlTemplateCache::with_capacity(64))
1439}
1440
1441#[derive(Debug, Clone, Copy)]
1447pub enum AdvancedQueryCapacity {
1448 Cte {
1450 cte_count: usize,
1452 avg_query_len: usize,
1454 },
1455 WindowFunction {
1457 window_count: usize,
1459 partition_cols: usize,
1461 order_cols: usize,
1463 },
1464 FullTextSearch {
1466 columns: usize,
1468 query_len: usize,
1470 },
1471 JsonPath {
1473 depth: usize,
1475 },
1476 Upsert {
1478 columns: usize,
1480 conflict_cols: usize,
1482 update_cols: usize,
1484 },
1485 ProcedureCall {
1487 params: usize,
1489 },
1490 TriggerDef {
1492 events: usize,
1494 body_len: usize,
1496 },
1497 RlsPolicy {
1499 expr_len: usize,
1501 },
1502}
1503
1504impl AdvancedQueryCapacity {
1505 #[inline]
1507 pub const fn estimate(&self) -> usize {
1508 match self {
1509 Self::Cte {
1510 cte_count,
1511 avg_query_len,
1512 } => {
1513 16 + *cte_count * (32 + *avg_query_len)
1515 }
1516 Self::WindowFunction {
1517 window_count,
1518 partition_cols,
1519 order_cols,
1520 } => {
1521 *window_count * (48 + *partition_cols * 16 + *order_cols * 20)
1523 }
1524 Self::FullTextSearch { columns, query_len } => {
1525 64 + *columns * 20 + *query_len
1527 }
1528 Self::JsonPath { depth } => {
1529 16 + *depth * 12
1531 }
1532 Self::Upsert {
1533 columns,
1534 conflict_cols,
1535 update_cols,
1536 } => {
1537 64 + *columns * 8 + *conflict_cols * 12 + *update_cols * 16
1539 }
1540 Self::ProcedureCall { params } => {
1541 32 + *params * 8
1543 }
1544 Self::TriggerDef { events, body_len } => {
1545 96 + *events * 12 + *body_len
1547 }
1548 Self::RlsPolicy { expr_len } => {
1549 64 + *expr_len
1551 }
1552 }
1553 }
1554
1555 #[inline]
1557 pub const fn to_query_capacity(&self) -> QueryCapacity {
1558 QueryCapacity::Custom(self.estimate())
1559 }
1560}
1561
1562impl FastSqlBuilder {
1564 #[inline]
1566 pub fn for_advanced(db_type: DatabaseType, capacity: AdvancedQueryCapacity) -> Self {
1567 Self::with_capacity(db_type, capacity.to_query_capacity())
1568 }
1569
1570 #[inline]
1572 pub fn for_cte(db_type: DatabaseType, cte_count: usize, avg_query_len: usize) -> Self {
1573 Self::for_advanced(
1574 db_type,
1575 AdvancedQueryCapacity::Cte {
1576 cte_count,
1577 avg_query_len,
1578 },
1579 )
1580 }
1581
1582 #[inline]
1584 pub fn for_window(
1585 db_type: DatabaseType,
1586 window_count: usize,
1587 partition_cols: usize,
1588 order_cols: usize,
1589 ) -> Self {
1590 Self::for_advanced(
1591 db_type,
1592 AdvancedQueryCapacity::WindowFunction {
1593 window_count,
1594 partition_cols,
1595 order_cols,
1596 },
1597 )
1598 }
1599
1600 #[inline]
1602 pub fn for_upsert(
1603 db_type: DatabaseType,
1604 columns: usize,
1605 conflict_cols: usize,
1606 update_cols: usize,
1607 ) -> Self {
1608 Self::for_advanced(
1609 db_type,
1610 AdvancedQueryCapacity::Upsert {
1611 columns,
1612 conflict_cols,
1613 update_cols,
1614 },
1615 )
1616 }
1617}
1618
1619pub mod parse {
1636 pub fn extract_where_body(sql: &str) -> Option<String> {
1640 let lower = sql.to_ascii_lowercase();
1641 let i = lower.find(" where ")?;
1642 Some(sql[i + " where ".len()..].trim().to_string())
1643 }
1644
1645 pub fn extract_insert_columns(sql: &str) -> Option<Vec<String>> {
1649 let open = sql.find('(')?;
1650 let close = sql[open..].find(')').map(|i| open + i)?;
1651 let body = &sql[open + 1..close];
1652 Some(
1653 body.split(',')
1654 .map(|c| c.trim().to_string())
1655 .filter(|c| !c.is_empty())
1656 .collect(),
1657 )
1658 }
1659
1660 pub fn count_set_placeholders(sql: &str) -> Option<usize> {
1666 let lower = sql.to_ascii_lowercase();
1667 let set_start = lower.find(" set ")?;
1668 let where_start = lower[set_start..]
1669 .find(" where ")
1670 .map(|i| set_start + i)
1671 .unwrap_or(sql.len());
1672 Some(sql[set_start..where_start].matches('?').count())
1673 }
1674
1675 #[cfg(test)]
1676 mod tests {
1677 use super::*;
1678
1679 #[test]
1680 fn extract_where_body_finds_lowercase_tail() {
1681 assert_eq!(
1682 extract_where_body("UPDATE t SET a = 1 WHERE id = 42"),
1683 Some("id = 42".to_string())
1684 );
1685 }
1686
1687 #[test]
1688 fn extract_where_body_case_insensitive() {
1689 assert_eq!(
1690 extract_where_body("update t set a = 1 where id = 42"),
1691 Some("id = 42".to_string())
1692 );
1693 }
1694
1695 #[test]
1696 fn extract_where_body_missing_returns_none() {
1697 assert_eq!(extract_where_body("SELECT * FROM t"), None);
1698 }
1699
1700 #[test]
1701 fn extract_insert_columns_parses_list() {
1702 assert_eq!(
1703 extract_insert_columns("INSERT INTO users (id, email, name) VALUES ($1, $2, $3)"),
1704 Some(vec!["id".into(), "email".into(), "name".into()])
1705 );
1706 }
1707
1708 #[test]
1709 fn count_set_placeholders_counts_between_set_and_where() {
1710 assert_eq!(
1711 count_set_placeholders("UPDATE t SET a = ?, b = ? WHERE id = ?"),
1712 Some(2)
1713 );
1714 }
1715
1716 #[test]
1717 fn count_set_placeholders_without_where_counts_to_end() {
1718 assert_eq!(count_set_placeholders("UPDATE t SET a = ?, b = ?"), Some(2));
1719 }
1720 }
1721}
1722
1723#[cfg(test)]
1724mod tests {
1725 use super::*;
1726
1727 #[test]
1728 fn test_escape_identifier() {
1729 assert_eq!(escape_identifier("user"), "\"user\"");
1730 assert_eq!(escape_identifier("my_table"), "\"my_table\"");
1731 assert_eq!(escape_identifier("has\"quote"), "\"has\"\"quote\"");
1732 }
1733
1734 #[test]
1735 fn test_needs_quoting() {
1736 assert!(needs_quoting("user"));
1737 assert!(needs_quoting("order"));
1738 assert!(needs_quoting("has space"));
1739 assert!(!needs_quoting("my_table"));
1740 assert!(!needs_quoting("users"));
1741 }
1742
1743 #[test]
1744 fn test_quote_identifier() {
1745 assert_eq!(quote_identifier("user"), "\"user\"");
1746 assert_eq!(quote_identifier("my_table"), "my_table");
1747 }
1748
1749 #[test]
1750 fn test_database_placeholder() {
1751 assert_eq!(DatabaseType::PostgreSQL.placeholder(1).as_ref(), "$1");
1753 assert_eq!(DatabaseType::PostgreSQL.placeholder(5).as_ref(), "$5");
1754 assert_eq!(DatabaseType::PostgreSQL.placeholder(100).as_ref(), "$100");
1755 assert_eq!(DatabaseType::PostgreSQL.placeholder(128).as_ref(), "$128");
1756 assert_eq!(DatabaseType::PostgreSQL.placeholder(256).as_ref(), "$256");
1757 assert_eq!(DatabaseType::MySQL.placeholder(1).as_ref(), "?");
1758 assert_eq!(DatabaseType::SQLite.placeholder(1).as_ref(), "?");
1759
1760 assert!(matches!(
1762 DatabaseType::MySQL.placeholder(1),
1763 Cow::Borrowed(_)
1764 ));
1765 assert!(matches!(
1766 DatabaseType::SQLite.placeholder(1),
1767 Cow::Borrowed(_)
1768 ));
1769
1770 assert!(matches!(
1772 DatabaseType::PostgreSQL.placeholder(1),
1773 Cow::Borrowed(_)
1774 ));
1775 assert!(matches!(
1776 DatabaseType::PostgreSQL.placeholder(50),
1777 Cow::Borrowed(_)
1778 ));
1779 assert!(matches!(
1780 DatabaseType::PostgreSQL.placeholder(128),
1781 Cow::Borrowed(_)
1782 ));
1783 assert!(matches!(
1784 DatabaseType::PostgreSQL.placeholder(256),
1785 Cow::Borrowed(_)
1786 ));
1787
1788 assert!(matches!(
1790 DatabaseType::PostgreSQL.placeholder(257),
1791 Cow::Owned(_)
1792 ));
1793 assert_eq!(DatabaseType::PostgreSQL.placeholder(257).as_ref(), "$257");
1794 assert_eq!(DatabaseType::PostgreSQL.placeholder(200).as_ref(), "$200");
1795
1796 assert!(matches!(
1798 DatabaseType::PostgreSQL.placeholder(0),
1799 Cow::Owned(_)
1800 ));
1801 assert_eq!(DatabaseType::PostgreSQL.placeholder(0).as_ref(), "$0");
1802 }
1803
1804 #[test]
1805 fn test_sql_builder() {
1806 let mut builder = SqlBuilder::postgres();
1807 builder
1808 .push("SELECT * FROM ")
1809 .push_identifier("user")
1810 .push(" WHERE ")
1811 .push_identifier("id")
1812 .push(" = ")
1813 .push_param(42i32);
1814
1815 let (sql, params) = builder.build();
1816 assert_eq!(sql, "SELECT * FROM \"user\" WHERE id = $1");
1817 assert_eq!(params.len(), 1);
1818 }
1819
1820 #[test]
1822 fn test_fast_builder_simple() {
1823 let mut builder = FastSqlBuilder::postgres(QueryCapacity::SimpleSelect);
1824 builder.push_str("SELECT * FROM users WHERE id = ");
1825 builder.bind(42i64);
1826 let (sql, params) = builder.build();
1827 assert_eq!(sql, "SELECT * FROM users WHERE id = $1");
1828 assert_eq!(params.len(), 1);
1829 }
1830
1831 #[test]
1832 fn test_fast_builder_complex() {
1833 let mut builder = FastSqlBuilder::with_capacity(
1834 DatabaseType::PostgreSQL,
1835 QueryCapacity::SelectWithFilters(5),
1836 );
1837 builder
1838 .push_str("SELECT * FROM users WHERE active = ")
1839 .bind(true)
1840 .push_str(" AND age > ")
1841 .bind(18i64)
1842 .push_str(" AND status = ")
1843 .bind("approved")
1844 .push_str(" ORDER BY created_at LIMIT ")
1845 .bind(10i64);
1846
1847 let (sql, params) = builder.build();
1848 assert!(sql.contains("$1"));
1849 assert!(sql.contains("$4"));
1850 assert_eq!(params.len(), 4);
1851 }
1852
1853 #[test]
1854 fn test_fast_builder_in_clause_postgres() {
1855 let mut builder = FastSqlBuilder::postgres(QueryCapacity::Custom(128));
1856 builder.push_str("SELECT * FROM users WHERE id IN (");
1857 let values: Vec<FilterValue> = (1..=5).map(FilterValue::Int).collect();
1858 builder.bind_in_clause(values);
1859 builder.push_char(')');
1860
1861 let (sql, params) = builder.build();
1862 assert_eq!(sql, "SELECT * FROM users WHERE id IN ($1, $2, $3, $4, $5)");
1863 assert_eq!(params.len(), 5);
1864 }
1865
1866 #[test]
1867 fn test_fast_builder_in_clause_mysql() {
1868 let mut builder = FastSqlBuilder::mysql(QueryCapacity::Custom(128));
1869 builder.push_str("SELECT * FROM users WHERE id IN (");
1870 let values: Vec<FilterValue> = (1..=5).map(FilterValue::Int).collect();
1871 builder.bind_in_clause(values);
1872 builder.push_char(')');
1873
1874 let (sql, params) = builder.build();
1875 assert_eq!(sql, "SELECT * FROM users WHERE id IN (?, ?, ?, ?, ?)");
1876 assert_eq!(params.len(), 5);
1877 }
1878
1879 #[test]
1880 fn test_fast_builder_identifier() {
1881 let mut builder = FastSqlBuilder::postgres(QueryCapacity::SimpleSelect);
1882 builder.push_str("SELECT * FROM ");
1883 builder.push_identifier("user"); builder.push_str(" WHERE ");
1885 builder.push_identifier("my_column"); builder.push_str(" = ");
1887 builder.bind(1i64);
1888
1889 let (sql, _) = builder.build();
1890 assert_eq!(sql, "SELECT * FROM \"user\" WHERE my_column = $1");
1891 }
1892
1893 #[test]
1894 fn test_fast_builder_identifier_with_quotes() {
1895 let mut builder = FastSqlBuilder::postgres(QueryCapacity::SimpleSelect);
1896 builder.push_str("SELECT * FROM ");
1897 builder.push_identifier("has\"quote");
1898
1899 let sql = builder.build_sql();
1900 assert_eq!(sql, "SELECT * FROM \"has\"\"quote\"");
1901 }
1902
1903 #[test]
1904 fn test_fast_builder_conditional() {
1905 let mut builder = FastSqlBuilder::postgres(QueryCapacity::SelectWithFilters(2));
1906 builder.push_str("SELECT * FROM users WHERE 1=1");
1907 builder.push_if(true, " AND active = true");
1908 builder.push_if(false, " AND deleted = false");
1909
1910 let sql = builder.build_sql();
1911 assert_eq!(sql, "SELECT * FROM users WHERE 1=1 AND active = true");
1912 }
1913
1914 #[test]
1916 fn test_template_select_by_id() {
1917 let sql = templates::select_by_id("users", &["id", "name", "email"]);
1918 assert_eq!(sql, "SELECT id, name, email FROM users WHERE id = $1");
1919 }
1920
1921 #[test]
1922 fn test_template_select_by_id_all_columns() {
1923 let sql = templates::select_by_id("users", &[]);
1924 assert_eq!(sql, "SELECT * FROM users WHERE id = $1");
1925 }
1926
1927 #[test]
1928 fn test_template_insert_returning() {
1929 let sql = templates::insert_returning("users", &["name", "email"]);
1930 assert_eq!(
1931 sql,
1932 "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *"
1933 );
1934 }
1935
1936 #[test]
1937 fn test_template_update_by_id() {
1938 let sql = templates::update_by_id("users", &["name", "email"]);
1939 assert_eq!(sql, "UPDATE users SET name = $1, email = $2 WHERE id = $3");
1940 }
1941
1942 #[test]
1943 fn test_template_delete_by_id() {
1944 let sql = templates::delete_by_id("users");
1945 assert_eq!(sql, "DELETE FROM users WHERE id = $1");
1946 }
1947
1948 #[test]
1949 fn test_template_batch_placeholders_postgres() {
1950 let sql = templates::batch_placeholders(DatabaseType::PostgreSQL, 3, 2);
1951 assert_eq!(sql, "($1, $2, $3), ($4, $5, $6)");
1952 }
1953
1954 #[test]
1955 fn test_template_batch_placeholders_mysql() {
1956 let sql = templates::batch_placeholders(DatabaseType::MySQL, 3, 2);
1957 assert_eq!(sql, "(?, ?, ?), (?, ?, ?)");
1958 }
1959
1960 #[test]
1961 fn test_query_capacity_estimates() {
1962 assert_eq!(QueryCapacity::SimpleSelect.estimate(), 64);
1963 assert_eq!(QueryCapacity::SelectWithFilters(5).estimate(), 64 + 5 * 32);
1964 assert_eq!(QueryCapacity::Insert(10).estimate(), 32 + 10 * 16);
1965 assert_eq!(QueryCapacity::Update(5).estimate(), 32 + 5 * 20);
1966 assert_eq!(QueryCapacity::Delete.estimate(), 48);
1967 assert_eq!(QueryCapacity::Custom(256).estimate(), 256);
1968 }
1969}