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. Gel's JSON output leaves its own implicit id out too, so a query
118    /// ported from it renders the same document here.
119    pub fn json_fields(&self) -> impl Iterator<Item = (&str, &Value)> {
120        let skip = usize::from(self.implicit_id);
121        self.fields.iter().skip(skip).map(|(n, v)| (n.as_str(), v))
122    }
123
124    pub fn get(&self, name: &str) -> Option<&Value> {
125        self.fields.iter().find(|(n, _)| n == name).map(|(_, v)| v)
126    }
127
128    pub fn len(&self) -> usize {
129        self.fields.len()
130    }
131
132    pub fn is_empty(&self) -> bool {
133        self.fields.is_empty()
134    }
135}
136
137/// Panics if `name` isn't a field on this object.
138impl Index<&str> for Object {
139    type Output = Value;
140
141    fn index(&self, name: &str) -> &Value {
142        self.get(name).unwrap_or_else(|| panic!("Object has no field {name:?}"))
143    }
144}
145
146/// A PostgreSQL range value. `lower`/`upper` are `None` for an unbounded
147/// side; `empty == true` means the whole range is empty (`lower`/`upper`
148/// are meaningless then, not "both unbounded").
149#[derive(Debug, Clone, PartialEq)]
150pub struct Range {
151    pub lower: Option<Value>,
152    pub upper: Option<Value>,
153    pub inc_lower: bool,
154    pub inc_upper: bool,
155    pub empty: bool,
156}
157
158/// Result of a `group` statement: one grouping key, the grouping label
159/// list, and the elements sharing that key.
160#[derive(Debug, Clone, PartialEq)]
161pub struct Group {
162    pub key: Object,
163    pub grouping: Vec<String>,
164    pub elements: Vec<Value>,
165}