Skip to main content

reinhardt_query/backend/
postgres.rs

1//! PostgreSQL query builder backend
2//!
3//! This module implements the SQL generation backend for PostgreSQL.
4//!
5//! # SQL Identifier Quoting
6//!
7//! PostgreSQL uses **double quotes** (`"`) for SQL identifiers (table names, column names,
8//! index names, etc.). This is different from string literals, which use single quotes (`'`).
9//!
10//! ## Quoting Behavior
11//!
12//! - All identifiers are automatically wrapped in double quotes
13//! - Double quotes within identifiers are escaped by doubling (`"` becomes `""`)
14//! - Case sensitivity is preserved when identifiers are quoted
15//!
16//! ## Examples
17//!
18//! | Input Identifier | Quoted Output |
19//! |-----------------|---------------|
20//! | `users` | `"users"` |
21//! | `user_name` | `"user_name"` |
22//! | `column"with"quotes` | `"column""with""quotes"` |
23//!
24//! ## Testing Generated SQL
25//!
26//! When writing tests for generated SQL, ensure you account for identifier quoting:
27//!
28//! ```rust,ignore
29//! use reinhardt_query::backend::{PostgresQueryBuilder, QueryBuilder};
30//! use reinhardt_query::prelude::*;
31//!
32//! let builder = PostgresQueryBuilder::new();
33//! let stmt = Query::select().column("name").from("users");
34//! let (sql, _) = builder.build_select(&stmt);
35//!
36//! // Note the double quotes around identifiers
37//! assert_eq!(sql, r#"SELECT "name" FROM "users""#);
38//! ```
39//!
40//! For more details on SQL syntax, see the
41//! [PostgreSQL Documentation](https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS).
42
43use std::fmt::Write as FmtWrite;
44
45use super::{QueryBuilder, SqlWriter};
46use crate::{
47	expr::{Condition, SimpleExpr},
48	query::{
49		AlterIndexStatement, AlterTableOperation, AlterTableStatement, CheckTableStatement,
50		CreateIndexStatement, CreateTableStatement, CreateTriggerStatement, CreateViewStatement,
51		DeleteStatement, DropIndexStatement, DropTableStatement, DropTriggerStatement,
52		DropViewStatement, InsertStatement, OptimizeTableStatement, ReindexStatement,
53		RepairTableStatement, SelectStatement, TruncateTableStatement, UpdateStatement,
54	},
55	types::{BinOper, ColumnRef, TableRef, TriggerBody},
56	value::Values,
57};
58
59/// PostgreSQL query builder
60///
61/// This struct implements SQL generation for PostgreSQL, using the following conventions:
62/// - Identifiers: Double quotes (`"table_name"`)
63/// - Placeholders: Numbered (`$1`, `$2`, ...)
64///
65/// # Examples
66///
67/// ```rust,ignore
68/// use reinhardt_query::backend::{PostgresQueryBuilder, QueryBuilder};
69/// use reinhardt_query::prelude::*;
70///
71/// let builder = PostgresQueryBuilder::new();
72/// let stmt = Query::select()
73///     .column("id")
74///     .from("users");
75///
76/// let (sql, values) = builder.build_select(&stmt);
77/// // sql: SELECT "id" FROM "users"
78/// ```
79#[derive(Debug, Clone, Default)]
80pub struct PostgresQueryBuilder;
81
82impl PostgresQueryBuilder {
83	/// Create a new PostgreSQL query builder
84	pub fn new() -> Self {
85		Self
86	}
87
88	/// Escape an identifier for PostgreSQL
89	///
90	/// PostgreSQL uses double quotes for identifiers.
91	///
92	/// # Arguments
93	///
94	/// * `ident` - The identifier to escape
95	///
96	/// # Returns
97	///
98	/// The escaped identifier (e.g., `"user"`)
99	fn escape_iden(&self, ident: &str) -> String {
100		// Escape double quotes within the identifier
101		let escaped = ident.replace('"', "\"\"");
102		format!("\"{}\"", escaped)
103	}
104
105	/// Format a placeholder for PostgreSQL
106	///
107	/// PostgreSQL uses numbered placeholders ($1, $2, ...).
108	///
109	/// # Arguments
110	///
111	/// * `index` - The parameter index (1-based)
112	///
113	/// # Returns
114	///
115	/// The placeholder string (e.g., `$1`)
116	fn placeholder(&self, index: usize) -> String {
117		format!("${}", index)
118	}
119
120	/// Adjust placeholder indices in SQL by adding an offset.
121	///
122	/// Uses a single-pass token-based approach to avoid false-positive replacements
123	/// that can occur when identifiers or string literals contain `$N` patterns.
124	/// The naive `sql.replace("$1", "$2")` approach can corrupt SQL when:
125	/// - Identifiers contain dollar signs (e.g., `"col$1"`)
126	/// - String literals contain dollar signs (e.g., `'price$1'`)
127	/// - Intermediate replacements collide (e.g., `$1` -> `$2` then `$2` -> `$3`)
128	fn adjust_placeholder_offsets(sql: &str, num_params: usize, offset: usize) -> String {
129		if offset == 0 || num_params == 0 {
130			return sql.to_string();
131		}
132
133		let mut result = String::with_capacity(sql.len() + num_params * 2);
134		let bytes = sql.as_bytes();
135		let len = bytes.len();
136		let mut i = 0;
137		// Track whether we are inside a quoted identifier or string literal
138		let mut in_single_quote = false;
139		let mut in_double_quote = false;
140
141		while i < len {
142			let ch = bytes[i];
143
144			// Toggle single-quote state (string literals)
145			if ch == b'\'' && !in_double_quote {
146				// Handle escaped single quotes ('')
147				if in_single_quote && i + 1 < len && bytes[i + 1] == b'\'' {
148					result.push('\'');
149					result.push('\'');
150					i += 2;
151					continue;
152				}
153				in_single_quote = !in_single_quote;
154				result.push('\'');
155				i += 1;
156				continue;
157			}
158
159			// Toggle double-quote state (identifiers)
160			if ch == b'"' && !in_single_quote {
161				// Handle escaped double quotes ("")
162				if in_double_quote && i + 1 < len && bytes[i + 1] == b'"' {
163					result.push('"');
164					result.push('"');
165					i += 2;
166					continue;
167				}
168				in_double_quote = !in_double_quote;
169				result.push('"');
170				i += 1;
171				continue;
172			}
173
174			// Handle dollar-quoted strings outside of single/double quotes.
175			// Dollar-quoted delimiters have the form $tag$ where tag is empty or
176			// matches [a-zA-Z_][a-zA-Z0-9_]*. We skip the entire dollar-quoted
177			// body verbatim so that $N patterns inside are not adjusted.
178			if ch == b'$' && !in_single_quote && !in_double_quote {
179				// Try to parse a dollar-quote opening delimiter
180				if let Some((delimiter, delim_end)) =
181					Self::try_parse_dollar_quote_delimiter(bytes, i, len)
182				{
183					// Copy the opening delimiter verbatim
184					result.push_str(&sql[i..delim_end]);
185					// Find the matching closing delimiter and copy body + closer
186					let body_start = delim_end;
187					if let Some(close_pos) = sql[body_start..].find(&delimiter) {
188						let close_end = body_start + close_pos + delimiter.len();
189						result.push_str(&sql[body_start..close_end]);
190						i = close_end;
191					} else {
192						// No closing delimiter found; copy the rest verbatim
193						result.push_str(&sql[body_start..]);
194						i = len;
195					}
196					continue;
197				}
198
199				// Not a dollar-quote delimiter -- try to parse $N placeholder
200				let start = i + 1;
201				let mut end = start;
202				while end < len && bytes[end].is_ascii_digit() {
203					end += 1;
204				}
205				if end > start
206					&& let Ok(n) = sql[start..end].parse::<usize>()
207					&& n >= 1 && n <= num_params
208				{
209					// Write adjusted placeholder
210					use std::fmt::Write;
211					let _ = write!(result, "${}", n + offset);
212					i = end;
213					continue;
214				}
215			}
216
217			// Copy the current UTF-8 character by its byte length to preserve
218			// multi-byte characters (avoids byte-as-char corruption).
219			let ch_len = utf8_char_width(ch);
220			result.push_str(&sql[i..i + ch_len]);
221			i += ch_len;
222		}
223
224		result
225	}
226
227	/// Try to parse a dollar-quote delimiter starting at position `pos`.
228	///
229	/// Returns `Some((delimiter, end_pos))` where `delimiter` is the full
230	/// delimiter string (e.g. `$$` or `$tag$`) and `end_pos` is the byte
231	/// index immediately after the delimiter.
232	fn try_parse_dollar_quote_delimiter(
233		bytes: &[u8],
234		pos: usize,
235		len: usize,
236	) -> Option<(String, usize)> {
237		debug_assert!(bytes[pos] == b'$');
238		let mut j = pos + 1;
239
240		// Empty tag: `$$`
241		if j < len && bytes[j] == b'$' {
242			return Some(("$$".to_string(), j + 1));
243		}
244
245		// Non-empty tag must start with [a-zA-Z_]
246		if j < len && (bytes[j].is_ascii_alphabetic() || bytes[j] == b'_') {
247			j += 1;
248			while j < len && (bytes[j].is_ascii_alphanumeric() || bytes[j] == b'_') {
249				j += 1;
250			}
251			// Must end with closing '$'
252			if j < len && bytes[j] == b'$' {
253				// Safety: all characters in the tag are ASCII, so this is valid UTF-8
254				let delimiter = std::str::from_utf8(&bytes[pos..=j])
255					.expect("dollar-quote tag is ASCII")
256					.to_string();
257				return Some((delimiter, j + 1));
258			}
259		}
260
261		None
262	}
263
264	/// Write a table reference
265	fn write_table_ref(&self, writer: &mut SqlWriter, table_ref: &TableRef) {
266		match table_ref {
267			TableRef::Table(iden) => {
268				writer.push_identifier(&iden.to_string(), |s| self.escape_iden(s));
269			}
270			TableRef::SchemaTable(schema, table) => {
271				writer.push_identifier(&schema.to_string(), |s| self.escape_iden(s));
272				writer.push(".");
273				writer.push_identifier(&table.to_string(), |s| self.escape_iden(s));
274			}
275			TableRef::DatabaseSchemaTable(db, schema, table) => {
276				writer.push_identifier(&db.to_string(), |s| self.escape_iden(s));
277				writer.push(".");
278				writer.push_identifier(&schema.to_string(), |s| self.escape_iden(s));
279				writer.push(".");
280				writer.push_identifier(&table.to_string(), |s| self.escape_iden(s));
281			}
282			TableRef::TableAlias(table, alias) => {
283				writer.push_identifier(&table.to_string(), |s| self.escape_iden(s));
284				writer.push_keyword("AS");
285				writer.push_space();
286				writer.push_identifier(&alias.to_string(), |s| self.escape_iden(s));
287			}
288			TableRef::SchemaTableAlias(schema, table, alias) => {
289				writer.push_identifier(&schema.to_string(), |s| self.escape_iden(s));
290				writer.push(".");
291				writer.push_identifier(&table.to_string(), |s| self.escape_iden(s));
292				writer.push_keyword("AS");
293				writer.push_space();
294				writer.push_identifier(&alias.to_string(), |s| self.escape_iden(s));
295			}
296			TableRef::SubQuery(query, alias) => {
297				let (subquery_sql, subquery_values) = self.build_select(query);
298
299				// Adjust placeholders using token-based approach to avoid false-positive replacements
300				let offset = writer.param_index() - 1;
301				let adjusted_sql =
302					Self::adjust_placeholder_offsets(&subquery_sql, subquery_values.len(), offset);
303
304				writer.push("(");
305				writer.push(&adjusted_sql);
306				writer.push(")");
307				writer.push_keyword("AS");
308				writer.push_space();
309				writer.push_identifier(&alias.to_string(), |s| self.escape_iden(s));
310
311				// Merge the values from the subquery
312				writer.append_values(&subquery_values);
313			}
314		}
315	}
316
317	/// Write a column reference
318	fn write_column_ref(&self, writer: &mut SqlWriter, col_ref: &ColumnRef) {
319		match col_ref {
320			ColumnRef::Column(iden) => {
321				writer.push_identifier(&iden.to_string(), |s| self.escape_iden(s));
322			}
323			ColumnRef::TableColumn(table, col) => {
324				writer.push_identifier(&table.to_string(), |s| self.escape_iden(s));
325				writer.push(".");
326				writer.push_identifier(&col.to_string(), |s| self.escape_iden(s));
327			}
328			ColumnRef::SchemaTableColumn(schema, table, col) => {
329				writer.push_identifier(&schema.to_string(), |s| self.escape_iden(s));
330				writer.push(".");
331				writer.push_identifier(&table.to_string(), |s| self.escape_iden(s));
332				writer.push(".");
333				writer.push_identifier(&col.to_string(), |s| self.escape_iden(s));
334			}
335			ColumnRef::Asterisk => {
336				writer.push("*");
337			}
338			ColumnRef::TableAsterisk(table) => {
339				writer.push_identifier(&table.to_string(), |s| self.escape_iden(s));
340				writer.push(".*");
341			}
342		}
343	}
344
345	/// Write a simple expression
346	fn write_simple_expr(&self, writer: &mut SqlWriter, expr: &SimpleExpr) {
347		match expr {
348			SimpleExpr::Column(col_ref) => {
349				self.write_column_ref(writer, col_ref);
350			}
351			SimpleExpr::Value(value) => {
352				writer.push_value(value.clone(), |i| self.placeholder(i));
353			}
354			SimpleExpr::Binary(left, op, right) => match (op, right.as_ref()) {
355				(BinOper::Between | BinOper::NotBetween, SimpleExpr::Tuple(items))
356					if items.len() == 2 =>
357				{
358					self.write_simple_expr(writer, left);
359					writer.push_space();
360					writer.push(op.as_str());
361					writer.push_space();
362					self.write_simple_expr(writer, &items[0]);
363					writer.push(" AND ");
364					self.write_simple_expr(writer, &items[1]);
365				}
366				(BinOper::In | BinOper::NotIn, SimpleExpr::Tuple(items)) => {
367					self.write_simple_expr(writer, left);
368					writer.push_space();
369					writer.push(op.as_str());
370					writer.push(" (");
371					writer.push_list(items, ", ", |w, item| {
372						self.write_simple_expr(w, item);
373					});
374					writer.push(")");
375				}
376				_ => {
377					self.write_simple_expr(writer, left);
378					writer.push_space();
379					writer.push(op.as_str());
380					writer.push_space();
381					self.write_simple_expr(writer, right);
382				}
383			},
384			SimpleExpr::Unary(op, expr) => {
385				writer.push(op.as_str());
386				writer.push_space();
387				self.write_simple_expr(writer, expr);
388			}
389			SimpleExpr::FunctionCall(func_name, args) => {
390				writer.push(&func_name.to_string());
391				writer.push("(");
392				writer.push_list(args, ", ", |w, arg| {
393					self.write_simple_expr(w, arg);
394				});
395				writer.push(")");
396			}
397			SimpleExpr::Constant(val) => {
398				writer.push(val.as_str());
399			}
400			SimpleExpr::SubQuery(op, select_stmt) => {
401				use crate::expr::SubQueryOper;
402
403				// Write the operator keyword if present
404				if let Some(operator) = op {
405					match operator {
406						SubQueryOper::Exists => {
407							writer.push("EXISTS");
408							writer.push_space();
409						}
410						SubQueryOper::NotExists => {
411							writer.push("NOT EXISTS");
412							writer.push_space();
413						}
414						SubQueryOper::In | SubQueryOper::NotIn => {
415							// These are handled in Binary expressions
416						}
417						SubQueryOper::All => {
418							writer.push("ALL");
419							writer.push_space();
420						}
421						SubQueryOper::Any => {
422							writer.push("ANY");
423							writer.push_space();
424						}
425						SubQueryOper::Some => {
426							writer.push("SOME");
427							writer.push_space();
428						}
429					}
430				}
431
432				// Write the subquery wrapped in parentheses
433				writer.push("(");
434
435				// Recursively build the subquery
436				let (subquery_sql, subquery_values) = self.build_select(select_stmt);
437
438				// Adjust placeholders using token-based approach to avoid false-positive replacements
439				let offset = writer.param_index() - 1;
440				let adjusted_sql =
441					Self::adjust_placeholder_offsets(&subquery_sql, subquery_values.len(), offset);
442
443				writer.push(&adjusted_sql);
444				writer.push(")");
445
446				// Merge the values from the subquery
447				writer.append_values(&subquery_values);
448			}
449			SimpleExpr::Window { func, window } => {
450				// Write the function
451				self.write_simple_expr(writer, func);
452				writer.push_space();
453				writer.push_keyword("OVER");
454				writer.push_space();
455				writer.push("(");
456				self.write_window_statement(writer, window);
457				writer.push(")");
458			}
459			SimpleExpr::WindowNamed { func, name } => {
460				// Write the function
461				self.write_simple_expr(writer, func);
462				writer.push_space();
463				writer.push_keyword("OVER");
464				writer.push_space();
465				writer.push_identifier(&name.to_string(), |s| self.escape_iden(s));
466			}
467			SimpleExpr::Tuple(items) => {
468				writer.push("(");
469				writer.push_list(items, ", ", |w, item| {
470					self.write_simple_expr(w, item);
471				});
472				writer.push(")");
473			}
474			SimpleExpr::Case(case) => {
475				writer.push_keyword("CASE");
476				for (condition, result) in &case.when_clauses {
477					writer.push_space();
478					writer.push_keyword("WHEN");
479					writer.push_space();
480					self.write_simple_expr(writer, condition);
481					writer.push_space();
482					writer.push_keyword("THEN");
483					writer.push_space();
484					self.write_simple_expr(writer, result);
485				}
486				if let Some(else_result) = &case.else_clause {
487					writer.push_space();
488					writer.push_keyword("ELSE");
489					writer.push_space();
490					self.write_simple_expr(writer, else_result);
491				}
492				writer.push_space();
493				writer.push_keyword("END");
494			}
495			SimpleExpr::Custom(sql) => {
496				writer.push(sql);
497			}
498			SimpleExpr::CustomWithExpr(template, exprs) => {
499				// Replace `?` placeholders with the rendered expressions
500				let mut parts = template.split('?');
501				if let Some(first) = parts.next() {
502					writer.push(first);
503				}
504				let mut expr_iter = exprs.iter();
505				for part in parts {
506					if let Some(expr) = expr_iter.next() {
507						self.write_simple_expr(writer, expr);
508					}
509					writer.push(part);
510				}
511			}
512			SimpleExpr::Asterisk => {
513				writer.push("*");
514			}
515			SimpleExpr::TableColumn(table, col) => {
516				writer.push_identifier(&table.to_string(), |s| self.escape_iden(s));
517				writer.push(".");
518				writer.push_identifier(&col.to_string(), |s| self.escape_iden(s));
519			}
520			SimpleExpr::AsEnum(name, expr) => {
521				self.write_simple_expr(writer, expr);
522				writer.push("::");
523				writer.push_identifier(&name.to_string(), |s| self.escape_iden(s));
524			}
525			SimpleExpr::ExprAlias(expr, alias) => {
526				self.write_simple_expr(writer, expr);
527				writer.push_keyword("AS");
528				writer.push_space();
529				writer.push_identifier(&alias.to_string(), |s| self.escape_iden(s));
530			}
531			SimpleExpr::Cast(expr, type_name) => {
532				writer.push("CAST(");
533				self.write_simple_expr(writer, expr);
534				writer.push(" AS ");
535				writer.push_identifier(&type_name.to_string(), |s| self.escape_iden(s));
536				writer.push(")");
537			}
538		}
539	}
540
541	/// Write a simple expression with values inlined (no placeholders)
542	///
543	/// This is used for CHECK constraints in DDL statements, which cannot use
544	/// parameterized queries. Values are written directly as SQL literals.
545	fn write_simple_expr_unquoted(&self, writer: &mut SqlWriter, expr: &SimpleExpr) {
546		use crate::types::BinOper;
547		use crate::value::Value;
548
549		match expr {
550			SimpleExpr::Column(col_ref) => {
551				self.write_column_ref(writer, col_ref);
552			}
553			SimpleExpr::Value(value) => {
554				// Write values inline as SQL literals instead of using placeholders
555				match value {
556					Value::Int(Some(n)) => {
557						writer.push(&n.to_string());
558					}
559					Value::BigInt(Some(n)) => {
560						writer.push(&n.to_string());
561					}
562					Value::TinyInt(Some(n)) => {
563						writer.push(&n.to_string());
564					}
565					Value::SmallInt(Some(n)) => {
566						writer.push(&n.to_string());
567					}
568					Value::Unsigned(Some(n)) => {
569						writer.push(&n.to_string());
570					}
571					Value::SmallUnsigned(Some(n)) => {
572						writer.push(&n.to_string());
573					}
574					Value::TinyUnsigned(Some(n)) => {
575						writer.push(&n.to_string());
576					}
577					Value::BigUnsigned(Some(n)) => {
578						writer.push(&n.to_string());
579					}
580					Value::String(Some(s)) => {
581						let escaped = s.as_str().replace('\'', "''");
582						writer.push(&format!("'{}'", escaped));
583					}
584					Value::Bool(Some(b)) => {
585						writer.push(if *b { "TRUE" } else { "FALSE" });
586					}
587					Value::Float(Some(f)) => {
588						writer.push(&f.to_string());
589					}
590					Value::Double(Some(d)) => {
591						writer.push(&d.to_string());
592					}
593					Value::Char(Some(c)) => {
594						let escaped = c.to_string().replace('\'', "''");
595						writer.push(&format!("'{}'", escaped));
596					}
597					Value::Bytes(Some(b)) => {
598						let mut hex = String::with_capacity(b.as_ref().len() * 2);
599						for byte in b.as_ref() {
600							write!(hex, "{:02x}", byte).unwrap();
601						}
602						writer.push("E'\\\\x");
603						writer.push(&hex);
604						writer.push("'");
605					}
606					#[cfg(feature = "with-chrono")]
607					Value::ChronoDate(Some(d)) => {
608						writer.push(&format!("'{}'", d));
609					}
610					#[cfg(feature = "with-chrono")]
611					Value::ChronoTime(Some(t)) => {
612						writer.push(&format!("'{}'", t));
613					}
614					#[cfg(feature = "with-chrono")]
615					Value::ChronoDateTime(Some(dt)) => {
616						writer.push(&format!("'{}'", dt));
617					}
618					#[cfg(feature = "with-chrono")]
619					Value::ChronoDateTimeUtc(Some(dt)) => {
620						writer.push(&format!("'{}'", dt));
621					}
622					#[cfg(feature = "with-chrono")]
623					Value::ChronoDateTimeLocal(Some(dt)) => {
624						writer.push(&format!("'{}'", dt));
625					}
626					#[cfg(feature = "with-chrono")]
627					Value::ChronoDateTimeWithTimeZone(Some(dt)) => {
628						writer.push(&format!("'{}'", dt));
629					}
630					#[cfg(feature = "with-uuid")]
631					Value::Uuid(Some(u)) => {
632						writer.push(&format!("'{}'", u));
633					}
634					#[cfg(feature = "with-json")]
635					Value::Json(Some(j)) => {
636						let escaped = j.to_string().replace('\'', "''");
637						writer.push(&format!("'{}'", escaped));
638					}
639					#[cfg(feature = "with-rust_decimal")]
640					Value::Decimal(Some(d)) => {
641						writer.push(&d.to_string());
642					}
643					#[cfg(feature = "with-bigdecimal")]
644					Value::BigDecimal(Some(d)) => {
645						writer.push(&d.to_string());
646					}
647					// None values and arrays render as NULL
648					_ => {
649						writer.push("NULL");
650					}
651				}
652			}
653			SimpleExpr::Binary(left, op, right) => match (op, right.as_ref()) {
654				(BinOper::Between | BinOper::NotBetween, SimpleExpr::Tuple(items))
655					if items.len() == 2 =>
656				{
657					self.write_simple_expr_unquoted(writer, left);
658					writer.push_space();
659					writer.push(op.as_str());
660					writer.push_space();
661					self.write_simple_expr_unquoted(writer, &items[0]);
662					writer.push(" AND ");
663					self.write_simple_expr_unquoted(writer, &items[1]);
664				}
665				(BinOper::In | BinOper::NotIn, SimpleExpr::Tuple(items)) => {
666					self.write_simple_expr_unquoted(writer, left);
667					writer.push_space();
668					writer.push(op.as_str());
669					writer.push(" (");
670					writer.push_list(items, ", ", |w, item| {
671						self.write_simple_expr_unquoted(w, item);
672					});
673					writer.push(")");
674				}
675				_ => {
676					self.write_simple_expr_unquoted(writer, left);
677					writer.push_space();
678					writer.push(op.as_str());
679					writer.push_space();
680					self.write_simple_expr_unquoted(writer, right);
681				}
682			},
683			SimpleExpr::Unary(op, expr) => {
684				writer.push(op.as_str());
685				writer.push_space();
686				self.write_simple_expr_unquoted(writer, expr);
687			}
688			SimpleExpr::FunctionCall(func_name, args) => {
689				writer.push(&func_name.to_string());
690				writer.push("(");
691				writer.push_list(args, ", ", |w, arg| {
692					self.write_simple_expr_unquoted(w, arg);
693				});
694				writer.push(")");
695			}
696			SimpleExpr::Constant(val) => {
697				writer.push(val.as_str());
698			}
699			SimpleExpr::Tuple(items) => {
700				writer.push("(");
701				writer.push_list(items, ", ", |w, item| {
702					self.write_simple_expr_unquoted(w, item);
703				});
704				writer.push(")");
705			}
706			SimpleExpr::Case(case) => {
707				writer.push_keyword("CASE");
708				for (condition, result) in &case.when_clauses {
709					writer.push_space();
710					writer.push_keyword("WHEN");
711					writer.push_space();
712					self.write_simple_expr_unquoted(writer, condition);
713					writer.push_space();
714					writer.push_keyword("THEN");
715					writer.push_space();
716					self.write_simple_expr_unquoted(writer, result);
717				}
718				if let Some(else_result) = &case.else_clause {
719					writer.push_space();
720					writer.push_keyword("ELSE");
721					writer.push_space();
722					self.write_simple_expr_unquoted(writer, else_result);
723				}
724				writer.push_space();
725				writer.push_keyword("END");
726			}
727			// Subqueries are not supported in CHECK constraints
728			SimpleExpr::SubQuery(_, _) => {
729				writer.push("(TRUE)");
730			}
731			// Window expressions are not supported in CHECK constraints
732			SimpleExpr::Window { .. } | SimpleExpr::WindowNamed { .. } => {
733				writer.push("(TRUE)");
734			}
735			SimpleExpr::Custom(sql) => {
736				writer.push(sql);
737			}
738			SimpleExpr::CustomWithExpr(template, exprs) => {
739				let mut parts = template.split('?');
740				if let Some(first) = parts.next() {
741					writer.push(first);
742				}
743				let mut expr_iter = exprs.iter();
744				for part in parts {
745					if let Some(expr) = expr_iter.next() {
746						self.write_simple_expr_unquoted(writer, expr);
747					}
748					writer.push(part);
749				}
750			}
751			SimpleExpr::Asterisk => {
752				writer.push("*");
753			}
754			SimpleExpr::TableColumn(table, col) => {
755				writer.push_identifier(&table.to_string(), |s| self.escape_iden(s));
756				writer.push(".");
757				writer.push_identifier(&col.to_string(), |s| self.escape_iden(s));
758			}
759			SimpleExpr::AsEnum(name, expr) => {
760				self.write_simple_expr_unquoted(writer, expr);
761				writer.push("::");
762				writer.push_identifier(&name.to_string(), |s| self.escape_iden(s));
763			}
764			SimpleExpr::ExprAlias(expr, alias) => {
765				self.write_simple_expr_unquoted(writer, expr);
766				writer.push_keyword("AS");
767				writer.push_space();
768				writer.push_identifier(&alias.to_string(), |s| self.escape_iden(s));
769			}
770			SimpleExpr::Cast(expr, type_name) => {
771				writer.push("CAST(");
772				self.write_simple_expr_unquoted(writer, expr);
773				writer.push(" AS ");
774				writer.push_identifier(&type_name.to_string(), |s| self.escape_iden(s));
775				writer.push(")");
776			}
777		}
778	}
779
780	/// Write a condition
781	fn write_condition(&self, writer: &mut SqlWriter, condition: &Condition) {
782		use crate::expr::ConditionType;
783
784		if condition.conditions.is_empty() {
785			return;
786		}
787
788		if condition.negate {
789			writer.push("NOT ");
790		}
791
792		if condition.conditions.len() == 1 {
793			self.write_condition_expr(writer, &condition.conditions[0]);
794			return;
795		}
796
797		writer.push("(");
798		let separator = match condition.condition_type {
799			ConditionType::All => " AND ",
800			ConditionType::Any => " OR ",
801		};
802		writer.push_list(&condition.conditions, separator, |w, cond_expr| {
803			self.write_condition_expr(w, cond_expr);
804		});
805		writer.push(")");
806	}
807
808	/// Write a condition expression
809	fn write_condition_expr(
810		&self,
811		writer: &mut SqlWriter,
812		cond_expr: &crate::expr::ConditionExpression,
813	) {
814		use crate::expr::ConditionExpression;
815
816		match cond_expr {
817			ConditionExpression::Condition(cond) => {
818				self.write_condition(writer, cond);
819			}
820			ConditionExpression::SimpleExpr(expr) => {
821				self.write_simple_expr(writer, expr);
822			}
823		}
824	}
825
826	/// Write a JOIN expression
827	fn write_join_expr(&self, writer: &mut SqlWriter, join: &crate::types::JoinExpr) {
828		use crate::types::JoinOn;
829
830		// JOIN type
831		writer.push_keyword(join.join.as_str());
832		writer.push_space();
833
834		// Table
835		self.write_table_ref(writer, &join.table);
836
837		// ON or USING clause
838		if let Some(on) = &join.on {
839			match on {
840				JoinOn::Columns(pair) => {
841					writer.push_keyword("ON");
842					writer.push_space();
843					self.write_column_spec(writer, &pair.left);
844					writer.push(" = ");
845					self.write_column_spec(writer, &pair.right);
846				}
847				JoinOn::Condition(cond) => {
848					writer.push_keyword("ON");
849					writer.push_space();
850					self.write_condition(writer, cond);
851				}
852				JoinOn::Using(cols) => {
853					writer.push_keyword("USING");
854					writer.push_space();
855					writer.push("(");
856					writer.push_list(cols, ", ", |w, col| {
857						w.push_identifier(&col.to_string(), |s| self.escape_iden(s));
858					});
859					writer.push(")");
860				}
861			}
862		}
863	}
864
865	/// Write a column specification (for JOIN ON conditions)
866	fn write_column_spec(&self, writer: &mut SqlWriter, spec: &crate::types::ColumnSpec) {
867		match spec {
868			crate::types::ColumnSpec::Column(iden) => {
869				writer.push_identifier(&iden.to_string(), |s| self.escape_iden(s));
870			}
871			crate::types::ColumnSpec::TableColumn(table, col) => {
872				writer.push_identifier(&table.to_string(), |s| self.escape_iden(s));
873				writer.push(".");
874				writer.push_identifier(&col.to_string(), |s| self.escape_iden(s));
875			}
876		}
877	}
878
879	/// Write a window statement (PARTITION BY, ORDER BY, frame clause)
880	fn write_window_statement(
881		&self,
882		writer: &mut SqlWriter,
883		window: &crate::types::WindowStatement,
884	) {
885		// PARTITION BY clause
886		if !window.partition_by.is_empty() {
887			writer.push_keyword("PARTITION BY");
888			writer.push_space();
889			writer.push_list(&window.partition_by, ", ", |w, expr| {
890				self.write_simple_expr(w, expr);
891			});
892			writer.push_space();
893		}
894
895		// ORDER BY clause
896		if !window.order_by.is_empty() {
897			writer.push_keyword("ORDER BY");
898			writer.push_space();
899			writer.push_list(&window.order_by, ", ", |w, order_expr| {
900				use crate::types::OrderExprKind;
901				match &order_expr.expr {
902					OrderExprKind::Column(iden) => {
903						w.push_identifier(&iden.to_string(), |s| self.escape_iden(s));
904					}
905					OrderExprKind::TableColumn(table, col) => {
906						w.push_identifier(&table.to_string(), |s| self.escape_iden(s));
907						w.push(".");
908						w.push_identifier(&col.to_string(), |s| self.escape_iden(s));
909					}
910					OrderExprKind::Expr(expr) => {
911						self.write_simple_expr(w, expr);
912					}
913				}
914				match order_expr.order {
915					crate::types::Order::Asc => {
916						w.push_keyword("ASC");
917					}
918					crate::types::Order::Desc => {
919						w.push_keyword("DESC");
920					}
921				}
922				if let Some(nulls) = order_expr.nulls {
923					w.push_space();
924					w.push(nulls.as_str());
925				}
926			});
927			writer.push_space();
928		}
929
930		// Frame clause
931		if let Some(frame) = &window.frame {
932			self.write_frame_clause(writer, frame);
933		}
934	}
935
936	/// Write a frame clause (ROWS/RANGE/GROUPS BETWEEN ... AND ...)
937	fn write_frame_clause(&self, writer: &mut SqlWriter, frame: &crate::types::FrameClause) {
938		use crate::types::FrameType;
939
940		// Frame type (ROWS, RANGE, GROUPS)
941		match frame.frame_type {
942			FrameType::Rows => writer.push_keyword("ROWS"),
943			FrameType::Range => writer.push_keyword("RANGE"),
944			FrameType::Groups => writer.push_keyword("GROUPS"),
945		}
946		writer.push_space();
947
948		// Frame specification
949		if let Some(end) = &frame.end {
950			writer.push_keyword("BETWEEN");
951			writer.push_space();
952			self.write_frame_boundary(writer, &frame.start);
953			writer.push_keyword("AND");
954			writer.push_space();
955			self.write_frame_boundary(writer, end);
956		} else {
957			self.write_frame_boundary(writer, &frame.start);
958		}
959	}
960
961	/// Write a frame boundary (UNBOUNDED PRECEDING, CURRENT ROW, etc.)
962	fn write_frame_boundary(&self, writer: &mut SqlWriter, frame: &crate::types::Frame) {
963		use crate::types::Frame;
964		match frame {
965			Frame::UnboundedPreceding => writer.push("UNBOUNDED PRECEDING"),
966			Frame::Preceding(n) => {
967				writer.push(&n.to_string());
968				writer.push(" PRECEDING");
969			}
970			Frame::CurrentRow => writer.push("CURRENT ROW"),
971			Frame::Following(n) => {
972				writer.push(&n.to_string());
973				writer.push(" FOLLOWING");
974			}
975			Frame::UnboundedFollowing => writer.push("UNBOUNDED FOLLOWING"),
976		}
977	}
978}
979
980impl QueryBuilder for PostgresQueryBuilder {
981	fn build_select(&self, stmt: &SelectStatement) -> (String, Values) {
982		let mut writer = SqlWriter::new();
983
984		// WITH clause (Common Table Expressions)
985		if !stmt.ctes.is_empty() {
986			// Check if any CTE is recursive
987			let has_recursive = stmt.ctes.iter().any(|cte| cte.recursive);
988
989			// Write WITH [RECURSIVE]
990			writer.push_keyword("WITH");
991			writer.push_space();
992			if has_recursive {
993				writer.push_keyword("RECURSIVE");
994				writer.push_space();
995			}
996
997			// Write each CTE
998			writer.push_list(&stmt.ctes, ", ", |w, cte| {
999				// CTE name
1000				w.push_identifier(&cte.name.to_string(), |s| self.escape_iden(s));
1001				w.push_space();
1002				w.push_keyword("AS");
1003				w.push_space();
1004				w.push("(");
1005
1006				// Recursively build the CTE query
1007				let (cte_sql, cte_values) = self.build_select(&cte.query);
1008
1009				// Adjust placeholders using token-based approach to avoid false-positive replacements
1010				let offset = w.param_index() - 1;
1011				let adjusted_sql =
1012					Self::adjust_placeholder_offsets(&cte_sql, cte_values.len(), offset);
1013
1014				w.push(&adjusted_sql);
1015				w.push(")");
1016
1017				// Merge the values from the CTE query
1018				w.append_values(&cte_values);
1019			});
1020
1021			writer.push_space();
1022		}
1023
1024		// SELECT clause
1025		writer.push("SELECT");
1026		writer.push_space();
1027
1028		// DISTINCT clause
1029		if let Some(distinct) = &stmt.distinct {
1030			use crate::query::SelectDistinct;
1031			match distinct {
1032				SelectDistinct::All => {
1033					// SELECT ALL - explicit but not required in PostgreSQL
1034				}
1035				SelectDistinct::Distinct => {
1036					writer.push_keyword("DISTINCT");
1037					writer.push_space();
1038				}
1039				SelectDistinct::DistinctRow => {
1040					panic!("PostgreSQL does not support DISTINCT ROW. Use DISTINCT instead.");
1041				}
1042				SelectDistinct::DistinctOn(cols) => {
1043					writer.push_keyword("DISTINCT ON");
1044					writer.push_space();
1045					writer.push("(");
1046					writer.push_list(cols, ", ", |w, col_ref| {
1047						self.write_column_ref(w, col_ref);
1048					});
1049					writer.push(")");
1050					writer.push_space();
1051				}
1052			}
1053		}
1054
1055		if stmt.selects.is_empty() {
1056			writer.push("*");
1057		} else {
1058			writer.push_list(&stmt.selects, ", ", |w, select_expr| {
1059				self.write_simple_expr(w, &select_expr.expr);
1060				if let Some(alias) = &select_expr.alias {
1061					w.push_keyword("AS");
1062					w.push_space();
1063					w.push_identifier(&alias.to_string(), |s| self.escape_iden(s));
1064				}
1065			});
1066		}
1067
1068		// FROM clause
1069		if !stmt.from.is_empty() {
1070			writer.push_keyword("FROM");
1071			writer.push_space();
1072			writer.push_list(&stmt.from, ", ", |w, table_ref| {
1073				self.write_table_ref(w, table_ref);
1074			});
1075		}
1076
1077		// JOIN clauses
1078		for join in &stmt.join {
1079			writer.push_space();
1080			self.write_join_expr(&mut writer, join);
1081		}
1082
1083		// WHERE clause
1084		if !stmt.r#where.is_empty() {
1085			writer.push_keyword("WHERE");
1086			writer.push_space();
1087			// Write all conditions in the ConditionHolder with AND
1088			writer.push_list(&stmt.r#where.conditions, " AND ", |w, cond_expr| {
1089				self.write_condition_expr(w, cond_expr);
1090			});
1091		}
1092
1093		// GROUP BY clause
1094		if !stmt.groups.is_empty() {
1095			writer.push_keyword("GROUP BY");
1096			writer.push_space();
1097			writer.push_list(&stmt.groups, ", ", |w, expr| {
1098				self.write_simple_expr(w, expr);
1099			});
1100		}
1101
1102		// HAVING clause
1103		if !stmt.having.conditions.is_empty() {
1104			writer.push_keyword("HAVING");
1105			writer.push_space();
1106			// Write all conditions in the ConditionHolder with AND
1107			writer.push_list(&stmt.having.conditions, " AND ", |w, cond_expr| {
1108				self.write_condition_expr(w, cond_expr);
1109			});
1110		}
1111
1112		// ORDER BY clause
1113		if !stmt.orders.is_empty() {
1114			writer.push_keyword("ORDER BY");
1115			writer.push_space();
1116			writer.push_list(&stmt.orders, ", ", |w, order_expr| {
1117				use crate::types::OrderExprKind;
1118				match &order_expr.expr {
1119					OrderExprKind::Column(iden) => {
1120						w.push_identifier(&iden.to_string(), |s| self.escape_iden(s));
1121					}
1122					OrderExprKind::TableColumn(table, col) => {
1123						w.push_identifier(&table.to_string(), |s| self.escape_iden(s));
1124						w.push(".");
1125						w.push_identifier(&col.to_string(), |s| self.escape_iden(s));
1126					}
1127					OrderExprKind::Expr(expr) => {
1128						self.write_simple_expr(w, expr);
1129					}
1130				}
1131				match order_expr.order {
1132					crate::types::Order::Asc => {
1133						w.push_keyword("ASC");
1134					}
1135					crate::types::Order::Desc => {
1136						w.push_keyword("DESC");
1137					}
1138				}
1139				if let Some(nulls) = order_expr.nulls {
1140					w.push_space();
1141					w.push(nulls.as_str());
1142				}
1143			});
1144		}
1145
1146		// WINDOW clause
1147		if !stmt.windows.is_empty() {
1148			writer.push_keyword("WINDOW");
1149			writer.push_space();
1150			writer.push_list(&stmt.windows, ", ", |w, (name, window)| {
1151				w.push_identifier(&name.to_string(), |s| self.escape_iden(s));
1152				w.push_space();
1153				w.push_keyword("AS");
1154				w.push_space();
1155				w.push("(");
1156				self.write_window_statement(w, window);
1157				w.push(")");
1158			});
1159		}
1160
1161		// LIMIT clause
1162		if let Some(limit) = &stmt.limit {
1163			writer.push_keyword("LIMIT");
1164			writer.push_space();
1165			writer.push_value(limit.clone(), |i| self.placeholder(i));
1166		}
1167
1168		// OFFSET clause
1169		if let Some(offset) = &stmt.offset {
1170			writer.push_keyword("OFFSET");
1171			writer.push_space();
1172			writer.push_value(offset.clone(), |i| self.placeholder(i));
1173		}
1174
1175		// UNION/INTERSECT/EXCEPT clauses
1176		for (union_type, union_stmt) in &stmt.unions {
1177			writer.push_space();
1178			use crate::query::UnionType;
1179			match union_type {
1180				UnionType::Distinct => {
1181					writer.push_keyword("UNION");
1182				}
1183				UnionType::All => {
1184					writer.push_keyword("UNION ALL");
1185				}
1186				UnionType::Intersect => {
1187					writer.push_keyword("INTERSECT");
1188				}
1189				UnionType::Except => {
1190					writer.push_keyword("EXCEPT");
1191				}
1192			}
1193			writer.push_space();
1194
1195			// Recursively build the union query
1196			let (union_sql, union_values) = self.build_select(union_stmt);
1197
1198			// Adjust placeholders using token-based approach to avoid false-positive replacements
1199			let offset = writer.param_index() - 1;
1200			let union_sql =
1201				Self::adjust_placeholder_offsets(&union_sql, union_values.len(), offset);
1202
1203			// Append the union SQL (wrapped in parentheses if it has unions itself)
1204			if !union_stmt.unions.is_empty() {
1205				writer.push("(");
1206				writer.push(&union_sql);
1207				writer.push(")");
1208			} else {
1209				writer.push(&union_sql);
1210			}
1211
1212			// Merge the values from the union query
1213			writer.append_values(&union_values);
1214		}
1215
1216		writer.finish()
1217	}
1218
1219	fn build_insert(&self, stmt: &InsertStatement) -> (String, Values) {
1220		use crate::query::insert::InsertSource;
1221
1222		let mut writer = SqlWriter::new();
1223
1224		// INSERT INTO clause
1225		writer.push("INSERT INTO");
1226		writer.push_space();
1227
1228		if let Some(table) = &stmt.table {
1229			self.write_table_ref(&mut writer, table);
1230		} else {
1231			// No table specified - this should not happen in valid SQL
1232			writer.push("(NO_TABLE)");
1233		}
1234
1235		// Column list
1236		if !stmt.columns.is_empty() {
1237			writer.push_space();
1238			writer.push("(");
1239			writer.push_list(&stmt.columns, ", ", |w, col| {
1240				w.push_identifier(&col.to_string(), |s| self.escape_iden(s));
1241			});
1242			writer.push(")");
1243		}
1244
1245		// VALUES clause or SELECT subquery
1246		match &stmt.source {
1247			InsertSource::Values(values) if !values.is_empty() => {
1248				writer.push_keyword("VALUES");
1249				writer.push_space();
1250
1251				writer.push_list(values, ", ", |w, row| {
1252					w.push("(");
1253					w.push_list(row, ", ", |w2, value| {
1254						w2.push_value(value.clone(), |i| self.placeholder(i));
1255					});
1256					w.push(")");
1257				});
1258			}
1259			InsertSource::Subquery(select) => {
1260				writer.push_space();
1261				let (select_sql, select_values) = self.build_select(select);
1262				writer.push(&select_sql);
1263				writer.append_values(&select_values);
1264			}
1265			_ => {
1266				// Empty values - this is valid SQL in some contexts
1267			}
1268		}
1269
1270		// ON CONFLICT clause
1271		if let Some(on_conflict) = &stmt.on_conflict {
1272			use crate::query::{OnConflictAction, OnConflictTarget};
1273			writer.push_keyword("ON CONFLICT");
1274			writer.push_space();
1275
1276			// Target columns
1277			writer.push("(");
1278			match &on_conflict.target {
1279				OnConflictTarget::Column(col) => {
1280					writer.push_identifier(&col.to_string(), |s| self.escape_iden(s));
1281				}
1282				OnConflictTarget::Columns(cols) => {
1283					writer.push_list(cols, ", ", |w, col| {
1284						w.push_identifier(&col.to_string(), |s| self.escape_iden(s));
1285					});
1286				}
1287			}
1288			writer.push(")");
1289
1290			// Action
1291			match &on_conflict.action {
1292				OnConflictAction::DoNothing => {
1293					writer.push_keyword("DO NOTHING");
1294				}
1295				OnConflictAction::DoUpdate(cols) => {
1296					writer.push_keyword("DO UPDATE SET");
1297					writer.push_space();
1298					writer.push_list(cols, ", ", |w, col| {
1299						let col_str = col.to_string();
1300						w.push_identifier(&col_str, |s| self.escape_iden(s));
1301						w.push(" = EXCLUDED.");
1302						w.push_identifier(&col_str, |s| self.escape_iden(s));
1303					});
1304				}
1305			}
1306		}
1307
1308		// RETURNING clause (PostgreSQL specific)
1309		if let Some(returning) = &stmt.returning {
1310			writer.push_keyword("RETURNING");
1311			writer.push_space();
1312
1313			use crate::query::ReturningClause;
1314			match returning {
1315				ReturningClause::All => {
1316					writer.push("*");
1317				}
1318				ReturningClause::Columns(cols) => {
1319					writer.push_list(cols, ", ", |w, col| {
1320						self.write_column_ref(w, col);
1321					});
1322				}
1323			}
1324		}
1325
1326		writer.finish()
1327	}
1328
1329	fn build_update(&self, stmt: &UpdateStatement) -> (String, Values) {
1330		let mut writer = SqlWriter::new();
1331
1332		// UPDATE clause
1333		writer.push("UPDATE");
1334		writer.push_space();
1335
1336		if let Some(table) = &stmt.table {
1337			self.write_table_ref(&mut writer, table);
1338		} else {
1339			writer.push("(NO_TABLE)");
1340		}
1341
1342		// SET clause
1343		if !stmt.values.is_empty() {
1344			writer.push_keyword("SET");
1345			writer.push_space();
1346
1347			writer.push_list(&stmt.values, ", ", |w, (col, value)| {
1348				w.push_identifier(&col.to_string(), |s| self.escape_iden(s));
1349				w.push(" = ");
1350				self.write_simple_expr(w, value);
1351			});
1352		}
1353
1354		// WHERE clause
1355		if !stmt.r#where.is_empty() {
1356			writer.push_keyword("WHERE");
1357			writer.push_space();
1358			writer.push_list(&stmt.r#where.conditions, " AND ", |w, cond_expr| {
1359				self.write_condition_expr(w, cond_expr);
1360			});
1361		}
1362
1363		// RETURNING clause (PostgreSQL specific)
1364		if let Some(returning) = &stmt.returning {
1365			writer.push_keyword("RETURNING");
1366			writer.push_space();
1367
1368			use crate::query::ReturningClause;
1369			match returning {
1370				ReturningClause::All => {
1371					writer.push("*");
1372				}
1373				ReturningClause::Columns(cols) => {
1374					writer.push_list(cols, ", ", |w, col| {
1375						self.write_column_ref(w, col);
1376					});
1377				}
1378			}
1379		}
1380
1381		writer.finish()
1382	}
1383
1384	fn build_delete(&self, stmt: &DeleteStatement) -> (String, Values) {
1385		let mut writer = SqlWriter::new();
1386
1387		// DELETE FROM clause
1388		writer.push("DELETE FROM");
1389		writer.push_space();
1390
1391		if let Some(table) = &stmt.table {
1392			self.write_table_ref(&mut writer, table);
1393		} else {
1394			writer.push("(NO_TABLE)");
1395		}
1396
1397		// WHERE clause
1398		if !stmt.r#where.is_empty() {
1399			writer.push_keyword("WHERE");
1400			writer.push_space();
1401			writer.push_list(&stmt.r#where.conditions, " AND ", |w, cond_expr| {
1402				self.write_condition_expr(w, cond_expr);
1403			});
1404		}
1405
1406		// RETURNING clause (PostgreSQL specific)
1407		if let Some(returning) = &stmt.returning {
1408			writer.push_keyword("RETURNING");
1409			writer.push_space();
1410
1411			use crate::query::ReturningClause;
1412			match returning {
1413				ReturningClause::All => {
1414					writer.push("*");
1415				}
1416				ReturningClause::Columns(cols) => {
1417					writer.push_list(cols, ", ", |w, col| {
1418						self.write_column_ref(w, col);
1419					});
1420				}
1421			}
1422		}
1423
1424		writer.finish()
1425	}
1426
1427	fn build_create_table(&self, stmt: &CreateTableStatement) -> (String, Values) {
1428		let mut writer = SqlWriter::new();
1429
1430		writer.push("CREATE TABLE");
1431		writer.push_space();
1432
1433		if stmt.if_not_exists {
1434			writer.push_keyword("IF NOT EXISTS");
1435			writer.push_space();
1436		}
1437
1438		if let Some(table) = &stmt.table {
1439			self.write_table_ref(&mut writer, table);
1440		}
1441
1442		writer.push_space();
1443		writer.push("(");
1444
1445		// Columns
1446		let mut first = true;
1447		for column in &stmt.columns {
1448			if !first {
1449				writer.push(", ");
1450			}
1451			first = false;
1452
1453			// Column name
1454			writer.push_identifier(&column.name.to_string(), |s| self.escape_iden(s));
1455			writer.push_space();
1456
1457			// Column type
1458			if let Some(col_type) = &column.column_type {
1459				// For auto_increment columns, use SERIAL types instead of INTEGER/BIGINT
1460				if column.auto_increment {
1461					use crate::types::ColumnType;
1462					let serial_type = match col_type {
1463						ColumnType::SmallInteger => "SMALLSERIAL",
1464						ColumnType::Integer => "SERIAL",
1465						ColumnType::BigInteger => "BIGSERIAL",
1466						_ => &self.column_type_to_sql(col_type),
1467					};
1468					writer.push(serial_type);
1469				} else {
1470					writer.push(&self.column_type_to_sql(col_type));
1471				}
1472			}
1473
1474			// NOT NULL
1475			if column.not_null {
1476				writer.push_space();
1477				writer.push_keyword("NOT NULL");
1478			}
1479
1480			// UNIQUE
1481			if column.unique {
1482				writer.push_space();
1483				writer.push_keyword("UNIQUE");
1484			}
1485
1486			// PRIMARY KEY
1487			if column.primary_key {
1488				writer.push_space();
1489				writer.push_keyword("PRIMARY KEY");
1490			}
1491
1492			// DEFAULT
1493			if let Some(default_expr) = &column.default {
1494				writer.push_space();
1495				writer.push_keyword("DEFAULT");
1496				writer.push_space();
1497				self.write_simple_expr(&mut writer, default_expr);
1498			}
1499
1500			// CHECK
1501			if let Some(check_expr) = &column.check {
1502				writer.push_space();
1503				writer.push_keyword("CHECK");
1504				writer.push_space();
1505				writer.push("(");
1506				self.write_simple_expr_unquoted(&mut writer, check_expr);
1507				writer.push(")");
1508			}
1509		}
1510
1511		// Table constraints
1512		for constraint in &stmt.constraints {
1513			writer.push(", ");
1514			self.write_table_constraint(&mut writer, constraint);
1515		}
1516
1517		writer.push(")");
1518
1519		writer.finish()
1520	}
1521
1522	fn build_alter_table(&self, stmt: &AlterTableStatement) -> (String, Values) {
1523		let mut writer = SqlWriter::new();
1524
1525		// ALTER TABLE table_name
1526		writer.push("ALTER TABLE");
1527		writer.push_space();
1528		if let Some(table) = &stmt.table {
1529			self.write_table_ref(&mut writer, table);
1530		}
1531
1532		// Process operations
1533		let mut first = true;
1534		for operation in &stmt.operations {
1535			if !first {
1536				writer.push(",");
1537			}
1538			first = false;
1539			writer.push_space();
1540
1541			match operation {
1542				AlterTableOperation::AddColumn(column_def) => {
1543					writer.push("ADD COLUMN");
1544					writer.push_space();
1545					writer.push_identifier(&column_def.name.to_string(), |s| self.escape_iden(s));
1546					writer.push_space();
1547					if let Some(col_type) = &column_def.column_type {
1548						writer.push(&self.column_type_to_sql(col_type));
1549					}
1550					if column_def.not_null {
1551						writer.push(" NOT NULL");
1552					}
1553					if column_def.unique {
1554						writer.push(" UNIQUE");
1555					}
1556					if let Some(default) = &column_def.default {
1557						writer.push(" DEFAULT ");
1558						self.write_simple_expr(&mut writer, default);
1559					}
1560					if let Some(check) = &column_def.check {
1561						writer.push(" CHECK (");
1562						self.write_simple_expr_unquoted(&mut writer, check);
1563						writer.push(")");
1564					}
1565				}
1566				AlterTableOperation::DropColumn { name, if_exists } => {
1567					writer.push("DROP COLUMN");
1568					writer.push_space();
1569					if *if_exists {
1570						writer.push("IF EXISTS");
1571						writer.push_space();
1572					}
1573					writer.push_identifier(&name.to_string(), |s| self.escape_iden(s));
1574				}
1575				AlterTableOperation::RenameColumn { old, new } => {
1576					writer.push("RENAME COLUMN");
1577					writer.push_space();
1578					writer.push_identifier(&old.to_string(), |s| self.escape_iden(s));
1579					writer.push_space();
1580					writer.push("TO");
1581					writer.push_space();
1582					writer.push_identifier(&new.to_string(), |s| self.escape_iden(s));
1583				}
1584				AlterTableOperation::ModifyColumn(column_def) => {
1585					// PostgreSQL uses ALTER COLUMN instead of MODIFY COLUMN
1586					writer.push("ALTER COLUMN");
1587					writer.push_space();
1588					writer.push_identifier(&column_def.name.to_string(), |s| self.escape_iden(s));
1589					writer.push_space();
1590
1591					// TYPE change
1592					if let Some(col_type) = &column_def.column_type {
1593						writer.push("TYPE");
1594						writer.push_space();
1595						writer.push(&self.column_type_to_sql(col_type));
1596					}
1597
1598					// NOT NULL / NULL
1599					if column_def.not_null {
1600						writer.push(", ALTER COLUMN ");
1601						writer
1602							.push_identifier(&column_def.name.to_string(), |s| self.escape_iden(s));
1603						writer.push(" SET NOT NULL");
1604					}
1605
1606					// DEFAULT
1607					if let Some(default) = &column_def.default {
1608						writer.push(", ALTER COLUMN ");
1609						writer
1610							.push_identifier(&column_def.name.to_string(), |s| self.escape_iden(s));
1611						writer.push(" SET DEFAULT ");
1612						self.write_simple_expr(&mut writer, default);
1613					}
1614				}
1615				AlterTableOperation::AddConstraint(constraint) => {
1616					writer.push("ADD ");
1617					self.write_table_constraint(&mut writer, constraint);
1618				}
1619				AlterTableOperation::DropConstraint { name, if_exists } => {
1620					writer.push("DROP CONSTRAINT");
1621					writer.push_space();
1622					if *if_exists {
1623						writer.push("IF EXISTS");
1624						writer.push_space();
1625					}
1626					writer.push_identifier(&name.to_string(), |s| self.escape_iden(s));
1627				}
1628				AlterTableOperation::RenameTable(new_name) => {
1629					writer.push("RENAME TO");
1630					writer.push_space();
1631					writer.push_identifier(&new_name.to_string(), |s| self.escape_iden(s));
1632				}
1633			}
1634		}
1635
1636		writer.finish()
1637	}
1638
1639	fn build_drop_table(&self, stmt: &DropTableStatement) -> (String, Values) {
1640		let mut writer = SqlWriter::new();
1641
1642		// DROP TABLE
1643		writer.push("DROP TABLE");
1644		writer.push_space();
1645
1646		// IF EXISTS clause
1647		if stmt.if_exists {
1648			writer.push_keyword("IF EXISTS");
1649			writer.push_space();
1650		}
1651
1652		// Table names
1653		writer.push_list(&stmt.tables, ", ", |w, table_ref| {
1654			self.write_table_ref(w, table_ref);
1655		});
1656
1657		// CASCADE/RESTRICT clause
1658		if stmt.cascade {
1659			writer.push_space();
1660			writer.push_keyword("CASCADE");
1661		} else if stmt.restrict {
1662			writer.push_space();
1663			writer.push_keyword("RESTRICT");
1664		}
1665
1666		writer.finish()
1667	}
1668
1669	fn build_create_index(&self, stmt: &CreateIndexStatement) -> (String, Values) {
1670		let mut writer = SqlWriter::new();
1671
1672		// CREATE UNIQUE INDEX IF NOT EXISTS
1673		writer.push("CREATE");
1674		writer.push_space();
1675		if stmt.unique {
1676			writer.push_keyword("UNIQUE");
1677			writer.push_space();
1678		}
1679		writer.push_keyword("INDEX");
1680		writer.push_space();
1681		if stmt.if_not_exists {
1682			writer.push_keyword("IF NOT EXISTS");
1683			writer.push_space();
1684		}
1685
1686		// Index name
1687		if let Some(name) = &stmt.name {
1688			writer.push_identifier(&name.to_string(), |s| self.escape_iden(s));
1689			writer.push_space();
1690		}
1691
1692		// ON table
1693		writer.push_keyword("ON");
1694		writer.push_space();
1695		if let Some(table) = &stmt.table {
1696			self.write_table_ref(&mut writer, table);
1697		}
1698		writer.push_space();
1699
1700		// USING method
1701		if let Some(method) = &stmt.using {
1702			writer.push_keyword("USING");
1703			writer.push_space();
1704			writer.push(self.index_method_to_sql(method));
1705			writer.push_space();
1706		}
1707
1708		// (column1 ASC, column2 DESC, ...)
1709		writer.push("(");
1710		let mut first = true;
1711		for col in &stmt.columns {
1712			if !first {
1713				writer.push(", ");
1714			}
1715			first = false;
1716			writer.push_identifier(&col.name.to_string(), |s| self.escape_iden(s));
1717			if let Some(order) = &col.order {
1718				writer.push_space();
1719				match order {
1720					crate::types::Order::Asc => writer.push("ASC"),
1721					crate::types::Order::Desc => writer.push("DESC"),
1722				}
1723			}
1724		}
1725		writer.push(")");
1726
1727		// WHERE clause (partial index)
1728		if let Some(where_expr) = &stmt.r#where {
1729			writer.push_space();
1730			writer.push_keyword("WHERE");
1731			writer.push_space();
1732			self.write_simple_expr(&mut writer, where_expr);
1733		}
1734
1735		writer.finish()
1736	}
1737
1738	fn build_drop_index(&self, stmt: &DropIndexStatement) -> (String, Values) {
1739		let mut writer = SqlWriter::new();
1740
1741		// DROP INDEX
1742		writer.push("DROP INDEX");
1743		writer.push_space();
1744
1745		// IF EXISTS clause
1746		if stmt.if_exists {
1747			writer.push_keyword("IF EXISTS");
1748			writer.push_space();
1749		}
1750
1751		// Index name
1752		if let Some(name) = &stmt.name {
1753			writer.push_identifier(&name.to_string(), |s| self.escape_iden(s));
1754		}
1755
1756		// CASCADE/RESTRICT clause
1757		if stmt.cascade {
1758			writer.push_space();
1759			writer.push_keyword("CASCADE");
1760		} else if stmt.restrict {
1761			writer.push_space();
1762			writer.push_keyword("RESTRICT");
1763		}
1764
1765		writer.finish()
1766	}
1767
1768	fn build_create_view(&self, stmt: &CreateViewStatement) -> (String, Values) {
1769		let mut writer = SqlWriter::new();
1770
1771		writer.push("CREATE");
1772
1773		if stmt.or_replace {
1774			writer.push_keyword("OR REPLACE");
1775		}
1776
1777		if stmt.materialized {
1778			writer.push_keyword("MATERIALIZED");
1779		}
1780
1781		writer.push_keyword("VIEW");
1782
1783		if stmt.if_not_exists {
1784			writer.push_keyword("IF NOT EXISTS");
1785		}
1786
1787		if let Some(name) = &stmt.name {
1788			writer.push_space();
1789			writer.push_identifier(&name.to_string(), |s| self.escape_iden(s));
1790		}
1791
1792		if !stmt.columns.is_empty() {
1793			writer.push_space();
1794			writer.push("(");
1795			writer.push_list(stmt.columns.iter(), ", ", |w, col| {
1796				w.push_identifier(&col.to_string(), |s| self.escape_iden(s));
1797			});
1798			writer.push(")");
1799		}
1800
1801		writer.push_keyword("AS");
1802
1803		if let Some(select) = &stmt.select {
1804			let (select_sql, select_values) = self.build_select(select);
1805			writer.push_space();
1806			writer.push(&select_sql);
1807			writer.append_values(&select_values);
1808		}
1809
1810		writer.finish()
1811	}
1812
1813	fn build_drop_view(&self, stmt: &DropViewStatement) -> (String, Values) {
1814		let mut writer = SqlWriter::new();
1815
1816		writer.push("DROP");
1817
1818		if stmt.materialized {
1819			writer.push_keyword("MATERIALIZED");
1820		}
1821
1822		writer.push_keyword("VIEW");
1823
1824		if stmt.if_exists {
1825			writer.push_keyword("IF EXISTS");
1826		}
1827
1828		writer.push_space();
1829		writer.push_list(stmt.names.iter(), ", ", |w, name| {
1830			w.push_identifier(&name.to_string(), |s| self.escape_iden(s));
1831		});
1832
1833		if stmt.cascade {
1834			writer.push_keyword("CASCADE");
1835		} else if stmt.restrict {
1836			writer.push_keyword("RESTRICT");
1837		}
1838
1839		writer.finish()
1840	}
1841
1842	fn build_truncate_table(&self, stmt: &TruncateTableStatement) -> (String, Values) {
1843		let mut writer = SqlWriter::new();
1844
1845		// TRUNCATE TABLE
1846		writer.push("TRUNCATE TABLE");
1847		writer.push_space();
1848
1849		// Table names
1850		writer.push_list(&stmt.tables, ", ", |w, table_ref| {
1851			self.write_table_ref(w, table_ref);
1852		});
1853
1854		// RESTART IDENTITY clause (PostgreSQL-specific)
1855		if stmt.restart_identity {
1856			writer.push_space();
1857			writer.push_keyword("RESTART IDENTITY");
1858		}
1859
1860		// CASCADE/RESTRICT clause
1861		if stmt.cascade {
1862			writer.push_space();
1863			writer.push_keyword("CASCADE");
1864		} else if stmt.restrict {
1865			writer.push_space();
1866			writer.push_keyword("RESTRICT");
1867		}
1868
1869		writer.finish()
1870	}
1871
1872	fn build_create_trigger(&self, stmt: &CreateTriggerStatement) -> (String, Values) {
1873		use crate::types::{TriggerEvent, TriggerScope, TriggerTiming};
1874
1875		let mut writer = SqlWriter::new();
1876
1877		// CREATE TRIGGER
1878		writer.push("CREATE TRIGGER");
1879
1880		// Trigger name
1881		if let Some(name) = &stmt.name {
1882			writer.push_space();
1883			writer.push_identifier(&name.to_string(), |s| self.escape_iden(s));
1884		}
1885
1886		// Timing: BEFORE / AFTER / INSTEAD OF
1887		if let Some(timing) = stmt.timing {
1888			writer.push_space();
1889			match timing {
1890				TriggerTiming::Before => writer.push("BEFORE"),
1891				TriggerTiming::After => writer.push("AFTER"),
1892				TriggerTiming::InsteadOf => writer.push("INSTEAD OF"),
1893			}
1894		}
1895
1896		// Events: INSERT / UPDATE [OF columns] / DELETE
1897		if !stmt.events.is_empty() {
1898			writer.push_space();
1899			let mut first = true;
1900			for event in &stmt.events {
1901				if !first {
1902					writer.push(" OR ");
1903				}
1904				first = false;
1905
1906				match event {
1907					TriggerEvent::Insert => writer.push("INSERT"),
1908					TriggerEvent::Update { columns } => {
1909						writer.push("UPDATE");
1910						if let Some(cols) = columns {
1911							writer.push(" OF ");
1912							writer.push_list(cols.iter(), ", ", |w, col| {
1913								w.push_identifier(col, |s| self.escape_iden(s));
1914							});
1915						}
1916					}
1917					TriggerEvent::Delete => writer.push("DELETE"),
1918				}
1919			}
1920		}
1921
1922		// ON table
1923		writer.push_keyword("ON");
1924		if let Some(table) = &stmt.table {
1925			writer.push_space();
1926			self.write_table_ref(&mut writer, table);
1927		}
1928
1929		// FOR EACH ROW / FOR EACH STATEMENT
1930		if let Some(scope) = stmt.scope {
1931			writer.push_space();
1932			match scope {
1933				TriggerScope::Row => writer.push("FOR EACH ROW"),
1934				TriggerScope::Statement => writer.push("FOR EACH STATEMENT"),
1935			}
1936		}
1937
1938		// WHEN (condition)
1939		if let Some(when_cond) = &stmt.when_condition {
1940			writer.push_keyword("WHEN");
1941			writer.push(" (");
1942			self.write_simple_expr(&mut writer, when_cond);
1943			writer.push(")");
1944		}
1945
1946		// EXECUTE FUNCTION function_name() or EXECUTE PROCEDURE (older syntax)
1947		if let Some(body) = &stmt.body {
1948			writer.push_space();
1949			match body {
1950				TriggerBody::PostgresFunction(func_name) => {
1951					writer.push("EXECUTE FUNCTION ");
1952					writer.push_identifier(func_name.as_str(), |s| self.escape_iden(s));
1953					writer.push("()");
1954				}
1955				TriggerBody::Single(_) | TriggerBody::Multiple(_) => {
1956					panic!(
1957						"PostgreSQL triggers require EXECUTE FUNCTION, not inline SQL statements"
1958					);
1959				}
1960			}
1961		}
1962
1963		writer.finish()
1964	}
1965
1966	fn build_drop_trigger(&self, stmt: &DropTriggerStatement) -> (String, Values) {
1967		let mut writer = SqlWriter::new();
1968
1969		// DROP TRIGGER
1970		writer.push("DROP TRIGGER");
1971
1972		// IF EXISTS
1973		if stmt.if_exists {
1974			writer.push_keyword("IF EXISTS");
1975		}
1976
1977		// Trigger name
1978		if let Some(name) = &stmt.name {
1979			writer.push_space();
1980			writer.push_identifier(&name.to_string(), |s| self.escape_iden(s));
1981		}
1982
1983		// ON table (optional in PostgreSQL, but recommended)
1984		if let Some(table) = &stmt.table {
1985			writer.push_keyword("ON");
1986			writer.push_space();
1987			self.write_table_ref(&mut writer, table);
1988		}
1989
1990		// CASCADE / RESTRICT
1991		if stmt.cascade {
1992			writer.push_keyword("CASCADE");
1993		} else if stmt.restrict {
1994			writer.push_keyword("RESTRICT");
1995		}
1996
1997		writer.finish()
1998	}
1999
2000	fn build_alter_index(&self, stmt: &AlterIndexStatement) -> (String, Values) {
2001		use crate::types::Iden;
2002
2003		let mut writer = SqlWriter::new();
2004		writer.push_keyword("ALTER INDEX");
2005		writer.push_space();
2006
2007		if let Some(ref name) = stmt.name {
2008			writer.push_identifier(&Iden::to_string(name.as_ref()), |s| self.escape_iden(s));
2009		} else {
2010			panic!("ALTER INDEX requires an index name");
2011		}
2012
2013		// RENAME TO clause
2014		if let Some(ref new_name) = stmt.rename_to {
2015			writer.push_space();
2016			writer.push_keyword("RENAME TO");
2017			writer.push_space();
2018			writer.push_identifier(&Iden::to_string(new_name.as_ref()), |s| self.escape_iden(s));
2019		}
2020
2021		// SET TABLESPACE clause
2022		if let Some(ref tablespace) = stmt.set_tablespace {
2023			writer.push_space();
2024			writer.push_keyword("SET TABLESPACE");
2025			writer.push_space();
2026			writer.push_identifier(&Iden::to_string(tablespace.as_ref()), |s| {
2027				self.escape_iden(s)
2028			});
2029		}
2030
2031		writer.finish()
2032	}
2033
2034	fn build_reindex(&self, stmt: &ReindexStatement) -> (String, Values) {
2035		use crate::types::Iden;
2036
2037		let mut writer = SqlWriter::new();
2038		writer.push_keyword("REINDEX");
2039
2040		// Options (CONCURRENTLY, VERBOSE, TABLESPACE)
2041		let mut options = Vec::new();
2042		if stmt.concurrently {
2043			options.push("CONCURRENTLY".to_string());
2044		}
2045		if stmt.verbose {
2046			options.push("VERBOSE".to_string());
2047		}
2048		if let Some(ref tablespace) = stmt.tablespace {
2049			let escaped = self.escape_iden(&Iden::to_string(tablespace.as_ref()));
2050			options.push(format!("TABLESPACE {}", escaped));
2051		}
2052
2053		if !options.is_empty() {
2054			writer.push_space();
2055			writer.push("(");
2056			writer.push(&options.join(", "));
2057			writer.push(")");
2058		}
2059
2060		// Target (INDEX, TABLE, SCHEMA, DATABASE, SYSTEM)
2061		writer.push_space();
2062		if let Some(target) = stmt.target {
2063			use crate::query::ReindexTarget;
2064			match target {
2065				ReindexTarget::Index => writer.push_keyword("INDEX"),
2066				ReindexTarget::Table => writer.push_keyword("TABLE"),
2067				ReindexTarget::Schema => writer.push_keyword("SCHEMA"),
2068				ReindexTarget::Database => writer.push_keyword("DATABASE"),
2069				ReindexTarget::System => writer.push_keyword("SYSTEM"),
2070			}
2071		} else {
2072			panic!("REINDEX requires a target");
2073		}
2074
2075		// Name
2076		writer.push_space();
2077		if let Some(ref name) = stmt.name {
2078			writer.push_identifier(&Iden::to_string(name.as_ref()), |s| self.escape_iden(s));
2079		} else {
2080			panic!("REINDEX requires a name");
2081		}
2082
2083		writer.finish()
2084	}
2085
2086	fn build_optimize_table(&self, _stmt: &OptimizeTableStatement) -> (String, Values) {
2087		panic!(
2088			"OPTIMIZE TABLE is MySQL-specific. PostgreSQL users should use VACUUM ANALYZE instead."
2089		);
2090	}
2091
2092	fn build_repair_table(&self, _stmt: &RepairTableStatement) -> (String, Values) {
2093		panic!(
2094			"REPAIR TABLE is not supported in PostgreSQL. PostgreSQL automatically repairs corrupted data during normal operation."
2095		);
2096	}
2097
2098	fn build_check_table(&self, _stmt: &CheckTableStatement) -> (String, Values) {
2099		panic!(
2100			"CHECK TABLE is not supported in PostgreSQL. Use pg_catalog system views or pg_stat_* functions to monitor table health."
2101		);
2102	}
2103
2104	fn build_create_function(
2105		&self,
2106		stmt: &crate::query::CreateFunctionStatement,
2107	) -> (String, Values) {
2108		use crate::types::{
2109			Iden,
2110			function::{FunctionBehavior, FunctionLanguage, FunctionSecurity},
2111		};
2112
2113		let mut writer = SqlWriter::new();
2114
2115		// CREATE [OR REPLACE] FUNCTION
2116		writer.push_keyword("CREATE");
2117		if stmt.function_def.or_replace {
2118			writer.push_keyword("OR REPLACE");
2119		}
2120		writer.push_keyword("FUNCTION");
2121
2122		// Function name
2123		writer.push_space();
2124		writer.push_identifier(&Iden::to_string(stmt.function_def.name.as_ref()), |s| {
2125			self.escape_iden(s)
2126		});
2127
2128		// Parameters (param1 type1, param2 type2, ...)
2129		writer.push("(");
2130		let mut first = true;
2131		for param in &stmt.function_def.parameters {
2132			if !first {
2133				writer.push(", ");
2134			}
2135			first = false;
2136
2137			// Parameter mode (IN, OUT, INOUT, VARIADIC)
2138			if let Some(mode) = &param.mode {
2139				use crate::types::function::ParameterMode;
2140				match mode {
2141					ParameterMode::In => writer.push("IN "),
2142					ParameterMode::Out => writer.push("OUT "),
2143					ParameterMode::InOut => writer.push("INOUT "),
2144					ParameterMode::Variadic => writer.push("VARIADIC "),
2145				}
2146			}
2147
2148			// Parameter name (optional)
2149			if let Some(name) = &param.name {
2150				writer.push_identifier(&Iden::to_string(name.as_ref()), |s| self.escape_iden(s));
2151				writer.push(" ");
2152			}
2153
2154			// Parameter type
2155			if let Some(param_type) = &param.param_type {
2156				writer.push(param_type);
2157			}
2158
2159			// Default value (optional)
2160			if let Some(default) = &param.default_value {
2161				writer.push(" DEFAULT ");
2162				writer.push(default);
2163			}
2164		}
2165		writer.push(")");
2166
2167		// RETURNS type
2168		if let Some(returns) = &stmt.function_def.returns {
2169			writer.push_keyword("RETURNS");
2170			writer.push_space();
2171			writer.push(returns);
2172		}
2173
2174		// LANGUAGE
2175		if let Some(language) = &stmt.function_def.language {
2176			writer.push_keyword("LANGUAGE");
2177			writer.push_space();
2178			match language {
2179				FunctionLanguage::Sql => writer.push("SQL"),
2180				FunctionLanguage::PlPgSql => writer.push("PLPGSQL"),
2181				FunctionLanguage::C => writer.push("C"),
2182				FunctionLanguage::Custom(lang) => writer.push(lang),
2183			}
2184		}
2185
2186		// Behavior (IMMUTABLE/STABLE/VOLATILE)
2187		if let Some(behavior) = &stmt.function_def.behavior {
2188			writer.push_space();
2189			match behavior {
2190				FunctionBehavior::Immutable => writer.push_keyword("IMMUTABLE"),
2191				FunctionBehavior::Stable => writer.push_keyword("STABLE"),
2192				FunctionBehavior::Volatile => writer.push_keyword("VOLATILE"),
2193			}
2194		}
2195
2196		// Security (SECURITY DEFINER/INVOKER)
2197		if let Some(security) = &stmt.function_def.security {
2198			writer.push_space();
2199			match security {
2200				FunctionSecurity::Definer => writer.push_keyword("SECURITY DEFINER"),
2201				FunctionSecurity::Invoker => writer.push_keyword("SECURITY INVOKER"),
2202			}
2203		}
2204
2205		// AS 'body'
2206		if let Some(body) = &stmt.function_def.body {
2207			writer.push_keyword("AS");
2208			writer.push_space();
2209			let delimiter = generate_safe_dollar_quote_delimiter(body);
2210			writer.push(&delimiter);
2211			writer.push(body);
2212			writer.push(&delimiter);
2213		}
2214
2215		writer.finish()
2216	}
2217
2218	fn build_alter_function(
2219		&self,
2220		stmt: &crate::query::AlterFunctionStatement,
2221	) -> (String, Values) {
2222		use crate::query::function::alter_function::AlterFunctionOperation;
2223		use crate::types::{
2224			Iden,
2225			function::{FunctionBehavior, FunctionSecurity},
2226		};
2227
2228		let mut writer = SqlWriter::new();
2229
2230		// ALTER FUNCTION
2231		writer.push_keyword("ALTER FUNCTION");
2232
2233		// Function name
2234		if let Some(name) = &stmt.name {
2235			writer.push_space();
2236			writer.push_identifier(&Iden::to_string(name.as_ref()), |s| self.escape_iden(s));
2237		}
2238
2239		// Function signature (for overloaded functions)
2240		if !stmt.parameters.is_empty() {
2241			writer.push("(");
2242			let mut first = true;
2243			for param in &stmt.parameters {
2244				if !first {
2245					writer.push(", ");
2246				}
2247				first = false;
2248
2249				// Parameter name (optional in signature)
2250				if let Some(name) = &param.name {
2251					let name_str = Iden::to_string(name.as_ref());
2252					if !name_str.is_empty() {
2253						writer.push_identifier(&name_str, |s| self.escape_iden(s));
2254						writer.push(" ");
2255					}
2256				}
2257
2258				// Parameter type
2259				if let Some(param_type) = &param.param_type {
2260					writer.push(param_type);
2261				}
2262			}
2263			writer.push(")");
2264		}
2265
2266		// ALTER FUNCTION operation
2267		if let Some(operation) = &stmt.operation {
2268			writer.push_space();
2269			match operation {
2270				AlterFunctionOperation::RenameTo(new_name) => {
2271					writer.push_keyword("RENAME TO");
2272					writer.push_space();
2273					writer.push_identifier(&Iden::to_string(new_name.as_ref()), |s| {
2274						self.escape_iden(s)
2275					});
2276				}
2277				AlterFunctionOperation::OwnerTo(new_owner) => {
2278					writer.push_keyword("OWNER TO");
2279					writer.push_space();
2280					writer.push_identifier(&Iden::to_string(new_owner.as_ref()), |s| {
2281						self.escape_iden(s)
2282					});
2283				}
2284				AlterFunctionOperation::SetSchema(new_schema) => {
2285					writer.push_keyword("SET SCHEMA");
2286					writer.push_space();
2287					writer.push_identifier(&Iden::to_string(new_schema.as_ref()), |s| {
2288						self.escape_iden(s)
2289					});
2290				}
2291				AlterFunctionOperation::SetBehavior(behavior) => match behavior {
2292					FunctionBehavior::Immutable => writer.push_keyword("IMMUTABLE"),
2293					FunctionBehavior::Stable => writer.push_keyword("STABLE"),
2294					FunctionBehavior::Volatile => writer.push_keyword("VOLATILE"),
2295				},
2296				AlterFunctionOperation::SetSecurity(security) => match security {
2297					FunctionSecurity::Definer => writer.push_keyword("SECURITY DEFINER"),
2298					FunctionSecurity::Invoker => writer.push_keyword("SECURITY INVOKER"),
2299				},
2300			}
2301		}
2302
2303		writer.finish()
2304	}
2305
2306	fn build_drop_function(&self, stmt: &crate::query::DropFunctionStatement) -> (String, Values) {
2307		use crate::types::Iden;
2308
2309		let mut writer = SqlWriter::new();
2310
2311		// DROP FUNCTION
2312		writer.push_keyword("DROP FUNCTION");
2313
2314		// IF EXISTS
2315		if stmt.if_exists {
2316			writer.push_keyword("IF EXISTS");
2317		}
2318
2319		// Function name
2320		if let Some(name) = &stmt.name {
2321			writer.push_space();
2322			writer.push_identifier(&Iden::to_string(name.as_ref()), |s| self.escape_iden(s));
2323		}
2324
2325		// Function signature (for overloaded functions)
2326		if !stmt.parameters.is_empty() {
2327			writer.push("(");
2328			let mut first = true;
2329			for param in &stmt.parameters {
2330				if !first {
2331					writer.push(", ");
2332				}
2333				first = false;
2334
2335				// Parameter name (optional in signature)
2336				if let Some(name) = &param.name {
2337					let name_str = Iden::to_string(name.as_ref());
2338					if !name_str.is_empty() {
2339						writer.push_identifier(&name_str, |s| self.escape_iden(s));
2340						writer.push(" ");
2341					}
2342				}
2343
2344				// Parameter type
2345				if let Some(param_type) = &param.param_type {
2346					writer.push(param_type);
2347				}
2348			}
2349			writer.push(")");
2350		}
2351
2352		// CASCADE
2353		if stmt.cascade {
2354			writer.push_keyword("CASCADE");
2355		}
2356
2357		writer.finish()
2358	}
2359
2360	fn build_grant(&self, stmt: &crate::dcl::GrantStatement) -> (String, Values) {
2361		use crate::dcl::Grantee;
2362
2363		let mut writer = SqlWriter::new();
2364
2365		// GRANT keyword
2366		writer.push("GRANT");
2367		writer.push_space();
2368
2369		// Privileges
2370		writer.push_list(&stmt.privileges, ", ", |w, privilege| {
2371			w.push(privilege.as_sql());
2372		});
2373
2374		// ON clause
2375		writer.push_keyword("ON");
2376		writer.push_space();
2377		writer.push(stmt.object_type.as_sql());
2378		writer.push_space();
2379
2380		// Objects
2381		writer.push_list(&stmt.objects, ", ", |w, obj| {
2382			w.push_identifier(&obj.to_string(), |s| self.escape_iden(s));
2383		});
2384
2385		// TO clause
2386		writer.push_keyword("TO");
2387		writer.push_space();
2388
2389		// Grantees
2390		writer.push_list(&stmt.grantees, ", ", |w, grantee| {
2391			match grantee {
2392				Grantee::Role(name) => {
2393					w.push_identifier(name, |s| self.escape_iden(s));
2394				}
2395				Grantee::User(_, _) => {
2396					// MySQL-specific, not supported in PostgreSQL
2397					w.push_identifier("(UNSUPPORTED_USER)", |s| self.escape_iden(s));
2398				}
2399				Grantee::Public => {
2400					w.push("PUBLIC");
2401				}
2402				Grantee::CurrentRole => {
2403					w.push("CURRENT_ROLE");
2404				}
2405				Grantee::CurrentUser => {
2406					w.push("CURRENT_USER");
2407				}
2408				Grantee::SessionUser => {
2409					w.push("SESSION_USER");
2410				}
2411			}
2412		});
2413
2414		// WITH GRANT OPTION
2415		if stmt.with_grant_option {
2416			writer.push_keyword("WITH GRANT OPTION");
2417		}
2418
2419		// GRANTED BY clause
2420		if let Some(grantor) = &stmt.granted_by {
2421			writer.push_keyword("GRANTED BY");
2422			writer.push_space();
2423			match grantor {
2424				Grantee::Role(name) => {
2425					writer.push_identifier(name, |s| self.escape_iden(s));
2426				}
2427				Grantee::User(_, _) => {
2428					writer.push_identifier("(UNSUPPORTED_USER)", |s| self.escape_iden(s));
2429				}
2430				Grantee::Public => {
2431					writer.push("PUBLIC");
2432				}
2433				Grantee::CurrentRole => {
2434					writer.push("CURRENT_ROLE");
2435				}
2436				Grantee::CurrentUser => {
2437					writer.push("CURRENT_USER");
2438				}
2439				Grantee::SessionUser => {
2440					writer.push("SESSION_USER");
2441				}
2442			}
2443		}
2444
2445		writer.finish()
2446	}
2447
2448	fn build_revoke(&self, stmt: &crate::dcl::RevokeStatement) -> (String, Values) {
2449		use crate::dcl::Grantee;
2450
2451		let mut writer = SqlWriter::new();
2452
2453		// REVOKE keyword
2454		writer.push("REVOKE");
2455		writer.push_space();
2456
2457		// GRANT OPTION FOR (if specified)
2458		if stmt.grant_option_for {
2459			writer.push("GRANT OPTION FOR");
2460			writer.push_space();
2461		}
2462
2463		// Privileges
2464		writer.push_list(&stmt.privileges, ", ", |w, privilege| {
2465			w.push(privilege.as_sql());
2466		});
2467
2468		// ON clause
2469		writer.push_keyword("ON");
2470		writer.push_space();
2471		writer.push(stmt.object_type.as_sql());
2472		writer.push_space();
2473
2474		// Objects
2475		writer.push_list(&stmt.objects, ", ", |w, obj| {
2476			w.push_identifier(&obj.to_string(), |s| self.escape_iden(s));
2477		});
2478
2479		// FROM clause
2480		writer.push_keyword("FROM");
2481		writer.push_space();
2482
2483		// Grantees
2484		writer.push_list(&stmt.grantees, ", ", |w, grantee| {
2485			match grantee {
2486				Grantee::Role(name) => {
2487					w.push_identifier(name, |s| self.escape_iden(s));
2488				}
2489				Grantee::User(_, _) => {
2490					// MySQL-specific, not supported in PostgreSQL
2491					w.push_identifier("(UNSUPPORTED_USER)", |s| self.escape_iden(s));
2492				}
2493				Grantee::Public => {
2494					w.push("PUBLIC");
2495				}
2496				Grantee::CurrentRole => {
2497					w.push("CURRENT_ROLE");
2498				}
2499				Grantee::CurrentUser => {
2500					w.push("CURRENT_USER");
2501				}
2502				Grantee::SessionUser => {
2503					w.push("SESSION_USER");
2504				}
2505			}
2506		});
2507
2508		// CASCADE / RESTRICT
2509		if stmt.cascade {
2510			writer.push_keyword("CASCADE");
2511		}
2512
2513		writer.finish()
2514	}
2515
2516	fn build_grant_role(&self, stmt: &crate::dcl::GrantRoleStatement) -> (String, Values) {
2517		let mut writer = SqlWriter::new();
2518
2519		// GRANT keyword
2520		writer.push("GRANT");
2521		writer.push_space();
2522
2523		// Roles (comma-separated list)
2524		writer.push_list(&stmt.roles, ", ", |w, role| {
2525			w.push_identifier(role, |s| self.escape_iden(s));
2526		});
2527
2528		// TO clause
2529		writer.push_keyword("TO");
2530		writer.push_space();
2531
2532		// Grantees
2533		writer.push_list(&stmt.grantees, ", ", |w, grantee| {
2534			w.push(Self::format_role_specification(grantee));
2535		});
2536
2537		// WITH ADMIN OPTION
2538		if stmt.with_admin_option {
2539			writer.push_keyword("WITH ADMIN OPTION");
2540		}
2541
2542		// GRANTED BY
2543		if let Some(ref grantor) = stmt.granted_by {
2544			writer.push_keyword("GRANTED BY");
2545			writer.push_space();
2546			writer.push(Self::format_role_specification(grantor));
2547		}
2548
2549		writer.finish()
2550	}
2551
2552	fn build_revoke_role(&self, stmt: &crate::dcl::RevokeRoleStatement) -> (String, Values) {
2553		use crate::dcl::DropBehavior;
2554
2555		let mut writer = SqlWriter::new();
2556
2557		// REVOKE keyword
2558		writer.push("REVOKE");
2559		writer.push_space();
2560
2561		// ADMIN OPTION FOR
2562		if stmt.admin_option_for {
2563			writer.push("ADMIN OPTION FOR");
2564			writer.push_space();
2565		}
2566
2567		// Roles (comma-separated list)
2568		writer.push_list(&stmt.roles, ", ", |w, role| {
2569			w.push_identifier(role, |s| self.escape_iden(s));
2570		});
2571
2572		// FROM clause
2573		writer.push_keyword("FROM");
2574		writer.push_space();
2575
2576		// Grantees
2577		writer.push_list(&stmt.grantees, ", ", |w, grantee| {
2578			w.push(Self::format_role_specification(grantee));
2579		});
2580
2581		// GRANTED BY
2582		if let Some(ref grantor) = stmt.granted_by {
2583			writer.push_keyword("GRANTED BY");
2584			writer.push_space();
2585			writer.push(Self::format_role_specification(grantor));
2586		}
2587
2588		// CASCADE / RESTRICT
2589		if let Some(behavior) = stmt.drop_behavior {
2590			match behavior {
2591				DropBehavior::Cascade => writer.push_keyword("CASCADE"),
2592				DropBehavior::Restrict => writer.push_keyword("RESTRICT"),
2593			}
2594		}
2595
2596		writer.finish()
2597	}
2598
2599	fn build_create_role(&self, stmt: &crate::dcl::CreateRoleStatement) -> (String, Values) {
2600		use crate::dcl::RoleAttribute;
2601		use crate::value::Value;
2602
2603		let mut writer = SqlWriter::new();
2604
2605		// CREATE ROLE keyword
2606		writer.push("CREATE ROLE");
2607		writer.push_space();
2608
2609		// Role name
2610		writer.push_identifier(&stmt.role_name, |s| self.escape_iden(s));
2611
2612		// WITH keyword (optional but commonly used)
2613		if !stmt.attributes.is_empty() {
2614			writer.push_keyword("WITH");
2615		}
2616
2617		// Attributes
2618		for attr in &stmt.attributes {
2619			writer.push_space();
2620			match attr {
2621				RoleAttribute::SuperUser => writer.push("SUPERUSER"),
2622				RoleAttribute::NoSuperUser => writer.push("NOSUPERUSER"),
2623				RoleAttribute::CreateDb => writer.push("CREATEDB"),
2624				RoleAttribute::NoCreateDb => writer.push("NOCREATEDB"),
2625				RoleAttribute::CreateRole => writer.push("CREATEROLE"),
2626				RoleAttribute::NoCreateRole => writer.push("NOCREATEROLE"),
2627				RoleAttribute::Inherit => writer.push("INHERIT"),
2628				RoleAttribute::NoInherit => writer.push("NOINHERIT"),
2629				RoleAttribute::Login => writer.push("LOGIN"),
2630				RoleAttribute::NoLogin => writer.push("NOLOGIN"),
2631				RoleAttribute::Replication => writer.push("REPLICATION"),
2632				RoleAttribute::NoReplication => writer.push("NOREPLICATION"),
2633				RoleAttribute::BypassRls => writer.push("BYPASSRLS"),
2634				RoleAttribute::NoBypassRls => writer.push("NOBYPASSRLS"),
2635				RoleAttribute::ConnectionLimit(limit) => {
2636					writer.push("CONNECTION LIMIT");
2637					writer.push_space();
2638					writer.push(&limit.to_string());
2639				}
2640				RoleAttribute::Password(pwd) => {
2641					writer.push("PASSWORD");
2642					writer.push_space();
2643					writer.push_value(Value::String(Some(Box::new(pwd.clone()))), |i| {
2644						self.placeholder(i)
2645					});
2646				}
2647				RoleAttribute::EncryptedPassword(pwd) => {
2648					writer.push("ENCRYPTED PASSWORD");
2649					writer.push_space();
2650					writer.push_value(Value::String(Some(Box::new(pwd.clone()))), |i| {
2651						self.placeholder(i)
2652					});
2653				}
2654				RoleAttribute::UnencryptedPassword(pwd) => {
2655					writer.push("UNENCRYPTED PASSWORD");
2656					writer.push_space();
2657					writer.push_value(Value::String(Some(Box::new(pwd.clone()))), |i| {
2658						self.placeholder(i)
2659					});
2660				}
2661				RoleAttribute::ValidUntil(timestamp) => {
2662					// PostgreSQL VALID UNTIL requires a string constant (Sconst),
2663					// not a bind parameter. Use inline literal with single-quote escaping.
2664					let escaped = timestamp.replace('\'', "''");
2665					writer.push("VALID UNTIL");
2666					writer.push_space();
2667					writer.push(&format!("'{}'", escaped));
2668				}
2669				RoleAttribute::InRole(roles) => {
2670					writer.push("IN ROLE");
2671					writer.push_space();
2672					writer.push_list(roles, ", ", |w, role| {
2673						w.push_identifier(role, |s| self.escape_iden(s));
2674					});
2675				}
2676				RoleAttribute::Role(roles) => {
2677					writer.push("ROLE");
2678					writer.push_space();
2679					writer.push_list(roles, ", ", |w, role| {
2680						w.push_identifier(role, |s| self.escape_iden(s));
2681					});
2682				}
2683				RoleAttribute::Admin(roles) => {
2684					writer.push("ADMIN");
2685					writer.push_space();
2686					writer.push_list(roles, ", ", |w, role| {
2687						w.push_identifier(role, |s| self.escape_iden(s));
2688					});
2689				}
2690			}
2691		}
2692
2693		writer.finish()
2694	}
2695
2696	fn build_drop_role(&self, stmt: &crate::dcl::DropRoleStatement) -> (String, Values) {
2697		let mut writer = SqlWriter::new();
2698
2699		// DROP ROLE keyword
2700		writer.push("DROP ROLE");
2701		writer.push_space();
2702
2703		// IF EXISTS clause
2704		if stmt.if_exists {
2705			writer.push("IF EXISTS");
2706			writer.push_space();
2707		}
2708
2709		// Role names (comma-separated)
2710		writer.push_list(&stmt.role_names, ", ", |w, role_name| {
2711			w.push_identifier(role_name, |s| self.escape_iden(s));
2712		});
2713
2714		writer.finish()
2715	}
2716
2717	fn build_alter_role(&self, stmt: &crate::dcl::AlterRoleStatement) -> (String, Values) {
2718		use crate::dcl::RoleAttribute;
2719		use crate::value::Value;
2720
2721		let mut writer = SqlWriter::new();
2722
2723		// Check for RENAME TO (special case in PostgreSQL)
2724		if let Some(ref new_name) = stmt.rename_to {
2725			writer.push("ALTER ROLE");
2726			writer.push_space();
2727			writer.push_identifier(&stmt.role_name, |s| self.escape_iden(s));
2728			writer.push_keyword("RENAME TO");
2729			writer.push_space();
2730			writer.push_identifier(new_name, |s| self.escape_iden(s));
2731			return writer.finish();
2732		}
2733
2734		// ALTER ROLE keyword
2735		writer.push("ALTER ROLE");
2736		writer.push_space();
2737
2738		// Role name
2739		writer.push_identifier(&stmt.role_name, |s| self.escape_iden(s));
2740
2741		// WITH keyword (optional but commonly used)
2742		if !stmt.attributes.is_empty() {
2743			writer.push_keyword("WITH");
2744		}
2745
2746		// Attributes (same as CREATE ROLE)
2747		for attr in &stmt.attributes {
2748			writer.push_space();
2749			match attr {
2750				RoleAttribute::SuperUser => writer.push("SUPERUSER"),
2751				RoleAttribute::NoSuperUser => writer.push("NOSUPERUSER"),
2752				RoleAttribute::CreateDb => writer.push("CREATEDB"),
2753				RoleAttribute::NoCreateDb => writer.push("NOCREATEDB"),
2754				RoleAttribute::CreateRole => writer.push("CREATEROLE"),
2755				RoleAttribute::NoCreateRole => writer.push("NOCREATEROLE"),
2756				RoleAttribute::Inherit => writer.push("INHERIT"),
2757				RoleAttribute::NoInherit => writer.push("NOINHERIT"),
2758				RoleAttribute::Login => writer.push("LOGIN"),
2759				RoleAttribute::NoLogin => writer.push("NOLOGIN"),
2760				RoleAttribute::Replication => writer.push("REPLICATION"),
2761				RoleAttribute::NoReplication => writer.push("NOREPLICATION"),
2762				RoleAttribute::BypassRls => writer.push("BYPASSRLS"),
2763				RoleAttribute::NoBypassRls => writer.push("NOBYPASSRLS"),
2764				RoleAttribute::ConnectionLimit(limit) => {
2765					writer.push("CONNECTION LIMIT");
2766					writer.push_space();
2767					writer.push(&limit.to_string());
2768				}
2769				RoleAttribute::Password(pwd) => {
2770					writer.push("PASSWORD");
2771					writer.push_space();
2772					writer.push_value(Value::String(Some(Box::new(pwd.clone()))), |i| {
2773						self.placeholder(i)
2774					});
2775				}
2776				RoleAttribute::EncryptedPassword(pwd) => {
2777					writer.push("ENCRYPTED PASSWORD");
2778					writer.push_space();
2779					writer.push_value(Value::String(Some(Box::new(pwd.clone()))), |i| {
2780						self.placeholder(i)
2781					});
2782				}
2783				RoleAttribute::UnencryptedPassword(pwd) => {
2784					writer.push("UNENCRYPTED PASSWORD");
2785					writer.push_space();
2786					writer.push_value(Value::String(Some(Box::new(pwd.clone()))), |i| {
2787						self.placeholder(i)
2788					});
2789				}
2790				RoleAttribute::ValidUntil(timestamp) => {
2791					// PostgreSQL VALID UNTIL requires a string constant (Sconst),
2792					// not a bind parameter. Use inline literal with single-quote escaping.
2793					let escaped = timestamp.replace('\'', "''");
2794					writer.push("VALID UNTIL");
2795					writer.push_space();
2796					writer.push(&format!("'{}'", escaped));
2797				}
2798				RoleAttribute::InRole(roles) => {
2799					writer.push("IN ROLE");
2800					writer.push_space();
2801					writer.push_list(roles, ", ", |w, role| {
2802						w.push_identifier(role, |s| self.escape_iden(s));
2803					});
2804				}
2805				RoleAttribute::Role(roles) => {
2806					writer.push("ROLE");
2807					writer.push_space();
2808					writer.push_list(roles, ", ", |w, role| {
2809						w.push_identifier(role, |s| self.escape_iden(s));
2810					});
2811				}
2812				RoleAttribute::Admin(roles) => {
2813					writer.push("ADMIN");
2814					writer.push_space();
2815					writer.push_list(roles, ", ", |w, role| {
2816						w.push_identifier(role, |s| self.escape_iden(s));
2817					});
2818				}
2819			}
2820		}
2821
2822		writer.finish()
2823	}
2824
2825	fn build_create_user(&self, stmt: &crate::dcl::CreateUserStatement) -> (String, Values) {
2826		use crate::dcl::{CreateRoleStatement, RoleAttribute};
2827
2828		// PostgreSQL CREATE USER is CREATE ROLE WITH LOGIN
2829		let mut create_role = CreateRoleStatement::new()
2830			.role(&stmt.user_name)
2831			.attribute(RoleAttribute::Login);
2832
2833		// Add all attributes from CREATE USER
2834		for attr in &stmt.attributes {
2835			create_role = create_role.attribute(attr.clone());
2836		}
2837
2838		// Use build_create_role to generate the SQL
2839		self.build_create_role(&create_role)
2840	}
2841
2842	fn build_drop_user(&self, stmt: &crate::dcl::DropUserStatement) -> (String, Values) {
2843		use crate::dcl::DropRoleStatement;
2844
2845		// PostgreSQL DROP USER is DROP ROLE
2846		let mut drop_role = DropRoleStatement::new();
2847		drop_role.role_names = stmt.user_names.clone();
2848		drop_role.if_exists = stmt.if_exists;
2849
2850		// Use build_drop_role to generate the SQL
2851		self.build_drop_role(&drop_role)
2852	}
2853
2854	fn build_alter_user(&self, stmt: &crate::dcl::AlterUserStatement) -> (String, Values) {
2855		use crate::dcl::AlterRoleStatement;
2856
2857		// PostgreSQL ALTER USER is ALTER ROLE
2858		let mut alter_role = AlterRoleStatement::new().role(&stmt.user_name);
2859
2860		// Add all attributes from ALTER USER
2861		for attr in &stmt.attributes {
2862			alter_role = alter_role.attribute(attr.clone());
2863		}
2864
2865		// Use build_alter_role to generate the SQL
2866		self.build_alter_role(&alter_role)
2867	}
2868
2869	fn build_rename_user(&self, _stmt: &crate::dcl::RenameUserStatement) -> (String, Values) {
2870		panic!("RENAME USER is not supported by PostgreSQL. Use ALTER USER ... RENAME TO instead.");
2871	}
2872
2873	fn build_set_role(&self, stmt: &crate::dcl::SetRoleStatement) -> (String, Values) {
2874		use crate::dcl::RoleTarget;
2875
2876		let mut writer = SqlWriter::new();
2877
2878		writer.push("SET ROLE");
2879		writer.push_space();
2880
2881		match &stmt.target {
2882			Some(RoleTarget::Named(name)) => {
2883				writer.push_identifier(name, |s| self.escape_iden(s));
2884			}
2885			Some(RoleTarget::None) => {
2886				writer.push("NONE");
2887			}
2888			Some(RoleTarget::All) => {
2889				panic!("SET ROLE ALL is not supported by PostgreSQL (MySQL only)");
2890			}
2891			Some(RoleTarget::AllExcept(_)) => {
2892				panic!("SET ROLE ALL EXCEPT is not supported by PostgreSQL (MySQL only)");
2893			}
2894			Some(RoleTarget::Default) => {
2895				panic!("SET ROLE DEFAULT is not supported by PostgreSQL (MySQL only)");
2896			}
2897			None => {
2898				panic!("SET ROLE requires a role target");
2899			}
2900		}
2901
2902		writer.finish()
2903	}
2904
2905	fn build_reset_role(&self, _stmt: &crate::dcl::ResetRoleStatement) -> (String, Values) {
2906		let mut writer = SqlWriter::new();
2907		writer.push("RESET ROLE");
2908		writer.finish()
2909	}
2910
2911	fn build_set_default_role(
2912		&self,
2913		_stmt: &crate::dcl::SetDefaultRoleStatement,
2914	) -> (String, Values) {
2915		panic!("SET DEFAULT ROLE is not supported by PostgreSQL (MySQL only)");
2916	}
2917
2918	fn escape_identifier(&self, ident: &str) -> String {
2919		self.escape_iden(ident)
2920	}
2921
2922	fn format_placeholder(&self, index: usize) -> String {
2923		self.placeholder(index)
2924	}
2925
2926	fn build_create_schema(&self, stmt: &crate::query::CreateSchemaStatement) -> (String, Values) {
2927		use crate::types::Iden;
2928
2929		let mut writer = SqlWriter::new();
2930
2931		// CREATE SCHEMA
2932		writer.push_keyword("CREATE SCHEMA");
2933
2934		// IF NOT EXISTS
2935		if stmt.if_not_exists {
2936			writer.push_keyword("IF NOT EXISTS");
2937		}
2938
2939		// Schema name
2940		if let Some(name) = &stmt.schema_name {
2941			writer.push_space();
2942			writer.push_identifier(&Iden::to_string(name.as_ref()), |s| self.escape_iden(s));
2943		}
2944
2945		// AUTHORIZATION owner
2946		if let Some(owner) = &stmt.authorization {
2947			writer.push_keyword("AUTHORIZATION");
2948			writer.push_space();
2949			writer.push_identifier(&Iden::to_string(owner.as_ref()), |s| self.escape_iden(s));
2950		}
2951
2952		writer.finish()
2953	}
2954
2955	fn build_alter_schema(&self, stmt: &crate::query::AlterSchemaStatement) -> (String, Values) {
2956		use crate::query::AlterSchemaOperation;
2957		use crate::types::Iden;
2958
2959		let mut writer = SqlWriter::new();
2960
2961		// ALTER SCHEMA
2962		writer.push_keyword("ALTER SCHEMA");
2963
2964		// Schema name
2965		if let Some(name) = &stmt.schema_name {
2966			writer.push_space();
2967			writer.push_identifier(&Iden::to_string(name.as_ref()), |s| self.escape_iden(s));
2968		}
2969
2970		// Operation
2971		if let Some(operation) = &stmt.operation {
2972			writer.push_space();
2973			match operation {
2974				AlterSchemaOperation::RenameTo(new_name) => {
2975					writer.push_keyword("RENAME TO");
2976					writer.push_space();
2977					writer.push_identifier(&Iden::to_string(new_name.as_ref()), |s| {
2978						self.escape_iden(s)
2979					});
2980				}
2981				AlterSchemaOperation::OwnerTo(new_owner) => {
2982					writer.push_keyword("OWNER TO");
2983					writer.push_space();
2984					writer.push_identifier(&Iden::to_string(new_owner.as_ref()), |s| {
2985						self.escape_iden(s)
2986					});
2987				}
2988			}
2989		}
2990
2991		writer.finish()
2992	}
2993
2994	fn build_drop_schema(&self, stmt: &crate::query::DropSchemaStatement) -> (String, Values) {
2995		use crate::types::Iden;
2996
2997		let mut writer = SqlWriter::new();
2998
2999		// DROP SCHEMA
3000		writer.push_keyword("DROP SCHEMA");
3001
3002		// IF EXISTS
3003		if stmt.if_exists {
3004			writer.push_keyword("IF EXISTS");
3005		}
3006
3007		// Schema name
3008		if let Some(name) = &stmt.schema_name {
3009			writer.push_space();
3010			writer.push_identifier(&Iden::to_string(name.as_ref()), |s| self.escape_iden(s));
3011		}
3012
3013		// CASCADE or RESTRICT
3014		if stmt.cascade {
3015			writer.push_keyword("CASCADE");
3016		}
3017
3018		writer.finish()
3019	}
3020
3021	fn build_create_sequence(
3022		&self,
3023		stmt: &crate::query::CreateSequenceStatement,
3024	) -> (String, Values) {
3025		use crate::types::{Iden, sequence::OwnedBy};
3026
3027		let mut writer = SqlWriter::new();
3028		let seq_def = &stmt.sequence_def;
3029
3030		// CREATE SEQUENCE
3031		writer.push_keyword("CREATE SEQUENCE");
3032
3033		// IF NOT EXISTS
3034		if seq_def.if_not_exists {
3035			writer.push_keyword("IF NOT EXISTS");
3036		}
3037
3038		// Sequence name
3039		writer.push_space();
3040		writer.push_identifier(&Iden::to_string(seq_def.name.as_ref()), |s| {
3041			self.escape_iden(s)
3042		});
3043
3044		// INCREMENT BY
3045		if let Some(increment) = seq_def.increment {
3046			writer.push_keyword("INCREMENT BY");
3047			writer.push_space();
3048			writer.push(&increment.to_string());
3049		}
3050
3051		// MINVALUE or NO MINVALUE
3052		if let Some(min_value) = &seq_def.min_value {
3053			writer.push_space();
3054			match min_value {
3055				Some(val) => {
3056					writer.push_keyword("MINVALUE");
3057					writer.push_space();
3058					writer.push(&val.to_string());
3059				}
3060				None => {
3061					writer.push_keyword("NO MINVALUE");
3062				}
3063			}
3064		}
3065
3066		// MAXVALUE or NO MAXVALUE
3067		if let Some(max_value) = &seq_def.max_value {
3068			writer.push_space();
3069			match max_value {
3070				Some(val) => {
3071					writer.push_keyword("MAXVALUE");
3072					writer.push_space();
3073					writer.push(&val.to_string());
3074				}
3075				None => {
3076					writer.push_keyword("NO MAXVALUE");
3077				}
3078			}
3079		}
3080
3081		// START WITH
3082		if let Some(start) = seq_def.start {
3083			writer.push_keyword("START WITH");
3084			writer.push_space();
3085			writer.push(&start.to_string());
3086		}
3087
3088		// CACHE
3089		if let Some(cache) = seq_def.cache {
3090			writer.push_keyword("CACHE");
3091			writer.push_space();
3092			writer.push(&cache.to_string());
3093		}
3094
3095		// CYCLE or NO CYCLE
3096		if let Some(cycle) = seq_def.cycle {
3097			writer.push_space();
3098			if cycle {
3099				writer.push_keyword("CYCLE");
3100			} else {
3101				writer.push_keyword("NO CYCLE");
3102			}
3103		}
3104
3105		// OWNED BY
3106		if let Some(owned_by) = &seq_def.owned_by {
3107			writer.push_keyword("OWNED BY");
3108			writer.push_space();
3109			match owned_by {
3110				OwnedBy::Column { table, column } => {
3111					writer
3112						.push_identifier(&Iden::to_string(table.as_ref()), |s| self.escape_iden(s));
3113					writer.push(".");
3114					writer.push_identifier(&Iden::to_string(column.as_ref()), |s| {
3115						self.escape_iden(s)
3116					});
3117				}
3118				OwnedBy::None => {
3119					writer.push_keyword("NONE");
3120				}
3121			}
3122		}
3123
3124		writer.finish()
3125	}
3126
3127	fn build_alter_sequence(
3128		&self,
3129		stmt: &crate::query::AlterSequenceStatement,
3130	) -> (String, Values) {
3131		use crate::types::{
3132			Iden,
3133			sequence::{OwnedBy, SequenceOption},
3134		};
3135
3136		let mut writer = SqlWriter::new();
3137
3138		// ALTER SEQUENCE
3139		writer.push_keyword("ALTER SEQUENCE");
3140
3141		// Sequence name
3142		writer.push_space();
3143		writer.push_identifier(&Iden::to_string(stmt.name.as_ref()), |s| {
3144			self.escape_iden(s)
3145		});
3146
3147		// Options
3148		for option in &stmt.options {
3149			writer.push_space();
3150			match option {
3151				SequenceOption::Restart(value) => {
3152					writer.push_keyword("RESTART");
3153					if let Some(val) = value {
3154						writer.push_keyword("WITH");
3155						writer.push_space();
3156						writer.push(&val.to_string());
3157					}
3158				}
3159				SequenceOption::IncrementBy(value) => {
3160					writer.push_keyword("INCREMENT BY");
3161					writer.push_space();
3162					writer.push(&value.to_string());
3163				}
3164				SequenceOption::MinValue(value) => {
3165					writer.push_keyword("MINVALUE");
3166					writer.push_space();
3167					writer.push(&value.to_string());
3168				}
3169				SequenceOption::NoMinValue => {
3170					writer.push_keyword("NO MINVALUE");
3171				}
3172				SequenceOption::MaxValue(value) => {
3173					writer.push_keyword("MAXVALUE");
3174					writer.push_space();
3175					writer.push(&value.to_string());
3176				}
3177				SequenceOption::NoMaxValue => {
3178					writer.push_keyword("NO MAXVALUE");
3179				}
3180				SequenceOption::Cache(value) => {
3181					writer.push_keyword("CACHE");
3182					writer.push_space();
3183					writer.push(&value.to_string());
3184				}
3185				SequenceOption::Cycle => {
3186					writer.push_keyword("CYCLE");
3187				}
3188				SequenceOption::NoCycle => {
3189					writer.push_keyword("NO CYCLE");
3190				}
3191				SequenceOption::OwnedBy(owned_by) => {
3192					writer.push_keyword("OWNED BY");
3193					writer.push_space();
3194					match owned_by {
3195						OwnedBy::Column { table, column } => {
3196							writer.push_identifier(&Iden::to_string(table.as_ref()), |s| {
3197								self.escape_iden(s)
3198							});
3199							writer.push(".");
3200							writer.push_identifier(&Iden::to_string(column.as_ref()), |s| {
3201								self.escape_iden(s)
3202							});
3203						}
3204						OwnedBy::None => {
3205							writer.push_keyword("NONE");
3206						}
3207					}
3208				}
3209			}
3210		}
3211
3212		writer.finish()
3213	}
3214
3215	fn build_drop_sequence(&self, stmt: &crate::query::DropSequenceStatement) -> (String, Values) {
3216		use crate::types::Iden;
3217
3218		let mut writer = SqlWriter::new();
3219
3220		// DROP SEQUENCE
3221		writer.push_keyword("DROP SEQUENCE");
3222
3223		// IF EXISTS
3224		if stmt.if_exists {
3225			writer.push_keyword("IF EXISTS");
3226		}
3227
3228		// Sequence name
3229		writer.push_space();
3230		writer.push_identifier(&Iden::to_string(stmt.name.as_ref()), |s| {
3231			self.escape_iden(s)
3232		});
3233
3234		// CASCADE or RESTRICT
3235		if stmt.cascade {
3236			writer.push_keyword("CASCADE");
3237		} else if stmt.restrict {
3238			writer.push_keyword("RESTRICT");
3239		}
3240
3241		writer.finish()
3242	}
3243
3244	fn build_comment(&self, stmt: &crate::query::CommentStatement) -> (String, Values) {
3245		use crate::types::{CommentTarget, Iden};
3246
3247		let mut writer = SqlWriter::new();
3248
3249		// COMMENT ON
3250		writer.push_keyword("COMMENT ON");
3251
3252		// Target object type and name
3253		if let Some(target) = &stmt.target {
3254			writer.push_space();
3255			match target {
3256				CommentTarget::Table(table) => {
3257					writer.push_keyword("TABLE");
3258					writer.push_space();
3259					writer
3260						.push_identifier(&Iden::to_string(table.as_ref()), |s| self.escape_iden(s));
3261				}
3262				CommentTarget::Column(table, column) => {
3263					writer.push_keyword("COLUMN");
3264					writer.push_space();
3265					writer
3266						.push_identifier(&Iden::to_string(table.as_ref()), |s| self.escape_iden(s));
3267					writer.push(".");
3268					writer.push_identifier(&Iden::to_string(column.as_ref()), |s| {
3269						self.escape_iden(s)
3270					});
3271				}
3272				CommentTarget::Index(index) => {
3273					writer.push_keyword("INDEX");
3274					writer.push_space();
3275					writer
3276						.push_identifier(&Iden::to_string(index.as_ref()), |s| self.escape_iden(s));
3277				}
3278				CommentTarget::View(view) => {
3279					writer.push_keyword("VIEW");
3280					writer.push_space();
3281					writer
3282						.push_identifier(&Iden::to_string(view.as_ref()), |s| self.escape_iden(s));
3283				}
3284				CommentTarget::MaterializedView(view) => {
3285					writer.push_keyword("MATERIALIZED VIEW");
3286					writer.push_space();
3287					writer
3288						.push_identifier(&Iden::to_string(view.as_ref()), |s| self.escape_iden(s));
3289				}
3290				CommentTarget::Sequence(seq) => {
3291					writer.push_keyword("SEQUENCE");
3292					writer.push_space();
3293					writer.push_identifier(&Iden::to_string(seq.as_ref()), |s| self.escape_iden(s));
3294				}
3295				CommentTarget::Schema(schema) => {
3296					writer.push_keyword("SCHEMA");
3297					writer.push_space();
3298					writer.push_identifier(&Iden::to_string(schema.as_ref()), |s| {
3299						self.escape_iden(s)
3300					});
3301				}
3302				CommentTarget::Database(db) => {
3303					writer.push_keyword("DATABASE");
3304					writer.push_space();
3305					writer.push_identifier(&Iden::to_string(db.as_ref()), |s| self.escape_iden(s));
3306				}
3307				CommentTarget::Function(func) => {
3308					writer.push_keyword("FUNCTION");
3309					writer.push_space();
3310					writer
3311						.push_identifier(&Iden::to_string(func.as_ref()), |s| self.escape_iden(s));
3312				}
3313				CommentTarget::Trigger(trigger, table) => {
3314					writer.push_keyword("TRIGGER");
3315					writer.push_space();
3316					writer.push_identifier(&Iden::to_string(trigger.as_ref()), |s| {
3317						self.escape_iden(s)
3318					});
3319					writer.push_keyword("ON");
3320					writer.push_space();
3321					writer
3322						.push_identifier(&Iden::to_string(table.as_ref()), |s| self.escape_iden(s));
3323				}
3324				CommentTarget::Type(typ) => {
3325					writer.push_keyword("TYPE");
3326					writer.push_space();
3327					writer.push_identifier(&Iden::to_string(typ.as_ref()), |s| self.escape_iden(s));
3328				}
3329			}
3330		}
3331
3332		// IS 'comment' or IS NULL
3333		writer.push_keyword("IS");
3334		writer.push_space();
3335		if stmt.is_null {
3336			writer.push_keyword("NULL");
3337		} else if let Some(comment) = &stmt.comment {
3338			// Escape single quotes in comment text
3339			let escaped = comment.replace('\'', "''");
3340			writer.push(&format!("'{}'", escaped));
3341		}
3342
3343		writer.finish()
3344	}
3345
3346	fn build_create_database(
3347		&self,
3348		stmt: &crate::query::CreateDatabaseStatement,
3349	) -> (String, Values) {
3350		use crate::types::Iden;
3351
3352		let mut writer = SqlWriter::new();
3353
3354		// CREATE DATABASE
3355		writer.push_keyword("CREATE DATABASE");
3356
3357		// IF NOT EXISTS - PostgreSQL does not support IF NOT EXISTS for CREATE DATABASE
3358		// if stmt.if_not_exists {
3359		//     writer.push_keyword("IF NOT EXISTS");
3360		// }
3361
3362		// Database name
3363		if let Some(name) = &stmt.database_name {
3364			writer.push_space();
3365			writer.push_identifier(&Iden::to_string(name.as_ref()), |s| self.escape_iden(s));
3366		}
3367
3368		// OWNER
3369		if let Some(owner) = &stmt.owner {
3370			writer.push_keyword("OWNER");
3371			writer.push_space();
3372			writer.push_identifier(&Iden::to_string(owner.as_ref()), |s| self.escape_iden(s));
3373		}
3374
3375		// TEMPLATE
3376		if let Some(template) = &stmt.template {
3377			writer.push_keyword("TEMPLATE");
3378			writer.push_space();
3379			writer.push_identifier(&Iden::to_string(template.as_ref()), |s| self.escape_iden(s));
3380		}
3381
3382		// ENCODING
3383		if let Some(encoding) = &stmt.encoding {
3384			writer.push_keyword("ENCODING");
3385			writer.push_space();
3386			let escaped = encoding.replace('\'', "''");
3387			writer.push(&format!("'{}'", escaped));
3388		}
3389
3390		// LC_COLLATE
3391		if let Some(lc_collate) = &stmt.lc_collate {
3392			writer.push_keyword("LC_COLLATE");
3393			writer.push_space();
3394			let escaped = lc_collate.replace('\'', "''");
3395			writer.push(&format!("'{}'", escaped));
3396		}
3397
3398		// LC_CTYPE
3399		if let Some(lc_ctype) = &stmt.lc_ctype {
3400			writer.push_keyword("LC_CTYPE");
3401			writer.push_space();
3402			let escaped = lc_ctype.replace('\'', "''");
3403			writer.push(&format!("'{}'", escaped));
3404		}
3405
3406		writer.finish()
3407	}
3408
3409	fn build_alter_database(
3410		&self,
3411		stmt: &crate::query::AlterDatabaseStatement,
3412	) -> (String, Values) {
3413		use crate::types::{DatabaseOperation, Iden};
3414
3415		let mut writer = SqlWriter::new();
3416
3417		// ALTER DATABASE
3418		writer.push_keyword("ALTER DATABASE");
3419
3420		// Database name
3421		if let Some(name) = &stmt.database_name {
3422			writer.push_space();
3423			writer.push_identifier(&Iden::to_string(name.as_ref()), |s| self.escape_iden(s));
3424		}
3425
3426		// Operations
3427		for (i, operation) in stmt.operations.iter().enumerate() {
3428			if i == 0 {
3429				writer.push_space();
3430			} else {
3431				writer.push(", ");
3432			}
3433			match operation {
3434				DatabaseOperation::RenameDatabase(new_name) => {
3435					writer.push_keyword("RENAME TO");
3436					writer.push_space();
3437					writer.push_identifier(&Iden::to_string(new_name.as_ref()), |s| {
3438						self.escape_iden(s)
3439					});
3440				}
3441				DatabaseOperation::OwnerTo(new_owner) => {
3442					writer.push_keyword("OWNER TO");
3443					writer.push_space();
3444					writer.push_identifier(&Iden::to_string(new_owner.as_ref()), |s| {
3445						self.escape_iden(s)
3446					});
3447				}
3448				// CockroachDB-specific operations are not supported in PostgreSQL
3449				DatabaseOperation::AddRegion(region) => {
3450					// For CockroachDB compatibility: ADD REGION 'region-name'
3451					writer.push_keyword("ADD REGION");
3452					writer.push_space();
3453					let escaped = region.replace('\'', "''");
3454					writer.push(&format!("'{}'", escaped));
3455				}
3456				DatabaseOperation::DropRegion(region) => {
3457					// For CockroachDB compatibility: DROP REGION 'region-name'
3458					writer.push_keyword("DROP REGION");
3459					writer.push_space();
3460					let escaped = region.replace('\'', "''");
3461					writer.push(&format!("'{}'", escaped));
3462				}
3463				DatabaseOperation::SetPrimaryRegion(region) => {
3464					// For CockroachDB compatibility: PRIMARY REGION 'region-name'
3465					writer.push_keyword("PRIMARY REGION");
3466					writer.push_space();
3467					let escaped = region.replace('\'', "''");
3468					writer.push(&format!("'{}'", escaped));
3469				}
3470				DatabaseOperation::ConfigureZone(zone_config) => {
3471					// For CockroachDB compatibility: CONFIGURE ZONE USING ...
3472					writer.push_keyword("CONFIGURE ZONE USING");
3473					writer.push_space();
3474
3475					let mut parts = Vec::new();
3476
3477					if let Some(num_replicas) = zone_config.num_replicas {
3478						parts.push(format!("num_replicas = {}", num_replicas));
3479					}
3480
3481					if !zone_config.constraints.is_empty() {
3482						let constraints: Vec<String> = zone_config
3483							.constraints
3484							.iter()
3485							.map(ToString::to_string)
3486							.collect();
3487						parts.push(format!("constraints = '[{}]'", constraints.join(", ")));
3488					}
3489
3490					if !zone_config.lease_preferences.is_empty() {
3491						let prefs: Vec<String> = zone_config
3492							.lease_preferences
3493							.iter()
3494							.map(|p| format!("[{}]", p))
3495							.collect();
3496						parts.push(format!("lease_preferences = '[{}]'", prefs.join(", ")));
3497					}
3498
3499					writer.push(&parts.join(", "));
3500				}
3501			}
3502		}
3503
3504		writer.finish()
3505	}
3506
3507	fn build_drop_database(&self, stmt: &crate::query::DropDatabaseStatement) -> (String, Values) {
3508		use crate::types::Iden;
3509
3510		let mut writer = SqlWriter::new();
3511
3512		// DROP DATABASE
3513		writer.push_keyword("DROP DATABASE");
3514
3515		// IF EXISTS
3516		if stmt.if_exists {
3517			writer.push_keyword("IF EXISTS");
3518		}
3519
3520		// Database name
3521		if let Some(name) = &stmt.database_name {
3522			writer.push_space();
3523			writer.push_identifier(&Iden::to_string(name.as_ref()), |s| self.escape_iden(s));
3524		}
3525
3526		// WITH (FORCE) - PostgreSQL 13+
3527		if stmt.force {
3528			writer.push_space();
3529			writer.push_keyword("WITH");
3530			writer.push(" (");
3531			writer.push("FORCE");
3532			writer.push(")");
3533		}
3534
3535		writer.finish()
3536	}
3537
3538	fn build_analyze(&self, stmt: &crate::query::AnalyzeStatement) -> (String, Values) {
3539		use crate::types::Iden;
3540		let mut writer = SqlWriter::new();
3541
3542		writer.push_keyword("ANALYZE");
3543
3544		if stmt.verbose {
3545			writer.push_keyword("VERBOSE");
3546		}
3547
3548		// Tables and columns
3549		if !stmt.tables.is_empty() {
3550			writer.push_space();
3551			writer.push_list(&stmt.tables, ", ", |w, table| {
3552				w.push_identifier(&Iden::to_string(table.table.as_ref()), |s| {
3553					self.escape_iden(s)
3554				});
3555				if !table.columns.is_empty() {
3556					w.push(" (");
3557					w.push_list(&table.columns, ", ", |w2, col| {
3558						w2.push_identifier(&Iden::to_string(col.as_ref()), |s| self.escape_iden(s));
3559					});
3560					w.push(")");
3561				}
3562			});
3563		}
3564
3565		writer.finish()
3566	}
3567
3568	fn build_vacuum(&self, stmt: &crate::query::VacuumStatement) -> (String, Values) {
3569		use crate::types::Iden;
3570		let mut writer = SqlWriter::new();
3571
3572		writer.push_keyword("VACUUM");
3573
3574		// Options
3575		if stmt.full {
3576			writer.push_keyword("FULL");
3577		}
3578		if stmt.freeze {
3579			writer.push_keyword("FREEZE");
3580		}
3581		if stmt.verbose {
3582			writer.push_keyword("VERBOSE");
3583		}
3584		if stmt.analyze {
3585			writer.push_keyword("ANALYZE");
3586		}
3587
3588		// Tables
3589		if !stmt.tables.is_empty() {
3590			writer.push_space();
3591			writer.push_list(&stmt.tables, ", ", |w, table| {
3592				w.push_identifier(&Iden::to_string(table.as_ref()), |s| self.escape_iden(s));
3593			});
3594		}
3595
3596		writer.finish()
3597	}
3598
3599	fn build_create_materialized_view(
3600		&self,
3601		stmt: &crate::query::CreateMaterializedViewStatement,
3602	) -> (String, Values) {
3603		use crate::types::Iden;
3604		let mut writer = SqlWriter::new();
3605
3606		writer.push_keyword("CREATE MATERIALIZED VIEW");
3607
3608		// IF NOT EXISTS
3609		if stmt.def.if_not_exists {
3610			writer.push_keyword("IF NOT EXISTS");
3611		}
3612
3613		// View name
3614		writer.push_space();
3615		writer.push_identifier(&Iden::to_string(stmt.def.name.as_ref()), |s| {
3616			self.escape_iden(s)
3617		});
3618
3619		// Column names
3620		if !stmt.def.columns.is_empty() {
3621			writer.push_space();
3622			writer.push("(");
3623			writer.push_list(&stmt.def.columns, ", ", |w, col| {
3624				w.push_identifier(&Iden::to_string(col.as_ref()), |s| self.escape_iden(s));
3625			});
3626			writer.push(")");
3627		}
3628
3629		// TABLESPACE
3630		if let Some(ref tablespace) = stmt.def.tablespace {
3631			writer.push_keyword("TABLESPACE");
3632			writer.push_space();
3633			writer.push_identifier(&Iden::to_string(tablespace.as_ref()), |s| {
3634				self.escape_iden(s)
3635			});
3636		}
3637
3638		// AS SELECT
3639		if let Some(ref select) = stmt.select {
3640			writer.push_keyword("AS");
3641			writer.push_space();
3642			let (select_sql, select_values) = self.build_select(select);
3643			writer.push(&select_sql);
3644
3645			// WITH [NO] DATA
3646			if let Some(with_data) = stmt.def.with_data {
3647				writer.push_space();
3648				if with_data {
3649					writer.push_keyword("WITH DATA");
3650				} else {
3651					writer.push_keyword("WITH NO DATA");
3652				}
3653			}
3654
3655			let (sql, _) = writer.finish();
3656			return (sql, select_values);
3657		}
3658
3659		writer.finish()
3660	}
3661
3662	fn build_alter_materialized_view(
3663		&self,
3664		stmt: &crate::query::AlterMaterializedViewStatement,
3665	) -> (String, Values) {
3666		use crate::types::{Iden, MaterializedViewOperation};
3667		let mut writer = SqlWriter::new();
3668
3669		writer.push_keyword("ALTER MATERIALIZED VIEW");
3670
3671		// View name
3672		if let Some(ref name) = stmt.name {
3673			writer.push_space();
3674			writer.push_identifier(&Iden::to_string(name.as_ref()), |s| self.escape_iden(s));
3675		}
3676
3677		// Operations
3678		for operation in &stmt.operations {
3679			writer.push_space();
3680			match operation {
3681				MaterializedViewOperation::Rename(new_name) => {
3682					writer.push_keyword("RENAME TO");
3683					writer.push_space();
3684					writer.push_identifier(&Iden::to_string(new_name.as_ref()), |s| {
3685						self.escape_iden(s)
3686					});
3687				}
3688				MaterializedViewOperation::OwnerTo(new_owner) => {
3689					writer.push_keyword("OWNER TO");
3690					writer.push_space();
3691					writer.push_identifier(&Iden::to_string(new_owner.as_ref()), |s| {
3692						self.escape_iden(s)
3693					});
3694				}
3695				MaterializedViewOperation::SetSchema(schema_name) => {
3696					writer.push_keyword("SET SCHEMA");
3697					writer.push_space();
3698					writer.push_identifier(&Iden::to_string(schema_name.as_ref()), |s| {
3699						self.escape_iden(s)
3700					});
3701				}
3702			}
3703		}
3704
3705		writer.finish()
3706	}
3707
3708	fn build_drop_materialized_view(
3709		&self,
3710		stmt: &crate::query::DropMaterializedViewStatement,
3711	) -> (String, Values) {
3712		use crate::types::Iden;
3713		let mut writer = SqlWriter::new();
3714
3715		writer.push_keyword("DROP MATERIALIZED VIEW");
3716
3717		// IF EXISTS
3718		if stmt.if_exists {
3719			writer.push_keyword("IF EXISTS");
3720		}
3721
3722		// View names
3723		writer.push_space();
3724		writer.push_list(&stmt.names, ", ", |w, name| {
3725			w.push_identifier(&Iden::to_string(name.as_ref()), |s| self.escape_iden(s));
3726		});
3727
3728		// CASCADE or RESTRICT
3729		if stmt.cascade {
3730			writer.push_keyword("CASCADE");
3731		} else if stmt.restrict {
3732			writer.push_keyword("RESTRICT");
3733		}
3734
3735		writer.finish()
3736	}
3737
3738	fn build_refresh_materialized_view(
3739		&self,
3740		stmt: &crate::query::RefreshMaterializedViewStatement,
3741	) -> (String, Values) {
3742		use crate::types::Iden;
3743		let mut writer = SqlWriter::new();
3744
3745		writer.push_keyword("REFRESH MATERIALIZED VIEW");
3746
3747		// CONCURRENTLY
3748		if stmt.concurrently {
3749			writer.push_keyword("CONCURRENTLY");
3750		}
3751
3752		// View name
3753		if let Some(ref name) = stmt.name {
3754			writer.push_space();
3755			writer.push_identifier(&Iden::to_string(name.as_ref()), |s| self.escape_iden(s));
3756		}
3757
3758		// WITH [NO] DATA
3759		if let Some(with_data) = stmt.with_data {
3760			writer.push_space();
3761			if with_data {
3762				writer.push_keyword("WITH DATA");
3763			} else {
3764				writer.push_keyword("WITH NO DATA");
3765			}
3766		}
3767
3768		writer.finish()
3769	}
3770
3771	fn build_create_procedure(
3772		&self,
3773		stmt: &crate::query::CreateProcedureStatement,
3774	) -> (String, Values) {
3775		use crate::types::{
3776			Iden,
3777			function::{FunctionBehavior, FunctionLanguage, FunctionSecurity},
3778		};
3779
3780		let mut writer = SqlWriter::new();
3781
3782		// CREATE [OR REPLACE] PROCEDURE
3783		writer.push_keyword("CREATE");
3784		if stmt.procedure_def.or_replace {
3785			writer.push_keyword("OR REPLACE");
3786		}
3787		writer.push_keyword("PROCEDURE");
3788
3789		// Procedure name
3790		writer.push_space();
3791		writer.push_identifier(&Iden::to_string(stmt.procedure_def.name.as_ref()), |s| {
3792			self.escape_iden(s)
3793		});
3794
3795		// Parameters (param1 type1, param2 type2, ...)
3796		writer.push("(");
3797		let mut first = true;
3798		for param in &stmt.procedure_def.parameters {
3799			if !first {
3800				writer.push(", ");
3801			}
3802			first = false;
3803
3804			// Parameter mode (IN, OUT, INOUT, VARIADIC)
3805			if let Some(mode) = &param.mode {
3806				use crate::types::function::ParameterMode;
3807				match mode {
3808					ParameterMode::In => writer.push("IN "),
3809					ParameterMode::Out => writer.push("OUT "),
3810					ParameterMode::InOut => writer.push("INOUT "),
3811					ParameterMode::Variadic => writer.push("VARIADIC "),
3812				}
3813			}
3814
3815			// Parameter name (optional)
3816			if let Some(name) = &param.name {
3817				writer.push_identifier(&Iden::to_string(name.as_ref()), |s| self.escape_iden(s));
3818				writer.push(" ");
3819			}
3820
3821			// Parameter type
3822			if let Some(param_type) = &param.param_type {
3823				writer.push(param_type);
3824			}
3825
3826			// Default value (optional)
3827			if let Some(default) = &param.default_value {
3828				writer.push(" DEFAULT ");
3829				writer.push(default);
3830			}
3831		}
3832		writer.push(")");
3833
3834		// LANGUAGE
3835		if let Some(language) = &stmt.procedure_def.language {
3836			writer.push_keyword("LANGUAGE");
3837			writer.push_space();
3838			match language {
3839				FunctionLanguage::Sql => writer.push("SQL"),
3840				FunctionLanguage::PlPgSql => writer.push("PLPGSQL"),
3841				FunctionLanguage::C => writer.push("C"),
3842				FunctionLanguage::Custom(lang) => writer.push(lang),
3843			}
3844		}
3845
3846		// Behavior (IMMUTABLE/STABLE/VOLATILE)
3847		if let Some(behavior) = &stmt.procedure_def.behavior {
3848			writer.push_space();
3849			match behavior {
3850				FunctionBehavior::Immutable => writer.push_keyword("IMMUTABLE"),
3851				FunctionBehavior::Stable => writer.push_keyword("STABLE"),
3852				FunctionBehavior::Volatile => writer.push_keyword("VOLATILE"),
3853			}
3854		}
3855
3856		// Security (SECURITY DEFINER/INVOKER)
3857		if let Some(security) = &stmt.procedure_def.security {
3858			writer.push_space();
3859			match security {
3860				FunctionSecurity::Definer => writer.push_keyword("SECURITY DEFINER"),
3861				FunctionSecurity::Invoker => writer.push_keyword("SECURITY INVOKER"),
3862			}
3863		}
3864
3865		// AS 'body'
3866		if let Some(body) = &stmt.procedure_def.body {
3867			writer.push_keyword("AS");
3868			writer.push_space();
3869			let delimiter = generate_safe_dollar_quote_delimiter(body);
3870			writer.push(&delimiter);
3871			writer.push(body);
3872			writer.push(&delimiter);
3873		}
3874
3875		writer.finish()
3876	}
3877
3878	fn build_alter_procedure(
3879		&self,
3880		stmt: &crate::query::AlterProcedureStatement,
3881	) -> (String, Values) {
3882		use crate::types::{
3883			Iden,
3884			function::{FunctionBehavior, FunctionSecurity},
3885			procedure::ProcedureOperation,
3886		};
3887
3888		let mut writer = SqlWriter::new();
3889
3890		// ALTER PROCEDURE
3891		writer.push_keyword("ALTER PROCEDURE");
3892
3893		// Procedure name
3894		if let Some(name) = &stmt.name {
3895			writer.push_space();
3896			writer.push_identifier(&Iden::to_string(name.as_ref()), |s| self.escape_iden(s));
3897		}
3898
3899		// Procedure signature (for overloaded procedures)
3900		if !stmt.parameters.is_empty() {
3901			writer.push("(");
3902			let mut first = true;
3903			for param in &stmt.parameters {
3904				if !first {
3905					writer.push(", ");
3906				}
3907				first = false;
3908
3909				// Parameter name (optional in signature)
3910				if let Some(name) = &param.name {
3911					let name_str = Iden::to_string(name.as_ref());
3912					if !name_str.is_empty() {
3913						writer.push_identifier(&name_str, |s| self.escape_iden(s));
3914						writer.push(" ");
3915					}
3916				}
3917
3918				// Parameter type
3919				if let Some(param_type) = &param.param_type {
3920					writer.push(param_type);
3921				}
3922			}
3923			writer.push(")");
3924		}
3925
3926		// ALTER PROCEDURE operation
3927		if let Some(operation) = &stmt.operation {
3928			writer.push_space();
3929			match operation {
3930				ProcedureOperation::RenameTo(new_name) => {
3931					writer.push_keyword("RENAME TO");
3932					writer.push_space();
3933					writer.push_identifier(&Iden::to_string(new_name.as_ref()), |s| {
3934						self.escape_iden(s)
3935					});
3936				}
3937				ProcedureOperation::OwnerTo(new_owner) => {
3938					writer.push_keyword("OWNER TO");
3939					writer.push_space();
3940					writer.push_identifier(&Iden::to_string(new_owner.as_ref()), |s| {
3941						self.escape_iden(s)
3942					});
3943				}
3944				ProcedureOperation::SetSchema(new_schema) => {
3945					writer.push_keyword("SET SCHEMA");
3946					writer.push_space();
3947					writer.push_identifier(&Iden::to_string(new_schema.as_ref()), |s| {
3948						self.escape_iden(s)
3949					});
3950				}
3951				ProcedureOperation::SetBehavior(behavior) => match behavior {
3952					FunctionBehavior::Immutable => writer.push_keyword("IMMUTABLE"),
3953					FunctionBehavior::Stable => writer.push_keyword("STABLE"),
3954					FunctionBehavior::Volatile => writer.push_keyword("VOLATILE"),
3955				},
3956				ProcedureOperation::SetSecurity(security) => match security {
3957					FunctionSecurity::Definer => writer.push_keyword("SECURITY DEFINER"),
3958					FunctionSecurity::Invoker => writer.push_keyword("SECURITY INVOKER"),
3959				},
3960			}
3961		}
3962
3963		writer.finish()
3964	}
3965
3966	fn build_drop_procedure(
3967		&self,
3968		stmt: &crate::query::DropProcedureStatement,
3969	) -> (String, Values) {
3970		use crate::types::Iden;
3971
3972		let mut writer = SqlWriter::new();
3973
3974		// DROP PROCEDURE
3975		writer.push_keyword("DROP PROCEDURE");
3976
3977		// IF EXISTS
3978		if stmt.if_exists {
3979			writer.push_keyword("IF EXISTS");
3980		}
3981
3982		// Procedure name
3983		if let Some(name) = &stmt.name {
3984			writer.push_space();
3985			writer.push_identifier(&Iden::to_string(name.as_ref()), |s| self.escape_iden(s));
3986		}
3987
3988		// Procedure signature (for overloaded procedures)
3989		if !stmt.parameters.is_empty() {
3990			writer.push("(");
3991			let mut first = true;
3992			for param in &stmt.parameters {
3993				if !first {
3994					writer.push(", ");
3995				}
3996				first = false;
3997
3998				// Parameter name (optional in signature)
3999				if let Some(name) = &param.name {
4000					let name_str = Iden::to_string(name.as_ref());
4001					if !name_str.is_empty() {
4002						writer.push_identifier(&name_str, |s| self.escape_iden(s));
4003						writer.push(" ");
4004					}
4005				}
4006
4007				// Parameter type
4008				if let Some(param_type) = &param.param_type {
4009					writer.push(param_type);
4010				}
4011			}
4012			writer.push(")");
4013		}
4014
4015		// CASCADE
4016		if stmt.cascade {
4017			writer.push_keyword("CASCADE");
4018		}
4019
4020		writer.finish()
4021	}
4022	//
4023	fn build_create_type(&self, stmt: &crate::query::CreateTypeStatement) -> (String, Values) {
4024		let mut writer = SqlWriter::new();
4025
4026		writer.push_keyword("CREATE TYPE");
4027		writer.push_space();
4028
4029		// Type name
4030		if let Some(name) = &stmt.name {
4031			writer.push_identifier(&name.to_string(), |s| self.escape_iden(s));
4032		}
4033
4034		// Type definition
4035		if let Some(kind) = &stmt.kind {
4036			use crate::types::type_def::TypeKind;
4037			match kind {
4038				TypeKind::Enum { values } => {
4039					writer.push_space();
4040					writer.push_keyword("AS ENUM");
4041					writer.push_space();
4042					writer.push("(");
4043					writer.push_list(values, ", ", |w, value| {
4044						w.push("'");
4045						w.push(&value.replace('\'', "''"));
4046						w.push("'");
4047					});
4048					writer.push(")");
4049				}
4050				TypeKind::Composite { attributes } => {
4051					writer.push_space();
4052					writer.push_keyword("AS");
4053					writer.push_space();
4054					writer.push("(");
4055					writer.push_list(attributes, ", ", |w, (name, type_name)| {
4056						w.push_identifier(name, |s| self.escape_iden(s));
4057						w.push_space();
4058						w.push(type_name);
4059					});
4060					writer.push(")");
4061				}
4062				TypeKind::Domain {
4063					base_type,
4064					constraint,
4065					default,
4066					not_null,
4067				} => {
4068					writer.push_space();
4069					writer.push_keyword("AS");
4070					writer.push_space();
4071					writer.push(base_type);
4072
4073					// DEFAULT clause
4074					if let Some(default_val) = default {
4075						writer.push_space();
4076						writer.push_keyword("DEFAULT");
4077						writer.push_space();
4078						writer.push(default_val);
4079					}
4080
4081					// CONSTRAINT clause
4082					if let Some(check) = constraint {
4083						writer.push_space();
4084						writer.push(check);
4085					}
4086
4087					// NOT NULL clause
4088					if *not_null {
4089						writer.push_space();
4090						writer.push_keyword("NOT NULL");
4091					}
4092				}
4093				TypeKind::Range {
4094					subtype,
4095					subtype_diff,
4096					canonical,
4097				} => {
4098					writer.push_space();
4099					writer.push_keyword("AS RANGE");
4100					writer.push_space();
4101					writer.push("(");
4102					writer.push("SUBTYPE = ");
4103					writer.push(subtype);
4104
4105					// SUBTYPE_DIFF clause
4106					if let Some(diff_fn) = subtype_diff {
4107						writer.push(", SUBTYPE_DIFF = ");
4108						writer.push(diff_fn);
4109					}
4110
4111					// CANONICAL clause
4112					if let Some(canonical_fn) = canonical {
4113						writer.push(", CANONICAL = ");
4114						writer.push(canonical_fn);
4115					}
4116
4117					writer.push(")");
4118				}
4119			}
4120		}
4121
4122		writer.finish()
4123	}
4124
4125	fn build_alter_type(&self, stmt: &crate::query::AlterTypeStatement) -> (String, Values) {
4126		let mut writer = SqlWriter::new();
4127
4128		writer.push_keyword("ALTER TYPE");
4129		writer.push_space();
4130		writer.push_identifier(&stmt.name.to_string(), |s| self.escape_iden(s));
4131
4132		// Process operations
4133		for operation in &stmt.operations {
4134			writer.push_space();
4135			use crate::types::type_def::TypeOperation;
4136			match operation {
4137				TypeOperation::RenameTo(new_name) => {
4138					writer.push_keyword("RENAME TO");
4139					writer.push_space();
4140					writer.push_identifier(&new_name.to_string(), |s| self.escape_iden(s));
4141				}
4142				TypeOperation::OwnerTo(owner) => {
4143					writer.push_keyword("OWNER TO");
4144					writer.push_space();
4145					writer.push_identifier(&owner.to_string(), |s| self.escape_iden(s));
4146				}
4147				TypeOperation::SetSchema(schema) => {
4148					writer.push_keyword("SET SCHEMA");
4149					writer.push_space();
4150					writer.push_identifier(&schema.to_string(), |s| self.escape_iden(s));
4151				}
4152				TypeOperation::AddValue(value, position) => {
4153					writer.push_keyword("ADD VALUE");
4154					writer.push_space();
4155					writer.push("'");
4156					writer.push(&value.replace('\'', "''"));
4157					writer.push("'");
4158
4159					if let Some(pos) = position {
4160						writer.push_space();
4161						writer.push_keyword("BEFORE");
4162						writer.push_space();
4163						writer.push("'");
4164						writer.push(&pos.replace('\'', "''"));
4165						writer.push("'");
4166					}
4167				}
4168				TypeOperation::RenameValue(old_value, new_value) => {
4169					writer.push_keyword("RENAME VALUE");
4170					writer.push_space();
4171					writer.push("'");
4172					writer.push(&old_value.replace('\'', "''"));
4173					writer.push("'");
4174					writer.push_space();
4175					writer.push_keyword("TO");
4176					writer.push_space();
4177					writer.push("'");
4178					writer.push(&new_value.replace('\'', "''"));
4179					writer.push("'");
4180				}
4181				TypeOperation::AddConstraint(name, check) => {
4182					writer.push_keyword("ADD CONSTRAINT");
4183					writer.push_space();
4184					writer.push_identifier(name, |s| self.escape_iden(s));
4185					writer.push_space();
4186					writer.push(check);
4187				}
4188				TypeOperation::DropConstraint(name, if_exists) => {
4189					writer.push_keyword("DROP CONSTRAINT");
4190					if *if_exists {
4191						writer.push_space();
4192						writer.push_keyword("IF EXISTS");
4193					}
4194					writer.push_space();
4195					writer.push_identifier(name, |s| self.escape_iden(s));
4196				}
4197				TypeOperation::SetDefault(value) => {
4198					writer.push_keyword("SET DEFAULT");
4199					writer.push_space();
4200					writer.push(value);
4201				}
4202				TypeOperation::DropDefault => {
4203					writer.push_keyword("DROP DEFAULT");
4204				}
4205				TypeOperation::SetNotNull => {
4206					writer.push_keyword("SET NOT NULL");
4207				}
4208				TypeOperation::DropNotNull => {
4209					writer.push_keyword("DROP NOT NULL");
4210				}
4211			}
4212		}
4213
4214		writer.finish()
4215	}
4216
4217	fn build_drop_type(&self, stmt: &crate::query::DropTypeStatement) -> (String, Values) {
4218		let mut writer = SqlWriter::new();
4219
4220		writer.push_keyword("DROP TYPE");
4221		writer.push_space();
4222
4223		// IF EXISTS clause
4224		if stmt.if_exists {
4225			writer.push_keyword("IF EXISTS");
4226			writer.push_space();
4227		}
4228
4229		// Type name
4230		writer.push_identifier(&stmt.name.to_string(), |s| self.escape_iden(s));
4231
4232		// CASCADE/RESTRICT clause
4233		if stmt.cascade {
4234			writer.push_space();
4235			writer.push_keyword("CASCADE");
4236		} else if stmt.restrict {
4237			writer.push_space();
4238			writer.push_keyword("RESTRICT");
4239		}
4240
4241		writer.finish()
4242	}
4243}
4244
4245// Helper methods for DDL operations
4246impl PostgresQueryBuilder {
4247	/// Convert ColumnType to PostgreSQL SQL type string
4248	///
4249	/// Note: The `self` parameter is used in recursive calls for Array types (e.g., INTEGER[]).
4250	/// The clippy::only_used_in_recursion warning is allowed because this is the intended design
4251	/// for handling nested array types, and keeping `self` maintains consistency with other
4252	/// backend implementations.
4253	#[allow(clippy::only_used_in_recursion)]
4254	fn column_type_to_sql(&self, col_type: &crate::types::ColumnType) -> String {
4255		use crate::types::ColumnType;
4256		match col_type {
4257			ColumnType::Char(len) => format!("CHAR({})", len.unwrap_or(1)),
4258			ColumnType::String(len) => {
4259				if let Some(l) = len {
4260					format!("VARCHAR({})", l)
4261				} else {
4262					"VARCHAR".to_string()
4263				}
4264			}
4265			ColumnType::Text => "TEXT".to_string(),
4266			ColumnType::TinyInteger => "SMALLINT".to_string(),
4267			ColumnType::SmallInteger => "SMALLINT".to_string(),
4268			ColumnType::Integer => "INTEGER".to_string(),
4269			ColumnType::BigInteger => "BIGINT".to_string(),
4270			ColumnType::Float => "REAL".to_string(),
4271			ColumnType::Double => "DOUBLE PRECISION".to_string(),
4272			ColumnType::Decimal(precision) => {
4273				// PostgreSQL uses NUMERIC as the canonical name (DECIMAL is an alias)
4274				if let Some((p, s)) = precision {
4275					format!("NUMERIC({}, {})", p, s)
4276				} else {
4277					"NUMERIC".to_string()
4278				}
4279			}
4280			ColumnType::DateTime => "TIMESTAMP".to_string(),
4281			ColumnType::Timestamp => "TIMESTAMP".to_string(),
4282			ColumnType::TimestampWithTimeZone => "TIMESTAMP WITH TIME ZONE".to_string(),
4283			ColumnType::Time => "TIME".to_string(),
4284			ColumnType::Date => "DATE".to_string(),
4285			ColumnType::Binary(_len) => {
4286				// PostgreSQL BYTEA does not support length modifiers
4287				"BYTEA".to_string()
4288			}
4289			ColumnType::VarBinary(_len) => {
4290				// PostgreSQL BYTEA does not support length modifiers
4291				"BYTEA".to_string()
4292			}
4293			ColumnType::Blob => "BYTEA".to_string(),
4294			ColumnType::Boolean => "BOOLEAN".to_string(),
4295			ColumnType::Json => "JSON".to_string(),
4296			ColumnType::JsonBinary => "JSONB".to_string(),
4297			ColumnType::Uuid => "UUID".to_string(),
4298			ColumnType::Array(inner_type) => {
4299				format!("{}[]", self.column_type_to_sql(inner_type))
4300			}
4301			ColumnType::Custom(name) => name.clone(),
4302		}
4303	}
4304
4305	fn write_table_constraint(
4306		&self,
4307		writer: &mut SqlWriter,
4308		constraint: &crate::types::TableConstraint,
4309	) {
4310		use crate::types::TableConstraint;
4311		match constraint {
4312			TableConstraint::PrimaryKey { name, columns } => {
4313				if let Some(n) = name {
4314					writer.push_keyword("CONSTRAINT");
4315					writer.push_space();
4316					writer.push_identifier(&n.to_string(), |s| self.escape_iden(s));
4317					writer.push_space();
4318				}
4319				writer.push_keyword("PRIMARY KEY");
4320				writer.push_space();
4321				writer.push("(");
4322				writer.push_list(columns, ", ", |w, col| {
4323					w.push_identifier(&col.to_string(), |s| self.escape_iden(s));
4324				});
4325				writer.push(")");
4326			}
4327			TableConstraint::Unique { name, columns } => {
4328				if let Some(n) = name {
4329					writer.push_keyword("CONSTRAINT");
4330					writer.push_space();
4331					writer.push_identifier(&n.to_string(), |s| self.escape_iden(s));
4332					writer.push_space();
4333				}
4334				writer.push_keyword("UNIQUE");
4335				writer.push_space();
4336				writer.push("(");
4337				writer.push_list(columns, ", ", |w, col| {
4338					w.push_identifier(&col.to_string(), |s| self.escape_iden(s));
4339				});
4340				writer.push(")");
4341			}
4342			TableConstraint::ForeignKey {
4343				name,
4344				columns,
4345				ref_table,
4346				ref_columns,
4347				on_delete,
4348				on_update,
4349			} => {
4350				if let Some(n) = name {
4351					writer.push_keyword("CONSTRAINT");
4352					writer.push_space();
4353					writer.push_identifier(&n.to_string(), |s| self.escape_iden(s));
4354					writer.push_space();
4355				}
4356				writer.push_keyword("FOREIGN KEY");
4357				writer.push_space();
4358				writer.push("(");
4359				writer.push_list(columns, ", ", |w, col| {
4360					w.push_identifier(&col.to_string(), |s| self.escape_iden(s));
4361				});
4362				writer.push(")");
4363				writer.push_space();
4364				writer.push_keyword("REFERENCES");
4365				writer.push_space();
4366				self.write_table_ref(writer, ref_table);
4367				writer.push_space();
4368				writer.push("(");
4369				writer.push_list(ref_columns, ", ", |w, col| {
4370					w.push_identifier(&col.to_string(), |s| self.escape_iden(s));
4371				});
4372				writer.push(")");
4373				if let Some(action) = on_delete {
4374					writer.push_space();
4375					writer.push_keyword("ON DELETE");
4376					writer.push_space();
4377					writer.push_keyword(self.foreign_key_action_to_sql(action));
4378				}
4379				if let Some(action) = on_update {
4380					writer.push_space();
4381					writer.push_keyword("ON UPDATE");
4382					writer.push_space();
4383					writer.push_keyword(self.foreign_key_action_to_sql(action));
4384				}
4385			}
4386			TableConstraint::Check { name, expr } => {
4387				if let Some(n) = name {
4388					writer.push_keyword("CONSTRAINT");
4389					writer.push_space();
4390					writer.push_identifier(&n.to_string(), |s| self.escape_iden(s));
4391					writer.push_space();
4392				}
4393				writer.push_keyword("CHECK");
4394				writer.push_space();
4395				writer.push("(");
4396				self.write_simple_expr(writer, expr);
4397				writer.push(")");
4398			}
4399		}
4400	}
4401
4402	fn foreign_key_action_to_sql(&self, action: &crate::types::ForeignKeyAction) -> &'static str {
4403		use crate::types::ForeignKeyAction;
4404		match action {
4405			ForeignKeyAction::Restrict => "RESTRICT",
4406			ForeignKeyAction::Cascade => "CASCADE",
4407			ForeignKeyAction::SetNull => "SET NULL",
4408			ForeignKeyAction::SetDefault => "SET DEFAULT",
4409			ForeignKeyAction::NoAction => "NO ACTION",
4410		}
4411	}
4412
4413	fn index_method_to_sql(&self, method: &crate::query::IndexMethod) -> &'static str {
4414		use crate::query::IndexMethod;
4415		match method {
4416			IndexMethod::BTree => "BTREE",
4417			IndexMethod::Hash => "HASH",
4418			IndexMethod::Gist => "GIST",
4419			IndexMethod::Gin => "GIN",
4420			IndexMethod::Brin => "BRIN",
4421			IndexMethod::FullText => "GIN", // PostgreSQL uses GIN for full-text search
4422			IndexMethod::Spatial => "GIST", // PostgreSQL uses GIST for spatial indexes
4423		}
4424	}
4425}
4426
4427impl PostgresQueryBuilder {
4428	/// Format a role specification for PostgreSQL
4429	///
4430	/// # Arguments
4431	///
4432	/// * `spec` - The role specification to format
4433	///
4434	/// # Returns
4435	///
4436	/// The SQL representation of the role specification
4437	fn format_role_specification(spec: &crate::dcl::RoleSpecification) -> &str {
4438		use crate::dcl::RoleSpecification;
4439
4440		match spec {
4441			RoleSpecification::RoleName(name) => name,
4442			RoleSpecification::CurrentRole => "CURRENT_ROLE",
4443			RoleSpecification::CurrentUser => "CURRENT_USER",
4444			RoleSpecification::SessionUser => "SESSION_USER",
4445		}
4446	}
4447}
4448
4449/// Collect all dollar-quote delimiters present in the body text.
4450///
4451/// A dollar-quote delimiter in PostgreSQL has the form `$tag$` where `tag` is
4452/// either empty or consists of `[a-zA-Z0-9_]` characters not starting with a
4453/// digit. This function scans the body and returns the set of all such
4454/// delimiters (including the surrounding `$` signs).
4455///
4456/// Using exact delimiter boundary detection instead of substring matching
4457/// prevents false positives (e.g. `$100` is not a delimiter) and false
4458/// negatives (e.g. partial overlap with candidate delimiter tags).
4459fn collect_dollar_quote_delimiters(body: &str) -> std::collections::HashSet<String> {
4460	let mut delimiters = std::collections::HashSet::new();
4461	let bytes = body.as_bytes();
4462	let len = bytes.len();
4463	let mut i = 0;
4464
4465	while i < len {
4466		if bytes[i] == b'$' {
4467			// Found a '$', try to parse a dollar-quote delimiter
4468			let start = i;
4469			i += 1;
4470
4471			// Empty tag: `$$`
4472			if i < len && bytes[i] == b'$' {
4473				delimiters.insert("$$".to_string());
4474				i += 1;
4475				continue;
4476			}
4477
4478			// Non-empty tag: tag must match [a-zA-Z_][a-zA-Z0-9_]*
4479			if i < len && (bytes[i].is_ascii_alphabetic() || bytes[i] == b'_') {
4480				let tag_start = i;
4481				i += 1;
4482				while i < len && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
4483					i += 1;
4484				}
4485				// Check for closing '$'
4486				if i < len && bytes[i] == b'$' {
4487					let delimiter = &body[start..=i];
4488					delimiters.insert(delimiter.to_string());
4489					i += 1;
4490					continue;
4491				}
4492				// Not a valid delimiter, continue from after the initial '$'
4493				i = tag_start;
4494				continue;
4495			}
4496
4497			// '$' followed by a digit or other non-tag character -- not a delimiter
4498			continue;
4499		}
4500		i += 1;
4501	}
4502
4503	delimiters
4504}
4505
4506/// Generate a safe dollar-quote delimiter that does not appear in the body.
4507///
4508/// PostgreSQL dollar-quoting uses `$$` as the default delimiter. If the function
4509/// body contains `$$`, an attacker could break out of the dollar-quoted string.
4510/// This function scans for all dollar-quote delimiter patterns in the body and
4511/// generates a unique delimiter that does not conflict with any of them.
4512fn generate_safe_dollar_quote_delimiter(body: &str) -> String {
4513	let existing = collect_dollar_quote_delimiters(body);
4514
4515	if !existing.contains("$$") {
4516		return "$$".to_string();
4517	}
4518
4519	// Try numbered delimiters: $body_0$, $body_1$, ...
4520	for i in 0u64.. {
4521		let candidate = format!("$body_{}$", i);
4522		if !existing.contains(&candidate) {
4523			return candidate;
4524		}
4525	}
4526
4527	// Unreachable in practice, but satisfy the compiler
4528	"$$".to_string()
4529}
4530
4531/// Determine the byte width of a UTF-8 character from its leading byte.
4532///
4533/// This avoids pushing individual bytes as `char` (which corrupts multi-byte
4534/// UTF-8 sequences). The function assumes valid UTF-8 input, which is
4535/// guaranteed because the input is a Rust `&str`.
4536fn utf8_char_width(leading_byte: u8) -> usize {
4537	if leading_byte < 0x80 {
4538		1
4539	} else if leading_byte < 0xE0 {
4540		2
4541	} else if leading_byte < 0xF0 {
4542		3
4543	} else {
4544		4
4545	}
4546}
4547
4548impl crate::query::QueryBuilderTrait for PostgresQueryBuilder {
4549	fn placeholder(&self) -> (&str, bool) {
4550		("$", true)
4551	}
4552
4553	fn quote_char(&self) -> char {
4554		'"'
4555	}
4556}
4557
4558#[cfg(test)]
4559mod tests {
4560	use super::*;
4561	use crate::{
4562		expr::{Expr, ExprTrait},
4563		query::Query,
4564		types::{Alias, IntoIden},
4565	};
4566	use rstest::rstest;
4567
4568	#[test]
4569	fn test_escape_identifier() {
4570		let builder = PostgresQueryBuilder::new();
4571		assert_eq!(builder.escape_identifier("user"), "\"user\"");
4572		assert_eq!(builder.escape_identifier("table_name"), "\"table_name\"");
4573	}
4574
4575	#[test]
4576	fn test_escape_identifier_with_quotes() {
4577		let builder = PostgresQueryBuilder::new();
4578		assert_eq!(builder.escape_identifier("user\"name"), "\"user\"\"name\"");
4579	}
4580
4581	#[test]
4582	fn test_format_placeholder() {
4583		let builder = PostgresQueryBuilder::new();
4584		assert_eq!(builder.format_placeholder(1), "$1");
4585		assert_eq!(builder.format_placeholder(2), "$2");
4586		assert_eq!(builder.format_placeholder(10), "$10");
4587	}
4588
4589	#[test]
4590	fn test_select_basic() {
4591		let builder = PostgresQueryBuilder::new();
4592		let mut stmt = Query::select();
4593		stmt.column("id").column("name").from("users");
4594
4595		let (sql, values) = builder.build_select(&stmt);
4596		assert_eq!(sql, "SELECT \"id\", \"name\" FROM \"users\"");
4597		assert_eq!(values.len(), 0);
4598	}
4599
4600	#[test]
4601	fn test_select_asterisk() {
4602		let builder = PostgresQueryBuilder::new();
4603		let mut stmt = Query::select();
4604		stmt.from("users");
4605
4606		let (sql, values) = builder.build_select(&stmt);
4607		assert_eq!(sql, "SELECT * FROM \"users\"");
4608		assert_eq!(values.len(), 0);
4609	}
4610
4611	#[test]
4612	fn test_select_with_where() {
4613		let builder = PostgresQueryBuilder::new();
4614		let mut stmt = Query::select();
4615		stmt.column("id")
4616			.from("users")
4617			.and_where(Expr::col("active").eq(true));
4618
4619		let (sql, _values) = builder.build_select(&stmt);
4620		// Note: The exact WHERE clause format depends on Expr implementation
4621		assert!(sql.contains("SELECT"));
4622		assert!(sql.contains("FROM"));
4623		assert!(sql.contains("WHERE"));
4624	}
4625
4626	#[test]
4627	fn test_select_with_limit_offset() {
4628		let builder = PostgresQueryBuilder::new();
4629		let mut stmt = Query::select();
4630		stmt.column("id").from("users").limit(10).offset(20);
4631
4632		let (sql, values) = builder.build_select(&stmt);
4633		assert!(sql.contains("SELECT"));
4634		assert!(sql.contains("FROM"));
4635		assert!(sql.contains("LIMIT"));
4636		assert!(sql.contains("OFFSET"));
4637		assert_eq!(values.len(), 2);
4638	}
4639
4640	#[test]
4641	fn test_insert_basic() {
4642		let builder = PostgresQueryBuilder::new();
4643		let mut stmt = Query::insert();
4644		stmt.into_table("users")
4645			.columns(["name", "email"])
4646			.values_panic(["Alice", "alice@example.com"]);
4647
4648		let (sql, values) = builder.build_insert(&stmt);
4649		assert_eq!(
4650			sql,
4651			"INSERT INTO \"users\" (\"name\", \"email\") VALUES ($1, $2)"
4652		);
4653		assert_eq!(values.len(), 2);
4654	}
4655
4656	#[test]
4657	fn test_insert_multiple_rows() {
4658		let builder = PostgresQueryBuilder::new();
4659		let mut stmt = Query::insert();
4660		stmt.into_table("users")
4661			.columns(["name", "email"])
4662			.values_panic(["Alice", "alice@example.com"])
4663			.values_panic(["Bob", "bob@example.com"]);
4664
4665		let (sql, values) = builder.build_insert(&stmt);
4666		assert_eq!(
4667			sql,
4668			"INSERT INTO \"users\" (\"name\", \"email\") VALUES ($1, $2), ($3, $4)"
4669		);
4670		assert_eq!(values.len(), 4);
4671	}
4672
4673	#[test]
4674	fn test_insert_with_returning() {
4675		let builder = PostgresQueryBuilder::new();
4676		let mut stmt = Query::insert();
4677		stmt.into_table("users")
4678			.columns(["name"])
4679			.values_panic(["Alice"])
4680			.returning(["id", "created_at"]);
4681
4682		let (sql, values) = builder.build_insert(&stmt);
4683		assert!(sql.contains("INSERT INTO"));
4684		assert!(sql.contains("VALUES"));
4685		assert!(sql.contains("RETURNING"));
4686		assert!(sql.contains("\"id\""));
4687		assert!(sql.contains("\"created_at\""));
4688		assert_eq!(values.len(), 1);
4689	}
4690
4691	#[test]
4692	fn test_insert_with_returning_all() {
4693		let builder = PostgresQueryBuilder::new();
4694		let mut stmt = Query::insert();
4695		stmt.into_table("users")
4696			.columns(["name"])
4697			.values_panic(["Alice"])
4698			.returning_all();
4699
4700		let (sql, values) = builder.build_insert(&stmt);
4701		assert!(sql.contains("RETURNING *"));
4702		assert_eq!(values.len(), 1);
4703	}
4704
4705	#[test]
4706	fn test_insert_from_subquery() {
4707		let builder = PostgresQueryBuilder::new();
4708
4709		// Create a SELECT subquery
4710		let select = Query::select()
4711			.column("name")
4712			.column("email")
4713			.from("temp_users")
4714			.to_owned();
4715
4716		// Create an INSERT with subquery
4717		let mut stmt = Query::insert();
4718		stmt.into_table("users")
4719			.columns(["name", "email"])
4720			.from_subquery(select);
4721
4722		let (sql, values) = builder.build_insert(&stmt);
4723		assert!(sql.contains("INSERT INTO \"users\""));
4724		assert!(sql.contains("\"name\", \"email\""));
4725		assert!(sql.contains("SELECT \"name\", \"email\" FROM \"temp_users\""));
4726		assert!(!sql.contains("VALUES"));
4727		assert_eq!(values.len(), 0);
4728	}
4729
4730	#[test]
4731	fn test_insert_from_subquery_with_where() {
4732		let builder = PostgresQueryBuilder::new();
4733
4734		// Create a SELECT subquery with WHERE clause
4735		let select = Query::select()
4736			.column("name")
4737			.column("email")
4738			.from("temp_users")
4739			.and_where(Expr::col("active").eq(true))
4740			.to_owned();
4741
4742		// Create an INSERT with subquery
4743		let mut stmt = Query::insert();
4744		stmt.into_table("users")
4745			.columns(["name", "email"])
4746			.from_subquery(select);
4747
4748		let (sql, values) = builder.build_insert(&stmt);
4749		assert!(sql.contains("INSERT INTO \"users\""));
4750		assert!(sql.contains("SELECT"));
4751		assert!(sql.contains("FROM \"temp_users\""));
4752		assert!(sql.contains("WHERE"));
4753		assert_eq!(values.len(), 1); // true value from WHERE clause
4754	}
4755
4756	#[test]
4757	fn test_update_basic() {
4758		let builder = PostgresQueryBuilder::new();
4759		let mut stmt = Query::update();
4760		stmt.table("users")
4761			.value("name", "Alice")
4762			.value("email", "alice@example.com");
4763
4764		let (sql, values) = builder.build_update(&stmt);
4765		assert_eq!(sql, "UPDATE \"users\" SET \"name\" = $1, \"email\" = $2");
4766		assert_eq!(values.len(), 2);
4767	}
4768
4769	#[test]
4770	fn test_update_with_where() {
4771		let builder = PostgresQueryBuilder::new();
4772		let mut stmt = Query::update();
4773		stmt.table("users")
4774			.value("active", false)
4775			.and_where(Expr::col("id").eq(1));
4776
4777		let (sql, values) = builder.build_update(&stmt);
4778		assert!(sql.contains("UPDATE"));
4779		assert!(sql.contains("SET"));
4780		assert!(sql.contains("WHERE"));
4781		assert_eq!(values.len(), 2); // false + 1
4782	}
4783
4784	#[test]
4785	fn test_update_with_returning() {
4786		let builder = PostgresQueryBuilder::new();
4787		let mut stmt = Query::update();
4788		stmt.table("users")
4789			.value("active", false)
4790			.and_where(Expr::col("id").eq(1))
4791			.returning(["id", "updated_at"]);
4792
4793		let (sql, values) = builder.build_update(&stmt);
4794		assert!(sql.contains("UPDATE"));
4795		assert!(sql.contains("RETURNING"));
4796		assert!(sql.contains("\"id\""));
4797		assert!(sql.contains("\"updated_at\""));
4798		assert_eq!(values.len(), 2);
4799	}
4800
4801	#[test]
4802	fn test_delete_basic() {
4803		let builder = PostgresQueryBuilder::new();
4804		let mut stmt = Query::delete();
4805		stmt.from_table("users")
4806			.and_where(Expr::col("active").eq(false));
4807
4808		let (sql, values) = builder.build_delete(&stmt);
4809		assert!(sql.contains("DELETE FROM"));
4810		assert!(sql.contains("\"users\""));
4811		assert!(sql.contains("WHERE"));
4812		assert_eq!(values.len(), 1); // false
4813	}
4814
4815	#[test]
4816	fn test_delete_no_where() {
4817		let builder = PostgresQueryBuilder::new();
4818		let mut stmt = Query::delete();
4819		stmt.from_table("users");
4820
4821		let (sql, values) = builder.build_delete(&stmt);
4822		assert_eq!(sql, "DELETE FROM \"users\"");
4823		assert_eq!(values.len(), 0);
4824	}
4825
4826	#[test]
4827	fn test_delete_with_returning() {
4828		let builder = PostgresQueryBuilder::new();
4829		let mut stmt = Query::delete();
4830		stmt.from_table("users")
4831			.and_where(Expr::col("id").eq(1))
4832			.returning(["id", "name"]);
4833
4834		let (sql, values) = builder.build_delete(&stmt);
4835		assert!(sql.contains("DELETE FROM"));
4836		assert!(sql.contains("RETURNING"));
4837		assert!(sql.contains("\"id\""));
4838		assert!(sql.contains("\"name\""));
4839		assert_eq!(values.len(), 1);
4840	}
4841
4842	#[test]
4843	fn test_delete_with_returning_all() {
4844		let builder = PostgresQueryBuilder::new();
4845		let mut stmt = Query::delete();
4846		stmt.from_table("users")
4847			.and_where(Expr::col("id").eq(1))
4848			.returning_all();
4849
4850		let (sql, values) = builder.build_delete(&stmt);
4851		assert!(sql.contains("RETURNING *"));
4852		assert_eq!(values.len(), 1);
4853	}
4854
4855	// JOIN tests
4856
4857	#[test]
4858	fn test_inner_join_simple() {
4859		let builder = PostgresQueryBuilder::new();
4860		let mut stmt = Query::select();
4861		stmt.column("users.name")
4862			.column("orders.amount")
4863			.from("users")
4864			.inner_join(
4865				"orders",
4866				Expr::col(("users", "id")).eq(Expr::col(("orders", "user_id"))),
4867			);
4868
4869		let (sql, _values) = builder.build_select(&stmt);
4870		assert!(sql.contains("FROM \"users\""));
4871		assert!(sql.contains("INNER JOIN \"orders\""));
4872		assert!(sql.contains("ON \"users\".\"id\" = \"orders\".\"user_id\""));
4873	}
4874
4875	#[test]
4876	fn test_left_join() {
4877		let builder = PostgresQueryBuilder::new();
4878		let mut stmt = Query::select();
4879		stmt.column("users.name")
4880			.column("profiles.bio")
4881			.from("users")
4882			.left_join(
4883				"profiles",
4884				Expr::col(("users", "id")).eq(Expr::col(("profiles", "user_id"))),
4885			);
4886
4887		let (sql, _values) = builder.build_select(&stmt);
4888		assert!(sql.contains("LEFT JOIN \"profiles\""));
4889		assert!(sql.contains("ON \"users\".\"id\" = \"profiles\".\"user_id\""));
4890	}
4891
4892	#[test]
4893	fn test_right_join() {
4894		let builder = PostgresQueryBuilder::new();
4895		let mut stmt = Query::select();
4896		stmt.column("users.name")
4897			.column("orders.amount")
4898			.from("users")
4899			.right_join(
4900				"orders",
4901				Expr::col(("users", "id")).eq(Expr::col(("orders", "user_id"))),
4902			);
4903
4904		let (sql, _values) = builder.build_select(&stmt);
4905		assert!(sql.contains("RIGHT JOIN \"orders\""));
4906		assert!(sql.contains("ON \"users\".\"id\" = \"orders\".\"user_id\""));
4907	}
4908
4909	#[test]
4910	fn test_full_outer_join() {
4911		let builder = PostgresQueryBuilder::new();
4912		let mut stmt = Query::select();
4913		stmt.column("users.name")
4914			.column("orders.amount")
4915			.from("users")
4916			.full_outer_join(
4917				"orders",
4918				Expr::col(("users", "id")).eq(Expr::col(("orders", "user_id"))),
4919			);
4920
4921		let (sql, _values) = builder.build_select(&stmt);
4922		assert!(sql.contains("FULL OUTER JOIN \"orders\""));
4923		assert!(sql.contains("ON \"users\".\"id\" = \"orders\".\"user_id\""));
4924	}
4925
4926	#[test]
4927	fn test_cross_join() {
4928		let builder = PostgresQueryBuilder::new();
4929		let mut stmt = Query::select();
4930		stmt.column("users.name")
4931			.column("roles.title")
4932			.from("users")
4933			.cross_join("roles");
4934
4935		let (sql, _values) = builder.build_select(&stmt);
4936		assert!(sql.contains("CROSS JOIN \"roles\""));
4937		assert!(!sql.contains("ON"));
4938	}
4939
4940	#[test]
4941	fn test_multiple_joins() {
4942		let builder = PostgresQueryBuilder::new();
4943		let mut stmt = Query::select();
4944		stmt.column("users.name")
4945			.column("orders.amount")
4946			.column("products.title")
4947			.from("users")
4948			.inner_join(
4949				"orders",
4950				Expr::col(("users", "id")).eq(Expr::col(("orders", "user_id"))),
4951			)
4952			.inner_join(
4953				"products",
4954				Expr::col(("orders", "product_id")).eq(Expr::col(("products", "id"))),
4955			);
4956
4957		let (sql, _values) = builder.build_select(&stmt);
4958		assert!(sql.contains("INNER JOIN \"orders\""));
4959		assert!(sql.contains("INNER JOIN \"products\""));
4960		assert!(sql.contains("\"users\".\"id\" = \"orders\".\"user_id\""));
4961		assert!(sql.contains("\"orders\".\"product_id\" = \"products\".\"id\""));
4962	}
4963
4964	#[test]
4965	fn test_join_with_complex_condition() {
4966		let builder = PostgresQueryBuilder::new();
4967		let mut stmt = Query::select();
4968		stmt.column("users.name")
4969			.column("orders.amount")
4970			.from("users")
4971			.inner_join(
4972				"orders",
4973				Expr::col(("users", "id"))
4974					.eq(Expr::col(("orders", "user_id")))
4975					.and(Expr::col(("orders", "status")).eq("active")),
4976			);
4977
4978		let (sql, values) = builder.build_select(&stmt);
4979		assert!(sql.contains("INNER JOIN \"orders\""));
4980		assert!(sql.contains("ON"));
4981		assert!(sql.contains("\"users\".\"id\" = \"orders\".\"user_id\""));
4982		assert!(sql.contains("AND"));
4983		assert!(sql.contains("\"orders\".\"status\" = $"));
4984		assert_eq!(values.len(), 1);
4985	}
4986
4987	// GROUP BY / HAVING tests
4988
4989	#[test]
4990	fn test_group_by_single_column() {
4991		let builder = PostgresQueryBuilder::new();
4992		let mut stmt = Query::select();
4993		stmt.column("category")
4994			.from("products")
4995			.group_by("category");
4996
4997		let (sql, _values) = builder.build_select(&stmt);
4998		assert!(sql.contains("GROUP BY \"category\""));
4999	}
5000
5001	#[test]
5002	fn test_group_by_multiple_columns() {
5003		let builder = PostgresQueryBuilder::new();
5004		let mut stmt = Query::select();
5005		stmt.column("category")
5006			.column("brand")
5007			.from("products")
5008			.group_by("category")
5009			.group_by("brand");
5010
5011		let (sql, _values) = builder.build_select(&stmt);
5012		assert!(sql.contains("GROUP BY \"category\", \"brand\""));
5013	}
5014
5015	#[test]
5016	fn test_group_by_with_count() {
5017		use crate::expr::SimpleExpr;
5018		use crate::types::{ColumnRef, IntoIden};
5019
5020		let builder = PostgresQueryBuilder::new();
5021		let mut stmt = Query::select();
5022		stmt.column("category")
5023			.expr(SimpleExpr::FunctionCall(
5024				"COUNT".into_iden(),
5025				vec![SimpleExpr::Column(ColumnRef::Asterisk)],
5026			))
5027			.from("products")
5028			.group_by("category");
5029
5030		let (sql, _values) = builder.build_select(&stmt);
5031		assert!(sql.contains("COUNT(*)"));
5032		assert!(sql.contains("GROUP BY \"category\""));
5033	}
5034
5035	#[test]
5036	fn test_having_simple() {
5037		use crate::expr::SimpleExpr;
5038		use crate::types::{BinOper, ColumnRef, IntoIden};
5039
5040		let builder = PostgresQueryBuilder::new();
5041		let mut stmt = Query::select();
5042		let count_expr = SimpleExpr::FunctionCall(
5043			"COUNT".into_iden(),
5044			vec![SimpleExpr::Column(ColumnRef::Asterisk)],
5045		);
5046
5047		stmt.column("category")
5048			.expr(count_expr.clone())
5049			.from("products")
5050			.group_by("category")
5051			.and_having(SimpleExpr::Binary(
5052				Box::new(count_expr),
5053				BinOper::GreaterThan,
5054				Box::new(SimpleExpr::Value(5.into())),
5055			));
5056
5057		let (sql, values) = builder.build_select(&stmt);
5058		assert!(sql.contains("GROUP BY \"category\""));
5059		assert!(sql.contains("HAVING"));
5060		assert!(sql.contains("COUNT(*)"));
5061		assert!(sql.contains(">"));
5062		assert_eq!(values.len(), 1);
5063	}
5064
5065	#[test]
5066	fn test_group_by_having_with_sum() {
5067		use crate::expr::SimpleExpr;
5068		use crate::types::{BinOper, ColumnRef, IntoIden};
5069
5070		let builder = PostgresQueryBuilder::new();
5071		let mut stmt = Query::select();
5072		let sum_expr = SimpleExpr::FunctionCall(
5073			"SUM".into_iden(),
5074			vec![SimpleExpr::Column(ColumnRef::column("amount"))],
5075		);
5076
5077		stmt.column("user_id")
5078			.expr(sum_expr.clone())
5079			.from("orders")
5080			.group_by("user_id")
5081			.and_having(SimpleExpr::Binary(
5082				Box::new(sum_expr),
5083				BinOper::GreaterThan,
5084				Box::new(SimpleExpr::Value(1000.into())),
5085			));
5086
5087		let (sql, values) = builder.build_select(&stmt);
5088		assert!(sql.contains("SUM(\"amount\")"));
5089		assert!(sql.contains("GROUP BY \"user_id\""));
5090		assert!(sql.contains("HAVING"));
5091		assert_eq!(values.len(), 1);
5092	}
5093
5094	#[test]
5095	fn test_select_distinct() {
5096		let builder = PostgresQueryBuilder::new();
5097		let mut stmt = Query::select();
5098		stmt.column("category").from("products").distinct();
5099
5100		let (sql, _values) = builder.build_select(&stmt);
5101		assert!(sql.starts_with("SELECT DISTINCT"));
5102		assert!(sql.contains("\"category\""));
5103		assert!(sql.contains("FROM \"products\""));
5104	}
5105
5106	#[test]
5107	fn test_select_distinct_on() {
5108		let builder = PostgresQueryBuilder::new();
5109		let mut stmt = Query::select();
5110		stmt.column("id")
5111			.column("name")
5112			.from("users")
5113			.distinct_on(vec!["category"])
5114			.order_by("category", crate::types::Order::Asc);
5115
5116		let (sql, _values) = builder.build_select(&stmt);
5117		assert!(sql.contains("SELECT DISTINCT ON (\"category\")"));
5118		assert!(sql.contains("\"id\""));
5119		assert!(sql.contains("\"name\""));
5120		assert!(sql.contains("ORDER BY \"category\" ASC"));
5121	}
5122
5123	#[test]
5124	#[should_panic(expected = "PostgreSQL does not support DISTINCT ROW")]
5125	fn test_select_distinct_row_panics() {
5126		use crate::query::SelectDistinct;
5127
5128		let builder = PostgresQueryBuilder::new();
5129		let mut stmt = Query::select();
5130		stmt.column("name").from("products");
5131		stmt.distinct = Some(SelectDistinct::DistinctRow);
5132
5133		let _ = builder.build_select(&stmt);
5134	}
5135
5136	#[test]
5137	fn test_select_union() {
5138		let builder = PostgresQueryBuilder::new();
5139		let mut stmt1 = Query::select();
5140		stmt1.column("id").from("users");
5141
5142		let mut stmt2 = Query::select();
5143		stmt2.column("id").from("customers");
5144
5145		stmt1.union(stmt2);
5146
5147		let (sql, _values) = builder.build_select(&stmt1);
5148		assert!(sql.contains("SELECT \"id\" FROM \"users\""));
5149		assert!(sql.contains("UNION SELECT \"id\" FROM \"customers\""));
5150	}
5151
5152	#[test]
5153	fn test_select_union_all() {
5154		let builder = PostgresQueryBuilder::new();
5155		let mut stmt1 = Query::select();
5156		stmt1.column("name").from("products");
5157
5158		let mut stmt2 = Query::select();
5159		stmt2.column("name").from("archived_products");
5160
5161		stmt1.union_all(stmt2);
5162
5163		let (sql, _values) = builder.build_select(&stmt1);
5164		assert!(sql.contains("SELECT \"name\" FROM \"products\""));
5165		assert!(sql.contains("UNION ALL SELECT \"name\" FROM \"archived_products\""));
5166	}
5167
5168	#[test]
5169	fn test_select_intersect() {
5170		let builder = PostgresQueryBuilder::new();
5171		let mut stmt1 = Query::select();
5172		stmt1.column("email").from("subscribers");
5173
5174		let mut stmt2 = Query::select();
5175		stmt2.column("email").from("customers");
5176
5177		stmt1.intersect(stmt2);
5178
5179		let (sql, _values) = builder.build_select(&stmt1);
5180		assert!(sql.contains("SELECT \"email\" FROM \"subscribers\""));
5181		assert!(sql.contains("INTERSECT SELECT \"email\" FROM \"customers\""));
5182	}
5183
5184	#[test]
5185	fn test_select_except() {
5186		let builder = PostgresQueryBuilder::new();
5187		let mut stmt1 = Query::select();
5188		stmt1.column("id").from("all_users");
5189
5190		let mut stmt2 = Query::select();
5191		stmt2.column("id").from("banned_users");
5192
5193		stmt1.except(stmt2);
5194
5195		let (sql, _values) = builder.build_select(&stmt1);
5196		assert!(sql.contains("SELECT \"id\" FROM \"all_users\""));
5197		assert!(sql.contains("EXCEPT SELECT \"id\" FROM \"banned_users\""));
5198	}
5199
5200	#[test]
5201	fn test_select_multiple_unions() {
5202		let builder = PostgresQueryBuilder::new();
5203		let mut stmt1 = Query::select();
5204		stmt1.column("id").from("table1");
5205
5206		let mut stmt2 = Query::select();
5207		stmt2.column("id").from("table2");
5208
5209		let mut stmt3 = Query::select();
5210		stmt3.column("id").from("table3");
5211
5212		stmt1.union(stmt2);
5213		stmt1.union_all(stmt3);
5214
5215		let (sql, _values) = builder.build_select(&stmt1);
5216		assert!(sql.contains("SELECT \"id\" FROM \"table1\""));
5217		assert!(sql.contains("UNION SELECT \"id\" FROM \"table2\""));
5218		assert!(sql.contains("UNION ALL SELECT \"id\" FROM \"table3\""));
5219	}
5220
5221	#[test]
5222	fn test_select_exists_subquery() {
5223		use crate::expr::Expr;
5224
5225		let builder = PostgresQueryBuilder::new();
5226		let mut stmt = Query::select();
5227
5228		// Main query
5229		stmt.column("name").from("users");
5230
5231		// Subquery
5232		let mut subquery = Query::select();
5233		subquery
5234			.column("id")
5235			.from("orders")
5236			.and_where(Expr::col(("orders", "user_id")).eq(Expr::col(("users", "id"))));
5237
5238		// Add EXISTS condition
5239		stmt.and_where(Expr::exists(subquery));
5240
5241		let (sql, _values) = builder.build_select(&stmt);
5242		assert!(sql.contains("SELECT \"name\" FROM \"users\""));
5243		assert!(sql.contains("WHERE"));
5244		assert!(sql.contains("EXISTS"));
5245		assert!(sql.contains("SELECT \"id\" FROM \"orders\""));
5246	}
5247
5248	#[test]
5249	fn test_select_in_subquery() {
5250		use crate::expr::Expr;
5251
5252		let builder = PostgresQueryBuilder::new();
5253		let mut stmt = Query::select();
5254
5255		// Main query
5256		stmt.column("name").from("users");
5257
5258		// Subquery
5259		let mut subquery = Query::select();
5260		subquery.column("user_id").from("premium_users");
5261
5262		// Add IN condition
5263		stmt.and_where(Expr::col("id").in_subquery(subquery));
5264
5265		let (sql, _values) = builder.build_select(&stmt);
5266		assert!(sql.contains("SELECT \"name\" FROM \"users\""));
5267		assert!(sql.contains("WHERE"));
5268		assert!(sql.contains("\"id\""));
5269		assert!(sql.contains("IN"));
5270		assert!(sql.contains("SELECT \"user_id\" FROM \"premium_users\""));
5271	}
5272
5273	#[test]
5274	fn test_select_not_exists_subquery() {
5275		use crate::expr::Expr;
5276
5277		let builder = PostgresQueryBuilder::new();
5278		let mut stmt = Query::select();
5279
5280		// Main query
5281		stmt.column("email").from("users");
5282
5283		// Subquery
5284		let mut subquery = Query::select();
5285		subquery
5286			.column("id")
5287			.from("banned_users")
5288			.and_where(Expr::col(("banned_users", "user_id")).eq(Expr::col(("users", "id"))));
5289
5290		// Add NOT EXISTS condition
5291		stmt.and_where(Expr::not_exists(subquery));
5292
5293		let (sql, _values) = builder.build_select(&stmt);
5294		assert!(sql.contains("SELECT \"email\" FROM \"users\""));
5295		assert!(sql.contains("WHERE"));
5296		assert!(sql.contains("NOT EXISTS"));
5297		assert!(sql.contains("SELECT \"id\" FROM \"banned_users\""));
5298	}
5299
5300	// --- Phase 5: Subquery Edge Case Tests ---
5301
5302	#[test]
5303	fn test_not_in_subquery() {
5304		let builder = PostgresQueryBuilder::new();
5305
5306		let mut subquery = Query::select();
5307		subquery
5308			.column("user_id")
5309			.from("blocked_users")
5310			.and_where(Expr::col("reason").eq("spam"));
5311
5312		let mut stmt = Query::select();
5313		stmt.column("name")
5314			.from("users")
5315			.and_where(Expr::col("id").not_in_subquery(subquery));
5316
5317		let (sql, values) = builder.build_select(&stmt);
5318		assert!(sql.contains("NOT IN"));
5319		assert!(sql.contains("SELECT \"user_id\" FROM \"blocked_users\""));
5320		assert!(sql.contains("\"reason\" = $"));
5321		assert_eq!(values.len(), 1);
5322	}
5323
5324	#[test]
5325	fn test_subquery_in_select_list() {
5326		let builder = PostgresQueryBuilder::new();
5327
5328		let mut subquery = Query::select();
5329		subquery
5330			.expr(Expr::col("count"))
5331			.from("order_counts")
5332			.and_where(Expr::col(("order_counts", "user_id")).eq(Expr::col(("users", "id"))));
5333
5334		let mut stmt = Query::select();
5335		stmt.column("name")
5336			.expr(Expr::subquery(subquery))
5337			.from("users");
5338
5339		let (sql, _values) = builder.build_select(&stmt);
5340		assert!(sql.contains("\"name\""));
5341		assert!(sql.contains("(SELECT \"count\" FROM \"order_counts\""));
5342		assert!(sql.contains("\"order_counts\".\"user_id\" = \"users\".\"id\""));
5343	}
5344
5345	#[test]
5346	fn test_multiple_exists_conditions() {
5347		let builder = PostgresQueryBuilder::new();
5348
5349		let mut sub1 = Query::select();
5350		sub1.column("id")
5351			.from("orders")
5352			.and_where(Expr::col(("orders", "user_id")).eq(Expr::col(("users", "id"))));
5353
5354		let mut sub2 = Query::select();
5355		sub2.column("id")
5356			.from("reviews")
5357			.and_where(Expr::col(("reviews", "user_id")).eq(Expr::col(("users", "id"))));
5358
5359		let mut stmt = Query::select();
5360		stmt.column("name")
5361			.from("users")
5362			.and_where(Expr::exists(sub1))
5363			.and_where(Expr::exists(sub2));
5364
5365		let (sql, _values) = builder.build_select(&stmt);
5366		assert!(sql.contains("EXISTS (SELECT \"id\" FROM \"orders\""));
5367		assert!(sql.contains("EXISTS (SELECT \"id\" FROM \"reviews\""));
5368	}
5369
5370	#[test]
5371	fn test_nested_subquery() {
5372		let builder = PostgresQueryBuilder::new();
5373
5374		let mut inner_subquery = Query::select();
5375		inner_subquery
5376			.column("department_id")
5377			.from("top_departments")
5378			.and_where(Expr::col("revenue").gt(1000000));
5379
5380		let mut outer_subquery = Query::select();
5381		outer_subquery
5382			.column("id")
5383			.from("employees")
5384			.and_where(Expr::col("department_id").in_subquery(inner_subquery));
5385
5386		let mut stmt = Query::select();
5387		stmt.column("name")
5388			.from("users")
5389			.and_where(Expr::col("employee_id").in_subquery(outer_subquery));
5390
5391		let (sql, values) = builder.build_select(&stmt);
5392		assert!(sql.contains("IN (SELECT \"id\" FROM \"employees\""));
5393		assert!(sql.contains("IN (SELECT \"department_id\" FROM \"top_departments\""));
5394		assert!(sql.contains("\"revenue\" > $"));
5395		assert_eq!(values.len(), 1);
5396	}
5397
5398	#[test]
5399	fn test_subquery_with_complex_where() {
5400		let builder = PostgresQueryBuilder::new();
5401
5402		let mut subquery = Query::select();
5403		subquery
5404			.column("product_id")
5405			.from("inventory")
5406			.and_where(Expr::col("quantity").gt(0))
5407			.and_where(Expr::col("warehouse").eq("main"))
5408			.and_where(Expr::col("status").eq("available"));
5409
5410		let mut stmt = Query::select();
5411		stmt.column("name")
5412			.column("price")
5413			.from("products")
5414			.and_where(Expr::col("id").in_subquery(subquery))
5415			.and_where(Expr::col("active").eq(true));
5416
5417		let (sql, values) = builder.build_select(&stmt);
5418		assert!(sql.contains("IN (SELECT \"product_id\" FROM \"inventory\""));
5419		assert!(sql.contains("\"quantity\" > $"));
5420		assert!(sql.contains("\"warehouse\" = $"));
5421		assert!(sql.contains("\"status\" = $"));
5422		assert!(sql.contains("\"active\" = $"));
5423		assert_eq!(values.len(), 4); // 0, "main", "available", true
5424	}
5425
5426	#[test]
5427	fn test_from_subquery_preserves_parameter_values() {
5428		let builder = PostgresQueryBuilder::new();
5429
5430		// Arrange
5431		let mut subquery = Query::select();
5432		subquery
5433			.column("id")
5434			.column("name")
5435			.from("users")
5436			.and_where(Expr::col("active").eq(true))
5437			.and_where(Expr::col("role").eq("admin"));
5438
5439		let mut stmt = Query::select();
5440		stmt.column("name")
5441			.from_subquery(subquery, Alias::new("active_admins"))
5442			.and_where(Expr::col("name").like("A%"));
5443
5444		// Act
5445		let (sql, values) = builder.build_select(&stmt);
5446
5447		// Assert
5448		assert!(sql.contains("(SELECT"));
5449		assert!(sql.contains(") AS \"active_admins\""));
5450		// Subquery params (true, "admin") + outer param ("A%") = 3 values
5451		assert_eq!(values.len(), 3);
5452	}
5453
5454	#[test]
5455	fn test_from_subquery_postgres_placeholder_renumbering() {
5456		let builder = PostgresQueryBuilder::new();
5457
5458		// Arrange: outer query has params before the FROM subquery
5459		let mut subquery = Query::select();
5460		subquery
5461			.column("id")
5462			.from("users")
5463			.and_where(Expr::col("role").eq("admin"));
5464
5465		let mut stmt = Query::select();
5466		stmt.column("name")
5467			.from_subquery(subquery, Alias::new("sub"))
5468			.and_where(Expr::col("status").eq("active"));
5469
5470		// Act
5471		let (sql, values) = builder.build_select(&stmt);
5472
5473		// Assert: subquery param should be $1, outer param should be $2
5474		assert!(sql.contains("$1"));
5475		assert!(sql.contains("$2"));
5476		assert_eq!(values.len(), 2);
5477	}
5478
5479	// --- Phase 5: NULL Handling Tests ---
5480
5481	#[test]
5482	fn test_where_is_null() {
5483		let builder = PostgresQueryBuilder::new();
5484		let mut stmt = Query::select();
5485		stmt.column("name")
5486			.from("users")
5487			.and_where(Expr::col("deleted_at").is_null());
5488
5489		let (sql, values) = builder.build_select(&stmt);
5490		assert!(sql.contains("\"deleted_at\" IS"));
5491		assert!(sql.to_uppercase().contains("NULL"));
5492		assert_eq!(values.len(), 0);
5493	}
5494
5495	#[test]
5496	fn test_where_is_not_null() {
5497		let builder = PostgresQueryBuilder::new();
5498		let mut stmt = Query::select();
5499		stmt.column("name")
5500			.from("users")
5501			.and_where(Expr::col("email").is_not_null());
5502
5503		let (sql, values) = builder.build_select(&stmt);
5504		assert!(sql.contains("\"email\" IS NOT"));
5505		assert!(sql.to_uppercase().contains("NULL"));
5506		assert_eq!(values.len(), 0);
5507	}
5508
5509	#[test]
5510	fn test_is_null_combined_with_other_conditions() {
5511		let builder = PostgresQueryBuilder::new();
5512		let mut stmt = Query::select();
5513		stmt.column("name")
5514			.from("users")
5515			.and_where(Expr::col("active").eq(true))
5516			.and_where(Expr::col("deleted_at").is_null())
5517			.and_where(Expr::col("email").is_not_null());
5518
5519		let (sql, values) = builder.build_select(&stmt);
5520		assert!(sql.contains("\"active\" = $"));
5521		assert!(sql.contains("\"deleted_at\" IS"));
5522		assert!(sql.contains("\"email\" IS NOT"));
5523		assert_eq!(values.len(), 1);
5524	}
5525
5526	#[test]
5527	fn test_is_null_with_join() {
5528		let builder = PostgresQueryBuilder::new();
5529		let mut stmt = Query::select();
5530		stmt.column(("users", "name"))
5531			.from("users")
5532			.left_join(
5533				"profiles",
5534				Expr::col(("users", "id")).eq(Expr::col(("profiles", "user_id"))),
5535			)
5536			.and_where(Expr::col(("profiles", "id")).is_null());
5537
5538		let (sql, values) = builder.build_select(&stmt);
5539		assert!(sql.contains("LEFT JOIN \"profiles\""));
5540		assert!(sql.contains("\"profiles\".\"id\" IS"));
5541		assert_eq!(values.len(), 0);
5542	}
5543
5544	// --- Phase 5: Complex WHERE Clause Tests ---
5545	#[test]
5546	fn test_where_or_condition() {
5547		use crate::expr::Condition;
5548
5549		let builder = PostgresQueryBuilder::new();
5550		let mut stmt = Query::select();
5551		stmt.column("name").from("users").cond_where(
5552			Condition::any()
5553				.add(Expr::col("status").eq("active"))
5554				.add(Expr::col("status").eq("pending")),
5555		);
5556
5557		let (sql, values) = builder.build_select(&stmt);
5558		assert!(sql.contains("\"status\" = $"));
5559		assert!(sql.contains(" OR "));
5560		assert_eq!(values.len(), 2);
5561	}
5562
5563	#[test]
5564	fn test_where_between() {
5565		let builder = PostgresQueryBuilder::new();
5566		let mut stmt = Query::select();
5567		stmt.column("name")
5568			.from("products")
5569			.and_where(Expr::col("price").between(100, 500));
5570
5571		let (sql, values) = builder.build_select(&stmt);
5572		assert!(sql.contains("\"price\" BETWEEN $"));
5573		assert!(sql.contains("AND $"));
5574		assert_eq!(values.len(), 2);
5575	}
5576
5577	#[test]
5578	fn test_where_not_between() {
5579		let builder = PostgresQueryBuilder::new();
5580		let mut stmt = Query::select();
5581		stmt.column("name")
5582			.from("products")
5583			.and_where(Expr::col("price").not_between(0, 10));
5584
5585		let (sql, values) = builder.build_select(&stmt);
5586		assert!(sql.contains("\"price\" NOT BETWEEN $"));
5587		assert!(sql.contains("AND $"));
5588		assert_eq!(values.len(), 2);
5589	}
5590
5591	#[test]
5592	fn test_where_like() {
5593		let builder = PostgresQueryBuilder::new();
5594		let mut stmt = Query::select();
5595		stmt.column("name")
5596			.from("users")
5597			.and_where(Expr::col("email").like("%@gmail.com"));
5598
5599		let (sql, values) = builder.build_select(&stmt);
5600		assert!(sql.contains("\"email\" LIKE $"));
5601		assert_eq!(values.len(), 1);
5602	}
5603
5604	#[test]
5605	fn test_where_in_values() {
5606		let builder = PostgresQueryBuilder::new();
5607		let mut stmt = Query::select();
5608		stmt.column("name")
5609			.from("users")
5610			.and_where(Expr::col("role").is_in(vec!["admin", "moderator", "editor"]));
5611
5612		let (sql, values) = builder.build_select(&stmt);
5613		assert!(sql.contains("\"role\" IN"));
5614		assert_eq!(values.len(), 3);
5615	}
5616
5617	#[test]
5618	fn test_insert_with_null_value() {
5619		use crate::value::Value;
5620
5621		let builder = PostgresQueryBuilder::new();
5622		let mut stmt = Query::insert();
5623		stmt.into_table("users")
5624			.columns(vec!["name", "email", "phone"])
5625			.values(vec![
5626				Value::String(Some(Box::new("John".to_string()))),
5627				Value::String(Some(Box::new("john@example.com".to_string()))),
5628				Value::String(None),
5629			])
5630			.unwrap();
5631
5632		let (sql, values) = builder.build_insert(&stmt);
5633		assert!(sql.contains("INSERT INTO \"users\""));
5634		assert!(sql.contains("\"name\""));
5635		assert!(sql.contains("\"email\""));
5636		assert!(sql.contains("\"phone\""));
5637		// NULL values are inlined directly, not parameterized
5638		assert!(sql.contains("NULL"));
5639		assert_eq!(values.len(), 2);
5640	}
5641
5642	#[test]
5643	fn test_select_with_single_cte() {
5644		let builder = PostgresQueryBuilder::new();
5645
5646		// Create CTE query
5647		let mut cte_query = Query::select();
5648		cte_query
5649			.column("id")
5650			.column("name")
5651			.from("employees")
5652			.and_where(Expr::col("department").eq("Engineering"));
5653
5654		// Main query using the CTE
5655		let mut stmt = Query::select();
5656		stmt.with_cte("eng_employees", cte_query)
5657			.column("name")
5658			.from("eng_employees");
5659
5660		let (sql, _values) = builder.build_select(&stmt);
5661		assert!(sql.contains("WITH"));
5662		assert!(sql.contains("\"eng_employees\""));
5663		assert!(sql.contains("AS"));
5664		assert!(sql.contains("SELECT \"id\", \"name\" FROM \"employees\""));
5665		assert!(sql.contains("SELECT \"name\" FROM \"eng_employees\""));
5666	}
5667
5668	#[test]
5669	fn test_select_with_multiple_ctes() {
5670		let builder = PostgresQueryBuilder::new();
5671
5672		// First CTE
5673		let mut cte1 = Query::select();
5674		cte1.column("id")
5675			.column("name")
5676			.from("employees")
5677			.and_where(Expr::col("department").eq("Engineering"));
5678
5679		// Second CTE
5680		let mut cte2 = Query::select();
5681		cte2.column("id")
5682			.column("name")
5683			.from("employees")
5684			.and_where(Expr::col("department").eq("Sales"));
5685
5686		// Main query using both CTEs
5687		let mut stmt = Query::select();
5688		stmt.with_cte("eng_emp", cte1)
5689			.with_cte("sales_emp", cte2)
5690			.column("name")
5691			.from("eng_emp");
5692
5693		let (sql, _values) = builder.build_select(&stmt);
5694		assert!(sql.contains("WITH"));
5695		assert!(sql.contains("\"eng_emp\""));
5696		assert!(sql.contains("\"sales_emp\""));
5697		assert!(sql.contains("AS"));
5698		// Both CTEs should be present, and there should be a comma between them
5699		assert!(sql.contains("\"eng_emp\" AS"));
5700		assert!(sql.contains("\"sales_emp\" AS"));
5701	}
5702
5703	#[test]
5704	fn test_select_with_recursive_cte() {
5705		let builder = PostgresQueryBuilder::new();
5706
5707		// Recursive CTE for organizational hierarchy
5708		let mut cte_query = Query::select();
5709		cte_query
5710			.column("id")
5711			.column("name")
5712			.column("manager_id")
5713			.from("employees");
5714
5715		// Main query using recursive CTE
5716		let mut stmt = Query::select();
5717		stmt.with_recursive_cte("employee_hierarchy", cte_query)
5718			.column("name")
5719			.from("employee_hierarchy");
5720
5721		let (sql, _values) = builder.build_select(&stmt);
5722		assert!(sql.contains("WITH RECURSIVE"));
5723		assert!(sql.contains("\"employee_hierarchy\""));
5724		assert!(sql.contains("AS"));
5725		assert!(sql.contains("SELECT \"id\", \"name\", \"manager_id\" FROM \"employees\""));
5726		assert!(sql.contains("SELECT \"name\" FROM \"employee_hierarchy\""));
5727	}
5728
5729	// Window function tests
5730
5731	#[test]
5732	fn test_window_row_number_with_partition_and_order() {
5733		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
5734
5735		let builder = PostgresQueryBuilder::new();
5736		let mut stmt = Query::select();
5737
5738		let window = WindowStatement {
5739			partition_by: vec![Expr::col("department").into_simple_expr()],
5740			order_by: vec![OrderExpr {
5741				expr: OrderExprKind::Column("salary".into_iden()),
5742				order: Order::Desc,
5743				nulls: None,
5744			}],
5745			frame: None,
5746		};
5747
5748		stmt.expr(Expr::row_number().over(window))
5749			.column("name")
5750			.from("employees");
5751
5752		let (sql, _values) = builder.build_select(&stmt);
5753		assert_eq!(
5754			sql,
5755			r#"SELECT ROW_NUMBER() OVER ( PARTITION BY "department" ORDER BY "salary" DESC ), "name" FROM "employees""#
5756		);
5757	}
5758
5759	#[test]
5760	fn test_window_row_number_order_only() {
5761		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
5762
5763		let builder = PostgresQueryBuilder::new();
5764		let mut stmt = Query::select();
5765
5766		let window = WindowStatement {
5767			partition_by: vec![],
5768			order_by: vec![OrderExpr {
5769				expr: OrderExprKind::Column("id".into_iden()),
5770				order: Order::Asc,
5771				nulls: None,
5772			}],
5773			frame: None,
5774		};
5775
5776		stmt.expr(Expr::row_number().over(window)).from("users");
5777
5778		let (sql, _values) = builder.build_select(&stmt);
5779		assert_eq!(
5780			sql,
5781			r#"SELECT ROW_NUMBER() OVER ( ORDER BY "id" ASC ) FROM "users""#
5782		);
5783	}
5784
5785	#[test]
5786	fn test_window_rank_basic() {
5787		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
5788
5789		let builder = PostgresQueryBuilder::new();
5790		let mut stmt = Query::select();
5791
5792		let window = WindowStatement {
5793			partition_by: vec![],
5794			order_by: vec![OrderExpr {
5795				expr: OrderExprKind::Column("score".into_iden()),
5796				order: Order::Desc,
5797				nulls: None,
5798			}],
5799			frame: None,
5800		};
5801
5802		stmt.expr(Expr::rank().over(window))
5803			.column("name")
5804			.from("students");
5805
5806		let (sql, _values) = builder.build_select(&stmt);
5807		assert_eq!(
5808			sql,
5809			r#"SELECT RANK() OVER ( ORDER BY "score" DESC ), "name" FROM "students""#
5810		);
5811	}
5812
5813	#[test]
5814	fn test_window_rank_with_partition() {
5815		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
5816
5817		let builder = PostgresQueryBuilder::new();
5818		let mut stmt = Query::select();
5819
5820		let window = WindowStatement {
5821			partition_by: vec![Expr::col("class").into_simple_expr()],
5822			order_by: vec![OrderExpr {
5823				expr: OrderExprKind::Column("score".into_iden()),
5824				order: Order::Desc,
5825				nulls: None,
5826			}],
5827			frame: None,
5828		};
5829
5830		stmt.expr(Expr::rank().over(window))
5831			.column("name")
5832			.from("students");
5833
5834		let (sql, _values) = builder.build_select(&stmt);
5835		assert_eq!(
5836			sql,
5837			r#"SELECT RANK() OVER ( PARTITION BY "class" ORDER BY "score" DESC ), "name" FROM "students""#
5838		);
5839	}
5840
5841	#[test]
5842	fn test_window_dense_rank_basic() {
5843		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
5844
5845		let builder = PostgresQueryBuilder::new();
5846		let mut stmt = Query::select();
5847
5848		let window = WindowStatement {
5849			partition_by: vec![],
5850			order_by: vec![OrderExpr {
5851				expr: OrderExprKind::Column("points".into_iden()),
5852				order: Order::Desc,
5853				nulls: None,
5854			}],
5855			frame: None,
5856		};
5857
5858		stmt.expr(Expr::dense_rank().over(window))
5859			.column("player")
5860			.from("scores");
5861
5862		let (sql, _values) = builder.build_select(&stmt);
5863		assert_eq!(
5864			sql,
5865			r#"SELECT DENSE_RANK() OVER ( ORDER BY "points" DESC ), "player" FROM "scores""#
5866		);
5867	}
5868
5869	#[test]
5870	fn test_window_dense_rank_with_partition() {
5871		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
5872
5873		let builder = PostgresQueryBuilder::new();
5874		let mut stmt = Query::select();
5875
5876		let window = WindowStatement {
5877			partition_by: vec![Expr::col("league").into_simple_expr()],
5878			order_by: vec![OrderExpr {
5879				expr: OrderExprKind::Column("points".into_iden()),
5880				order: Order::Desc,
5881				nulls: None,
5882			}],
5883			frame: None,
5884		};
5885
5886		stmt.expr(Expr::dense_rank().over(window))
5887			.column("player")
5888			.from("scores");
5889
5890		let (sql, _values) = builder.build_select(&stmt);
5891		assert_eq!(
5892			sql,
5893			r#"SELECT DENSE_RANK() OVER ( PARTITION BY "league" ORDER BY "points" DESC ), "player" FROM "scores""#
5894		);
5895	}
5896
5897	#[test]
5898	fn test_window_ntile_four_buckets() {
5899		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
5900
5901		let builder = PostgresQueryBuilder::new();
5902		let mut stmt = Query::select();
5903
5904		let window = WindowStatement {
5905			partition_by: vec![],
5906			order_by: vec![OrderExpr {
5907				expr: OrderExprKind::Column("salary".into_iden()),
5908				order: Order::Asc,
5909				nulls: None,
5910			}],
5911			frame: None,
5912		};
5913
5914		stmt.expr(Expr::ntile(4).over(window))
5915			.column("name")
5916			.from("employees");
5917
5918		let (sql, _values) = builder.build_select(&stmt);
5919		assert_eq!(
5920			sql,
5921			r#"SELECT NTILE($1) OVER ( ORDER BY "salary" ASC ), "name" FROM "employees""#
5922		);
5923	}
5924
5925	#[test]
5926	fn test_window_ntile_custom_buckets() {
5927		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
5928
5929		let builder = PostgresQueryBuilder::new();
5930		let mut stmt = Query::select();
5931
5932		let window = WindowStatement {
5933			partition_by: vec![Expr::col("department").into_simple_expr()],
5934			order_by: vec![OrderExpr {
5935				expr: OrderExprKind::Column("salary".into_iden()),
5936				order: Order::Desc,
5937				nulls: None,
5938			}],
5939			frame: None,
5940		};
5941
5942		stmt.expr(Expr::ntile(3).over(window))
5943			.column("name")
5944			.from("employees");
5945
5946		let (sql, _values) = builder.build_select(&stmt);
5947		assert_eq!(
5948			sql,
5949			r#"SELECT NTILE($1) OVER ( PARTITION BY "department" ORDER BY "salary" DESC ), "name" FROM "employees""#
5950		);
5951	}
5952
5953	#[test]
5954	fn test_window_lead_basic() {
5955		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
5956
5957		let builder = PostgresQueryBuilder::new();
5958		let mut stmt = Query::select();
5959
5960		let window = WindowStatement {
5961			partition_by: vec![],
5962			order_by: vec![OrderExpr {
5963				expr: OrderExprKind::Column("date".into_iden()),
5964				order: Order::Asc,
5965				nulls: None,
5966			}],
5967			frame: None,
5968		};
5969
5970		stmt.expr(Expr::lead(Expr::col("price").into_simple_expr(), None, None).over(window))
5971			.column("date")
5972			.from("stocks");
5973
5974		let (sql, values) = builder.build_select(&stmt);
5975		assert_eq!(
5976			sql,
5977			r#"SELECT LEAD("price") OVER ( ORDER BY "date" ASC ), "date" FROM "stocks""#
5978		);
5979		assert_eq!(values.len(), 0);
5980	}
5981
5982	#[test]
5983	fn test_window_lead_with_offset_and_default() {
5984		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
5985
5986		let builder = PostgresQueryBuilder::new();
5987		let mut stmt = Query::select();
5988
5989		let window = WindowStatement {
5990			partition_by: vec![Expr::col("ticker").into_simple_expr()],
5991			order_by: vec![OrderExpr {
5992				expr: OrderExprKind::Column("date".into_iden()),
5993				order: Order::Asc,
5994				nulls: None,
5995			}],
5996			frame: None,
5997		};
5998
5999		stmt.expr(
6000			Expr::lead(
6001				Expr::col("price").into_simple_expr(),
6002				Some(2),
6003				Some(0.0.into()),
6004			)
6005			.over(window),
6006		)
6007		.column("date")
6008		.from("stocks");
6009
6010		let (sql, values) = builder.build_select(&stmt);
6011		assert!(sql.contains("LEAD"));
6012		assert!(sql.contains("OVER"));
6013		assert!(sql.contains(r#"PARTITION BY "ticker""#));
6014		assert!(sql.contains(r#"ORDER BY "date" ASC"#));
6015		assert_eq!(values.len(), 2);
6016	}
6017
6018	#[test]
6019	fn test_window_lag_basic() {
6020		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
6021
6022		let builder = PostgresQueryBuilder::new();
6023		let mut stmt = Query::select();
6024
6025		let window = WindowStatement {
6026			partition_by: vec![],
6027			order_by: vec![OrderExpr {
6028				expr: OrderExprKind::Column("month".into_iden()),
6029				order: Order::Asc,
6030				nulls: None,
6031			}],
6032			frame: None,
6033		};
6034
6035		stmt.expr(Expr::lag(Expr::col("revenue").into_simple_expr(), None, None).over(window))
6036			.column("month")
6037			.from("sales");
6038
6039		let (sql, values) = builder.build_select(&stmt);
6040		assert_eq!(
6041			sql,
6042			r#"SELECT LAG("revenue") OVER ( ORDER BY "month" ASC ), "month" FROM "sales""#
6043		);
6044		assert_eq!(values.len(), 0);
6045	}
6046
6047	#[test]
6048	fn test_window_lag_with_offset_and_default() {
6049		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
6050
6051		let builder = PostgresQueryBuilder::new();
6052		let mut stmt = Query::select();
6053
6054		let window = WindowStatement {
6055			partition_by: vec![Expr::col("product").into_simple_expr()],
6056			order_by: vec![OrderExpr {
6057				expr: OrderExprKind::Column("month".into_iden()),
6058				order: Order::Asc,
6059				nulls: None,
6060			}],
6061			frame: None,
6062		};
6063
6064		stmt.expr(
6065			Expr::lag(
6066				Expr::col("revenue").into_simple_expr(),
6067				Some(3),
6068				Some(0.0.into()),
6069			)
6070			.over(window),
6071		)
6072		.column("month")
6073		.from("sales");
6074
6075		let (sql, values) = builder.build_select(&stmt);
6076		assert!(sql.contains("LAG"));
6077		assert!(sql.contains("OVER"));
6078		assert!(sql.contains(r#"PARTITION BY "product""#));
6079		assert!(sql.contains(r#"ORDER BY "month" ASC"#));
6080		assert_eq!(values.len(), 2);
6081	}
6082
6083	#[test]
6084	fn test_window_first_value() {
6085		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
6086
6087		let builder = PostgresQueryBuilder::new();
6088		let mut stmt = Query::select();
6089
6090		let window = WindowStatement {
6091			partition_by: vec![Expr::col("category").into_simple_expr()],
6092			order_by: vec![OrderExpr {
6093				expr: OrderExprKind::Column("price".into_iden()),
6094				order: Order::Asc,
6095				nulls: None,
6096			}],
6097			frame: None,
6098		};
6099
6100		stmt.expr(Expr::first_value(Expr::col("name").into_simple_expr()).over(window))
6101			.column("name")
6102			.from("products");
6103
6104		let (sql, _values) = builder.build_select(&stmt);
6105		assert_eq!(
6106			sql,
6107			r#"SELECT FIRST_VALUE("name") OVER ( PARTITION BY "category" ORDER BY "price" ASC ), "name" FROM "products""#
6108		);
6109	}
6110
6111	#[test]
6112	fn test_window_last_value() {
6113		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
6114
6115		let builder = PostgresQueryBuilder::new();
6116		let mut stmt = Query::select();
6117
6118		let window = WindowStatement {
6119			partition_by: vec![Expr::col("category").into_simple_expr()],
6120			order_by: vec![OrderExpr {
6121				expr: OrderExprKind::Column("price".into_iden()),
6122				order: Order::Desc,
6123				nulls: None,
6124			}],
6125			frame: None,
6126		};
6127
6128		stmt.expr(Expr::last_value(Expr::col("name").into_simple_expr()).over(window))
6129			.column("name")
6130			.from("products");
6131
6132		let (sql, _values) = builder.build_select(&stmt);
6133		assert_eq!(
6134			sql,
6135			r#"SELECT LAST_VALUE("name") OVER ( PARTITION BY "category" ORDER BY "price" DESC ), "name" FROM "products""#
6136		);
6137	}
6138
6139	#[test]
6140	fn test_window_nth_value() {
6141		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
6142
6143		let builder = PostgresQueryBuilder::new();
6144		let mut stmt = Query::select();
6145
6146		let window = WindowStatement {
6147			partition_by: vec![Expr::col("department").into_simple_expr()],
6148			order_by: vec![OrderExpr {
6149				expr: OrderExprKind::Column("salary".into_iden()),
6150				order: Order::Desc,
6151				nulls: None,
6152			}],
6153			frame: None,
6154		};
6155
6156		stmt.expr(Expr::nth_value(Expr::col("name").into_simple_expr(), 2).over(window))
6157			.column("name")
6158			.from("employees");
6159
6160		let (sql, values) = builder.build_select(&stmt);
6161		assert!(sql.contains("NTH_VALUE"));
6162		assert!(sql.contains(r#"PARTITION BY "department""#));
6163		assert!(sql.contains(r#"ORDER BY "salary" DESC"#));
6164		assert_eq!(values.len(), 1); // The "2" parameter
6165	}
6166
6167	#[test]
6168	fn test_window_row_number_multiple_partition_columns() {
6169		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
6170
6171		let builder = PostgresQueryBuilder::new();
6172		let mut stmt = Query::select();
6173
6174		let window = WindowStatement {
6175			partition_by: vec![
6176				Expr::col("country").into_simple_expr(),
6177				Expr::col("city").into_simple_expr(),
6178			],
6179			order_by: vec![OrderExpr {
6180				expr: OrderExprKind::Column("population".into_iden()),
6181				order: Order::Desc,
6182				nulls: None,
6183			}],
6184			frame: None,
6185		};
6186
6187		stmt.expr(Expr::row_number().over(window))
6188			.column("name")
6189			.from("cities");
6190
6191		let (sql, _values) = builder.build_select(&stmt);
6192		assert_eq!(
6193			sql,
6194			r#"SELECT ROW_NUMBER() OVER ( PARTITION BY "country", "city" ORDER BY "population" DESC ), "name" FROM "cities""#
6195		);
6196	}
6197
6198	#[test]
6199	fn test_window_ntile_with_partition() {
6200		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
6201
6202		let builder = PostgresQueryBuilder::new();
6203		let mut stmt = Query::select();
6204
6205		let window = WindowStatement {
6206			partition_by: vec![Expr::col("region").into_simple_expr()],
6207			order_by: vec![OrderExpr {
6208				expr: OrderExprKind::Column("revenue".into_iden()),
6209				order: Order::Desc,
6210				nulls: None,
6211			}],
6212			frame: None,
6213		};
6214
6215		stmt.expr(Expr::ntile(5).over(window))
6216			.column("store_name")
6217			.from("stores");
6218
6219		let (sql, _values) = builder.build_select(&stmt);
6220		assert_eq!(
6221			sql,
6222			r#"SELECT NTILE($1) OVER ( PARTITION BY "region" ORDER BY "revenue" DESC ), "store_name" FROM "stores""#
6223		);
6224	}
6225
6226	#[test]
6227	fn test_window_lead_with_offset_no_default() {
6228		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
6229
6230		let builder = PostgresQueryBuilder::new();
6231		let mut stmt = Query::select();
6232
6233		let window = WindowStatement {
6234			partition_by: vec![],
6235			order_by: vec![OrderExpr {
6236				expr: OrderExprKind::Column("quarter".into_iden()),
6237				order: Order::Asc,
6238				nulls: None,
6239			}],
6240			frame: None,
6241		};
6242
6243		stmt.expr(Expr::lead(Expr::col("sales").into_simple_expr(), Some(2), None).over(window))
6244			.column("quarter")
6245			.from("quarterly_sales");
6246
6247		let (sql, _values) = builder.build_select(&stmt);
6248		assert_eq!(
6249			sql,
6250			r#"SELECT LEAD("sales", $1) OVER ( ORDER BY "quarter" ASC ), "quarter" FROM "quarterly_sales""#
6251		);
6252	}
6253
6254	#[test]
6255	fn test_window_lag_with_different_offset() {
6256		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
6257
6258		let builder = PostgresQueryBuilder::new();
6259		let mut stmt = Query::select();
6260
6261		let window = WindowStatement {
6262			partition_by: vec![Expr::col("sensor_id").into_simple_expr()],
6263			order_by: vec![OrderExpr {
6264				expr: OrderExprKind::Column("timestamp".into_iden()),
6265				order: Order::Asc,
6266				nulls: None,
6267			}],
6268			frame: None,
6269		};
6270
6271		stmt.expr(Expr::lag(Expr::col("reading").into_simple_expr(), Some(5), None).over(window))
6272			.column("timestamp")
6273			.from("sensor_data");
6274
6275		let (sql, values) = builder.build_select(&stmt);
6276		assert_eq!(
6277			sql,
6278			r#"SELECT LAG("reading", $1) OVER ( PARTITION BY "sensor_id" ORDER BY "timestamp" ASC ), "timestamp" FROM "sensor_data""#
6279		);
6280		assert_eq!(values.len(), 1);
6281	}
6282
6283	#[test]
6284	fn test_window_multiple_functions_in_query() {
6285		use crate::types::{Order, OrderExpr, OrderExprKind, WindowStatement};
6286
6287		let builder = PostgresQueryBuilder::new();
6288		let mut stmt = Query::select();
6289
6290		let window1 = WindowStatement {
6291			partition_by: vec![Expr::col("department").into_simple_expr()],
6292			order_by: vec![OrderExpr {
6293				expr: OrderExprKind::Column("salary".into_iden()),
6294				order: Order::Desc,
6295				nulls: None,
6296			}],
6297			frame: None,
6298		};
6299
6300		let window2 = WindowStatement {
6301			partition_by: vec![],
6302			order_by: vec![OrderExpr {
6303				expr: OrderExprKind::Column("hire_date".into_iden()),
6304				order: Order::Asc,
6305				nulls: None,
6306			}],
6307			frame: None,
6308		};
6309
6310		stmt.expr(Expr::row_number().over(window1))
6311			.expr(Expr::rank().over(window2))
6312			.column("name")
6313			.from("employees");
6314
6315		let (sql, _values) = builder.build_select(&stmt);
6316		assert!(
6317			sql.contains(
6318				r#"ROW_NUMBER() OVER ( PARTITION BY "department" ORDER BY "salary" DESC )"#
6319			)
6320		);
6321		assert!(sql.contains(r#"RANK() OVER ( ORDER BY "hire_date" ASC )"#));
6322		assert!(sql.contains(r#""name""#));
6323		assert!(sql.contains(r#"FROM "employees""#));
6324	}
6325
6326	// JOIN enhancement tests
6327
6328	#[test]
6329	fn test_join_three_tables() {
6330		let builder = PostgresQueryBuilder::new();
6331		let mut stmt = Query::select();
6332		stmt.column(("users", "name"))
6333			.column(("orders", "order_date"))
6334			.column(("products", "product_name"))
6335			.from("users")
6336			.inner_join(
6337				"orders",
6338				Expr::col(("users", "id")).eq(Expr::col(("orders", "user_id"))),
6339			)
6340			.inner_join(
6341				"products",
6342				Expr::col(("orders", "product_id")).eq(Expr::col(("products", "id"))),
6343			);
6344
6345		let (sql, _values) = builder.build_select(&stmt);
6346		assert_eq!(
6347			sql,
6348			r#"SELECT "users"."name", "orders"."order_date", "products"."product_name" FROM "users" INNER JOIN "orders" ON "users"."id" = "orders"."user_id" INNER JOIN "products" ON "orders"."product_id" = "products"."id""#
6349		);
6350	}
6351
6352	#[test]
6353	fn test_self_join() {
6354		use crate::types::TableRef;
6355
6356		let builder = PostgresQueryBuilder::new();
6357		let mut stmt = Query::select();
6358		stmt.column(("e1", "name"))
6359			.column(("e2", "name"))
6360			.from(TableRef::table_alias("employees", "e1"))
6361			.inner_join(
6362				TableRef::table_alias("employees", "e2"),
6363				Expr::col(("e1", "manager_id")).eq(Expr::col(("e2", "id"))),
6364			);
6365
6366		let (sql, _values) = builder.build_select(&stmt);
6367		assert!(sql.contains(r#"FROM "employees" AS "e1""#));
6368		assert!(sql.contains(r#"INNER JOIN "employees" AS "e2""#));
6369		assert!(sql.contains(r#"ON "e1"."manager_id" = "e2"."id""#));
6370	}
6371
6372	#[test]
6373	fn test_join_complex_conditions() {
6374		let builder = PostgresQueryBuilder::new();
6375		let mut stmt = Query::select();
6376		stmt.from("orders").left_join(
6377			"customers",
6378			Expr::col(("orders", "customer_id"))
6379				.eq(Expr::col(("customers", "id")))
6380				.and(Expr::col(("customers", "active")).eq(true))
6381				.and(
6382					Expr::col(("orders", "created_at"))
6383						.gt(Expr::col(("customers", "registered_at"))),
6384				),
6385		);
6386
6387		let (sql, values) = builder.build_select(&stmt);
6388		assert!(sql.contains("LEFT JOIN \"customers\""));
6389		assert!(sql.contains("\"orders\".\"customer_id\" = \"customers\".\"id\""));
6390		assert!(sql.contains("AND \"customers\".\"active\" = $"));
6391		assert!(sql.contains("AND \"orders\".\"created_at\" > \"customers\".\"registered_at\""));
6392		assert_eq!(values.len(), 1); // true value
6393	}
6394
6395	#[test]
6396	fn test_join_with_subquery_in_condition() {
6397		let builder = PostgresQueryBuilder::new();
6398
6399		let mut subquery = Query::select();
6400		subquery.expr(Expr::col("max_id")).from("user_stats");
6401
6402		let mut stmt = Query::select();
6403		stmt.from("users").inner_join(
6404			"profiles",
6405			Expr::col(("users", "id"))
6406				.eq(Expr::col(("profiles", "user_id")))
6407				.and(Expr::col(("users", "id")).in_subquery(subquery)),
6408		);
6409
6410		let (sql, _values) = builder.build_select(&stmt);
6411		assert!(sql.contains("INNER JOIN \"profiles\""));
6412		assert!(sql.contains("\"users\".\"id\" = \"profiles\".\"user_id\""));
6413		assert!(sql.contains("IN"));
6414		assert!(sql.contains("SELECT \"max_id\" FROM \"user_stats\""));
6415	}
6416
6417	#[test]
6418	fn test_multiple_left_joins() {
6419		let builder = PostgresQueryBuilder::new();
6420		let mut stmt = Query::select();
6421		stmt.column(("users", "name"))
6422			.column(("profiles", "bio"))
6423			.column(("addresses", "city"))
6424			.column(("phone_numbers", "number"))
6425			.from("users")
6426			.left_join(
6427				"profiles",
6428				Expr::col(("users", "id")).eq(Expr::col(("profiles", "user_id"))),
6429			)
6430			.left_join(
6431				"addresses",
6432				Expr::col(("users", "id")).eq(Expr::col(("addresses", "user_id"))),
6433			)
6434			.left_join(
6435				"phone_numbers",
6436				Expr::col(("users", "id")).eq(Expr::col(("phone_numbers", "user_id"))),
6437			);
6438
6439		let (sql, _values) = builder.build_select(&stmt);
6440		assert!(sql.contains("LEFT JOIN \"profiles\""));
6441		assert!(sql.contains("LEFT JOIN \"addresses\""));
6442		assert!(sql.contains("LEFT JOIN \"phone_numbers\""));
6443	}
6444
6445	#[test]
6446	fn test_mixed_join_types() {
6447		let builder = PostgresQueryBuilder::new();
6448		let mut stmt = Query::select();
6449		stmt.column(("users", "name"))
6450			.from("users")
6451			.inner_join(
6452				"orders",
6453				Expr::col(("users", "id")).eq(Expr::col(("orders", "user_id"))),
6454			)
6455			.left_join(
6456				"reviews",
6457				Expr::col(("orders", "id")).eq(Expr::col(("reviews", "order_id"))),
6458			)
6459			.right_join(
6460				"refunds",
6461				Expr::col(("orders", "id")).eq(Expr::col(("refunds", "order_id"))),
6462			);
6463
6464		let (sql, _values) = builder.build_select(&stmt);
6465		assert!(sql.contains("INNER JOIN \"orders\""));
6466		assert!(sql.contains("LEFT JOIN \"reviews\""));
6467		assert!(sql.contains("RIGHT JOIN \"refunds\""));
6468	}
6469
6470	#[test]
6471	fn test_join_with_group_by() {
6472		use crate::expr::SimpleExpr;
6473		use crate::types::{BinOper, ColumnRef, IntoIden};
6474
6475		let builder = PostgresQueryBuilder::new();
6476		let mut stmt = Query::select();
6477		let count_expr = SimpleExpr::FunctionCall(
6478			"COUNT".into_iden(),
6479			vec![SimpleExpr::Column(ColumnRef::Asterisk)],
6480		);
6481
6482		stmt.column(("users", "name"))
6483			.expr(count_expr.clone())
6484			.from("users")
6485			.inner_join(
6486				"orders",
6487				Expr::col(("users", "id")).eq(Expr::col(("orders", "user_id"))),
6488			)
6489			.group_by(("users", "name"))
6490			.and_having(SimpleExpr::Binary(
6491				Box::new(count_expr),
6492				BinOper::GreaterThan,
6493				Box::new(SimpleExpr::Value(5.into())),
6494			));
6495
6496		let (sql, values) = builder.build_select(&stmt);
6497		assert!(sql.contains("INNER JOIN \"orders\""));
6498		assert!(sql.contains("GROUP BY \"users\".\"name\""));
6499		assert!(sql.contains("HAVING"));
6500		assert!(sql.contains("COUNT(*) > $"));
6501		assert_eq!(values.len(), 1);
6502	}
6503
6504	#[test]
6505	fn test_join_with_window_function() {
6506		use crate::types::{IntoIden, Order, OrderExpr, OrderExprKind, WindowStatement};
6507
6508		let builder = PostgresQueryBuilder::new();
6509		let mut stmt = Query::select();
6510
6511		let window = WindowStatement {
6512			partition_by: vec![Expr::col(("departments", "name")).into_simple_expr()],
6513			order_by: vec![OrderExpr {
6514				expr: OrderExprKind::TableColumn("employees".into_iden(), "salary".into_iden()),
6515				order: Order::Desc,
6516				nulls: None,
6517			}],
6518			frame: None,
6519		};
6520
6521		stmt.column(("employees", "name"))
6522			.expr(Expr::row_number().over(window))
6523			.from("employees")
6524			.inner_join(
6525				"departments",
6526				Expr::col(("employees", "department_id")).eq(Expr::col(("departments", "id"))),
6527			);
6528
6529		let (sql, _values) = builder.build_select(&stmt);
6530		assert!(sql.contains("INNER JOIN \"departments\""));
6531		assert!(sql.contains("ROW_NUMBER() OVER"));
6532		assert!(sql.contains(r#"PARTITION BY "departments"."name""#));
6533	}
6534
6535	#[test]
6536	fn test_four_table_join() {
6537		let builder = PostgresQueryBuilder::new();
6538		let mut stmt = Query::select();
6539		stmt.column(("users", "name"))
6540			.column(("orders", "order_date"))
6541			.column(("products", "product_name"))
6542			.column(("categories", "category_name"))
6543			.from("users")
6544			.inner_join(
6545				"orders",
6546				Expr::col(("users", "id")).eq(Expr::col(("orders", "user_id"))),
6547			)
6548			.inner_join(
6549				"products",
6550				Expr::col(("orders", "product_id")).eq(Expr::col(("products", "id"))),
6551			)
6552			.inner_join(
6553				"categories",
6554				Expr::col(("products", "category_id")).eq(Expr::col(("categories", "id"))),
6555			);
6556
6557		let (sql, _values) = builder.build_select(&stmt);
6558		assert!(sql.contains("FROM \"users\""));
6559		assert!(sql.contains("INNER JOIN \"orders\""));
6560		assert!(sql.contains("INNER JOIN \"products\""));
6561		assert!(sql.contains("INNER JOIN \"categories\""));
6562	}
6563
6564	#[test]
6565	fn test_join_with_cte() {
6566		use crate::types::TableRef;
6567
6568		let builder = PostgresQueryBuilder::new();
6569
6570		let mut cte = Query::select();
6571		cte.column("user_id")
6572			.expr(Expr::col("total"))
6573			.from("order_totals")
6574			.and_where(Expr::col("total").gt(1000));
6575
6576		let mut stmt = Query::select();
6577		stmt.with_cte("high_value_customers", cte)
6578			.column(("users", "name"))
6579			.column(("hvc", "total"))
6580			.from("users")
6581			.inner_join(
6582				TableRef::table_alias("high_value_customers", "hvc"),
6583				Expr::col(("users", "id")).eq(Expr::col(("hvc", "user_id"))),
6584			);
6585
6586		let (sql, values) = builder.build_select(&stmt);
6587		assert!(sql.contains("WITH \"high_value_customers\" AS"));
6588		assert!(sql.contains("INNER JOIN \"high_value_customers\" AS \"hvc\""));
6589		assert_eq!(values.len(), 1); // 1000
6590	}
6591
6592	#[test]
6593	fn test_cte_with_where_and_params() {
6594		let builder = PostgresQueryBuilder::new();
6595
6596		let mut cte_query = Query::select();
6597		cte_query
6598			.column("id")
6599			.column("total")
6600			.from("orders")
6601			.and_where(Expr::col("status").eq("completed"))
6602			.and_where(Expr::col("amount").gt(1000));
6603
6604		let mut stmt = Query::select();
6605		stmt.with_cte("large_orders", cte_query)
6606			.column("id")
6607			.column("total")
6608			.from("large_orders");
6609
6610		let (sql, values) = builder.build_select(&stmt);
6611		assert!(sql.contains("WITH"));
6612		assert!(sql.contains(r#""large_orders" AS"#));
6613		assert!(sql.contains(r#""status" = $"#));
6614		assert!(sql.contains(r#""amount" > $"#));
6615		assert_eq!(values.len(), 2);
6616	}
6617
6618	#[test]
6619	fn test_cte_used_in_join() {
6620		use crate::types::TableRef;
6621
6622		let builder = PostgresQueryBuilder::new();
6623
6624		let mut cte_query = Query::select();
6625		cte_query
6626			.column("user_id")
6627			.column("order_count")
6628			.from("orders")
6629			.group_by("user_id");
6630
6631		let mut stmt = Query::select();
6632		stmt.with_cte("user_orders", cte_query)
6633			.column(("users", "name"))
6634			.column(("uo", "order_count"))
6635			.from("users")
6636			.inner_join(
6637				TableRef::table_alias("user_orders", "uo"),
6638				Expr::col(("users", "id")).eq(Expr::col(("uo", "user_id"))),
6639			);
6640
6641		let (sql, values) = builder.build_select(&stmt);
6642		assert!(sql.contains("WITH"));
6643		assert!(sql.contains(r#""user_orders" AS"#));
6644		assert!(sql.contains(r#"INNER JOIN "user_orders" AS "uo""#));
6645		assert!(sql.contains(r#""users"."id" = "uo"."user_id""#));
6646		assert_eq!(values.len(), 0);
6647	}
6648
6649	#[test]
6650	fn test_cte_with_aggregation() {
6651		use crate::expr::SimpleExpr;
6652		use crate::types::{ColumnRef, IntoIden};
6653
6654		let builder = PostgresQueryBuilder::new();
6655
6656		let mut cte_query = Query::select();
6657		cte_query
6658			.column("category")
6659			.expr(SimpleExpr::FunctionCall(
6660				"COUNT".into_iden(),
6661				vec![SimpleExpr::Column(ColumnRef::Asterisk)],
6662			))
6663			.expr(SimpleExpr::FunctionCall(
6664				"SUM".into_iden(),
6665				vec![SimpleExpr::Column(ColumnRef::column("price"))],
6666			))
6667			.from("products")
6668			.group_by("category");
6669
6670		let mut stmt = Query::select();
6671		stmt.with_cte("category_stats", cte_query)
6672			.column("category")
6673			.from("category_stats");
6674
6675		let (sql, values) = builder.build_select(&stmt);
6676		assert!(sql.contains("WITH"));
6677		assert!(sql.contains(r#""category_stats" AS"#));
6678		assert!(sql.contains("COUNT(*)"));
6679		assert!(sql.contains(r#"SUM("price")"#));
6680		assert!(sql.contains(r#"GROUP BY "category""#));
6681		assert_eq!(values.len(), 0);
6682	}
6683
6684	#[test]
6685	fn test_cte_with_subquery() {
6686		let builder = PostgresQueryBuilder::new();
6687
6688		let mut sub = Query::select();
6689		sub.column("user_id").from("vip_users");
6690
6691		let mut cte_query = Query::select();
6692		cte_query
6693			.column("id")
6694			.column("total")
6695			.from("orders")
6696			.and_where(Expr::col("user_id").in_subquery(sub))
6697			.and_where(Expr::col("status").eq("shipped"));
6698
6699		let mut stmt = Query::select();
6700		stmt.with_cte("vip_orders", cte_query)
6701			.column("id")
6702			.column("total")
6703			.from("vip_orders");
6704
6705		let (sql, values) = builder.build_select(&stmt);
6706		assert!(sql.contains("WITH"));
6707		assert!(sql.contains(r#""vip_orders" AS"#));
6708		assert!(sql.contains("IN"));
6709		assert!(sql.contains(r#"SELECT "user_id" FROM "vip_users""#));
6710		assert!(sql.contains(r#""status" = $"#));
6711		assert_eq!(values.len(), 1);
6712	}
6713
6714	#[test]
6715	fn test_multiple_recursive_and_regular_ctes() {
6716		let builder = PostgresQueryBuilder::new();
6717
6718		// Regular CTE
6719		let mut regular_cte = Query::select();
6720		regular_cte
6721			.column("id")
6722			.column("name")
6723			.from("departments")
6724			.and_where(Expr::col("active").eq(true));
6725
6726		// Recursive CTE
6727		let mut recursive_cte = Query::select();
6728		recursive_cte
6729			.column("id")
6730			.column("name")
6731			.column("parent_id")
6732			.from("categories");
6733
6734		// Main query
6735		let mut stmt = Query::select();
6736		stmt.with_cte("active_depts", regular_cte)
6737			.with_recursive_cte("category_tree", recursive_cte)
6738			.column("name")
6739			.from("category_tree");
6740
6741		let (sql, values) = builder.build_select(&stmt);
6742		assert!(sql.contains("WITH RECURSIVE"));
6743		assert!(sql.contains(r#""active_depts" AS"#));
6744		assert!(sql.contains(r#""category_tree" AS"#));
6745		assert!(sql.contains(r#""active" = $"#));
6746		assert!(sql.contains(r#"FROM "category_tree""#));
6747		assert_eq!(values.len(), 1);
6748	}
6749
6750	// CASE expression tests
6751
6752	#[test]
6753	fn test_case_simple_when_else() {
6754		let builder = PostgresQueryBuilder::new();
6755
6756		let case_expr = Expr::case()
6757			.when(Expr::col("status").eq("active"), "Active")
6758			.else_result("Inactive");
6759
6760		let mut stmt = Query::select();
6761		stmt.expr_as(case_expr, "status_label").from("users");
6762
6763		let (sql, values) = builder.build_select(&stmt);
6764		assert!(sql.contains("CASE"));
6765		assert!(sql.contains("WHEN"));
6766		assert!(sql.contains(r#""status" = $"#));
6767		assert!(sql.contains("THEN"));
6768		assert!(sql.contains("ELSE"));
6769		assert!(sql.contains("END"));
6770		assert!(sql.contains(r#"AS "status_label""#));
6771		assert_eq!(values.len(), 3);
6772	}
6773
6774	#[test]
6775	fn test_case_multiple_when_clauses() {
6776		let builder = PostgresQueryBuilder::new();
6777
6778		let case_expr = Expr::case()
6779			.when(Expr::col("score").gte(90), "A")
6780			.when(Expr::col("score").gte(80), "B")
6781			.when(Expr::col("score").gte(70), "C")
6782			.else_result("F");
6783
6784		let mut stmt = Query::select();
6785		stmt.expr_as(case_expr, "grade").from("students");
6786
6787		let (sql, values) = builder.build_select(&stmt);
6788		assert!(sql.contains("CASE"));
6789		// Verify multiple WHEN clauses
6790		let when_count = sql.matches("WHEN").count();
6791		assert_eq!(when_count, 3);
6792		let then_count = sql.matches("THEN").count();
6793		assert_eq!(then_count, 3);
6794		assert!(sql.contains("ELSE"));
6795		assert!(sql.contains("END"));
6796		// 3 score comparisons + 3 THEN values + 1 ELSE value = 7
6797		assert_eq!(values.len(), 7);
6798	}
6799
6800	#[test]
6801	fn test_case_without_else() {
6802		let builder = PostgresQueryBuilder::new();
6803
6804		let case_expr = Expr::case()
6805			.when(Expr::col("type").eq("admin"), "Administrator")
6806			.when(Expr::col("type").eq("user"), "Regular User")
6807			.build();
6808
6809		let mut stmt = Query::select();
6810		stmt.expr_as(case_expr, "type_label").from("accounts");
6811
6812		let (sql, values) = builder.build_select(&stmt);
6813		assert!(sql.contains("CASE"));
6814		assert!(sql.contains("WHEN"));
6815		assert!(sql.contains("THEN"));
6816		assert!(!sql.contains("ELSE"));
6817		assert!(sql.contains("END"));
6818		assert_eq!(values.len(), 4);
6819	}
6820
6821	#[test]
6822	fn test_case_in_where_clause() {
6823		let builder = PostgresQueryBuilder::new();
6824
6825		let case_expr = Expr::case()
6826			.when(Expr::col("role").eq("admin"), 1)
6827			.else_result(0);
6828
6829		let mut stmt = Query::select();
6830		stmt.column("name").from("users").and_where(case_expr.eq(1));
6831
6832		let (sql, values) = builder.build_select(&stmt);
6833		assert!(sql.contains("WHERE"));
6834		assert!(sql.contains("CASE"));
6835		assert!(sql.contains("WHEN"));
6836		assert!(sql.contains("END"));
6837		assert!(values.len() >= 3);
6838	}
6839
6840	#[test]
6841	fn test_case_in_order_by() {
6842		let builder = PostgresQueryBuilder::new();
6843
6844		let case_expr = Expr::case()
6845			.when(Expr::col("priority").eq("high"), 1)
6846			.when(Expr::col("priority").eq("medium"), 2)
6847			.else_result(3);
6848
6849		let mut stmt = Query::select();
6850		stmt.column("name")
6851			.column("priority")
6852			.from("tasks")
6853			.order_by_expr(case_expr, crate::types::Order::Asc);
6854
6855		let (sql, values) = builder.build_select(&stmt);
6856		assert!(sql.contains("ORDER BY"));
6857		assert!(sql.contains("CASE"));
6858		assert!(sql.contains("WHEN"));
6859		assert!(sql.contains("END"));
6860		assert!(sql.contains("ASC"));
6861		assert_eq!(values.len(), 5);
6862	}
6863
6864	// ORDER BY / LIMIT edge case tests
6865
6866	#[test]
6867	fn test_order_by_multiple_columns_mixed() {
6868		let builder = PostgresQueryBuilder::new();
6869
6870		let mut stmt = Query::select();
6871		stmt.column("name")
6872			.column("age")
6873			.column("score")
6874			.from("students")
6875			.order_by("name", crate::types::Order::Asc)
6876			.order_by("age", crate::types::Order::Desc)
6877			.order_by("score", crate::types::Order::Asc);
6878
6879		let (sql, _values) = builder.build_select(&stmt);
6880		assert!(sql.contains("ORDER BY"));
6881		assert!(sql.contains(r#""name" ASC"#));
6882		assert!(sql.contains(r#""age" DESC"#));
6883		assert!(sql.contains(r#""score" ASC"#));
6884	}
6885
6886	#[test]
6887	fn test_order_by_nulls_first() {
6888		use crate::types::{IntoColumnRef, NullOrdering, OrderExpr, OrderExprKind};
6889
6890		let builder = PostgresQueryBuilder::new();
6891
6892		let mut stmt = Query::select();
6893		stmt.column("name").column("created_at").from("events");
6894		stmt.orders.push(OrderExpr {
6895			expr: OrderExprKind::Expr(Box::new(SimpleExpr::Column("created_at".into_column_ref()))),
6896			order: crate::types::Order::Desc,
6897			nulls: Some(NullOrdering::First),
6898		});
6899
6900		let (sql, _values) = builder.build_select(&stmt);
6901		assert!(sql.contains("ORDER BY"));
6902		assert!(sql.contains("DESC"));
6903		assert!(sql.contains("NULLS FIRST"));
6904	}
6905
6906	#[test]
6907	fn test_order_by_nulls_last() {
6908		use crate::types::{IntoColumnRef, NullOrdering, OrderExpr, OrderExprKind};
6909
6910		let builder = PostgresQueryBuilder::new();
6911
6912		let mut stmt = Query::select();
6913		stmt.column("name").column("updated_at").from("posts");
6914		stmt.orders.push(OrderExpr {
6915			expr: OrderExprKind::Expr(Box::new(SimpleExpr::Column("updated_at".into_column_ref()))),
6916			order: crate::types::Order::Asc,
6917			nulls: Some(NullOrdering::Last),
6918		});
6919
6920		let (sql, _values) = builder.build_select(&stmt);
6921		assert!(sql.contains("ORDER BY"));
6922		assert!(sql.contains("ASC"));
6923		assert!(sql.contains("NULLS LAST"));
6924	}
6925
6926	#[test]
6927	fn test_limit_without_offset() {
6928		let builder = PostgresQueryBuilder::new();
6929
6930		let mut stmt = Query::select();
6931		stmt.column("id").from("items").limit(5);
6932
6933		let (sql, values) = builder.build_select(&stmt);
6934		assert!(sql.contains("LIMIT"));
6935		assert!(!sql.contains("OFFSET"));
6936		assert_eq!(values.len(), 1);
6937	}
6938
6939	// Arithmetic / string operation tests
6940
6941	#[test]
6942	fn test_arithmetic_add_sub() {
6943		let builder = PostgresQueryBuilder::new();
6944		let mut stmt = Query::select();
6945		stmt.column("name").from("products");
6946		stmt.and_where(Expr::col("price").add(10i32).gt(100i32));
6947
6948		let (sql, values) = builder.build_select(&stmt);
6949		assert!(sql.contains(r#""price" + $1"#));
6950		assert!(sql.contains("> $2"));
6951		assert_eq!(values.len(), 2);
6952	}
6953
6954	#[test]
6955	fn test_arithmetic_mul_div_mod() {
6956		let builder = PostgresQueryBuilder::new();
6957		let mut stmt = Query::select();
6958		stmt.column("name").from("items");
6959		stmt.and_where(
6960			Expr::col("quantity")
6961				.mul(Expr::col("unit_price"))
6962				.gt(1000i32),
6963		);
6964
6965		let (sql, values) = builder.build_select(&stmt);
6966		assert!(sql.contains(r#""quantity" * "unit_price""#));
6967		assert!(sql.contains("> $1"));
6968		assert_eq!(values.len(), 1);
6969	}
6970
6971	#[test]
6972	fn test_like_ilike_pattern() {
6973		let builder = PostgresQueryBuilder::new();
6974		let mut stmt = Query::select();
6975		stmt.column("name").from("users");
6976		stmt.and_where(Expr::col("email").like("%@example.com"));
6977
6978		let (sql, values) = builder.build_select(&stmt);
6979		assert!(sql.contains(r#""email" LIKE $1"#));
6980		assert_eq!(values.len(), 1);
6981	}
6982
6983	#[test]
6984	fn test_pg_concat_operator() {
6985		use crate::types::{BinOper, IntoColumnRef, PgBinOper};
6986		let builder = PostgresQueryBuilder::new();
6987		let mut stmt = Query::select();
6988		stmt.expr(SimpleExpr::Binary(
6989			Box::new(SimpleExpr::Column("first_name".into_column_ref())),
6990			BinOper::PgOperator(PgBinOper::Concatenate),
6991			Box::new(SimpleExpr::Column("last_name".into_column_ref())),
6992		));
6993		stmt.from("users");
6994
6995		let (sql, _values) = builder.build_select(&stmt);
6996		assert!(sql.contains(r#""first_name" || "last_name""#));
6997	}
6998
6999	// DDL Tests
7000
7001	#[test]
7002	fn test_drop_table_basic() {
7003		let builder = PostgresQueryBuilder::new();
7004		let mut stmt = Query::drop_table();
7005		stmt.table("users");
7006
7007		let (sql, values) = builder.build_drop_table(&stmt);
7008		assert_eq!(sql, "DROP TABLE \"users\"");
7009		assert_eq!(values.len(), 0);
7010	}
7011
7012	#[test]
7013	fn test_drop_table_if_exists() {
7014		let builder = PostgresQueryBuilder::new();
7015		let mut stmt = Query::drop_table();
7016		stmt.table("users").if_exists();
7017
7018		let (sql, values) = builder.build_drop_table(&stmt);
7019		assert_eq!(sql, "DROP TABLE IF EXISTS \"users\"");
7020		assert_eq!(values.len(), 0);
7021	}
7022
7023	#[test]
7024	fn test_drop_table_cascade() {
7025		let builder = PostgresQueryBuilder::new();
7026		let mut stmt = Query::drop_table();
7027		stmt.table("users").cascade();
7028
7029		let (sql, values) = builder.build_drop_table(&stmt);
7030		assert_eq!(sql, "DROP TABLE \"users\" CASCADE");
7031		assert_eq!(values.len(), 0);
7032	}
7033
7034	#[test]
7035	fn test_drop_table_restrict() {
7036		let builder = PostgresQueryBuilder::new();
7037		let mut stmt = Query::drop_table();
7038		stmt.table("users").restrict();
7039
7040		let (sql, values) = builder.build_drop_table(&stmt);
7041		assert_eq!(sql, "DROP TABLE \"users\" RESTRICT");
7042		assert_eq!(values.len(), 0);
7043	}
7044
7045	#[test]
7046	fn test_drop_table_multiple() {
7047		let builder = PostgresQueryBuilder::new();
7048		let mut stmt = Query::drop_table();
7049		stmt.table("users").table("posts");
7050
7051		let (sql, values) = builder.build_drop_table(&stmt);
7052		assert_eq!(sql, "DROP TABLE \"users\", \"posts\"");
7053		assert_eq!(values.len(), 0);
7054	}
7055
7056	#[test]
7057	fn test_drop_index_basic() {
7058		let builder = PostgresQueryBuilder::new();
7059		let mut stmt = Query::drop_index();
7060		stmt.name("idx_email");
7061
7062		let (sql, values) = builder.build_drop_index(&stmt);
7063		assert_eq!(sql, "DROP INDEX \"idx_email\"");
7064		assert_eq!(values.len(), 0);
7065	}
7066
7067	#[test]
7068	fn test_drop_index_if_exists() {
7069		let builder = PostgresQueryBuilder::new();
7070		let mut stmt = Query::drop_index();
7071		stmt.name("idx_email").if_exists();
7072
7073		let (sql, values) = builder.build_drop_index(&stmt);
7074		assert_eq!(sql, "DROP INDEX IF EXISTS \"idx_email\"");
7075		assert_eq!(values.len(), 0);
7076	}
7077
7078	#[test]
7079	fn test_drop_index_cascade() {
7080		let builder = PostgresQueryBuilder::new();
7081		let mut stmt = Query::drop_index();
7082		stmt.name("idx_email").cascade();
7083
7084		let (sql, values) = builder.build_drop_index(&stmt);
7085		assert_eq!(sql, "DROP INDEX \"idx_email\" CASCADE");
7086		assert_eq!(values.len(), 0);
7087	}
7088
7089	#[test]
7090	fn test_drop_index_restrict() {
7091		let builder = PostgresQueryBuilder::new();
7092		let mut stmt = Query::drop_index();
7093		stmt.name("idx_email").restrict();
7094
7095		let (sql, values) = builder.build_drop_index(&stmt);
7096		assert_eq!(sql, "DROP INDEX \"idx_email\" RESTRICT");
7097		assert_eq!(values.len(), 0);
7098	}
7099
7100	// CREATE TABLE tests
7101
7102	#[test]
7103	fn test_create_table_basic() {
7104		use crate::types::{ColumnDef, ColumnType};
7105
7106		let builder = PostgresQueryBuilder::new();
7107		let mut stmt = Query::create_table();
7108		stmt.table("users");
7109		stmt.columns.push(ColumnDef {
7110			name: "id".into_iden(),
7111			column_type: Some(ColumnType::Integer),
7112			not_null: false,
7113			unique: false,
7114			primary_key: false,
7115			auto_increment: false,
7116			default: None,
7117			check: None,
7118			comment: None,
7119		});
7120		stmt.columns.push(ColumnDef {
7121			name: "name".into_iden(),
7122			column_type: Some(ColumnType::String(Some(255))),
7123			not_null: false,
7124			unique: false,
7125			primary_key: false,
7126			auto_increment: false,
7127			default: None,
7128			check: None,
7129			comment: None,
7130		});
7131
7132		let (sql, values) = builder.build_create_table(&stmt);
7133		assert!(sql.contains("CREATE TABLE \"users\""));
7134		assert!(sql.contains("\"id\" INTEGER"));
7135		assert!(sql.contains("\"name\" VARCHAR(255)"));
7136		assert_eq!(values.len(), 0);
7137	}
7138
7139	#[test]
7140	fn test_create_table_if_not_exists() {
7141		use crate::types::{ColumnDef, ColumnType};
7142
7143		let builder = PostgresQueryBuilder::new();
7144		let mut stmt = Query::create_table();
7145		stmt.table("users").if_not_exists();
7146		stmt.columns.push(ColumnDef {
7147			name: "id".into_iden(),
7148			column_type: Some(ColumnType::Integer),
7149			not_null: false,
7150			unique: false,
7151			primary_key: false,
7152			auto_increment: false,
7153			default: None,
7154			check: None,
7155			comment: None,
7156		});
7157
7158		let (sql, values) = builder.build_create_table(&stmt);
7159		assert!(sql.contains("CREATE TABLE IF NOT EXISTS \"users\""));
7160		assert!(sql.contains("\"id\" INTEGER"));
7161		assert_eq!(values.len(), 0);
7162	}
7163
7164	#[test]
7165	fn test_create_table_with_primary_key() {
7166		use crate::types::{ColumnDef, ColumnType};
7167
7168		let builder = PostgresQueryBuilder::new();
7169		let mut stmt = Query::create_table();
7170		stmt.table("users");
7171		stmt.columns.push(ColumnDef {
7172			name: "id".into_iden(),
7173			column_type: Some(ColumnType::Integer),
7174			not_null: false,
7175			unique: false,
7176			primary_key: true,
7177			auto_increment: false,
7178			default: None,
7179			check: None,
7180			comment: None,
7181		});
7182
7183		let (sql, values) = builder.build_create_table(&stmt);
7184		assert!(sql.contains("CREATE TABLE \"users\""));
7185		assert!(sql.contains("\"id\" INTEGER PRIMARY KEY"));
7186		assert_eq!(values.len(), 0);
7187	}
7188
7189	#[test]
7190	fn test_create_table_with_not_null() {
7191		use crate::types::{ColumnDef, ColumnType};
7192
7193		let builder = PostgresQueryBuilder::new();
7194		let mut stmt = Query::create_table();
7195		stmt.table("users");
7196		stmt.columns.push(ColumnDef {
7197			name: "email".into_iden(),
7198			column_type: Some(ColumnType::String(Some(255))),
7199			not_null: true,
7200			unique: false,
7201			primary_key: false,
7202			auto_increment: false,
7203			default: None,
7204			check: None,
7205			comment: None,
7206		});
7207
7208		let (sql, values) = builder.build_create_table(&stmt);
7209		assert!(sql.contains("\"email\" VARCHAR(255) NOT NULL"));
7210		assert_eq!(values.len(), 0);
7211	}
7212
7213	#[test]
7214	fn test_create_table_with_unique() {
7215		use crate::types::{ColumnDef, ColumnType};
7216
7217		let builder = PostgresQueryBuilder::new();
7218		let mut stmt = Query::create_table();
7219		stmt.table("users");
7220		stmt.columns.push(ColumnDef {
7221			name: "username".into_iden(),
7222			column_type: Some(ColumnType::String(Some(50))),
7223			not_null: false,
7224			unique: true,
7225			primary_key: false,
7226			auto_increment: false,
7227			default: None,
7228			check: None,
7229			comment: None,
7230		});
7231
7232		let (sql, values) = builder.build_create_table(&stmt);
7233		assert!(sql.contains("\"username\" VARCHAR(50) UNIQUE"));
7234		assert_eq!(values.len(), 0);
7235	}
7236
7237	#[test]
7238	fn test_create_table_with_default() {
7239		use crate::types::{ColumnDef, ColumnType};
7240
7241		let builder = PostgresQueryBuilder::new();
7242		let mut stmt = Query::create_table();
7243		stmt.table("users");
7244		stmt.columns.push(ColumnDef {
7245			name: "active".into_iden(),
7246			column_type: Some(ColumnType::Boolean),
7247			not_null: false,
7248			unique: false,
7249			primary_key: false,
7250			auto_increment: false,
7251			default: Some(Expr::value(true).into_simple_expr()),
7252			check: None,
7253			comment: None,
7254		});
7255
7256		let (sql, values) = builder.build_create_table(&stmt);
7257		assert!(sql.contains("\"active\" BOOLEAN DEFAULT"));
7258		assert_eq!(values.len(), 1);
7259	}
7260
7261	#[test]
7262	fn test_create_table_with_check() {
7263		use crate::types::{ColumnDef, ColumnType};
7264
7265		let builder = PostgresQueryBuilder::new();
7266		let mut stmt = Query::create_table();
7267		stmt.table("users");
7268		stmt.columns.push(ColumnDef {
7269			name: "age".into_iden(),
7270			column_type: Some(ColumnType::Integer),
7271			not_null: false,
7272			unique: false,
7273			primary_key: false,
7274			auto_increment: false,
7275			default: None,
7276			check: Some(Expr::col("age").gte(0).into_simple_expr()),
7277			comment: None,
7278		});
7279
7280		let (sql, values) = builder.build_create_table(&stmt);
7281		// CHECK constraints use inlined values (not parameters) in PostgreSQL
7282		assert!(sql.contains("\"age\" INTEGER CHECK"));
7283		assert!(sql.contains(">= 0"));
7284		assert_eq!(values.len(), 0);
7285	}
7286
7287	#[test]
7288	fn test_create_table_with_table_constraint() {
7289		use crate::types::{ColumnDef, ColumnType, TableConstraint};
7290
7291		let builder = PostgresQueryBuilder::new();
7292		let mut stmt = Query::create_table();
7293		stmt.table("users");
7294		stmt.columns.push(ColumnDef {
7295			name: "id".into_iden(),
7296			column_type: Some(ColumnType::Integer),
7297			not_null: false,
7298			unique: false,
7299			primary_key: false,
7300			auto_increment: false,
7301			default: None,
7302			check: None,
7303			comment: None,
7304		});
7305		stmt.columns.push(ColumnDef {
7306			name: "email".into_iden(),
7307			column_type: Some(ColumnType::String(Some(255))),
7308			not_null: false,
7309			unique: false,
7310			primary_key: false,
7311			auto_increment: false,
7312			default: None,
7313			check: None,
7314			comment: None,
7315		});
7316		stmt.constraints.push(TableConstraint::PrimaryKey {
7317			name: Some("pk_users".into_iden()),
7318			columns: vec!["id".into_iden()],
7319		});
7320
7321		let (sql, values) = builder.build_create_table(&stmt);
7322		assert!(sql.contains("CONSTRAINT \"pk_users\" PRIMARY KEY (\"id\")"));
7323		assert_eq!(values.len(), 0);
7324	}
7325
7326	#[test]
7327	fn test_create_table_with_foreign_key() {
7328		use crate::types::{
7329			ColumnDef, ColumnType, ForeignKeyAction, IntoTableRef, TableConstraint,
7330		};
7331
7332		let builder = PostgresQueryBuilder::new();
7333		let mut stmt = Query::create_table();
7334		stmt.table("posts");
7335		stmt.columns.push(ColumnDef {
7336			name: "id".into_iden(),
7337			column_type: Some(ColumnType::Integer),
7338			not_null: false,
7339			unique: false,
7340			primary_key: true,
7341			auto_increment: false,
7342			default: None,
7343			check: None,
7344			comment: None,
7345		});
7346		stmt.columns.push(ColumnDef {
7347			name: "user_id".into_iden(),
7348			column_type: Some(ColumnType::Integer),
7349			not_null: false,
7350			unique: false,
7351			primary_key: false,
7352			auto_increment: false,
7353			default: None,
7354			check: None,
7355			comment: None,
7356		});
7357		stmt.constraints.push(TableConstraint::ForeignKey {
7358			name: Some("fk_user".into_iden()),
7359			columns: vec!["user_id".into_iden()],
7360			ref_table: Box::new("users".into_table_ref()),
7361			ref_columns: vec!["id".into_iden()],
7362			on_delete: Some(ForeignKeyAction::Cascade),
7363			on_update: Some(ForeignKeyAction::Restrict),
7364		});
7365
7366		let (sql, values) = builder.build_create_table(&stmt);
7367		assert!(sql.contains("CONSTRAINT \"fk_user\" FOREIGN KEY (\"user_id\")"));
7368		assert!(sql.contains("REFERENCES \"users\" (\"id\")"));
7369		assert!(sql.contains("ON DELETE CASCADE"));
7370		assert!(sql.contains("ON UPDATE RESTRICT"));
7371		assert_eq!(values.len(), 0);
7372	}
7373
7374	#[test]
7375	fn test_create_index_basic() {
7376		use crate::query::IndexColumn;
7377
7378		let builder = PostgresQueryBuilder::new();
7379		let mut stmt = Query::create_index();
7380		stmt.name("idx_users_email");
7381		stmt.table("users");
7382		stmt.columns.push(IndexColumn {
7383			name: "email".into_iden(),
7384			order: None,
7385		});
7386
7387		let (sql, values) = builder.build_create_index(&stmt);
7388		assert_eq!(
7389			sql,
7390			r#"CREATE INDEX "idx_users_email" ON "users" ("email")"#
7391		);
7392		assert_eq!(values.len(), 0);
7393	}
7394
7395	#[test]
7396	fn test_create_index_unique() {
7397		use crate::query::IndexColumn;
7398
7399		let builder = PostgresQueryBuilder::new();
7400		let mut stmt = Query::create_index();
7401		stmt.name("idx_users_username");
7402		stmt.table("users");
7403		stmt.unique = true;
7404		stmt.columns.push(IndexColumn {
7405			name: "username".into_iden(),
7406			order: None,
7407		});
7408
7409		let (sql, values) = builder.build_create_index(&stmt);
7410		assert_eq!(
7411			sql,
7412			r#"CREATE UNIQUE INDEX "idx_users_username" ON "users" ("username")"#
7413		);
7414		assert_eq!(values.len(), 0);
7415	}
7416
7417	#[test]
7418	fn test_create_index_if_not_exists() {
7419		use crate::query::IndexColumn;
7420
7421		let builder = PostgresQueryBuilder::new();
7422		let mut stmt = Query::create_index();
7423		stmt.name("idx_users_email");
7424		stmt.table("users");
7425		stmt.if_not_exists = true;
7426		stmt.columns.push(IndexColumn {
7427			name: "email".into_iden(),
7428			order: None,
7429		});
7430
7431		let (sql, values) = builder.build_create_index(&stmt);
7432		assert_eq!(
7433			sql,
7434			r#"CREATE INDEX IF NOT EXISTS "idx_users_email" ON "users" ("email")"#
7435		);
7436		assert_eq!(values.len(), 0);
7437	}
7438
7439	#[test]
7440	fn test_create_index_with_order() {
7441		use crate::query::IndexColumn;
7442		use crate::types::Order;
7443
7444		let builder = PostgresQueryBuilder::new();
7445		let mut stmt = Query::create_index();
7446		stmt.name("idx_users_created");
7447		stmt.table("users");
7448		stmt.columns.push(IndexColumn {
7449			name: "created_at".into_iden(),
7450			order: Some(Order::Desc),
7451		});
7452
7453		let (sql, values) = builder.build_create_index(&stmt);
7454		assert_eq!(
7455			sql,
7456			r#"CREATE INDEX "idx_users_created" ON "users" ("created_at" DESC)"#
7457		);
7458		assert_eq!(values.len(), 0);
7459	}
7460
7461	#[test]
7462	fn test_create_index_multiple_columns() {
7463		use crate::query::IndexColumn;
7464		use crate::types::Order;
7465
7466		let builder = PostgresQueryBuilder::new();
7467		let mut stmt = Query::create_index();
7468		stmt.name("idx_users_name");
7469		stmt.table("users");
7470		stmt.columns.push(IndexColumn {
7471			name: "last_name".into_iden(),
7472			order: Some(Order::Asc),
7473		});
7474		stmt.columns.push(IndexColumn {
7475			name: "first_name".into_iden(),
7476			order: Some(Order::Asc),
7477		});
7478
7479		let (sql, values) = builder.build_create_index(&stmt);
7480		assert_eq!(
7481			sql,
7482			r#"CREATE INDEX "idx_users_name" ON "users" ("last_name" ASC, "first_name" ASC)"#
7483		);
7484		assert_eq!(values.len(), 0);
7485	}
7486
7487	#[test]
7488	fn test_create_index_with_using_btree() {
7489		use crate::query::{IndexColumn, IndexMethod};
7490
7491		let builder = PostgresQueryBuilder::new();
7492		let mut stmt = Query::create_index();
7493		stmt.name("idx_users_id");
7494		stmt.table("users");
7495		stmt.using = Some(IndexMethod::BTree);
7496		stmt.columns.push(IndexColumn {
7497			name: "id".into_iden(),
7498			order: None,
7499		});
7500
7501		let (sql, values) = builder.build_create_index(&stmt);
7502		assert_eq!(
7503			sql,
7504			r#"CREATE INDEX "idx_users_id" ON "users" USING BTREE ("id")"#
7505		);
7506		assert_eq!(values.len(), 0);
7507	}
7508
7509	#[test]
7510	fn test_create_index_with_using_gin() {
7511		use crate::query::{IndexColumn, IndexMethod};
7512
7513		let builder = PostgresQueryBuilder::new();
7514		let mut stmt = Query::create_index();
7515		stmt.name("idx_posts_tags");
7516		stmt.table("posts");
7517		stmt.using = Some(IndexMethod::Gin);
7518		stmt.columns.push(IndexColumn {
7519			name: "tags".into_iden(),
7520			order: None,
7521		});
7522
7523		let (sql, values) = builder.build_create_index(&stmt);
7524		assert_eq!(
7525			sql,
7526			r#"CREATE INDEX "idx_posts_tags" ON "posts" USING GIN ("tags")"#
7527		);
7528		assert_eq!(values.len(), 0);
7529	}
7530
7531	#[test]
7532	fn test_create_index_partial_with_where() {
7533		use crate::query::IndexColumn;
7534
7535		let builder = PostgresQueryBuilder::new();
7536		let mut stmt = Query::create_index();
7537		stmt.name("idx_users_active_email");
7538		stmt.table("users");
7539		stmt.columns.push(IndexColumn {
7540			name: "email".into_iden(),
7541			order: None,
7542		});
7543		stmt.r#where = Some(Expr::col("active").eq(true).into_simple_expr());
7544
7545		let (sql, values) = builder.build_create_index(&stmt);
7546		assert_eq!(
7547			sql,
7548			r#"CREATE INDEX "idx_users_active_email" ON "users" ("email") WHERE "active" = $1"#
7549		);
7550		assert_eq!(values.len(), 1);
7551	}
7552
7553	#[test]
7554	fn test_alter_table_add_column() {
7555		use crate::query::AlterTableOperation;
7556		use crate::types::{ColumnDef, ColumnType};
7557
7558		let builder = PostgresQueryBuilder::new();
7559		let mut stmt = Query::alter_table();
7560		stmt.table("users");
7561		stmt.operations
7562			.push(AlterTableOperation::AddColumn(ColumnDef {
7563				name: "age".into_iden(),
7564				column_type: Some(ColumnType::Integer),
7565				not_null: false,
7566				unique: false,
7567				primary_key: false,
7568				auto_increment: false,
7569				default: None,
7570				check: None,
7571				comment: None,
7572			}));
7573
7574		let (sql, values) = builder.build_alter_table(&stmt);
7575		assert_eq!(sql, r#"ALTER TABLE "users" ADD COLUMN "age" INTEGER"#);
7576		assert_eq!(values.len(), 0);
7577	}
7578
7579	#[test]
7580	fn test_alter_table_drop_column() {
7581		use crate::query::AlterTableOperation;
7582
7583		let builder = PostgresQueryBuilder::new();
7584		let mut stmt = Query::alter_table();
7585		stmt.table("users");
7586		stmt.operations.push(AlterTableOperation::DropColumn {
7587			name: "age".into_iden(),
7588			if_exists: false,
7589		});
7590
7591		let (sql, values) = builder.build_alter_table(&stmt);
7592		assert_eq!(sql, r#"ALTER TABLE "users" DROP COLUMN "age""#);
7593		assert_eq!(values.len(), 0);
7594	}
7595
7596	#[test]
7597	fn test_alter_table_drop_column_if_exists() {
7598		use crate::query::AlterTableOperation;
7599
7600		let builder = PostgresQueryBuilder::new();
7601		let mut stmt = Query::alter_table();
7602		stmt.table("users");
7603		stmt.operations.push(AlterTableOperation::DropColumn {
7604			name: "age".into_iden(),
7605			if_exists: true,
7606		});
7607
7608		let (sql, values) = builder.build_alter_table(&stmt);
7609		assert_eq!(sql, r#"ALTER TABLE "users" DROP COLUMN IF EXISTS "age""#);
7610		assert_eq!(values.len(), 0);
7611	}
7612
7613	#[test]
7614	fn test_alter_table_rename_column() {
7615		use crate::query::AlterTableOperation;
7616
7617		let builder = PostgresQueryBuilder::new();
7618		let mut stmt = Query::alter_table();
7619		stmt.table("users");
7620		stmt.operations.push(AlterTableOperation::RenameColumn {
7621			old: "email".into_iden(),
7622			new: "email_address".into_iden(),
7623		});
7624
7625		let (sql, values) = builder.build_alter_table(&stmt);
7626		assert_eq!(
7627			sql,
7628			r#"ALTER TABLE "users" RENAME COLUMN "email" TO "email_address""#
7629		);
7630		assert_eq!(values.len(), 0);
7631	}
7632
7633	#[test]
7634	fn test_alter_table_modify_column_type() {
7635		use crate::query::AlterTableOperation;
7636		use crate::types::{ColumnDef, ColumnType};
7637
7638		let builder = PostgresQueryBuilder::new();
7639		let mut stmt = Query::alter_table();
7640		stmt.table("users");
7641		stmt.operations
7642			.push(AlterTableOperation::ModifyColumn(ColumnDef {
7643				name: "age".into_iden(),
7644				column_type: Some(ColumnType::BigInteger),
7645				not_null: false,
7646				unique: false,
7647				primary_key: false,
7648				auto_increment: false,
7649				default: None,
7650				check: None,
7651				comment: None,
7652			}));
7653
7654		let (sql, values) = builder.build_alter_table(&stmt);
7655		assert_eq!(sql, r#"ALTER TABLE "users" ALTER COLUMN "age" TYPE BIGINT"#);
7656		assert_eq!(values.len(), 0);
7657	}
7658
7659	#[test]
7660	fn test_alter_table_add_constraint() {
7661		use crate::query::AlterTableOperation;
7662		use crate::types::TableConstraint;
7663
7664		let builder = PostgresQueryBuilder::new();
7665		let mut stmt = Query::alter_table();
7666		stmt.table("users");
7667		stmt.operations.push(AlterTableOperation::AddConstraint(
7668			TableConstraint::Unique {
7669				name: Some("unique_email".into_iden()),
7670				columns: vec!["email".into_iden()],
7671			},
7672		));
7673
7674		let (sql, values) = builder.build_alter_table(&stmt);
7675		assert_eq!(
7676			sql,
7677			r#"ALTER TABLE "users" ADD CONSTRAINT "unique_email" UNIQUE ("email")"#
7678		);
7679		assert_eq!(values.len(), 0);
7680	}
7681
7682	#[test]
7683	fn test_alter_table_drop_constraint() {
7684		use crate::query::AlterTableOperation;
7685
7686		let builder = PostgresQueryBuilder::new();
7687		let mut stmt = Query::alter_table();
7688		stmt.table("users");
7689		stmt.operations.push(AlterTableOperation::DropConstraint {
7690			name: "unique_email".into_iden(),
7691			if_exists: false,
7692		});
7693
7694		let (sql, values) = builder.build_alter_table(&stmt);
7695		assert_eq!(sql, r#"ALTER TABLE "users" DROP CONSTRAINT "unique_email""#);
7696		assert_eq!(values.len(), 0);
7697	}
7698
7699	#[test]
7700	fn test_alter_table_rename_table() {
7701		use crate::query::AlterTableOperation;
7702
7703		let builder = PostgresQueryBuilder::new();
7704		let mut stmt = Query::alter_table();
7705		stmt.table("users");
7706		stmt.operations
7707			.push(AlterTableOperation::RenameTable("accounts".into_iden()));
7708
7709		let (sql, values) = builder.build_alter_table(&stmt);
7710		assert_eq!(sql, r#"ALTER TABLE "users" RENAME TO "accounts""#);
7711		assert_eq!(values.len(), 0);
7712	}
7713
7714	// TRUNCATE TABLE tests
7715
7716	#[test]
7717	fn test_truncate_table_basic() {
7718		let builder = PostgresQueryBuilder::new();
7719		let mut stmt = Query::truncate_table();
7720		stmt.table("users");
7721
7722		let (sql, values) = builder.build_truncate_table(&stmt);
7723		assert_eq!(sql, r#"TRUNCATE TABLE "users""#);
7724		assert_eq!(values.len(), 0);
7725	}
7726
7727	#[test]
7728	fn test_truncate_table_multiple() {
7729		let builder = PostgresQueryBuilder::new();
7730		let mut stmt = Query::truncate_table();
7731		stmt.table("users").table("posts").table("comments");
7732
7733		let (sql, values) = builder.build_truncate_table(&stmt);
7734		assert_eq!(sql, r#"TRUNCATE TABLE "users", "posts", "comments""#);
7735		assert_eq!(values.len(), 0);
7736	}
7737
7738	#[test]
7739	fn test_truncate_table_restart_identity() {
7740		let builder = PostgresQueryBuilder::new();
7741		let mut stmt = Query::truncate_table();
7742		stmt.table("users").restart_identity();
7743
7744		let (sql, values) = builder.build_truncate_table(&stmt);
7745		assert_eq!(sql, r#"TRUNCATE TABLE "users" RESTART IDENTITY"#);
7746		assert_eq!(values.len(), 0);
7747	}
7748
7749	#[test]
7750	fn test_truncate_table_cascade() {
7751		let builder = PostgresQueryBuilder::new();
7752		let mut stmt = Query::truncate_table();
7753		stmt.table("users").cascade();
7754
7755		let (sql, values) = builder.build_truncate_table(&stmt);
7756		assert_eq!(sql, r#"TRUNCATE TABLE "users" CASCADE"#);
7757		assert_eq!(values.len(), 0);
7758	}
7759
7760	#[test]
7761	fn test_truncate_table_restrict() {
7762		let builder = PostgresQueryBuilder::new();
7763		let mut stmt = Query::truncate_table();
7764		stmt.table("users").restrict();
7765
7766		let (sql, values) = builder.build_truncate_table(&stmt);
7767		assert_eq!(sql, r#"TRUNCATE TABLE "users" RESTRICT"#);
7768		assert_eq!(values.len(), 0);
7769	}
7770
7771	#[test]
7772	fn test_truncate_table_restart_identity_cascade() {
7773		let builder = PostgresQueryBuilder::new();
7774		let mut stmt = Query::truncate_table();
7775		stmt.table("users").restart_identity().cascade();
7776
7777		let (sql, values) = builder.build_truncate_table(&stmt);
7778		assert_eq!(sql, r#"TRUNCATE TABLE "users" RESTART IDENTITY CASCADE"#);
7779		assert_eq!(values.len(), 0);
7780	}
7781
7782	#[test]
7783	fn test_create_trigger_basic() {
7784		use crate::types::{TriggerEvent, TriggerScope, TriggerTiming};
7785
7786		let builder = PostgresQueryBuilder::new();
7787		let mut stmt = Query::create_trigger();
7788		stmt.name("audit_log")
7789			.timing(TriggerTiming::After)
7790			.event(TriggerEvent::Insert)
7791			.on_table("users")
7792			.for_each(TriggerScope::Row)
7793			.execute_function("log_user_insert");
7794
7795		let (sql, values) = builder.build_create_trigger(&stmt);
7796		assert_eq!(
7797			sql,
7798			r#"CREATE TRIGGER "audit_log" AFTER INSERT ON "users" FOR EACH ROW EXECUTE FUNCTION "log_user_insert"()"#
7799		);
7800		assert_eq!(values.len(), 0);
7801	}
7802
7803	#[test]
7804	fn test_create_trigger_before_update() {
7805		use crate::types::{TriggerEvent, TriggerScope, TriggerTiming};
7806
7807		let builder = PostgresQueryBuilder::new();
7808		let mut stmt = Query::create_trigger();
7809		stmt.name("update_timestamp")
7810			.timing(TriggerTiming::Before)
7811			.event(TriggerEvent::Update { columns: None })
7812			.on_table("users")
7813			.for_each(TriggerScope::Row)
7814			.execute_function("update_modified_at");
7815
7816		let (sql, values) = builder.build_create_trigger(&stmt);
7817		assert_eq!(
7818			sql,
7819			r#"CREATE TRIGGER "update_timestamp" BEFORE UPDATE ON "users" FOR EACH ROW EXECUTE FUNCTION "update_modified_at"()"#
7820		);
7821		assert_eq!(values.len(), 0);
7822	}
7823
7824	#[test]
7825	fn test_create_trigger_delete_for_statement() {
7826		use crate::types::{TriggerEvent, TriggerScope, TriggerTiming};
7827
7828		let builder = PostgresQueryBuilder::new();
7829		let mut stmt = Query::create_trigger();
7830		stmt.name("audit_delete")
7831			.timing(TriggerTiming::After)
7832			.event(TriggerEvent::Delete)
7833			.on_table("users")
7834			.for_each(TriggerScope::Statement)
7835			.execute_function("log_bulk_delete");
7836
7837		let (sql, values) = builder.build_create_trigger(&stmt);
7838		assert_eq!(
7839			sql,
7840			r#"CREATE TRIGGER "audit_delete" AFTER DELETE ON "users" FOR EACH STATEMENT EXECUTE FUNCTION "log_bulk_delete"()"#
7841		);
7842		assert_eq!(values.len(), 0);
7843	}
7844
7845	#[test]
7846	fn test_drop_trigger_basic() {
7847		let builder = PostgresQueryBuilder::new();
7848		let mut stmt = Query::drop_trigger();
7849		stmt.name("audit_log").on_table("users");
7850
7851		let (sql, values) = builder.build_drop_trigger(&stmt);
7852		assert_eq!(sql, r#"DROP TRIGGER "audit_log" ON "users""#);
7853		assert_eq!(values.len(), 0);
7854	}
7855
7856	#[test]
7857	fn test_drop_trigger_if_exists() {
7858		let builder = PostgresQueryBuilder::new();
7859		let mut stmt = Query::drop_trigger();
7860		stmt.name("audit_log").on_table("users").if_exists();
7861
7862		let (sql, values) = builder.build_drop_trigger(&stmt);
7863		assert_eq!(sql, r#"DROP TRIGGER IF EXISTS "audit_log" ON "users""#);
7864		assert_eq!(values.len(), 0);
7865	}
7866
7867	#[test]
7868	fn test_drop_trigger_cascade() {
7869		let builder = PostgresQueryBuilder::new();
7870		let mut stmt = Query::drop_trigger();
7871		stmt.name("audit_log").on_table("users").cascade();
7872
7873		let (sql, values) = builder.build_drop_trigger(&stmt);
7874		assert_eq!(sql, r#"DROP TRIGGER "audit_log" ON "users" CASCADE"#);
7875		assert_eq!(values.len(), 0);
7876	}
7877
7878	// CREATE FUNCTION tests
7879	#[test]
7880	fn test_create_function_basic() {
7881		use crate::types::function::FunctionLanguage;
7882
7883		let builder = PostgresQueryBuilder::new();
7884		let mut stmt = Query::create_function();
7885		stmt.name("my_func")
7886			.returns("integer")
7887			.language(FunctionLanguage::Sql)
7888			.body("SELECT 1");
7889
7890		let (sql, values) = builder.build_create_function(&stmt);
7891		assert_eq!(
7892			sql,
7893			r#"CREATE FUNCTION "my_func"() RETURNS integer LANGUAGE SQL AS $$SELECT 1$$"#
7894		);
7895		assert_eq!(values.len(), 0);
7896	}
7897
7898	#[test]
7899	fn test_create_function_or_replace() {
7900		use crate::types::function::FunctionLanguage;
7901
7902		let builder = PostgresQueryBuilder::new();
7903		let mut stmt = Query::create_function();
7904		stmt.name("my_func")
7905			.or_replace()
7906			.returns("integer")
7907			.language(FunctionLanguage::Sql)
7908			.body("SELECT 1");
7909
7910		let (sql, values) = builder.build_create_function(&stmt);
7911		assert_eq!(
7912			sql,
7913			r#"CREATE OR REPLACE FUNCTION "my_func"() RETURNS integer LANGUAGE SQL AS $$SELECT 1$$"#
7914		);
7915		assert_eq!(values.len(), 0);
7916	}
7917
7918	#[test]
7919	fn test_create_function_with_parameters() {
7920		use crate::types::function::FunctionLanguage;
7921
7922		let builder = PostgresQueryBuilder::new();
7923		let mut stmt = Query::create_function();
7924		stmt.name("add_numbers")
7925			.add_parameter("a", "integer")
7926			.add_parameter("b", "integer")
7927			.returns("integer")
7928			.language(FunctionLanguage::Sql)
7929			.body("SELECT $1 + $2");
7930
7931		let (sql, values) = builder.build_create_function(&stmt);
7932		assert_eq!(
7933			sql,
7934			r#"CREATE FUNCTION "add_numbers"("a" integer, "b" integer) RETURNS integer LANGUAGE SQL AS $$SELECT $1 + $2$$"#
7935		);
7936		assert_eq!(values.len(), 0);
7937	}
7938
7939	#[test]
7940	fn test_create_function_with_behavior() {
7941		use crate::types::function::{FunctionBehavior, FunctionLanguage};
7942
7943		let builder = PostgresQueryBuilder::new();
7944		let mut stmt = Query::create_function();
7945		stmt.name("my_func")
7946			.returns("integer")
7947			.language(FunctionLanguage::Sql)
7948			.behavior(FunctionBehavior::Immutable)
7949			.body("SELECT 1");
7950
7951		let (sql, values) = builder.build_create_function(&stmt);
7952		assert_eq!(
7953			sql,
7954			r#"CREATE FUNCTION "my_func"() RETURNS integer LANGUAGE SQL IMMUTABLE AS $$SELECT 1$$"#
7955		);
7956		assert_eq!(values.len(), 0);
7957	}
7958
7959	#[test]
7960	fn test_create_function_with_security() {
7961		use crate::types::function::{FunctionLanguage, FunctionSecurity};
7962
7963		let builder = PostgresQueryBuilder::new();
7964		let mut stmt = Query::create_function();
7965		stmt.name("my_func")
7966			.returns("integer")
7967			.language(FunctionLanguage::Sql)
7968			.security(FunctionSecurity::Definer)
7969			.body("SELECT 1");
7970
7971		let (sql, values) = builder.build_create_function(&stmt);
7972		assert_eq!(
7973			sql,
7974			r#"CREATE FUNCTION "my_func"() RETURNS integer LANGUAGE SQL SECURITY DEFINER AS $$SELECT 1$$"#
7975		);
7976		assert_eq!(values.len(), 0);
7977	}
7978
7979	#[test]
7980	fn test_create_function_plpgsql() {
7981		use crate::types::function::FunctionLanguage;
7982
7983		let builder = PostgresQueryBuilder::new();
7984		let mut stmt = Query::create_function();
7985		stmt.name("increment")
7986			.add_parameter("val", "integer")
7987			.returns("integer")
7988			.language(FunctionLanguage::PlPgSql)
7989			.body("BEGIN RETURN val + 1; END;");
7990
7991		let (sql, values) = builder.build_create_function(&stmt);
7992		assert_eq!(
7993			sql,
7994			r#"CREATE FUNCTION "increment"("val" integer) RETURNS integer LANGUAGE PLPGSQL AS $$BEGIN RETURN val + 1; END;$$"#
7995		);
7996		assert_eq!(values.len(), 0);
7997	}
7998
7999	#[test]
8000	fn test_create_function_all_options() {
8001		use crate::types::function::{FunctionBehavior, FunctionLanguage, FunctionSecurity};
8002
8003		let builder = PostgresQueryBuilder::new();
8004		let mut stmt = Query::create_function();
8005		stmt.name("complex_func")
8006			.or_replace()
8007			.add_parameter("a", "integer")
8008			.add_parameter("b", "text")
8009			.returns("integer")
8010			.language(FunctionLanguage::PlPgSql)
8011			.behavior(FunctionBehavior::Stable)
8012			.security(FunctionSecurity::Definer)
8013			.body("BEGIN RETURN a + LENGTH(b); END;");
8014
8015		let (sql, values) = builder.build_create_function(&stmt);
8016		assert_eq!(
8017			sql,
8018			r#"CREATE OR REPLACE FUNCTION "complex_func"("a" integer, "b" text) RETURNS integer LANGUAGE PLPGSQL STABLE SECURITY DEFINER AS $$BEGIN RETURN a + LENGTH(b); END;$$"#
8019		);
8020		assert_eq!(values.len(), 0);
8021	}
8022
8023	// ALTER FUNCTION tests
8024	#[test]
8025	fn test_alter_function_rename_to() {
8026		let builder = PostgresQueryBuilder::new();
8027		let mut stmt = Query::alter_function();
8028		stmt.name("my_func").rename_to("new_func");
8029
8030		let (sql, values) = builder.build_alter_function(&stmt);
8031		assert_eq!(sql, r#"ALTER FUNCTION "my_func" RENAME TO "new_func""#);
8032		assert_eq!(values.len(), 0);
8033	}
8034
8035	#[test]
8036	fn test_alter_function_owner_to() {
8037		let builder = PostgresQueryBuilder::new();
8038		let mut stmt = Query::alter_function();
8039		stmt.name("my_func").owner_to("new_owner");
8040
8041		let (sql, values) = builder.build_alter_function(&stmt);
8042		assert_eq!(sql, r#"ALTER FUNCTION "my_func" OWNER TO "new_owner""#);
8043		assert_eq!(values.len(), 0);
8044	}
8045
8046	#[test]
8047	fn test_alter_function_set_schema() {
8048		let builder = PostgresQueryBuilder::new();
8049		let mut stmt = Query::alter_function();
8050		stmt.name("my_func").set_schema("new_schema");
8051
8052		let (sql, values) = builder.build_alter_function(&stmt);
8053		assert_eq!(sql, r#"ALTER FUNCTION "my_func" SET SCHEMA "new_schema""#);
8054		assert_eq!(values.len(), 0);
8055	}
8056
8057	#[test]
8058	fn test_alter_function_set_behavior_immutable() {
8059		use crate::types::function::FunctionBehavior;
8060
8061		let builder = PostgresQueryBuilder::new();
8062		let mut stmt = Query::alter_function();
8063		stmt.name("my_func")
8064			.set_behavior(FunctionBehavior::Immutable);
8065
8066		let (sql, values) = builder.build_alter_function(&stmt);
8067		assert_eq!(sql, r#"ALTER FUNCTION "my_func" IMMUTABLE"#);
8068		assert_eq!(values.len(), 0);
8069	}
8070
8071	#[test]
8072	fn test_alter_function_set_security_definer() {
8073		use crate::types::function::FunctionSecurity;
8074
8075		let builder = PostgresQueryBuilder::new();
8076		let mut stmt = Query::alter_function();
8077		stmt.name("my_func").set_security(FunctionSecurity::Definer);
8078
8079		let (sql, values) = builder.build_alter_function(&stmt);
8080		assert_eq!(sql, r#"ALTER FUNCTION "my_func" SECURITY DEFINER"#);
8081		assert_eq!(values.len(), 0);
8082	}
8083
8084	#[test]
8085	fn test_alter_function_with_parameters() {
8086		let builder = PostgresQueryBuilder::new();
8087		let mut stmt = Query::alter_function();
8088		stmt.name("my_func")
8089			.add_parameter("a", "integer")
8090			.add_parameter("b", "text")
8091			.rename_to("new_func");
8092
8093		let (sql, values) = builder.build_alter_function(&stmt);
8094		assert_eq!(
8095			sql,
8096			r#"ALTER FUNCTION "my_func"("a" integer, "b" text) RENAME TO "new_func""#
8097		);
8098		assert_eq!(values.len(), 0);
8099	}
8100
8101	// DROP FUNCTION tests
8102	#[test]
8103	fn test_drop_function_basic() {
8104		let builder = PostgresQueryBuilder::new();
8105		let mut stmt = Query::drop_function();
8106		stmt.name("my_func");
8107
8108		let (sql, values) = builder.build_drop_function(&stmt);
8109		assert_eq!(sql, r#"DROP FUNCTION "my_func""#);
8110		assert_eq!(values.len(), 0);
8111	}
8112
8113	#[test]
8114	fn test_drop_function_if_exists() {
8115		let builder = PostgresQueryBuilder::new();
8116		let mut stmt = Query::drop_function();
8117		stmt.name("my_func").if_exists();
8118
8119		let (sql, values) = builder.build_drop_function(&stmt);
8120		assert_eq!(sql, r#"DROP FUNCTION IF EXISTS "my_func""#);
8121		assert_eq!(values.len(), 0);
8122	}
8123
8124	#[test]
8125	fn test_drop_function_cascade() {
8126		let builder = PostgresQueryBuilder::new();
8127		let mut stmt = Query::drop_function();
8128		stmt.name("my_func").cascade();
8129
8130		let (sql, values) = builder.build_drop_function(&stmt);
8131		assert_eq!(sql, r#"DROP FUNCTION "my_func" CASCADE"#);
8132		assert_eq!(values.len(), 0);
8133	}
8134
8135	#[test]
8136	fn test_drop_function_with_parameters() {
8137		let builder = PostgresQueryBuilder::new();
8138		let mut stmt = Query::drop_function();
8139		stmt.name("my_func")
8140			.add_parameter("", "integer")
8141			.add_parameter("", "text");
8142
8143		let (sql, values) = builder.build_drop_function(&stmt);
8144		assert_eq!(sql, r#"DROP FUNCTION "my_func"(integer, text)"#);
8145		assert_eq!(values.len(), 0);
8146	}
8147
8148	#[test]
8149	fn test_drop_function_all_options() {
8150		let builder = PostgresQueryBuilder::new();
8151		let mut stmt = Query::drop_function();
8152		stmt.name("my_func")
8153			.if_exists()
8154			.add_parameter("", "integer")
8155			.cascade();
8156
8157		let (sql, values) = builder.build_drop_function(&stmt);
8158		assert_eq!(sql, r#"DROP FUNCTION IF EXISTS "my_func"(integer) CASCADE"#);
8159		assert_eq!(values.len(), 0);
8160	}
8161
8162	// Procedure tests
8163	#[test]
8164	fn test_create_procedure_basic() {
8165		use crate::types::function::FunctionLanguage;
8166
8167		let builder = PostgresQueryBuilder::new();
8168		let mut stmt = Query::create_procedure();
8169		stmt.name("my_proc")
8170			.language(FunctionLanguage::Sql)
8171			.body("SELECT 1");
8172
8173		let (sql, values) = builder.build_create_procedure(&stmt);
8174		assert_eq!(
8175			sql,
8176			r#"CREATE PROCEDURE "my_proc"() LANGUAGE SQL AS $$SELECT 1$$"#
8177		);
8178		assert_eq!(values.len(), 0);
8179	}
8180
8181	#[test]
8182	fn test_create_procedure_or_replace() {
8183		use crate::types::function::FunctionLanguage;
8184
8185		let builder = PostgresQueryBuilder::new();
8186		let mut stmt = Query::create_procedure();
8187		stmt.name("my_proc")
8188			.or_replace()
8189			.language(FunctionLanguage::PlPgSql)
8190			.body("BEGIN SELECT 1; END;");
8191
8192		let (sql, values) = builder.build_create_procedure(&stmt);
8193		assert_eq!(
8194			sql,
8195			r#"CREATE OR REPLACE PROCEDURE "my_proc"() LANGUAGE PLPGSQL AS $$BEGIN SELECT 1; END;$$"#
8196		);
8197		assert_eq!(values.len(), 0);
8198	}
8199
8200	#[test]
8201	fn test_create_procedure_with_parameters() {
8202		use crate::types::function::FunctionLanguage;
8203
8204		let builder = PostgresQueryBuilder::new();
8205		let mut stmt = Query::create_procedure();
8206		stmt.name("my_proc")
8207			.add_parameter("a", "integer")
8208			.add_parameter("b", "text")
8209			.language(FunctionLanguage::PlPgSql)
8210			.body("BEGIN INSERT INTO log VALUES (a, b); END;");
8211
8212		let (sql, values) = builder.build_create_procedure(&stmt);
8213		assert_eq!(
8214			sql,
8215			r#"CREATE PROCEDURE "my_proc"("a" integer, "b" text) LANGUAGE PLPGSQL AS $$BEGIN INSERT INTO log VALUES (a, b); END;$$"#
8216		);
8217		assert_eq!(values.len(), 0);
8218	}
8219
8220	#[test]
8221	fn test_create_procedure_with_behavior() {
8222		use crate::types::function::{FunctionBehavior, FunctionLanguage};
8223
8224		let builder = PostgresQueryBuilder::new();
8225		let mut stmt = Query::create_procedure();
8226		stmt.name("my_proc")
8227			.language(FunctionLanguage::Sql)
8228			.behavior(FunctionBehavior::Immutable)
8229			.body("SELECT 1");
8230
8231		let (sql, values) = builder.build_create_procedure(&stmt);
8232		assert_eq!(
8233			sql,
8234			r#"CREATE PROCEDURE "my_proc"() LANGUAGE SQL IMMUTABLE AS $$SELECT 1$$"#
8235		);
8236		assert_eq!(values.len(), 0);
8237	}
8238
8239	#[test]
8240	fn test_create_procedure_with_security() {
8241		use crate::types::function::{FunctionLanguage, FunctionSecurity};
8242
8243		let builder = PostgresQueryBuilder::new();
8244		let mut stmt = Query::create_procedure();
8245		stmt.name("my_proc")
8246			.language(FunctionLanguage::Sql)
8247			.security(FunctionSecurity::Definer)
8248			.body("SELECT 1");
8249
8250		let (sql, values) = builder.build_create_procedure(&stmt);
8251		assert_eq!(
8252			sql,
8253			r#"CREATE PROCEDURE "my_proc"() LANGUAGE SQL SECURITY DEFINER AS $$SELECT 1$$"#
8254		);
8255		assert_eq!(values.len(), 0);
8256	}
8257
8258	#[test]
8259	fn test_create_procedure_all_options() {
8260		use crate::types::function::{FunctionBehavior, FunctionLanguage, FunctionSecurity};
8261
8262		let builder = PostgresQueryBuilder::new();
8263		let mut stmt = Query::create_procedure();
8264		stmt.name("my_proc")
8265			.or_replace()
8266			.add_parameter("a", "integer")
8267			.add_parameter("b", "text")
8268			.language(FunctionLanguage::PlPgSql)
8269			.behavior(FunctionBehavior::Immutable)
8270			.security(FunctionSecurity::Definer)
8271			.body("BEGIN INSERT INTO log VALUES (a, b); END;");
8272
8273		let (sql, values) = builder.build_create_procedure(&stmt);
8274		assert_eq!(
8275			sql,
8276			r#"CREATE OR REPLACE PROCEDURE "my_proc"("a" integer, "b" text) LANGUAGE PLPGSQL IMMUTABLE SECURITY DEFINER AS $$BEGIN INSERT INTO log VALUES (a, b); END;$$"#
8277		);
8278		assert_eq!(values.len(), 0);
8279	}
8280
8281	#[test]
8282	fn test_alter_procedure_rename_to() {
8283		let builder = PostgresQueryBuilder::new();
8284		let mut stmt = Query::alter_procedure();
8285		stmt.name("my_proc").rename_to("new_proc");
8286
8287		let (sql, values) = builder.build_alter_procedure(&stmt);
8288		assert_eq!(sql, r#"ALTER PROCEDURE "my_proc" RENAME TO "new_proc""#);
8289		assert_eq!(values.len(), 0);
8290	}
8291
8292	#[test]
8293	fn test_alter_procedure_owner_to() {
8294		let builder = PostgresQueryBuilder::new();
8295		let mut stmt = Query::alter_procedure();
8296		stmt.name("my_proc").owner_to("new_owner");
8297
8298		let (sql, values) = builder.build_alter_procedure(&stmt);
8299		assert_eq!(sql, r#"ALTER PROCEDURE "my_proc" OWNER TO "new_owner""#);
8300		assert_eq!(values.len(), 0);
8301	}
8302
8303	#[test]
8304	fn test_alter_procedure_set_schema() {
8305		let builder = PostgresQueryBuilder::new();
8306		let mut stmt = Query::alter_procedure();
8307		stmt.name("my_proc").set_schema("new_schema");
8308
8309		let (sql, values) = builder.build_alter_procedure(&stmt);
8310		assert_eq!(sql, r#"ALTER PROCEDURE "my_proc" SET SCHEMA "new_schema""#);
8311		assert_eq!(values.len(), 0);
8312	}
8313
8314	#[test]
8315	fn test_alter_procedure_with_signature() {
8316		let builder = PostgresQueryBuilder::new();
8317		let mut stmt = Query::alter_procedure();
8318		stmt.name("my_proc")
8319			.add_parameter("a", "integer")
8320			.rename_to("new_proc");
8321
8322		let (sql, values) = builder.build_alter_procedure(&stmt);
8323		assert_eq!(
8324			sql,
8325			r#"ALTER PROCEDURE "my_proc"("a" integer) RENAME TO "new_proc""#
8326		);
8327		assert_eq!(values.len(), 0);
8328	}
8329
8330	#[test]
8331	fn test_drop_procedure_basic() {
8332		let builder = PostgresQueryBuilder::new();
8333		let mut stmt = Query::drop_procedure();
8334		stmt.name("my_proc");
8335
8336		let (sql, values) = builder.build_drop_procedure(&stmt);
8337		assert_eq!(sql, r#"DROP PROCEDURE "my_proc""#);
8338		assert_eq!(values.len(), 0);
8339	}
8340
8341	#[test]
8342	fn test_drop_procedure_if_exists() {
8343		let builder = PostgresQueryBuilder::new();
8344		let mut stmt = Query::drop_procedure();
8345		stmt.name("my_proc").if_exists();
8346
8347		let (sql, values) = builder.build_drop_procedure(&stmt);
8348		assert_eq!(sql, r#"DROP PROCEDURE IF EXISTS "my_proc""#);
8349		assert_eq!(values.len(), 0);
8350	}
8351
8352	#[test]
8353	fn test_drop_procedure_cascade() {
8354		let builder = PostgresQueryBuilder::new();
8355		let mut stmt = Query::drop_procedure();
8356		stmt.name("my_proc").cascade();
8357
8358		let (sql, values) = builder.build_drop_procedure(&stmt);
8359		assert_eq!(sql, r#"DROP PROCEDURE "my_proc" CASCADE"#);
8360		assert_eq!(values.len(), 0);
8361	}
8362
8363	#[test]
8364	fn test_drop_procedure_with_signature() {
8365		let builder = PostgresQueryBuilder::new();
8366		let mut stmt = Query::drop_procedure();
8367		stmt.name("my_proc").add_parameter("", "integer");
8368
8369		let (sql, values) = builder.build_drop_procedure(&stmt);
8370		assert_eq!(sql, r#"DROP PROCEDURE "my_proc"(integer)"#);
8371		assert_eq!(values.len(), 0);
8372	}
8373
8374	#[test]
8375	fn test_drop_procedure_all_options() {
8376		let builder = PostgresQueryBuilder::new();
8377		let mut stmt = Query::drop_procedure();
8378		stmt.name("my_proc")
8379			.if_exists()
8380			.add_parameter("", "integer")
8381			.cascade();
8382
8383		let (sql, values) = builder.build_drop_procedure(&stmt);
8384		assert_eq!(
8385			sql,
8386			r#"DROP PROCEDURE IF EXISTS "my_proc"(integer) CASCADE"#
8387		);
8388		assert_eq!(values.len(), 0);
8389	}
8390
8391	// CREATE TYPE tests
8392	#[test]
8393	fn test_create_type_enum() {
8394		let builder = PostgresQueryBuilder::new();
8395		let mut stmt = Query::create_type();
8396		stmt.name("mood")
8397			.as_enum(vec!["happy".to_string(), "sad".to_string()]);
8398
8399		let (sql, values) = builder.build_create_type(&stmt);
8400		assert_eq!(sql, r#"CREATE TYPE "mood" AS ENUM ('happy', 'sad')"#);
8401		assert_eq!(values.len(), 0);
8402	}
8403
8404	#[test]
8405	fn test_create_type_enum_with_single_quote() {
8406		let builder = PostgresQueryBuilder::new();
8407		let mut stmt = Query::create_type();
8408		stmt.name("test").as_enum(vec!["it's".to_string()]);
8409
8410		let (sql, values) = builder.build_create_type(&stmt);
8411		assert_eq!(sql, r#"CREATE TYPE "test" AS ENUM ('it''s')"#);
8412		assert_eq!(values.len(), 0);
8413	}
8414
8415	#[test]
8416	fn test_create_type_composite() {
8417		let builder = PostgresQueryBuilder::new();
8418		let mut stmt = Query::create_type();
8419		stmt.name("address").as_composite(vec![
8420			("street".to_string(), "text".to_string()),
8421			("city".to_string(), "text".to_string()),
8422		]);
8423
8424		let (sql, values) = builder.build_create_type(&stmt);
8425		assert_eq!(
8426			sql,
8427			r#"CREATE TYPE "address" AS ("street" text, "city" text)"#
8428		);
8429		assert_eq!(values.len(), 0);
8430	}
8431
8432	#[test]
8433	fn test_create_type_domain_minimal() {
8434		let builder = PostgresQueryBuilder::new();
8435		let mut stmt = Query::create_type();
8436		stmt.name("positive_int").as_domain("integer".to_string());
8437
8438		let (sql, values) = builder.build_create_type(&stmt);
8439		assert_eq!(sql, r#"CREATE TYPE "positive_int" AS integer"#);
8440		assert_eq!(values.len(), 0);
8441	}
8442
8443	#[test]
8444	fn test_create_type_domain_with_constraint() {
8445		let builder = PostgresQueryBuilder::new();
8446		let mut stmt = Query::create_type();
8447		stmt.name("positive_int")
8448			.as_domain("integer".to_string())
8449			.constraint(
8450				"check_positive".to_string(),
8451				"CHECK (VALUE > 0)".to_string(),
8452			);
8453
8454		let (sql, values) = builder.build_create_type(&stmt);
8455		assert_eq!(
8456			sql,
8457			r#"CREATE TYPE "positive_int" AS integer CHECK (VALUE > 0)"#
8458		);
8459		assert_eq!(values.len(), 0);
8460	}
8461
8462	#[test]
8463	fn test_create_type_domain_with_default() {
8464		let builder = PostgresQueryBuilder::new();
8465		let mut stmt = Query::create_type();
8466		stmt.name("my_domain")
8467			.as_domain("integer".to_string())
8468			.default_value("0".to_string());
8469
8470		let (sql, values) = builder.build_create_type(&stmt);
8471		assert_eq!(sql, r#"CREATE TYPE "my_domain" AS integer DEFAULT 0"#);
8472		assert_eq!(values.len(), 0);
8473	}
8474
8475	#[test]
8476	fn test_create_type_domain_not_null() {
8477		let builder = PostgresQueryBuilder::new();
8478		let mut stmt = Query::create_type();
8479		stmt.name("my_domain")
8480			.as_domain("integer".to_string())
8481			.not_null();
8482
8483		let (sql, values) = builder.build_create_type(&stmt);
8484		assert_eq!(sql, r#"CREATE TYPE "my_domain" AS integer NOT NULL"#);
8485		assert_eq!(values.len(), 0);
8486	}
8487
8488	#[test]
8489	fn test_create_type_domain_full() {
8490		let builder = PostgresQueryBuilder::new();
8491		let mut stmt = Query::create_type();
8492		stmt.name("positive_int")
8493			.as_domain("integer".to_string())
8494			.default_value("1".to_string())
8495			.constraint(
8496				"check_positive".to_string(),
8497				"CHECK (VALUE > 0)".to_string(),
8498			)
8499			.not_null();
8500
8501		let (sql, values) = builder.build_create_type(&stmt);
8502		assert_eq!(
8503			sql,
8504			r#"CREATE TYPE "positive_int" AS integer DEFAULT 1 CHECK (VALUE > 0) NOT NULL"#
8505		);
8506		assert_eq!(values.len(), 0);
8507	}
8508
8509	#[test]
8510	fn test_create_type_range_minimal() {
8511		let builder = PostgresQueryBuilder::new();
8512		let mut stmt = Query::create_type();
8513		stmt.name("int_range").as_range("integer".to_string());
8514
8515		let (sql, values) = builder.build_create_type(&stmt);
8516		assert_eq!(
8517			sql,
8518			r#"CREATE TYPE "int_range" AS RANGE (SUBTYPE = integer)"#
8519		);
8520		assert_eq!(values.len(), 0);
8521	}
8522
8523	#[test]
8524	fn test_create_type_range_with_subtype_diff() {
8525		let builder = PostgresQueryBuilder::new();
8526		let mut stmt = Query::create_type();
8527		stmt.name("int_range")
8528			.as_range("integer".to_string())
8529			.subtype_diff("int4range_subdiff".to_string());
8530
8531		let (sql, values) = builder.build_create_type(&stmt);
8532		assert_eq!(
8533			sql,
8534			r#"CREATE TYPE "int_range" AS RANGE (SUBTYPE = integer, SUBTYPE_DIFF = int4range_subdiff)"#
8535		);
8536		assert_eq!(values.len(), 0);
8537	}
8538
8539	#[test]
8540	fn test_create_type_range_full() {
8541		let builder = PostgresQueryBuilder::new();
8542		let mut stmt = Query::create_type();
8543		stmt.name("int_range")
8544			.as_range("integer".to_string())
8545			.subtype_diff("int4range_subdiff".to_string())
8546			.canonical("int4range_canonical".to_string());
8547
8548		let (sql, values) = builder.build_create_type(&stmt);
8549		assert_eq!(
8550			sql,
8551			r#"CREATE TYPE "int_range" AS RANGE (SUBTYPE = integer, SUBTYPE_DIFF = int4range_subdiff, CANONICAL = int4range_canonical)"#
8552		);
8553		assert_eq!(values.len(), 0);
8554	}
8555
8556	// ALTER TYPE tests
8557	#[test]
8558	fn test_alter_type_rename_to() {
8559		let builder = PostgresQueryBuilder::new();
8560		let mut stmt = Query::alter_type();
8561		stmt.name("old_name").rename_to("new_name");
8562
8563		let (sql, values) = builder.build_alter_type(&stmt);
8564		assert_eq!(sql, r#"ALTER TYPE "old_name" RENAME TO "new_name""#);
8565		assert_eq!(values.len(), 0);
8566	}
8567
8568	#[test]
8569	fn test_alter_type_owner_to() {
8570		let builder = PostgresQueryBuilder::new();
8571		let mut stmt = Query::alter_type();
8572		stmt.name("my_type").owner_to("new_owner");
8573
8574		let (sql, values) = builder.build_alter_type(&stmt);
8575		assert_eq!(sql, r#"ALTER TYPE "my_type" OWNER TO "new_owner""#);
8576		assert_eq!(values.len(), 0);
8577	}
8578
8579	#[test]
8580	fn test_alter_type_set_schema() {
8581		let builder = PostgresQueryBuilder::new();
8582		let mut stmt = Query::alter_type();
8583		stmt.name("my_type").set_schema("new_schema");
8584
8585		let (sql, values) = builder.build_alter_type(&stmt);
8586		assert_eq!(sql, r#"ALTER TYPE "my_type" SET SCHEMA "new_schema""#);
8587		assert_eq!(values.len(), 0);
8588	}
8589
8590	#[test]
8591	fn test_alter_type_add_value() {
8592		let builder = PostgresQueryBuilder::new();
8593		let mut stmt = Query::alter_type();
8594		stmt.name("mood").add_value("excited", None);
8595
8596		let (sql, values) = builder.build_alter_type(&stmt);
8597		assert_eq!(sql, r#"ALTER TYPE "mood" ADD VALUE 'excited'"#);
8598		assert_eq!(values.len(), 0);
8599	}
8600
8601	#[test]
8602	fn test_alter_type_add_value_before() {
8603		let builder = PostgresQueryBuilder::new();
8604		let mut stmt = Query::alter_type();
8605		stmt.name("mood").add_value("excited", Some("happy"));
8606
8607		let (sql, values) = builder.build_alter_type(&stmt);
8608		assert_eq!(
8609			sql,
8610			r#"ALTER TYPE "mood" ADD VALUE 'excited' BEFORE 'happy'"#
8611		);
8612		assert_eq!(values.len(), 0);
8613	}
8614
8615	#[test]
8616	fn test_alter_type_rename_value() {
8617		let builder = PostgresQueryBuilder::new();
8618		let mut stmt = Query::alter_type();
8619		stmt.name("mood").rename_value("happy", "joyful");
8620
8621		let (sql, values) = builder.build_alter_type(&stmt);
8622		assert_eq!(sql, r#"ALTER TYPE "mood" RENAME VALUE 'happy' TO 'joyful'"#);
8623		assert_eq!(values.len(), 0);
8624	}
8625
8626	#[test]
8627	fn test_alter_type_add_constraint() {
8628		let builder = PostgresQueryBuilder::new();
8629		let mut stmt = Query::alter_type();
8630		stmt.name("my_domain")
8631			.add_constraint("positive_check", "CHECK (VALUE > 0)");
8632
8633		let (sql, values) = builder.build_alter_type(&stmt);
8634		assert_eq!(
8635			sql,
8636			r#"ALTER TYPE "my_domain" ADD CONSTRAINT "positive_check" CHECK (VALUE > 0)"#
8637		);
8638		assert_eq!(values.len(), 0);
8639	}
8640
8641	#[test]
8642	fn test_alter_type_drop_constraint() {
8643		let builder = PostgresQueryBuilder::new();
8644		let mut stmt = Query::alter_type();
8645		stmt.name("my_domain")
8646			.drop_constraint("my_constraint", false);
8647
8648		let (sql, values) = builder.build_alter_type(&stmt);
8649		assert_eq!(
8650			sql,
8651			r#"ALTER TYPE "my_domain" DROP CONSTRAINT "my_constraint""#
8652		);
8653		assert_eq!(values.len(), 0);
8654	}
8655
8656	#[test]
8657	fn test_alter_type_drop_constraint_if_exists() {
8658		let builder = PostgresQueryBuilder::new();
8659		let mut stmt = Query::alter_type();
8660		stmt.name("my_domain")
8661			.drop_constraint("my_constraint", true);
8662
8663		let (sql, values) = builder.build_alter_type(&stmt);
8664		assert_eq!(
8665			sql,
8666			r#"ALTER TYPE "my_domain" DROP CONSTRAINT IF EXISTS "my_constraint""#
8667		);
8668		assert_eq!(values.len(), 0);
8669	}
8670
8671	#[test]
8672	fn test_alter_type_set_default() {
8673		let builder = PostgresQueryBuilder::new();
8674		let mut stmt = Query::alter_type();
8675		stmt.name("my_domain").set_default("0");
8676
8677		let (sql, values) = builder.build_alter_type(&stmt);
8678		assert_eq!(sql, r#"ALTER TYPE "my_domain" SET DEFAULT 0"#);
8679		assert_eq!(values.len(), 0);
8680	}
8681
8682	#[test]
8683	fn test_alter_type_drop_default() {
8684		let builder = PostgresQueryBuilder::new();
8685		let mut stmt = Query::alter_type();
8686		stmt.name("my_domain").drop_default();
8687
8688		let (sql, values) = builder.build_alter_type(&stmt);
8689		assert_eq!(sql, r#"ALTER TYPE "my_domain" DROP DEFAULT"#);
8690		assert_eq!(values.len(), 0);
8691	}
8692
8693	#[test]
8694	fn test_alter_type_set_not_null() {
8695		let builder = PostgresQueryBuilder::new();
8696		let mut stmt = Query::alter_type();
8697		stmt.name("my_domain").set_not_null();
8698
8699		let (sql, values) = builder.build_alter_type(&stmt);
8700		assert_eq!(sql, r#"ALTER TYPE "my_domain" SET NOT NULL"#);
8701		assert_eq!(values.len(), 0);
8702	}
8703
8704	#[test]
8705	fn test_alter_type_drop_not_null() {
8706		let builder = PostgresQueryBuilder::new();
8707		let mut stmt = Query::alter_type();
8708		stmt.name("my_domain").drop_not_null();
8709
8710		let (sql, values) = builder.build_alter_type(&stmt);
8711		assert_eq!(sql, r#"ALTER TYPE "my_domain" DROP NOT NULL"#);
8712		assert_eq!(values.len(), 0);
8713	}
8714
8715	// DROP TYPE tests
8716	#[test]
8717	fn test_drop_type_basic() {
8718		let builder = PostgresQueryBuilder::new();
8719		let mut stmt = Query::drop_type();
8720		stmt.name("my_type");
8721
8722		let (sql, values) = builder.build_drop_type(&stmt);
8723		assert_eq!(sql, r#"DROP TYPE "my_type""#);
8724		assert_eq!(values.len(), 0);
8725	}
8726
8727	#[test]
8728	fn test_drop_type_if_exists() {
8729		let builder = PostgresQueryBuilder::new();
8730		let mut stmt = Query::drop_type();
8731		stmt.name("my_type").if_exists();
8732
8733		let (sql, values) = builder.build_drop_type(&stmt);
8734		assert_eq!(sql, r#"DROP TYPE IF EXISTS "my_type""#);
8735		assert_eq!(values.len(), 0);
8736	}
8737
8738	#[test]
8739	fn test_drop_type_cascade() {
8740		let builder = PostgresQueryBuilder::new();
8741		let mut stmt = Query::drop_type();
8742		stmt.name("my_type").cascade();
8743
8744		let (sql, values) = builder.build_drop_type(&stmt);
8745		assert_eq!(sql, r#"DROP TYPE "my_type" CASCADE"#);
8746		assert_eq!(values.len(), 0);
8747	}
8748
8749	#[test]
8750	fn test_drop_type_restrict() {
8751		let builder = PostgresQueryBuilder::new();
8752		let mut stmt = Query::drop_type();
8753		stmt.name("my_type").restrict();
8754
8755		let (sql, values) = builder.build_drop_type(&stmt);
8756		assert_eq!(sql, r#"DROP TYPE "my_type" RESTRICT"#);
8757		assert_eq!(values.len(), 0);
8758	}
8759
8760	#[test]
8761	fn test_drop_type_all_options() {
8762		let builder = PostgresQueryBuilder::new();
8763		let mut stmt = Query::drop_type();
8764		stmt.name("my_type").if_exists().cascade();
8765
8766		let (sql, values) = builder.build_drop_type(&stmt);
8767		assert_eq!(sql, r#"DROP TYPE IF EXISTS "my_type" CASCADE"#);
8768		assert_eq!(values.len(), 0);
8769	}
8770
8771	// MySQL-specific maintenance command panic tests
8772	#[test]
8773	#[should_panic(expected = "PostgreSQL users should use VACUUM ANALYZE")]
8774	fn test_optimize_table_panics() {
8775		let builder = PostgresQueryBuilder::new();
8776		let mut stmt = Query::optimize_table();
8777		stmt.table("users");
8778
8779		let _ = builder.build_optimize_table(&stmt);
8780	}
8781
8782	#[test]
8783	#[should_panic(expected = "not supported in PostgreSQL")]
8784	fn test_repair_table_panics() {
8785		let builder = PostgresQueryBuilder::new();
8786		let mut stmt = Query::repair_table();
8787		stmt.table("users");
8788
8789		let _ = builder.build_repair_table(&stmt);
8790	}
8791
8792	#[test]
8793	#[should_panic(expected = "not supported in PostgreSQL")]
8794	fn test_check_table_panics() {
8795		let builder = PostgresQueryBuilder::new();
8796		let mut stmt = Query::check_table();
8797		stmt.table("users");
8798
8799		let _ = builder.build_check_table(&stmt);
8800	}
8801
8802	// DCL (Data Control Language) Tests
8803
8804	#[test]
8805	fn test_grant_single_privilege_on_table() {
8806		use crate::dcl::{GrantStatement, Privilege};
8807
8808		let builder = PostgresQueryBuilder::new();
8809		let stmt = GrantStatement::new()
8810			.privilege(Privilege::Select)
8811			.on_table("users")
8812			.to("app_user");
8813
8814		let (sql, values) = builder.build_grant(&stmt);
8815		assert_eq!(sql, r#"GRANT SELECT ON TABLE "users" TO "app_user""#);
8816		assert!(values.is_empty());
8817	}
8818
8819	#[test]
8820	fn test_grant_multiple_privileges() {
8821		use crate::dcl::{GrantStatement, Privilege};
8822
8823		let builder = PostgresQueryBuilder::new();
8824		let stmt = GrantStatement::new()
8825			.privileges(vec![
8826				Privilege::Select,
8827				Privilege::Insert,
8828				Privilege::Update,
8829			])
8830			.on_table("users")
8831			.to("app_user");
8832
8833		let (sql, values) = builder.build_grant(&stmt);
8834		assert_eq!(
8835			sql,
8836			r#"GRANT SELECT, INSERT, UPDATE ON TABLE "users" TO "app_user""#
8837		);
8838		assert!(values.is_empty());
8839	}
8840
8841	#[test]
8842	fn test_grant_multiple_objects() {
8843		use crate::dcl::{GrantStatement, ObjectType, Privilege};
8844
8845		let builder = PostgresQueryBuilder::new();
8846		let stmt = GrantStatement::new()
8847			.privilege(Privilege::Select)
8848			.object_type(ObjectType::Table)
8849			.object("users")
8850			.object("posts")
8851			.to("app_user");
8852
8853		let (sql, values) = builder.build_grant(&stmt);
8854		assert_eq!(
8855			sql,
8856			r#"GRANT SELECT ON TABLE "users", "posts" TO "app_user""#
8857		);
8858		assert!(values.is_empty());
8859	}
8860
8861	#[test]
8862	fn test_grant_multiple_grantees() {
8863		use crate::dcl::{GrantStatement, Grantee, Privilege};
8864
8865		let builder = PostgresQueryBuilder::new();
8866		let stmt = GrantStatement::new()
8867			.privilege(Privilege::Select)
8868			.on_table("users")
8869			.grantee(Grantee::role("app_user"))
8870			.grantee(Grantee::role("readonly_user"));
8871
8872		let (sql, values) = builder.build_grant(&stmt);
8873		assert_eq!(
8874			sql,
8875			r#"GRANT SELECT ON TABLE "users" TO "app_user", "readonly_user""#
8876		);
8877		assert!(values.is_empty());
8878	}
8879
8880	#[test]
8881	fn test_grant_with_grant_option() {
8882		use crate::dcl::{GrantStatement, Privilege};
8883
8884		let builder = PostgresQueryBuilder::new();
8885		let stmt = GrantStatement::new()
8886			.privilege(Privilege::Select)
8887			.on_table("users")
8888			.to("app_user")
8889			.with_grant_option(true);
8890
8891		let (sql, values) = builder.build_grant(&stmt);
8892		assert_eq!(
8893			sql,
8894			r#"GRANT SELECT ON TABLE "users" TO "app_user" WITH GRANT OPTION"#
8895		);
8896		assert!(values.is_empty());
8897	}
8898
8899	#[test]
8900	fn test_grant_with_granted_by() {
8901		use crate::dcl::{GrantStatement, Grantee, Privilege};
8902
8903		let builder = PostgresQueryBuilder::new();
8904		let stmt = GrantStatement::new()
8905			.privilege(Privilege::Select)
8906			.on_table("users")
8907			.to("app_user")
8908			.granted_by(Grantee::role("admin"));
8909
8910		let (sql, values) = builder.build_grant(&stmt);
8911		assert_eq!(
8912			sql,
8913			r#"GRANT SELECT ON TABLE "users" TO "app_user" GRANTED BY "admin""#
8914		);
8915		assert!(values.is_empty());
8916	}
8917
8918	#[test]
8919	fn test_grant_on_database() {
8920		use crate::dcl::{GrantStatement, Privilege};
8921
8922		let builder = PostgresQueryBuilder::new();
8923		let stmt = GrantStatement::new()
8924			.privilege(Privilege::Create)
8925			.on_database("mydb")
8926			.to("app_user");
8927
8928		let (sql, values) = builder.build_grant(&stmt);
8929		assert_eq!(sql, r#"GRANT CREATE ON DATABASE "mydb" TO "app_user""#);
8930		assert!(values.is_empty());
8931	}
8932
8933	#[test]
8934	fn test_grant_on_schema() {
8935		use crate::dcl::{GrantStatement, Privilege};
8936
8937		let builder = PostgresQueryBuilder::new();
8938		let stmt = GrantStatement::new()
8939			.privilege(Privilege::Usage)
8940			.on_schema("public")
8941			.to("app_user");
8942
8943		let (sql, values) = builder.build_grant(&stmt);
8944		assert_eq!(sql, r#"GRANT USAGE ON SCHEMA "public" TO "app_user""#);
8945		assert!(values.is_empty());
8946	}
8947
8948	#[test]
8949	fn test_grant_on_sequence() {
8950		use crate::dcl::{GrantStatement, Privilege};
8951
8952		let builder = PostgresQueryBuilder::new();
8953		let stmt = GrantStatement::new()
8954			.privilege(Privilege::Usage)
8955			.on_sequence("user_id_seq")
8956			.to("app_user");
8957
8958		let (sql, values) = builder.build_grant(&stmt);
8959		assert_eq!(
8960			sql,
8961			r#"GRANT USAGE ON SEQUENCE "user_id_seq" TO "app_user""#
8962		);
8963		assert!(values.is_empty());
8964	}
8965
8966	#[test]
8967	fn test_grant_all_privileges() {
8968		use crate::dcl::{GrantStatement, Privilege};
8969
8970		let builder = PostgresQueryBuilder::new();
8971		let stmt = GrantStatement::new()
8972			.privilege(Privilege::All)
8973			.on_table("users")
8974			.to("admin");
8975
8976		let (sql, values) = builder.build_grant(&stmt);
8977		assert_eq!(sql, r#"GRANT ALL PRIVILEGES ON TABLE "users" TO "admin""#);
8978		assert!(values.is_empty());
8979	}
8980
8981	#[test]
8982	fn test_grant_to_public() {
8983		use crate::dcl::{GrantStatement, Grantee, Privilege};
8984
8985		let builder = PostgresQueryBuilder::new();
8986		let stmt = GrantStatement::new()
8987			.privilege(Privilege::Select)
8988			.on_table("public_data")
8989			.grantee(Grantee::Public);
8990
8991		let (sql, values) = builder.build_grant(&stmt);
8992		assert_eq!(sql, r#"GRANT SELECT ON TABLE "public_data" TO PUBLIC"#);
8993		assert!(values.is_empty());
8994	}
8995
8996	#[test]
8997	fn test_grant_to_current_user() {
8998		use crate::dcl::{GrantStatement, Grantee, Privilege};
8999
9000		let builder = PostgresQueryBuilder::new();
9001		let stmt = GrantStatement::new()
9002			.privilege(Privilege::Select)
9003			.on_table("users")
9004			.grantee(Grantee::CurrentUser);
9005
9006		let (sql, values) = builder.build_grant(&stmt);
9007		assert_eq!(sql, r#"GRANT SELECT ON TABLE "users" TO CURRENT_USER"#);
9008		assert!(values.is_empty());
9009	}
9010
9011	#[test]
9012	fn test_grant_complex() {
9013		use crate::dcl::{GrantStatement, Grantee, Privilege};
9014
9015		let builder = PostgresQueryBuilder::new();
9016		let stmt = GrantStatement::new()
9017			.privileges(vec![
9018				Privilege::Select,
9019				Privilege::Insert,
9020				Privilege::Update,
9021			])
9022			.on_table("users")
9023			.on_table("posts")
9024			.grantee(Grantee::role("app_user"))
9025			.grantee(Grantee::role("readonly_user"))
9026			.with_grant_option(true)
9027			.granted_by(Grantee::role("admin"));
9028
9029		let (sql, values) = builder.build_grant(&stmt);
9030		assert!(sql.starts_with("GRANT SELECT, INSERT, UPDATE ON TABLE"));
9031		assert!(sql.contains(r#""users", "posts""#));
9032		assert!(sql.contains(r#"TO "app_user", "readonly_user""#));
9033		assert!(sql.contains("WITH GRANT OPTION"));
9034		assert!(sql.contains(r#"GRANTED BY "admin""#));
9035		assert!(values.is_empty());
9036	}
9037
9038	// REVOKE tests
9039
9040	#[test]
9041	fn test_revoke_single_privilege() {
9042		use crate::dcl::{Privilege, RevokeStatement};
9043
9044		let builder = PostgresQueryBuilder::new();
9045		let stmt = RevokeStatement::new()
9046			.privilege(Privilege::Insert)
9047			.from_table("users")
9048			.from("app_user");
9049
9050		let (sql, values) = builder.build_revoke(&stmt);
9051		assert_eq!(sql, r#"REVOKE INSERT ON TABLE "users" FROM "app_user""#);
9052		assert!(values.is_empty());
9053	}
9054
9055	#[test]
9056	fn test_revoke_multiple_privileges() {
9057		use crate::dcl::{Privilege, RevokeStatement};
9058
9059		let builder = PostgresQueryBuilder::new();
9060		let stmt = RevokeStatement::new()
9061			.privileges(vec![
9062				Privilege::Select,
9063				Privilege::Insert,
9064				Privilege::Update,
9065			])
9066			.from_table("users")
9067			.from("app_user");
9068
9069		let (sql, values) = builder.build_revoke(&stmt);
9070		assert_eq!(
9071			sql,
9072			r#"REVOKE SELECT, INSERT, UPDATE ON TABLE "users" FROM "app_user""#
9073		);
9074		assert!(values.is_empty());
9075	}
9076
9077	#[test]
9078	fn test_revoke_with_cascade() {
9079		use crate::dcl::{Privilege, RevokeStatement};
9080
9081		let builder = PostgresQueryBuilder::new();
9082		let stmt = RevokeStatement::new()
9083			.privilege(Privilege::All)
9084			.from_table("users")
9085			.from("app_user")
9086			.cascade(true);
9087
9088		let (sql, values) = builder.build_revoke(&stmt);
9089		assert_eq!(
9090			sql,
9091			r#"REVOKE ALL PRIVILEGES ON TABLE "users" FROM "app_user" CASCADE"#
9092		);
9093		assert!(values.is_empty());
9094	}
9095
9096	#[test]
9097	fn test_revoke_grant_option_for() {
9098		use crate::dcl::{Privilege, RevokeStatement};
9099
9100		let builder = PostgresQueryBuilder::new();
9101		let stmt = RevokeStatement::new()
9102			.privilege(Privilege::Select)
9103			.from_table("users")
9104			.from("app_user")
9105			.grant_option_for(true);
9106
9107		let (sql, values) = builder.build_revoke(&stmt);
9108		assert_eq!(
9109			sql,
9110			r#"REVOKE GRANT OPTION FOR SELECT ON TABLE "users" FROM "app_user""#
9111		);
9112		assert!(values.is_empty());
9113	}
9114
9115	#[test]
9116	fn test_revoke_from_database() {
9117		use crate::dcl::{Privilege, RevokeStatement};
9118
9119		let builder = PostgresQueryBuilder::new();
9120		let stmt = RevokeStatement::new()
9121			.privilege(Privilege::Create)
9122			.from_database("mydb")
9123			.from("app_user");
9124
9125		let (sql, values) = builder.build_revoke(&stmt);
9126		assert_eq!(sql, r#"REVOKE CREATE ON DATABASE "mydb" FROM "app_user""#);
9127		assert!(values.is_empty());
9128	}
9129
9130	#[test]
9131	fn test_revoke_from_schema() {
9132		use crate::dcl::{Privilege, RevokeStatement};
9133
9134		let builder = PostgresQueryBuilder::new();
9135		let stmt = RevokeStatement::new()
9136			.privilege(Privilege::Usage)
9137			.from_schema("public")
9138			.from("app_user");
9139
9140		let (sql, values) = builder.build_revoke(&stmt);
9141		assert_eq!(sql, r#"REVOKE USAGE ON SCHEMA "public" FROM "app_user""#);
9142		assert!(values.is_empty());
9143	}
9144
9145	#[test]
9146	fn test_revoke_from_sequence() {
9147		use crate::dcl::{Privilege, RevokeStatement};
9148
9149		let builder = PostgresQueryBuilder::new();
9150		let stmt = RevokeStatement::new()
9151			.privilege(Privilege::Usage)
9152			.from_sequence("user_id_seq")
9153			.from("app_user");
9154
9155		let (sql, values) = builder.build_revoke(&stmt);
9156		assert_eq!(
9157			sql,
9158			r#"REVOKE USAGE ON SEQUENCE "user_id_seq" FROM "app_user""#
9159		);
9160		assert!(values.is_empty());
9161	}
9162
9163	#[test]
9164	fn test_revoke_from_public() {
9165		use crate::dcl::{Grantee, Privilege, RevokeStatement};
9166
9167		let builder = PostgresQueryBuilder::new();
9168		let stmt = RevokeStatement::new()
9169			.privilege(Privilege::Select)
9170			.from_table("public_data")
9171			.grantee(Grantee::Public);
9172
9173		let (sql, values) = builder.build_revoke(&stmt);
9174		assert_eq!(sql, r#"REVOKE SELECT ON TABLE "public_data" FROM PUBLIC"#);
9175		assert!(values.is_empty());
9176	}
9177
9178	#[test]
9179	fn test_revoke_from_current_user() {
9180		use crate::dcl::{Grantee, Privilege, RevokeStatement};
9181
9182		let builder = PostgresQueryBuilder::new();
9183		let stmt = RevokeStatement::new()
9184			.privilege(Privilege::Select)
9185			.from_table("users")
9186			.grantee(Grantee::CurrentUser);
9187
9188		let (sql, values) = builder.build_revoke(&stmt);
9189		assert_eq!(sql, r#"REVOKE SELECT ON TABLE "users" FROM CURRENT_USER"#);
9190		assert!(values.is_empty());
9191	}
9192
9193	#[test]
9194	fn test_revoke_complex() {
9195		use crate::dcl::{Grantee, Privilege, RevokeStatement};
9196
9197		let builder = PostgresQueryBuilder::new();
9198		let stmt = RevokeStatement::new()
9199			.privileges(vec![Privilege::Select, Privilege::Insert])
9200			.from_table("users")
9201			.from_table("posts")
9202			.grantee(Grantee::role("app_user"))
9203			.grantee(Grantee::role("readonly_user"))
9204			.cascade(true);
9205
9206		let (sql, values) = builder.build_revoke(&stmt);
9207		assert!(sql.starts_with("REVOKE SELECT, INSERT ON TABLE"));
9208		assert!(sql.contains(r#""users", "posts""#));
9209		assert!(sql.contains(r#"FROM "app_user", "readonly_user""#));
9210		assert!(sql.contains("CASCADE"));
9211		assert!(values.is_empty());
9212	}
9213
9214	#[test]
9215	fn test_create_role_simple() {
9216		use crate::dcl::CreateRoleStatement;
9217
9218		let builder = PostgresQueryBuilder::new();
9219		let stmt = CreateRoleStatement::new().role("developer");
9220
9221		let (sql, values) = builder.build_create_role(&stmt);
9222		assert_eq!(sql, r#"CREATE ROLE "developer""#);
9223		assert!(values.is_empty());
9224	}
9225
9226	#[test]
9227	fn test_create_role_with_login() {
9228		use crate::dcl::{CreateRoleStatement, RoleAttribute};
9229		use crate::value::Value;
9230
9231		let builder = PostgresQueryBuilder::new();
9232		let stmt = CreateRoleStatement::new()
9233			.role("app_user")
9234			.attribute(RoleAttribute::Login)
9235			.attribute(RoleAttribute::Password("secret".to_string()));
9236
9237		let (sql, values) = builder.build_create_role(&stmt);
9238		assert_eq!(sql, r#"CREATE ROLE "app_user" WITH LOGIN PASSWORD $1"#);
9239		assert_eq!(values.len(), 1);
9240		assert_eq!(
9241			values[0],
9242			Value::String(Some(Box::new("secret".to_string())))
9243		);
9244	}
9245
9246	#[test]
9247	fn test_create_role_with_multiple_attributes() {
9248		use crate::dcl::{CreateRoleStatement, RoleAttribute};
9249
9250		let builder = PostgresQueryBuilder::new();
9251		let stmt = CreateRoleStatement::new()
9252			.role("superuser")
9253			.attribute(RoleAttribute::SuperUser)
9254			.attribute(RoleAttribute::CreateDb)
9255			.attribute(RoleAttribute::CreateRole)
9256			.attribute(RoleAttribute::ConnectionLimit(10));
9257
9258		let (sql, values) = builder.build_create_role(&stmt);
9259		assert_eq!(
9260			sql,
9261			r#"CREATE ROLE "superuser" WITH SUPERUSER CREATEDB CREATEROLE CONNECTION LIMIT 10"#
9262		);
9263		assert!(values.is_empty());
9264	}
9265
9266	#[test]
9267	fn test_drop_role_simple() {
9268		use crate::dcl::DropRoleStatement;
9269
9270		let builder = PostgresQueryBuilder::new();
9271		let stmt = DropRoleStatement::new().role("old_role");
9272
9273		let (sql, values) = builder.build_drop_role(&stmt);
9274		assert_eq!(sql, r#"DROP ROLE "old_role""#);
9275		assert!(values.is_empty());
9276	}
9277
9278	#[test]
9279	fn test_drop_role_if_exists() {
9280		use crate::dcl::DropRoleStatement;
9281
9282		let builder = PostgresQueryBuilder::new();
9283		let stmt = DropRoleStatement::new().role("old_role").if_exists(true);
9284
9285		let (sql, values) = builder.build_drop_role(&stmt);
9286		assert_eq!(sql, r#"DROP ROLE IF EXISTS "old_role""#);
9287		assert!(values.is_empty());
9288	}
9289
9290	#[test]
9291	fn test_drop_role_multiple() {
9292		use crate::dcl::DropRoleStatement;
9293
9294		let builder = PostgresQueryBuilder::new();
9295		let stmt = DropRoleStatement::new()
9296			.role("role1")
9297			.role("role2")
9298			.role("role3");
9299
9300		let (sql, values) = builder.build_drop_role(&stmt);
9301		assert_eq!(sql, r#"DROP ROLE "role1", "role2", "role3""#);
9302		assert!(values.is_empty());
9303	}
9304
9305	#[test]
9306	fn test_alter_role_with_attributes() {
9307		use crate::dcl::{AlterRoleStatement, RoleAttribute};
9308
9309		let builder = PostgresQueryBuilder::new();
9310		let stmt = AlterRoleStatement::new()
9311			.role("developer")
9312			.attribute(RoleAttribute::NoLogin)
9313			.attribute(RoleAttribute::ConnectionLimit(5));
9314
9315		let (sql, values) = builder.build_alter_role(&stmt);
9316		assert_eq!(
9317			sql,
9318			r#"ALTER ROLE "developer" WITH NOLOGIN CONNECTION LIMIT 5"#
9319		);
9320		assert!(values.is_empty());
9321	}
9322
9323	#[test]
9324	fn test_alter_role_rename_to() {
9325		use crate::dcl::AlterRoleStatement;
9326
9327		let builder = PostgresQueryBuilder::new();
9328		let stmt = AlterRoleStatement::new()
9329			.role("old_name")
9330			.rename_to("new_name");
9331
9332		let (sql, values) = builder.build_alter_role(&stmt);
9333		assert_eq!(sql, r#"ALTER ROLE "old_name" RENAME TO "new_name""#);
9334		assert!(values.is_empty());
9335	}
9336
9337	// CREATE USER tests
9338	#[test]
9339	fn test_create_user_basic() {
9340		use crate::dcl::CreateUserStatement;
9341
9342		let builder = PostgresQueryBuilder::new();
9343		let stmt = CreateUserStatement::new().user("app_user");
9344
9345		let (sql, values) = builder.build_create_user(&stmt);
9346		assert_eq!(sql, r#"CREATE ROLE "app_user" WITH LOGIN"#);
9347		assert!(values.is_empty());
9348	}
9349
9350	#[test]
9351	fn test_create_user_with_password() {
9352		use crate::dcl::{CreateUserStatement, RoleAttribute};
9353		use crate::value::Value;
9354
9355		let builder = PostgresQueryBuilder::new();
9356		let stmt = CreateUserStatement::new()
9357			.user("app_user")
9358			.attribute(RoleAttribute::Password("secret".to_string()));
9359
9360		let (sql, values) = builder.build_create_user(&stmt);
9361		assert_eq!(sql, r#"CREATE ROLE "app_user" WITH LOGIN PASSWORD $1"#);
9362		assert_eq!(values.len(), 1);
9363		assert_eq!(
9364			values[0],
9365			Value::String(Some(Box::new("secret".to_string())))
9366		);
9367	}
9368
9369	// DROP USER tests
9370	#[test]
9371	fn test_drop_user_basic() {
9372		use crate::dcl::DropUserStatement;
9373
9374		let builder = PostgresQueryBuilder::new();
9375		let stmt = DropUserStatement::new().user("app_user");
9376
9377		let (sql, values) = builder.build_drop_user(&stmt);
9378		assert_eq!(sql, r#"DROP ROLE "app_user""#);
9379		assert!(values.is_empty());
9380	}
9381
9382	#[test]
9383	fn test_drop_user_if_exists() {
9384		use crate::dcl::DropUserStatement;
9385
9386		let builder = PostgresQueryBuilder::new();
9387		let stmt = DropUserStatement::new().user("app_user").if_exists(true);
9388
9389		let (sql, values) = builder.build_drop_user(&stmt);
9390		assert_eq!(sql, r#"DROP ROLE IF EXISTS "app_user""#);
9391		assert!(values.is_empty());
9392	}
9393
9394	// ALTER USER tests
9395	#[test]
9396	fn test_alter_user_basic() {
9397		use crate::dcl::{AlterUserStatement, RoleAttribute};
9398		use crate::value::Value;
9399
9400		let builder = PostgresQueryBuilder::new();
9401		let stmt = AlterUserStatement::new()
9402			.user("app_user")
9403			.attribute(RoleAttribute::Password("new_secret".to_string()));
9404
9405		let (sql, values) = builder.build_alter_user(&stmt);
9406		assert_eq!(sql, r#"ALTER ROLE "app_user" WITH PASSWORD $1"#);
9407		assert_eq!(values.len(), 1);
9408		assert_eq!(
9409			values[0],
9410			Value::String(Some(Box::new("new_secret".to_string())))
9411		);
9412	}
9413
9414	// RENAME USER panic test
9415	#[test]
9416	#[should_panic(expected = "RENAME USER is not supported by PostgreSQL")]
9417	fn test_rename_user_panics() {
9418		use crate::dcl::RenameUserStatement;
9419
9420		let builder = PostgresQueryBuilder::new();
9421		let stmt = RenameUserStatement::new().rename("old", "new");
9422
9423		builder.build_rename_user(&stmt);
9424	}
9425
9426	// SET ROLE tests
9427	#[test]
9428	fn test_set_role_named() {
9429		use crate::dcl::{RoleTarget, SetRoleStatement};
9430
9431		let builder = PostgresQueryBuilder::new();
9432		let stmt = SetRoleStatement::new().role(RoleTarget::Named("admin".to_string()));
9433
9434		let (sql, values) = builder.build_set_role(&stmt);
9435		assert_eq!(sql, r#"SET ROLE "admin""#);
9436		assert!(values.is_empty());
9437	}
9438
9439	#[test]
9440	fn test_set_role_none() {
9441		use crate::dcl::{RoleTarget, SetRoleStatement};
9442
9443		let builder = PostgresQueryBuilder::new();
9444		let stmt = SetRoleStatement::new().role(RoleTarget::None);
9445
9446		let (sql, values) = builder.build_set_role(&stmt);
9447		assert_eq!(sql, "SET ROLE NONE");
9448		assert!(values.is_empty());
9449	}
9450
9451	#[test]
9452	#[should_panic(expected = "SET ROLE ALL is not supported by PostgreSQL")]
9453	fn test_set_role_all_panics() {
9454		use crate::dcl::{RoleTarget, SetRoleStatement};
9455
9456		let builder = PostgresQueryBuilder::new();
9457		let stmt = SetRoleStatement::new().role(RoleTarget::All);
9458
9459		builder.build_set_role(&stmt);
9460	}
9461
9462	// RESET ROLE test
9463	#[test]
9464	fn test_reset_role() {
9465		use crate::dcl::ResetRoleStatement;
9466
9467		let builder = PostgresQueryBuilder::new();
9468		let stmt = ResetRoleStatement::new();
9469
9470		let (sql, values) = builder.build_reset_role(&stmt);
9471		assert_eq!(sql, "RESET ROLE");
9472		assert!(values.is_empty());
9473	}
9474
9475	// SET DEFAULT ROLE panic test
9476	#[test]
9477	#[should_panic(expected = "SET DEFAULT ROLE is not supported by PostgreSQL")]
9478	fn test_set_default_role_panics() {
9479		use crate::dcl::{DefaultRoleSpec, SetDefaultRoleStatement};
9480
9481		let builder = PostgresQueryBuilder::new();
9482		let stmt = SetDefaultRoleStatement::new()
9483			.roles(DefaultRoleSpec::All)
9484			.user("app_user");
9485
9486		builder.build_set_default_role(&stmt);
9487	}
9488
9489	// ==================== SQL identifier escaping tests ====================
9490
9491	#[rstest]
9492	fn test_as_enum_escapes_type_name_with_special_characters() {
9493		// Arrange
9494		let builder = PostgresQueryBuilder::new();
9495		let mut stmt = Query::select();
9496		stmt.expr(Expr::val("active").as_enum(Alias::new("user\"status")))
9497			.from("users");
9498
9499		// Act
9500		let (sql, _) = builder.build_select(&stmt);
9501
9502		// Assert: enum type name must be quoted and inner quotes doubled
9503		assert!(sql.contains("::\"user\"\"status\""));
9504	}
9505
9506	#[rstest]
9507	fn test_cast_escapes_type_name_with_special_characters() {
9508		// Arrange
9509		let builder = PostgresQueryBuilder::new();
9510		let mut stmt = Query::select();
9511		stmt.expr(Expr::col("age").cast_as(Alias::new("my\"type")))
9512			.from("users");
9513
9514		// Act
9515		let (sql, _) = builder.build_select(&stmt);
9516
9517		// Assert: cast type name must be quoted and inner quotes doubled
9518		assert!(sql.contains("CAST(\"age\" AS \"my\"\"type\")"));
9519	}
9520
9521	#[rstest]
9522	fn test_trigger_function_name_is_escaped() {
9523		// Arrange
9524		let builder = PostgresQueryBuilder::new();
9525		let mut stmt = Query::create_trigger();
9526		stmt.name("test_trigger")
9527			.timing(crate::types::TriggerTiming::After)
9528			.event(crate::types::TriggerEvent::Insert)
9529			.on_table("users")
9530			.for_each(crate::types::TriggerScope::Row)
9531			.execute_function("my\"func");
9532
9533		// Act
9534		let (sql, _) = builder.build_create_trigger(&stmt);
9535
9536		// Assert: function name must be quoted and inner quotes doubled
9537		assert!(sql.contains("EXECUTE FUNCTION \"my\"\"func\"()"));
9538	}
9539
9540	#[rstest]
9541	fn test_as_enum_normal_type_name_is_quoted() {
9542		// Arrange
9543		let builder = PostgresQueryBuilder::new();
9544		let mut stmt = Query::select();
9545		stmt.expr(Expr::val("active").as_enum(Alias::new("status")))
9546			.from("users");
9547
9548		// Act
9549		let (sql, _) = builder.build_select(&stmt);
9550
9551		// Assert: even normal identifiers should be quoted
9552		assert!(sql.contains("::\"status\""));
9553	}
9554
9555	#[rstest]
9556	fn test_cast_normal_type_name_is_quoted() {
9557		// Arrange
9558		let builder = PostgresQueryBuilder::new();
9559		let mut stmt = Query::select();
9560		stmt.expr(Expr::col("age").cast_as(Alias::new("INTEGER")))
9561			.from("users");
9562
9563		// Act
9564		let (sql, _) = builder.build_select(&stmt);
9565
9566		// Assert: cast type name should be quoted
9567		assert!(sql.contains("CAST(\"age\" AS \"INTEGER\")"));
9568	}
9569
9570	// ==================== Dollar-quote delimiter safety tests ====================
9571
9572	#[rstest]
9573	fn test_safe_delimiter_default_when_body_has_no_dollar_quotes() {
9574		// Arrange
9575		let body = "BEGIN RETURN 1; END;";
9576
9577		// Act
9578		let delimiter = generate_safe_dollar_quote_delimiter(body);
9579
9580		// Assert
9581		assert_eq!(delimiter, "$$");
9582	}
9583
9584	#[rstest]
9585	fn test_safe_delimiter_avoids_collision_with_dollar_dollar() {
9586		// Arrange
9587		let body = "BEGIN $$ nested $$ END;";
9588
9589		// Act
9590		let delimiter = generate_safe_dollar_quote_delimiter(body);
9591
9592		// Assert
9593		assert_ne!(
9594			delimiter, "$$",
9595			"Delimiter must not be $$ when body contains $$"
9596		);
9597		assert_eq!(delimiter, "$body_0$");
9598	}
9599
9600	#[rstest]
9601	fn test_safe_delimiter_injection_attempt_with_dollar_quotes() {
9602		// Arrange: attacker tries to break out of dollar-quoting
9603		let body = "$$ ; DROP TABLE users; --";
9604
9605		// Act
9606		let delimiter = generate_safe_dollar_quote_delimiter(body);
9607
9608		// Assert
9609		assert_ne!(delimiter, "$$");
9610		let delimiters = collect_dollar_quote_delimiters(body);
9611		assert!(
9612			!delimiters.contains(&delimiter),
9613			"Generated delimiter must not conflict with any delimiter in body"
9614		);
9615	}
9616
9617	#[rstest]
9618	fn test_safe_delimiter_skips_collision_with_body_0() {
9619		// Arrange: body contains both $$ and $body_0$
9620		let body = "BEGIN $$ test $body_0$ END;";
9621
9622		// Act
9623		let delimiter = generate_safe_dollar_quote_delimiter(body);
9624
9625		// Assert
9626		assert_eq!(delimiter, "$body_1$");
9627	}
9628
9629	#[rstest]
9630	fn test_safe_delimiter_multiple_collisions() {
9631		// Arrange: body contains $$, $body_0$, $body_1$
9632		let body = "$$ $body_0$ $body_1$";
9633
9634		// Act
9635		let delimiter = generate_safe_dollar_quote_delimiter(body);
9636
9637		// Assert
9638		assert_eq!(delimiter, "$body_2$");
9639	}
9640
9641	#[rstest]
9642	fn test_safe_delimiter_ignores_dollar_amount_not_delimiter() {
9643		// Arrange: $100 is not a dollar-quote delimiter (digit after $)
9644		let body = "SELECT $100 + $200";
9645
9646		// Act
9647		let delimiter = generate_safe_dollar_quote_delimiter(body);
9648
9649		// Assert: $$ is safe because $100 is not a delimiter
9650		assert_eq!(delimiter, "$$");
9651	}
9652
9653	#[rstest]
9654	fn test_safe_delimiter_empty_body() {
9655		// Arrange
9656		let body = "";
9657
9658		// Act
9659		let delimiter = generate_safe_dollar_quote_delimiter(body);
9660
9661		// Assert
9662		assert_eq!(delimiter, "$$");
9663	}
9664
9665	#[rstest]
9666	fn test_safe_delimiter_whitespace_only_body() {
9667		// Arrange
9668		let body = "   \t\n  ";
9669
9670		// Act
9671		let delimiter = generate_safe_dollar_quote_delimiter(body);
9672
9673		// Assert
9674		assert_eq!(delimiter, "$$");
9675	}
9676
9677	#[rstest]
9678	fn test_safe_delimiter_nested_dollar_quotes() {
9679		// Arrange: body contains nested dollar-quoted strings
9680		let body = "$inner$ SELECT 1 $inner$ $$ nested $$";
9681
9682		// Act
9683		let delimiter = generate_safe_dollar_quote_delimiter(body);
9684
9685		// Assert: must avoid both $$ and $inner$
9686		assert_ne!(delimiter, "$$");
9687		assert_ne!(delimiter, "$inner$");
9688		assert_eq!(delimiter, "$body_0$");
9689	}
9690
9691	#[rstest]
9692	fn test_safe_delimiter_tag_style_delimiters() {
9693		// Arrange: body contains $tag$ style delimiters
9694		let body = "$func$ BEGIN RETURN 1; END; $func$";
9695
9696		// Act
9697		let delimiter = generate_safe_dollar_quote_delimiter(body);
9698
9699		// Assert: $$ is safe because body only contains $func$
9700		assert_eq!(delimiter, "$$");
9701	}
9702
9703	// ==================== Dollar-quote delimiter collection tests ====================
9704
9705	#[rstest]
9706	fn test_collect_delimiters_empty_body() {
9707		// Arrange
9708		let body = "";
9709
9710		// Act
9711		let delimiters = collect_dollar_quote_delimiters(body);
9712
9713		// Assert
9714		assert!(delimiters.is_empty());
9715	}
9716
9717	#[rstest]
9718	fn test_collect_delimiters_no_dollar_signs() {
9719		// Arrange
9720		let body = "SELECT 1 + 2";
9721
9722		// Act
9723		let delimiters = collect_dollar_quote_delimiters(body);
9724
9725		// Assert
9726		assert!(delimiters.is_empty());
9727	}
9728
9729	#[rstest]
9730	fn test_collect_delimiters_dollar_amounts_are_not_delimiters() {
9731		// Arrange: $1, $2 are PostgreSQL parameter placeholders, not delimiters
9732		let body = "SELECT $1 + $2";
9733
9734		// Act
9735		let delimiters = collect_dollar_quote_delimiters(body);
9736
9737		// Assert
9738		assert!(delimiters.is_empty());
9739	}
9740
9741	#[rstest]
9742	fn test_collect_delimiters_finds_empty_tag() {
9743		// Arrange
9744		let body = "$$ body content $$";
9745
9746		// Act
9747		let delimiters = collect_dollar_quote_delimiters(body);
9748
9749		// Assert
9750		assert_eq!(delimiters.len(), 1);
9751		assert!(delimiters.contains("$$"));
9752	}
9753
9754	#[rstest]
9755	fn test_collect_delimiters_finds_named_tag() {
9756		// Arrange
9757		let body = "$func$ body $func$";
9758
9759		// Act
9760		let delimiters = collect_dollar_quote_delimiters(body);
9761
9762		// Assert
9763		assert_eq!(delimiters.len(), 1);
9764		assert!(delimiters.contains("$func$"));
9765	}
9766
9767	#[rstest]
9768	fn test_collect_delimiters_finds_multiple_tags() {
9769		// Arrange
9770		let body = "$$ outer $inner$ nested $inner$ outer $$";
9771
9772		// Act
9773		let delimiters = collect_dollar_quote_delimiters(body);
9774
9775		// Assert
9776		assert_eq!(delimiters.len(), 2);
9777		assert!(delimiters.contains("$$"));
9778		assert!(delimiters.contains("$inner$"));
9779	}
9780
9781	#[rstest]
9782	fn test_collect_delimiters_underscore_in_tag() {
9783		// Arrange
9784		let body = "$my_tag$ content $my_tag$";
9785
9786		// Act
9787		let delimiters = collect_dollar_quote_delimiters(body);
9788
9789		// Assert
9790		assert!(delimiters.contains("$my_tag$"));
9791	}
9792
9793	#[rstest]
9794	fn test_collect_delimiters_rejects_digit_start_tag() {
9795		// Arrange: $1tag$ is not valid because tag starts with digit
9796		let body = "$1tag$ content";
9797
9798		// Act
9799		let delimiters = collect_dollar_quote_delimiters(body);
9800
9801		// Assert: $1tag$ is not a valid delimiter
9802		assert!(!delimiters.contains("$1tag$"));
9803	}
9804
9805	// =========================================================================
9806	// Issue #2560: Placeholder adjustment avoids false-positive replacements
9807	// =========================================================================
9808
9809	#[rstest]
9810	fn test_adjust_placeholder_offsets_basic() {
9811		// Arrange
9812		let sql = "SELECT * FROM t WHERE a = $1 AND b = $2";
9813
9814		// Act
9815		let adjusted = PostgresQueryBuilder::adjust_placeholder_offsets(sql, 2, 3);
9816
9817		// Assert
9818		assert_eq!(adjusted, "SELECT * FROM t WHERE a = $4 AND b = $5");
9819	}
9820
9821	#[rstest]
9822	fn test_adjust_placeholder_offsets_zero_offset() {
9823		// Arrange
9824		let sql = "SELECT * FROM t WHERE a = $1";
9825
9826		// Act
9827		let adjusted = PostgresQueryBuilder::adjust_placeholder_offsets(sql, 1, 0);
9828
9829		// Assert - no change when offset is 0
9830		assert_eq!(adjusted, "SELECT * FROM t WHERE a = $1");
9831	}
9832
9833	#[rstest]
9834	fn test_adjust_placeholder_offsets_skips_quoted_identifiers() {
9835		// Arrange - identifier contains $1 pattern
9836		let sql = r#"SELECT "col$1" FROM t WHERE a = $1"#;
9837
9838		// Act
9839		let adjusted = PostgresQueryBuilder::adjust_placeholder_offsets(sql, 1, 5);
9840
9841		// Assert - $1 inside quotes is NOT adjusted, only outside
9842		assert_eq!(adjusted, r#"SELECT "col$1" FROM t WHERE a = $6"#);
9843	}
9844
9845	#[rstest]
9846	fn test_adjust_placeholder_offsets_skips_string_literals() {
9847		// Arrange - string literal contains $1 pattern
9848		let sql = "SELECT * FROM t WHERE a = $1 AND b = 'price$1'";
9849
9850		// Act
9851		let adjusted = PostgresQueryBuilder::adjust_placeholder_offsets(sql, 1, 2);
9852
9853		// Assert - $1 inside single quotes is NOT adjusted
9854		assert_eq!(adjusted, "SELECT * FROM t WHERE a = $3 AND b = 'price$1'");
9855	}
9856
9857	#[rstest]
9858	fn test_adjust_placeholder_offsets_no_collision() {
9859		// Arrange - this was the original bug: $1->$2 then $2->$3 caused collisions
9860		let sql = "SELECT * FROM t WHERE a = $1 AND b = $2";
9861
9862		// Act
9863		let adjusted = PostgresQueryBuilder::adjust_placeholder_offsets(sql, 2, 1);
9864
9865		// Assert - single-pass avoids collision
9866		assert_eq!(adjusted, "SELECT * FROM t WHERE a = $2 AND b = $3");
9867	}
9868
9869	// =========================================================================
9870	// Issue #2570: expr_as renders AS alias (not ::type_cast)
9871	// =========================================================================
9872
9873	#[rstest]
9874	fn test_expr_as_renders_as_alias() {
9875		// Arrange
9876		let builder = PostgresQueryBuilder::new();
9877		let mut stmt = Query::select();
9878		stmt.expr(Expr::col("name").expr_as("display_name"))
9879			.from("users");
9880
9881		// Act
9882		let (sql, _) = builder.build_select(&stmt);
9883
9884		// Assert - should contain AS alias, not ::type_cast
9885		assert!(
9886			sql.contains("AS \"display_name\""),
9887			"Expected AS alias in SQL, got: {}",
9888			sql
9889		);
9890		assert!(
9891			!sql.contains("::\"display_name\""),
9892			"Should NOT contain type cast syntax, got: {}",
9893			sql
9894		);
9895	}
9896}