1use std::fmt;
2use std::ops::Deref;
3
4use ptype::parameter::Parameter;
5use substrait::proto;
6use substrait::proto::r#type::{self as ptype};
7
8use super::foundation::{NONSPECIFIC, Scope};
9use super::{PlanError, Textify};
10use crate::extensions::simple::ExtensionKind;
11use crate::textify::foundation::{ErrorToken, MaybeToken, Visibility};
12
13const NULLABILITY_UNSPECIFIED: &str = "⁉";
14
15impl Textify for ptype::Nullability {
16 fn name() -> &'static str {
17 "Nullability"
18 }
19
20 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
21 match self {
22 ptype::Nullability::Unspecified => {
23 ctx.push_error(
24 PlanError::invalid("Nullability", NONSPECIFIC, "Nullability left Unspecified")
25 .into(),
26 );
27
28 w.write_str(NULLABILITY_UNSPECIFIED)?;
30 }
31 ptype::Nullability::Nullable => write!(w, "?")?,
32 ptype::Nullability::Required => {}
33 };
34 Ok(())
35 }
36}
37
38pub fn is_identifer(s: &str) -> bool {
45 let mut chars = s.chars();
46 let first = match chars.next() {
47 Some(c) => c,
48 None => return false,
49 };
50
51 if !first.is_ascii_alphabetic() {
52 return false;
53 }
54
55 for c in chars {
56 if !c.is_ascii_alphanumeric() && c != '_' {
57 return false;
58 }
59 }
60
61 true
62}
63
64pub fn escaped(s: &str) -> impl fmt::Display + fmt::Debug {
66 s.escape_debug()
67}
68
69#[derive(Debug, Copy, Clone)]
72pub struct Name<'a>(pub &'a str);
73
74impl<'a> fmt::Display for Name<'a> {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 if is_identifer(self.0) {
77 write!(f, "{}", self.0)
78 } else {
79 write!(f, "\"{}\"", escaped(self.0))
80 }
81 }
82}
83
84impl<'a> Textify for Name<'a> {
85 fn name() -> &'static str {
86 "Name"
87 }
88
89 fn textify<S: Scope, W: fmt::Write>(&self, _ctx: &S, w: &mut W) -> fmt::Result {
90 write!(w, "{self}")
91 }
92}
93
94#[derive(Debug, Copy, Clone)]
95pub struct Anchor {
96 reference: u32,
97 required: bool,
98}
99
100impl Anchor {
101 pub fn new(reference: u32, required: bool) -> Self {
102 Self {
103 reference,
104 required,
105 }
106 }
107}
108
109impl Textify for Anchor {
110 fn name() -> &'static str {
111 "Anchor"
112 }
113
114 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
115 match ctx.options().show_simple_extension_anchors {
116 Visibility::Never => return Ok(()),
117 Visibility::Required if !self.required => {
118 return Ok(());
119 }
120 Visibility::Required => {}
121 Visibility::Always => {}
122 }
123 write!(w, "#{}", self.reference)
124 }
125}
126
127#[derive(Debug, Copy, Clone)]
128pub struct NamedAnchor<'a> {
129 pub name: MaybeToken<&'a str>,
130 pub anchor: u32,
131 pub unique: bool,
133}
134
135impl<'a> NamedAnchor<'a> {
136 pub fn lookup<S: Scope>(ctx: &'a S, kind: ExtensionKind, anchor: u32) -> Self {
138 let ext = ctx.extensions().find_by_anchor(kind, anchor);
139 let found = ext.map_err(|e| ctx.push_error(e.into())).ok();
140
141 let (name, unique) = match found {
142 Some((_, n)) => match ctx.extensions().is_name_unique(kind, anchor, n) {
143 Ok(unique) => (MaybeToken(Ok(n)), unique),
145 Err(e) => {
146 ctx.push_error(e.into());
147 (MaybeToken(Err(ErrorToken(kind.name()))), false)
148 }
149 },
150 None => (MaybeToken(Err(ErrorToken(kind.name()))), false),
151 };
152 Self {
153 name,
154 anchor,
155 unique,
156 }
157 }
158}
159
160impl<'a> Textify for NamedAnchor<'a> {
161 fn name() -> &'static str {
162 "NamedAnchor"
163 }
164
165 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
166 let anchor = Anchor::new(self.anchor, !self.unique);
167 write!(
168 w,
169 "{name}{anchor}",
170 name = self.name,
171 anchor = ctx.display(&anchor)
172 )
173 }
174}
175
176#[derive(Debug, Copy, Clone)]
181pub struct OutputType<T: Deref<Target = proto::Type>>(pub Option<T>);
182
183impl<T: Deref<Target = proto::Type>> Textify for OutputType<T> {
184 fn name() -> &'static str {
185 "OutputType"
186 }
187
188 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
189 match self.0 {
190 Some(ref t) => write!(w, ":{}", ctx.display(t.deref())),
191 None => Ok(()),
192 }
193 }
194}
195
196struct TypeVariation(u32);
197
198impl Textify for TypeVariation {
199 fn name() -> &'static str {
200 "TypeVariation"
201 }
202
203 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
204 let &TypeVariation(anchor) = self;
205 if anchor == 0 {
206 return Ok(());
208 }
209 let name_and_anchor = NamedAnchor::lookup(ctx, ExtensionKind::TypeVariation, anchor);
210
211 write!(
212 w,
213 "[{name_and_anchor}]",
214 name_and_anchor = ctx.display(&name_and_anchor)
215 )
216 }
217}
218
219fn textify_type<S: Scope, W: fmt::Write>(
224 ctx: &S,
225 f: &mut W,
226 name: impl AsRef<str>,
227 nullability: ptype::Nullability,
228 variant: u32,
229 params: Parameters,
230) -> fmt::Result {
231 write!(
232 f,
233 "{name}{null}{var}{params}",
234 name = name.as_ref(),
235 null = ctx.display(&nullability),
236 var = ctx.display(&TypeVariation(variant)),
237 params = ctx.display(¶ms)
238 )
239}
240
241macro_rules! textify_kind {
242 ($ctx:expr, $f:expr, $kind:ident, $name:expr) => {
243 textify_type(
244 $ctx,
245 $f,
246 $name,
247 $kind.nullability(),
248 $kind.type_variation_reference,
249 Parameters(&[]),
250 )
251 };
252}
253
254impl Textify for Parameter {
255 fn name() -> &'static str {
256 "Parameter"
257 }
258
259 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
260 match self {
261 Parameter::Boolean(true) => write!(w, "true")?,
262 Parameter::Boolean(false) => write!(w, "false")?,
263 Parameter::DataType(t) => write!(w, "{}", ctx.display(t))?,
264 Parameter::Enum(e) => write!(w, "{e}")?,
265 Parameter::Integer(i) => write!(w, "{i}")?,
266 Parameter::String(s) => write!(w, "{s}")?,
268 Parameter::Null(_) => write!(w, "null")?,
269 };
270
271 Ok(())
272 }
273}
274impl Textify for ptype::Parameter {
275 fn name() -> &'static str {
276 "Parameter"
277 }
278
279 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
280 write!(w, "{}", ctx.expect(self.parameter.as_ref()))
281 }
282}
283
284struct Parameters<'a>(&'a [Option<Parameter>]);
285
286impl<'a> Textify for Parameters<'a> {
287 fn name() -> &'static str {
288 "Parameters"
289 }
290
291 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
292 let mut first = true;
293 for param in self.0.iter() {
294 if first {
295 write!(w, "<")?;
296 } else {
297 write!(w, ", ")?;
298 }
299 write!(w, "{}", ctx.expect(param.as_ref()))?;
300 first = false;
301 }
302 if !first {
303 write!(w, ">")?;
304 }
305
306 Ok(())
307 }
308}
309
310impl Textify for ptype::UserDefined {
311 fn name() -> &'static str {
312 "UserDefined"
313 }
314
315 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
316 {
317 let name_and_anchor =
318 NamedAnchor::lookup(ctx, ExtensionKind::Type, self.type_reference);
319
320 let param_vec: Vec<Option<Parameter>> = self
321 .type_parameters
322 .iter()
323 .map(|t| t.parameter.clone())
324 .collect();
325 let params = Parameters(¶m_vec);
326
327 write!(
328 w,
329 "{name_and_anchor}{null}{var}{params}",
330 name_and_anchor = ctx.display(&name_and_anchor),
331 null = ctx.display(&self.nullability()),
332 var = ctx.display(&TypeVariation(self.type_variation_reference)),
333 params = ctx.display(¶ms)
334 )
335 }
336 }
337}
338
339impl Textify for ptype::Kind {
340 fn name() -> &'static str {
341 "Kind"
342 }
343
344 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
345 match self {
346 ptype::Kind::Bool(k) => textify_type(
350 ctx,
351 w,
352 "boolean",
353 k.nullability(),
354 k.type_variation_reference,
355 Parameters(&[]),
356 ),
357 ptype::Kind::I8(k) => textify_kind!(ctx, w, k, "i8"),
358 ptype::Kind::I16(k) => textify_kind!(ctx, w, k, "i16"),
359 ptype::Kind::I32(k) => textify_kind!(ctx, w, k, "i32"),
360 ptype::Kind::I64(k) => textify_kind!(ctx, w, k, "i64"),
361 ptype::Kind::Fp32(k) => textify_kind!(ctx, w, k, "fp32"),
362 ptype::Kind::Fp64(k) => textify_kind!(ctx, w, k, "fp64"),
363 ptype::Kind::String(k) => textify_kind!(ctx, w, k, "string"),
364 ptype::Kind::Binary(k) => textify_kind!(ctx, w, k, "binary"),
365 ptype::Kind::Timestamp(k) => textify_kind!(ctx, w, k, "timestamp"),
366 ptype::Kind::Date(k) => textify_kind!(ctx, w, k, "date"),
367 ptype::Kind::Time(k) => textify_kind!(ctx, w, k, "time"),
368 ptype::Kind::IntervalYear(i) => {
369 textify_kind!(ctx, w, i, "interval_year")
370 }
371
372 ptype::Kind::TimestampTz(ts) => {
373 textify_kind!(ctx, w, ts, "timestamp_tz")
374 }
375 ptype::Kind::Uuid(uuid) => textify_kind!(ctx, w, uuid, "uuid"),
376
377 ptype::Kind::IntervalDay(i) => textify_type(
378 ctx,
379 w,
380 "interval_day",
381 i.nullability(),
382 i.type_variation_reference,
383 Parameters(&[Some(Parameter::Integer(i.precision.unwrap_or(6) as i64))]),
385 ),
386 ptype::Kind::IntervalCompound(i) => textify_type(
387 ctx,
388 w,
389 "interval_compound",
390 i.nullability(),
391 i.type_variation_reference,
392 Parameters(&[Some(Parameter::Integer(i.precision as i64))]),
393 ),
394 ptype::Kind::FixedChar(c) => textify_type(
395 ctx,
396 w,
397 "fixedchar",
398 c.nullability(),
399 c.type_variation_reference,
400 Parameters(&[Some(Parameter::Integer(c.length as i64))]),
401 ),
402 ptype::Kind::Varchar(_c) => todo!(),
403 ptype::Kind::FixedBinary(_b) => todo!(),
404 ptype::Kind::Decimal(_d) => todo!(),
405 ptype::Kind::PrecisionTime(p) => textify_type(
406 ctx,
407 w,
408 "precisiontime",
409 p.nullability(),
410 p.type_variation_reference,
411 Parameters(&[Some(Parameter::Integer(p.precision as i64))]),
412 ),
413 ptype::Kind::PrecisionTimestamp(p) => textify_type(
414 ctx,
415 w,
416 "precisiontimestamp",
417 p.nullability(),
418 p.type_variation_reference,
419 Parameters(&[Some(Parameter::Integer(p.precision as i64))]),
420 ),
421 ptype::Kind::PrecisionTimestampTz(_p) => todo!(),
422 ptype::Kind::Struct(s) => textify_type(
423 ctx,
424 w,
425 "struct",
426 s.nullability(),
427 s.type_variation_reference,
428 Parameters(
429 &s.types
430 .iter()
431 .map(|t| Some(Parameter::DataType(t.clone())))
432 .collect::<Vec<_>>(),
433 ),
434 ),
435 ptype::Kind::List(l) => {
436 let p = l
437 .r#type
438 .as_ref()
439 .map(|t| Parameter::DataType((**t).to_owned()));
440 textify_type(
441 ctx,
442 w,
443 "list",
444 l.nullability(),
445 l.type_variation_reference,
446 Parameters(&[p]),
447 )
448 }
449 ptype::Kind::Map(m) => {
450 let k = m
451 .key
452 .as_ref()
453 .map(|t| Parameter::DataType((**t).to_owned()));
454 let v = m
455 .value
456 .as_ref()
457 .map(|t| Parameter::DataType((**t).to_owned()));
458 textify_type(
459 ctx,
460 w,
461 "map",
462 m.nullability(),
463 m.type_variation_reference,
464 Parameters(&[k, v]),
465 )
466 }
467 ptype::Kind::UserDefined(u) => u.textify(ctx, w),
468 ptype::Kind::UserDefinedTypeReference(r) => {
469 let udf = ptype::UserDefined {
472 type_reference: *r,
473 type_variation_reference: 0,
474 nullability: ptype::Nullability::Required as i32,
475 type_parameters: vec![],
476 };
477 ptype::Kind::UserDefined(udf).textify(ctx, w)
478 }
479 }
480 }
481}
482
483impl Textify for proto::Type {
484 fn name() -> &'static str {
485 "Type"
486 }
487
488 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
489 write!(w, "{}", ctx.expect(self.kind.as_ref()))
490 }
491}
492
493#[cfg(test)]
589mod tests {
590
591 use super::*;
592 use crate::extensions::simple::{ExtensionKind, MissingReference};
593 use crate::fixtures::TestContext;
594 use crate::textify::foundation::FormatError;
595
596 #[test]
597 fn type_display() {
598 let ctx = TestContext::new()
599 .with_uri(1, "first")
600 .with_type_variation(1, 2, "u8");
601
602 let t = proto::Type {
603 kind: Some(ptype::Kind::Bool(ptype::Boolean {
604 type_variation_reference: 2,
605 nullability: ptype::Nullability::Nullable as i32,
606 })),
607 };
608
609 let s = ctx.textify_no_errors(&t);
610 assert_eq!(s, "boolean?[u8]");
611
612 let t = proto::Type {
613 kind: Some(ptype::Kind::I8(ptype::I8 {
614 type_variation_reference: 0,
615 nullability: ptype::Nullability::Required as i32,
616 })),
617 };
618 assert_eq!(ctx.textify_no_errors(&t), "i8");
619
620 let t = proto::Type {
621 kind: Some(ptype::Kind::PrecisionTimestamp(ptype::PrecisionTimestamp {
622 type_variation_reference: 0,
623 nullability: ptype::Nullability::Nullable as i32,
624 precision: 3,
625 })),
626 };
627 assert_eq!(ctx.textify_no_errors(&t), "precisiontimestamp?<3>");
628
629 let mut ctx = ctx.with_type_variation(1, 8, "int");
630 ctx.options.show_simple_extension_anchors = Visibility::Always;
631
632 let t = proto::Type {
633 kind: Some(ptype::Kind::PrecisionTime(ptype::PrecisionTime {
634 type_variation_reference: 8,
635 nullability: ptype::Nullability::Nullable as i32,
636 precision: 9,
637 })),
638 };
639 assert_eq!(ctx.textify_no_errors(&t), "precisiontime?[int#8]<9>");
640 }
641
642 #[test]
643 fn type_display_with_errors() {
644 let ctx = TestContext::new()
645 .with_uri(1, "first")
646 .with_type(1, 100, "cow");
647
648 let t = proto::Type {
649 kind: Some(ptype::Kind::Bool(ptype::Boolean {
650 type_variation_reference: 200,
651 nullability: ptype::Nullability::Nullable as i32,
652 })),
653 };
654 let (s, errs) = ctx.textify(&t);
655 assert_eq!(s, "boolean?[!{type_variation}#200]");
656 let err = errs.first();
657 let (&k, &a) = match err {
658 FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => (k, a),
659 _ => panic!("Expected Lookup MissingAnchor: {err}"),
660 };
661
662 assert_eq!(k, ExtensionKind::TypeVariation);
663 assert_eq!(a, 200);
664
665 let t = proto::Type {
666 kind: Some(ptype::Kind::UserDefined(ptype::UserDefined {
667 type_variation_reference: 0,
668 nullability: ptype::Nullability::Required as i32,
669 type_reference: 100,
670 type_parameters: vec![],
671 })),
672 };
673
674 let (s, errs) = ctx.textify(&t);
675 assert!(errs.is_empty());
676 assert_eq!(s, "cow");
677
678 let t = proto::Type {
679 kind: Some(ptype::Kind::UserDefined(ptype::UserDefined {
680 type_variation_reference: 0,
681 nullability: ptype::Nullability::Required as i32,
682 type_reference: 12589,
683 type_parameters: vec![],
684 })),
685 };
686
687 let (s, errs) = ctx.textify(&t);
688 let err = errs.first();
689 let (&k, &a) = match err {
690 FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => (k, a),
691 _ => panic!("Expected Lookup MissingAnchor: {err}"),
692 };
693 assert_eq!(k, ExtensionKind::Type);
694 assert_eq!(a, 12589);
695 assert_eq!(s, "!{type}#12589");
696 }
697
698 #[test]
699 fn struct_display() {
700 let ctx = TestContext::new();
701
702 let t = proto::Type {
703 kind: Some(ptype::Kind::Struct(ptype::Struct {
704 type_variation_reference: 0,
705 nullability: ptype::Nullability::Nullable as i32,
706 types: vec![
707 proto::Type {
708 kind: Some(ptype::Kind::String(ptype::String {
709 type_variation_reference: 0,
710 nullability: ptype::Nullability::Required as i32,
711 })),
712 },
713 proto::Type {
714 kind: Some(ptype::Kind::I8(ptype::I8 {
715 type_variation_reference: 0,
716 nullability: ptype::Nullability::Required as i32,
717 })),
718 },
719 proto::Type {
720 kind: Some(ptype::Kind::I32(ptype::I32 {
721 type_variation_reference: 0,
722 nullability: ptype::Nullability::Nullable as i32,
723 })),
724 },
725 proto::Type {
726 kind: Some(ptype::Kind::TimestampTz(ptype::TimestampTz {
727 type_variation_reference: 0,
728 nullability: ptype::Nullability::Required as i32,
729 })),
730 },
731 ],
732 })),
733 };
734 assert_eq!(
735 ctx.textify_no_errors(&t),
736 "struct?<string, i8, i32?, timestamp_tz>"
737 );
738 }
739
740 #[test]
741 fn names_display() {
742 let ctx = TestContext::new();
743
744 let n = Name("name");
745 assert_eq!(ctx.textify_no_errors(&n), "name");
746
747 let n = Name("name with spaces");
748 assert_eq!(ctx.textify_no_errors(&n), "\"name with spaces\"");
749 }
750
751 }