Skip to main content

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}