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