tank_core/writer/
sql_writer.rs

1use crate::{
2    Action, BinaryOp, BinaryOpType, ColumnDef, ColumnRef, DataSet, EitherIterator, Entity,
3    Expression, Fragment, Interval, Join, JoinType, Operand, Order, Ordered, PrimaryKeyType,
4    TableRef, UnaryOp, UnaryOpType, Value, possibly_parenthesized, separated_by, writer::Context,
5};
6use core::f64;
7use futures::future::Either;
8use std::{collections::HashMap, fmt::Write};
9use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
10
11macro_rules! write_integer {
12    ($out:ident, $value:expr) => {{
13        let mut buffer = itoa::Buffer::new();
14        $out.push_str(buffer.format($value));
15    }};
16}
17macro_rules! write_float {
18    ($this:ident, $context:ident,$out:ident, $value:expr) => {{
19        if $value.is_infinite() {
20            $this.write_value_infinity($context, $out, $value.is_sign_negative());
21        } else if $value.is_nan() {
22            $this.write_value_nan($context, $out);
23        } else {
24            let mut buffer = ryu::Buffer::new();
25            $out.push_str(buffer.format($value));
26        }
27    }};
28}
29
30/// Dialect printer converting semantic constructs into concrete SQL strings.
31pub trait SqlWriter {
32    fn as_dyn(&self) -> &dyn SqlWriter;
33
34    /// Whether the current fragment context allows alias declaration.
35    fn alias_declaration(&self, context: &mut Context) -> bool {
36        match context.fragment {
37            Fragment::SqlSelectFrom | Fragment::SqlJoin => true,
38            _ => false,
39        }
40    }
41
42    /// Escape occurrences of `search` char with `replace` while copying into buffer.
43    fn write_escaped(
44        &self,
45        _context: &mut Context,
46        out: &mut String,
47        value: &str,
48        search: char,
49        replace: &str,
50    ) {
51        let mut position = 0;
52        for (i, c) in value.char_indices() {
53            if c == search {
54                out.push_str(&value[position..i]);
55                out.push_str(replace);
56                position = i + 1;
57            }
58        }
59        out.push_str(&value[position..]);
60    }
61
62    /// Quote identifiers ("name") doubling inner quotes.
63    fn write_identifier_quoted(&self, context: &mut Context, out: &mut String, value: &str) {
64        out.push('"');
65        self.write_escaped(context, out, value, '"', "\"\"");
66        out.push('"');
67    }
68
69    /// Render a table reference with optional alias.
70    fn write_table_ref(&self, context: &mut Context, out: &mut String, value: &TableRef) {
71        if self.alias_declaration(context) || value.alias.is_empty() {
72            if !value.schema.is_empty() {
73                self.write_identifier_quoted(context, out, &value.schema);
74                out.push('.');
75            }
76            self.write_identifier_quoted(context, out, &value.name);
77        }
78        if !value.alias.is_empty() {
79            let _ = write!(out, " {}", value.alias);
80        }
81    }
82
83    /// Render a column reference optionally qualifying with schema/table.
84    fn write_column_ref(&self, context: &mut Context, out: &mut String, value: &ColumnRef) {
85        if context.qualify_columns && !value.table.is_empty() {
86            if !value.schema.is_empty() {
87                self.write_identifier_quoted(context, out, &value.schema);
88                out.push('.');
89            }
90            self.write_identifier_quoted(context, out, &value.table);
91            out.push('.');
92        }
93        self.write_identifier_quoted(context, out, &value.name);
94    }
95
96    /// Render the SQL type for a `Value` prototype.
97    fn write_column_type(&self, context: &mut Context, out: &mut String, value: &Value) {
98        match value {
99            Value::Boolean(..) => out.push_str("BOOLEAN"),
100            Value::Int8(..) => out.push_str("TINYINT"),
101            Value::Int16(..) => out.push_str("SMALLINT"),
102            Value::Int32(..) => out.push_str("INTEGER"),
103            Value::Int64(..) => out.push_str("BIGINT"),
104            Value::Int128(..) => out.push_str("HUGEINT"),
105            Value::UInt8(..) => out.push_str("UTINYINT"),
106            Value::UInt16(..) => out.push_str("USMALLINT"),
107            Value::UInt32(..) => out.push_str("UINTEGER"),
108            Value::UInt64(..) => out.push_str("UBIGINT"),
109            Value::UInt128(..) => out.push_str("UHUGEINT"),
110            Value::Float32(..) => out.push_str("FLOAT"),
111            Value::Float64(..) => out.push_str("DOUBLE"),
112            Value::Decimal(.., precision, scale) => {
113                out.push_str("DECIMAL");
114                if (precision, scale) != (&0, &0) {
115                    let _ = write!(out, "({},{})", precision, scale);
116                }
117            }
118            Value::Char(..) => out.push_str("CHAR(1)"),
119            Value::Varchar(..) => out.push_str("VARCHAR"),
120            Value::Blob(..) => out.push_str("BLOB"),
121            Value::Date(..) => out.push_str("DATE"),
122            Value::Time(..) => out.push_str("TIME"),
123            Value::Timestamp(..) => out.push_str("TIMESTAMP"),
124            Value::TimestampWithTimezone(..) => out.push_str("TIMESTAMPTZ"),
125            Value::Interval(..) => out.push_str("INTERVAL"),
126            Value::Uuid(..) => out.push_str("UUID"),
127            Value::Array(.., inner, size) => {
128                self.write_column_type(context, out, inner);
129                let _ = write!(out, "[{}]", size);
130            }
131            Value::List(.., inner) => {
132                self.write_column_type(context, out, inner);
133                out.push_str("[]");
134            }
135            Value::Map(.., key, value) => {
136                out.push_str("MAP(");
137                self.write_column_type(context, out, key);
138                out.push(',');
139                self.write_column_type(context, out, value);
140                out.push(')');
141            }
142            _ => log::error!(
143                "Unexpected tank::Value, variant {:?} is not supported",
144                value
145            ),
146        };
147    }
148
149    /// Render a concrete value (including proper quoting / escaping).
150    fn write_value(&self, context: &mut Context, out: &mut String, value: &Value) {
151        match value {
152            v if v.is_null() => self.write_value_none(context, out),
153            Value::Boolean(Some(v), ..) => self.write_value_bool(context, out, *v),
154            Value::Int8(Some(v), ..) => write_integer!(out, *v),
155            Value::Int16(Some(v), ..) => write_integer!(out, *v),
156            Value::Int32(Some(v), ..) => write_integer!(out, *v),
157            Value::Int64(Some(v), ..) => write_integer!(out, *v),
158            Value::Int128(Some(v), ..) => write_integer!(out, *v),
159            Value::UInt8(Some(v), ..) => write_integer!(out, *v),
160            Value::UInt16(Some(v), ..) => write_integer!(out, *v),
161            Value::UInt32(Some(v), ..) => write_integer!(out, *v),
162            Value::UInt64(Some(v), ..) => write_integer!(out, *v),
163            Value::UInt128(Some(v), ..) => write_integer!(out, *v),
164            Value::Float32(Some(v), ..) => write_float!(self, context, out, *v),
165            Value::Float64(Some(v), ..) => write_float!(self, context, out, *v),
166            Value::Decimal(Some(v), ..) => drop(write!(out, "{}", v)),
167            Value::Char(Some(v), ..) => {
168                out.push('\'');
169                out.push(*v);
170                out.push('\'');
171            }
172            Value::Varchar(Some(v), ..) => self.write_value_string(context, out, v),
173            Value::Blob(Some(v), ..) => self.write_value_blob(context, out, v.as_ref()),
174            Value::Date(Some(v), ..) => self.write_value_date(context, out, v, false),
175            Value::Time(Some(v), ..) => self.write_value_time(context, out, v, false),
176            Value::Timestamp(Some(v), ..) => self.write_value_timestamp(context, out, v),
177            Value::TimestampWithTimezone(Some(v), ..) => {
178                self.write_value_timestamptz(context, out, v)
179            }
180            Value::Interval(Some(v), ..) => self.write_value_interval(context, out, v),
181            Value::Uuid(Some(v), ..) => drop(write!(out, "'{}'", v)),
182            Value::Array(Some(..), ..) | Value::List(Some(..), ..) => match value {
183                Value::Array(Some(v), ..) => {
184                    self.write_value_list(context, out, Either::Left(v), value)
185                }
186                Value::List(Some(v), ..) => {
187                    self.write_value_list(context, out, Either::Right(v), value)
188                }
189                _ => unreachable!(),
190            },
191            Value::Map(Some(v), ..) => self.write_value_map(context, out, v),
192            Value::Struct(Some(v), ..) => self.write_value_struct(context, out, v),
193            _ => {
194                log::error!("Cannot write {:?}", value);
195            }
196        };
197    }
198
199    /// Render NULL literal.
200    fn write_value_none(&self, _context: &mut Context, out: &mut String) {
201        out.push_str("NULL");
202    }
203
204    /// Render boolean literal.
205    fn write_value_bool(&self, _context: &mut Context, out: &mut String, value: bool) {
206        out.push_str(["false", "true"][value as usize]);
207    }
208
209    /// Render +/- INF via CAST for dialect portability.
210    fn write_value_infinity(&self, context: &mut Context, out: &mut String, negative: bool) {
211        let mut buffer = ryu::Buffer::new();
212        self.write_expression_binary_op(
213            context,
214            out,
215            &BinaryOp {
216                op: BinaryOpType::Cast,
217                lhs: &Operand::LitStr(buffer.format(if negative {
218                    f64::NEG_INFINITY
219                } else {
220                    f64::INFINITY
221                })),
222                rhs: &Operand::Type(Value::Float64(None)),
223            },
224        );
225    }
226
227    /// Render NaN via CAST for dialect portability.
228    fn write_value_nan(&self, context: &mut Context, out: &mut String) {
229        let mut buffer = ryu::Buffer::new();
230        self.write_expression_binary_op(
231            context,
232            out,
233            &BinaryOp {
234                op: BinaryOpType::Cast,
235                lhs: &Operand::LitStr(buffer.format(f64::NAN)),
236                rhs: &Operand::Type(Value::Float64(None)),
237            },
238        );
239    }
240
241    /// Render and escape a string literal using single quotes.
242    fn write_value_string(&self, _context: &mut Context, out: &mut String, value: &str) {
243        out.push('\'');
244        let mut pos = 0;
245        for (i, c) in value.char_indices() {
246            if c == '\'' {
247                out.push_str(&value[pos..i]);
248                out.push_str("''");
249                pos = i + 1;
250            } else if c == '\n' {
251                out.push_str(&value[pos..i]);
252                out.push_str("\\n");
253                pos = i + 1;
254            }
255        }
256        out.push_str(&value[pos..]);
257        out.push('\'');
258    }
259
260    /// Render a blob literal using hex escapes.
261    fn write_value_blob(&self, _context: &mut Context, out: &mut String, value: &[u8]) {
262        out.push('\'');
263        for b in value {
264            let _ = write!(out, "\\x{:X}", b);
265        }
266        out.push('\'');
267    }
268
269    /// Render a DATE literal (optionally as part of TIMESTAMP composition).
270    fn write_value_date(
271        &self,
272        _context: &mut Context,
273        out: &mut String,
274        value: &Date,
275        timestamp: bool,
276    ) {
277        let b = if timestamp { "" } else { "'" };
278        let _ = write!(
279            out,
280            "{b}{:04}-{:02}-{:02}{b}",
281            value.year(),
282            value.month() as u8,
283            value.day()
284        );
285    }
286
287    /// Render a TIME literal (optionally as part of TIMESTAMP composition).
288    fn write_value_time(
289        &self,
290        _context: &mut Context,
291        out: &mut String,
292        value: &Time,
293        timestamp: bool,
294    ) {
295        let mut subsecond = value.nanosecond();
296        let mut width = 9;
297        while width > 1 && subsecond % 10 == 0 {
298            subsecond /= 10;
299            width -= 1;
300        }
301        let b = if timestamp { "" } else { "'" };
302        let _ = write!(
303            out,
304            "{b}{:02}:{:02}:{:02}.{:0width$}{b}",
305            value.hour(),
306            value.minute(),
307            value.second(),
308            subsecond
309        );
310    }
311
312    /// Render a TIMESTAMP literal.
313    fn write_value_timestamp(
314        &self,
315        context: &mut Context,
316        out: &mut String,
317        value: &PrimitiveDateTime,
318    ) {
319        out.push('\'');
320        self.write_value_date(context, out, &value.date(), true);
321        out.push('T');
322        self.write_value_time(context, out, &value.time(), true);
323        out.push('\'');
324    }
325
326    /// Render a TIMESTAMPTZ literal.
327    fn write_value_timestamptz(
328        &self,
329        context: &mut Context,
330        out: &mut String,
331        value: &OffsetDateTime,
332    ) {
333        let date_time = value.to_utc();
334        self.write_value_timestamp(
335            context,
336            out,
337            &PrimitiveDateTime::new(date_time.date(), date_time.time()),
338        );
339    }
340
341    /// Ordered units used to decompose intervals.
342    fn value_interval_units(&self) -> &[(&str, i128)] {
343        static UNITS: &[(&str, i128)] = &[
344            ("DAY", Interval::NANOS_IN_DAY),
345            ("HOUR", Interval::NANOS_IN_SEC * 3600),
346            ("MINUTE", Interval::NANOS_IN_SEC * 60),
347            ("SECOND", Interval::NANOS_IN_SEC),
348            ("MICROSECOND", 1_000),
349            ("NANOSECOND", 1),
350        ];
351        UNITS
352    }
353
354    /// Render INTERVAL literal using largest representative units.
355    fn write_value_interval(&self, _context: &mut Context, out: &mut String, value: &Interval) {
356        out.push_str("INTERVAL '");
357        if value.is_zero() {
358            out.push_str("0 SECONDS");
359        }
360        macro_rules! write_unit {
361            ($out:ident, $len:ident, $val:expr, $unit:expr) => {
362                if $out.len() > $len {
363                    $out.push(' ');
364                    $len = $out.len();
365                }
366                let _ = write!(
367                    $out,
368                    "{} {}{}",
369                    $val,
370                    $unit,
371                    if $val != 1 { "S" } else { "" }
372                );
373            };
374        }
375        let mut months = value.months;
376        let mut nanos = value.nanos + value.days as i128 * Interval::NANOS_IN_DAY;
377        let mut len = out.len();
378        if months != 0 {
379            if months > 48 || months % 12 == 0 {
380                write_unit!(out, len, months / 12, "YEAR");
381                months = months % 12;
382            }
383            if months != 0 {
384                write_unit!(out, len, months, "MONTH");
385            }
386        }
387        for &(name, factor) in self.value_interval_units() {
388            let rem = nanos % factor;
389            if rem == 0 || factor / rem > 1_000_000 {
390                let value = nanos / factor;
391                if value != 0 {
392                    write_unit!(out, len, value, name);
393                    nanos = rem;
394                    if nanos == 0 {
395                        break;
396                    }
397                }
398            }
399        }
400        out.push('\'');
401    }
402
403    /// Render list/array literal.
404    fn write_value_list<'a>(
405        &self,
406        context: &mut Context,
407        out: &mut String,
408        value: Either<&Box<[Value]>, &Vec<Value>>,
409        _ty: &Value,
410    ) {
411        out.push('[');
412        separated_by(
413            out,
414            match value {
415                Either::Left(v) => v.iter(),
416                Either::Right(v) => v.iter(),
417            },
418            |out, v| {
419                self.write_value(context, out, v);
420            },
421            ",",
422        );
423        out.push(']');
424    }
425
426    /// Render map literal.
427    fn write_value_map(
428        &self,
429        context: &mut Context,
430        out: &mut String,
431        value: &HashMap<Value, Value>,
432    ) {
433        out.push('{');
434        separated_by(
435            out,
436            value,
437            |out, (k, v)| {
438                self.write_value(context, out, k);
439                out.push(':');
440                self.write_value(context, out, v);
441            },
442            ",",
443        );
444        out.push('}');
445    }
446
447    /// Render struct literal.
448    fn write_value_struct(
449        &self,
450        context: &mut Context,
451        out: &mut String,
452        value: &Vec<(String, Value)>,
453    ) {
454        out.push('{');
455        separated_by(
456            out,
457            value,
458            |out, (k, v)| {
459                self.write_value_string(context, out, k);
460                out.push(':');
461                self.write_value(context, out, v);
462            },
463            ",",
464        );
465        out.push('}');
466    }
467
468    /// Precedence table for unary operators.
469    fn expression_unary_op_precedence<'a>(&self, value: &UnaryOpType) -> i32 {
470        match value {
471            UnaryOpType::Negative => 1250,
472            UnaryOpType::Not => 250,
473        }
474    }
475
476    /// Precedence table for binary operators.
477    fn expression_binary_op_precedence<'a>(&self, value: &BinaryOpType) -> i32 {
478        match value {
479            BinaryOpType::Or => 100,
480            BinaryOpType::And => 200,
481            BinaryOpType::Equal => 300,
482            BinaryOpType::NotEqual => 300,
483            BinaryOpType::Less => 300,
484            BinaryOpType::Greater => 300,
485            BinaryOpType::LessEqual => 300,
486            BinaryOpType::GreaterEqual => 300,
487            BinaryOpType::Is => 400,
488            BinaryOpType::IsNot => 400,
489            BinaryOpType::Like => 400,
490            BinaryOpType::NotLike => 400,
491            BinaryOpType::Regexp => 400,
492            BinaryOpType::NotRegexp => 400,
493            BinaryOpType::Glob => 400,
494            BinaryOpType::NotGlob => 400,
495            BinaryOpType::BitwiseOr => 500,
496            BinaryOpType::BitwiseAnd => 600,
497            BinaryOpType::ShiftLeft => 700,
498            BinaryOpType::ShiftRight => 700,
499            BinaryOpType::Subtraction => 800,
500            BinaryOpType::Addition => 800,
501            BinaryOpType::Multiplication => 900,
502            BinaryOpType::Division => 900,
503            BinaryOpType::Remainder => 900,
504            BinaryOpType::Indexing => 1000,
505            BinaryOpType::Cast => 1100,
506            BinaryOpType::Alias => 1200,
507        }
508    }
509
510    /// Render an operand (literal / variable / nested expression).
511    fn write_expression_operand(&self, context: &mut Context, out: &mut String, value: &Operand) {
512        match value {
513            Operand::LitBool(v) => self.write_value_bool(context, out, *v),
514            Operand::LitFloat(v) => write_float!(self, context, out, *v),
515            Operand::LitIdent(v) => drop(out.push_str(v)),
516            Operand::LitField(v) => separated_by(out, *v, |out, v| out.push_str(v), "."),
517            Operand::LitInt(v) => write_integer!(out, *v),
518            Operand::LitStr(v) => self.write_value_string(context, out, v),
519            Operand::LitArray(v) => {
520                out.push('[');
521                separated_by(
522                    out,
523                    *v,
524                    |out, v| {
525                        v.write_query(self.as_dyn(), context, out);
526                    },
527                    ", ",
528                );
529                out.push(']');
530            }
531            Operand::Null => drop(out.push_str("NULL")),
532            Operand::Type(v) => self.write_column_type(context, out, v),
533            Operand::Variable(v) => self.write_value(context, out, v),
534            Operand::Call(f, args) => {
535                out.push_str(f);
536                out.push('(');
537                separated_by(
538                    out,
539                    *args,
540                    |out, v| {
541                        v.write_query(self.as_dyn(), context, out);
542                    },
543                    ",",
544                );
545                out.push(')');
546            }
547            Operand::Asterisk => drop(out.push('*')),
548            Operand::QuestionMark => self.write_expression_operand_question_mark(context, out),
549        };
550    }
551
552    /// Render parameter placeholder (dialect may override).
553    fn write_expression_operand_question_mark(&self, _context: &mut Context, out: &mut String) {
554        out.push('?');
555    }
556
557    /// Render unary operator expression.
558    fn write_expression_unary_op(
559        &self,
560        context: &mut Context,
561        out: &mut String,
562        value: &UnaryOp<&dyn Expression>,
563    ) {
564        match value.op {
565            UnaryOpType::Negative => out.push('-'),
566            UnaryOpType::Not => out.push_str("NOT "),
567        };
568        possibly_parenthesized!(
569            out,
570            value.arg.precedence(self.as_dyn()) <= self.expression_unary_op_precedence(&value.op),
571            value.arg.write_query(self.as_dyn(), context, out)
572        );
573    }
574
575    /// Render binary operator expression handling precedence / parenthesis.
576    fn write_expression_binary_op(
577        &self,
578        context: &mut Context,
579        out: &mut String,
580        value: &BinaryOp<&dyn Expression, &dyn Expression>,
581    ) {
582        let (prefix, infix, suffix, lhs_parenthesized, rhs_parenthesized) = match value.op {
583            BinaryOpType::Indexing => ("", "[", "]", false, true),
584            BinaryOpType::Cast => ("CAST(", " AS ", ")", true, true),
585            BinaryOpType::Multiplication => ("", " * ", "", false, false),
586            BinaryOpType::Division => ("", " / ", "", false, false),
587            BinaryOpType::Remainder => ("", " % ", "", false, false),
588            BinaryOpType::Addition => ("", " + ", "", false, false),
589            BinaryOpType::Subtraction => ("", " - ", "", false, false),
590            BinaryOpType::ShiftLeft => ("", " << ", "", false, false),
591            BinaryOpType::ShiftRight => ("", " >> ", "", false, false),
592            BinaryOpType::BitwiseAnd => ("", " & ", "", false, false),
593            BinaryOpType::BitwiseOr => ("", " | ", "", false, false),
594            BinaryOpType::Is => ("", " IS ", "", false, false),
595            BinaryOpType::IsNot => ("", " IS NOT ", "", false, false),
596            BinaryOpType::Like => ("", " LIKE ", "", false, false),
597            BinaryOpType::NotLike => ("", " NOT LIKE ", "", false, false),
598            BinaryOpType::Regexp => ("", " REGEXP ", "", false, false),
599            BinaryOpType::NotRegexp => ("", " NOT REGEXP ", "", false, false),
600            BinaryOpType::Glob => ("", " GLOB ", "", false, false),
601            BinaryOpType::NotGlob => ("", " NOT GLOB ", "", false, false),
602            BinaryOpType::Equal => ("", " = ", "", false, false),
603            BinaryOpType::NotEqual => ("", " != ", "", false, false),
604            BinaryOpType::Less => ("", " < ", "", false, false),
605            BinaryOpType::LessEqual => ("", " <= ", "", false, false),
606            BinaryOpType::Greater => ("", " > ", "", false, false),
607            BinaryOpType::GreaterEqual => ("", " >= ", "", false, false),
608            BinaryOpType::And => ("", " AND ", "", false, false),
609            BinaryOpType::Or => ("", " OR ", "", false, false),
610            BinaryOpType::Alias => {
611                if context.fragment == Fragment::SqlSelectOrderBy {
612                    return value.lhs.write_query(self.as_dyn(), context, out);
613                } else {
614                    ("", " AS ", "", false, false)
615                }
616            }
617        };
618        let mut context = context.switch_fragment(if value.op == BinaryOpType::Cast {
619            Fragment::Casting
620        } else {
621            context.fragment
622        });
623        let precedence = self.expression_binary_op_precedence(&value.op);
624        out.push_str(prefix);
625        possibly_parenthesized!(
626            out,
627            !lhs_parenthesized && value.lhs.precedence(self.as_dyn()) < precedence,
628            value
629                .lhs
630                .write_query(self.as_dyn(), &mut context.current, out)
631        );
632        out.push_str(infix);
633        possibly_parenthesized!(
634            out,
635            !rhs_parenthesized && value.rhs.precedence(self.as_dyn()) <= precedence,
636            value
637                .rhs
638                .write_query(self.as_dyn(), &mut context.current, out)
639        );
640        out.push_str(suffix);
641    }
642
643    /// Render ordered expression inside ORDER BY.
644    fn write_expression_ordered(
645        &self,
646        context: &mut Context,
647        out: &mut String,
648        value: &Ordered<&dyn Expression>,
649    ) {
650        value.expression.write_query(self.as_dyn(), context, out);
651        if context.fragment == Fragment::SqlSelectOrderBy {
652            let _ = write!(
653                out,
654                " {}",
655                match value.order {
656                    Order::ASC => "ASC",
657                    Order::DESC => "DESC",
658                }
659            );
660        }
661    }
662
663    /// Render join keyword(s) for the given join type.
664    fn write_join_type(&self, _context: &mut Context, out: &mut String, join_type: &JoinType) {
665        out.push_str(match &join_type {
666            JoinType::Default => "JOIN",
667            JoinType::Inner => "INNER JOIN",
668            JoinType::Outer => "OUTER JOIN",
669            JoinType::Left => "LEFT JOIN",
670            JoinType::Right => "RIGHT JOIN",
671            JoinType::Cross => "CROSS",
672            JoinType::Natural => "NATURAL JOIN",
673        });
674    }
675
676    /// Render a JOIN clause.
677    fn write_join(
678        &self,
679        context: &mut Context,
680        out: &mut String,
681        join: &Join<&dyn DataSet, &dyn DataSet, &dyn Expression>,
682    ) {
683        let mut context = context.switch_fragment(Fragment::SqlJoin);
684        context.current.qualify_columns = true;
685        join.lhs
686            .write_query(self.as_dyn(), &mut context.current, out);
687        out.push(' ');
688        self.write_join_type(&mut context.current, out, &join.join);
689        out.push(' ');
690        join.rhs
691            .write_query(self.as_dyn(), &mut context.current, out);
692        if let Some(on) = &join.on {
693            out.push_str(" ON ");
694            on.write_query(self.as_dyn(), &mut context.current, out);
695        }
696    }
697
698    /// Emit BEGIN statement.
699    fn write_transaction_begin(&self, out: &mut String) {
700        out.push_str("BEGIN;");
701    }
702
703    /// Emit COMMIT statement.
704    fn write_transaction_commit(&self, out: &mut String) {
705        out.push_str("COMMIT;");
706    }
707
708    /// Emit ROLLBACK statement.
709    fn write_transaction_rollback(&self, out: &mut String) {
710        out.push_str("ROLLBACK;");
711    }
712
713    /// Emit CREATE SCHEMA.
714    fn write_create_schema<E>(&self, out: &mut String, if_not_exists: bool)
715    where
716        Self: Sized,
717        E: Entity,
718    {
719        out.reserve(32 + E::table().schema.len());
720        if !out.is_empty() {
721            out.push('\n');
722        }
723        out.push_str("CREATE SCHEMA ");
724        let mut context = Context::new(Fragment::SqlCreateSchema, E::qualified_columns());
725        if if_not_exists {
726            out.push_str("IF NOT EXISTS ");
727        }
728        self.write_identifier_quoted(&mut context, out, E::table().schema);
729        out.push(';');
730    }
731
732    /// Emit DROP SCHEMA.
733    fn write_drop_schema<E>(&self, out: &mut String, if_exists: bool)
734    where
735        Self: Sized,
736        E: Entity,
737    {
738        out.reserve(24 + E::table().schema.len());
739        if !out.is_empty() {
740            out.push('\n');
741        }
742        out.push_str("DROP SCHEMA ");
743        let mut context = Context::new(Fragment::SqlDropSchema, E::qualified_columns());
744        if if_exists {
745            out.push_str("IF EXISTS ");
746        }
747        self.write_identifier_quoted(&mut context, out, E::table().schema);
748        out.push(';');
749    }
750
751    /// Emit CREATE TABLE with columns, constraints & comments.
752    fn write_create_table<E>(&self, out: &mut String, if_not_exists: bool)
753    where
754        Self: Sized,
755        E: Entity,
756    {
757        let mut context = Context::new(Fragment::SqlCreateTable, E::qualified_columns());
758        let estimated = 128 + E::columns().len() * 64 + E::primary_key_def().len() * 24;
759        out.reserve(estimated);
760        if !out.is_empty() {
761            out.push('\n');
762        }
763        out.push_str("CREATE TABLE ");
764        if if_not_exists {
765            out.push_str("IF NOT EXISTS ");
766        }
767        self.write_table_ref(&mut context, out, E::table());
768        out.push_str(" (\n");
769        separated_by(
770            out,
771            E::columns(),
772            |out, v| {
773                self.write_create_table_column_fragment(&mut context, out, v);
774            },
775            ",\n",
776        );
777        let primary_key = E::primary_key_def();
778        if primary_key.len() > 1 {
779            out.push_str(",\nPRIMARY KEY (");
780            separated_by(
781                out,
782                primary_key,
783                |out, v| {
784                    self.write_identifier_quoted(
785                        &mut context
786                            .switch_fragment(Fragment::SqlCreateTablePrimaryKey)
787                            .current,
788                        out,
789                        v.name(),
790                    );
791                },
792                ", ",
793            );
794            out.push(')');
795        }
796        for unique in E::unique_defs() {
797            if unique.len() > 1 {
798                out.push_str(",\nUNIQUE (");
799                separated_by(
800                    out,
801                    unique,
802                    |out, v| {
803                        self.write_identifier_quoted(
804                            &mut context
805                                .switch_fragment(Fragment::SqlCreateTableUnique)
806                                .current,
807                            out,
808                            v.name(),
809                        );
810                    },
811                    ", ",
812                );
813                out.push(')');
814            }
815        }
816        out.push_str(");");
817        self.write_column_comments::<E>(&mut context, out);
818    }
819
820    /// Emit COMMENT ON COLUMN statements for columns carrying comments.
821    fn write_column_comments<E>(&self, context: &mut Context, out: &mut String)
822    where
823        Self: Sized,
824        E: Entity,
825    {
826        let mut context = context.switch_fragment(Fragment::SqlCommentOnColumn);
827        context.current.qualify_columns = true;
828        for c in E::columns().iter().filter(|c| !c.comment.is_empty()) {
829            out.push_str("\nCOMMENT ON COLUMN ");
830            self.write_column_ref(&mut context.current, out, c.into());
831            out.push_str(" IS ");
832            self.write_value_string(&mut context.current, out, c.comment);
833            out.push(';');
834        }
835    }
836
837    /// Emit single column definition fragment.
838    fn write_create_table_column_fragment(
839        &self,
840        context: &mut Context,
841        out: &mut String,
842        column: &ColumnDef,
843    ) where
844        Self: Sized,
845    {
846        self.write_identifier_quoted(context, out, &column.name());
847        out.push(' ');
848        if !column.column_type.is_empty() {
849            out.push_str(&column.column_type);
850        } else {
851            SqlWriter::write_column_type(self, context, out, &column.value);
852        }
853        if !column.nullable && column.primary_key == PrimaryKeyType::None {
854            out.push_str(" NOT NULL");
855        }
856        if let Some(default) = &column.default {
857            out.push_str(" DEFAULT ");
858            default.write_query(self.as_dyn(), context, out);
859        }
860        if column.primary_key == PrimaryKeyType::PrimaryKey {
861            // Composite primary key will be printed elsewhere
862            out.push_str(" PRIMARY KEY");
863        }
864        if column.unique && column.primary_key != PrimaryKeyType::PrimaryKey {
865            out.push_str(" UNIQUE");
866        }
867        if let Some(references) = column.references {
868            out.push_str(" REFERENCES ");
869            self.write_table_ref(context, out, &references.table());
870            out.push('(');
871            self.write_column_ref(context, out, &references);
872            out.push(')');
873            if let Some(on_delete) = &column.on_delete {
874                out.push_str(" ON DELETE ");
875                self.write_create_table_references_action(context, out, on_delete);
876            }
877            if let Some(on_update) = &column.on_update {
878                out.push_str(" ON UPDATE ");
879                self.write_create_table_references_action(context, out, on_update);
880            }
881        }
882    }
883
884    /// Emit referential action keyword.
885    fn write_create_table_references_action(
886        &self,
887        _context: &mut Context,
888        out: &mut String,
889        action: &Action,
890    ) {
891        out.push_str(match action {
892            Action::NoAction => "NO ACTION",
893            Action::Restrict => "RESTRICT",
894            Action::Cascade => "CASCADE",
895            Action::SetNull => "SET NULL",
896            Action::SetDefault => "SET DEFAULT",
897        });
898    }
899
900    /// Emit DROP TABLE statement.
901    fn write_drop_table<E>(&self, out: &mut String, if_exists: bool)
902    where
903        Self: Sized,
904        E: Entity,
905    {
906        out.reserve(24 + E::table().schema.len() + E::table().name.len());
907        if !out.is_empty() {
908            out.push('\n');
909        }
910        out.push_str("DROP TABLE ");
911        let mut context = Context::new(Fragment::SqlDropTable, E::qualified_columns());
912        if if_exists {
913            out.push_str("IF EXISTS ");
914        }
915        self.write_table_ref(&mut context, out, E::table());
916        out.push(';');
917    }
918
919    /// Emit SELECT statement (projection, FROM, WHERE, ORDER, LIMIT).
920    fn write_select<Item, Cols, Data, Cond>(
921        &self,
922        out: &mut String,
923        columns: Cols,
924        from: &Data,
925        condition: &Cond,
926        limit: Option<u32>,
927    ) where
928        Self: Sized,
929        Item: Expression,
930        Cols: IntoIterator<Item = Item> + Clone,
931        Data: DataSet,
932        Cond: Expression,
933    {
934        let cols = columns.clone().into_iter().count();
935        out.reserve(128 + cols * 32);
936        if !out.is_empty() {
937            out.push('\n');
938        }
939        out.push_str("SELECT ");
940        let mut has_order_by = false;
941        let mut context = Context::new(Fragment::SqlSelect, Data::qualified_columns());
942        separated_by(
943            out,
944            columns.clone(),
945            |out, col| {
946                col.write_query(self, &mut context, out);
947                has_order_by = has_order_by || col.is_ordered();
948            },
949            ", ",
950        );
951        out.push_str("\nFROM ");
952        from.write_query(
953            self,
954            &mut context.switch_fragment(Fragment::SqlSelectFrom).current,
955            out,
956        );
957        out.push_str("\nWHERE ");
958        condition.write_query(
959            self,
960            &mut context.switch_fragment(Fragment::SqlSelectWhere).current,
961            out,
962        );
963        if has_order_by {
964            out.push_str("\nORDER BY ");
965            let mut order_context = context.switch_fragment(Fragment::SqlSelectOrderBy);
966            separated_by(
967                out,
968                columns.into_iter().filter(Expression::is_ordered),
969                |out, col| {
970                    col.write_query(self, &mut order_context.current, out);
971                },
972                ", ",
973            );
974        }
975        if let Some(limit) = limit {
976            let _ = write!(out, "\nLIMIT {}", limit);
977        }
978        out.push(';');
979    }
980
981    /// Emit INSERT (single/multi-row) optionally with ON CONFLICT DO UPDATE.
982    fn write_insert<'b, E, It>(&self, out: &mut String, entities: It, update: bool)
983    where
984        Self: Sized,
985        E: Entity + 'b,
986        It: IntoIterator<Item = &'b E>,
987    {
988        let mut rows = entities.into_iter().map(Entity::row_filtered).peekable();
989        let Some(mut row) = rows.next() else {
990            return;
991        };
992        let cols = E::columns().len();
993        out.reserve(128 + cols * 48);
994        if !out.is_empty() {
995            out.push('\n');
996        }
997        out.push_str("INSERT INTO ");
998        let mut context = Context::new(Fragment::SqlInsertInto, E::qualified_columns());
999        self.write_table_ref(&mut context, out, E::table());
1000        out.push_str(" (");
1001        let columns = E::columns().iter();
1002        let single = rows.peek().is_none();
1003        if single {
1004            // Inserting a single row uses row_labeled to filter out Passive::NotSet columns
1005            separated_by(
1006                out,
1007                row.iter(),
1008                |out, v| {
1009                    self.write_identifier_quoted(&mut context, out, v.0);
1010                },
1011                ", ",
1012            );
1013        } else {
1014            separated_by(
1015                out,
1016                columns.clone(),
1017                |out, v| {
1018                    self.write_identifier_quoted(&mut context, out, v.name());
1019                },
1020                ", ",
1021            );
1022        };
1023        out.push_str(") VALUES\n");
1024        let mut context = context.switch_fragment(Fragment::SqlInsertIntoValues);
1025        let mut first_row = None;
1026        let mut separate = false;
1027        loop {
1028            if separate {
1029                out.push_str(",\n");
1030            }
1031            out.push('(');
1032            let mut fields = row.iter();
1033            let mut field = fields.next();
1034            separated_by(
1035                out,
1036                E::columns(),
1037                |out, col| {
1038                    if Some(col.name()) == field.map(|v| v.0) {
1039                        self.write_value(
1040                            &mut context.current,
1041                            out,
1042                            field
1043                                .map(|v| &v.1)
1044                                .expect(&format!("Column {} does not have a value", col.name())),
1045                        );
1046                        field = fields.next();
1047                    } else if !single {
1048                        out.push_str("DEFAULT");
1049                    }
1050                },
1051                ", ",
1052            );
1053            out.push(')');
1054            separate = true;
1055            if first_row.is_none() {
1056                first_row = row.into();
1057            }
1058            if let Some(next) = rows.next() {
1059                row = next;
1060            } else {
1061                break;
1062            };
1063        }
1064        let first_row = first_row
1065            .expect("Should have at least one row")
1066            .into_iter()
1067            .map(|(v, _)| v);
1068        if update {
1069            self.write_insert_update_fragment::<E, _>(
1070                &mut context.current,
1071                out,
1072                if single {
1073                    EitherIterator::Left(
1074                        // If there is only one row to insert then list only the columns that appear
1075                        columns.filter(|c| first_row.clone().find(|n| *n == c.name()).is_some()),
1076                    )
1077                } else {
1078                    EitherIterator::Right(columns)
1079                },
1080            );
1081        }
1082        out.push(';');
1083    }
1084
1085    /// Emit ON CONFLICT DO UPDATE fragment for upsert.
1086    fn write_insert_update_fragment<'a, E, It>(
1087        &self,
1088        context: &mut Context,
1089        out: &mut String,
1090        columns: It,
1091    ) where
1092        Self: Sized,
1093        E: Entity,
1094        It: Iterator<Item = &'a ColumnDef>,
1095    {
1096        let pk = E::primary_key_def();
1097        if pk.len() == 0 {
1098            return;
1099        }
1100        out.push_str("\nON CONFLICT");
1101        context.fragment = Fragment::SqlInsertIntoOnConflict;
1102        if pk.len() > 0 {
1103            out.push_str(" (");
1104            separated_by(
1105                out,
1106                pk,
1107                |out, v| {
1108                    self.write_identifier_quoted(context, out, v.name());
1109                },
1110                ", ",
1111            );
1112            out.push(')');
1113        }
1114        out.push_str(" DO UPDATE SET\n");
1115        separated_by(
1116            out,
1117            columns.filter(|c| c.primary_key == PrimaryKeyType::None),
1118            |out, v| {
1119                self.write_identifier_quoted(context, out, v.name());
1120                out.push_str(" = EXCLUDED.");
1121                self.write_identifier_quoted(context, out, v.name());
1122            },
1123            ",\n",
1124        );
1125    }
1126
1127    /// Emit DELETE statement with WHERE clause.
1128    fn write_delete<E>(&self, out: &mut String, condition: &impl Expression)
1129    where
1130        Self: Sized,
1131        E: Entity,
1132    {
1133        out.reserve(128 + E::table().schema.len() + E::table().name.len());
1134        if !out.is_empty() {
1135            out.push('\n');
1136        }
1137        out.push_str("DELETE FROM ");
1138        let mut context = Context::new(Fragment::SqlDeleteFrom, E::qualified_columns());
1139        self.write_table_ref(&mut context, out, E::table());
1140        out.push_str("\nWHERE ");
1141        condition.write_query(
1142            self,
1143            &mut context
1144                .switch_fragment(Fragment::SqlDeleteFromWhere)
1145                .current,
1146            out,
1147        );
1148        out.push(';');
1149    }
1150}
1151
1152/// Fallback generic SQL writer (closest to PostgreSQL / DuckDB conventions).
1153pub struct GenericSqlWriter;
1154impl GenericSqlWriter {
1155    /// Construct a new generic writer.
1156    pub fn new() -> Self {
1157        Self {}
1158    }
1159}
1160impl SqlWriter for GenericSqlWriter {
1161    fn as_dyn(&self) -> &dyn SqlWriter {
1162        self
1163    }
1164}