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