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::{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 (name, unique) = match ext {
140 Ok((_, n)) => match ctx.extensions().is_name_unique(kind, anchor, n) {
141 Ok(unique) => (MaybeToken(Ok(n)), unique),
143 Err(e) => (MaybeToken(Err(ctx.failure(e))), false),
144 },
145 Err(e) => (MaybeToken(Err(ctx.failure(e))), false),
146 };
147 Self {
148 name,
149 anchor,
150 unique,
151 }
152 }
153}
154
155impl<'a> Textify for NamedAnchor<'a> {
156 fn name() -> &'static str {
157 "NamedAnchor"
158 }
159
160 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
161 let anchor = Anchor::new(self.anchor, !self.unique);
162 write!(
163 w,
164 "{name}{anchor}",
165 name = self.name,
166 anchor = ctx.display(&anchor)
167 )
168 }
169}
170
171#[derive(Debug, Copy, Clone)]
176pub struct OutputType<T: Deref<Target = proto::Type>>(pub Option<T>);
177
178impl<T: Deref<Target = proto::Type>> Textify for OutputType<T> {
179 fn name() -> &'static str {
180 "OutputType"
181 }
182
183 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
184 match self.0 {
185 Some(ref t) => write!(w, ":{}", ctx.display(t.deref())),
186 None => Ok(()),
187 }
188 }
189}
190
191struct TypeVariation(u32);
192
193impl Textify for TypeVariation {
194 fn name() -> &'static str {
195 "TypeVariation"
196 }
197
198 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
199 let &TypeVariation(anchor) = self;
200 if anchor == 0 {
201 return Ok(());
203 }
204 let name_and_anchor = NamedAnchor::lookup(ctx, ExtensionKind::TypeVariation, anchor);
205
206 write!(
207 w,
208 "[{name_and_anchor}]",
209 name_and_anchor = ctx.display(&name_and_anchor)
210 )
211 }
212}
213
214fn textify_type<S: Scope, W: fmt::Write>(
219 ctx: &S,
220 f: &mut W,
221 name: impl AsRef<str>,
222 nullability: ptype::Nullability,
223 variant: u32,
224 params: Parameters,
225) -> fmt::Result {
226 write!(
227 f,
228 "{name}{null}{var}{params}",
229 name = name.as_ref(),
230 null = ctx.display(&nullability),
231 var = ctx.display(&TypeVariation(variant)),
232 params = ctx.display(¶ms)
233 )
234}
235
236macro_rules! textify_kind {
237 ($ctx:expr, $f:expr, $kind:ident, $name:expr) => {
238 textify_type(
239 $ctx,
240 $f,
241 $name,
242 $kind.nullability(),
243 $kind.type_variation_reference,
244 Parameters(&[]),
245 )
246 };
247}
248
249impl Textify for Parameter {
250 fn name() -> &'static str {
251 "Parameter"
252 }
253
254 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
255 match self {
256 Parameter::Boolean(true) => write!(w, "true")?,
257 Parameter::Boolean(false) => write!(w, "false")?,
258 Parameter::DataType(t) => write!(w, "{}", ctx.display(t))?,
259 Parameter::Enum(e) => write!(w, "{e}")?,
260 Parameter::Integer(i) => write!(w, "{i}")?,
261 Parameter::String(s) => write!(w, "{s}")?,
263 Parameter::Null(_) => write!(w, "null")?,
264 };
265
266 Ok(())
267 }
268}
269impl Textify for ptype::Parameter {
270 fn name() -> &'static str {
271 "Parameter"
272 }
273
274 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
275 write!(w, "{}", ctx.expect(self.parameter.as_ref()))
276 }
277}
278
279struct Parameters<'a>(&'a [Option<Parameter>]);
280
281impl<'a> Textify for Parameters<'a> {
282 fn name() -> &'static str {
283 "Parameters"
284 }
285
286 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
287 let mut first = true;
288 for param in self.0.iter() {
289 if first {
290 write!(w, "<")?;
291 } else {
292 write!(w, ", ")?;
293 }
294 write!(w, "{}", ctx.expect(param.as_ref()))?;
295 first = false;
296 }
297 if !first {
298 write!(w, ">")?;
299 }
300
301 Ok(())
302 }
303}
304
305impl Textify for ptype::UserDefined {
306 fn name() -> &'static str {
307 "UserDefined"
308 }
309
310 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
311 {
312 let name_and_anchor =
313 NamedAnchor::lookup(ctx, ExtensionKind::Type, self.type_reference);
314
315 let param_vec: Vec<Option<Parameter>> = self
316 .type_parameters
317 .iter()
318 .map(|t| t.parameter.clone())
319 .collect();
320 let params = Parameters(¶m_vec);
321
322 write!(
323 w,
324 "{name_and_anchor}{null}{var}{params}",
325 name_and_anchor = ctx.display(&name_and_anchor),
326 null = ctx.display(&self.nullability()),
327 var = ctx.display(&TypeVariation(self.type_variation_reference)),
328 params = ctx.display(¶ms)
329 )
330 }
331 }
332}
333
334impl Textify for ptype::Kind {
335 fn name() -> &'static str {
336 "Kind"
337 }
338
339 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
340 match self {
341 ptype::Kind::Bool(k) => textify_type(
345 ctx,
346 w,
347 "boolean",
348 k.nullability(),
349 k.type_variation_reference,
350 Parameters(&[]),
351 ),
352 ptype::Kind::I8(k) => textify_kind!(ctx, w, k, "i8"),
353 ptype::Kind::I16(k) => textify_kind!(ctx, w, k, "i16"),
354 ptype::Kind::I32(k) => textify_kind!(ctx, w, k, "i32"),
355 ptype::Kind::I64(k) => textify_kind!(ctx, w, k, "i64"),
356 ptype::Kind::Fp32(k) => textify_kind!(ctx, w, k, "fp32"),
357 ptype::Kind::Fp64(k) => textify_kind!(ctx, w, k, "fp64"),
358 ptype::Kind::String(k) => textify_kind!(ctx, w, k, "string"),
359 ptype::Kind::Binary(k) => textify_kind!(ctx, w, k, "binary"),
360 #[allow(deprecated)]
361 ptype::Kind::Timestamp(k) => textify_kind!(ctx, w, k, "timestamp"),
362 ptype::Kind::Date(k) => textify_kind!(ctx, w, k, "date"),
363 ptype::Kind::Time(k) => textify_kind!(ctx, w, k, "time"),
364 ptype::Kind::IntervalYear(i) => {
365 textify_kind!(ctx, w, i, "interval_year")
366 }
367 #[allow(deprecated)]
368 ptype::Kind::TimestampTz(ts) => {
369 textify_kind!(ctx, w, ts, "timestamp_tz")
370 }
371 ptype::Kind::Uuid(uuid) => textify_kind!(ctx, w, uuid, "uuid"),
372
373 ptype::Kind::IntervalDay(i) => textify_type(
374 ctx,
375 w,
376 "interval_day",
377 i.nullability(),
378 i.type_variation_reference,
379 Parameters(&[Some(Parameter::Integer(i.precision.unwrap_or(6) as i64))]),
381 ),
382 ptype::Kind::IntervalCompound(i) => textify_type(
383 ctx,
384 w,
385 "interval_compound",
386 i.nullability(),
387 i.type_variation_reference,
388 Parameters(&[Some(Parameter::Integer(i.precision as i64))]),
389 ),
390 ptype::Kind::FixedChar(c) => textify_type(
391 ctx,
392 w,
393 "fixedchar",
394 c.nullability(),
395 c.type_variation_reference,
396 Parameters(&[Some(Parameter::Integer(c.length as i64))]),
397 ),
398 ptype::Kind::Varchar(c) => textify_type(
399 ctx,
400 w,
401 "varchar",
402 c.nullability(),
403 c.type_variation_reference,
404 Parameters(&[Some(Parameter::Integer(c.length as i64))]),
405 ),
406 ptype::Kind::FixedBinary(b) => textify_type(
407 ctx,
408 w,
409 "fixedbinary",
410 b.nullability(),
411 b.type_variation_reference,
412 Parameters(&[Some(Parameter::Integer(b.length as i64))]),
413 ),
414 ptype::Kind::Decimal(d) => {
415 let p = Parameter::Integer(d.precision as i64);
416 let s = Parameter::Integer(d.scale as i64);
417 textify_type(
418 ctx,
419 w,
420 "decimal",
421 d.nullability(),
422 d.type_variation_reference,
423 Parameters(&[Some(p), Some(s)]),
424 )
425 }
426 ptype::Kind::PrecisionTime(p) => textify_type(
427 ctx,
428 w,
429 "precisiontime",
430 p.nullability(),
431 p.type_variation_reference,
432 Parameters(&[Some(Parameter::Integer(p.precision as i64))]),
433 ),
434 ptype::Kind::PrecisionTimestamp(p) => textify_type(
435 ctx,
436 w,
437 "precisiontimestamp",
438 p.nullability(),
439 p.type_variation_reference,
440 Parameters(&[Some(Parameter::Integer(p.precision as i64))]),
441 ),
442 ptype::Kind::PrecisionTimestampTz(p) => textify_type(
443 ctx,
444 w,
445 "precisiontimestamptz",
446 p.nullability(),
447 p.type_variation_reference,
448 Parameters(&[Some(Parameter::Integer(p.precision as i64))]),
449 ),
450 ptype::Kind::Struct(s) => textify_type(
451 ctx,
452 w,
453 "struct",
454 s.nullability(),
455 s.type_variation_reference,
456 Parameters(
457 &s.types
458 .iter()
459 .map(|t| Some(Parameter::DataType(t.clone())))
460 .collect::<Vec<_>>(),
461 ),
462 ),
463 ptype::Kind::List(l) => {
464 let p = l
465 .r#type
466 .as_ref()
467 .map(|t| Parameter::DataType((**t).to_owned()));
468 textify_type(
469 ctx,
470 w,
471 "list",
472 l.nullability(),
473 l.type_variation_reference,
474 Parameters(&[p]),
475 )
476 }
477 ptype::Kind::Map(m) => {
478 let k = m
479 .key
480 .as_ref()
481 .map(|t| Parameter::DataType((**t).to_owned()));
482 let v = m
483 .value
484 .as_ref()
485 .map(|t| Parameter::DataType((**t).to_owned()));
486 textify_type(
487 ctx,
488 w,
489 "map",
490 m.nullability(),
491 m.type_variation_reference,
492 Parameters(&[k, v]),
493 )
494 }
495 ptype::Kind::UserDefined(u) => u.textify(ctx, w),
496 #[allow(deprecated)]
497 ptype::Kind::UserDefinedTypeReference(r) => {
498 let udf = ptype::UserDefined {
501 type_reference: *r,
502 type_variation_reference: 0,
503 nullability: ptype::Nullability::Required as i32,
504 type_parameters: vec![],
505 };
506 ptype::Kind::UserDefined(udf).textify(ctx, w)
507 }
508 ptype::Kind::Alias(_p) => {
509 write!(
510 w,
511 "{}",
512 ctx.failure(PlanError::unimplemented(
513 "AliasType",
514 Some("Alias"),
515 "TypeAliasReference textification not implemented",
516 ))
517 )
518 }
519 }
520 }
521}
522
523impl Textify for proto::Type {
524 fn name() -> &'static str {
525 "Type"
526 }
527
528 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
529 write!(w, "{}", ctx.expect(self.kind.as_ref()))
530 }
531}
532
533#[cfg(test)]
629mod tests {
630
631 use super::*;
632 use crate::extensions::simple::{ExtensionKind, MissingReference};
633 use crate::fixtures::TestContext;
634 use crate::textify::foundation::FormatError;
635
636 #[test]
637 fn type_display() {
638 let ctx = TestContext::new()
639 .with_urn(1, "first")
640 .with_type_variation(1, 2, "u8");
641
642 let t = proto::Type {
643 kind: Some(ptype::Kind::Bool(ptype::Boolean {
644 type_variation_reference: 2,
645 nullability: ptype::Nullability::Nullable as i32,
646 })),
647 };
648
649 let s = ctx.textify_no_errors(&t);
650 assert_eq!(s, "boolean?[u8]");
651
652 let t = proto::Type {
653 kind: Some(ptype::Kind::I8(ptype::I8 {
654 type_variation_reference: 0,
655 nullability: ptype::Nullability::Required as i32,
656 })),
657 };
658 assert_eq!(ctx.textify_no_errors(&t), "i8");
659
660 let t = proto::Type {
661 kind: Some(ptype::Kind::PrecisionTimestamp(ptype::PrecisionTimestamp {
662 type_variation_reference: 0,
663 nullability: ptype::Nullability::Nullable as i32,
664 precision: 3,
665 })),
666 };
667 assert_eq!(ctx.textify_no_errors(&t), "precisiontimestamp?<3>");
668
669 let mut ctx = ctx.with_type_variation(1, 8, "int");
670 ctx.options.show_simple_extension_anchors = Visibility::Always;
671
672 let t = proto::Type {
673 kind: Some(ptype::Kind::PrecisionTime(ptype::PrecisionTime {
674 type_variation_reference: 8,
675 nullability: ptype::Nullability::Nullable as i32,
676 precision: 9,
677 })),
678 };
679 assert_eq!(ctx.textify_no_errors(&t), "precisiontime?[int#8]<9>");
680 }
681
682 #[test]
683 fn type_display_with_errors() {
684 let ctx = TestContext::new()
685 .with_urn(1, "first")
686 .with_type(1, 100, "cow");
687
688 let t = proto::Type {
689 kind: Some(ptype::Kind::Bool(ptype::Boolean {
690 type_variation_reference: 200,
691 nullability: ptype::Nullability::Nullable as i32,
692 })),
693 };
694 let (s, errs) = ctx.textify(&t);
695 assert_eq!(s, "boolean?[!{type_variation}#200]");
696 let err = errs.first();
697 let (&k, &a) = match err {
698 FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => (k, a),
699 _ => panic!("Expected Lookup MissingAnchor: {err}"),
700 };
701
702 assert_eq!(k, ExtensionKind::TypeVariation);
703 assert_eq!(a, 200);
704
705 let t = proto::Type {
706 kind: Some(ptype::Kind::UserDefined(ptype::UserDefined {
707 type_variation_reference: 0,
708 nullability: ptype::Nullability::Required as i32,
709 type_reference: 100,
710 type_parameters: vec![],
711 })),
712 };
713
714 let (s, errs) = ctx.textify(&t);
715 assert!(errs.is_empty());
716 assert_eq!(s, "cow");
717
718 let t = proto::Type {
719 kind: Some(ptype::Kind::UserDefined(ptype::UserDefined {
720 type_variation_reference: 0,
721 nullability: ptype::Nullability::Required as i32,
722 type_reference: 12589,
723 type_parameters: vec![],
724 })),
725 };
726
727 let (s, errs) = ctx.textify(&t);
728 let err = errs.first();
729 let (&k, &a) = match err {
730 FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => (k, a),
731 _ => panic!("Expected Lookup MissingAnchor: {err}"),
732 };
733 assert_eq!(k, ExtensionKind::Type);
734 assert_eq!(a, 12589);
735 assert_eq!(s, "!{type}#12589");
736 }
737
738 #[test]
739 fn struct_display() {
740 let ctx = TestContext::new();
741 let t = proto::Type {
742 kind: Some(ptype::Kind::Struct(ptype::Struct {
743 type_variation_reference: 0,
744 nullability: ptype::Nullability::Nullable as i32,
745 types: vec![
746 proto::Type {
747 kind: Some(ptype::Kind::String(ptype::String {
748 type_variation_reference: 0,
749 nullability: ptype::Nullability::Required as i32,
750 })),
751 },
752 proto::Type {
753 kind: Some(ptype::Kind::I8(ptype::I8 {
754 type_variation_reference: 0,
755 nullability: ptype::Nullability::Required as i32,
756 })),
757 },
758 proto::Type {
759 kind: Some(ptype::Kind::I32(ptype::I32 {
760 type_variation_reference: 0,
761 nullability: ptype::Nullability::Nullable as i32,
762 })),
763 },
764 proto::Type {
765 #[allow(deprecated)] kind: Some(ptype::Kind::TimestampTz(ptype::TimestampTz {
767 type_variation_reference: 0,
768 nullability: ptype::Nullability::Required as i32,
769 })),
770 },
771 ],
772 })),
773 };
774 assert_eq!(
775 ctx.textify_no_errors(&t),
776 "struct?<string, i8, i32?, timestamp_tz>"
777 );
778 }
779
780 #[test]
781 fn names_display() {
782 let ctx = TestContext::new();
783
784 let n = Name("name");
785 assert_eq!(ctx.textify_no_errors(&n), "name");
786
787 let n = Name("name with spaces");
788 assert_eq!(ctx.textify_no_errors(&n), "\"name with spaces\"");
789 }
790
791 }