1use std::fmt::{self};
2
3use chrono::{DateTime, NaiveDate};
4use expr::RexType;
5use substrait::proto::expression::field_reference::ReferenceType;
6use substrait::proto::expression::literal::LiteralType;
7use substrait::proto::expression::{
8 FieldReference, ReferenceSegment, ScalarFunction, reference_segment,
9};
10use substrait::proto::function_argument::ArgType;
11use substrait::proto::r#type::{self as ptype, Kind, Nullability};
12use substrait::proto::{
13 AggregateFunction, Expression, FunctionArgument, FunctionOption, expression as expr,
14};
15
16use super::{PlanError, Scope, Textify, Visibility};
17use crate::extensions::SimpleExtensions;
18use crate::extensions::simple::ExtensionKind;
19use crate::textify::types::{Name, NamedAnchor, OutputType, escaped};
20
21pub fn textify_binary<S: Scope, W: fmt::Write>(items: &[u8], ctx: &S, w: &mut W) -> fmt::Result {
34 if ctx.options().show_literal_binaries {
35 write!(w, "0x")?;
36 for &n in items {
37 write!(w, "{n:02x}")?;
38 }
39 } else {
40 write!(w, "{{binary}}")?;
41 }
42 Ok(())
43}
44
45pub fn textify_literal_from_string<S: Scope, W: fmt::Write>(
46 s: &str,
47 t: Kind,
48 ctx: &S,
49 w: &mut W,
50) -> fmt::Result {
51 write!(w, "\'{}\':{}", escaped(s), ctx.display(&t))
52}
53
54pub fn textify_enum<S: Scope, W: fmt::Write>(s: &str, _ctx: &S, w: &mut W) -> fmt::Result {
57 write!(w, "&{}", Name(s))
58}
59
60pub fn timestamp_to_string(t: i64) -> String {
61 let ts = chrono::DateTime::from_timestamp_nanos(t);
62 ts.to_rfc3339()
63}
64
65fn days_to_date_string(days: i32) -> String {
67 let epoch = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
68 let date = epoch + chrono::Duration::days(days as i64);
69 date.format("%Y-%m-%d").to_string()
70}
71
72fn microseconds_to_time_string(microseconds: i64) -> String {
74 let total_seconds = microseconds / 1_000_000;
75 let remaining_microseconds = microseconds % 1_000_000;
76
77 let hours = total_seconds / 3600;
78 let minutes = (total_seconds % 3600) / 60;
79 let seconds = total_seconds % 60;
80
81 if remaining_microseconds == 0 {
82 format!("{hours:02}:{minutes:02}:{seconds:02}")
83 } else {
84 let fractional = remaining_microseconds as f64 / 1_000_000.0;
86 format!("{hours:02}:{minutes:02}:{seconds:02}{fractional:.6}")
87 .trim_end_matches('0')
88 .trim_end_matches('.')
89 .to_string()
90 }
91}
92
93fn microseconds_to_timestamp_string(microseconds: i64) -> String {
95 let epoch = DateTime::from_timestamp(0, 0).unwrap().naive_utc();
96 let duration = chrono::Duration::microseconds(microseconds);
97 let datetime = epoch + duration;
98
99 let formatted = datetime.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
101
102 if formatted.contains('.') {
104 formatted
105 .trim_end_matches('0')
106 .trim_end_matches('.')
107 .to_string()
108 } else {
109 formatted
110 }
111}
112
113trait Kinded {
114 fn kind(&self, ctx: &SimpleExtensions) -> Option<Kind>;
115}
116
117impl Kinded for LiteralType {
118 fn kind(&self, _ctx: &SimpleExtensions) -> Option<Kind> {
119 match self {
120 LiteralType::Boolean(_) => Some(Kind::Bool(ptype::Boolean {
121 type_variation_reference: 0,
122 nullability: Nullability::Required.into(),
123 })),
124 LiteralType::I8(_) => Some(Kind::I8(ptype::I8 {
125 type_variation_reference: 0,
126 nullability: Nullability::Required.into(),
127 })),
128 LiteralType::I16(_) => Some(Kind::I16(ptype::I16 {
129 type_variation_reference: 0,
130 nullability: Nullability::Required.into(),
131 })),
132 LiteralType::I32(_) => Some(Kind::I32(ptype::I32 {
133 type_variation_reference: 0,
134 nullability: Nullability::Required.into(),
135 })),
136 LiteralType::I64(_) => Some(Kind::I64(ptype::I64 {
137 type_variation_reference: 0,
138 nullability: Nullability::Required.into(),
139 })),
140 LiteralType::Fp32(_) => Some(Kind::Fp32(ptype::Fp32 {
141 type_variation_reference: 0,
142 nullability: Nullability::Required.into(),
143 })),
144 LiteralType::Fp64(_) => Some(Kind::Fp64(ptype::Fp64 {
145 type_variation_reference: 0,
146 nullability: Nullability::Required.into(),
147 })),
148 LiteralType::String(_) => Some(Kind::String(ptype::String {
149 type_variation_reference: 0,
150 nullability: Nullability::Required.into(),
151 })),
152 LiteralType::Binary(_) => Some(Kind::Binary(ptype::Binary {
153 type_variation_reference: 0,
154 nullability: Nullability::Required.into(),
155 })),
156 LiteralType::Timestamp(_) => Some(Kind::Timestamp(ptype::Timestamp {
157 type_variation_reference: 0,
158 nullability: Nullability::Required.into(),
159 })),
160 LiteralType::Date(_) => Some(Kind::Date(ptype::Date {
161 type_variation_reference: 0,
162 nullability: Nullability::Required.into(),
163 })),
164 LiteralType::Time(_) => Some(Kind::Time(ptype::Time {
165 type_variation_reference: 0,
166 nullability: Nullability::Required.into(),
167 })),
168 LiteralType::IntervalYearToMonth(_) => Some(Kind::IntervalYear(ptype::IntervalYear {
169 type_variation_reference: 0,
170 nullability: Nullability::Required.into(),
171 })),
172 LiteralType::IntervalDayToSecond(i) => {
173 let precision = match i.precision_mode {
174 Some(expr::literal::interval_day_to_second::PrecisionMode::Microseconds(
175 _m,
176 )) => Some(6),
177 Some(expr::literal::interval_day_to_second::PrecisionMode::Precision(p)) => {
178 Some(p)
179 }
180 None => None,
182 };
183
184 Some(Kind::IntervalDay(ptype::IntervalDay {
185 type_variation_reference: 0,
186 nullability: Nullability::Required.into(),
187 precision,
188 }))
189 }
190 LiteralType::IntervalCompound(_) => todo!(),
191 LiteralType::FixedChar(_) => todo!(),
192 LiteralType::VarChar(_c) => todo!(),
193 LiteralType::FixedBinary(_b) => todo!(),
194 LiteralType::Decimal(_d) => todo!(),
195 LiteralType::PrecisionTime(_t) => todo!(),
196 LiteralType::PrecisionTimestamp(t) => {
197 Some(Kind::PrecisionTimestamp(ptype::PrecisionTimestamp {
198 type_variation_reference: 0,
199 nullability: Nullability::Required.into(),
200 precision: t.precision,
201 }))
202 }
203 LiteralType::PrecisionTimestampTz(_t) => todo!(),
204 LiteralType::Struct(_s) => todo!(),
205 LiteralType::Map(_m) => todo!(),
206 LiteralType::TimestampTz(_t) => todo!(),
207 LiteralType::Uuid(_u) => todo!(),
208 LiteralType::Null(_n) => todo!(),
209 LiteralType::List(_l) => todo!(),
210 LiteralType::EmptyList(_l) => todo!(),
211 LiteralType::EmptyMap(_m) => todo!(),
212 LiteralType::UserDefined(_u) => todo!(),
213 }
214 }
215}
216
217impl Textify for LiteralType {
218 fn name() -> &'static str {
219 "LiteralType"
220 }
221
222 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
223 match self {
224 LiteralType::Boolean(true) => write!(w, "true")?,
225 LiteralType::Boolean(false) => write!(w, "false")?,
226 LiteralType::I8(i) => write!(w, "{i}:i8")?,
227 LiteralType::I16(i) => write!(w, "{i}:i16")?,
228 LiteralType::I32(i) => write!(w, "{i}:i32")?,
229 LiteralType::I64(i) => {
230 match ctx.options().literal_types {
231 Visibility::Always => write!(w, "{i}:i64")?,
232 Visibility::Required => write!(w, "{i}")?, Visibility::Never => write!(w, "{i}")?,
234 }
235 }
236 LiteralType::Fp32(f) => write!(w, "{f}:fp32")?,
237 LiteralType::Fp64(f) => {
238 match ctx.options().literal_types {
239 Visibility::Always => write!(w, "{f}:fp64")?,
240 Visibility::Required => write!(w, "{f}")?, Visibility::Never => write!(w, "{f}")?,
242 }
243 }
244 LiteralType::String(s) => write!(w, "'{}'", s.escape_debug())?,
245 LiteralType::Binary(items) => textify_binary(items, ctx, w)?,
246 LiteralType::Timestamp(microseconds) => {
247 let k = match self.kind(ctx.extensions()) {
248 Some(k) => k,
249 None => {
250 let err = PlanError::internal(
251 "LiteralType",
252 Some("Timestamp"),
253 format!("No kind found for {self:?}"),
254 );
255 write!(w, "{}", ctx.failure(err))?;
256 return Ok(());
257 }
258 };
259 let s = microseconds_to_timestamp_string(*microseconds);
260 textify_literal_from_string(&s, k, ctx, w)?
261 }
262 LiteralType::Date(days) => {
263 let k = match self.kind(ctx.extensions()) {
264 Some(k) => k,
265 None => {
266 let err = PlanError::internal(
267 "LiteralType",
268 Some("Date"),
269 format!("No kind found for {self:?}"),
270 );
271 write!(w, "{}", ctx.failure(err))?;
272 return Ok(());
273 }
274 };
275 let s = days_to_date_string(*days);
276 textify_literal_from_string(&s, k, ctx, w)?
277 }
278 LiteralType::Time(microseconds) => {
279 let k = match self.kind(ctx.extensions()) {
280 Some(k) => k,
281 None => {
282 let err = PlanError::internal(
283 "LiteralType",
284 Some("Time"),
285 format!("No kind found for {self:?}"),
286 );
287 write!(w, "{}", ctx.failure(err))?;
288 return Ok(());
289 }
290 };
291 let s = microseconds_to_time_string(*microseconds);
292 textify_literal_from_string(&s, k, ctx, w)?
293 }
294 LiteralType::IntervalYearToMonth(_i) => {
295 return write!(
296 w,
297 "{}",
298 ctx.failure(PlanError::unimplemented(
299 "LiteralType",
300 Some("IntervalYearToMonth"),
301 "IntervalYearToMonth literal textification not implemented",
302 ))
303 );
304 }
305 LiteralType::IntervalDayToSecond(_i) => {
306 return write!(
307 w,
308 "{}",
309 ctx.failure(PlanError::unimplemented(
310 "LiteralType",
311 Some("IntervalDayToSecond"),
312 "IntervalDayToSecond literal textification not implemented",
313 ))
314 );
315 }
316 LiteralType::IntervalCompound(_i) => {
317 return write!(
318 w,
319 "{}",
320 ctx.failure(PlanError::unimplemented(
321 "LiteralType",
322 Some("IntervalCompound"),
323 "IntervalCompound literal textification not implemented",
324 ))
325 );
326 }
327 LiteralType::FixedChar(_c) => {
328 return write!(
329 w,
330 "{}",
331 ctx.failure(PlanError::unimplemented(
332 "LiteralType",
333 Some("FixedChar"),
334 "FixedChar literal textification not implemented",
335 ))
336 );
337 }
338 LiteralType::VarChar(_c) => {
339 return write!(
340 w,
341 "{}",
342 ctx.failure(PlanError::unimplemented(
343 "LiteralType",
344 Some("VarChar"),
345 "VarChar literal textification not implemented",
346 ))
347 );
348 }
349 LiteralType::FixedBinary(_i) => {
350 return write!(
351 w,
352 "{}",
353 ctx.failure(PlanError::unimplemented(
354 "LiteralType",
355 Some("FixedBinary"),
356 "FixedBinary literal textification not implemented",
357 ))
358 );
359 }
360 LiteralType::Decimal(_d) => {
361 return write!(
362 w,
363 "{}",
364 ctx.failure(PlanError::unimplemented(
365 "LiteralType",
366 Some("Decimal"),
367 "Decimal literal textification not implemented",
368 ))
369 );
370 }
371 LiteralType::PrecisionTime(_p) => {
372 return write!(
373 w,
374 "{}",
375 ctx.failure(PlanError::unimplemented(
376 "LiteralType",
377 Some("PrecisionTime"),
378 "PrecisionTime literal textification not implemented",
379 ))
380 );
381 }
382 LiteralType::PrecisionTimestamp(_p) => {
383 return write!(
384 w,
385 "{}",
386 ctx.failure(PlanError::unimplemented(
387 "LiteralType",
388 Some("PrecisionTimestamp"),
389 "PrecisionTimestamp literal textification not implemented",
390 ))
391 );
392 }
393 LiteralType::PrecisionTimestampTz(_p) => {
394 return write!(
395 w,
396 "{}",
397 ctx.failure(PlanError::unimplemented(
398 "LiteralType",
399 Some("PrecisionTimestampTz"),
400 "PrecisionTimestampTz literal textification not implemented",
401 ))
402 );
403 }
404 LiteralType::Struct(_s) => {
405 return write!(
406 w,
407 "{}",
408 ctx.failure(PlanError::unimplemented(
409 "LiteralType",
410 Some("Struct"),
411 "Struct literal textification not implemented",
412 )),
413 );
414 }
415 LiteralType::Map(_m) => {
416 return write!(
417 w,
418 "{}",
419 ctx.failure(PlanError::unimplemented(
420 "LiteralType",
421 Some("Map"),
422 "Map literal textification not implemented",
423 )),
424 );
425 }
426 LiteralType::TimestampTz(_t) => {
427 return write!(
428 w,
429 "{}",
430 ctx.failure(PlanError::unimplemented(
431 "LiteralType",
432 Some("TimestampTz"),
433 "TimestampTz literal textification not implemented",
434 ))
435 );
436 }
437 LiteralType::Uuid(_u) => {
438 return write!(
439 w,
440 "{}",
441 ctx.failure(PlanError::unimplemented(
442 "LiteralType",
443 Some("Uuid"),
444 "Uuid literal textification not implemented",
445 ))
446 );
447 }
448 LiteralType::Null(_n) => {
449 return write!(
450 w,
451 "{}",
452 ctx.failure(PlanError::unimplemented(
453 "LiteralType",
454 Some("Null"),
455 "Null literal textification not implemented",
456 ))
457 );
458 }
459 LiteralType::List(_l) => {
460 return write!(
461 w,
462 "{}",
463 ctx.failure(PlanError::unimplemented(
464 "LiteralType",
465 Some("List"),
466 "List literal textification not implemented",
467 ))
468 );
469 }
470 LiteralType::EmptyList(_l) => {
471 return write!(
472 w,
473 "{}",
474 ctx.failure(PlanError::unimplemented(
475 "LiteralType",
476 Some("EmptyList"),
477 "EmptyList literal textification not implemented",
478 ))
479 );
480 }
481 LiteralType::EmptyMap(_l) => {
482 return write!(
483 w,
484 "{}",
485 ctx.failure(PlanError::unimplemented(
486 "LiteralType",
487 Some("EmptyMap"),
488 "EmptyMap literal textification not implemented",
489 ))
490 );
491 }
492 LiteralType::UserDefined(_u) => {
493 return write!(
494 w,
495 "{}",
496 ctx.failure(PlanError::unimplemented(
497 "LiteralType",
498 Some("UserDefined"),
499 "UserDefined literal textification not implemented",
500 ))
501 );
502 }
503 }
504 Ok(())
505 }
506}
507
508impl Textify for expr::Literal {
509 fn name() -> &'static str {
510 "Literal"
511 }
512
513 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
514 write!(w, "{}", ctx.expect(self.literal_type.as_ref()))
515 }
516}
517
518pub struct Reference(pub i32);
519
520impl fmt::Display for Reference {
521 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522 write!(f, "${}", self.0)
523 }
524}
525
526impl From<Reference> for Expression {
527 fn from(r: Reference) -> Self {
528 Expression {
531 rex_type: Some(RexType::Selection(Box::new(FieldReference {
532 reference_type: Some(ReferenceType::DirectReference(ReferenceSegment {
533 reference_type: Some(reference_segment::ReferenceType::StructField(Box::new(
534 reference_segment::StructField {
535 field: r.0,
536 child: None,
537 },
538 ))),
539 })),
540 root_type: None,
541 }))),
542 }
543 }
544}
545
546impl Textify for Reference {
547 fn name() -> &'static str {
548 "Reference"
549 }
550
551 fn textify<S: Scope, W: fmt::Write>(&self, _ctx: &S, w: &mut W) -> fmt::Result {
552 write!(w, "{self}")
553 }
554}
555
556impl Textify for FieldReference {
557 fn name() -> &'static str {
558 "FieldReference"
559 }
560
561 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
562 let ref_type = match &self.reference_type {
563 None => {
564 return write!(
565 w,
566 "{}",
567 ctx.failure(PlanError::invalid(
568 "FieldReference",
569 Some("reference_type"),
570 "Required field reference_type is missing",
571 ))
572 );
573 }
574 Some(ReferenceType::DirectReference(r)) => r,
575 _ => {
576 return write!(
577 w,
578 "{}",
579 ctx.failure(PlanError::unimplemented(
580 "FieldReference",
581 Some("FieldReference"),
582 "FieldReference textification implemented only for StructField",
583 ))
584 );
585 }
586 };
587
588 match &ref_type.reference_type {
589 Some(reference_segment::ReferenceType::StructField(s)) => {
590 write!(w, "{}", Reference(s.field))
591 }
592 None => write!(
593 w,
594 "{}",
595 ctx.failure(PlanError::invalid(
596 "ReferenceSegment",
597 Some("reference_type"),
598 "Required field reference_type is missing",
599 ))
600 ),
601 _ => write!(
602 w,
603 "{}",
604 ctx.failure(PlanError::unimplemented(
605 "ReferenceSegment",
606 Some("reference_type"),
607 "ReferenceSegment textification implemented only for StructField",
608 ))
609 ),
610 }
611 }
612}
613
614impl Textify for ScalarFunction {
615 fn name() -> &'static str {
616 "ScalarFunction"
617 }
618
619 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
620 let name_and_anchor =
621 NamedAnchor::lookup(ctx, ExtensionKind::Function, self.function_reference);
622 let name_and_anchor = ctx.display(&name_and_anchor);
623
624 let args = ctx.separated(&self.arguments, ", ");
625 let options = ctx.separated(&self.options, ", ");
626 let between = if self.arguments.is_empty() || self.options.is_empty() {
627 ""
628 } else {
629 ", "
630 };
631
632 let output = OutputType(self.output_type.as_ref());
633 let output_type = ctx.optional(&output, ctx.options().fn_types);
634
635 write!(
636 w,
637 "{name_and_anchor}({args}{between}{options}){output_type}"
638 )?;
639 Ok(())
640 }
641}
642
643impl Textify for FunctionOption {
644 fn name() -> &'static str {
645 "FunctionOption"
646 }
647
648 fn textify<S: Scope, W: fmt::Write>(&self, _ctx: &S, w: &mut W) -> fmt::Result {
649 write!(w, "{}⇒[", self.name)?;
650 let mut first = true;
651 for pref in self.preference.iter() {
652 if !first {
653 write!(w, ", ")?;
654 } else {
655 first = false;
656 }
657 write!(w, "{pref}")?;
658 }
659 write!(w, "]")?;
660 Ok(())
661 }
662}
663
664impl Textify for FunctionArgument {
665 fn name() -> &'static str {
666 "FunctionArgument"
667 }
668
669 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
670 write!(w, "{}", ctx.expect(self.arg_type.as_ref()))
671 }
672}
673
674impl Textify for ArgType {
675 fn name() -> &'static str {
676 "ArgType"
677 }
678
679 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
680 match self {
681 ArgType::Type(t) => t.textify(ctx, w),
682 ArgType::Value(v) => v.textify(ctx, w),
683 ArgType::Enum(e) => textify_enum(e, ctx, w),
684 }
685 }
686}
687
688impl Textify for RexType {
689 fn name() -> &'static str {
690 "RexType"
691 }
692
693 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
694 match self {
695 RexType::Literal(literal) => literal.textify(ctx, w),
696 RexType::Selection(f) => f.textify(ctx, w),
697 RexType::ScalarFunction(s) => s.textify(ctx, w),
698 RexType::WindowFunction(_w) => write!(
699 w,
700 "{}",
701 ctx.failure(PlanError::unimplemented(
702 "RexType",
703 Some("WindowFunction"),
704 "WindowFunction textification not implemented",
705 ))
706 ),
707 RexType::IfThen(_i) => write!(
708 w,
709 "{}",
710 ctx.failure(PlanError::unimplemented(
711 "RexType",
712 Some("IfThen"),
713 "IfThen textification not implemented",
714 ))
715 ),
716 RexType::SwitchExpression(_s) => write!(
717 w,
718 "{}",
719 ctx.failure(PlanError::unimplemented(
720 "RexType",
721 Some("SwitchExpression"),
722 "SwitchExpression textification not implemented",
723 ))
724 ),
725 RexType::SingularOrList(_s) => write!(
726 w,
727 "{}",
728 ctx.failure(PlanError::unimplemented(
729 "RexType",
730 Some("SingularOrList"),
731 "SingularOrList textification not implemented",
732 ))
733 ),
734 RexType::MultiOrList(_m) => write!(
735 w,
736 "{}",
737 ctx.failure(PlanError::unimplemented(
738 "RexType",
739 Some("MultiOrList"),
740 "MultiOrList textification not implemented",
741 ))
742 ),
743 RexType::Cast(_c) => write!(
744 w,
745 "{}",
746 ctx.failure(PlanError::unimplemented(
747 "RexType",
748 Some("Cast"),
749 "Cast textification not implemented",
750 ))
751 ),
752 RexType::Subquery(_s) => write!(
753 w,
754 "{}",
755 ctx.failure(PlanError::unimplemented(
756 "RexType",
757 Some("Subquery"),
758 "Subquery textification not implemented",
759 ))
760 ),
761 RexType::Nested(_n) => write!(
762 w,
763 "{}",
764 ctx.failure(PlanError::unimplemented(
765 "RexType",
766 Some("Nested"),
767 "Nested textification not implemented",
768 ))
769 ),
770 RexType::DynamicParameter(_d) => write!(
771 w,
772 "{}",
773 ctx.failure(PlanError::unimplemented(
774 "RexType",
775 Some("DynamicParameter"),
776 "DynamicParameter textification not implemented",
777 ))
778 ),
779 RexType::Enum(_) => write!(
780 w,
781 "{}",
782 ctx.failure(PlanError::unimplemented(
783 "RexType",
784 Some("Enum"),
785 "Enum textification not implemented",
786 ))
787 ),
788 }
789 }
790}
791
792impl Textify for Expression {
793 fn name() -> &'static str {
794 "Expression"
795 }
796
797 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
798 write!(w, "{}", ctx.expect(self.rex_type.as_ref()))
799 }
800}
801
802impl Textify for AggregateFunction {
803 fn name() -> &'static str {
804 "AggregateFunction"
805 }
806
807 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
808 let name_and_anchor = crate::textify::types::NamedAnchor::lookup(
810 ctx,
811 crate::extensions::simple::ExtensionKind::Function,
812 self.function_reference,
813 );
814 let name_and_anchor = ctx.display(&name_and_anchor);
815
816 let args = ctx.separated(&self.arguments, ", ");
817 let options = ctx.separated(&self.options, ", ");
818 let between = if self.arguments.is_empty() || self.options.is_empty() {
819 ""
820 } else {
821 ", "
822 };
823
824 let output = crate::textify::types::OutputType(self.output_type.as_ref());
825 let output_type = ctx.optional(&output, ctx.options().fn_types);
826
827 write!(
828 w,
829 "{name_and_anchor}({args}{between}{options}){output_type}"
830 )
831 }
832}
833
834#[cfg(test)]
835mod tests {
836 use super::*;
837 use crate::extensions::simple::{ExtensionKind, MissingReference};
838 use crate::fixtures::TestContext;
839 use crate::textify::foundation::FormatError;
840
841 #[test]
842 fn test_literal_textify() {
843 let ctx = TestContext::new();
844
845 let literal = LiteralType::Boolean(true);
846 assert_eq!(ctx.textify_no_errors(&literal), "true");
847 }
848
849 #[test]
850 fn test_expression_textify() {
851 let ctx = TestContext::new();
852
853 let expr_empty = Expression { rex_type: None }; let (s, errs) = ctx.textify(&expr_empty);
856 assert!(!errs.is_empty());
857 assert_eq!(s, "!{RexType}");
858
859 let expr_lit = Expression {
861 rex_type: Some(RexType::Literal(expr::Literal {
862 nullable: false,
863 type_variation_reference: 0,
864 literal_type: Some(expr::literal::LiteralType::Boolean(true)),
865 })),
866 };
867 assert_eq!(ctx.textify_no_errors(&expr_lit), "true");
868 }
869
870 #[test]
871 fn test_rextype_textify() {
872 let ctx = TestContext::new();
873
874 let func = RexType::ScalarFunction(ScalarFunction {
875 function_reference: 1000, arguments: vec![],
877 options: vec![],
878 output_type: None,
879 #[allow(deprecated)]
880 args: vec![],
881 });
882 let (s, errq) = ctx.textify(&func);
887 let errs: Vec<_> = errq.0;
888 match errs[0] {
889 FormatError::Lookup(MissingReference::MissingAnchor(k, a)) => {
890 assert_eq!(k, ExtensionKind::Function);
891 assert_eq!(a, 1000);
892 }
893 _ => panic!("Expected Lookup MissingAnchor: {}", errs[0]),
894 }
895 assert_eq!(s, "!{function}#1000()");
896
897 let ctx = ctx.with_uri(1, "first").with_function(1, 100, "first");
898 let func = RexType::ScalarFunction(ScalarFunction {
899 function_reference: 100,
900 arguments: vec![],
901 options: vec![],
902 output_type: None,
903 #[allow(deprecated)]
904 args: vec![],
905 });
906 let s = ctx.textify_no_errors(&func);
907 assert_eq!(s, "first()");
908
909 let options_show_anchor = Default::default();
911
912 let ctx = TestContext::new()
913 .with_options(options_show_anchor)
914 .with_uri(1, "somewhere_on_the_internet")
915 .with_uri(2, "somewhere_else")
916 .with_function(1, 231, "duplicated")
917 .with_function(2, 232, "duplicated");
918
919 let rex_dup = RexType::ScalarFunction(ScalarFunction {
920 function_reference: 231,
921 arguments: vec![FunctionArgument {
922 arg_type: Some(ArgType::Value(Expression {
923 rex_type: Some(RexType::Literal(expr::Literal {
924 nullable: false,
925 type_variation_reference: 0,
926 literal_type: Some(expr::literal::LiteralType::Boolean(true)),
927 })),
928 })),
929 }],
930 options: vec![],
931 output_type: None,
932 #[allow(deprecated)]
933 args: vec![],
934 });
935 let s = ctx.textify_no_errors(&rex_dup);
936 assert_eq!(s, "duplicated#231(true)");
937 }
938}