Skip to main content

reinhardt_query/backend/
postgres.rs

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