1use std::fmt::{self, Write};
16
17#[cfg(test)]
18use rudb_common::LogicalType;
19use rudb_common::Value;
20
21use crate::expr::Expr;
22use crate::node::Node;
23use crate::plan::Plan;
24use crate::{ExprRef, NodeRef, Slice};
25
26pub(crate) const RESERVED: [&str; 3] = ["CAST", "TRY_CAST", "CASE"];
30
31impl fmt::Display for Plan {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 write_node(self, f, self.root(), 0)
34 }
35}
36
37impl Plan {
38 #[must_use]
45 pub fn operator(&self, node: NodeRef) -> String {
46 let mut out = String::new();
47 let held = self.node(node);
48 let _ = out.write_str(held.keyword());
50 let _ = write_arguments(self, &mut out, held);
51 out
52 }
53}
54
55fn write_node<W: Write>(plan: &Plan, out: &mut W, node: NodeRef, depth: usize) -> fmt::Result {
56 for _ in 0..depth {
57 out.write_str(" ")?;
58 }
59 let held = plan.node(node);
60 out.write_str(held.keyword())?;
61 write_arguments(plan, out, held)?;
62 out.write_char('\n')?;
63 for child in held.children().into_iter().flatten() {
64 write_node(plan, out, child, depth + 1)?;
65 }
66 Ok(())
67}
68
69fn write_arguments<W: Write>(plan: &Plan, out: &mut W, node: &Node) -> fmt::Result {
70 match *node {
71 Node::Dummy | Node::CrossProduct { .. } => Ok(()),
72 Node::Get { catalog, schema, table, alias, index, columns } => {
73 out.write_char(' ')?;
74 write_identifier(out, plan.string(catalog))?;
75 out.write_char('.')?;
76 write_identifier(out, plan.string(schema))?;
77 out.write_char('.')?;
78 write_identifier(out, plan.string(table))?;
79 out.write_str(" AS ")?;
80 write_identifier(out, plan.string(alias))?;
81 write!(out, " #{index} ")?;
82 write_schema(plan, out, columns)
83 }
84 Node::Values { index, columns, rows } => {
85 write!(out, " #{index} ")?;
86 write_schema(plan, out, columns)?;
87 out.write_str(" rows=[")?;
88 for (position, row) in plan.row_list(rows).iter().enumerate() {
89 if position > 0 {
90 out.write_str(", ")?;
91 }
92 write_expr_list(plan, out, *row)?;
93 }
94 out.write_char(']')
95 }
96 Node::TableFunction { index, function, args, options, settings, columns } => {
97 out.write_char(' ')?;
98 write_identifier(out, plan.string(function))?;
99 out.write_str(" args=")?;
100 write_expr_list(plan, out, args)?;
101 if options.len > 0 {
104 out.write_str(" options=[")?;
105 for (at, (&name, &value)) in
106 plan.name_list(options).iter().zip(plan.expr_list(settings)).enumerate()
107 {
108 if at > 0 {
109 out.write_str(", ")?;
110 }
111 write_identifier(out, plan.string(name))?;
112 out.write_char('=')?;
113 write_expr(plan, out, value)?;
114 }
115 out.write_char(']')?;
116 }
117 write!(out, " #{index} ")?;
118 write_schema(plan, out, columns)
119 }
120 Node::Fetch { index, args, columns, row, .. } => {
121 out.write_str(" args=")?;
122 write_expr_list(plan, out, args)?;
123 out.write_str(" row=")?;
124 write_expr(plan, out, row)?;
125 write!(out, " #{index} ")?;
126 write_schema(plan, out, columns)
127 }
128 Node::Filter { predicate, .. } => {
129 out.write_char(' ')?;
130 write_expr(plan, out, predicate)
131 }
132 Node::Project { index, exprs, names, .. } => {
133 write!(out, " #{index} [")?;
134 for (position, &expr) in plan.expr_list(exprs).iter().enumerate() {
135 if position > 0 {
136 out.write_str(", ")?;
137 }
138 write_expr(plan, out, expr)?;
139 out.write_str(" AS ")?;
140 write_identifier(out, plan.string(plan.name_list(names)[position]))?;
141 }
142 out.write_char(']')
143 }
144 Node::Aggregate { index, groups, aggregates, .. } => {
145 write!(out, " #{index} groups=")?;
146 write_expr_list(plan, out, groups)?;
147 out.write_str(" aggregates=")?;
148 write_expr_list(plan, out, aggregates)
149 }
150 Node::Sort { keys, .. } => write_sort_keys(plan, out, keys),
151 Node::Limit { count, offset, .. } => {
152 match count {
153 Some(count) => write!(out, " {count}")?,
154 None => out.write_str(" ALL")?,
155 }
156 write!(out, " offset {offset}")
157 }
158 Node::TopN { keys, count, offset, .. } => {
159 write!(out, " {count} offset {offset}")?;
160 write_sort_keys(plan, out, keys)
161 }
162 Node::Distinct { on, .. } => {
163 out.write_str(" on=")?;
164 write_expr_list(plan, out, on)
165 }
166 Node::Join { kind, conditions, .. } => {
167 write!(out, " {} on=", kind.keyword())?;
168 write_expr_list(plan, out, conditions)
169 }
170 Node::SetOp { kind, all, index, .. } => {
171 let quantifier = if all { "ALL" } else { "DISTINCT" };
172 write!(out, " {} {quantifier} #{index}", kind.keyword())
173 }
174 }
175}
176
177fn write_schema<W: Write>(plan: &Plan, out: &mut W, columns: Slice) -> fmt::Result {
179 out.write_char('[')?;
180 for (position, field) in plan.field_list(columns).iter().enumerate() {
181 if position > 0 {
182 out.write_str(", ")?;
183 }
184 write_identifier(out, &field.name)?;
185 write!(out, "::{}", field.ty)?;
186 }
187 out.write_char(']')
188}
189
190fn write_sort_keys<W: Write>(plan: &Plan, out: &mut W, keys: Slice) -> fmt::Result {
192 out.write_str(" [")?;
193 for (position, key) in plan.sort_key_list(keys).iter().enumerate() {
194 if position > 0 {
195 out.write_str(", ")?;
196 }
197 write_expr(plan, out, key.expr)?;
198 out.write_str(if key.descending { " DESC" } else { " ASC" })?;
199 out.write_str(if key.nulls_first { " NULLS FIRST" } else { " NULLS LAST" })?;
200 }
201 out.write_char(']')
202}
203
204fn write_expr_list<W: Write>(plan: &Plan, out: &mut W, list: Slice) -> fmt::Result {
205 out.write_char('[')?;
206 for (position, &expr) in plan.expr_list(list).iter().enumerate() {
207 if position > 0 {
208 out.write_str(", ")?;
209 }
210 write_expr(plan, out, expr)?;
211 }
212 out.write_char(']')
213}
214
215fn write_expr<W: Write>(plan: &Plan, out: &mut W, expr: ExprRef) -> fmt::Result {
216 write_form(plan, out, expr)?;
217 write!(out, "::{}", plan.expr_type(expr))
218}
219
220fn write_form<W: Write>(plan: &Plan, out: &mut W, expr: ExprRef) -> fmt::Result {
221 match *plan.expr(expr) {
222 Expr::Column(binding) => write!(out, "#{}.{}", binding.table, binding.column),
223 Expr::Constant(value) => write_value(out, plan.value(value)),
224 Expr::Cast { input, try_cast } => {
225 out.write_str(if try_cast { "TRY_CAST(" } else { "CAST(" })?;
226 write_expr(plan, out, input)?;
227 out.write_char(')')
228 }
229 Expr::Compare { op, left, right } => {
230 out.write_char('(')?;
231 write_expr(plan, out, left)?;
232 write!(out, " {} ", op.symbol())?;
233 write_expr(plan, out, right)?;
234 out.write_char(')')
235 }
236 Expr::Conjunction { op, children } => {
237 out.write_char('(')?;
238 for (position, &child) in plan.expr_list(children).iter().enumerate() {
239 if position > 0 {
240 write!(out, " {} ", op.keyword())?;
241 }
242 write_expr(plan, out, child)?;
243 }
244 out.write_char(')')
245 }
246 Expr::Function { name, args } => {
247 write_function_name(out, plan.string(name))?;
248 out.write_char('(')?;
249 write_arguments_of(plan, out, args)?;
250 out.write_char(')')
251 }
252 Expr::Aggregate { name, args, distinct, filter } => {
253 write_function_name(out, plan.string(name))?;
254 out.write_char('(')?;
255 if distinct {
256 out.write_str("DISTINCT ")?;
257 }
258 write_arguments_of(plan, out, args)?;
259 if let Some(filter) = filter {
260 if !plan.expr_list(args).is_empty() {
263 out.write_char(' ')?;
264 }
265 out.write_str("FILTER ")?;
266 write_expr(plan, out, filter)?;
267 }
268 out.write_char(')')
269 }
270 Expr::Case { arms, otherwise } => {
271 out.write_str("CASE")?;
272 for arm in plan.arm_list(arms) {
273 out.write_str(" WHEN ")?;
274 write_expr(plan, out, arm.when)?;
275 out.write_str(" THEN ")?;
276 write_expr(plan, out, arm.then)?;
277 }
278 if let Some(otherwise) = otherwise {
279 out.write_str(" ELSE ")?;
280 write_expr(plan, out, otherwise)?;
281 }
282 out.write_str(" END")
283 }
284 }
285}
286
287fn write_arguments_of<W: Write>(plan: &Plan, out: &mut W, args: Slice) -> fmt::Result {
288 for (position, &arg) in plan.expr_list(args).iter().enumerate() {
289 if position > 0 {
290 out.write_str(", ")?;
291 }
292 write_expr(plan, out, arg)?;
293 }
294 Ok(())
295}
296
297fn write_value<W: Write>(out: &mut W, value: &Value) -> fmt::Result {
306 match value {
307 Value::Null => out.write_str("NULL"),
308 Value::Boolean(held) => out.write_str(if *held { "TRUE" } else { "FALSE" }),
309 Value::TinyInt(held) => write!(out, "{held}"),
310 Value::SmallInt(held) => write!(out, "{held}"),
311 Value::Integer(held) => write!(out, "{held}"),
312 Value::BigInt(held) => write!(out, "{held}"),
313 Value::HugeInt(held) => write!(out, "{held}"),
314 Value::UTinyInt(held) => write!(out, "{held}"),
315 Value::USmallInt(held) => write!(out, "{held}"),
316 Value::UInteger(held) => write!(out, "{held}"),
317 Value::UBigInt(held) => write!(out, "{held}"),
318 Value::UHugeInt(held) => write!(out, "{held}"),
319 Value::Float(held) => write!(out, "{held:?}"),
323 Value::Double(held) => write!(out, "{held:?}"),
324 Value::Decimal { unscaled, scale, .. } => out.write_str(&decimal_text(*unscaled, *scale)),
325 Value::Varchar(held) => write_string(out, held),
326 Value::Blob(held) => {
327 out.write_str("X'")?;
328 for byte in held {
329 write!(out, "{byte:02x}")?;
330 }
331 out.write_char('\'')
332 }
333 Value::Date(held) => write!(out, "{held}"),
334 Value::Time(held) | Value::Timestamp(held) => write!(out, "{held}"),
335 Value::Interval { months, days, micros } => write!(out, "{{{months}, {days}, {micros}}}"),
336 Value::List { values, .. } => {
337 out.write_char('{')?;
338 for (position, element) in values.iter().enumerate() {
339 if position > 0 {
340 out.write_str(", ")?;
341 }
342 write_value(out, element)?;
343 }
344 out.write_char('}')
345 }
346 Value::Struct(fields) => {
349 out.write_char('{')?;
350 for (position, (_, held)) in fields.iter().enumerate() {
351 if position > 0 {
352 out.write_str(", ")?;
353 }
354 write_value(out, held)?;
355 }
356 out.write_char('}')
357 }
358 other => write!(out, "<no textual form for {other:?}>"),
363 }
364}
365
366pub(crate) fn decimal_text(unscaled: i128, scale: u8) -> String {
372 if scale == 0 {
373 return unscaled.to_string();
374 }
375 let scale = usize::from(scale);
376 let digits = unscaled.unsigned_abs().to_string();
377 let padded = if digits.len() <= scale {
380 format!("{}{digits}", "0".repeat(scale + 1 - digits.len()))
381 } else {
382 digits
383 };
384 let point = padded.len() - scale;
385 let sign = if unscaled < 0 { "-" } else { "" };
386 format!("{sign}{}.{}", &padded[..point], &padded[point..])
387}
388
389fn write_string<W: Write>(out: &mut W, text: &str) -> fmt::Result {
395 out.write_char('\'')?;
396 for character in text.chars() {
397 match character {
398 '\'' => out.write_str("''")?,
399 '\\' => out.write_str("\\\\")?,
400 control if control.is_control() => write!(out, "\\x{:02x}", control as u32)?,
401 other => out.write_char(other)?,
402 }
403 }
404 out.write_char('\'')
405}
406
407pub(crate) fn is_plain_identifier(name: &str) -> bool {
409 !name.is_empty()
410 && name.chars().next().is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
411 && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
412}
413
414fn write_identifier<W: Write>(out: &mut W, name: &str) -> fmt::Result {
416 if is_plain_identifier(name) {
417 return out.write_str(name);
418 }
419 out.write_char('"')?;
420 for character in name.chars() {
421 if character == '"' {
422 out.write_str("\"\"")?;
423 } else {
424 out.write_char(character)?;
425 }
426 }
427 out.write_char('"')
428}
429
430fn write_function_name<W: Write>(out: &mut W, name: &str) -> fmt::Result {
432 if RESERVED.iter().any(|reserved| name.eq_ignore_ascii_case(reserved)) {
433 return write!(out, "\"{name}\"");
434 }
435 write_identifier(out, name)
436}
437
438#[cfg(test)]
445pub(crate) fn prints_readably(ty: &LogicalType) -> bool {
446 let text = ty.to_string();
447 crate::parse::type_extent(&text, 0) == text.len()
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453
454 #[test]
455 fn a_decimal_gets_its_point_from_its_scale() {
456 assert_eq!(decimal_text(1234, 2), "12.34");
457 assert_eq!(decimal_text(1234, 0), "1234");
458 assert_eq!(decimal_text(5, 3), "0.005");
459 assert_eq!(decimal_text(-5, 3), "-0.005");
460 assert_eq!(decimal_text(-1234, 2), "-12.34");
461 assert_eq!(decimal_text(0, 2), "0.00");
462 }
463
464 #[test]
465 fn the_widest_decimal_still_prints() {
466 let widest = 10i128.pow(37) - 1;
467 assert_eq!(decimal_text(widest, 0).len(), 37);
468 assert_eq!(decimal_text(widest, 37).len(), 39);
469 }
470
471 #[test]
472 fn a_name_that_needs_quoting_gets_it() {
473 let quoted = |name: &str| {
474 let mut out = String::new();
475 write_identifier(&mut out, name).unwrap();
476 out
477 };
478 assert_eq!(quoted("SearchPhrase"), "SearchPhrase");
479 assert_eq!(quoted("_hidden9"), "_hidden9");
480 assert_eq!(quoted("a b"), "\"a b\"");
481 assert_eq!(quoted("9lives"), "\"9lives\"", "a name cannot start with a digit");
482 assert_eq!(quoted(""), "\"\"");
483 assert_eq!(quoted("say \"hi\""), "\"say \"\"hi\"\"\"");
484 }
485
486 #[test]
487 fn a_function_named_after_a_reserved_word_is_quoted() {
488 for name in ["cast", "CAST", "Try_Cast", "case"] {
489 let mut out = String::new();
490 write_function_name(&mut out, name).unwrap();
491 assert!(out.starts_with('"'), "{name} would be read back as syntax");
492 }
493 let mut out = String::new();
494 write_function_name(&mut out, "casting").unwrap();
495 assert_eq!(out, "casting", "only the reserved words themselves are reserved");
496 }
497
498 #[test]
499 fn a_string_never_contains_a_newline_when_it_is_written() {
500 let mut out = String::new();
501 write_string(&mut out, "one\ntwo\ttab'quote\\slash").unwrap();
502 assert!(!out.contains('\n'), "a value would split an operator across two lines");
503 assert_eq!(out, "'one\\x0atwo\\x09tab''quote\\\\slash'");
504 }
505
506 #[test]
510 fn a_float_prints_the_text_that_reads_back_as_the_same_bits() {
511 for held in [0.1f32, f32::MIN, f32::MAX, f32::EPSILON, -0.0, 1e-40] {
512 let mut out = String::new();
513 write_value(&mut out, &Value::Float(held)).unwrap();
514 let back: f32 = out.parse().expect("a float we printed parses");
515 assert_eq!(back.to_bits(), held.to_bits(), "{out} is not the same float");
516 }
517 for held in [0.1f64, f64::MIN, f64::MAX, f64::EPSILON, -0.0, 1e-308] {
518 let mut out = String::new();
519 write_value(&mut out, &Value::Double(held)).unwrap();
520 let back: f64 = out.parse().expect("a double we printed parses");
521 assert_eq!(back.to_bits(), held.to_bits(), "{out} is not the same double");
522 }
523 }
524
525 #[test]
526 fn a_plan_that_is_only_a_dummy_prints_one_line() {
527 assert_eq!(Plan::new().to_string(), "Dummy\n");
528 }
529
530 #[test]
536 fn every_type_prints_as_something_the_reader_can_find_the_end_of() {
537 let scalars = [
538 LogicalType::Null,
539 LogicalType::Boolean,
540 LogicalType::TinyInt,
541 LogicalType::SmallInt,
542 LogicalType::Integer,
543 LogicalType::BigInt,
544 LogicalType::HugeInt,
545 LogicalType::UTinyInt,
546 LogicalType::USmallInt,
547 LogicalType::UInteger,
548 LogicalType::UBigInt,
549 LogicalType::UHugeInt,
550 LogicalType::Float,
551 LogicalType::Double,
552 LogicalType::Varchar,
553 LogicalType::Blob,
554 LogicalType::Bit,
555 LogicalType::Uuid,
556 LogicalType::Date,
557 LogicalType::Time,
558 LogicalType::TimeTz,
559 LogicalType::Timestamp,
560 LogicalType::TimestampS,
561 LogicalType::TimestampMs,
562 LogicalType::TimestampNs,
563 LogicalType::TimestampTz,
564 LogicalType::Interval,
565 ];
566 let mut all: Vec<LogicalType> = scalars.to_vec();
567 all.push(LogicalType::decimal(18, 3).expect("18 and 3 is a decimal"));
568 all.push(LogicalType::decimal(38, 0).expect("the widest decimal"));
569 for scalar in &scalars {
570 all.push(LogicalType::list(scalar.clone()));
571 all.push(LogicalType::array(scalar.clone(), 4));
572 all.push(LogicalType::map(LogicalType::Varchar, scalar.clone()));
573 all.push(LogicalType::Struct(vec![
574 rudb_common::Field::new("a", scalar.clone()),
575 rudb_common::Field::new("b b", LogicalType::Varchar),
576 ]));
577 all.push(LogicalType::Union(vec![rudb_common::Field::new("u", scalar.clone())]));
578 }
579 all.push(LogicalType::list(LogicalType::list(LogicalType::Integer)));
580 all.push(LogicalType::list(LogicalType::map(
581 LogicalType::Varchar,
582 LogicalType::TimestampTz,
583 )));
584
585 for ty in all {
586 let text = ty.to_string();
587 assert!(prints_readably(&ty), "the reader cannot find the end of {text}");
588 let back = LogicalType::parse(&text)
589 .unwrap_or_else(|error| panic!("{text} does not parse: {error}"));
590 assert_eq!(back, ty, "{text} does not read back as itself");
591 }
592 }
593}