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