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 => {
1311 self.postgres.get_or_init(|| (self.generator)(db_type))
1312 }
1313 DatabaseType::MySQL => self.mysql.get_or_init(|| (self.generator)(db_type)),
1314 DatabaseType::SQLite => self.sqlite.get_or_init(|| (self.generator)(db_type)),
1315 DatabaseType::MSSQL => self.mssql.get_or_init(|| (self.generator)(db_type)),
1316 }
1317 }
1318}
1319
1320pub struct SqlTemplateCache {
1349 cache: RwLock<HashMap<(String, DatabaseType), Arc<String>>>,
1350}
1351
1352impl Default for SqlTemplateCache {
1353 fn default() -> Self {
1354 Self::new()
1355 }
1356}
1357
1358impl SqlTemplateCache {
1359 pub fn new() -> Self {
1361 Self {
1362 cache: RwLock::new(HashMap::new()),
1363 }
1364 }
1365
1366 pub fn with_capacity(capacity: usize) -> Self {
1368 Self {
1369 cache: RwLock::new(HashMap::with_capacity(capacity)),
1370 }
1371 }
1372
1373 pub fn get_or_insert<F>(&self, key: &str, db_type: DatabaseType, generator: F) -> Arc<String>
1378 where
1379 F: FnOnce() -> String,
1380 {
1381 let cache_key = (key.to_string(), db_type);
1382
1383 {
1385 let cache = self.cache.read().unwrap();
1386 if let Some(sql) = cache.get(&cache_key) {
1387 return Arc::clone(sql);
1388 }
1389 }
1390
1391 let mut cache = self.cache.write().unwrap();
1393
1394 if let Some(sql) = cache.get(&cache_key) {
1396 return Arc::clone(sql);
1397 }
1398
1399 let sql = Arc::new(generator());
1400 cache.insert(cache_key, Arc::clone(&sql));
1401 sql
1402 }
1403
1404 pub fn contains(&self, key: &str, db_type: DatabaseType) -> bool {
1406 let cache_key = (key.to_string(), db_type);
1407 self.cache.read().unwrap().contains_key(&cache_key)
1408 }
1409
1410 pub fn clear(&self) {
1412 self.cache.write().unwrap().clear();
1413 }
1414
1415 pub fn len(&self) -> usize {
1417 self.cache.read().unwrap().len()
1418 }
1419
1420 pub fn is_empty(&self) -> bool {
1422 self.cache.read().unwrap().is_empty()
1423 }
1424}
1425
1426pub fn global_sql_cache() -> &'static SqlTemplateCache {
1441 static CACHE: OnceLock<SqlTemplateCache> = OnceLock::new();
1442 CACHE.get_or_init(|| SqlTemplateCache::with_capacity(64))
1443}
1444
1445#[derive(Debug, Clone, Copy)]
1451pub enum AdvancedQueryCapacity {
1452 Cte {
1454 cte_count: usize,
1456 avg_query_len: usize,
1458 },
1459 WindowFunction {
1461 window_count: usize,
1463 partition_cols: usize,
1465 order_cols: usize,
1467 },
1468 FullTextSearch {
1470 columns: usize,
1472 query_len: usize,
1474 },
1475 JsonPath {
1477 depth: usize,
1479 },
1480 Upsert {
1482 columns: usize,
1484 conflict_cols: usize,
1486 update_cols: usize,
1488 },
1489 ProcedureCall {
1491 params: usize,
1493 },
1494 TriggerDef {
1496 events: usize,
1498 body_len: usize,
1500 },
1501 RlsPolicy {
1503 expr_len: usize,
1505 },
1506}
1507
1508impl AdvancedQueryCapacity {
1509 #[inline]
1511 pub const fn estimate(&self) -> usize {
1512 match self {
1513 Self::Cte {
1514 cte_count,
1515 avg_query_len,
1516 } => {
1517 16 + *cte_count * (32 + *avg_query_len)
1519 }
1520 Self::WindowFunction {
1521 window_count,
1522 partition_cols,
1523 order_cols,
1524 } => {
1525 *window_count * (48 + *partition_cols * 16 + *order_cols * 20)
1527 }
1528 Self::FullTextSearch { columns, query_len } => {
1529 64 + *columns * 20 + *query_len
1531 }
1532 Self::JsonPath { depth } => {
1533 16 + *depth * 12
1535 }
1536 Self::Upsert {
1537 columns,
1538 conflict_cols,
1539 update_cols,
1540 } => {
1541 64 + *columns * 8 + *conflict_cols * 12 + *update_cols * 16
1543 }
1544 Self::ProcedureCall { params } => {
1545 32 + *params * 8
1547 }
1548 Self::TriggerDef { events, body_len } => {
1549 96 + *events * 12 + *body_len
1551 }
1552 Self::RlsPolicy { expr_len } => {
1553 64 + *expr_len
1555 }
1556 }
1557 }
1558
1559 #[inline]
1561 pub const fn to_query_capacity(&self) -> QueryCapacity {
1562 QueryCapacity::Custom(self.estimate())
1563 }
1564}
1565
1566impl FastSqlBuilder {
1568 #[inline]
1570 pub fn for_advanced(db_type: DatabaseType, capacity: AdvancedQueryCapacity) -> Self {
1571 Self::with_capacity(db_type, capacity.to_query_capacity())
1572 }
1573
1574 #[inline]
1576 pub fn for_cte(db_type: DatabaseType, cte_count: usize, avg_query_len: usize) -> Self {
1577 Self::for_advanced(
1578 db_type,
1579 AdvancedQueryCapacity::Cte {
1580 cte_count,
1581 avg_query_len,
1582 },
1583 )
1584 }
1585
1586 #[inline]
1588 pub fn for_window(
1589 db_type: DatabaseType,
1590 window_count: usize,
1591 partition_cols: usize,
1592 order_cols: usize,
1593 ) -> Self {
1594 Self::for_advanced(
1595 db_type,
1596 AdvancedQueryCapacity::WindowFunction {
1597 window_count,
1598 partition_cols,
1599 order_cols,
1600 },
1601 )
1602 }
1603
1604 #[inline]
1606 pub fn for_upsert(
1607 db_type: DatabaseType,
1608 columns: usize,
1609 conflict_cols: usize,
1610 update_cols: usize,
1611 ) -> Self {
1612 Self::for_advanced(
1613 db_type,
1614 AdvancedQueryCapacity::Upsert {
1615 columns,
1616 conflict_cols,
1617 update_cols,
1618 },
1619 )
1620 }
1621}
1622
1623#[cfg(test)]
1624mod tests {
1625 use super::*;
1626
1627 #[test]
1628 fn test_escape_identifier() {
1629 assert_eq!(escape_identifier("user"), "\"user\"");
1630 assert_eq!(escape_identifier("my_table"), "\"my_table\"");
1631 assert_eq!(escape_identifier("has\"quote"), "\"has\"\"quote\"");
1632 }
1633
1634 #[test]
1635 fn test_needs_quoting() {
1636 assert!(needs_quoting("user"));
1637 assert!(needs_quoting("order"));
1638 assert!(needs_quoting("has space"));
1639 assert!(!needs_quoting("my_table"));
1640 assert!(!needs_quoting("users"));
1641 }
1642
1643 #[test]
1644 fn test_quote_identifier() {
1645 assert_eq!(quote_identifier("user"), "\"user\"");
1646 assert_eq!(quote_identifier("my_table"), "my_table");
1647 }
1648
1649 #[test]
1650 fn test_database_placeholder() {
1651 assert_eq!(DatabaseType::PostgreSQL.placeholder(1).as_ref(), "$1");
1653 assert_eq!(DatabaseType::PostgreSQL.placeholder(5).as_ref(), "$5");
1654 assert_eq!(DatabaseType::PostgreSQL.placeholder(100).as_ref(), "$100");
1655 assert_eq!(DatabaseType::PostgreSQL.placeholder(128).as_ref(), "$128");
1656 assert_eq!(DatabaseType::PostgreSQL.placeholder(256).as_ref(), "$256");
1657 assert_eq!(DatabaseType::MySQL.placeholder(1).as_ref(), "?");
1658 assert_eq!(DatabaseType::SQLite.placeholder(1).as_ref(), "?");
1659
1660 assert!(matches!(
1662 DatabaseType::MySQL.placeholder(1),
1663 Cow::Borrowed(_)
1664 ));
1665 assert!(matches!(
1666 DatabaseType::SQLite.placeholder(1),
1667 Cow::Borrowed(_)
1668 ));
1669
1670 assert!(matches!(
1672 DatabaseType::PostgreSQL.placeholder(1),
1673 Cow::Borrowed(_)
1674 ));
1675 assert!(matches!(
1676 DatabaseType::PostgreSQL.placeholder(50),
1677 Cow::Borrowed(_)
1678 ));
1679 assert!(matches!(
1680 DatabaseType::PostgreSQL.placeholder(128),
1681 Cow::Borrowed(_)
1682 ));
1683 assert!(matches!(
1684 DatabaseType::PostgreSQL.placeholder(256),
1685 Cow::Borrowed(_)
1686 ));
1687
1688 assert!(matches!(
1690 DatabaseType::PostgreSQL.placeholder(257),
1691 Cow::Owned(_)
1692 ));
1693 assert_eq!(DatabaseType::PostgreSQL.placeholder(257).as_ref(), "$257");
1694 assert_eq!(DatabaseType::PostgreSQL.placeholder(200).as_ref(), "$200");
1695
1696 assert!(matches!(
1698 DatabaseType::PostgreSQL.placeholder(0),
1699 Cow::Owned(_)
1700 ));
1701 assert_eq!(DatabaseType::PostgreSQL.placeholder(0).as_ref(), "$0");
1702 }
1703
1704 #[test]
1705 fn test_sql_builder() {
1706 let mut builder = SqlBuilder::postgres();
1707 builder
1708 .push("SELECT * FROM ")
1709 .push_identifier("user")
1710 .push(" WHERE ")
1711 .push_identifier("id")
1712 .push(" = ")
1713 .push_param(42i32);
1714
1715 let (sql, params) = builder.build();
1716 assert_eq!(sql, "SELECT * FROM \"user\" WHERE id = $1");
1717 assert_eq!(params.len(), 1);
1718 }
1719
1720 #[test]
1722 fn test_fast_builder_simple() {
1723 let mut builder = FastSqlBuilder::postgres(QueryCapacity::SimpleSelect);
1724 builder.push_str("SELECT * FROM users WHERE id = ");
1725 builder.bind(42i64);
1726 let (sql, params) = builder.build();
1727 assert_eq!(sql, "SELECT * FROM users WHERE id = $1");
1728 assert_eq!(params.len(), 1);
1729 }
1730
1731 #[test]
1732 fn test_fast_builder_complex() {
1733 let mut builder = FastSqlBuilder::with_capacity(
1734 DatabaseType::PostgreSQL,
1735 QueryCapacity::SelectWithFilters(5),
1736 );
1737 builder
1738 .push_str("SELECT * FROM users WHERE active = ")
1739 .bind(true)
1740 .push_str(" AND age > ")
1741 .bind(18i64)
1742 .push_str(" AND status = ")
1743 .bind("approved")
1744 .push_str(" ORDER BY created_at LIMIT ")
1745 .bind(10i64);
1746
1747 let (sql, params) = builder.build();
1748 assert!(sql.contains("$1"));
1749 assert!(sql.contains("$4"));
1750 assert_eq!(params.len(), 4);
1751 }
1752
1753 #[test]
1754 fn test_fast_builder_in_clause_postgres() {
1755 let mut builder = FastSqlBuilder::postgres(QueryCapacity::Custom(128));
1756 builder.push_str("SELECT * FROM users WHERE id IN (");
1757 let values: Vec<FilterValue> = (1..=5).map(|i| FilterValue::Int(i)).collect();
1758 builder.bind_in_clause(values);
1759 builder.push_char(')');
1760
1761 let (sql, params) = builder.build();
1762 assert_eq!(sql, "SELECT * FROM users WHERE id IN ($1, $2, $3, $4, $5)");
1763 assert_eq!(params.len(), 5);
1764 }
1765
1766 #[test]
1767 fn test_fast_builder_in_clause_mysql() {
1768 let mut builder = FastSqlBuilder::mysql(QueryCapacity::Custom(128));
1769 builder.push_str("SELECT * FROM users WHERE id IN (");
1770 let values: Vec<FilterValue> = (1..=5).map(|i| FilterValue::Int(i)).collect();
1771 builder.bind_in_clause(values);
1772 builder.push_char(')');
1773
1774 let (sql, params) = builder.build();
1775 assert_eq!(sql, "SELECT * FROM users WHERE id IN (?, ?, ?, ?, ?)");
1776 assert_eq!(params.len(), 5);
1777 }
1778
1779 #[test]
1780 fn test_fast_builder_identifier() {
1781 let mut builder = FastSqlBuilder::postgres(QueryCapacity::SimpleSelect);
1782 builder.push_str("SELECT * FROM ");
1783 builder.push_identifier("user"); builder.push_str(" WHERE ");
1785 builder.push_identifier("my_column"); builder.push_str(" = ");
1787 builder.bind(1i64);
1788
1789 let (sql, _) = builder.build();
1790 assert_eq!(sql, "SELECT * FROM \"user\" WHERE my_column = $1");
1791 }
1792
1793 #[test]
1794 fn test_fast_builder_identifier_with_quotes() {
1795 let mut builder = FastSqlBuilder::postgres(QueryCapacity::SimpleSelect);
1796 builder.push_str("SELECT * FROM ");
1797 builder.push_identifier("has\"quote");
1798
1799 let sql = builder.build_sql();
1800 assert_eq!(sql, "SELECT * FROM \"has\"\"quote\"");
1801 }
1802
1803 #[test]
1804 fn test_fast_builder_conditional() {
1805 let mut builder = FastSqlBuilder::postgres(QueryCapacity::SelectWithFilters(2));
1806 builder.push_str("SELECT * FROM users WHERE 1=1");
1807 builder.push_if(true, " AND active = true");
1808 builder.push_if(false, " AND deleted = false");
1809
1810 let sql = builder.build_sql();
1811 assert_eq!(sql, "SELECT * FROM users WHERE 1=1 AND active = true");
1812 }
1813
1814 #[test]
1816 fn test_template_select_by_id() {
1817 let sql = templates::select_by_id("users", &["id", "name", "email"]);
1818 assert_eq!(sql, "SELECT id, name, email FROM users WHERE id = $1");
1819 }
1820
1821 #[test]
1822 fn test_template_select_by_id_all_columns() {
1823 let sql = templates::select_by_id("users", &[]);
1824 assert_eq!(sql, "SELECT * FROM users WHERE id = $1");
1825 }
1826
1827 #[test]
1828 fn test_template_insert_returning() {
1829 let sql = templates::insert_returning("users", &["name", "email"]);
1830 assert_eq!(
1831 sql,
1832 "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *"
1833 );
1834 }
1835
1836 #[test]
1837 fn test_template_update_by_id() {
1838 let sql = templates::update_by_id("users", &["name", "email"]);
1839 assert_eq!(sql, "UPDATE users SET name = $1, email = $2 WHERE id = $3");
1840 }
1841
1842 #[test]
1843 fn test_template_delete_by_id() {
1844 let sql = templates::delete_by_id("users");
1845 assert_eq!(sql, "DELETE FROM users WHERE id = $1");
1846 }
1847
1848 #[test]
1849 fn test_template_batch_placeholders_postgres() {
1850 let sql = templates::batch_placeholders(DatabaseType::PostgreSQL, 3, 2);
1851 assert_eq!(sql, "($1, $2, $3), ($4, $5, $6)");
1852 }
1853
1854 #[test]
1855 fn test_template_batch_placeholders_mysql() {
1856 let sql = templates::batch_placeholders(DatabaseType::MySQL, 3, 2);
1857 assert_eq!(sql, "(?, ?, ?), (?, ?, ?)");
1858 }
1859
1860 #[test]
1861 fn test_query_capacity_estimates() {
1862 assert_eq!(QueryCapacity::SimpleSelect.estimate(), 64);
1863 assert_eq!(QueryCapacity::SelectWithFilters(5).estimate(), 64 + 5 * 32);
1864 assert_eq!(QueryCapacity::Insert(10).estimate(), 32 + 10 * 16);
1865 assert_eq!(QueryCapacity::Update(5).estimate(), 32 + 5 * 20);
1866 assert_eq!(QueryCapacity::Delete.estimate(), 48);
1867 assert_eq!(QueryCapacity::Custom(256).estimate(), 256);
1868 }
1869}