1use vibesql_types::{DataType, SqlValue};
22
23use crate::{
24 expression::{
25 CaseWhen, CharacterUnit, Expression, FrameBound, FrameExclude, FrameUnit, FulltextMode,
26 IntervalUnit, PseudoTable, Quantifier, TrimPosition, WindowFrame, WindowFunctionSpec,
27 WindowSpec,
28 },
29 operators::{BinaryOperator, UnaryOperator},
30 select::{
31 CommonTableExpr, CteMaterialization, FromClause, GroupByClause, GroupingElement,
32 GroupingSet, JoinType, MixedGroupingItem, OrderByItem, OrderDirection, SelectItem,
33 SelectStmt, SetOperation, SetOperator,
34 },
35};
36
37pub trait ToSql {
39 fn to_sql(&self) -> String;
41}
42
43impl ToSql for BinaryOperator {
48 fn to_sql(&self) -> String {
49 match self {
50 BinaryOperator::Plus => "+".to_string(),
51 BinaryOperator::Minus => "-".to_string(),
52 BinaryOperator::Multiply => "*".to_string(),
53 BinaryOperator::Divide => "/".to_string(),
54 BinaryOperator::IntegerDivide => "DIV".to_string(),
55 BinaryOperator::Modulo => "%".to_string(),
56 BinaryOperator::Equal => "=".to_string(),
57 BinaryOperator::NotEqual => "<>".to_string(),
58 BinaryOperator::LessThan => "<".to_string(),
59 BinaryOperator::LessThanOrEqual => "<=".to_string(),
60 BinaryOperator::GreaterThan => ">".to_string(),
61 BinaryOperator::GreaterThanOrEqual => ">=".to_string(),
62 BinaryOperator::And => "AND".to_string(),
63 BinaryOperator::Or => "OR".to_string(),
64 BinaryOperator::Concat => "||".to_string(),
65 BinaryOperator::CosineDistance => "<->".to_string(),
66 BinaryOperator::NegativeInnerProduct => "<#>".to_string(),
67 BinaryOperator::L2Distance => "<=>".to_string(),
68 BinaryOperator::BitwiseAnd => "&".to_string(),
69 BinaryOperator::BitwiseOr => "|".to_string(),
70 BinaryOperator::LeftShift => "<<".to_string(),
71 BinaryOperator::RightShift => ">>".to_string(),
72 BinaryOperator::JsonExtract => "->".to_string(),
73 BinaryOperator::JsonExtractText => "->>".to_string(),
74 }
75 }
76}
77
78impl ToSql for UnaryOperator {
79 fn to_sql(&self) -> String {
80 match self {
81 UnaryOperator::Not => "NOT".to_string(),
82 UnaryOperator::Minus => "-".to_string(),
83 UnaryOperator::Plus => "+".to_string(),
84 UnaryOperator::BitwiseNot => "~".to_string(),
85 UnaryOperator::IsNull => "IS NULL".to_string(),
86 UnaryOperator::IsNotNull => "IS NOT NULL".to_string(),
87 }
88 }
89}
90
91fn format_string_literal(s: &str) -> String {
97 let escaped = s.replace('\'', "''");
99 format!("'{}'", escaped)
100}
101
102fn format_identifier(name: &str) -> String {
104 let needs_quoting = name.contains(' ')
106 || name.contains('-')
107 || name.contains('.')
108 || name.chars().next().is_some_and(|c| c.is_ascii_digit());
109
110 if needs_quoting {
111 format!("\"{}\"", name.replace('"', "\"\""))
112 } else {
113 name.to_string()
114 }
115}
116
117impl ToSql for SqlValue {
122 fn to_sql(&self) -> String {
123 match self {
124 SqlValue::Integer(i) => i.to_string(),
125 SqlValue::Smallint(i) => i.to_string(),
126 SqlValue::Bigint(i) => i.to_string(),
127 SqlValue::Unsigned(u) => u.to_string(),
128 SqlValue::Numeric(n) => {
129 if n.is_nan() || n.is_infinite() {
130 "NULL".to_string()
132 } else if n.fract() == 0.0 {
133 format!("{:.1}", n) } else {
135 n.to_string()
136 }
137 }
138 SqlValue::Float(f) => {
139 let f64_val = *f as f64;
140 if f64_val.is_nan() || f64_val.is_infinite() {
141 "NULL".to_string()
142 } else if f64_val.fract() == 0.0 {
143 format!("{:.1}", f64_val)
144 } else {
145 f64_val.to_string()
146 }
147 }
148 SqlValue::Real(r) => {
149 let f64_val = *r;
150 if f64_val.is_nan() || f64_val.is_infinite() {
151 "NULL".to_string()
152 } else if f64_val.fract() == 0.0 {
153 format!("{:.1}", f64_val)
154 } else {
155 f64_val.to_string()
156 }
157 }
158 SqlValue::Double(d) => {
159 if d.is_nan() || d.is_infinite() {
160 "NULL".to_string()
161 } else if d.fract() == 0.0 {
162 format!("{:.1}", d)
163 } else {
164 d.to_string()
165 }
166 }
167 SqlValue::Character(s) | SqlValue::Varchar(s) => format_string_literal(s),
168 SqlValue::Boolean(true) => "TRUE".to_string(),
169 SqlValue::Boolean(false) => "FALSE".to_string(),
170 SqlValue::Date(s) => format!("DATE '{}'", s),
171 SqlValue::Time(s) => format!("TIME '{}'", s),
172 SqlValue::Timestamp(s) => format!("TIMESTAMP '{}'", s),
173 SqlValue::Interval(s) => format!("INTERVAL '{}'", s),
174 SqlValue::Vector(v) => {
175 let formatted: Vec<String> = v.iter().map(|x| x.to_string()).collect();
176 format!("[{}]", formatted.join(", "))
177 }
178 SqlValue::Blob(b) => {
179 let hex: String = b.iter().map(|byte| format!("{:02X}", byte)).collect();
180 format!("x'{}'", hex)
181 }
182 SqlValue::Null => "NULL".to_string(),
183 }
184 }
185}
186
187impl ToSql for DataType {
192 fn to_sql(&self) -> String {
193 match self {
194 DataType::Integer => "INTEGER".to_string(),
195 DataType::Smallint => "SMALLINT".to_string(),
196 DataType::Bigint => "BIGINT".to_string(),
197 DataType::Unsigned => "BIGINT UNSIGNED".to_string(),
198 DataType::Numeric { precision, scale } => {
199 if *scale == 0 {
200 format!("NUMERIC({})", precision)
201 } else {
202 format!("NUMERIC({}, {})", precision, scale)
203 }
204 }
205 DataType::Decimal { precision, scale } => {
206 if *scale == 0 {
207 format!("DECIMAL({})", precision)
208 } else {
209 format!("DECIMAL({}, {})", precision, scale)
210 }
211 }
212 DataType::Float { precision } => {
213 if *precision == 53 {
214 "FLOAT".to_string()
215 } else {
216 format!("FLOAT({})", precision)
217 }
218 }
219 DataType::Real => "REAL".to_string(),
220 DataType::DoublePrecision => "DOUBLE PRECISION".to_string(),
221 DataType::Character { length } => format!("CHARACTER({})", length),
222 DataType::Varchar { max_length } => match max_length {
223 Some(len) => format!("VARCHAR({})", len),
224 None => "VARCHAR".to_string(),
225 },
226 DataType::CharacterLargeObject => "CLOB".to_string(),
227 DataType::Name => "NAME".to_string(),
228 DataType::Boolean => "BOOLEAN".to_string(),
229 DataType::Date => "DATE".to_string(),
230 DataType::Time { with_timezone } => {
231 if *with_timezone {
232 "TIME WITH TIME ZONE".to_string()
233 } else {
234 "TIME".to_string()
235 }
236 }
237 DataType::Timestamp { with_timezone } => {
238 if *with_timezone {
239 "TIMESTAMP WITH TIME ZONE".to_string()
240 } else {
241 "TIMESTAMP".to_string()
242 }
243 }
244 DataType::Interval { start_field, end_field } => {
245 let start = interval_field_to_sql(start_field);
246 match end_field {
247 Some(end) => format!("INTERVAL {} TO {}", start, interval_field_to_sql(end)),
248 None => format!("INTERVAL {}", start),
249 }
250 }
251 DataType::BinaryLargeObject => "BLOB".to_string(),
252 DataType::Bit { length } => match length {
253 Some(len) => format!("BIT({})", len),
254 None => "BIT".to_string(),
255 },
256 DataType::Vector { dimensions } => format!("VECTOR({})", dimensions),
257 DataType::UserDefined { type_name } => type_name.clone(),
258 DataType::Null => "NULL".to_string(),
259 }
260 }
261}
262
263fn interval_field_to_sql(field: &vibesql_types::IntervalField) -> &'static str {
264 use vibesql_types::IntervalField;
265 match field {
266 IntervalField::Year => "YEAR",
267 IntervalField::Month => "MONTH",
268 IntervalField::Day => "DAY",
269 IntervalField::Hour => "HOUR",
270 IntervalField::Minute => "MINUTE",
271 IntervalField::Second => "SECOND",
272 }
273}
274
275impl ToSql for Expression {
280 fn to_sql(&self) -> String {
281 match self {
282 Expression::Literal(value) => value.to_sql(),
283
284 Expression::Placeholder(_) => "?".to_string(),
285
286 Expression::NumberedPlaceholder(n) => format!("${}", n),
287
288 Expression::NamedPlaceholder(name) => format!(":{}", name),
289
290 Expression::ColumnRef(col_id) => {
291 col_id.display().to_string()
293 }
294
295 Expression::BinaryOp { op, left, right } => {
296 let left_sql = left.to_sql();
297 let right_sql = right.to_sql();
298 let op_sql = op.to_sql();
299
300 let left_str =
302 if needs_parens(left, op, true) { format!("({})", left_sql) } else { left_sql };
303 let right_str = if needs_parens(right, op, false) {
304 format!("({})", right_sql)
305 } else {
306 right_sql
307 };
308
309 if op.is_word_operator() {
312 format!("{} {} {}", left_str, op_sql, right_str)
313 } else {
314 format!("{}{}{}", left_str, op_sql, right_str)
315 }
316 }
317
318 Expression::Conjunction(exprs) => {
319 let parts: Vec<String> = exprs
320 .iter()
321 .map(|e| {
322 if matches!(e, Expression::Disjunction(_)) {
324 format!("({})", e.to_sql())
325 } else {
326 e.to_sql()
327 }
328 })
329 .collect();
330 parts.join(" AND ")
331 }
332
333 Expression::Disjunction(exprs) => {
334 let parts: Vec<String> = exprs.iter().map(|e| e.to_sql()).collect();
335 parts.join(" OR ")
336 }
337
338 Expression::UnaryOp { op, expr } => {
339 let expr_sql = expr.to_sql();
340 match op {
341 UnaryOperator::Not => format!("NOT {}", maybe_paren(&expr_sql, expr)),
342 UnaryOperator::Minus => format!("-{}", maybe_paren(&expr_sql, expr)),
343 UnaryOperator::Plus => format!("+{}", maybe_paren(&expr_sql, expr)),
344 UnaryOperator::BitwiseNot => format!("~{}", maybe_paren(&expr_sql, expr)),
345 UnaryOperator::IsNull => format!("{} IS NULL", expr_sql),
346 UnaryOperator::IsNotNull => format!("{} IS NOT NULL", expr_sql),
347 }
348 }
349
350 Expression::Function { name, args, character_unit } => {
351 let args_sql: Vec<String> = args.iter().map(|a| a.to_sql()).collect();
352 let mut result =
353 format!("{}({})", name.canonical().to_uppercase(), args_sql.join(", "));
354 if let Some(unit) = character_unit {
355 result.push_str(&format!(" USING {}", unit.to_sql()));
356 }
357 result
358 }
359
360 Expression::AggregateFunction { name, distinct, args, order_by, filter } => {
361 let args_sql: Vec<String> = args.iter().map(|a| a.to_sql()).collect();
362 let order_by_sql = order_by.as_ref().map(|items| {
363 let items_sql: Vec<String> = items
364 .iter()
365 .map(|item| {
366 let dir = match item.direction {
367 crate::OrderDirection::Asc => "",
368 crate::OrderDirection::Desc => " DESC",
369 };
370 format!("{}{}", item.expr.to_sql(), dir)
371 })
372 .collect();
373 format!(" ORDER BY {}", items_sql.join(", "))
374 });
375 let filter_sql = filter
376 .as_ref()
377 .map(|f| format!(" FILTER (WHERE {})", f.to_sql()))
378 .unwrap_or_default();
379 if *distinct {
380 format!(
381 "{}(DISTINCT {}{}){}",
382 name.canonical().to_uppercase(),
383 args_sql.join(", "),
384 order_by_sql.unwrap_or_default(),
385 filter_sql
386 )
387 } else {
388 format!(
389 "{}({}{}){}",
390 name.canonical().to_uppercase(),
391 args_sql.join(", "),
392 order_by_sql.unwrap_or_default(),
393 filter_sql
394 )
395 }
396 }
397
398 Expression::IsNull { expr, negated } => {
399 let expr_sql = expr.to_sql();
400 if *negated {
401 format!("{} IS NOT NULL", expr_sql)
402 } else {
403 format!("{} IS NULL", expr_sql)
404 }
405 }
406
407 Expression::IsDistinctFrom { left, right, negated } => {
408 let left_sql = left.to_sql();
409 let right_sql = right.to_sql();
410 if *negated {
411 format!("{} IS NOT DISTINCT FROM {}", left_sql, right_sql)
412 } else {
413 format!("{} IS DISTINCT FROM {}", left_sql, right_sql)
414 }
415 }
416
417 Expression::IsTruthValue { expr, truth_value, negated } => {
418 let expr_sql = expr.to_sql();
419 let tv_str = match truth_value {
420 crate::TruthValue::True => "TRUE",
421 crate::TruthValue::False => "FALSE",
422 crate::TruthValue::Unknown => "UNKNOWN",
423 };
424 if *negated {
425 format!("{} IS NOT {}", expr_sql, tv_str)
426 } else {
427 format!("{} IS {}", expr_sql, tv_str)
428 }
429 }
430
431 Expression::Wildcard => "*".to_string(),
432
433 Expression::Case { operand, when_clauses, else_result } => {
434 let mut result = "CASE".to_string();
435 if let Some(op) = operand {
436 result.push_str(&format!(" {}", op.to_sql()));
437 }
438 for when in when_clauses {
439 result.push_str(&format!(" {}", when.to_sql()));
440 }
441 if let Some(else_expr) = else_result {
442 result.push_str(&format!(" ELSE {}", else_expr.to_sql()));
443 }
444 result.push_str(" END");
445 result
446 }
447
448 Expression::ScalarSubquery(query) => format!("({})", query.to_sql()),
449
450 Expression::In { expr, subquery, negated } => {
451 let not_str = if *negated { "NOT " } else { "" };
452 format!("{} {}IN ({})", expr.to_sql(), not_str, subquery.to_sql())
453 }
454
455 Expression::InList { expr, values, negated } => {
456 let values_sql: Vec<String> = values.iter().map(|v| v.to_sql()).collect();
457 let not_str = if *negated { "NOT " } else { "" };
458 format!("{} {}IN ({})", expr.to_sql(), not_str, values_sql.join(", "))
459 }
460
461 Expression::Between { expr, low, high, negated, symmetric } => {
462 let not_str = if *negated { "NOT " } else { "" };
463 let sym_str = if *symmetric { "SYMMETRIC " } else { "" };
464 format!(
465 "{} {}BETWEEN {}{} AND {}",
466 expr.to_sql(),
467 not_str,
468 sym_str,
469 low.to_sql(),
470 high.to_sql()
471 )
472 }
473
474 Expression::Cast { expr, data_type } => {
475 format!("CAST({} AS {})", expr.to_sql(), data_type.to_sql())
476 }
477
478 Expression::Position { substring, string, character_unit } => {
479 let mut result = format!("POSITION({} IN {})", substring.to_sql(), string.to_sql());
480 if let Some(unit) = character_unit {
481 result.push_str(&format!(" USING {}", unit.to_sql()));
482 }
483 result
484 }
485
486 Expression::Trim { position, removal_char, string } => {
487 let mut result = "TRIM(".to_string();
488 if let Some(pos) = position {
489 result.push_str(&format!("{} ", pos.to_sql()));
490 }
491 if let Some(char_expr) = removal_char {
492 result.push_str(&format!("{} FROM ", char_expr.to_sql()));
493 }
494 result.push_str(&string.to_sql());
495 result.push(')');
496 result
497 }
498
499 Expression::Extract { field, expr } => {
500 format!("EXTRACT({} FROM {})", field.to_sql(), expr.to_sql())
501 }
502
503 Expression::Like { expr, pattern, negated, escape } => {
504 let not_str = if *negated { "NOT " } else { "" };
505 let escape_str = escape
506 .as_ref()
507 .map(|e| format!(" ESCAPE {}", e.to_sql()))
508 .unwrap_or_default();
509 format!("{} {}LIKE {}{}", expr.to_sql(), not_str, pattern.to_sql(), escape_str)
510 }
511
512 Expression::Glob { expr, pattern, negated, escape } => {
513 let not_str = if *negated { "NOT " } else { "" };
514 let escape_str = escape
515 .as_ref()
516 .map(|e| format!(" ESCAPE {}", e.to_sql()))
517 .unwrap_or_default();
518 format!("{} {}GLOB {}{}", expr.to_sql(), not_str, pattern.to_sql(), escape_str)
519 }
520
521 Expression::Exists { subquery, negated } => {
522 let not_str = if *negated { "NOT " } else { "" };
523 format!("{}EXISTS ({})", not_str, subquery.to_sql())
524 }
525
526 Expression::QuantifiedComparison { expr, op, quantifier, subquery } => {
527 format!(
528 "{} {} {} ({})",
529 expr.to_sql(),
530 op.to_sql(),
531 quantifier.to_sql(),
532 subquery.to_sql()
533 )
534 }
535
536 Expression::CurrentDate => "CURRENT_DATE".to_string(),
537
538 Expression::CurrentTime { precision } => match precision {
539 Some(p) => format!("CURRENT_TIME({})", p),
540 None => "CURRENT_TIME".to_string(),
541 },
542
543 Expression::CurrentTimestamp { precision } => match precision {
544 Some(p) => format!("CURRENT_TIMESTAMP({})", p),
545 None => "CURRENT_TIMESTAMP".to_string(),
546 },
547
548 Expression::Interval { value, unit, leading_precision, fractional_precision } => {
549 let mut result = format!("INTERVAL {} {}", value.to_sql(), unit.to_sql());
550 if let Some(lp) = leading_precision {
551 result.push_str(&format!("({})", lp));
552 }
553 if let Some(fp) = fractional_precision {
554 result.push_str(&format!("({})", fp));
555 }
556 result
557 }
558
559 Expression::Default => "DEFAULT".to_string(),
560
561 Expression::DuplicateKeyValue { column } => {
562 format!("VALUES({})", format_identifier(column))
563 }
564
565 Expression::WindowFunction { function, over } => {
566 format!("{} OVER ({})", function.to_sql(), over.to_sql())
567 }
568
569 Expression::NextValue { sequence_name } => {
570 format!("NEXT VALUE FOR {}", format_identifier(sequence_name))
571 }
572
573 Expression::MatchAgainst { columns, search_modifier, mode } => {
574 let cols_sql: Vec<String> = columns.iter().map(|c| format_identifier(c)).collect();
575 let mode_sql = match mode {
576 FulltextMode::NaturalLanguage => "",
577 FulltextMode::Boolean => " IN BOOLEAN MODE",
578 FulltextMode::QueryExpansion => " WITH QUERY EXPANSION",
579 };
580 format!(
581 "MATCH({}) AGAINST ({}{})",
582 cols_sql.join(", "),
583 search_modifier.to_sql(),
584 mode_sql
585 )
586 }
587
588 Expression::PseudoVariable { pseudo_table, column } => {
589 format!("{}.{}", pseudo_table.to_sql(), format_identifier(column))
590 }
591
592 Expression::SessionVariable { name } => format!("@@{}", name),
593
594 Expression::RowValueConstructor(values) => {
595 let values_sql: Vec<String> = values.iter().map(|v| v.to_sql()).collect();
596 format!("({})", values_sql.join(", "))
597 }
598
599 Expression::Collate { expr, collation } => {
600 format!("{} COLLATE {}", expr.to_sql(), collation)
601 }
602 }
603 }
604}
605
606fn needs_parens(expr: &Expression, parent_op: &BinaryOperator, is_left: bool) -> bool {
608 match expr {
609 Expression::BinaryOp { op, .. } => {
610 let child_prec = operator_precedence(op);
611 let parent_prec = operator_precedence(parent_op);
612
613 child_prec < parent_prec || (child_prec == parent_prec && !is_left)
616 }
617 Expression::Conjunction(_) | Expression::Disjunction(_) => true,
618 _ => false,
619 }
620}
621
622fn operator_precedence(op: &BinaryOperator) -> u8 {
624 match op {
625 BinaryOperator::Or => 1,
626 BinaryOperator::And => 2,
627 BinaryOperator::BitwiseOr => 3,
629 BinaryOperator::BitwiseAnd => 4,
630 BinaryOperator::Equal
631 | BinaryOperator::NotEqual
632 | BinaryOperator::LessThan
633 | BinaryOperator::LessThanOrEqual
634 | BinaryOperator::GreaterThan
635 | BinaryOperator::GreaterThanOrEqual
636 | BinaryOperator::CosineDistance
637 | BinaryOperator::NegativeInnerProduct
638 | BinaryOperator::L2Distance => 5,
639 BinaryOperator::LeftShift | BinaryOperator::RightShift => 6,
641 BinaryOperator::Plus | BinaryOperator::Minus | BinaryOperator::Concat => 7,
642 BinaryOperator::Multiply
643 | BinaryOperator::Divide
644 | BinaryOperator::IntegerDivide
645 | BinaryOperator::Modulo => 8,
646 BinaryOperator::JsonExtract | BinaryOperator::JsonExtractText => 9,
648 }
649}
650
651fn maybe_paren(sql: &str, expr: &Expression) -> String {
653 match expr {
654 Expression::BinaryOp { .. }
655 | Expression::Conjunction(_)
656 | Expression::Disjunction(_)
657 | Expression::UnaryOp { .. } => format!("({})", sql),
658 _ => sql.to_string(),
659 }
660}
661
662impl ToSql for CaseWhen {
667 fn to_sql(&self) -> String {
668 let conds: Vec<String> = self.conditions.iter().map(|c| c.to_sql()).collect();
669 format!("WHEN {} THEN {}", conds.join(", "), self.result.to_sql())
670 }
671}
672
673impl ToSql for Quantifier {
674 fn to_sql(&self) -> String {
675 match self {
676 Quantifier::All => "ALL".to_string(),
677 Quantifier::Any => "ANY".to_string(),
678 Quantifier::Some => "SOME".to_string(),
679 }
680 }
681}
682
683impl ToSql for CharacterUnit {
684 fn to_sql(&self) -> String {
685 match self {
686 CharacterUnit::Characters => "CHARACTERS".to_string(),
687 CharacterUnit::Octets => "OCTETS".to_string(),
688 }
689 }
690}
691
692impl ToSql for TrimPosition {
693 fn to_sql(&self) -> String {
694 match self {
695 TrimPosition::Both => "BOTH".to_string(),
696 TrimPosition::Leading => "LEADING".to_string(),
697 TrimPosition::Trailing => "TRAILING".to_string(),
698 }
699 }
700}
701
702impl ToSql for IntervalUnit {
703 fn to_sql(&self) -> String {
704 match self {
705 IntervalUnit::Microsecond => "MICROSECOND".to_string(),
706 IntervalUnit::Second => "SECOND".to_string(),
707 IntervalUnit::Minute => "MINUTE".to_string(),
708 IntervalUnit::Hour => "HOUR".to_string(),
709 IntervalUnit::Day => "DAY".to_string(),
710 IntervalUnit::Week => "WEEK".to_string(),
711 IntervalUnit::Month => "MONTH".to_string(),
712 IntervalUnit::Quarter => "QUARTER".to_string(),
713 IntervalUnit::Year => "YEAR".to_string(),
714 IntervalUnit::SecondMicrosecond => "SECOND_MICROSECOND".to_string(),
715 IntervalUnit::MinuteMicrosecond => "MINUTE_MICROSECOND".to_string(),
716 IntervalUnit::MinuteSecond => "MINUTE_SECOND".to_string(),
717 IntervalUnit::HourMicrosecond => "HOUR_MICROSECOND".to_string(),
718 IntervalUnit::HourSecond => "HOUR_SECOND".to_string(),
719 IntervalUnit::HourMinute => "HOUR_MINUTE".to_string(),
720 IntervalUnit::DayMicrosecond => "DAY_MICROSECOND".to_string(),
721 IntervalUnit::DaySecond => "DAY_SECOND".to_string(),
722 IntervalUnit::DayMinute => "DAY_MINUTE".to_string(),
723 IntervalUnit::DayHour => "DAY_HOUR".to_string(),
724 IntervalUnit::YearMonth => "YEAR_MONTH".to_string(),
725 }
726 }
727}
728
729impl ToSql for PseudoTable {
730 fn to_sql(&self) -> String {
731 match self {
732 PseudoTable::Old => "OLD".to_string(),
733 PseudoTable::New => "NEW".to_string(),
734 }
735 }
736}
737
738impl ToSql for WindowFunctionSpec {
739 fn to_sql(&self) -> String {
740 match self {
741 WindowFunctionSpec::Aggregate { name, args, filter } => {
742 let args_sql: Vec<String> = args.iter().map(|a| a.to_sql()).collect();
743 let filter_sql = filter
744 .as_ref()
745 .map(|f| format!(" FILTER (WHERE {})", f.to_sql()))
746 .unwrap_or_default();
747 format!(
748 "{}({}){}",
749 name.canonical().to_uppercase(),
750 args_sql.join(", "),
751 filter_sql
752 )
753 }
754 WindowFunctionSpec::Ranking { name, args } => {
755 let args_sql: Vec<String> = args.iter().map(|a| a.to_sql()).collect();
756 if args_sql.is_empty() {
757 format!("{}()", name.canonical().to_uppercase())
758 } else {
759 format!("{}({})", name.canonical().to_uppercase(), args_sql.join(", "))
760 }
761 }
762 WindowFunctionSpec::Value { name, args } => {
763 let args_sql: Vec<String> = args.iter().map(|a| a.to_sql()).collect();
764 format!("{}({})", name.canonical().to_uppercase(), args_sql.join(", "))
765 }
766 }
767 }
768}
769
770impl ToSql for WindowSpec {
771 fn to_sql(&self) -> String {
772 let mut parts = Vec::new();
773
774 if let Some(partition_by) = &self.partition_by {
775 let exprs: Vec<String> = partition_by.iter().map(|e| e.to_sql()).collect();
776 parts.push(format!("PARTITION BY {}", exprs.join(", ")));
777 }
778
779 if let Some(order_by) = &self.order_by {
780 let items: Vec<String> = order_by.iter().map(|o| o.to_sql()).collect();
781 parts.push(format!("ORDER BY {}", items.join(", ")));
782 }
783
784 if let Some(frame) = &self.frame {
785 parts.push(frame.to_sql());
786 }
787
788 parts.join(" ")
789 }
790}
791
792impl ToSql for WindowFrame {
793 fn to_sql(&self) -> String {
794 let unit = match self.unit {
795 FrameUnit::Rows => "ROWS",
796 FrameUnit::Range => "RANGE",
797 FrameUnit::Groups => "GROUPS",
798 };
799
800 let frame_bounds = match &self.end {
801 Some(end) => {
802 format!("{} BETWEEN {} AND {}", unit, self.start.to_sql(), end.to_sql())
803 }
804 None => format!("{} {}", unit, self.start.to_sql()),
805 };
806
807 match &self.exclude {
809 Some(exclude) if *exclude != FrameExclude::NoOthers => {
810 format!("{} {}", frame_bounds, exclude.to_sql())
811 }
812 _ => frame_bounds,
813 }
814 }
815}
816
817impl ToSql for FrameExclude {
818 fn to_sql(&self) -> String {
819 match self {
820 FrameExclude::NoOthers => "EXCLUDE NO OTHERS".to_string(),
821 FrameExclude::CurrentRow => "EXCLUDE CURRENT ROW".to_string(),
822 FrameExclude::Group => "EXCLUDE GROUP".to_string(),
823 FrameExclude::Ties => "EXCLUDE TIES".to_string(),
824 }
825 }
826}
827
828impl ToSql for FrameBound {
829 fn to_sql(&self) -> String {
830 match self {
831 FrameBound::UnboundedPreceding => "UNBOUNDED PRECEDING".to_string(),
832 FrameBound::Preceding(n) => format!("{} PRECEDING", n.to_sql()),
833 FrameBound::CurrentRow => "CURRENT ROW".to_string(),
834 FrameBound::Following(n) => format!("{} FOLLOWING", n.to_sql()),
835 FrameBound::UnboundedFollowing => "UNBOUNDED FOLLOWING".to_string(),
836 }
837 }
838}
839
840impl ToSql for SelectStmt {
845 fn to_sql(&self) -> String {
846 let mut result = String::new();
847
848 if let Some(ctes) = &self.with_clause {
850 let cte_strs: Vec<String> = ctes.iter().map(|c| c.to_sql()).collect();
851 result.push_str(&format!("WITH {} ", cte_strs.join(", ")));
852 }
853
854 if let Some(rows) = &self.values {
856 result.push_str("VALUES");
858 let row_strs: Vec<String> = rows
859 .iter()
860 .map(|row| {
861 let exprs: Vec<String> = row.iter().map(|e| e.to_sql()).collect();
862 format!("({})", exprs.join(", "))
863 })
864 .collect();
865 result.push_str(&row_strs.join(", "));
866 } else {
867 result.push_str("SELECT ");
869
870 if self.distinct {
872 result.push_str("DISTINCT ");
873 }
874
875 let items: Vec<String> = self.select_list.iter().map(|i| i.to_sql()).collect();
877 result.push_str(&items.join(", "));
878
879 if let Some(table) = &self.into_table {
881 result.push_str(&format!(" INTO {}", format_identifier(table)));
882 }
883
884 if let Some(vars) = &self.into_variables {
886 result.push_str(&format!(" INTO {}", vars.join(", ")));
887 }
888
889 if let Some(from) = &self.from {
891 result.push_str(&format!(" FROM {}", from.to_sql()));
892 }
893
894 if let Some(where_clause) = &self.where_clause {
896 result.push_str(&format!(" WHERE {}", where_clause.to_sql()));
897 }
898
899 if let Some(group_by) = &self.group_by {
901 result.push_str(&format!(" GROUP BY {}", group_by.to_sql()));
902 }
903
904 if let Some(having) = &self.having {
906 result.push_str(&format!(" HAVING {}", having.to_sql()));
907 }
908 }
909
910 if let Some(order_by) = &self.order_by {
912 let items: Vec<String> = order_by.iter().map(|o| o.to_sql()).collect();
913 result.push_str(&format!(" ORDER BY {}", items.join(", ")));
914 }
915
916 if let Some(limit) = &self.limit {
918 result.push_str(&format!(" LIMIT {}", limit.to_sql()));
919 }
920
921 if let Some(offset) = &self.offset {
923 result.push_str(&format!(" OFFSET {}", offset.to_sql()));
924 }
925
926 if let Some(set_op) = &self.set_operation {
928 result.push_str(&format!(" {}", set_op.to_sql()));
929 }
930
931 result
932 }
933}
934
935impl ToSql for CommonTableExpr {
936 fn to_sql(&self) -> String {
937 let mut result = format_identifier(&self.name);
938 if let Some(cols) = &self.columns {
939 result.push_str(&format!(" ({})", cols.join(", ")));
940 }
941 let materialization_hint = match self.materialization {
943 CteMaterialization::Default => "",
944 CteMaterialization::Materialized => " MATERIALIZED",
945 CteMaterialization::NotMaterialized => " NOT MATERIALIZED",
946 };
947 result.push_str(&format!(" AS{}({})", materialization_hint, self.query.to_sql()));
948 result
949 }
950}
951
952impl ToSql for SelectItem {
953 fn to_sql(&self) -> String {
954 match self {
955 SelectItem::Wildcard { alias } => match alias {
956 Some(cols) => format!("* AS ({})", cols.join(", ")),
957 None => "*".to_string(),
958 },
959 SelectItem::QualifiedWildcard { qualifier, alias } => {
960 let base = format!("{}.*", format_identifier(qualifier));
961 match alias {
962 Some(cols) => format!("{} AS ({})", base, cols.join(", ")),
963 None => base,
964 }
965 }
966 SelectItem::Expression { expr, alias, .. } => {
967 let expr_sql = expr.to_sql();
968 match alias {
969 Some(a) => format!("{} AS {}", expr_sql, format_identifier(a)),
970 None => expr_sql,
971 }
972 }
973 }
974 }
975}
976
977impl ToSql for FromClause {
978 fn to_sql(&self) -> String {
979 match self {
980 FromClause::Table { name, alias, column_aliases, quoted } => {
981 let mut result = if *quoted {
983 format!("\"{}\"", name.replace('"', "\"\""))
984 } else {
985 format_identifier(name)
986 };
987 if let Some(a) = alias {
988 result.push_str(&format!(" AS {}", format_identifier(a)));
989 if let Some(cols) = column_aliases {
990 result.push_str(&format!(" ({})", cols.join(", ")));
991 }
992 }
993 result
994 }
995 FromClause::Join { left, right, join_type, condition, using_columns, natural, alias } => {
996 let mut result = left.to_sql();
997
998 if *natural {
999 result.push_str(" NATURAL");
1000 }
1001
1002 result.push_str(&format!(" {} {}", join_type.to_sql(), right.to_sql()));
1003
1004 if let Some(cond) = condition {
1005 result.push_str(&format!(" ON {}", cond.to_sql()));
1006 }
1007
1008 if let Some(cols) = using_columns {
1009 result.push_str(&format!(" USING ({})", cols.join(", ")));
1010 }
1011
1012 if let Some(a) = alias {
1013 result.push_str(&format!(" AS {}", format_identifier(a)));
1014 }
1015
1016 result
1017 }
1018 FromClause::Subquery { query, alias, column_aliases } => {
1019 let mut result = format!("({}) AS {}", query.to_sql(), format_identifier(alias));
1020 if let Some(cols) = column_aliases {
1021 result.push_str(&format!(" ({})", cols.join(", ")));
1022 }
1023 result
1024 }
1025 FromClause::Values { rows, alias, column_aliases } => {
1026 let rows_sql: Vec<String> = rows
1027 .iter()
1028 .map(|row| {
1029 let cols: Vec<String> = row.iter().map(|e| e.to_sql()).collect();
1030 format!("({})", cols.join(", "))
1031 })
1032 .collect();
1033 let mut result =
1034 format!("(VALUES {}) AS {}", rows_sql.join(", "), format_identifier(alias));
1035 if let Some(cols) = column_aliases {
1036 result.push_str(&format!(" ({})", cols.join(", ")));
1037 }
1038 result
1039 }
1040 }
1041 }
1042}
1043
1044impl ToSql for JoinType {
1045 fn to_sql(&self) -> String {
1046 match self {
1047 JoinType::Inner => "INNER JOIN".to_string(),
1048 JoinType::LeftOuter => "LEFT OUTER JOIN".to_string(),
1049 JoinType::RightOuter => "RIGHT OUTER JOIN".to_string(),
1050 JoinType::FullOuter => "FULL OUTER JOIN".to_string(),
1051 JoinType::Cross => "CROSS JOIN".to_string(),
1052 JoinType::Semi => "SEMI JOIN".to_string(),
1053 JoinType::Anti => "ANTI JOIN".to_string(),
1054 }
1055 }
1056}
1057
1058impl ToSql for OrderByItem {
1059 fn to_sql(&self) -> String {
1060 format!("{} {}", self.expr.to_sql(), self.direction.to_sql())
1061 }
1062}
1063
1064impl ToSql for OrderDirection {
1065 fn to_sql(&self) -> String {
1066 match self {
1067 OrderDirection::Asc => "ASC".to_string(),
1068 OrderDirection::Desc => "DESC".to_string(),
1069 }
1070 }
1071}
1072
1073impl ToSql for SetOperation {
1074 fn to_sql(&self) -> String {
1075 let op = self.op.to_sql();
1076 let all_str = if self.all { " ALL" } else { "" };
1077 format!("{}{} {}", op, all_str, self.right.to_sql())
1078 }
1079}
1080
1081impl ToSql for SetOperator {
1082 fn to_sql(&self) -> String {
1083 match self {
1084 SetOperator::Union => "UNION".to_string(),
1085 SetOperator::Intersect => "INTERSECT".to_string(),
1086 SetOperator::Except => "EXCEPT".to_string(),
1087 }
1088 }
1089}
1090
1091impl ToSql for GroupByClause {
1096 fn to_sql(&self) -> String {
1097 match self {
1098 GroupByClause::Simple(exprs) => {
1099 let strs: Vec<String> = exprs.iter().map(|e| e.to_sql()).collect();
1100 strs.join(", ")
1101 }
1102 GroupByClause::Rollup(elements) => {
1103 let strs: Vec<String> = elements.iter().map(|e| e.to_sql()).collect();
1104 format!("ROLLUP({})", strs.join(", "))
1105 }
1106 GroupByClause::Cube(elements) => {
1107 let strs: Vec<String> = elements.iter().map(|e| e.to_sql()).collect();
1108 format!("CUBE({})", strs.join(", "))
1109 }
1110 GroupByClause::GroupingSets(sets) => {
1111 let strs: Vec<String> = sets.iter().map(|s| s.to_sql()).collect();
1112 format!("GROUPING SETS({})", strs.join(", "))
1113 }
1114 GroupByClause::Mixed(items) => {
1115 let strs: Vec<String> = items.iter().map(|i| i.to_sql()).collect();
1116 strs.join(", ")
1117 }
1118 }
1119 }
1120}
1121
1122impl ToSql for GroupingElement {
1123 fn to_sql(&self) -> String {
1124 match self {
1125 GroupingElement::Single(expr) => expr.to_sql(),
1126 GroupingElement::Composite(exprs) => {
1127 let strs: Vec<String> = exprs.iter().map(|e| e.to_sql()).collect();
1128 format!("({})", strs.join(", "))
1129 }
1130 }
1131 }
1132}
1133
1134impl ToSql for GroupingSet {
1135 fn to_sql(&self) -> String {
1136 if self.columns.is_empty() {
1137 "()".to_string()
1138 } else {
1139 let strs: Vec<String> = self.columns.iter().map(|e| e.to_sql()).collect();
1140 format!("({})", strs.join(", "))
1141 }
1142 }
1143}
1144
1145impl ToSql for MixedGroupingItem {
1146 fn to_sql(&self) -> String {
1147 match self {
1148 MixedGroupingItem::Simple(expr) => expr.to_sql(),
1149 MixedGroupingItem::Rollup(elements) => {
1150 let strs: Vec<String> = elements.iter().map(|e| e.to_sql()).collect();
1151 format!("ROLLUP({})", strs.join(", "))
1152 }
1153 MixedGroupingItem::Cube(elements) => {
1154 let strs: Vec<String> = elements.iter().map(|e| e.to_sql()).collect();
1155 format!("CUBE({})", strs.join(", "))
1156 }
1157 MixedGroupingItem::GroupingSets(sets) => {
1158 let strs: Vec<String> = sets.iter().map(|s| s.to_sql()).collect();
1159 format!("GROUPING SETS({})", strs.join(", "))
1160 }
1161 }
1162 }
1163}
1164
1165#[cfg(test)]
1170mod tests {
1171 use super::*;
1172 use crate::{ColumnIdentifier, FunctionIdentifier};
1173
1174 #[test]
1175 fn test_binary_operators() {
1176 assert_eq!(BinaryOperator::Plus.to_sql(), "+");
1177 assert_eq!(BinaryOperator::Equal.to_sql(), "=");
1178 assert_eq!(BinaryOperator::And.to_sql(), "AND");
1179 assert_eq!(BinaryOperator::Or.to_sql(), "OR");
1180 assert_eq!(BinaryOperator::Concat.to_sql(), "||");
1181 }
1182
1183 #[test]
1184 fn test_unary_operators() {
1185 assert_eq!(UnaryOperator::Not.to_sql(), "NOT");
1186 assert_eq!(UnaryOperator::Minus.to_sql(), "-");
1187 assert_eq!(UnaryOperator::IsNull.to_sql(), "IS NULL");
1188 }
1189
1190 #[test]
1191 fn test_sql_values() {
1192 assert_eq!(SqlValue::Integer(42).to_sql(), "42");
1193 assert_eq!(SqlValue::Varchar("hello".into()).to_sql(), "'hello'");
1194 assert_eq!(SqlValue::Varchar("it's".into()).to_sql(), "'it''s'");
1195 assert_eq!(SqlValue::Boolean(true).to_sql(), "TRUE");
1196 assert_eq!(SqlValue::Null.to_sql(), "NULL");
1197 }
1198
1199 #[test]
1200 fn test_data_types() {
1201 assert_eq!(DataType::Integer.to_sql(), "INTEGER");
1202 assert_eq!(DataType::Varchar { max_length: Some(255) }.to_sql(), "VARCHAR(255)");
1203 assert_eq!(DataType::Numeric { precision: 10, scale: 2 }.to_sql(), "NUMERIC(10, 2)");
1204 assert_eq!(DataType::Boolean.to_sql(), "BOOLEAN");
1205 }
1206
1207 #[test]
1208 fn test_column_ref() {
1209 let expr = Expression::ColumnRef(ColumnIdentifier::simple("id", false));
1210 assert_eq!(expr.to_sql(), "id");
1211
1212 let expr =
1213 Expression::ColumnRef(ColumnIdentifier::qualified("users", false, "name", false));
1214 assert_eq!(expr.to_sql(), "users.name");
1215 }
1216
1217 #[test]
1218 fn test_binary_op() {
1219 let expr = Expression::BinaryOp {
1220 op: BinaryOperator::Equal,
1221 left: Box::new(Expression::ColumnRef(ColumnIdentifier::simple("id", false))),
1222 right: Box::new(Expression::Literal(SqlValue::Integer(1))),
1223 };
1224 assert_eq!(expr.to_sql(), "id=1");
1226 }
1227
1228 #[test]
1229 fn test_simple_select() {
1230 let stmt = SelectStmt {
1231 with_clause: None,
1232 distinct: false,
1233 select_list: vec![SelectItem::Expression {
1234 expr: Expression::ColumnRef(ColumnIdentifier::simple("id", false)),
1235 alias: None,
1236 source_text: None,
1237 }],
1238 into_table: None,
1239 into_variables: None,
1240 from: Some(FromClause::Table {
1241 name: "users".to_string(),
1242 alias: None,
1243 column_aliases: None,
1244 quoted: false,
1245 }),
1246 where_clause: None,
1247 group_by: None,
1248 having: None,
1249 order_by: None,
1250 limit: None,
1251 offset: None,
1252 set_operation: None,
1253 values: None,
1254 };
1255 assert_eq!(stmt.to_sql(), "SELECT id FROM users");
1256 }
1257
1258 #[test]
1259 fn test_select_with_where() {
1260 let stmt = SelectStmt {
1261 with_clause: None,
1262 distinct: false,
1263 select_list: vec![
1264 SelectItem::Expression {
1265 expr: Expression::ColumnRef(ColumnIdentifier::simple("id", false)),
1266 alias: None,
1267 source_text: None,
1268 },
1269 SelectItem::Expression {
1270 expr: Expression::ColumnRef(ColumnIdentifier::simple("name", false)),
1271 alias: None,
1272 source_text: None,
1273 },
1274 ],
1275 into_table: None,
1276 into_variables: None,
1277 from: Some(FromClause::Table {
1278 name: "users".to_string(),
1279 alias: None,
1280 column_aliases: None,
1281 quoted: false,
1282 }),
1283 where_clause: Some(Expression::BinaryOp {
1284 op: BinaryOperator::Equal,
1285 left: Box::new(Expression::ColumnRef(ColumnIdentifier::simple("active", false))),
1286 right: Box::new(Expression::Literal(SqlValue::Integer(1))),
1287 }),
1288 group_by: None,
1289 having: None,
1290 order_by: None,
1291 limit: None,
1292 offset: None,
1293 set_operation: None,
1294 values: None,
1295 };
1296 assert_eq!(stmt.to_sql(), "SELECT id, name FROM users WHERE active=1");
1298 }
1299
1300 #[test]
1301 fn test_select_distinct_with_order() {
1302 let stmt = SelectStmt {
1303 with_clause: None,
1304 distinct: true,
1305 select_list: vec![SelectItem::Expression {
1306 expr: Expression::ColumnRef(ColumnIdentifier::simple("name", false)),
1307 alias: None,
1308 source_text: None,
1309 }],
1310 into_table: None,
1311 into_variables: None,
1312 from: Some(FromClause::Table {
1313 name: "users".to_string(),
1314 alias: None,
1315 column_aliases: None,
1316 quoted: false,
1317 }),
1318 where_clause: None,
1319 group_by: None,
1320 having: None,
1321 order_by: Some(vec![OrderByItem {
1322 expr: Expression::ColumnRef(ColumnIdentifier::simple("name", false)),
1323 direction: OrderDirection::Asc,
1324 nulls_order: None,
1325 }]),
1326 limit: Some(Expression::Literal(SqlValue::Integer(10))),
1327 offset: None,
1328 set_operation: None,
1329 values: None,
1330 };
1331 assert_eq!(stmt.to_sql(), "SELECT DISTINCT name FROM users ORDER BY name ASC LIMIT 10");
1332 }
1333
1334 #[test]
1335 fn test_join() {
1336 let from = FromClause::Join {
1337 left: Box::new(FromClause::Table {
1338 name: "orders".to_string(),
1339 alias: Some("o".to_string()),
1340 column_aliases: None,
1341 quoted: false,
1342 }),
1343 right: Box::new(FromClause::Table {
1344 name: "customers".to_string(),
1345 alias: Some("c".to_string()),
1346 column_aliases: None,
1347 quoted: false,
1348 }),
1349 join_type: JoinType::Inner,
1350 condition: Some(Expression::BinaryOp {
1351 op: BinaryOperator::Equal,
1352 left: Box::new(Expression::ColumnRef(ColumnIdentifier::qualified(
1353 "o",
1354 false,
1355 "customer_id",
1356 false,
1357 ))),
1358 right: Box::new(Expression::ColumnRef(ColumnIdentifier::qualified(
1359 "c", false, "id", false,
1360 ))),
1361 }),
1362 using_columns: None,
1363 natural: false,
1364 alias: None,
1365 };
1366
1367 assert_eq!(from.to_sql(), "orders AS o INNER JOIN customers AS c ON o.customer_id=c.id");
1369 }
1370
1371 #[test]
1372 fn test_aggregate_function() {
1373 let expr = Expression::AggregateFunction {
1374 name: FunctionIdentifier::new("count"),
1375 distinct: true,
1376 args: vec![Expression::ColumnRef(ColumnIdentifier::simple("id", false))],
1377 order_by: None,
1378 filter: None,
1379 };
1380 assert_eq!(expr.to_sql(), "COUNT(DISTINCT id)");
1381 }
1382
1383 #[test]
1384 fn test_case_expression() {
1385 let expr = Expression::Case {
1386 operand: None,
1387 when_clauses: vec![CaseWhen {
1388 conditions: vec![Expression::BinaryOp {
1389 op: BinaryOperator::GreaterThan,
1390 left: Box::new(Expression::ColumnRef(ColumnIdentifier::simple("x", false))),
1391 right: Box::new(Expression::Literal(SqlValue::Integer(0))),
1392 }],
1393 result: Expression::Literal(SqlValue::Varchar("positive".into())),
1394 }],
1395 else_result: Some(Box::new(Expression::Literal(SqlValue::Varchar(
1396 "non-positive".into(),
1397 )))),
1398 };
1399 assert_eq!(expr.to_sql(), "CASE WHEN x>0 THEN 'positive' ELSE 'non-positive' END");
1401 }
1402
1403 #[test]
1404 fn test_in_list() {
1405 let expr = Expression::InList {
1406 expr: Box::new(Expression::ColumnRef(ColumnIdentifier::simple("id", false))),
1407 values: vec![
1408 Expression::Literal(SqlValue::Integer(1)),
1409 Expression::Literal(SqlValue::Integer(2)),
1410 Expression::Literal(SqlValue::Integer(3)),
1411 ],
1412 negated: false,
1413 };
1414 assert_eq!(expr.to_sql(), "id IN (1, 2, 3)");
1415 }
1416
1417 #[test]
1418 fn test_between() {
1419 let expr = Expression::Between {
1420 expr: Box::new(Expression::ColumnRef(ColumnIdentifier::simple("age", false))),
1421 low: Box::new(Expression::Literal(SqlValue::Integer(18))),
1422 high: Box::new(Expression::Literal(SqlValue::Integer(65))),
1423 negated: false,
1424 symmetric: false,
1425 };
1426 assert_eq!(expr.to_sql(), "age BETWEEN 18 AND 65");
1427 }
1428
1429 #[test]
1430 fn test_group_by_rollup() {
1431 let group_by = GroupByClause::Rollup(vec![
1432 GroupingElement::Single(Expression::ColumnRef(ColumnIdentifier::simple("year", false))),
1433 GroupingElement::Single(Expression::ColumnRef(ColumnIdentifier::simple(
1434 "month", false,
1435 ))),
1436 ]);
1437 assert_eq!(group_by.to_sql(), "ROLLUP(year, month)");
1438 }
1439
1440 #[test]
1441 fn test_window_function() {
1442 let expr = Expression::WindowFunction {
1443 function: WindowFunctionSpec::Ranking {
1444 name: FunctionIdentifier::new("row_number"),
1445 args: vec![],
1446 },
1447 over: WindowSpec {
1448 partition_by: Some(vec![Expression::ColumnRef(ColumnIdentifier::simple(
1449 "dept", false,
1450 ))]),
1451 order_by: Some(vec![OrderByItem {
1452 expr: Expression::ColumnRef(ColumnIdentifier::simple("salary", false)),
1453 direction: OrderDirection::Desc,
1454 nulls_order: None,
1455 }]),
1456 frame: None,
1457 },
1458 };
1459 assert_eq!(expr.to_sql(), "ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC)");
1460 }
1461}