use crate::error::{DatabaseError, DatabaseResult, ErrorContext}; use sqlx::postgres::{PgArguments, PgRow}; use sqlx::{Arguments, Executor, FromRow, Postgres}; use std::fmt; /// Type-safe query builder for PostgreSQL /// /// Provides a fluent interface for building SQL queries with parameter binding /// and type safety. Supports SELECT, INSERT, UPDATE, and DELETE operations. #[derive(Debug, Clone)] pub struct QueryBuilder { /// The SQL query string being built query: String, /// Bound arguments for the query args: PgArguments, } impl QueryBuilder { /// Create a new empty query builder /// /// # Returns /// /// A new `QueryBuilder` instance ready for query construction /// /// # Examples /// /// ```rust /// use database::QueryBuilder; /// /// let builder = QueryBuilder::new(); /// ``` pub fn new() -> Self { Self { query: String::new(), args: PgArguments::default(), } } /// Create a SELECT query builder /// /// # Arguments /// /// * `columns` - Array of column names to select. If empty, selects all columns (*) /// /// # Returns /// /// A `SelectBuilder` for constructing the SELECT query /// /// # Examples /// /// ```rust /// use database::QueryBuilder; /// /// let query = QueryBuilder::select(&["id", "name"]) /// .from("users") /// .build() /// .unwrap(); /// ``` pub fn select>(columns: &[T]) -> SelectBuilder { let columns_str = if columns.is_empty() { "*".to_string() } else { columns .iter() .map(|c| c.as_ref()) .collect::>() .join(", ") }; SelectBuilder { query: QueryBuilder::new(), columns: columns_str, from_clause: None, where_conditions: Vec::new(), order_by: Vec::new(), group_by: Vec::new(), having_conditions: Vec::new(), limit_clause: None, offset_clause: None, joins: Vec::new(), } } /// Create an INSERT query builder /// /// # Arguments /// /// * `table` - The table name to insert into /// /// # Returns /// /// An `InsertBuilder` for constructing the INSERT query /// /// # Examples /// /// ```rust /// use database::QueryBuilder; /// /// let query = QueryBuilder::insert("users") /// .values(&[("name", "John"), ("email", "john@example.com")]) /// .build() /// .unwrap(); /// ``` pub fn insert(table: &str) -> InsertBuilder { InsertBuilder { query: QueryBuilder::new(), table: table.to_string(), columns: Vec::new(), values: Vec::new(), on_conflict: None, returning: None, } } /// Create an UPDATE query builder /// /// # Arguments /// /// * `table` - The table name to update /// /// # Returns /// /// An `UpdateBuilder` for constructing the UPDATE query /// /// # Examples /// /// ```rust /// use database::QueryBuilder; /// /// let query = QueryBuilder::update("users") /// .set("name", "Jane") /// .where_eq("id", 1) /// .build() /// .unwrap(); /// ``` pub fn update(table: &str) -> UpdateBuilder { UpdateBuilder { query: QueryBuilder::new(), table: table.to_string(), set_clauses: Vec::new(), where_conditions: Vec::new(), returning: None, } } /// Create a DELETE query builder /// /// # Arguments /// /// * `table` - The table name to delete from /// /// # Returns /// /// A `DeleteBuilder` for constructing the DELETE query /// /// # Examples /// /// ```rust /// use database::QueryBuilder; /// /// let query = QueryBuilder::delete("users") /// .where_eq("id", 1) /// .build() /// .unwrap(); /// ``` pub fn delete(table: &str) -> DeleteBuilder { DeleteBuilder { query: QueryBuilder::new(), table: table.to_string(), where_conditions: Vec::new(), returning: None, } } /// Add a parameter to the query /// /// Binds a value as a parameter to the query, using PostgreSQL's /// parameter placeholder system ($1, $2, etc.). /// /// # Arguments /// /// * `value` - The value to bind as a parameter /// /// # Returns /// /// A mutable reference to self for method chaining /// /// # Type Parameters /// /// * `T` - Must implement SQLx encoding and type traits for PostgreSQL pub fn bind(&mut self, value: T) -> &mut Self where T: for<'q> sqlx::Encode<'q, Postgres> + sqlx::Type + Send, { let _result = self.args.add(value); self } /// Get the generated SQL query string /// /// # Returns /// /// The SQL query string that has been built pub fn sql(&self) -> &str { &self.query } /// Get the bound arguments for the query /// /// # Returns /// /// A reference to the PostgreSQL arguments that have been bound pub fn arguments(&self) -> &PgArguments { &self.args } /// Execute the query and return the number of affected rows /// /// # Arguments /// /// * `executor` - Database executor (pool, connection, or transaction) /// /// # Returns /// /// The number of rows affected by the query /// /// # Errors /// /// Returns `DatabaseError` if the query execution fails pub async fn execute<'e, E>(&self, executor: E) -> DatabaseResult where E: Executor<'e, Database = Postgres>, { let result = sqlx::query_with(&self.query, self.args.clone()) .execute(executor) .await .with_query_context(&self.query)?; Ok(result.rows_affected()) } /// Execute the query and fetch all matching rows /// /// # Arguments /// /// * `executor` - Database executor (pool, connection, or transaction) /// /// # Returns /// /// A vector of all rows returned by the query /// /// # Errors /// /// Returns `DatabaseError` if the query execution fails /// /// # Type Parameters /// /// * `T` - The type to deserialize rows into (must implement `FromRow`) pub async fn fetch_all<'e, E, T>(&self, executor: E) -> DatabaseResult> where E: Executor<'e, Database = Postgres>, T: for<'r> FromRow<'r, PgRow> + Send + Unpin, { let rows = sqlx::query_as_with(&self.query, self.args.clone()) .fetch_all(executor) .await .with_query_context(&self.query)?; Ok(rows) } /// Execute the query and fetch exactly one row /// /// # Arguments /// /// * `executor` - Database executor (pool, connection, or transaction) /// /// # Returns /// /// The single row returned by the query /// /// # Errors /// /// Returns `DatabaseError` if: /// - The query execution fails /// /// - No rows are found /// - More than one row is found /// /// # Type Parameters /// /// * `T` - The type to deserialize the row into (must implement `FromRow`) pub async fn fetch_one<'e, E, T>(&self, executor: E) -> DatabaseResult where E: Executor<'e, Database = Postgres>, T: for<'r> FromRow<'r, PgRow> + Send + Unpin, { let row = sqlx::query_as_with(&self.query, self.args.clone()) .fetch_one(executor) .await .with_query_context(&self.query)?; Ok(row) } /// Execute the query and fetch an optional row /// /// # Arguments /// /// * `executor` - Database executor (pool, connection, or transaction) /// /// # Returns /// /// `Some(row)` if a row is found, `None` if no rows match /// /// # Errors /// /// Returns `DatabaseError` if: /// - The query execution fails /// /// - More than one row is found /// /// # Type Parameters /// /// * `T` - The type to deserialize the row into (must implement `FromRow`) pub async fn fetch_optional<'e, E, T>(&self, executor: E) -> DatabaseResult> where E: Executor<'e, Database = Postgres>, T: for<'r> FromRow<'r, PgRow> + Send + Unpin, { let row = sqlx::query_as_with(&self.query, self.args.clone()) .fetch_optional(executor) .await .with_query_context(&self.query)?; Ok(row) } } impl Default for QueryBuilder { fn default() -> Self { Self::new() } } /// SELECT query builder /// /// Provides a fluent interface for building SELECT queries with support for /// /// WHERE conditions, JOINs, ORDER BY, GROUP BY, LIMIT, and OFFSET clauses. #[derive(Debug, Clone)] pub struct SelectBuilder { /// The underlying query builder query: QueryBuilder, /// Comma-separated column names to select columns: String, /// FROM table clause from_clause: Option, /// WHERE conditions that will be joined with AND where_conditions: Vec, /// ORDER BY clauses order_by: Vec, /// GROUP BY columns group_by: Vec, /// HAVING conditions for grouped queries having_conditions: Vec, /// LIMIT clause for result count limit_clause: Option, /// OFFSET clause for result pagination offset_clause: Option, /// JOIN clauses (INNER, LEFT, etc.) joins: Vec, } impl SelectBuilder { /// Add FROM clause to specify the source table /// /// # Arguments /// /// * `table` - The table name to select from /// /// # Returns /// /// Self for method chaining /// /// # Examples /// /// ```rust /// use database::QueryBuilder; /// /// let query = QueryBuilder::select(&["*"]) /// .from("users") /// .build() /// .unwrap(); /// ``` pub fn from(mut self, table: &str) -> Self { self.from_clause = Some(table.to_string()); self } /// Add WHERE equality condition /// /// # Arguments /// /// * `column` - The column name to filter on /// * `value` - The value to compare against /// /// # Returns /// /// Self for method chaining /// /// # Type Parameters /// /// * `T` - Must implement SQLx encoding and type traits for PostgreSQL /// /// # Examples /// /// ```rust /// use database::QueryBuilder; /// /// let query = QueryBuilder::select(&["*"]) /// .from("users") /// .where_eq("active", true) /// .build() /// .unwrap(); /// ``` pub fn where_eq(mut self, column: &str, value: T) -> Self where T: for<'q> sqlx::Encode<'q, Postgres> + sqlx::Type + Send, { let placeholder = format!("${}", self.query.args.len() + 1); self.where_conditions .push(format!("{} = {}", column, placeholder)); self.query.bind(value); self } /// Add WHERE IN condition for multiple values /// /// # Arguments /// /// * `column` - The column name to filter on /// * `values` - Vector of values to match against /// /// # Returns /// /// Self for method chaining /// /// # Type Parameters /// /// * `T` - Must implement SQLx encoding and type traits for PostgreSQL /// /// # Examples /// /// ```rust /// use database::QueryBuilder; /// /// let query = QueryBuilder::select(&["*"]) /// .from("users") /// .where_in("status", vec!["active", "pending"]) /// .build() /// .unwrap(); /// ``` pub fn where_in(mut self, column: &str, values: Vec) -> Self where T: for<'q> sqlx::Encode<'q, Postgres> + sqlx::Type + Send, { if values.is_empty() { return self; } let placeholders: Vec = values .into_iter() .map(|value| { let placeholder = format!("${}", self.query.args.len() + 1); self.query.bind(value); placeholder }) .collect(); self.where_conditions .push(format!("{} IN ({})", column, placeholders.join(", "))); self } /// Add custom WHERE condition with raw SQL /// /// # Arguments /// /// * `condition` - Raw SQL condition string /// /// # Returns /// /// Self for method chaining /// /// # Safety /// /// This method allows raw SQL which could be vulnerable to SQL injection. /// /// Only use with trusted input or properly escaped values. /// /// # Examples /// /// ```rust /// use database::QueryBuilder; /// /// let query = QueryBuilder::select(&["*"]) /// .from("users") /// .where_raw("created_at > NOW() - INTERVAL '1 day'") /// .build() /// .unwrap(); /// ``` pub fn where_raw(mut self, condition: &str) -> Self { self.where_conditions.push(condition.to_string()); self } /// Add INNER JOIN clause /// /// # Arguments /// /// * `table` - The table to join with /// * `on` - The join condition /// /// # Returns /// /// Self for method chaining /// /// # Examples /// /// ```rust /// use database::QueryBuilder; /// /// let query = QueryBuilder::select(&["u.name", "p.title"]) /// .from("users u") /// .inner_join("posts p", "u.id = p.user_id") /// .build() /// .unwrap(); /// ``` pub fn inner_join(mut self, table: &str, on: &str) -> Self { self.joins.push(format!("INNER JOIN {} ON {}", table, on)); self } /// Add LEFT JOIN clause /// /// # Arguments /// /// * `table` - The table to join with /// * `on` - The join condition /// /// # Returns /// /// Self for method chaining /// /// # Examples /// /// ```rust /// use database::QueryBuilder; /// /// let query = QueryBuilder::select(&["u.name", "p.title"]) /// .from("users u") /// .left_join("posts p", "u.id = p.user_id") /// .build() /// .unwrap(); /// ``` pub fn left_join(mut self, table: &str, on: &str) -> Self { self.joins.push(format!("LEFT JOIN {} ON {}", table, on)); self } /// Add ORDER BY clause for result sorting /// /// # Arguments /// /// * `column` - The column name to sort by /// * `direction` - Sort direction (ASC or DESC) /// /// # Returns /// /// Self for method chaining /// /// # Examples /// /// ```rust /// use database::{QueryBuilder, OrderDirection}; /// /// let query = QueryBuilder::select(&["*"]) /// .from("users") /// .order_by("created_at", OrderDirection::Desc) /// .build() /// .unwrap(); /// ``` pub fn order_by(mut self, column: &str, direction: OrderDirection) -> Self { self.order_by.push(format!("{} {}", column, direction)); self } /// Add GROUP BY clause for result grouping /// /// # Arguments /// /// * `column` - The column name to group by /// /// # Returns /// /// Self for method chaining /// /// # Examples /// /// ```rust /// use database::QueryBuilder; /// /// let query = QueryBuilder::select(&["status", "COUNT(*)"]) /// .from("users") /// .group_by("status") /// .build() /// .unwrap(); /// ``` pub fn group_by(mut self, column: &str) -> Self { self.group_by.push(column.to_string()); self } /// Add HAVING condition for grouped results /// /// # Arguments /// /// * `condition` - The HAVING condition /// /// # Returns /// /// Self for method chaining /// /// # Examples /// /// ```rust /// use database::QueryBuilder; /// /// let query = QueryBuilder::select(&["status", "COUNT(*) as count"]) /// .from("users") /// .group_by("status") /// .having("COUNT(*) > 10") /// .build() /// .unwrap(); /// ``` pub fn having(mut self, condition: &str) -> Self { self.having_conditions.push(condition.to_string()); self } /// Add LIMIT clause to restrict number of results /// /// # Arguments /// /// * `count` - Maximum number of rows to return /// /// # Returns /// /// Self for method chaining /// /// # Examples /// /// ```rust /// use database::QueryBuilder; /// /// let query = QueryBuilder::select(&["*"]) /// .from("users") /// .limit(10) /// .build() /// .unwrap(); /// ``` pub fn limit(mut self, count: u64) -> Self { self.limit_clause = Some(count); self } /// Add OFFSET clause for result pagination /// /// # Arguments /// /// * `count` - Number of rows to skip /// /// # Returns /// /// Self for method chaining /// /// # Examples /// /// ```rust /// use database::QueryBuilder; /// /// let query = QueryBuilder::select(&["*"]) /// .from("users") /// .limit(10) /// .offset(20) /// .build() /// .unwrap(); /// ``` pub fn offset(mut self, count: u64) -> Self { self.offset_clause = Some(count); self } /// Build the final SELECT query /// /// Constructs the complete SQL query string from all the added clauses. /// /// # Returns /// /// A `QueryBuilder` ready for execution /// /// # Errors /// /// Returns `DatabaseError::Query` if the query is incomplete (missing FROM clause) /// /// # Examples /// /// ```rust /// use database::QueryBuilder; /// /// let query = QueryBuilder::select(&["id", "name"]) /// .from("users") /// .where_eq("active", true) /// .build() /// .unwrap(); /// ``` pub fn build(mut self) -> DatabaseResult { let mut query_parts = vec![format!("SELECT {}", self.columns)]; if let Some(from) = &self.from_clause { query_parts.push(format!("FROM {}", from)); } else { return Err(DatabaseError::Query { query: "incomplete".to_string(), message: "SELECT query must have FROM clause".to_string(), }); } // Add JOINs query_parts.extend(self.joins); // Add WHERE conditions if !self.where_conditions.is_empty() { query_parts.push(format!("WHERE {}", self.where_conditions.join(" AND "))); } // Add GROUP BY if !self.group_by.is_empty() { query_parts.push(format!("GROUP BY {}", self.group_by.join(", "))); } // Add HAVING if !self.having_conditions.is_empty() { query_parts.push(format!("HAVING {}", self.having_conditions.join(" AND "))); } // Add ORDER BY if !self.order_by.is_empty() { query_parts.push(format!("ORDER BY {}", self.order_by.join(", "))); } // Add LIMIT if let Some(limit) = self.limit_clause { query_parts.push(format!("LIMIT {}", limit)); } // Add OFFSET if let Some(offset) = self.offset_clause { query_parts.push(format!("OFFSET {}", offset)); } self.query.query = query_parts.join(" "); Ok(self.query) } } /// INSERT query builder /// /// Provides a fluent interface for building INSERT queries with support for /// multiple value sets, conflict resolution, and RETURNING clauses. #[derive(Debug, Clone)] pub struct InsertBuilder { /// The underlying query builder query: QueryBuilder, /// Target table name table: String, /// Column names for insertion columns: Vec, /// Value placeholders for each row to insert values: Vec>, /// ON CONFLICT clause for handling constraint violations on_conflict: Option, /// RETURNING clause for getting inserted data back returning: Option, } impl InsertBuilder { /// Add a single row of values pub fn values(mut self, values: &[(&str, T)]) -> Self where T: for<'q> sqlx::Encode<'q, Postgres> + sqlx::Type + Send + Clone, { if self.columns.is_empty() { self.columns = values.iter().map(|(col, _)| col.to_string()).collect(); } let placeholders: Vec = values .iter() .map(|(_, value)| { let placeholder = format!("${}", self.query.args.len() + 1); self.query.bind(value.clone()); placeholder }) .collect(); self.values.push(placeholders); self } /// Add ON CONFLICT clause pub fn on_conflict(mut self, conflict: &str) -> Self { self.on_conflict = Some(conflict.to_string()); self } /// Add RETURNING clause pub fn returning(mut self, columns: &str) -> Self { self.returning = Some(columns.to_string()); self } /// Build the final query pub fn build(mut self) -> DatabaseResult { if self.columns.is_empty() || self.values.is_empty() { return Err(DatabaseError::Query { query: "incomplete".to_string(), message: "INSERT query must have columns and values".to_string(), }); } let mut query_parts = vec![ format!("INSERT INTO {} ({})", self.table, self.columns.join(", ")), format!( "VALUES {}", self.values .iter() .map(|row| format!("({})", row.join(", "))) .collect::>() .join(", ") ), ]; if let Some(conflict) = &self.on_conflict { query_parts.push(format!("ON CONFLICT {}", conflict)); } if let Some(returning) = &self.returning { query_parts.push(format!("RETURNING {}", returning)); } self.query.query = query_parts.join(" "); Ok(self.query) } } /// UPDATE query builder /// /// Provides a fluent interface for building UPDATE queries with support for /// /// SET clauses, WHERE conditions, and RETURNING clauses. #[derive(Debug, Clone)] pub struct UpdateBuilder { /// The underlying query builder query: QueryBuilder, /// Target table name table: String, /// SET clauses for column updates set_clauses: Vec, /// WHERE conditions to filter rows for update where_conditions: Vec, /// RETURNING clause for getting updated data back returning: Option, } impl UpdateBuilder { /// Add SET clause pub fn set(mut self, column: &str, value: T) -> Self where T: for<'q> sqlx::Encode<'q, Postgres> + sqlx::Type + Send, { let placeholder = format!("${}", self.query.args.len() + 1); self.set_clauses .push(format!("{} = {}", column, placeholder)); self.query.bind(value); self } /// Add WHERE condition pub fn where_eq(mut self, column: &str, value: T) -> Self where T: for<'q> sqlx::Encode<'q, Postgres> + sqlx::Type + Send, { let placeholder = format!("${}", self.query.args.len() + 1); self.where_conditions .push(format!("{} = {}", column, placeholder)); self.query.bind(value); self } /// Add RETURNING clause pub fn returning(mut self, columns: &str) -> Self { self.returning = Some(columns.to_string()); self } /// Build the final query pub fn build(mut self) -> DatabaseResult { if self.set_clauses.is_empty() { return Err(DatabaseError::Query { query: "incomplete".to_string(), message: "UPDATE query must have SET clauses".to_string(), }); } let mut query_parts = vec![ format!("UPDATE {}", self.table), format!("SET {}", self.set_clauses.join(", ")), ]; if !self.where_conditions.is_empty() { query_parts.push(format!("WHERE {}", self.where_conditions.join(" AND "))); } if let Some(returning) = &self.returning { query_parts.push(format!("RETURNING {}", returning)); } self.query.query = query_parts.join(" "); Ok(self.query) } } /// DELETE query builder /// /// Provides a fluent interface for building DELETE queries with support for /// /// WHERE conditions and RETURNING clauses. #[derive(Debug, Clone)] pub struct DeleteBuilder { /// The underlying query builder query: QueryBuilder, /// Target table name table: String, /// WHERE conditions to filter rows for deletion where_conditions: Vec, /// RETURNING clause for getting deleted data back returning: Option, } impl DeleteBuilder { /// Add WHERE condition pub fn where_eq(mut self, column: &str, value: T) -> Self where T: for<'q> sqlx::Encode<'q, Postgres> + sqlx::Type + Send, { let placeholder = format!("${}", self.query.args.len() + 1); self.where_conditions .push(format!("{} = {}", column, placeholder)); self.query.bind(value); self } /// Add custom WHERE condition pub fn where_raw(mut self, condition: &str) -> Self { self.where_conditions.push(condition.to_string()); self } /// Add RETURNING clause pub fn returning(mut self, columns: &str) -> Self { self.returning = Some(columns.to_string()); self } /// Build the final query pub fn build(mut self) -> DatabaseResult { let mut query_parts = vec![format!("DELETE FROM {}", self.table)]; if !self.where_conditions.is_empty() { query_parts.push(format!("WHERE {}", self.where_conditions.join(" AND "))); } if let Some(returning) = &self.returning { query_parts.push(format!("RETURNING {}", returning)); } self.query.query = query_parts.join(" "); Ok(self.query) } } /// Order direction for ORDER BY clauses /// /// Specifies whether to sort in ascending or descending order. #[derive(Debug, Clone, Copy)] pub enum OrderDirection { /// Ascending order (smallest to largest) Asc, /// Descending order (largest to smallest) Desc, } impl fmt::Display for OrderDirection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { OrderDirection::Asc => write!(f, "ASC"), OrderDirection::Desc => write!(f, "DESC"), } } } /// Helper for building dynamic WHERE conditions /// /// Provides a convenient way to build complex WHERE clauses dynamically /// with proper parameter binding and type safety. #[derive(Debug, Clone)] pub struct WhereBuilder { /// List of WHERE conditions that will be joined with AND conditions: Vec, /// Bound arguments for the WHERE conditions args: PgArguments, } impl WhereBuilder { /// Create a new WHERE builder pub fn new() -> Self { Self { conditions: Vec::new(), args: PgArguments::default(), } } /// Add an equality condition pub fn eq(&mut self, column: &str, value: T) -> &mut Self where T: for<'q> sqlx::Encode<'q, Postgres> + sqlx::Type + Send, { let placeholder = format!("${}", self.args.len() + 1); self.conditions .push(format!("{} = {}", column, placeholder)); let _result = self.args.add(value); self } /// Add an IN condition pub fn in_values(&mut self, column: &str, values: Vec) -> &mut Self where T: for<'q> sqlx::Encode<'q, Postgres> + sqlx::Type + Send, { if values.is_empty() { return self; } let placeholders: Vec = values .into_iter() .map(|value| { let placeholder = format!("${}", self.args.len() + 1); let _result = self.args.add(value); placeholder }) .collect(); self.conditions .push(format!("{} IN ({})", column, placeholders.join(", "))); self } /// Add a custom condition pub fn custom(&mut self, condition: &str) -> &mut Self { self.conditions.push(condition.to_string()); self } /// Build the WHERE clause pub fn build(self) -> (String, PgArguments) { let clause = if self.conditions.is_empty() { String::new() } else { format!("WHERE {}", self.conditions.join(" AND ")) }; (clause, self.args) } } impl Default for WhereBuilder { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_select_builder() { let query = QueryBuilder::select(&["id", "name"]) .from("users") .where_eq("active", true) .order_by("name", OrderDirection::Asc) .limit(10) .build() .unwrap(); assert!(query.sql().contains("SELECT id, name")); assert!(query.sql().contains("FROM users")); assert!(query.sql().contains("WHERE active = $1")); assert!(query.sql().contains("ORDER BY name ASC")); assert!(query.sql().contains("LIMIT 10")); } #[test] fn test_insert_builder() { let query = QueryBuilder::insert("users") .values(&[("name", "John"), ("email", "john@example.com")]) .returning("id") .build() .unwrap(); assert!(query.sql().contains("INSERT INTO users")); assert!(query.sql().contains("(name, email)")); assert!(query.sql().contains("VALUES ($1, $2)")); assert!(query.sql().contains("RETURNING id")); } #[test] fn test_update_builder() { let query = QueryBuilder::update("users") .set("name", "Jane") .set("updated_at", "NOW()") .where_eq("id", 1) .build() .unwrap(); assert!(query.sql().contains("UPDATE users")); assert!(query.sql().contains("SET name = $1")); assert!(query.sql().contains("WHERE id = $")); } #[test] fn test_delete_builder() { let query = QueryBuilder::delete("users") .where_eq("id", 1) .returning("name") .build() .unwrap(); assert!(query.sql().contains("DELETE FROM users")); assert!(query.sql().contains("WHERE id = $1")); assert!(query.sql().contains("RETURNING name")); } #[test] fn test_where_builder() { let mut where_builder = WhereBuilder::new(); where_builder.eq("active", true); where_builder.in_values("status", vec!["active", "pending"]); where_builder.custom("created_at > NOW() - INTERVAL '1 day'"); let (clause, _) = where_builder.build(); assert!(clause.contains("WHERE")); assert!(clause.contains("active = $1")); assert!(clause.contains("status IN")); assert!(clause.contains("created_at >")); } }