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 ptype::Kind::Timestamp(k) => textify_kind!(ctx, w, k, "timestamp"),
361 ptype::Kind::Date(k) => textify_kind!(ctx, w, k, "date"),
362 ptype::Kind::Time(k) => textify_kind!(ctx, w, k, "time"),
363 ptype::Kind::IntervalYear(i) => {
364 textify_kind!(ctx, w, i, "interval_year")
365 }
366
367 ptype::Kind::TimestampTz(ts) => {
368 textify_kind!(ctx, w, ts, "timestamp_tz")
369 }
370 ptype::Kind::Uuid(uuid) => textify_kind!(ctx, w, uuid, "uuid"),
371
372 ptype::Kind::IntervalDay(i) => textify_type(
373 ctx,
374 w,
375 "interval_day",
376 i.nullability(),
377 i.type_variation_reference,
378 Parameters(&[Some(Parameter::Integer(i.precision.unwrap_or(6) as i64))]),
380 ),
381 ptype::Kind::IntervalCompound(i) => textify_type(
382 ctx,
383 w,
384 "interval_compound",
385 i.nullability(),
386 i.type_variation_reference,
387 Parameters(&[Some(Parameter::Integer(i.precision as i64))]),
388 ),
389 ptype::Kind::FixedChar(c) => textify_type(
390 ctx,
391 w,
392 "fixedchar",
393 c.nullability(),
394 c.type_variation_reference,
395 Parameters(&[Some(Parameter::Integer(c.length as i64))]),
396 ),
397 ptype::Kind::Varchar(_c) => todo!(),
398 ptype::Kind::FixedBinary(_b) => todo!(),
399 ptype::Kind::Decimal(_d) => todo!(),
400 ptype::Kind::PrecisionTime(p) => textify_type(
401 ctx,
402 w,
403 "precisiontime",
404 p.nullability(),
405 p.type_variation_reference,
406 Parameters(&[Some(Parameter::Integer(p.precision as i64))]),
407 ),
408 ptype::Kind::PrecisionTimestamp(p) => textify_type(
409 ctx,
410 w,
411 "precisiontimestamp",
412 p.nullability(),
413 p.type_variation_reference,
414 Parameters(&[Some(Parameter::Integer(p.precision as i64))]),
415 ),
416 ptype::Kind::PrecisionTimestampTz(_p) => todo!(),
417 ptype::Kind::Struct(s) => textify_type(
418 ctx,
419 w,
420 "struct",
421 s.nullability(),
422 s.type_variation_reference,
423 Parameters(
424 &s.types
425 .iter()
426 .map(|t| Some(Parameter::DataType(t.clone())))
427 .collect::<Vec<_>>(),
428 ),
429 ),
430 ptype::Kind::List(l) => {
431 let p = l
432 .r#type
433 .as_ref()
434 .map(|t| Parameter::DataType((**t).to_owned()));
435 textify_type(
436 ctx,
437 w,
438 "list",
439 l.nullability(),
440 l.type_variation_reference,
441 Parameters(&[p]),
442 )
443 }
444 ptype::Kind::Map(m) => {
445 let k = m
446 .key
447 .as_ref()
448 .map(|t| Parameter::DataType((**t).to_owned()));
449 let v = m
450 .value
451 .as_ref()
452 .map(|t| Parameter::DataType((**t).to_owned()));
453 textify_type(
454 ctx,
455 w,
456 "map",
457 m.nullability(),
458 m.type_variation_reference,
459 Parameters(&[k, v]),
460 )
461 }
462 ptype::Kind::UserDefined(u) => u.textify(ctx, w),
463 ptype::Kind::UserDefinedTypeReference(r) => {
464 let udf = ptype::UserDefined {
467 type_reference: *r,
468 type_variation_reference: 0,
469 nullability: ptype::Nullability::Required as i32,
470 type_parameters: vec![],
471 };
472 ptype::Kind::UserDefined(udf).textify(ctx, w)
473 }
474 }
475 }
476}
477
478impl Textify for proto::Type {
479 fn name() -> &'static str {
480 "Type"
481 }
482
483 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
484 write!(w, "{}", ctx.expect(self.kind.as_ref()))
485 }
486}
487
488#[cfg(test)]
584mod tests {
585
586 use super::*;
587 use crate::extensions::simple::{ExtensionKind, MissingReference};
588 use crate::fixtures::TestContext;
589 use crate::textify::foundation::FormatError;
590
591 #[test]
592 fn type_display() {
593 let ctx = TestContext::new()
594 .with_uri(1, "first")
595 .with_type_variation(1, 2, "u8");
596
597 let t = proto::Type {
598 kind: Some(ptype::Kind::Bool(ptype::Boolean {
599 type_variation_reference: 2,
600 nullability: ptype::Nullability::Nullable as i32,
601 })),
602 };
603
604 let s = ctx.textify_no_errors(&t);
605 assert_eq!(s, "boolean?[u8]");
606
607 let t = proto::Type {
608 kind: Some(ptype::Kind::I8(ptype::I8 {
609 type_variation_reference: 0,
610 nullability: ptype::Nullability::Required as i32,
611 })),
612 };
613 assert_eq!(ctx.textify_no_errors(&t), "i8");
614
615 let t = proto::Type {
616 kind: Some(ptype::Kind::PrecisionTimestamp(ptype::PrecisionTimestamp {
617 type_variation_reference: 0,
618 nullability: ptype::Nullability::Nullable as i32,
619 precision: 3,
620 })),
621 };
622 assert_eq!(ctx.textify_no_errors(&t), "precisiontimestamp?<3>");
623
624 let mut ctx = ctx.with_type_variation(1, 8, "int");
625 ctx.options.show_simple_extension_anchors = Visibility::Always;
626
627 let t = proto::Type {
628 kind: Some(ptype::Kind::PrecisionTime(ptype::PrecisionTime {
629 type_variation_reference: 8,
630 nullability: ptype::Nullability::Nullable as i32,
631 precision: 9,
632 })),
633 };
634 assert_eq!(ctx.textify_no_errors(&t), "precisiontime?[int#8]<9>");
635 }
636
637 #[test]
638 fn type_display_with_errors() {
639 let ctx = TestContext::new()
640 .with_uri(1, "first")
641 .with_type(1, 100, "cow");
642
643 let t = proto::Type {
644 kind: Some(ptype::Kind::Bool(ptype::Boolean {
645 type_variation_reference: 200,
646 nullability: ptype::Nullability::Nullable as i32,
647 })),
648 };
649 let (s, errs) = ctx.textify(&t);
650 assert_eq!(s, "boolean?[!{type_variation}#200]");
651 let err = errs.first();
652 let (&k, &a) = match err {
653 FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => (k, a),
654 _ => panic!("Expected Lookup MissingAnchor: {err}"),
655 };
656
657 assert_eq!(k, ExtensionKind::TypeVariation);
658 assert_eq!(a, 200);
659
660 let t = proto::Type {
661 kind: Some(ptype::Kind::UserDefined(ptype::UserDefined {
662 type_variation_reference: 0,
663 nullability: ptype::Nullability::Required as i32,
664 type_reference: 100,
665 type_parameters: vec![],
666 })),
667 };
668
669 let (s, errs) = ctx.textify(&t);
670 assert!(errs.is_empty());
671 assert_eq!(s, "cow");
672
673 let t = proto::Type {
674 kind: Some(ptype::Kind::UserDefined(ptype::UserDefined {
675 type_variation_reference: 0,
676 nullability: ptype::Nullability::Required as i32,
677 type_reference: 12589,
678 type_parameters: vec![],
679 })),
680 };
681
682 let (s, errs) = ctx.textify(&t);
683 let err = errs.first();
684 let (&k, &a) = match err {
685 FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => (k, a),
686 _ => panic!("Expected Lookup MissingAnchor: {err}"),
687 };
688 assert_eq!(k, ExtensionKind::Type);
689 assert_eq!(a, 12589);
690 assert_eq!(s, "!{type}#12589");
691 }
692
693 #[test]
694 fn struct_display() {
695 let ctx = TestContext::new();
696
697 let t = proto::Type {
698 kind: Some(ptype::Kind::Struct(ptype::Struct {
699 type_variation_reference: 0,
700 nullability: ptype::Nullability::Nullable as i32,
701 types: vec![
702 proto::Type {
703 kind: Some(ptype::Kind::String(ptype::String {
704 type_variation_reference: 0,
705 nullability: ptype::Nullability::Required as i32,
706 })),
707 },
708 proto::Type {
709 kind: Some(ptype::Kind::I8(ptype::I8 {
710 type_variation_reference: 0,
711 nullability: ptype::Nullability::Required as i32,
712 })),
713 },
714 proto::Type {
715 kind: Some(ptype::Kind::I32(ptype::I32 {
716 type_variation_reference: 0,
717 nullability: ptype::Nullability::Nullable as i32,
718 })),
719 },
720 proto::Type {
721 kind: Some(ptype::Kind::TimestampTz(ptype::TimestampTz {
722 type_variation_reference: 0,
723 nullability: ptype::Nullability::Required as i32,
724 })),
725 },
726 ],
727 })),
728 };
729 assert_eq!(
730 ctx.textify_no_errors(&t),
731 "struct?<string, i8, i32?, timestamp_tz>"
732 );
733 }
734
735 #[test]
736 fn names_display() {
737 let ctx = TestContext::new();
738
739 let n = Name("name");
740 assert_eq!(ctx.textify_no_errors(&n), "name");
741
742 let n = Name("name with spaces");
743 assert_eq!(ctx.textify_no_errors(&n), "\"name with spaces\"");
744 }
745
746 }