Skip to main content

pylon_client/
value.rs

1//
2// This source file is part of the Pylon open source project.
3//
4// Copyright (c) 2026 Jaldis B.V.
5//
6// Licensed under the MIT OR Apache-2.0 license (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10//     https://opensource.org/licenses/MIT
11//     https://www.apache.org/licenses/LICENSE-2.0
12//
13// Unless required by applicable law or agreed to in writing, software
14// distributed under the License is distributed on an "AS IS" BASIS,
15// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16// See the License for the specific language governing permissions and
17// limitations under the License.
18//
19
20//! The crate's generic result value — Rust has no equivalent of the
21//! per-type dataclasses `pylon-py` hydrates results into, so a query result
22//! decodes into this instead: a generic, dynamically-typed value rather
23//! than a per-type generated struct.
24
25use std::ops::Index;
26
27/// A decoded query result value. One variant per `ShapeNode`/`DecodedValue`
28/// kind `decode.rs` knows how to produce — see that module for the walk
29/// that builds these.
30#[derive(Debug, Clone, PartialEq)]
31pub enum Value {
32    Null,
33    Bool(bool),
34    Int64(i64),
35    Float64(f64),
36    Str(String),
37    Bytes(Vec<u8>),
38    Uuid(uuid::Uuid),
39    /// Arbitrary-precision decimal, in its canonical string form — matches
40    /// `pylon_value::DecodedValue::Decimal`'s own representation, since
41    /// there's no single obviously-correct native Rust decimal type to
42    /// commit this generic client to.
43    Decimal(String),
44    /// A PostgreSQL `interval` (`std::duration` / `cal::relative_duration`)
45    /// — kept as its three raw wire components, same reasoning as
46    /// `DecodedValue::Interval`.
47    Duration {
48        months: i32,
49        days: i32,
50        microseconds: i64,
51    },
52    /// Whole days since the PG epoch (2000-01-01). Backs `cal::local_date`.
53    Date(i32),
54    /// Microseconds since midnight. Backs `cal::local_time`.
55    Time(i64),
56    /// Microseconds since the PG epoch, no timezone. Backs `cal::local_datetime`.
57    Timestamp(i64),
58    /// Microseconds since the PG epoch, UTC. Backs `std::datetime`.
59    Timestamptz(i64),
60    Range(Box<Range>),
61    /// A genuine Postgres array.
62    Array(Vec<Value>),
63    /// An anonymous positional tuple (`tuple<...>` with no member names).
64    Tuple(Vec<Value>),
65    /// A schema object or a free/named-tuple object — see `Object`.
66    Object(Object),
67    /// An enum value, hydrated to its qualified type name + variant label
68    /// rather than a generated Rust enum (there's no per-schema-type codegen
69    /// on this client, matching the generic-`Object` design as a whole).
70    Enum {
71        type_name: String,
72        value: String,
73    },
74    /// Result of a `group` statement.
75    Group(Box<Group>),
76    /// Result of a `vector::search` statement.
77    VectorSearch {
78        object: Box<Value>,
79        distance: f64,
80    },
81    /// Result of an `fts::search` statement.
82    FtsSearch {
83        object: Box<Value>,
84        score: f64,
85    },
86}
87
88/// A schema object, a free object literal, or a named tuple — all three
89/// decode to the same by-name field-access shape. `type_name` is `Some`
90/// only for a real schema type (including the concrete type of a
91/// polymorphic interface query result) or a registered named tuple;
92/// `None` for a free object (`select { a := 1 }`) or an unregistered
93/// structural named tuple.
94#[derive(Debug, Clone, PartialEq, Default)]
95pub struct Object {
96    pub(crate) type_name: Option<String>,
97    /// Field order matches the order the query's shape declared them in.
98    pub(crate) fields: Vec<(String, Value)>,
99    /// True when `fields[0]` is the `id` the compiler added to a shape that
100    /// did not select one — see `pylon_core::query::ShapeNode::Object`.
101    pub(crate) implicit_id: bool,
102}
103
104impl Object {
105    pub fn type_name(&self) -> Option<&str> {
106        self.type_name.as_deref()
107    }
108
109    /// Every field, the implicit `id` included — what a caller decoding into
110    /// their own type reads, and what makes `o.id` work on a shape that only
111    /// named other pointers.
112    pub fn fields(&self) -> impl Iterator<Item = (&str, &Value)> {
113        self.fields.iter().map(|(n, v)| (n.as_str(), v))
114    }
115
116    /// The fields JSON output carries — the same, less an `id` nobody asked
117    /// for. A rendered document holds exactly what the shape named.
118    pub fn json_fields(&self) -> impl Iterator<Item = (&str, &Value)> {
119        let skip = usize::from(self.implicit_id);
120        self.fields.iter().skip(skip).map(|(n, v)| (n.as_str(), v))
121    }
122
123    pub fn get(&self, name: &str) -> Option<&Value> {
124        self.fields.iter().find(|(n, _)| n == name).map(|(_, v)| v)
125    }
126
127    pub fn len(&self) -> usize {
128        self.fields.len()
129    }
130
131    pub fn is_empty(&self) -> bool {
132        self.fields.is_empty()
133    }
134}
135
136/// Panics if `name` isn't a field on this object.
137impl Index<&str> for Object {
138    type Output = Value;
139
140    fn index(&self, name: &str) -> &Value {
141        self.get(name).unwrap_or_else(|| panic!("Object has no field {name:?}"))
142    }
143}
144
145/// A PostgreSQL range value. `lower`/`upper` are `None` for an unbounded
146/// side; `empty == true` means the whole range is empty (`lower`/`upper`
147/// are meaningless then, not "both unbounded").
148#[derive(Debug, Clone, PartialEq)]
149pub struct Range {
150    pub lower: Option<Value>,
151    pub upper: Option<Value>,
152    pub inc_lower: bool,
153    pub inc_upper: bool,
154    pub empty: bool,
155}
156
157/// Result of a `group` statement: one grouping key, the grouping label
158/// list, and the elements sharing that key.
159#[derive(Debug, Clone, PartialEq)]
160pub struct Group {
161    pub key: Object,
162    pub grouping: Vec<String>,
163    pub elements: Vec<Value>,
164}