Skip to main content

gpui_query/core/
select.rs

1//! Select/transform support for projecting query data into derived views.
2//!
3//! This module provides [`SelectTransform`] and [`MappedQueryResource`], which
4//! together implement the "select" pattern found in TanStack Query: a query
5//! resource holds raw data of type `T`, and a `MappedQueryResource` applies a
6//! `SelectTransform<T, U>` to project it into type `U` without duplicating the
7//! underlying cache entry.
8//!
9//! # Hook integration
10//!
11//! The [`use_query_select`] hook wires a `SelectTransform` into the `use_query`
12//! lifecycle. It returns a `MappedQueryResource` entity that is kept in sync
13//! with the underlying `QueryResource` via an observer. Each time the source
14//! resource changes (fetch completes, cache hit, etc.), the mapped resource
15//! re-reads the source data and the transform is applied on access.
16//!
17//! # Example
18//!
19//! ```
20//! use gpui_query::core::{SelectTransform, MappedQueryResource};
21//!
22//! // Raw query data: a list of users.
23//! let users = vec!["Alice", "Bob", "Carol"];
24//!
25//! // Transform: extract just the count.
26//! let transform = SelectTransform::new(|users: &Vec<&str>| users.len());
27//!
28//! let mapped = MappedQueryResource::<_, usize, ()>::new(Some(std::sync::Arc::new(users)), transform);
29//! assert_eq!(mapped.data(), Some(3));
30//! ```
31//!
32//! ## Example with the hook
33//!
34//! ```ignore
35//! use gpui_query::hook::{use_query_select, QueryOptions};
36//! use gpui_query::core::SelectTransform;
37//! # #[derive(Clone, PartialEq)]
38//! # struct User;
39//! # #[derive(Clone, Debug)]
40//! # struct MyError;
41//!
42//! struct UserCountView {
43//!     mapped: gpui::Entity<gpui_query::core::MappedQueryResource<Vec<User>, usize, MyError>>,
44//!     _subs: (gpui::Subscription, gpui::Subscription),
45//! }
46//!
47//! impl UserCountView {
48//!     fn new(cx: &mut gpui::Context<Self>) -> Self {
49//!         let count_transform = SelectTransform::new(|users: &Vec<User>| users.len());
50//!         let (mapped, _, _subs) = use_query_select(
51//!             QueryOptions::new("users"),
52//!             count_transform,
53//!             |signal| async move {
54//!                 // Your async fetcher here
55//!                 Ok(vec![])
56//!             },
57//!             cx,
58//!         );
59//!         Self { mapped, _subs }
60//!     }
61//! }
62//! ```
63
64use std::sync::Arc;
65
66/// A select transform that maps cached data of type `T` to output type `U`.
67///
68/// Stored as `Arc<dyn Fn(&T) -> U>` to be `Clone + Send + Sync`. Use this
69/// with [`MappedQueryResource`] to derive a projected view from cached query
70/// data without storing a separate copy.
71///
72/// # Example
73///
74/// ```
75/// use gpui_query::core::SelectTransform;
76///
77/// let uppercase = SelectTransform::new(|name: &String| name.to_uppercase());
78/// assert_eq!(uppercase.apply(&"hello".to_string()), "HELLO");
79/// ```
80pub struct SelectTransform<T, U> {
81    transform: Arc<dyn Fn(&T) -> U + Send + Sync>,
82    _marker: std::marker::PhantomData<(T, U)>,
83}
84
85impl<T, U> Clone for SelectTransform<T, U> {
86    fn clone(&self) -> Self {
87        Self {
88            transform: Arc::clone(&self.transform),
89            _marker: std::marker::PhantomData,
90        }
91    }
92}
93
94impl<T, U> std::fmt::Debug for SelectTransform<T, U> {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        f.debug_struct("SelectTransform").finish()
97    }
98}
99
100impl<T, U> PartialEq for SelectTransform<T, U> {
101    fn eq(&self, other: &Self) -> bool {
102        // Closures have no PartialEq; compare by shared pointer identity,
103        // mirroring QuerySignal's Arc::ptr_eq approach (signal.rs).
104        Arc::ptr_eq(&self.transform, &other.transform)
105    }
106}
107
108impl<T, U> Eq for SelectTransform<T, U> {}
109
110impl<T, U> SelectTransform<T, U> {
111    /// Create a new select transform from a closure.
112    pub fn new(transform: impl Fn(&T) -> U + Send + Sync + 'static) -> Self {
113        Self {
114            transform: Arc::new(transform),
115            _marker: std::marker::PhantomData,
116        }
117    }
118
119    /// Apply the transform to data.
120    pub fn apply(&self, data: &T) -> U {
121        (self.transform)(data)
122    }
123}
124
125/// A mapped view over a `QueryResource` that applies a [`SelectTransform`].
126///
127/// This implements the "select" pattern: multiple consumers can derive
128/// different views from the same underlying cached data, each with their own
129/// `MappedQueryResource` holding a different transform function. The source
130/// data is shared, so there is no duplication.
131///
132/// # Type parameters
133///
134/// - `T`: The source data type (the cached query result).
135/// - `U`: The projected output type (the derived view).
136/// - `E`: The error type (carried through for API consistency).
137///
138/// # Storage
139///
140/// Source data is held as `Option<Arc<T>>` (audit #20) so that cloning a
141/// `MappedQueryResource` (e.g. for derived views) is a cheap `Arc::clone`
142/// rather than a full copy of `T`. `Arc<T>` is `Send + Sync` exactly when `T`
143/// is, so the existing bounds are preserved.
144#[derive(Clone, Debug, PartialEq, Eq)]
145pub struct MappedQueryResource<T, U, E> {
146    source_data: Option<Arc<T>>,
147    transform: SelectTransform<T, U>,
148    _error_marker: std::marker::PhantomData<E>,
149}
150
151impl<T, U, E> MappedQueryResource<T, U, E> {
152    /// Create a new mapped resource.
153    ///
154    /// Takes ownership of the source data as `Option<Arc<T>>` (audit #20). For
155    /// the common case of constructing from a plain `T`, wrap it with
156    /// `Some(Arc::new(t))`.
157    pub fn new(source_data: Option<Arc<T>>, transform: SelectTransform<T, U>) -> Self {
158        Self {
159            source_data,
160            transform,
161            _error_marker: std::marker::PhantomData,
162        }
163    }
164
165    /// Apply the transform to get the selected data.
166    ///
167    /// **Note (audit #3):** This re-applies the transform closure on every call.
168    /// `MappedQueryResource` is a derived view with no separate output cache — it
169    /// stores only the source data and the transform function. If the transform is
170    /// expensive and you need the result multiple times in a single render pass
171    /// (e.g., once for display and once for an equality check), cache the result
172    /// in a local variable:
173    ///
174    /// ```
175    /// use gpui_query::core::{MappedQueryResource, SelectTransform};
176    ///
177    /// let transform = SelectTransform::new(|v: &Vec<i32>| v.len());
178    /// let mapped = MappedQueryResource::<_, usize, ()>::new(Some(std::sync::Arc::new(vec![1, 2, 3])), transform);
179    /// let data = mapped.data(); // transform runs once
180    /// assert_eq!(data, Some(3));
181    /// // use `data` freely below
182    /// ```
183    ///
184    /// For lightweight transforms (field access, counting, simple projections) the
185    /// cost is negligible and no caching is needed.
186    pub fn data(&self) -> Option<U> {
187        // `source_data` is `Option<Arc<T>>`; deref the Arc so the transform
188        // still receives `&T` as documented (audit #20).
189        self.source_data
190            .as_ref()
191            .map(|d| self.transform.apply(d.as_ref()))
192    }
193
194    /// Whether source data exists.
195    pub fn has_data(&self) -> bool {
196        self.source_data.is_some()
197    }
198
199    /// Read-only access to the source data.
200    ///
201    /// Returns `Option<&T>` by dereferencing the stored `Arc<T>` (audit #20).
202    /// Keeping the `&T` return type (rather than `&Arc<T>`) is the least
203    /// disruptive choice: existing callers compare the pointed-to `T` and do
204    /// not need to change. Used by the hook layer to detect when the source
205    /// has changed before re-storing.
206    pub fn source_data(&self) -> Option<&T> {
207        self.source_data.as_ref().map(|arc| arc.as_ref())
208    }
209
210    /// Cheaply hand out the cached source `Arc<T>` as an owned value.
211    ///
212    /// Returns `Option<Arc<T>>` via a refcount bump (`Arc::clone`) — no `T`
213    /// clone. Audit H1: the hook layer uses this to compare the cached source
214    /// against a fresh read WITHOUT cloning `T` on unchanged notifications.
215    /// Because the returned `Arc<T>` is owned, the mapped borrow ends with
216    /// this call, so a subsequent `entity.read_with` does not create the
217    /// nested borrow that audit #115 removed.
218    pub fn source_arc(&self) -> Option<Arc<T>> {
219        self.source_data.clone()
220    }
221
222    /// Update the source data from the underlying query resource.
223    ///
224    /// Call this when the source `QueryResource` changes (fetch completes,
225    /// cache invalidation, etc.) to keep the mapped view in sync. The transform
226    /// is not applied here — it is applied lazily when [`data()`](Self::data)
227    /// is called. Takes `Option<Arc<T>>` so callers can hand over a cheap
228    /// `Arc::clone` instead of cloning the full `T` (audit #20).
229    pub fn update_source(&mut self, data: Option<Arc<T>>) {
230        self.source_data = data;
231    }
232}