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 => ("", " AS ", "", false, false),
611        };
612        let mut context = context.switch_fragment(if value.op == BinaryOpType::Cast {
613            Fragment::Casting
614        } else {
615            context.fragment
616        });
617        let precedence = self.expression_binary_op_precedence(&value.op);
618        out.push_str(prefix);
619        possibly_parenthesized!(
620            out,
621            !lhs_parenthesized && value.lhs.precedence(self.as_dyn()) < precedence,
622            value
623                .lhs
624                .write_query(self.as_dyn(), &mut context.current, out)
625        );
626        out.push_str(infix);
627        possibly_parenthesized!(
628            out,
629            !rhs_parenthesized && value.rhs.precedence(self.as_dyn()) <= precedence,
630            value
631                .rhs
632                .write_query(self.as_dyn(), &mut context.current, out)
633        );
634        out.push_str(suffix);
635    }
636
637    /// Render ordered expression inside ORDER BY.
638    fn write_expression_ordered(
639        &self,
640        context: &mut Context,
641        out: &mut String,
642        value: &Ordered<&dyn Expression>,
643    ) {
644        value.expression.write_query(self.as_dyn(), context, out);
645        if context.fragment == Fragment::SqlSelectOrderBy {
646            let _ = write!(
647                out,
648                " {}",
649                match value.order {
650                    Order::ASC => "ASC",
651                    Order::DESC => "DESC",
652                }
653            );
654        }
655    }
656
657    /// Render join keyword(s) for the given join type.
658    fn write_join_type(&self, _context: &mut Context, out: &mut String, join_type: &JoinType) {
659        out.push_str(match &join_type {
660            JoinType::Default => "JOIN",
661            JoinType::Inner => "INNER JOIN",
662            JoinType::Outer => "OUTER JOIN",
663            JoinType::Left => "LEFT JOIN",
664            JoinType::Right => "RIGHT JOIN",
665            JoinType::Cross => "CROSS",
666            JoinType::Natural => "NATURAL JOIN",
667        });
668    }
669
670    /// Render a JOIN clause.
671    fn write_join(
672        &self,
673        context: &mut Context,
674        out: &mut String,
675        join: &Join<&dyn DataSet, &dyn DataSet, &dyn Expression>,
676    ) {
677        let mut context = context.switch_fragment(Fragment::SqlJoin);
678        context.current.qualify_columns = true;
679        join.lhs
680            .write_query(self.as_dyn(), &mut context.current, out);
681        out.push(' ');
682        self.write_join_type(&mut context.current, out, &join.join);
683        out.push(' ');
684        join.rhs
685            .write_query(self.as_dyn(), &mut context.current, out);
686        if let Some(on) = &join.on {
687            out.push_str(" ON ");
688            on.write_query(self.as_dyn(), &mut context.current, out);
689        }
690    }
691
692    /// Emit BEGIN statement.
693    fn write_transaction_begin(&self, out: &mut String) {
694        out.push_str("BEGIN;");
695    }
696
697    /// Emit COMMIT statement.
698    fn write_transaction_commit(&self, out: &mut String) {
699        out.push_str("COMMIT;");
700    }
701
702    /// Emit ROLLBACK statement.
703    fn write_transaction_rollback(&self, out: &mut String) {
704        out.push_str("ROLLBACK;");
705    }
706
707    /// Emit CREATE SCHEMA.
708    fn write_create_schema<E>(&self, out: &mut String, if_not_exists: bool)
709    where
710        Self: Sized,
711        E: Entity,
712    {
713        out.reserve(32 + E::table().schema.len());
714        if !out.is_empty() {
715            out.push('\n');
716        }
717        out.push_str("CREATE SCHEMA ");
718        let mut context = Context::new(Fragment::SqlCreateSchema, E::qualified_columns());
719        if if_not_exists {
720            out.push_str("IF NOT EXISTS ");
721        }
722        self.write_identifier_quoted(&mut context, out, E::table().schema);
723        out.push(';');
724    }
725
726    /// Emit DROP SCHEMA.
727    fn write_drop_schema<E>(&self, out: &mut String, if_exists: bool)
728    where
729        Self: Sized,
730        E: Entity,
731    {
732        out.reserve(24 + E::table().schema.len());
733        if !out.is_empty() {
734            out.push('\n');
735        }
736        out.push_str("DROP SCHEMA ");
737        let mut context = Context::new(Fragment::SqlDropSchema, E::qualified_columns());
738        if if_exists {
739            out.push_str("IF EXISTS ");
740        }
741        self.write_identifier_quoted(&mut context, out, E::table().schema);
742        out.push(';');
743    }
744
745    /// Emit CREATE TABLE with columns, constraints & comments.
746    fn write_create_table<E>(&self, out: &mut String, if_not_exists: bool)
747    where
748        Self: Sized,
749        E: Entity,
750    {
751        let mut context = Context::new(Fragment::SqlCreateTable, E::qualified_columns());
752        let estimated = 128 + E::columns().len() * 64 + E::primary_key_def().len() * 24;
753        out.reserve(estimated);
754        if !out.is_empty() {
755            out.push('\n');
756        }
757        out.push_str("CREATE TABLE ");
758        if if_not_exists {
759            out.push_str("IF NOT EXISTS ");
760        }
761        self.write_table_ref(&mut context, out, E::table());
762        out.push_str(" (\n");
763        separated_by(
764            out,
765            E::columns(),
766            |out, v| {
767                self.write_create_table_column_fragment(&mut context, out, v);
768            },
769            ",\n",
770        );
771        let primary_key = E::primary_key_def();
772        if primary_key.len() > 1 {
773            out.push_str(",\nPRIMARY KEY (");
774            separated_by(
775                out,
776                primary_key,
777                |out, v| {
778                    self.write_identifier_quoted(
779                        &mut context
780                            .switch_fragment(Fragment::SqlCreateTablePrimaryKey)
781                            .current,
782                        out,
783                        v.name(),
784                    );
785                },
786                ", ",
787            );
788            out.push(')');
789        }
790        for unique in E::unique_defs() {
791            if unique.len() > 1 {
792                out.push_str(",\nUNIQUE (");
793                separated_by(
794                    out,
795                    unique,
796                    |out, v| {
797                        self.write_identifier_quoted(
798                            &mut context
799                                .switch_fragment(Fragment::SqlCreateTableUnique)
800                                .current,
801                            out,
802                            v.name(),
803                        );
804                    },
805                    ", ",
806                );
807                out.push(')');
808            }
809        }
810        out.push_str(");");
811        self.write_column_comments::<E>(&mut context, out);
812    }
813
814    /// Emit COMMENT ON COLUMN statements for columns carrying comments.
815    fn write_column_comments<E>(&self, context: &mut Context, out: &mut String)
816    where
817        Self: Sized,
818        E: Entity,
819    {
820        let mut context = context.switch_fragment(Fragment::SqlCommentOnColumn);
821        context.current.qualify_columns = true;
822        for c in E::columns().iter().filter(|c| !c.comment.is_empty()) {
823            out.push_str("\nCOMMENT ON COLUMN ");
824            self.write_column_ref(&mut context.current, out, c.into());
825            out.push_str(" IS ");
826            self.write_value_string(&mut context.current, out, c.comment);
827            out.push(';');
828        }
829    }
830
831    /// Emit single column definition fragment.
832    fn write_create_table_column_fragment(
833        &self,
834        context: &mut Context,
835        out: &mut String,
836        column: &ColumnDef,
837    ) where
838        Self: Sized,
839    {
840        self.write_identifier_quoted(context, out, &column.name());
841        out.push(' ');
842        if !column.column_type.is_empty() {
843            out.push_str(&column.column_type);
844        } else {
845            SqlWriter::write_column_type(self, context, out, &column.value);
846        }
847        if !column.nullable && column.primary_key == PrimaryKeyType::None {
848            out.push_str(" NOT NULL");
849        }
850        if let Some(default) = &column.default {
851            out.push_str(" DEFAULT ");
852            default.write_query(self.as_dyn(), context, out);
853        }
854        if column.primary_key == PrimaryKeyType::PrimaryKey {
855            // Composite primary key will be printed elsewhere
856            out.push_str(" PRIMARY KEY");
857        }
858        if column.unique && column.primary_key != PrimaryKeyType::PrimaryKey {
859            out.push_str(" UNIQUE");
860        }
861        if let Some(references) = column.references {
862            out.push_str(" REFERENCES ");
863            self.write_table_ref(context, out, &references.table());
864            out.push('(');
865            self.write_column_ref(context, out, &references);
866            out.push(')');
867            if let Some(on_delete) = &column.on_delete {
868                out.push_str(" ON DELETE ");
869                self.write_create_table_references_action(context, out, on_delete);
870            }
871            if let Some(on_update) = &column.on_update {
872                out.push_str(" ON UPDATE ");
873                self.write_create_table_references_action(context, out, on_update);
874            }
875        }
876    }
877
878    /// Emit referential action keyword.
879    fn write_create_table_references_action(
880        &self,
881        _context: &mut Context,
882        out: &mut String,
883        action: &Action,
884    ) {
885        out.push_str(match action {
886            Action::NoAction => "NO ACTION",
887            Action::Restrict => "RESTRICT",
888            Action::Cascade => "CASCADE",
889            Action::SetNull => "SET NULL",
890            Action::SetDefault => "SET DEFAULT",
891        });
892    }
893
894    /// Emit DROP TABLE statement.
895    fn write_drop_table<E>(&self, out: &mut String, if_exists: bool)
896    where
897        Self: Sized,
898        E: Entity,
899    {
900        out.reserve(24 + E::table().schema.len() + E::table().name.len());
901        if !out.is_empty() {
902            out.push('\n');
903        }
904        out.push_str("DROP TABLE ");
905        let mut context = Context::new(Fragment::SqlDropTable, E::qualified_columns());
906        if if_exists {
907            out.push_str("IF EXISTS ");
908        }
909        self.write_table_ref(&mut context, out, E::table());
910        out.push(';');
911    }
912
913    /// Emit SELECT statement (projection, FROM, WHERE, ORDER, LIMIT).
914    fn write_select<Item, Cols, Data, Cond>(
915        &self,
916        out: &mut String,
917        columns: Cols,
918        from: &Data,
919        condition: &Cond,
920        limit: Option<u32>,
921    ) where
922        Self: Sized,
923        Item: Expression,
924        Cols: IntoIterator<Item = Item> + Clone,
925        Data: DataSet,
926        Cond: Expression,
927    {
928        let cols = columns.clone().into_iter().count();
929        out.reserve(128 + cols * 32);
930        if !out.is_empty() {
931            out.push('\n');
932        }
933        out.push_str("SELECT ");
934        let mut has_order_by = false;
935        let mut context = Context::new(Fragment::SqlSelect, Data::qualified_columns());
936        separated_by(
937            out,
938            columns.clone(),
939            |out, col| {
940                col.write_query(self, &mut context, out);
941                has_order_by = has_order_by || col.is_ordered();
942            },
943            ", ",
944        );
945        out.push_str("\nFROM ");
946        from.write_query(
947            self,
948            &mut context.switch_fragment(Fragment::SqlSelectFrom).current,
949            out,
950        );
951        out.push_str("\nWHERE ");
952        condition.write_query(
953            self,
954            &mut context.switch_fragment(Fragment::SqlSelectWhere).current,
955            out,
956        );
957        if has_order_by {
958            out.push_str("\nORDER BY ");
959            let mut order_context = context.switch_fragment(Fragment::SqlSelectOrderBy);
960            separated_by(
961                out,
962                columns.into_iter().filter(Expression::is_ordered),
963                |out, col| {
964                    col.write_query(self, &mut order_context.current, out);
965                },
966                ", ",
967            );
968        }
969        if let Some(limit) = limit {
970            let _ = write!(out, "\nLIMIT {}", limit);
971        }
972        out.push(';');
973    }
974
975    /// Emit INSERT (single/multi-row) optionally with ON CONFLICT DO UPDATE.
976    fn write_insert<'b, E, It>(&self, out: &mut String, entities: It, update: bool)
977    where
978        Self: Sized,
979        E: Entity + 'b,
980        It: IntoIterator<Item = &'b E>,
981    {
982        let mut rows = entities.into_iter().map(Entity::row_filtered).peekable();
983        let Some(mut row) = rows.next() else {
984            return;
985        };
986        let cols = E::columns().len();
987        out.reserve(128 + cols * 48);
988        if !out.is_empty() {
989            out.push('\n');
990        }
991        out.push_str("INSERT INTO ");
992        let mut context = Context::new(Fragment::SqlInsertInto, E::qualified_columns());
993        self.write_table_ref(&mut context, out, E::table());
994        out.push_str(" (");
995        let columns = E::columns().iter();
996        let single = rows.peek().is_none();
997        if single {
998            // Inserting a single row uses row_labeled to filter out Passive::NotSet columns
999            separated_by(
1000                out,
1001                row.iter(),
1002                |out, v| {
1003                    self.write_identifier_quoted(&mut context, out, v.0);
1004                },
1005                ", ",
1006            );
1007        } else {
1008            separated_by(
1009                out,
1010                columns.clone(),
1011                |out, v| {
1012                    self.write_identifier_quoted(&mut context, out, v.name());
1013                },
1014                ", ",
1015            );
1016        };
1017        out.push_str(") VALUES\n");
1018        let mut context = context.switch_fragment(Fragment::SqlInsertIntoValues);
1019        let mut first_row = None;
1020        let mut separate = false;
1021        loop {
1022            if separate {
1023                out.push_str(",\n");
1024            }
1025            out.push('(');
1026            let mut fields = row.iter();
1027            let mut field = fields.next();
1028            separated_by(
1029                out,
1030                E::columns(),
1031                |out, col| {
1032                    if Some(col.name()) == field.map(|v| v.0) {
1033                        self.write_value(
1034                            &mut context.current,
1035                            out,
1036                            field
1037                                .map(|v| &v.1)
1038                                .expect(&format!("Column {} does not have a value", col.name())),
1039                        );
1040                        field = fields.next();
1041                    } else if !single {
1042                        out.push_str("DEFAULT");
1043                    }
1044                },
1045                ", ",
1046            );
1047            out.push(')');
1048            separate = true;
1049            if first_row.is_none() {
1050                first_row = row.into();
1051            }
1052            if let Some(next) = rows.next() {
1053                row = next;
1054            } else {
1055                break;
1056            };
1057        }
1058        let first_row = first_row
1059            .expect("Should have at least one row")
1060            .into_iter()
1061            .map(|(v, _)| v);
1062        if update {
1063            self.write_insert_update_fragment::<E, _>(
1064                &mut context.current,
1065                out,
1066                if single {
1067                    EitherIterator::Left(
1068                        // If there is only one row to insert then list only the columns that appear
1069                        columns.filter(|c| first_row.clone().find(|n| *n == c.name()).is_some()),
1070                    )
1071                } else {
1072                    EitherIterator::Right(columns)
1073                },
1074            );
1075        }
1076        out.push(';');
1077    }
1078
1079    /// Emit ON CONFLICT DO UPDATE fragment for upsert.
1080    fn write_insert_update_fragment<'a, E, It>(
1081        &self,
1082        context: &mut Context,
1083        out: &mut String,
1084        columns: It,
1085    ) where
1086        Self: Sized,
1087        E: Entity,
1088        It: Iterator<Item = &'a ColumnDef>,
1089    {
1090        let pk = E::primary_key_def();
1091        if pk.len() == 0 {
1092            return;
1093        }
1094        out.push_str("\nON CONFLICT");
1095        context.fragment = Fragment::SqlInsertIntoOnConflict;
1096        if pk.len() > 0 {
1097            out.push_str(" (");
1098            separated_by(
1099                out,
1100                pk,
1101                |out, v| {
1102                    self.write_identifier_quoted(context, out, v.name());
1103                },
1104                ", ",
1105            );
1106            out.push(')');
1107        }
1108        out.push_str(" DO UPDATE SET\n");
1109        separated_by(
1110            out,
1111            columns.filter(|c| c.primary_key == PrimaryKeyType::None),
1112            |out, v| {
1113                self.write_identifier_quoted(context, out, v.name());
1114                out.push_str(" = EXCLUDED.");
1115                self.write_identifier_quoted(context, out, v.name());
1116            },
1117            ",\n",
1118        );
1119    }
1120
1121    /// Emit DELETE statement with WHERE clause.
1122    fn write_delete<E>(&self, out: &mut String, condition: &impl Expression)
1123    where
1124        Self: Sized,
1125        E: Entity,
1126    {
1127        out.reserve(128 + E::table().schema.len() + E::table().name.len());
1128        if !out.is_empty() {
1129            out.push('\n');
1130        }
1131        out.push_str("DELETE FROM ");
1132        let mut context = Context::new(Fragment::SqlDeleteFrom, E::qualified_columns());
1133        self.write_table_ref(&mut context, out, E::table());
1134        out.push_str("\nWHERE ");
1135        condition.write_query(
1136            self,
1137            &mut context
1138                .switch_fragment(Fragment::SqlDeleteFromWhere)
1139                .current,
1140            out,
1141        );
1142        out.push(';');
1143    }
1144}
1145
1146/// Fallback generic SQL writer (closest to PostgreSQL / DuckDB conventions).
1147pub struct GenericSqlWriter;
1148impl GenericSqlWriter {
1149    /// Construct a new generic writer.
1150    pub fn new() -> Self {
1151        Self {}
1152    }
1153}
1154impl SqlWriter for GenericSqlWriter {
1155    fn as_dyn(&self) -> &dyn SqlWriter {
1156        self
1157    }
1158}