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