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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! Query statement traits
//!
//! This module defines the core traits for building and executing SQL queries.
use std::{any::Any, fmt::Debug};
use crate::value::Values;
/// Replace parameter placeholders in SQL with inline value literals.
///
/// Supports both numbered (`$1, $2, ...`) and positional (`?`) placeholders.
/// This enables `to_string()` to produce complete SQL with values inlined,
/// matching reinhardt-query behavior for debugging and non-parameterized execution.
pub fn inline_params(sql: &str, values: &Values) -> String {
if values.is_empty() {
return sql.to_string();
}
let vals = values.iter().collect::<Vec<_>>();
// Detect placeholder style: if SQL contains `$1`, it's numbered (PostgreSQL)
if sql.contains("$1") {
// Replace from highest index down to avoid `$1` matching inside `$10`
let mut result = sql.to_string();
for i in (0..vals.len()).rev() {
let placeholder = format!("${}", i + 1);
result = result.replacen(&placeholder, &vals[i].to_sql_literal(), 1);
}
result
} else {
// Positional `?` placeholders (MySQL/SQLite)
let mut result = String::with_capacity(sql.len());
let mut val_idx = 0;
let chars = sql.chars().peekable();
let mut in_single_quote = false;
for ch in chars {
// Track single-quoted strings to avoid replacing `?` inside them
if ch == '\'' {
in_single_quote = !in_single_quote;
result.push(ch);
} else if ch == '?' && !in_single_quote && val_idx < vals.len() {
result.push_str(&vals[val_idx].to_sql_literal());
val_idx += 1;
} else {
result.push(ch);
}
}
result
}
}
/// Trait for building query statements
///
/// This trait provides methods to build SQL statements for different database backends
/// and collect query parameters.
pub trait QueryStatementBuilder: Debug {
/// Build SQL statement for a database backend and collect query parameters
///
/// This is the primary method for generating parameterized SQL queries.
///
/// # Examples
///
/// ```rust,ignore
/// use reinhardt_query::prelude::*;
///
/// let query = Query::select()
/// .column(Expr::col("name"))
/// .from("users");
///
/// // Build for PostgreSQL
/// let (sql, values) = query.build(PostgresQueryBuilder);
/// // sql = "SELECT \"name\" FROM \"users\""
/// // values = Values(vec![])
/// ```
fn build_any(&self, query_builder: &dyn QueryBuilderTrait) -> (String, Values);
/// Build SQL statement for a database backend and return SQL string
/// with values inlined as SQL literals.
///
/// This produces a complete SQL string with parameter values embedded
/// directly, suitable for debugging, inspection, or execution against
/// databases that do not support parameterized queries.
///
/// # Examples
///
/// ```rust,ignore
/// use reinhardt_query::prelude::*;
///
/// let query = Query::select()
/// .column(Expr::col("name"))
/// .from("users")
/// .and_where(Expr::col("active").eq(true));
///
/// let sql = query.to_string(MysqlQueryBuilder);
/// // sql = "SELECT `name` FROM `users` WHERE `active` = TRUE"
/// ```
fn to_string<T: QueryBuilderTrait>(&self, query_builder: T) -> String {
let (sql, values) = self.build(query_builder);
inline_params(&sql, &values)
}
/// Build SQL statement with parameter collection
///
/// This is a convenience method that wraps `build_any()` with a concrete
/// query builder type.
fn build<T: QueryBuilderTrait>(&self, query_builder: T) -> (String, Values) {
self.build_any(&query_builder)
}
}
/// Trait for query statement writers
///
/// This trait extends [`QueryStatementBuilder`] with additional methods for
/// writing SQL statements.
pub trait QueryStatementWriter: QueryStatementBuilder {}
/// Placeholder trait for query builders (will be implemented in Phase 5)
///
/// This trait defines the interface for database-specific query builders
/// that generate SQL syntax for different backends (PostgreSQL, MySQL, SQLite).
pub trait QueryBuilderTrait: Debug + Any {
/// Get placeholder format for this backend
///
/// Returns a tuple of (placeholder_format, is_numbered):
/// - PostgreSQL: ("$", true) -> $1, $2, $3...
/// - MySQL: ("?", false) -> ?, ?, ?...
/// - SQLite: ("?", false) -> ?, ?, ?...
fn placeholder(&self) -> (&str, bool);
/// Get quote character for this backend
///
/// - PostgreSQL: " (double quote)
/// - MySQL: ` (backtick)
/// - SQLite: " (double quote)
fn quote_char(&self) -> char;
}