1use pylon_core::query::{JsonMember, JsonMemberKind, ShapeNode};
28use pylon_value::DecodedValue;
29
30use crate::value::{Group, Object, Range, Value};
31
32pub fn decode(shape: &ShapeNode, value: &DecodedValue) -> Value {
36 match shape {
37 ShapeNode::Object { position: 0, .. } => decode_inner(shape, value, Some(0)),
39 ShapeNode::Tuple { position: 0, .. } => decode_inner(shape, value, Some(0)),
42 _ => decode_inner(shape, value, None),
43 }
44}
45
46fn decode_inner(shape: &ShapeNode, value: &DecodedValue, position_override: Option<usize>) -> Value {
52 match shape {
53 ShapeNode::Scalar { position, .. } => {
54 cached_to_value(&composite_at(value, position_override.unwrap_or(*position)))
55 }
56 ShapeNode::RawScalar | ShapeNode::JsonScalar => cached_to_value(value),
57 ShapeNode::Object {
58 type_name,
59 position,
60 pointers,
61 has_implicit_id,
62 ..
63 } => {
64 let obj_tuple = match position_override {
67 Some(_) => value.clone(),
68 None => composite_at(value, *position),
69 };
70 decode_object(&obj_tuple, type_name.as_deref(), pointers, *has_implicit_id)
71 }
72 ShapeNode::Array { position, element, .. } => {
73 decode_array(value, position_override.unwrap_or(*position), element)
74 }
75 ShapeNode::NamedTuple {
76 position,
77 type_name,
78 members,
79 ..
80 } => decode_named_tuple(
81 value,
82 position_override.unwrap_or(*position),
83 type_name.as_deref(),
84 members.as_deref(),
85 ),
86 ShapeNode::Enum {
87 position, enum_type, ..
88 } => decode_enum(value, position_override.unwrap_or(*position), enum_type),
89 ShapeNode::Tuple {
93 position,
94 elements,
95 names,
96 type_name,
97 ..
98 } => {
99 let row = match position_override {
100 Some(_) => value.clone(),
101 None => composite_at(value, *position),
102 };
103 decode_composite_tuple(&row, elements, names.as_deref(), type_name.as_deref())
104 }
105 ShapeNode::Group {
106 key_nodes,
107 grouping_position,
108 elements_position,
109 element,
110 } => decode_group(value, key_nodes, *grouping_position, *elements_position, element),
111 ShapeNode::VectorSearch {
112 object_position,
113 distance_position,
114 object_node,
115 } => decode_vector_search(value, *object_position, *distance_position, object_node),
116 ShapeNode::FtsSearch {
117 object_position,
118 rank_position,
119 object_node,
120 } => decode_fts_search(value, *object_position, *rank_position, object_node),
121 }
122}
123
124fn composite_at(value: &DecodedValue, pos: usize) -> DecodedValue {
132 match value {
133 DecodedValue::Composite(fields) => fields.get(pos).cloned().unwrap_or(DecodedValue::Null),
134 _ => DecodedValue::Null,
135 }
136}
137
138fn pointer_name(node: &ShapeNode) -> &str {
143 match node {
144 ShapeNode::Scalar { name, .. }
145 | ShapeNode::Enum { name, .. }
146 | ShapeNode::NamedTuple { name, .. }
147 | ShapeNode::Tuple { name, .. }
148 | ShapeNode::Object { name, .. }
149 | ShapeNode::Array { name, .. } => name,
150 other => unreachable!("shape node kind never appears as an object's own pointer: {other:?}"),
151 }
152}
153
154fn pointer_position(node: &ShapeNode) -> usize {
155 match node {
156 ShapeNode::Scalar { position, .. }
157 | ShapeNode::Enum { position, .. }
158 | ShapeNode::NamedTuple { position, .. }
159 | ShapeNode::Object { position, .. }
160 | ShapeNode::Array { position, .. } => *position,
161 other => unreachable!("shape node kind never appears as an object's own pointer: {other:?}"),
162 }
163}
164
165fn pg_schema_qualified_to_pylon(qualified: &str) -> String {
170 match qualified.split_once("::") {
171 Some(("public", rest)) => format!("default::{rest}"),
172 _ => qualified.to_string(),
173 }
174}
175
176fn decode_object(
177 obj_tuple: &DecodedValue,
178 type_name: Option<&str>,
179 pointers: &[ShapeNode],
180 implicit_id: bool,
181) -> Value {
182 if matches!(obj_tuple, DecodedValue::Null) {
183 return Value::Null;
184 }
185 let mut fields: Vec<(String, Value)> = Vec::new();
189 for p in pointers
190 .iter()
191 .filter(|p| !(pointer_name(p) == "__type__" && pointer_position(p) == 0))
192 {
193 let value = decode_inner(p, obj_tuple, None);
194 match fields.iter_mut().find(|(name, _)| name == pointer_name(p)) {
198 Some((_, existing)) if is_nothing(existing) => *existing = value,
199 Some(_) => {}
200 None => fields.push((pointer_name(p).to_string(), value)),
201 }
202 }
203 let resolved_type_name = type_name.map(|static_name| match composite_at(obj_tuple, 0) {
207 DecodedValue::Str(s) if !s.is_empty() => s,
208 _ => static_name.to_string(),
209 });
210 Value::Object(Object {
211 type_name: resolved_type_name,
212 fields,
213 implicit_id,
214 })
215}
216
217fn is_nothing(value: &Value) -> bool {
218 match value {
219 Value::Null => true,
220 Value::Array(items) => items.is_empty(),
221 _ => false,
222 }
223}
224
225fn decode_array(value: &DecodedValue, position: usize, element: &ShapeNode) -> Value {
226 let items = match composite_at(value, position) {
228 DecodedValue::Array(items) => items,
229 DecodedValue::Null if !matches!(element, ShapeNode::Object { .. }) => return Value::Null,
230 _ => vec![],
231 };
232 Value::Array(items.iter().map(|item| decode_inner(element, item, Some(0))).collect())
233}
234
235fn decode_composite_tuple(
243 row: &DecodedValue,
244 elements: &[ShapeNode],
245 names: Option<&[String]>,
246 type_name: Option<&str>,
247) -> Value {
248 if matches!(row, DecodedValue::Null) {
249 return Value::Null;
250 }
251 let items: Vec<Value> = elements.iter().map(|e| decode_inner(e, row, None)).collect();
252 match names {
253 None => Value::Tuple(items),
254 Some(names) => Value::Object(Object {
255 type_name: type_name.map(str::to_string),
256 fields: names.iter().cloned().zip(items).collect(),
257 implicit_id: false,
258 }),
259 }
260}
261
262fn decode_named_tuple(
263 value: &DecodedValue,
264 position: usize,
265 type_name: Option<&str>,
266 members: Option<&[JsonMember]>,
267) -> Value {
268 let raw = match value {
273 DecodedValue::Object(_) | DecodedValue::Array(_) | DecodedValue::Null => value.clone(),
274 _ => composite_at(value, position),
275 };
276 decode_json_tuple(&raw, type_name, members)
277}
278
279fn decode_json_tuple(value: &DecodedValue, type_name: Option<&str>, members: Option<&[JsonMember]>) -> Value {
287 if matches!(value, DecodedValue::Null) {
288 return Value::Null;
289 }
290 let Some(members) = members else {
291 return cached_to_value(value);
292 };
293 let positional = members.iter().all(|m| m.key.is_none());
294 if positional {
295 let items: &[DecodedValue] = match value {
296 DecodedValue::Array(a) => a.as_slice(),
297 _ => &[],
298 };
299 let elements = members
300 .iter()
301 .enumerate()
302 .map(|(i, m)| decode_json_member(items.get(i).unwrap_or(&DecodedValue::Null), m))
303 .collect();
304 return Value::Tuple(elements);
305 }
306 let obj_fields: &[(String, DecodedValue)] = match value {
307 DecodedValue::Object(o) => o.as_slice(),
308 _ => &[],
309 };
310 let fields = members
311 .iter()
312 .map(|m| {
313 let key = m.key.clone().expect("named branch: every member has a key");
314 let raw = obj_fields
315 .iter()
316 .find(|(k, _)| *k == key)
317 .map(|(_, v)| v)
318 .unwrap_or(&DecodedValue::Null);
319 (key, decode_json_member(raw, m))
320 })
321 .collect();
322 Value::Object(Object {
323 type_name: type_name.map(str::to_string),
324 fields,
325 implicit_id: false,
326 })
327}
328
329fn decode_json_member(value: &DecodedValue, member: &JsonMember) -> Value {
330 match &member.kind {
331 JsonMemberKind::Scalar => cached_to_value(value),
332 JsonMemberKind::Decimal => match value {
336 DecodedValue::JsonNumber(digits) | DecodedValue::Str(digits) => Value::Decimal(digits.clone()),
337 DecodedValue::I64(integer) => Value::Decimal(integer.to_string()),
338 other => cached_to_value(other),
339 },
340 JsonMemberKind::Enum { enum_type } => match value {
341 DecodedValue::Null => Value::Null,
342 DecodedValue::Str(s) => Value::Enum {
343 type_name: pg_schema_qualified_to_pylon(enum_type),
344 value: s.clone(),
345 },
346 other => cached_to_value(other),
347 },
348 JsonMemberKind::Tuple { type_name, members } => decode_json_tuple(value, type_name.as_deref(), Some(members)),
349 }
350}
351
352fn decode_enum(value: &DecodedValue, position: usize, enum_type: &str) -> Value {
353 let raw = match value {
356 DecodedValue::Composite(_) => composite_at(value, position),
357 other => other.clone(),
358 };
359 match raw {
360 DecodedValue::Null => Value::Null,
361 DecodedValue::Str(s) => Value::Enum {
362 type_name: pg_schema_qualified_to_pylon(enum_type),
363 value: s,
364 },
365 other => cached_to_value(&other),
366 }
367}
368
369fn decode_group(
370 value: &DecodedValue,
371 key_nodes: &[ShapeNode],
372 grouping_position: usize,
373 elements_position: usize,
374 element: &ShapeNode,
375) -> Value {
376 let key_fields: Vec<(String, Value)> = key_nodes
377 .iter()
378 .map(|kn| (pointer_name(kn).to_string(), decode_inner(kn, value, None)))
379 .collect();
380 let key = Object {
381 type_name: None,
382 fields: key_fields,
383 implicit_id: false,
384 };
385
386 let grouping = match composite_at(value, grouping_position) {
387 DecodedValue::Array(items) => items
388 .into_iter()
389 .filter_map(|v| match v {
390 DecodedValue::Str(s) => Some(s),
391 _ => None,
392 })
393 .collect(),
394 _ => vec![],
395 };
396
397 let elements = match composite_at(value, elements_position) {
398 DecodedValue::Array(items) => items.iter().map(|item| decode_inner(element, item, Some(0))).collect(),
399 _ => vec![],
400 };
401
402 Value::Group(Box::new(Group {
403 key,
404 grouping,
405 elements,
406 }))
407}
408
409fn decode_vector_search(
410 value: &DecodedValue,
411 object_position: usize,
412 distance_position: usize,
413 object_node: &ShapeNode,
414) -> Value {
415 let obj_tuple = composite_at(value, object_position);
416 let distance = as_f64(&composite_at(value, distance_position));
417 let object = decode_inner(object_node, &obj_tuple, Some(0));
418 Value::VectorSearch {
419 object: Box::new(object),
420 distance,
421 }
422}
423
424fn decode_fts_search(
425 value: &DecodedValue,
426 object_position: usize,
427 rank_position: usize,
428 object_node: &ShapeNode,
429) -> Value {
430 let obj_tuple = composite_at(value, object_position);
431 let score = as_f64(&composite_at(value, rank_position));
432 let object = decode_inner(object_node, &obj_tuple, Some(0));
433 Value::FtsSearch {
434 object: Box::new(object),
435 score,
436 }
437}
438
439fn as_f64(value: &DecodedValue) -> f64 {
440 match value {
441 DecodedValue::F64(f) => *f,
442 DecodedValue::I64(i) => *i as f64,
443 _ => 0.0,
444 }
445}
446
447pub(crate) fn cached_to_value(value: &DecodedValue) -> Value {
452 match value {
453 DecodedValue::Null => Value::Null,
454 DecodedValue::Bool(b) => Value::Bool(*b),
455 DecodedValue::I64(i) => Value::Int64(*i),
456 DecodedValue::F64(f) => Value::Float64(*f),
457 DecodedValue::Str(s) => Value::Str(s.clone()),
458 DecodedValue::Bytes(b) => Value::Bytes(b.clone()),
459 DecodedValue::Uuid(bytes) => Value::Uuid(uuid::Uuid::from_bytes(*bytes)),
460 DecodedValue::Decimal(s) => Value::Decimal(s.clone()),
461 DecodedValue::JsonNumber(digits) => Value::Float64(digits.parse().unwrap_or(f64::NAN)),
465 DecodedValue::Interval {
466 months,
467 days,
468 microseconds,
469 } => Value::Duration {
470 months: *months,
471 days: *days,
472 microseconds: *microseconds,
473 },
474 DecodedValue::Date(d) => Value::Date(*d),
475 DecodedValue::Time(t) => Value::Time(*t),
476 DecodedValue::Timestamp(t) => Value::Timestamp(*t),
477 DecodedValue::Timestamptz(t) => Value::Timestamptz(*t),
478 DecodedValue::Array(items) => Value::Array(items.iter().map(cached_to_value).collect()),
479 DecodedValue::Composite(items) => Value::Tuple(items.iter().map(cached_to_value).collect()),
480 DecodedValue::Object(fields) => Value::Object(Object {
481 type_name: None,
482 fields: fields.iter().map(|(k, v)| (k.clone(), cached_to_value(v))).collect(),
483 implicit_id: false,
484 }),
485 DecodedValue::Range {
486 lower,
487 upper,
488 inc_lower,
489 inc_upper,
490 empty,
491 } => Value::Range(Box::new(Range {
492 lower: lower.as_ref().map(|b| cached_to_value(b)),
493 upper: upper.as_ref().map(|b| cached_to_value(b)),
494 inc_lower: *inc_lower,
495 inc_upper: *inc_upper,
496 empty: *empty,
497 })),
498 }
499}
500
501#[cfg(test)]
502mod tests {
503 use super::*;
504 use pylon_core::query::Cardinality;
505
506 fn comp(items: Vec<DecodedValue>) -> DecodedValue {
507 DecodedValue::Composite(items)
508 }
509
510 #[test]
511 fn decodes_a_root_object_with_a_scalar_property() {
512 let value = comp(vec![
515 DecodedValue::Str("default::Person".into()),
516 DecodedValue::Str("Bob".into()),
517 ]);
518 let shape = ShapeNode::Object {
519 name: String::new(),
520 type_name: Some("default::Person".into()),
521 position: 0,
522 cardinality: Cardinality::Required,
523 pointers: vec![
524 ShapeNode::Scalar {
525 name: "__type__".into(),
526 position: 0,
527 },
528 ShapeNode::Scalar {
529 name: "name".into(),
530 position: 1,
531 },
532 ],
533 has_implicit_id: false,
534 };
535 let decoded = decode(&shape, &value);
536 let Value::Object(obj) = decoded else {
537 panic!("expected Object, got {decoded:?}")
538 };
539 assert_eq!(obj.type_name(), Some("default::Person"));
540 assert_eq!(obj.get("name"), Some(&Value::Str("Bob".into())));
541 assert_eq!(obj.get("__type__"), None);
544 assert_eq!(obj.len(), 1);
545 }
546
547 #[test]
552 fn an_implicit_id_decodes_as_a_field_but_is_left_out_of_json() {
553 let id = uuid::Uuid::from_u128(0x0199c3e1_9702_795c_b787_f93f7a88fd4c);
554 let value = comp(vec![
555 DecodedValue::Str("default::Person".into()),
556 DecodedValue::Uuid(*id.as_bytes()),
557 DecodedValue::Str("Bob".into()),
558 ]);
559 let shape = ShapeNode::Object {
560 name: String::new(),
561 type_name: Some("default::Person".into()),
562 position: 0,
563 cardinality: Cardinality::Required,
564 pointers: vec![
565 ShapeNode::Scalar {
566 name: "__type__".into(),
567 position: 0,
568 },
569 ShapeNode::Scalar {
570 name: "id".into(),
571 position: 1,
572 },
573 ShapeNode::Scalar {
574 name: "name".into(),
575 position: 2,
576 },
577 ],
578 has_implicit_id: true,
579 };
580
581 let decoded = decode(&shape, &value);
582 let Value::Object(obj) = &decoded else {
583 panic!("expected Object, got {decoded:?}")
584 };
585 assert_eq!(obj.get("id"), Some(&Value::Uuid(id)));
586 assert_eq!(obj.len(), 2);
587 assert_eq!(crate::json::to_json(&decoded), r#"{"name": "Bob"}"#);
588 }
589
590 #[test]
593 fn an_id_the_query_asked_for_is_rendered_as_json() {
594 let id = uuid::Uuid::from_u128(0x0199c3e1_9702_795c_b787_f93f7a88fd4c);
595 let value = comp(vec![
596 DecodedValue::Str("default::Person".into()),
597 DecodedValue::Uuid(*id.as_bytes()),
598 DecodedValue::Str("Bob".into()),
599 ]);
600 let shape = ShapeNode::Object {
601 name: String::new(),
602 type_name: Some("default::Person".into()),
603 position: 0,
604 cardinality: Cardinality::Required,
605 pointers: vec![
606 ShapeNode::Scalar {
607 name: "__type__".into(),
608 position: 0,
609 },
610 ShapeNode::Scalar {
611 name: "id".into(),
612 position: 1,
613 },
614 ShapeNode::Scalar {
615 name: "name".into(),
616 position: 2,
617 },
618 ],
619 has_implicit_id: false,
620 };
621
622 let decoded = decode(&shape, &value);
623 assert_eq!(
624 crate::json::to_json(&decoded),
625 r#"{"id": "0199c3e1-9702-795c-b787-f93f7a88fd4c", "name": "Bob"}"#
626 );
627 }
628
629 #[test]
630 fn a_free_objects_first_field_is_read_at_its_position() {
631 let value = comp(vec![comp(vec![
634 DecodedValue::Str("default::Address".into()),
635 DecodedValue::Str("Main St".into()),
636 ])]);
637 let shape = ShapeNode::Object {
638 name: String::new(),
639 type_name: None,
640 position: 0,
641 cardinality: Cardinality::Many,
642 pointers: vec![ShapeNode::Object {
643 name: "b".into(),
644 type_name: Some("default::Address".into()),
645 position: 0,
646 cardinality: Cardinality::Optional,
647 pointers: vec![
648 ShapeNode::Scalar {
649 name: "__type__".into(),
650 position: 0,
651 },
652 ShapeNode::Scalar {
653 name: "address".into(),
654 position: 1,
655 },
656 ],
657 has_implicit_id: false,
658 }],
659 has_implicit_id: false,
660 };
661 let Value::Object(root) = decode(&shape, &value) else {
662 panic!()
663 };
664 let Some(Value::Object(b)) = root.get("b") else {
665 panic!("{root:?}")
666 };
667 assert_eq!(b.get("address"), Some(&Value::Str("Main St".into())));
668 }
669
670 #[test]
671 fn a_name_two_splats_share_keeps_the_value_it_has() {
672 let value = comp(vec![
675 DecodedValue::Str("default::A".into()),
676 DecodedValue::Str("kept".into()),
677 DecodedValue::Null,
678 ]);
679 let shape = ShapeNode::Object {
680 name: String::new(),
681 type_name: Some("default::A".into()),
682 position: 0,
683 cardinality: Cardinality::Required,
684 pointers: vec![
685 ShapeNode::Scalar {
686 name: "__type__".into(),
687 position: 0,
688 },
689 ShapeNode::Scalar {
690 name: "identifier".into(),
691 position: 1,
692 },
693 ShapeNode::Scalar {
694 name: "identifier".into(),
695 position: 2,
696 },
697 ],
698 has_implicit_id: false,
699 };
700 let Value::Object(obj) = decode(&shape, &value) else {
701 panic!("expected Object")
702 };
703 assert_eq!(obj.get("identifier"), Some(&Value::Str("kept".into())));
704 assert_eq!(obj.len(), 1);
705 }
706
707 #[test]
708 fn polymorphic_object_uses_the_actual_row_type_not_the_static_one() {
709 let value = comp(vec![DecodedValue::Str("default::Individual".into())]);
710 let shape = ShapeNode::Object {
711 name: String::new(),
712 type_name: Some("default::Account".into()),
713 position: 0,
714 cardinality: Cardinality::Required,
715 pointers: vec![ShapeNode::Scalar {
716 name: "__type__".into(),
717 position: 0,
718 }],
719 has_implicit_id: false,
720 };
721 let Value::Object(obj) = decode(&shape, &value) else {
722 panic!("expected Object")
723 };
724 assert_eq!(obj.type_name(), Some("default::Individual"));
725 }
726
727 #[test]
728 fn null_object_decodes_to_null() {
729 let value = comp(vec![DecodedValue::Null, DecodedValue::Null]);
730 let shape = ShapeNode::Object {
731 name: "employer".into(),
732 type_name: Some("default::Company".into()),
733 position: 1,
734 cardinality: Cardinality::Optional,
735 pointers: vec![],
736 has_implicit_id: false,
737 };
738 assert_eq!(decode(&shape, &value), Value::Null);
739 }
740
741 #[test]
742 fn decodes_a_nested_array_of_objects() {
743 let item = comp(vec![
744 DecodedValue::Str("default::Tag".into()),
745 DecodedValue::Str("rust".into()),
746 ]);
747 let value = comp(vec![
748 DecodedValue::Str("default::Post".into()),
749 DecodedValue::Array(vec![item]),
750 ]);
751 let shape = ShapeNode::Array {
752 name: "tags".into(),
753 position: 1,
754 element: Box::new(ShapeNode::Object {
755 name: String::new(),
756 type_name: Some("default::Tag".into()),
757 position: 0,
758 cardinality: Cardinality::Many,
759 pointers: vec![
760 ShapeNode::Scalar {
761 name: "__type__".into(),
762 position: 0,
763 },
764 ShapeNode::Scalar {
765 name: "name".into(),
766 position: 1,
767 },
768 ],
769 has_implicit_id: false,
770 }),
771 };
772 let Value::Array(items) = decode(&shape, &value) else {
773 panic!("expected Array")
774 };
775 assert_eq!(items.len(), 1);
776 let Value::Object(tag) = &items[0] else {
777 panic!("expected Object element")
778 };
779 assert_eq!(tag.get("name"), Some(&Value::Str("rust".into())));
780 }
781
782 #[test]
783 fn an_unset_array_property_decodes_to_null() {
784 let value = comp(vec![DecodedValue::Str("default::Person".into()), DecodedValue::Null]);
785 let shape = ShapeNode::Array {
786 name: "roles".into(),
787 position: 1,
788 element: Box::new(ShapeNode::Enum {
789 name: String::new(),
790 position: 0,
791 enum_type: "default::Role".into(),
792 }),
793 };
794 assert_eq!(decode(&shape, &value), Value::Null);
795 }
796
797 #[test]
798 fn enum_translates_public_schema_back_to_default_module() {
799 let value = comp(vec![DecodedValue::Str("Male".into())]);
800 let shape = ShapeNode::Enum {
801 name: "gender".into(),
802 position: 0,
803 enum_type: "public::Gender".into(),
804 };
805 assert_eq!(
806 decode(&shape, &value),
807 Value::Enum {
808 type_name: "default::Gender".into(),
809 value: "Male".into()
810 },
811 );
812 }
813
814 #[test]
815 fn enum_in_a_non_default_module_is_left_unqualified_untranslated() {
816 let value = comp(vec![DecodedValue::Str("Active".into())]);
817 let shape = ShapeNode::Enum {
818 name: "status".into(),
819 position: 0,
820 enum_type: "billing::Status".into(),
821 };
822 assert_eq!(
823 decode(&shape, &value),
824 Value::Enum {
825 type_name: "billing::Status".into(),
826 value: "Active".into()
827 },
828 );
829 }
830
831 #[test]
832 fn raw_scalar_and_json_scalar_pass_the_value_through_directly() {
833 let value = DecodedValue::Array(vec![DecodedValue::I64(1), DecodedValue::I64(2)]);
834 assert_eq!(
835 decode(&ShapeNode::RawScalar, &value),
836 Value::Array(vec![Value::Int64(1), Value::Int64(2)])
837 );
838 assert_eq!(
839 decode(&ShapeNode::JsonScalar, &value),
840 Value::Array(vec![Value::Int64(1), Value::Int64(2)])
841 );
842 }
843
844 #[test]
845 fn decodes_a_positional_structural_tuple() {
846 let value = comp(vec![DecodedValue::I64(1), DecodedValue::I64(2)]);
847 let shape = ShapeNode::Tuple {
848 name: String::new(),
849 position: 0,
850 elements: vec![
851 ShapeNode::Scalar {
852 name: String::new(),
853 position: 0,
854 },
855 ShapeNode::Scalar {
856 name: String::new(),
857 position: 1,
858 },
859 ],
860 names: None,
861 type_name: None,
862 };
863 assert_eq!(
864 decode(&shape, &value),
865 Value::Tuple(vec![Value::Int64(1), Value::Int64(2)])
866 );
867 }
868
869 #[test]
870 fn decodes_a_registered_named_tuple_from_jsonb() {
871 let raw = DecodedValue::Object(vec![
872 ("x".into(), DecodedValue::F64(1.0)),
873 ("y".into(), DecodedValue::F64(2.0)),
874 ]);
875 let value = comp(vec![DecodedValue::Str("default::Person".into()), raw]);
876 let shape = ShapeNode::NamedTuple {
877 name: "location".into(),
878 position: 1,
879 type_name: Some("default::Point".into()),
880 members: Some(vec![
881 JsonMember {
882 key: Some("x".into()),
883 kind: JsonMemberKind::Scalar,
884 },
885 JsonMember {
886 key: Some("y".into()),
887 kind: JsonMemberKind::Scalar,
888 },
889 ]),
890 is_free_object: false,
891 };
892 let Value::Object(point) = decode(&shape, &value) else {
893 panic!("expected Object")
894 };
895 assert_eq!(point.type_name(), Some("default::Point"));
896 assert_eq!(point.get("x"), Some(&Value::Float64(1.0)));
897 assert_eq!(point.get("y"), Some(&Value::Float64(2.0)));
898 }
899
900 #[test]
901 fn decodes_an_unregistered_structural_named_tuple_member_enum() {
902 let raw = DecodedValue::Object(vec![("gender".into(), DecodedValue::Str("Female".into()))]);
903 let shape = ShapeNode::NamedTuple {
904 name: String::new(),
905 position: 0,
906 type_name: None,
907 members: Some(vec![JsonMember {
908 key: Some("gender".into()),
909 kind: JsonMemberKind::Enum {
910 enum_type: "public::Gender".into(),
911 },
912 }]),
913 is_free_object: false,
914 };
915 let Value::Object(obj) = decode(&shape, &raw) else {
916 panic!("expected Object")
917 };
918 assert_eq!(obj.type_name(), None);
919 assert_eq!(
920 obj.get("gender"),
921 Some(&Value::Enum {
922 type_name: "default::Gender".into(),
923 value: "Female".into()
924 }),
925 );
926 }
927
928 #[test]
929 fn decodes_group_result() {
930 let value = comp(vec![
931 DecodedValue::Str("Male".into()),
932 DecodedValue::Array(vec![DecodedValue::Str("gender".into())]),
933 DecodedValue::Array(vec![comp(vec![
934 DecodedValue::Str("default::Person".into()),
935 DecodedValue::Str("Bob".into()),
936 ])]),
937 ]);
938 let shape = ShapeNode::Group {
939 key_nodes: vec![ShapeNode::Scalar {
940 name: "gender".into(),
941 position: 0,
942 }],
943 grouping_position: 1,
944 elements_position: 2,
945 element: Box::new(ShapeNode::Object {
946 name: String::new(),
947 type_name: Some("default::Person".into()),
948 position: 0,
949 cardinality: Cardinality::Many,
950 pointers: vec![
951 ShapeNode::Scalar {
952 name: "__type__".into(),
953 position: 0,
954 },
955 ShapeNode::Scalar {
956 name: "name".into(),
957 position: 1,
958 },
959 ],
960 has_implicit_id: false,
961 }),
962 };
963 let Value::Group(group) = decode(&shape, &value) else {
964 panic!("expected Group")
965 };
966 assert_eq!(group.key.get("gender"), Some(&Value::Str("Male".into())));
967 assert_eq!(group.grouping, vec!["gender".to_string()]);
968 assert_eq!(group.elements.len(), 1);
969 let Value::Object(person) = &group.elements[0] else {
970 panic!("expected Object element")
971 };
972 assert_eq!(person.get("name"), Some(&Value::Str("Bob".into())));
973 }
974
975 #[test]
976 fn decodes_vector_search_result() {
977 let obj = comp(vec![
978 DecodedValue::Str("default::Product".into()),
979 DecodedValue::Str("Widget".into()),
980 ]);
981 let value = comp(vec![DecodedValue::Null, obj, DecodedValue::F64(0.25)]);
982 let shape = ShapeNode::VectorSearch {
983 object_position: 1,
984 distance_position: 2,
985 object_node: Box::new(ShapeNode::Object {
986 name: "object".into(),
987 type_name: Some("default::Product".into()),
988 position: 1,
989 cardinality: Cardinality::Many,
990 pointers: vec![
991 ShapeNode::Scalar {
992 name: "__type__".into(),
993 position: 0,
994 },
995 ShapeNode::Scalar {
996 name: "name".into(),
997 position: 1,
998 },
999 ],
1000 has_implicit_id: false,
1001 }),
1002 };
1003 let Value::VectorSearch { object, distance } = decode(&shape, &value) else {
1004 panic!("expected VectorSearch")
1005 };
1006 assert_eq!(distance, 0.25);
1007 let Value::Object(product) = *object else {
1008 panic!("expected Object")
1009 };
1010 assert_eq!(product.get("name"), Some(&Value::Str("Widget".into())));
1011 }
1012
1013 #[test]
1014 fn cached_to_value_converts_a_range() {
1015 let value = DecodedValue::Range {
1016 lower: Some(Box::new(DecodedValue::I64(1))),
1017 upper: Some(Box::new(DecodedValue::I64(10))),
1018 inc_lower: true,
1019 inc_upper: false,
1020 empty: false,
1021 };
1022 let Value::Range(range) = cached_to_value(&value) else {
1023 panic!("expected Range")
1024 };
1025 assert_eq!(range.lower, Some(Value::Int64(1)));
1026 assert_eq!(range.upper, Some(Value::Int64(10)));
1027 assert!(range.inc_lower && !range.inc_upper && !range.empty);
1028 }
1029}