Skip to main content

type_bridge/
query.rs

1#![deny(missing_docs)]
2//! Owner-branded query sessions, bindings, predicates, and the one query
3//! facade (Flight 3: F3-01 foundation, F3-02 algebra, F3-03 singular shapes).
4
5use std::marker::PhantomData;
6use std::sync::Arc;
7use std::sync::atomic::{AtomicU64, Ordering};
8
9use type_bridge_contract::codec::from_canonical_json;
10use type_bridge_contract::decimal::parse_decimal;
11use type_bridge_contract::id::TypeId;
12use type_bridge_contract::query_plan::CompatibilityValueV2;
13use type_bridge_contract::temporal::{
14    CanonicalDate, CanonicalDateTime, CanonicalDateTimeTz, CanonicalDuration,
15};
16use type_bridge_orm::match_request::handles::{
17    BindingHandle as OrmBindingHandle, FieldHandle as OrmFieldHandle,
18    OrderHandle as OrmOrderHandle, PredicateHandle as OrmPredicateHandle,
19    QueryHandle as OrmQueryHandle, SelectionHandle as OrmSelectionHandle,
20    SessionHandle as OrmSessionHandle, ShapeHandle as OrmShapeHandle,
21};
22use type_bridge_orm::match_request::model::{
23    ComparisonOp, MissingOrder, RowCardinality, SortDirection, Window,
24};
25use type_bridge_orm::match_request::result::{
26    HydratedThing, MatchResult, MatchRow, SlotValue, ValidatedMatchResult,
27};
28use type_bridge_orm::match_request::validation::ValidatedMatchRequest;
29use type_bridge_orm::{
30    AttributeValue, DescriptorRegistry, DynamicEntityRow, DynamicRelationRow, DynamicRolePlayer,
31    InstalledRuntimeProjection,
32};
33
34use crate::__codegen::{
35    CompleteModel, EncodedScalar, FieldToken, HydratedRow, HydrationCapability, Model, QueryValued,
36    RelationModel, RolePlayer, RoleToken, RoleTokenCompatible, SubtypeRootModel, ThingModel,
37    TypeToken, ValidationError,
38};
39use crate::entity_codec::{hydrate_entity, map_validation_error};
40use crate::error::{Error, ModelValidationPhase};
41use crate::relation_codec::hydrate_relation;
42use crate::schema::Schema;
43use crate::{Database, Result};
44
45#[cfg(test)]
46mod tests;
47
48static QUERY_SESSION_NONCE: AtomicU64 = AtomicU64::new(1);
49
50fn schema_not_bound() -> Error {
51    Error::model_validation(
52        ModelValidationPhase::Input,
53        "schema_not_bound",
54        vec![],
55        "database is not schema-bound",
56        None,
57    )
58}
59
60fn cross_session_handle() -> Error {
61    Error::model_validation(
62        ModelValidationPhase::Input,
63        "cross_session_handle",
64        vec![],
65        "binding belongs to a different query session",
66        None,
67    )
68}
69
70fn model_label(type_id_json: &'static str) -> Result<String> {
71    let id = from_canonical_json::<TypeId>(type_id_json.as_bytes()).map_err(|source| {
72        Error::model_validation(
73            ModelValidationPhase::Input,
74            "invalid_model_identity",
75            vec!["type".into()],
76            "generated model identity is not canonical",
77            Some(Box::new(source)),
78        )
79    })?;
80    Ok(id.label().as_str().to_owned())
81}
82
83fn parse_owns_identity(owns_id_json: &'static str) -> Result<(String, String)> {
84    let invalid = || {
85        Error::model_validation(
86            ModelValidationPhase::Input,
87            "invalid_field_identity",
88            vec!["type".into()],
89            "generated field identity is not canonical",
90            None,
91        )
92    };
93    let value: serde_json::Value = serde_json::from_str(owns_id_json).map_err(|_| invalid())?;
94    let attribute = value
95        .get("attribute")
96        .and_then(serde_json::Value::as_str)
97        .ok_or_else(invalid)?;
98    let owner = value
99        .get("owner")
100        .and_then(|owner| owner.get("label"))
101        .and_then(serde_json::Value::as_str)
102        .ok_or_else(invalid)?;
103    Ok((owner.to_owned(), attribute.to_owned()))
104}
105
106fn parse_role_identity(role_id_json: &'static str) -> Result<type_bridge_contract::id::RoleId> {
107    from_canonical_json::<type_bridge_contract::id::RoleId>(role_id_json.as_bytes()).map_err(
108        |source| {
109            Error::model_validation(
110                ModelValidationPhase::Input,
111                "invalid_role_identity",
112                vec!["type".into()],
113                "generated role identity is not canonical",
114                Some(Box::new(source)),
115            )
116        },
117    )
118}
119
120mod mode_sealed {
121    pub trait Sealed {}
122}
123
124/// Sealed marker for a binding's exact-versus-subtypes selection behavior.
125pub trait SelectionMode: mode_sealed::Sealed + 'static {}
126
127/// Exact-match selection: results materialize as the bound concrete model.
128#[derive(Clone, Copy, Debug)]
129pub struct Exact;
130impl mode_sealed::Sealed for Exact {}
131impl SelectionMode for Exact {}
132
133/// Subtype-inclusive selection: results materialize as the generated leaf or
134/// closed family associated with the bound root.
135#[derive(Clone, Copy, Debug)]
136pub struct Subtypes;
137impl mode_sealed::Sealed for Subtypes {}
138impl SelectionMode for Subtypes {}
139
140/// One isolated owner-branded query authoring session.
141///
142/// Every binding call allocates a fresh binding identity; bindings are
143/// lightweight `Copy` values valid only for the session that created them.
144/// Bindings from another session fail before I/O with
145/// `cross_session_handle`.
146pub struct QuerySession<'db, S: Schema> {
147    installed: &'db InstalledRuntimeProjection,
148    execution: QueryExecution<'db, S>,
149    session: OrmSessionHandle,
150    registry: Arc<DescriptorRegistry>,
151    nonce: u64,
152    bindings: Vec<OrmBindingHandle>,
153    marker: PhantomData<fn() -> S>,
154}
155
156enum QueryExecution<'db, S: Schema> {
157    Local(&'db Database<S>),
158    Borrowed(&'db type_bridge_orm::session::context::TransactionContext),
159    Remote(&'db crate::remote::RemoteDatabase<S>),
160}
161
162impl<S: Schema> std::fmt::Debug for QuerySession<'_, S> {
163    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        formatter
165            .debug_struct("QuerySession")
166            .field("session", &self.nonce)
167            .field("bindings", &self.bindings.len())
168            .finish_non_exhaustive()
169    }
170}
171
172/// Opaque session-scoped binding identity carried by `Copy` handles.
173#[doc(hidden)]
174#[derive(Clone, Copy, Debug, PartialEq, Eq)]
175pub struct BindingKey {
176    pub(crate) nonce: u64,
177    pub(crate) index: u32,
178}
179
180/// One schema/model-branded `Copy` binding token allocated by a
181/// [`QuerySession`]. Copying preserves the binding identity; only a fresh
182/// session binding call creates a new occurrence.
183pub struct Binding<S: Schema, M: ThingModel<Schema = S>, Mode: SelectionMode = Exact> {
184    key: BindingKey,
185    #[allow(clippy::type_complexity)]
186    marker: PhantomData<fn() -> (S, M, Mode)>,
187}
188
189impl<S: Schema, M: ThingModel<Schema = S>, Mode: SelectionMode> Copy for Binding<S, M, Mode> {}
190impl<S: Schema, M: ThingModel<Schema = S>, Mode: SelectionMode> Clone for Binding<S, M, Mode> {
191    fn clone(&self) -> Self {
192        *self
193    }
194}
195impl<S: Schema, M: ThingModel<Schema = S>, Mode: SelectionMode> PartialEq for Binding<S, M, Mode> {
196    fn eq(&self, other: &Self) -> bool {
197        self.key == other.key
198    }
199}
200impl<S: Schema, M: ThingModel<Schema = S>, Mode: SelectionMode> Eq for Binding<S, M, Mode> {}
201impl<S: Schema, M: ThingModel<Schema = S>, Mode: SelectionMode> std::fmt::Debug
202    for Binding<S, M, Mode>
203{
204    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205        formatter
206            .debug_struct("Binding")
207            .field("session", &self.key.nonce)
208            .field("index", &self.key.index)
209            .finish()
210    }
211}
212
213impl<S: Schema, M: ThingModel<Schema = S>, Mode: SelectionMode> Binding<S, M, Mode> {
214    pub(crate) fn key(self) -> BindingKey {
215        self.key
216    }
217
218    /// Select this binding as an owned collection per distinct page root,
219    /// preserving match multiplicity by default.
220    #[must_use]
221    pub fn collect(self) -> Collected<S, Self>
222    where
223        Self: Selectable<S>,
224    {
225        Collected {
226            selection: self,
227            distinct: false,
228            order: Vec::new(),
229        }
230    }
231}
232
233impl<S: Schema> Database<S> {
234    /// Start one owner-branded query authoring session over this
235    /// schema-bound database.
236    pub fn query(&self) -> Result<QuerySession<'_, S>> {
237        let registry = self.match_registry().ok_or_else(schema_not_bound)?;
238        let installed = self
239            .installed_schema()
240            .map(Arc::as_ref)
241            .ok_or_else(schema_not_bound)?;
242        Ok(QuerySession::new(
243            installed,
244            Arc::clone(registry),
245            QueryExecution::Local(self),
246        ))
247    }
248}
249
250impl<'db, S: Schema> QuerySession<'db, S> {
251    fn new(
252        installed: &'db InstalledRuntimeProjection,
253        registry: Arc<DescriptorRegistry>,
254        execution: QueryExecution<'db, S>,
255    ) -> Self {
256        Self {
257            installed,
258            execution,
259            session: OrmSessionHandle::new(Arc::clone(&registry)),
260            registry,
261            nonce: QUERY_SESSION_NONCE.fetch_add(1, Ordering::Relaxed),
262            bindings: Vec::new(),
263            marker: PhantomData,
264        }
265    }
266
267    pub(crate) fn borrowed(
268        installed: &'db InstalledRuntimeProjection,
269        registry: Arc<DescriptorRegistry>,
270        transaction: &'db type_bridge_orm::session::context::TransactionContext,
271    ) -> Self {
272        Self::new(installed, registry, QueryExecution::Borrowed(transaction))
273    }
274
275    pub(crate) fn remote(
276        installed: &'db InstalledRuntimeProjection,
277        registry: Arc<DescriptorRegistry>,
278        remote: &'db crate::remote::RemoteDatabase<S>,
279    ) -> Self {
280        Self::new(installed, registry, QueryExecution::Remote(remote))
281    }
282}
283
284impl<'db, S: Schema> QuerySession<'db, S> {
285    fn push_binding<M: ThingModel<Schema = S>, Mode: SelectionMode>(
286        &mut self,
287        handle: OrmBindingHandle,
288    ) -> Result<Binding<S, M, Mode>> {
289        let index = u32::try_from(self.bindings.len()).map_err(|source| {
290            Error::model_validation(
291                ModelValidationPhase::Input,
292                "too_many_bindings",
293                vec![],
294                "query session binding capacity exceeded",
295                Some(Box::new(source)),
296            )
297        })?;
298        self.bindings.push(handle);
299        Ok(Binding {
300            key: BindingKey {
301                nonce: self.nonce,
302                index,
303            },
304            marker: PhantomData,
305        })
306    }
307
308    /// Allocate a fresh exact-match binding for one concrete complete
309    /// generated model. Abstract models have no exact binding constructor.
310    pub fn exact<M>(&mut self) -> Result<Binding<S, M, Exact>>
311    where
312        M: ThingModel<Schema = S> + CompleteModel,
313    {
314        let label = model_label(M::TYPE_ID_JSON)?;
315        let handle = self.session.exact(&label).map_err(Error::from_orm)?;
316        self.push_binding(handle)
317    }
318
319    /// Allocate a fresh subtype-inclusive binding for one generated subtype
320    /// root; results materialize as the generated leaf or closed family.
321    pub fn subtypes<M>(&mut self) -> Result<Binding<S, M, Subtypes>>
322    where
323        M: ThingModel<Schema = S> + SubtypeRootModel,
324    {
325        let label = model_label(M::TYPE_ID_JSON)?;
326        let handle = self.session.subtypes(&label).map_err(Error::from_orm)?;
327        self.push_binding(handle)
328    }
329
330    pub(crate) fn handle_by_key(&self, key: BindingKey) -> Result<&OrmBindingHandle> {
331        if key.nonce != self.nonce {
332            return Err(cross_session_handle());
333        }
334        self.bindings
335            .get(key.index as usize)
336            .ok_or_else(cross_session_handle)
337    }
338
339    fn installed(&self) -> Result<&InstalledRuntimeProjection> {
340        Ok(self.installed)
341    }
342
343    fn field_name_for(&self, owner_label: &str, attribute_label: &str) -> Result<String> {
344        let descriptor = self.registry.get(owner_label).ok_or_else(|| {
345            Error::model_validation(
346                ModelValidationPhase::Input,
347                "unknown_field_owner",
348                vec!["type".into()],
349                format!("field owner '{owner_label}' is not registered in this session"),
350                None,
351            )
352        })?;
353        let found = match &descriptor {
354            type_bridge_orm::TypeDescriptorRef::Entity(entity) => entity
355                .owned_attributes
356                .iter()
357                .find(|attribute| attribute.attr_name == attribute_label)
358                .map(|attribute| attribute.field_name.clone()),
359            type_bridge_orm::TypeDescriptorRef::Relation(relation) => relation
360                .owned_attributes
361                .iter()
362                .find(|attribute| attribute.attr_name == attribute_label)
363                .map(|attribute| attribute.field_name.clone()),
364        };
365        found.ok_or_else(|| {
366            Error::model_validation(
367                ModelValidationPhase::Input,
368                "unknown_field",
369                vec!["type".into()],
370                format!("owner '{owner_label}' has no field for attribute '{attribute_label}'"),
371                None,
372            )
373        })
374    }
375
376    pub(crate) fn lower_field(
377        &self,
378        key: BindingKey,
379        owns_id_json: &'static str,
380    ) -> Result<OrmFieldHandle> {
381        let handle = self.handle_by_key(key)?;
382        let (owner_label, attribute_label) = parse_owns_identity(owns_id_json)?;
383        let field_name = self.field_name_for(&owner_label, &attribute_label)?;
384        handle
385            .field_owned_by(&owner_label, &field_name)
386            .map_err(Error::from_orm)
387    }
388
389    fn lower_order(&self, order: &Order<S>) -> Result<OrmOrderHandle> {
390        let field = self.lower_field(order.key, order.owns_id_json)?;
391        Ok(field.order(order.direction, order.missing))
392    }
393
394    fn lower_predicate(&self, expr: &PredicateExpr) -> Result<OrmPredicateHandle> {
395        match expr {
396            PredicateExpr::FieldValue {
397                binding,
398                owns_id_json,
399                operator,
400                value,
401            } => {
402                let field = self.lower_field(*binding, owns_id_json)?;
403                Ok(field.compare_value(*operator, value.clone()))
404            }
405            PredicateExpr::FieldField {
406                left_binding,
407                left_owns_id_json,
408                operator,
409                right_binding,
410                right_owns_id_json,
411            } => {
412                let left = self.lower_field(*left_binding, left_owns_id_json)?;
413                let right = self.lower_field(*right_binding, right_owns_id_json)?;
414                left.compare_field(*operator, &right)
415                    .map_err(Error::from_orm)
416            }
417            PredicateExpr::Connects {
418                relation,
419                role_id_json,
420                player,
421            } => {
422                let relation_handle = self.handle_by_key(*relation)?;
423                let role = parse_role_identity(role_id_json)?;
424                let role_handle = relation_handle
425                    .role_owned_by(role.declaring_relation().as_str(), role.label().as_str())
426                    .map_err(Error::from_orm)?;
427                let player_handle = self.handle_by_key(*player)?;
428                role_handle.connects(player_handle).map_err(Error::from_orm)
429            }
430            PredicateExpr::Reachable {
431                relation_type_id_json,
432                role_from_id_json,
433                role_to_id_json,
434                source,
435                target,
436                min_depth,
437                max_depth,
438            } => {
439                let relation = model_label(relation_type_id_json)?;
440                let role_from = parse_role_identity(role_from_id_json)?;
441                let role_to = parse_role_identity(role_to_id_json)?;
442                self.session
443                    .reachable(
444                        &relation,
445                        role_from.label().as_str(),
446                        role_to.label().as_str(),
447                        self.handle_by_key(*source)?,
448                        self.handle_by_key(*target)?,
449                        *min_depth,
450                        *max_depth,
451                    )
452                    .map_err(Error::from_orm)
453            }
454            PredicateExpr::And(terms) => self.lower_composed(terms, |left, right| {
455                left.and(right).map_err(Error::from_orm)
456            }),
457            PredicateExpr::Or(terms) => {
458                self.lower_composed(terms, |left, right| left.or(right).map_err(Error::from_orm))
459            }
460            PredicateExpr::Not(inner) => Ok(self.lower_predicate(inner)?.not()),
461        }
462    }
463
464    fn lower_composed(
465        &self,
466        terms: &[PredicateExpr],
467        combine: impl Fn(&OrmPredicateHandle, &OrmPredicateHandle) -> Result<OrmPredicateHandle>,
468    ) -> Result<OrmPredicateHandle> {
469        let mut lowered = terms.iter().map(|term| self.lower_predicate(term));
470        let mut combined = lowered.next().ok_or_else(|| {
471            Error::model_validation(
472                ModelValidationPhase::Input,
473                "empty_predicate",
474                vec![],
475                "boolean composition requires at least one predicate",
476                None,
477            )
478        })??;
479        for term in lowered {
480            combined = combine(&combined, &term?)?;
481        }
482        Ok(combined)
483    }
484
485    fn client_row_for(&self, thing: &HydratedThing) -> Result<HydratedRow> {
486        use type_bridge_orm::match_request::model::ThingKind as OrmThingKind;
487        let installed = self.installed()?;
488        let type_name = self
489            .registry
490            .descriptor_type_name(thing.concrete_descriptor())
491            .ok_or_else(|| {
492                Error::model_validation(
493                    ModelValidationPhase::Hydration,
494                    "invalid_installed_projection",
495                    vec!["type".into()],
496                    "selected concrete descriptor is absent from the installed registry",
497                    None,
498                )
499            })?;
500        let mut attributes = Vec::new();
501        for attribute in thing.attributes() {
502            let provider_name = self
503                .registry
504                .provider_attribute_name(attribute.field())
505                .ok_or_else(|| {
506                    Error::model_validation(
507                        ModelValidationPhase::Hydration,
508                        "invalid_installed_projection",
509                        vec![],
510                        "selected field identity has no provider attribute name",
511                        None,
512                    )
513                })?;
514            for value in attribute.values() {
515                attributes.push((
516                    provider_name.clone(),
517                    canonicalize_selected_value(value.clone())?,
518                ));
519            }
520        }
521        match thing.kind() {
522            OrmThingKind::Entity => {
523                let id = TypeId::new(type_bridge_contract::id::TypeKind::Entity, &type_name)
524                    .map_err(|source| {
525                        Error::model_validation(
526                            ModelValidationPhase::Hydration,
527                            "invalid_discovered_type",
528                            vec!["type".into()],
529                            "selected entity type label is invalid",
530                            Some(Box::new(source)),
531                        )
532                    })?;
533                hydrate_entity(
534                    DynamicEntityRow {
535                        iid: Some(thing.concept_id().as_str().to_owned()),
536                        type_name: Some(type_name),
537                        attributes,
538                    },
539                    &id,
540                    installed,
541                )
542            }
543            OrmThingKind::Relation => {
544                let id = TypeId::new(type_bridge_contract::id::TypeKind::Relation, &type_name)
545                    .map_err(|source| {
546                        Error::model_validation(
547                            ModelValidationPhase::Hydration,
548                            "invalid_discovered_type",
549                            vec!["type".into()],
550                            "selected relation type label is invalid",
551                            Some(Box::new(source)),
552                        )
553                    })?;
554                let mut role_players = Vec::new();
555                for role in thing.roles() {
556                    for player in role.players() {
557                        let mut raw = Vec::new();
558                        for attribute in player.attributes() {
559                            let provider_name = self
560                                .registry
561                                .provider_attribute_name(attribute.field())
562                                .ok_or_else(|| {
563                                    Error::model_validation(
564                                        ModelValidationPhase::Hydration,
565                                        "invalid_installed_projection",
566                                        vec![],
567                                        "player field identity has no provider attribute name",
568                                        None,
569                                    )
570                                })?;
571                            for value in attribute.values() {
572                                raw.push((
573                                    provider_name.clone(),
574                                    plain_json(&canonicalize_selected_value(value.clone())?),
575                                ));
576                            }
577                        }
578                        let player_type_name = self
579                            .registry
580                            .descriptor_type_name(player.concrete_descriptor())
581                            .ok_or_else(|| {
582                                Error::model_validation(
583                                    ModelValidationPhase::Hydration,
584                                    "invalid_installed_projection",
585                                    vec!["roles".into(), role.role().name.clone()],
586                                    "selected role-player descriptor is absent from the installed registry",
587                                    None,
588                                )
589                            })?;
590                        role_players.push(DynamicRolePlayer {
591                            role_name: role.role().name.clone(),
592                            player_iid: Some(player.concept_id().as_str().to_owned()),
593                            player_type_name: Some(player_type_name),
594                            attributes: raw,
595                        });
596                    }
597                }
598                hydrate_relation(
599                    DynamicRelationRow {
600                        iid: Some(thing.concept_id().as_str().to_owned()),
601                        type_name: Some(type_name),
602                        attributes,
603                        role_players,
604                    },
605                    &id,
606                    installed,
607                )
608            }
609        }
610    }
611}
612
613fn canonicalize_selected_value(value: AttributeValue) -> Result<AttributeValue> {
614    let malformed = || {
615        Error::model_validation(
616            ModelValidationPhase::Hydration,
617            "hydrated_attribute_value_type",
618            vec![],
619            "selected attribute value is outside its canonical scalar domain",
620            None,
621        )
622    };
623    match value {
624        AttributeValue::Date(value) => value
625            .parse::<CanonicalDate>()
626            .map(|value| AttributeValue::Date(value.to_string()))
627            .map_err(|_| malformed()),
628        AttributeValue::DateTime(value) => normalize_provider_fraction(value)
629            .parse::<CanonicalDateTime>()
630            .map(|value| AttributeValue::DateTime(value.to_string()))
631            .map_err(|_| malformed()),
632        AttributeValue::DateTimeTZ(value) => normalize_provider_datetime_tz(value)
633            .parse::<CanonicalDateTimeTz>()
634            .map(|value| AttributeValue::DateTimeTZ(value.to_string()))
635            .map_err(|_| malformed()),
636        AttributeValue::Decimal(value) => parse_decimal(&value)
637            .map(|value| AttributeValue::Decimal(value.canonical_string()))
638            .ok_or_else(malformed),
639        AttributeValue::Duration(value) => {
640            let value = normalize_provider_fraction(value);
641            match value.parse::<CanonicalDuration>() {
642                Ok(value) => Ok(AttributeValue::Duration(value.to_string())),
643                Err(_) => CompatibilityValueV2::released_duration(value.clone())
644                    .map(|_| AttributeValue::Duration(value))
645                    .map_err(|_| malformed()),
646            }
647        }
648        value => Ok(value),
649    }
650}
651
652fn normalize_provider_datetime_tz(value: String) -> String {
653    let mut normalized = normalize_provider_fraction(value);
654    for zero_offset in ["+00:00:00", "-00:00:00", "+00:00", "-00:00"] {
655        if normalized.ends_with(zero_offset) {
656            normalized.truncate(normalized.len() - zero_offset.len());
657            normalized.push('Z');
658            break;
659        }
660    }
661    normalized
662}
663
664fn normalize_provider_fraction(value: String) -> String {
665    let Some(dot) = value.find('.') else {
666        return value;
667    };
668    let fraction_end = value[dot + 1..]
669        .find(|character: char| !character.is_ascii_digit())
670        .map_or(value.len(), |offset| dot + 1 + offset);
671    let trimmed_end = value[dot + 1..fraction_end].trim_end_matches('0').len() + dot + 1;
672    if trimmed_end == fraction_end {
673        return value;
674    }
675    let mut normalized = String::with_capacity(value.len());
676    normalized.push_str(
677        &value[..if trimmed_end == dot + 1 {
678            dot
679        } else {
680            trimmed_end
681        }],
682    );
683    normalized.push_str(&value[fraction_end..]);
684    normalized
685}
686
687fn plain_json(value: &AttributeValue) -> serde_json::Value {
688    match value {
689        AttributeValue::String(value) => serde_json::Value::String(value.clone()),
690        AttributeValue::Long(value) => serde_json::Value::from(*value),
691        AttributeValue::Double(value) => {
692            serde_json::Number::from_f64(*value).map_or(serde_json::Value::Null, Into::into)
693        }
694        AttributeValue::Boolean(value) => serde_json::Value::Bool(*value),
695        AttributeValue::Date(value)
696        | AttributeValue::DateTime(value)
697        | AttributeValue::DateTimeTZ(value)
698        | AttributeValue::Decimal(value)
699        | AttributeValue::Duration(value) => serde_json::Value::String(value.clone()),
700    }
701}
702
703/// Sealed conversion from client literals and generated value wrappers into
704/// canonical query operands.
705pub trait QueryOperand: operand_sealed::Sealed {
706    #[doc(hidden)]
707    type Domain;
708
709    #[doc(hidden)]
710    fn into_operand(self) -> AttributeValue;
711}
712
713/// Sealed marker for canonically ordered query operands.
714pub trait OrderedOperand: QueryOperand {}
715
716mod operand_sealed {
717    pub trait Sealed {}
718}
719
720macro_rules! operand {
721    ($ty:ty, $domain:ty, $self_:ident => $convert:expr, ordered: $ordered:tt) => {
722        impl operand_sealed::Sealed for $ty {}
723        impl QueryOperand for $ty {
724            type Domain = $domain;
725
726            fn into_operand($self_) -> AttributeValue {
727                $convert
728            }
729        }
730        operand!(@ordered $ty, $ordered);
731    };
732    (@ordered $ty:ty, true) => {
733        impl OrderedOperand for $ty {}
734    };
735    (@ordered $ty:ty, false) => {};
736}
737
738fn encoded_query_operand(value: EncodedScalar) -> AttributeValue {
739    match value {
740        EncodedScalar::String(value) => AttributeValue::String(value),
741        EncodedScalar::Long(value) => AttributeValue::Long(value),
742        EncodedScalar::Double(value) => AttributeValue::Double(value.get()),
743        EncodedScalar::Boolean(value) => AttributeValue::Boolean(value),
744        EncodedScalar::Date(value) => AttributeValue::Date(value.as_str().to_owned()),
745        EncodedScalar::DateTime(value) => AttributeValue::DateTime(value.as_str().to_owned()),
746        EncodedScalar::DateTimeTz(value) => AttributeValue::DateTimeTZ(value.as_str().to_owned()),
747        EncodedScalar::Decimal(value) => AttributeValue::Decimal(value.as_str().to_owned()),
748        EncodedScalar::Duration(value) => AttributeValue::Duration(value.as_str().to_owned()),
749    }
750}
751
752impl<T: QueryValued> operand_sealed::Sealed for T {}
753impl<T: QueryValued> QueryOperand for T {
754    type Domain = T::Domain;
755
756    fn into_operand(self) -> AttributeValue {
757        encoded_query_operand(self.into_encoded_scalar())
758    }
759}
760
761impl OrderedOperand for i64 {}
762
763operand!(
764    crate::value::Text,
765    String,
766    self => AttributeValue::String(self.into_string()),
767    ordered: false
768);
769operand!(
770    crate::value::Double,
771    crate::__codegen::CanonicalDouble,
772    self => AttributeValue::Double(self.get()),
773    ordered: true
774);
775operand!(
776    crate::value::Decimal,
777    crate::__codegen::Decimal,
778    self => AttributeValue::Decimal(self.into_string()),
779    ordered: true
780);
781operand!(
782    crate::value::Date,
783    crate::__codegen::Date,
784    self => AttributeValue::Date(self.into_string()),
785    ordered: true
786);
787operand!(
788    crate::value::DateTime,
789    crate::__codegen::DateTime,
790    self => AttributeValue::DateTime(self.into_string()),
791    ordered: true
792);
793operand!(
794    crate::value::DateTimeTz,
795    crate::__codegen::DateTimeTz,
796    self => AttributeValue::DateTimeTZ(self.into_string()),
797    ordered: true
798);
799operand!(
800    crate::value::Duration,
801    crate::__codegen::Duration,
802    self => AttributeValue::Duration(self.into_string()),
803    ordered: true
804);
805
806#[derive(Clone, Debug, PartialEq)]
807pub(crate) enum PredicateExpr {
808    FieldValue {
809        binding: BindingKey,
810        owns_id_json: &'static str,
811        operator: ComparisonOp,
812        value: AttributeValue,
813    },
814    FieldField {
815        left_binding: BindingKey,
816        left_owns_id_json: &'static str,
817        operator: ComparisonOp,
818        right_binding: BindingKey,
819        right_owns_id_json: &'static str,
820    },
821    Connects {
822        relation: BindingKey,
823        role_id_json: &'static str,
824        player: BindingKey,
825    },
826    Reachable {
827        relation_type_id_json: &'static str,
828        role_from_id_json: &'static str,
829        role_to_id_json: &'static str,
830        source: BindingKey,
831        target: BindingKey,
832        min_depth: u8,
833        max_depth: u8,
834    },
835    And(Vec<PredicateExpr>),
836    Or(Vec<PredicateExpr>),
837    Not(Box<PredicateExpr>),
838}
839
840/// One schema-branded, composable query predicate.
841///
842/// Operators are domain-restricted at construction; predicates compose with
843/// `&`, `|`, and `!` (or the named [`Predicate::and`], [`Predicate::or`],
844/// and [`Predicate::not`]) and are validated against the owning session
845/// before any executor invocation.
846#[derive(Debug, PartialEq)]
847pub struct Predicate<S: Schema> {
848    pub(crate) expr: PredicateExpr,
849    marker: PhantomData<fn() -> S>,
850}
851
852impl<S: Schema> Clone for Predicate<S> {
853    fn clone(&self) -> Self {
854        Self {
855            expr: self.expr.clone(),
856            marker: PhantomData,
857        }
858    }
859}
860
861impl<S: Schema> Predicate<S> {
862    fn new(expr: PredicateExpr) -> Self {
863        Self {
864            expr,
865            marker: PhantomData,
866        }
867    }
868
869    /// Conjunction; equivalent to `self & other`.
870    #[must_use]
871    pub fn and(self, other: Predicate<S>) -> Predicate<S> {
872        self & other
873    }
874
875    /// Disjunction; equivalent to `self | other`.
876    #[must_use]
877    pub fn or(self, other: Predicate<S>) -> Predicate<S> {
878        self | other
879    }
880
881    /// Negation; equivalent to `!self`.
882    #[must_use]
883    #[allow(clippy::should_implement_trait)]
884    pub fn not(self) -> Predicate<S> {
885        !self
886    }
887}
888
889impl<'db, S: Schema> QuerySession<'db, S> {
890    /// Require a bounded directed walk between two generated endpoint
891    /// bindings through one exact generated relation.
892    ///
893    /// Each hop follows `role_from -> role_to`. Bounds are inclusive and a
894    /// zero-hop branch requires identical endpoint concepts. Generated role
895    /// compatibility and player-union evidence reject inactive roles and
896    /// invalid endpoint models at compile time; bounds, session ownership,
897    /// and installed-schema compatibility are validated before provider I/O.
898    #[allow(clippy::too_many_arguments)]
899    pub fn reachable<
900        R,
901        FromOwner,
902        FromPlayers,
903        ToOwner,
904        ToPlayers,
905        Source,
906        SourceMode,
907        Target,
908        TargetMode,
909    >(
910        &self,
911        relation: TypeToken<R>,
912        role_from: RoleToken<FromOwner, FromPlayers>,
913        role_to: RoleToken<ToOwner, ToPlayers>,
914        source: Binding<S, Source, SourceMode>,
915        target: Binding<S, Target, TargetMode>,
916        min_depth: u8,
917        max_depth: u8,
918    ) -> Result<Predicate<S>>
919    where
920        R: RelationModel<Schema = S>
921            + CompleteModel
922            + RoleTokenCompatible<FromOwner, FromPlayers>
923            + RoleTokenCompatible<ToOwner, ToPlayers>,
924        FromOwner: RelationModel<Schema = S>,
925        ToOwner: RelationModel<Schema = S>,
926        FromPlayers: RolePlayer<Source>,
927        ToPlayers: RolePlayer<Target>,
928        Source: ThingModel<Schema = S>,
929        SourceMode: SelectionMode,
930        Target: ThingModel<Schema = S>,
931        TargetMode: SelectionMode,
932    {
933        let predicate = Predicate::new(PredicateExpr::Reachable {
934            relation_type_id_json: relation.type_id_json(),
935            role_from_id_json: role_from.role_id_json(),
936            role_to_id_json: role_to.role_id_json(),
937            source: source.key,
938            target: target.key,
939            min_depth,
940            max_depth,
941        });
942        self.lower_predicate(&predicate.expr)?;
943        Ok(predicate)
944    }
945}
946
947impl<S: Schema> std::ops::BitAnd for Predicate<S> {
948    type Output = Predicate<S>;
949    fn bitand(self, other: Predicate<S>) -> Predicate<S> {
950        let mut terms = match self.expr {
951            PredicateExpr::And(terms) => terms,
952            expr => vec![expr],
953        };
954        match other.expr {
955            PredicateExpr::And(more) => terms.extend(more),
956            expr => terms.push(expr),
957        }
958        Predicate::new(PredicateExpr::And(terms))
959    }
960}
961
962impl<S: Schema> std::ops::BitOr for Predicate<S> {
963    type Output = Predicate<S>;
964    fn bitor(self, other: Predicate<S>) -> Predicate<S> {
965        let mut terms = match self.expr {
966            PredicateExpr::Or(terms) => terms,
967            expr => vec![expr],
968        };
969        match other.expr {
970            PredicateExpr::Or(more) => terms.extend(more),
971            expr => terms.push(expr),
972        }
973        Predicate::new(PredicateExpr::Or(terms))
974    }
975}
976
977impl<S: Schema> std::ops::Not for Predicate<S> {
978    type Output = Predicate<S>;
979    fn not(self) -> Predicate<S> {
980        Predicate::new(PredicateExpr::Not(Box::new(self.expr)))
981    }
982}
983
984/// One generated field resolved against one session binding occurrence.
985///
986/// The token retains its declaring owner; owner/binding compatibility is
987/// enforced against the installed registry when the predicate is lowered,
988/// before any I/O.
989pub struct BoundField<S: Schema, Owner: Model<Schema = S>, V> {
990    key: BindingKey,
991    owns_id_json: &'static str,
992    marker: PhantomData<fn() -> (Owner, V)>,
993}
994
995impl<S: Schema, Owner: Model<Schema = S>, V> Copy for BoundField<S, Owner, V> {}
996impl<S: Schema, Owner: Model<Schema = S>, V> Clone for BoundField<S, Owner, V> {
997    fn clone(&self) -> Self {
998        *self
999    }
1000}
1001
1002impl<S: Schema, M: ThingModel<Schema = S>, Mode: SelectionMode> Binding<S, M, Mode> {
1003    /// Resolve one generated owned field against this binding occurrence.
1004    ///
1005    /// The declaring owner must be this binding's model or a generated
1006    /// nominal ancestor; unrelated same-spelled owners fail to type-check,
1007    /// and the installed registry re-validates the admission at lowering.
1008    #[must_use]
1009    pub fn field<Owner, V>(self, token: FieldToken<Owner, V>) -> BoundField<S, Owner, V>
1010    where
1011        Owner: Model<Schema = S>,
1012        M: crate::__codegen::NominalUpcast<Owner>,
1013    {
1014        BoundField {
1015            key: self.key,
1016            owns_id_json: token.owns_id_json(),
1017            marker: PhantomData,
1018        }
1019    }
1020}
1021
1022impl<S: Schema, M: RelationModel<Schema = S> + ThingModel<Schema = S>, Mode: SelectionMode>
1023    Binding<S, M, Mode>
1024{
1025    /// Resolve one active generated relation role against this relation
1026    /// binding occurrence. Only relation bindings expose roles;
1027    /// specialized-away ancestor tokens have no generated compatibility
1028    /// evidence.
1029    #[must_use]
1030    pub fn role<Owner, Players>(
1031        self,
1032        token: RoleToken<Owner, Players>,
1033    ) -> BoundRole<S, Owner, Players>
1034    where
1035        Owner: RelationModel<Schema = S>,
1036        M: RoleTokenCompatible<Owner, Players>,
1037    {
1038        BoundRole {
1039            key: self.key,
1040            role_id_json: token.role_id_json(),
1041            marker: PhantomData,
1042        }
1043    }
1044}
1045
1046impl<S: Schema, Owner: Model<Schema = S>, V> BoundField<S, Owner, V> {
1047    pub(crate) fn reduction_input(self) -> (BindingKey, &'static str) {
1048        (self.key, self.owns_id_json)
1049    }
1050
1051    fn value_predicate(self, operator: ComparisonOp, value: AttributeValue) -> Predicate<S> {
1052        Predicate::new(PredicateExpr::FieldValue {
1053            binding: self.key,
1054            owns_id_json: self.owns_id_json,
1055            operator,
1056            value,
1057        })
1058    }
1059
1060    /// Equality against a canonical literal of the field's scalar domain.
1061    #[must_use]
1062    pub fn eq<O>(self, operand: O) -> Predicate<S>
1063    where
1064        V: QueryValued,
1065        O: QueryOperand<Domain = V::Domain>,
1066    {
1067        self.value_predicate(ComparisonOp::Equal, operand.into_operand())
1068    }
1069
1070    /// Inequality against a canonical literal of the field's scalar domain.
1071    #[must_use]
1072    pub fn ne<O>(self, operand: O) -> Predicate<S>
1073    where
1074        V: QueryValued,
1075        O: QueryOperand<Domain = V::Domain>,
1076    {
1077        self.value_predicate(ComparisonOp::NotEqual, operand.into_operand())
1078    }
1079
1080    /// Strictly-less ordering against a canonically ordered literal;
1081    /// admitted only for canonically ordered field domains.
1082    #[must_use]
1083    pub fn lt(self, operand: impl OrderedOperand) -> Predicate<S>
1084    where
1085        V: crate::__codegen::OrderedValued,
1086    {
1087        self.value_predicate(ComparisonOp::LessThan, operand.into_operand())
1088    }
1089
1090    /// Less-or-equal ordering against a canonically ordered literal;
1091    /// admitted only for canonically ordered field domains.
1092    #[must_use]
1093    pub fn le(self, operand: impl OrderedOperand) -> Predicate<S>
1094    where
1095        V: crate::__codegen::OrderedValued,
1096    {
1097        self.value_predicate(ComparisonOp::LessThanOrEqual, operand.into_operand())
1098    }
1099
1100    /// Strictly-greater ordering against a canonically ordered literal;
1101    /// admitted only for canonically ordered field domains.
1102    #[must_use]
1103    pub fn gt(self, operand: impl OrderedOperand) -> Predicate<S>
1104    where
1105        V: crate::__codegen::OrderedValued,
1106    {
1107        self.value_predicate(ComparisonOp::GreaterThan, operand.into_operand())
1108    }
1109
1110    /// Greater-or-equal ordering against a canonically ordered literal;
1111    /// admitted only for canonically ordered field domains.
1112    #[must_use]
1113    pub fn ge(self, operand: impl OrderedOperand) -> Predicate<S>
1114    where
1115        V: crate::__codegen::OrderedValued,
1116    {
1117        self.value_predicate(ComparisonOp::GreaterThanOrEqual, operand.into_operand())
1118    }
1119
1120    /// Text containment against bounded canonical text; admitted only for
1121    /// text field domains.
1122    #[must_use]
1123    pub fn contains(self, text: crate::value::Text) -> Predicate<S>
1124    where
1125        V: crate::__codegen::TextValued,
1126    {
1127        self.value_predicate(
1128            ComparisonOp::Contains,
1129            AttributeValue::String(text.into_string()),
1130        )
1131    }
1132
1133    /// Anchored text prefix against bounded canonical text; admitted only
1134    /// for text field domains.
1135    #[must_use]
1136    pub fn starts_with(self, text: crate::value::Text) -> Predicate<S>
1137    where
1138        V: crate::__codegen::TextValued,
1139    {
1140        self.value_predicate(
1141            ComparisonOp::StartsWith,
1142            AttributeValue::String(text.into_string()),
1143        )
1144    }
1145
1146    /// Anchored text suffix against bounded canonical text; admitted only
1147    /// for text field domains.
1148    #[must_use]
1149    pub fn ends_with(self, text: crate::value::Text) -> Predicate<S>
1150    where
1151        V: crate::__codegen::TextValued,
1152    {
1153        self.value_predicate(
1154            ComparisonOp::EndsWith,
1155            AttributeValue::String(text.into_string()),
1156        )
1157    }
1158
1159    /// Regular-expression match against a client-owned validated pattern;
1160    /// admitted only for text field domains.
1161    #[must_use]
1162    pub fn regex(self, pattern: crate::value::Regex) -> Predicate<S>
1163    where
1164        V: crate::__codegen::TextValued,
1165    {
1166        self.value_predicate(
1167            ComparisonOp::Regex,
1168            AttributeValue::String(pattern.into_string()),
1169        )
1170    }
1171
1172    /// Compare against another compatible bound field of the same scalar
1173    /// value type; the comparison carries no literal.
1174    #[must_use]
1175    pub fn eq_field<Owner2>(self, other: BoundField<S, Owner2, V>) -> Predicate<S>
1176    where
1177        Owner2: Model<Schema = S>,
1178    {
1179        Predicate::new(PredicateExpr::FieldField {
1180            left_binding: self.key,
1181            left_owns_id_json: self.owns_id_json,
1182            operator: ComparisonOp::Equal,
1183            right_binding: other.key,
1184            right_owns_id_json: other.owns_id_json,
1185        })
1186    }
1187
1188    /// Order ascending by this bound field; missing keys fail closed unless
1189    /// an explicit missing-value policy is admitted.
1190    #[must_use]
1191    pub fn asc(self) -> Order<S> {
1192        Order {
1193            key: self.key,
1194            owns_id_json: self.owns_id_json,
1195            direction: SortDirection::Ascending,
1196            missing: MissingOrder::Reject,
1197            marker: PhantomData,
1198        }
1199    }
1200
1201    /// Order descending by this bound field; missing keys fail closed unless
1202    /// an explicit missing-value policy is admitted.
1203    #[must_use]
1204    pub fn desc(self) -> Order<S> {
1205        Order {
1206            key: self.key,
1207            owns_id_json: self.owns_id_json,
1208            direction: SortDirection::Descending,
1209            missing: MissingOrder::Reject,
1210            marker: PhantomData,
1211        }
1212    }
1213}
1214
1215/// One stable public ordering term over a bound field.
1216#[derive(Debug)]
1217pub struct Order<S: Schema> {
1218    key: BindingKey,
1219    owns_id_json: &'static str,
1220    direction: SortDirection,
1221    missing: MissingOrder,
1222    marker: PhantomData<fn() -> S>,
1223}
1224
1225impl<S: Schema> Copy for Order<S> {}
1226impl<S: Schema> Clone for Order<S> {
1227    fn clone(&self) -> Self {
1228        *self
1229    }
1230}
1231
1232impl<S: Schema> Order<S> {
1233    /// Admit missing keys and place them before all present keys.
1234    #[must_use]
1235    pub fn missing_first(mut self) -> Self {
1236        self.missing = MissingOrder::First;
1237        self
1238    }
1239
1240    /// Admit missing keys and place them after all present keys.
1241    #[must_use]
1242    pub fn missing_last(mut self) -> Self {
1243        self.missing = MissingOrder::Last;
1244        self
1245    }
1246}
1247
1248/// One typed collection selection used inside a distinct-root page shape.
1249pub struct Collected<S: Schema, B: Selectable<S>> {
1250    selection: B,
1251    distinct: bool,
1252    order: Vec<Order<S>>,
1253}
1254
1255impl<S: Schema, B: Selectable<S>> Clone for Collected<S, B> {
1256    fn clone(&self) -> Self {
1257        Self {
1258            selection: self.selection,
1259            distinct: self.distinct,
1260            order: self.order.clone(),
1261        }
1262    }
1263}
1264
1265impl<S: Schema, B: Selectable<S>> Collected<S, B> {
1266    /// Deduplicate collection members by TypeDB concept identity.
1267    #[must_use]
1268    pub fn distinct(mut self) -> Self {
1269        self.distinct = true;
1270        self
1271    }
1272
1273    /// Append one stable order term owned by this collected binding.
1274    pub fn order_by(mut self, order: Order<S>) -> Result<Self> {
1275        if order.key != self.selection.binding_key() {
1276            return Err(Error::model_validation(
1277                ModelValidationPhase::Input,
1278                "collection_order_binding_mismatch",
1279                vec![],
1280                "collection ordering must reference the collected binding",
1281                None,
1282            ));
1283        }
1284        self.order.push(order);
1285        Ok(self)
1286    }
1287}
1288
1289/// One generated relation role resolved against one relation binding
1290/// occurrence.
1291pub struct BoundRole<S: Schema, Owner: Model<Schema = S>, Players> {
1292    key: BindingKey,
1293    role_id_json: &'static str,
1294    marker: PhantomData<fn() -> (Owner, Players)>,
1295}
1296
1297impl<S: Schema, Owner: Model<Schema = S>, Players> Copy for BoundRole<S, Owner, Players> {}
1298impl<S: Schema, Owner: Model<Schema = S>, Players> Clone for BoundRole<S, Owner, Players> {
1299    fn clone(&self) -> Self {
1300        *self
1301    }
1302}
1303
1304impl<S: Schema, Owner: Model<Schema = S>, Players> BoundRole<S, Owner, Players> {
1305    /// Require this relation role to connect an admitted generated player
1306    /// binding.
1307    #[must_use]
1308    pub fn connects<MP: ThingModel<Schema = S>, ModeP: SelectionMode>(
1309        self,
1310        player: Binding<S, MP, ModeP>,
1311    ) -> Predicate<S>
1312    where
1313        Players: RolePlayer<MP>,
1314    {
1315        Predicate::new(PredicateExpr::Connects {
1316            relation: self.key,
1317            role_id_json: self.role_id_json,
1318            player: player.key,
1319        })
1320    }
1321}
1322
1323mod selectable_sealed {
1324    pub trait Sealed {}
1325}
1326
1327/// Sealed resolution from one selected binding to its typed query output.
1328pub trait Selectable<S: Schema>: selectable_sealed::Sealed + Copy {
1329    /// The materialized output type for one selected row.
1330    type Output;
1331    #[doc(hidden)]
1332    fn binding_key(self) -> BindingKey;
1333    #[doc(hidden)]
1334    fn materialize_output(row: &HydratedRow) -> std::result::Result<Self::Output, ValidationError>;
1335
1336    #[doc(hidden)]
1337    fn __selection_handle(self, session: &QuerySession<'_, S>) -> Result<OrmSelectionHandle> {
1338        Ok(session.handle_by_key(self.binding_key())?.one())
1339    }
1340
1341    #[doc(hidden)]
1342    fn __materialize_slot(
1343        self,
1344        session: &QuerySession<'_, S>,
1345        slot: &SlotValue,
1346    ) -> Result<Self::Output> {
1347        let SlotValue::One(thing) = slot else {
1348            return Err(Error::model_validation(
1349                ModelValidationPhase::Hydration,
1350                "wrong_result_shape",
1351                vec![],
1352                "provider returned a collection slot for a singular selection",
1353                None,
1354            ));
1355        };
1356        let row = session.client_row_for(thing)?;
1357        Self::materialize_output(&row)
1358            .map_err(|error| map_validation_error(error, ModelValidationPhase::Hydration))
1359    }
1360}
1361
1362impl<S: Schema, M: ThingModel<Schema = S> + CompleteModel> selectable_sealed::Sealed
1363    for Binding<S, M, Exact>
1364{
1365}
1366impl<S: Schema, M: ThingModel<Schema = S> + CompleteModel> Selectable<S> for Binding<S, M, Exact> {
1367    type Output = M;
1368    fn binding_key(self) -> BindingKey {
1369        self.key()
1370    }
1371    fn materialize_output(row: &HydratedRow) -> std::result::Result<M, ValidationError> {
1372        M::materialize(row, &HydrationCapability::new())
1373    }
1374}
1375
1376impl<S: Schema, M: ThingModel<Schema = S> + SubtypeRootModel> selectable_sealed::Sealed
1377    for Binding<S, M, Subtypes>
1378{
1379}
1380impl<S: Schema, M: ThingModel<Schema = S> + SubtypeRootModel> Selectable<S>
1381    for Binding<S, M, Subtypes>
1382{
1383    type Output = M::Subtypes;
1384    fn binding_key(self) -> BindingKey {
1385        self.key()
1386    }
1387    fn materialize_output(row: &HydratedRow) -> std::result::Result<M::Subtypes, ValidationError> {
1388        M::__tb_dispatch_subtype(row, &HydrationCapability::new())
1389    }
1390}
1391
1392mod selected_slot_sealed {
1393    pub trait Sealed<S> {}
1394}
1395
1396/// Sealed resolution from one singular or collected selection to its typed
1397/// slot output.
1398#[doc(hidden)]
1399pub trait SelectedSlot<S: Schema>: selected_slot_sealed::Sealed<S> + Clone {
1400    type Output;
1401
1402    fn __selection_handle(&self, session: &QuerySession<'_, S>) -> Result<OrmSelectionHandle>;
1403
1404    fn __materialize_slot(
1405        &self,
1406        session: &QuerySession<'_, S>,
1407        slot: &SlotValue,
1408    ) -> Result<Self::Output>;
1409}
1410
1411impl<S: Schema, B: Selectable<S>> selected_slot_sealed::Sealed<S> for B {}
1412impl<S: Schema, B: Selectable<S>> SelectedSlot<S> for B {
1413    type Output = B::Output;
1414
1415    fn __selection_handle(&self, session: &QuerySession<'_, S>) -> Result<OrmSelectionHandle> {
1416        (*self).__selection_handle(session)
1417    }
1418
1419    fn __materialize_slot(
1420        &self,
1421        session: &QuerySession<'_, S>,
1422        slot: &SlotValue,
1423    ) -> Result<Self::Output> {
1424        (*self).__materialize_slot(session, slot)
1425    }
1426}
1427
1428impl<S: Schema, B: Selectable<S>> selected_slot_sealed::Sealed<S> for Collected<S, B> {}
1429impl<S: Schema, B: Selectable<S>> SelectedSlot<S> for Collected<S, B> {
1430    type Output = Vec<B::Output>;
1431
1432    fn __selection_handle(&self, session: &QuerySession<'_, S>) -> Result<OrmSelectionHandle> {
1433        let binding = session.handle_by_key(self.selection.binding_key())?;
1434        let mut selection = binding
1435            .collect()
1436            .distinct(self.distinct)
1437            .map_err(Error::from_orm)?;
1438        for order in &self.order {
1439            selection = selection
1440                .order_by(session.lower_order(order)?)
1441                .map_err(Error::from_orm)?;
1442        }
1443        Ok(selection)
1444    }
1445
1446    fn __materialize_slot(
1447        &self,
1448        session: &QuerySession<'_, S>,
1449        slot: &SlotValue,
1450    ) -> Result<Self::Output> {
1451        let SlotValue::Many(things) = slot else {
1452            return Err(Error::model_validation(
1453                ModelValidationPhase::Hydration,
1454                "wrong_result_shape",
1455                vec![],
1456                "provider returned a singular slot for a collection selection",
1457                None,
1458            ));
1459        };
1460        let mut outputs = Vec::with_capacity(things.len());
1461        for thing in things {
1462            let row = session.client_row_for(thing)?;
1463            outputs.push(
1464                B::materialize_output(&row).map_err(|error| {
1465                    map_validation_error(error, ModelValidationPhase::Hydration)
1466                })?,
1467            );
1468        }
1469        Ok(outputs)
1470    }
1471}
1472
1473mod selected_shape_sealed {
1474    pub trait Sealed<S> {}
1475}
1476
1477/// Sealed typed selected-output shape accepted by the one query facade.
1478///
1479/// Implementations are supplied for one binding, positional tuples through
1480/// the canonical sixteen-slot ceiling, and derive-backed named rows.
1481pub trait SelectedShape<S: Schema>: selected_shape_sealed::Sealed<S> + Clone {
1482    /// One fully materialized public row.
1483    type Output;
1484
1485    #[doc(hidden)]
1486    fn __shape_handle(&self, session: &QuerySession<'_, S>) -> Result<OrmShapeHandle>;
1487
1488    #[doc(hidden)]
1489    fn __materialize_row(
1490        &self,
1491        session: &QuerySession<'_, S>,
1492        row: &MatchRow,
1493    ) -> Result<Self::Output>;
1494}
1495
1496impl<S: Schema, B: Selectable<S>> selected_shape_sealed::Sealed<S> for B {}
1497impl<S: Schema, B: Selectable<S>> SelectedShape<S> for B {
1498    type Output = B::Output;
1499
1500    fn __shape_handle(&self, session: &QuerySession<'_, S>) -> Result<OrmShapeHandle> {
1501        session
1502            .session
1503            .positional([SelectedSlot::__selection_handle(self, session)?])
1504            .map_err(Error::from_orm)
1505    }
1506
1507    fn __materialize_row(
1508        &self,
1509        session: &QuerySession<'_, S>,
1510        row: &MatchRow,
1511    ) -> Result<Self::Output> {
1512        let [slot] = row.slots() else {
1513            return Err(selected_shape_arity_error(1, row.slots().len()));
1514        };
1515        SelectedSlot::__materialize_slot(self, session, slot)
1516    }
1517}
1518
1519impl<S: Schema, B: Selectable<S>> selected_shape_sealed::Sealed<S> for Collected<S, B> {}
1520impl<S: Schema, B: Selectable<S>> SelectedShape<S> for Collected<S, B> {
1521    type Output = Vec<B::Output>;
1522
1523    fn __shape_handle(&self, session: &QuerySession<'_, S>) -> Result<OrmShapeHandle> {
1524        session
1525            .session
1526            .positional([SelectedSlot::__selection_handle(self, session)?])
1527            .map_err(Error::from_orm)
1528    }
1529
1530    fn __materialize_row(
1531        &self,
1532        session: &QuerySession<'_, S>,
1533        row: &MatchRow,
1534    ) -> Result<Self::Output> {
1535        let [slot] = row.slots() else {
1536            return Err(selected_shape_arity_error(1, row.slots().len()));
1537        };
1538        SelectedSlot::__materialize_slot(self, session, slot)
1539    }
1540}
1541
1542mod singular_selected_shape_sealed {
1543    pub trait Sealed<S> {}
1544}
1545
1546/// Sealed marker for a selected shape containing singular slots only.
1547pub trait SingularSelectedShape<S: Schema>:
1548    SelectedShape<S> + singular_selected_shape_sealed::Sealed<S>
1549{
1550}
1551
1552impl<S: Schema, B: Selectable<S>> singular_selected_shape_sealed::Sealed<S> for B {}
1553impl<S: Schema, B: Selectable<S>> SingularSelectedShape<S> for B {}
1554
1555#[doc(hidden)]
1556pub trait SelectedTuple<S: Schema>: Clone {
1557    const ARITY: usize;
1558
1559    type Output;
1560
1561    fn __selection_handles(&self, session: &QuerySession<'_, S>)
1562    -> Result<Vec<OrmSelectionHandle>>;
1563
1564    fn __materialize_slots(
1565        &self,
1566        session: &QuerySession<'_, S>,
1567        slots: &[SlotValue],
1568    ) -> Result<Self::Output>;
1569}
1570
1571#[doc(hidden)]
1572pub trait SingularSelectedTuple<S: Schema>: SelectedTuple<S> {}
1573
1574macro_rules! selected_tuple {
1575    ($length:literal; $(($type:ident, $index:tt)),+ $(,)?) => {
1576        impl<S: Schema, $($type: SelectedSlot<S>),+> SelectedTuple<S> for ($($type,)+) {
1577            const ARITY: usize = $length;
1578
1579            type Output = ($(<$type as SelectedSlot<S>>::Output,)+);
1580
1581            fn __selection_handles(
1582                &self,
1583                session: &QuerySession<'_, S>,
1584            ) -> Result<Vec<OrmSelectionHandle>> {
1585                Ok(vec![$(SelectedSlot::__selection_handle(&self.$index, session)?),+])
1586            }
1587
1588            fn __materialize_slots(
1589                &self,
1590                session: &QuerySession<'_, S>,
1591                slots: &[SlotValue],
1592            ) -> Result<Self::Output> {
1593                let slots: &[SlotValue; $length] = slots
1594                    .try_into()
1595                    .map_err(|_| selected_shape_arity_error($length, slots.len()))?;
1596                Ok(($(SelectedSlot::__materialize_slot(
1597                    &self.$index,
1598                    session,
1599                    &slots[$index],
1600                )?,)+))
1601            }
1602        }
1603
1604        impl<S: Schema, $($type: SelectedSlot<S>),+> selected_shape_sealed::Sealed<S>
1605            for ($($type,)+)
1606        {
1607        }
1608
1609        impl<S: Schema, $($type: SelectedSlot<S>),+> SelectedShape<S> for ($($type,)+) {
1610            type Output = <Self as SelectedTuple<S>>::Output;
1611
1612            fn __shape_handle(
1613                &self,
1614                session: &QuerySession<'_, S>,
1615            ) -> Result<OrmShapeHandle> {
1616                session
1617                    .session
1618                    .positional(self.__selection_handles(session)?)
1619                    .map_err(Error::from_orm)
1620            }
1621
1622            fn __materialize_row(
1623                &self,
1624                session: &QuerySession<'_, S>,
1625                row: &MatchRow,
1626            ) -> Result<Self::Output> {
1627                self.__materialize_slots(session, row.slots())
1628            }
1629        }
1630
1631        impl<S: Schema, $($type: Selectable<S>),+> singular_selected_shape_sealed::Sealed<S>
1632            for ($($type,)+)
1633        {
1634        }
1635
1636        impl<S: Schema, $($type: Selectable<S>),+> SingularSelectedShape<S>
1637            for ($($type,)+)
1638        {
1639        }
1640
1641        impl<S: Schema, $($type: Selectable<S>),+> SingularSelectedTuple<S>
1642            for ($($type,)+)
1643        {
1644        }
1645    };
1646}
1647
1648selected_tuple!(1; (A, 0));
1649selected_tuple!(2; (A, 0), (B, 1));
1650selected_tuple!(3; (A, 0), (B, 1), (C, 2));
1651selected_tuple!(4; (A, 0), (B, 1), (C, 2), (D, 3));
1652selected_tuple!(5; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4));
1653selected_tuple!(6; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5));
1654selected_tuple!(7; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6));
1655selected_tuple!(8; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6), (H, 7));
1656selected_tuple!(9; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6), (H, 7), (I, 8));
1657selected_tuple!(10; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6), (H, 7), (I, 8), (J, 9));
1658selected_tuple!(11; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6), (H, 7), (I, 8), (J, 9), (K, 10));
1659selected_tuple!(12; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6), (H, 7), (I, 8), (J, 9), (K, 10), (L, 11));
1660selected_tuple!(13; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6), (H, 7), (I, 8), (J, 9), (K, 10), (L, 11), (M, 12));
1661selected_tuple!(14; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6), (H, 7), (I, 8), (J, 9), (K, 10), (L, 11), (M, 12), (N, 13));
1662selected_tuple!(15; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6), (H, 7), (I, 8), (J, 9), (K, 10), (L, 11), (M, 12), (N, 13), (O, 14));
1663selected_tuple!(16; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6), (H, 7), (I, 8), (J, 9), (K, 10), (L, 11), (M, 12), (N, 13), (O, 14), (P, 15));
1664
1665/// Construction contract generated by `#[derive(type_bridge::SelectedRow)]`.
1666#[doc(hidden)]
1667pub trait SelectedRowSpec<Outputs>: Sized {
1668    fn __from_selected_outputs(outputs: Outputs) -> Self;
1669}
1670
1671/// A declaration-ordered named selected shape produced by `SelectedRow`.
1672pub struct NamedSelection<S: Schema, Row, Slots> {
1673    slots: Slots,
1674    names: &'static [&'static str],
1675    marker: PhantomData<fn() -> (S, Row)>,
1676}
1677
1678impl<S: Schema, Row, Slots: Clone> Clone for NamedSelection<S, Row, Slots> {
1679    fn clone(&self) -> Self {
1680        Self {
1681            slots: self.slots.clone(),
1682            names: self.names,
1683            marker: PhantomData,
1684        }
1685    }
1686}
1687
1688impl<S: Schema, Row, Slots: SelectedTuple<S>> NamedSelection<S, Row, Slots> {
1689    #[doc(hidden)]
1690    pub fn __new(slots: Slots, names: &'static [&'static str]) -> Result<Self> {
1691        if names.len() != Slots::ARITY {
1692            return Err(Error::model_validation(
1693                ModelValidationPhase::Input,
1694                "invalid_selected_shape",
1695                vec![],
1696                format!(
1697                    "named selected shape has {} names for {} slots",
1698                    names.len(),
1699                    Slots::ARITY
1700                ),
1701                None,
1702            ));
1703        }
1704        Ok(Self {
1705            slots,
1706            names,
1707            marker: PhantomData,
1708        })
1709    }
1710}
1711
1712impl<S: Schema, Row, Slots> selected_shape_sealed::Sealed<S> for NamedSelection<S, Row, Slots> {}
1713
1714impl<S, Row, Slots> SelectedShape<S> for NamedSelection<S, Row, Slots>
1715where
1716    S: Schema,
1717    Slots: SelectedTuple<S>,
1718    Row: SelectedRowSpec<Slots::Output>,
1719{
1720    type Output = Row;
1721
1722    fn __shape_handle(&self, session: &QuerySession<'_, S>) -> Result<OrmShapeHandle> {
1723        let selections = self.slots.__selection_handles(session)?;
1724        session
1725            .session
1726            .named(
1727                self.names
1728                    .iter()
1729                    .copied()
1730                    .zip(selections)
1731                    .map(|(name, selection)| (name.to_owned(), selection)),
1732            )
1733            .map_err(Error::from_orm)
1734    }
1735
1736    fn __materialize_row(
1737        &self,
1738        session: &QuerySession<'_, S>,
1739        row: &MatchRow,
1740    ) -> Result<Self::Output> {
1741        Ok(Row::__from_selected_outputs(
1742            self.slots.__materialize_slots(session, row.slots())?,
1743        ))
1744    }
1745}
1746
1747impl<S, Row, Slots> singular_selected_shape_sealed::Sealed<S> for NamedSelection<S, Row, Slots>
1748where
1749    S: Schema,
1750    Slots: SingularSelectedTuple<S>,
1751    Row: SelectedRowSpec<Slots::Output>,
1752{
1753}
1754
1755impl<S, Row, Slots> SingularSelectedShape<S> for NamedSelection<S, Row, Slots>
1756where
1757    S: Schema,
1758    Slots: SingularSelectedTuple<S>,
1759    Row: SelectedRowSpec<Slots::Output>,
1760{
1761}
1762
1763fn selected_shape_arity_error(expected: usize, actual: usize) -> Error {
1764    Error::model_validation(
1765        ModelValidationPhase::Hydration,
1766        "wrong_result_shape",
1767        vec![],
1768        format!("selected row has {actual} slots; expected {expected}"),
1769        None,
1770    )
1771}
1772
1773/// Bounded options for one ordered row fetch.
1774#[derive(Debug)]
1775pub struct RowsOptions<S: Schema> {
1776    limit: u64,
1777    offset: u64,
1778    order: Vec<Order<S>>,
1779}
1780
1781impl<S: Schema> Clone for RowsOptions<S> {
1782    fn clone(&self) -> Self {
1783        Self {
1784            limit: self.limit,
1785            offset: self.offset,
1786            order: self.order.clone(),
1787        }
1788    }
1789}
1790
1791impl<S: Schema> RowsOptions<S> {
1792    /// Create resource-bounded row options with a nonzero limit.
1793    #[must_use]
1794    pub fn new(limit: u64) -> Self {
1795        Self {
1796            limit,
1797            offset: 0,
1798            order: Vec::new(),
1799        }
1800    }
1801
1802    /// Skip the first `offset` distinct rows.
1803    #[must_use]
1804    pub fn offset(mut self, offset: u64) -> Self {
1805        self.offset = offset;
1806        self
1807    }
1808
1809    /// Append one stable public ordering term.
1810    #[must_use]
1811    pub fn order_by(mut self, order: Order<S>) -> Self {
1812        self.order.push(order);
1813        self
1814    }
1815}
1816
1817/// Bounded options for one ordered distinct-root page.
1818#[derive(Debug)]
1819pub struct PageOptions<S: Schema> {
1820    limit: u64,
1821    offset: u64,
1822    include_total: bool,
1823    order: Vec<Order<S>>,
1824}
1825
1826impl<S: Schema> Clone for PageOptions<S> {
1827    fn clone(&self) -> Self {
1828        Self {
1829            limit: self.limit,
1830            offset: self.offset,
1831            include_total: self.include_total,
1832            order: self.order.clone(),
1833        }
1834    }
1835}
1836
1837impl<S: Schema> PageOptions<S> {
1838    /// Create resource-bounded page options with a nonzero terminal limit.
1839    #[must_use]
1840    pub fn new(limit: u64) -> Self {
1841        Self {
1842            limit,
1843            offset: 0,
1844            include_total: false,
1845            order: Vec::new(),
1846        }
1847    }
1848
1849    /// Skip the first `offset` distinct roots.
1850    #[must_use]
1851    pub fn offset(mut self, offset: u64) -> Self {
1852        self.offset = offset;
1853        self
1854    }
1855
1856    /// Request a same-snapshot total distinct-root count.
1857    #[must_use]
1858    pub fn include_total(mut self, include_total: bool) -> Self {
1859        self.include_total = include_total;
1860        self
1861    }
1862
1863    /// Append one stable root-ordering term.
1864    #[must_use]
1865    pub fn order_by(mut self, order: Order<S>) -> Self {
1866        self.order.push(order);
1867        self
1868    }
1869}
1870
1871/// One immutable owned distinct-root page.
1872#[derive(Clone, Debug)]
1873pub struct Page<T> {
1874    items: Vec<T>,
1875    offset: u64,
1876    limit: u64,
1877    total: Option<u64>,
1878}
1879
1880impl<T> Page<T> {
1881    /// Borrow page items in stable root order.
1882    #[must_use]
1883    pub fn items(&self) -> &[T] {
1884        &self.items
1885    }
1886
1887    /// Return the requested root offset.
1888    #[must_use]
1889    pub const fn offset(&self) -> u64 {
1890        self.offset
1891    }
1892
1893    /// Return the requested root limit.
1894    #[must_use]
1895    pub const fn limit(&self) -> u64 {
1896        self.limit
1897    }
1898
1899    /// Return the same-snapshot total when it was requested.
1900    #[must_use]
1901    pub const fn total(&self) -> Option<u64> {
1902        self.total
1903    }
1904
1905    /// Consume the page and return its owned items.
1906    #[must_use]
1907    pub fn into_items(self) -> Vec<T> {
1908        self.items
1909    }
1910}
1911
1912/// One persistent, reusable singular-shape query lineage.
1913///
1914/// Each authoring method returns a new lineage and leaves its ancestor
1915/// usable.
1916pub struct Query<'s, 'db, S: Schema, Shape: SelectedShape<S>> {
1917    session: &'s QuerySession<'db, S>,
1918    selection: Shape,
1919    predicates: Vec<Predicate<S>>,
1920}
1921
1922impl<'s, 'db, S: Schema, Shape: SelectedShape<S>> Clone for Query<'s, 'db, S, Shape> {
1923    fn clone(&self) -> Self {
1924        Self {
1925            session: self.session,
1926            selection: self.selection.clone(),
1927            predicates: self.predicates.clone(),
1928        }
1929    }
1930}
1931
1932impl<'db, S: Schema> QuerySession<'db, S> {
1933    /// Begin one persistent query lineage from a singular selected shape.
1934    pub fn query<Shape: SelectedShape<S>>(
1935        &self,
1936        selection: Shape,
1937    ) -> Result<Query<'_, 'db, S, Shape>> {
1938        selection.__shape_handle(self)?;
1939        Ok(Query {
1940            session: self,
1941            selection,
1942            predicates: Vec::new(),
1943        })
1944    }
1945}
1946
1947impl<'s, 'db, S: Schema, Shape: SelectedShape<S>> Query<'s, 'db, S, Shape> {
1948    /// Attach one predicate; repeated calls form a conjunction in call order.
1949    pub fn where_(&self, predicate: Predicate<S>) -> Result<Self> {
1950        let mut next = self.clone();
1951        next.predicates.push(predicate);
1952        Ok(next)
1953    }
1954
1955    /// Attach predicates as one implicit conjunction in source order.
1956    pub fn where_all(&self, predicates: impl IntoIterator<Item = Predicate<S>>) -> Result<Self> {
1957        let mut next = self.clone();
1958        next.predicates.extend(predicates);
1959        Ok(next)
1960    }
1961
1962    fn lineage(&self) -> Result<OrmQueryHandle> {
1963        self.lineage_with_hidden(None)
1964    }
1965
1966    fn lineage_with_hidden(&self, hidden: Option<&OrmBindingHandle>) -> Result<OrmQueryHandle> {
1967        let shape = self.selection.__shape_handle(self.session)?;
1968        let mut query = self.session.session.query(shape).map_err(Error::from_orm)?;
1969        if let Some(hidden) = hidden {
1970            query = query.add_hidden(hidden.clone()).map_err(Error::from_orm)?;
1971        }
1972        for predicate in &self.predicates {
1973            let lowered = self.session.lower_predicate(&predicate.expr)?;
1974            query = query.where_predicate(lowered).map_err(Error::from_orm)?;
1975        }
1976        Ok(query)
1977    }
1978
1979    pub(crate) fn validated_rows(
1980        &self,
1981        order: &[Order<S>],
1982        window: Window,
1983    ) -> Result<ValidatedMatchRequest> {
1984        let lineage = self.lineage()?;
1985        let mut lowered_orders = Vec::with_capacity(order.len());
1986        for term in order {
1987            lowered_orders.push(self.session.lower_order(term)?);
1988        }
1989        lineage
1990            .validate_fetch_rows(&lowered_orders, window, RowCardinality::BoundedMany)
1991            .map_err(Error::from_orm)
1992    }
1993
1994    pub(crate) fn validated_page<R: Selectable<S>>(
1995        &self,
1996        root: R,
1997        order: &[Order<S>],
1998        window: Window,
1999        include_total: bool,
2000    ) -> Result<ValidatedMatchRequest> {
2001        let root = self.session.handle_by_key(root.binding_key())?;
2002        let lineage = self.lineage()?;
2003        let mut lowered_orders = Vec::with_capacity(order.len());
2004        for term in order {
2005            lowered_orders.push(self.session.lower_order(term)?);
2006        }
2007        lineage
2008            .validate_page_by(root, &lowered_orders, window, include_total)
2009            .map_err(Error::from_orm)
2010    }
2011
2012    pub(crate) fn validated_count_by<R: Selectable<S>>(
2013        &self,
2014        root: R,
2015    ) -> Result<ValidatedMatchRequest> {
2016        let root = self.session.handle_by_key(root.binding_key())?;
2017        self.lineage()?
2018            .validate_count_by(root)
2019            .map_err(Error::from_orm)
2020    }
2021
2022    pub(crate) fn validated_exists_by<R: Selectable<S>>(
2023        &self,
2024        root: R,
2025    ) -> Result<ValidatedMatchRequest> {
2026        let root = self.session.handle_by_key(root.binding_key())?;
2027        self.lineage()?
2028            .validate_exists_by(root)
2029            .map_err(Error::from_orm)
2030    }
2031
2032    fn materialize_rows(&self, rows: &[MatchRow]) -> Result<Vec<Shape::Output>> {
2033        let mut outputs = Vec::with_capacity(rows.len());
2034        for row in rows {
2035            outputs.push(self.selection.__materialize_row(self.session, row)?);
2036        }
2037        Ok(outputs)
2038    }
2039
2040    pub(crate) fn outputs_from_rows(
2041        &self,
2042        validated: &ValidatedMatchRequest,
2043        result: &ValidatedMatchResult,
2044    ) -> Result<Vec<Shape::Output>> {
2045        let rows = match result
2046            .for_request(validated)
2047            .map_err(|error| Error::from_orm_hydration(error.into()))?
2048        {
2049            MatchResult::Rows { rows } => rows,
2050            _ => {
2051                return Err(Error::model_validation(
2052                    ModelValidationPhase::Hydration,
2053                    "wrong_result_shape",
2054                    vec![],
2055                    "provider returned a non-row result for a row fetch",
2056                    None,
2057                ));
2058            }
2059        };
2060        self.materialize_rows(rows)
2061    }
2062
2063    pub(crate) fn output_page(
2064        &self,
2065        validated: &ValidatedMatchRequest,
2066        result: &ValidatedMatchResult,
2067    ) -> Result<Page<Shape::Output>> {
2068        let (entries, window, total) = match result
2069            .for_request(validated)
2070            .map_err(|error| Error::from_orm_hydration(error.into()))?
2071        {
2072            MatchResult::Page {
2073                entries,
2074                window,
2075                total,
2076                ..
2077            } => (entries, *window, *total),
2078            _ => {
2079                return Err(Error::model_validation(
2080                    ModelValidationPhase::Hydration,
2081                    "wrong_result_shape",
2082                    vec![],
2083                    "provider returned a non-page result for a page fetch",
2084                    None,
2085                ));
2086            }
2087        };
2088        Ok(Page {
2089            items: self.materialize_rows(entries)?,
2090            offset: window.offset,
2091            limit: window.limit,
2092            total,
2093        })
2094    }
2095
2096    async fn execute(
2097        &self,
2098        validated: ValidatedMatchRequest,
2099    ) -> Result<(ValidatedMatchRequest, ValidatedMatchResult)> {
2100        let result = match &self.session.execution {
2101            QueryExecution::Borrowed(transaction) => {
2102                transaction
2103                    .execute_match(&self.session.registry, &validated)
2104                    .await
2105            }
2106            QueryExecution::Local(database) => {
2107                database
2108                    .inner_orm()
2109                    .execute_match(&self.session.registry, &validated)
2110                    .await
2111            }
2112            QueryExecution::Remote(remote) => {
2113                return remote
2114                    .execute_match(&self.session.registry, validated)
2115                    .await;
2116            }
2117        };
2118        Ok((validated, result.map_err(Error::from_orm_hydration)?))
2119    }
2120}
2121
2122impl<'s, 'db, S, Shape> Query<'s, 'db, S, Shape>
2123where
2124    S: Schema,
2125    Shape: SingularSelectedShape<S>,
2126{
2127    /// Return exactly one distinct selected identity, failing `no_result` on
2128    /// an empty stream and `not_unique` on more than one.
2129    pub async fn one(&self) -> Result<Shape::Output> {
2130        let validated = self.validated_rows(
2131            &[],
2132            Window {
2133                offset: 0,
2134                limit: 2,
2135            },
2136        )?;
2137        let (validated, result) = self.execute(validated).await?;
2138        let mut outputs = self.outputs_from_rows(&validated, &result)?;
2139        match outputs.len() {
2140            0 => Err(Error::model_validation(
2141                ModelValidationPhase::Hydration,
2142                "no_result",
2143                vec![],
2144                "query selected no distinct identity",
2145                None,
2146            )),
2147            1 => Ok(outputs.remove(0)),
2148            _ => Err(Error::model_validation(
2149                ModelValidationPhase::Hydration,
2150                "not_unique",
2151                vec![],
2152                "query selected more than one distinct identity",
2153                None,
2154            )),
2155        }
2156    }
2157
2158    /// Return a resource-bounded ordered sequence of distinct selected
2159    /// identities; the limit must be nonzero.
2160    pub async fn rows(&self, options: RowsOptions<S>) -> Result<Vec<Shape::Output>> {
2161        if options.limit == 0 {
2162            return Err(Error::model_validation(
2163                ModelValidationPhase::Input,
2164                "zero_limit",
2165                vec![],
2166                "row fetches require a nonzero limit",
2167                None,
2168            ));
2169        }
2170        let validated = self.validated_rows(
2171            &options.order,
2172            Window {
2173                offset: options.offset,
2174                limit: options.limit,
2175            },
2176        )?;
2177        let (validated, result) = self.execute(validated).await?;
2178        self.outputs_from_rows(&validated, &result)
2179    }
2180
2181    /// Return the first distinct selected identity under a stable order.
2182    pub async fn first(&self, order: Order<S>) -> Result<Option<Shape::Output>> {
2183        let validated = self.validated_rows(
2184            &[order],
2185            Window {
2186                offset: 0,
2187                limit: 1,
2188            },
2189        )?;
2190        let (validated, result) = self.execute(validated).await?;
2191        Ok(self.outputs_from_rows(&validated, &result)?.pop())
2192    }
2193}
2194
2195impl<'s, 'db, S: Schema, Shape: SelectedShape<S>> Query<'s, 'db, S, Shape> {
2196    /// Return one resource-bounded page grouped by distinct root identity.
2197    pub async fn page_by<R: Selectable<S>>(
2198        &self,
2199        root: R,
2200        options: PageOptions<S>,
2201    ) -> Result<Page<Shape::Output>> {
2202        if options.limit == 0 {
2203            return Err(Error::model_validation(
2204                ModelValidationPhase::Input,
2205                "zero_limit",
2206                vec![],
2207                "page fetches require a nonzero limit",
2208                None,
2209            ));
2210        }
2211        let validated = self.validated_page(
2212            root,
2213            &options.order,
2214            Window {
2215                offset: options.offset,
2216                limit: options.limit,
2217            },
2218            options.include_total,
2219        )?;
2220        let (validated, result) = self.execute(validated).await?;
2221        self.output_page(&validated, &result)
2222    }
2223
2224    /// Count distinct identities of one selected root binding.
2225    pub async fn count_by<R: Selectable<S>>(&self, root: R) -> Result<u64> {
2226        let validated = self.validated_count_by(root)?;
2227        let (validated, result) = self.execute(validated).await?;
2228        match result
2229            .for_request(&validated)
2230            .map_err(|error| Error::from_orm_hydration(error.into()))?
2231        {
2232            MatchResult::Count { value, .. } => Ok(*value),
2233            _ => Err(Error::model_validation(
2234                ModelValidationPhase::Hydration,
2235                "wrong_result_shape",
2236                vec![],
2237                "provider returned a non-count result for a count",
2238                None,
2239            )),
2240        }
2241    }
2242
2243    /// Test whether any distinct identity of one selected root binding
2244    /// exists.
2245    pub async fn exists_by<R: Selectable<S>>(&self, root: R) -> Result<bool> {
2246        let validated = self.validated_exists_by(root)?;
2247        let (validated, result) = self.execute(validated).await?;
2248        match result
2249            .for_request(&validated)
2250            .map_err(|error| Error::from_orm_hydration(error.into()))?
2251        {
2252            MatchResult::Exists { value, .. } => Ok(*value),
2253            _ => Err(Error::model_validation(
2254                ModelValidationPhase::Hydration,
2255                "wrong_result_shape",
2256                vec![],
2257                "provider returned a non-existence result for an existence test",
2258                None,
2259            )),
2260        }
2261    }
2262}
2263
2264impl<'s, 'db, S: Schema, B: Selectable<S>> Query<'s, 'db, S, B> {
2265    /// Count distinct selected identities.
2266    pub async fn count(&self) -> Result<u64> {
2267        self.count_by(self.selection).await
2268    }
2269
2270    /// Test whether any distinct selected identity exists.
2271    pub async fn exists(&self) -> Result<bool> {
2272        self.exists_by(self.selection).await
2273    }
2274
2275    fn lowered_reduce_terms(
2276        &self,
2277        terms: &[(
2278            type_bridge_orm::match_request::Reduction,
2279            Option<(BindingKey, &'static str)>,
2280        )],
2281    ) -> Result<Vec<OrmFieldHandle>> {
2282        let mut lowered = Vec::new();
2283        for (_, input) in terms {
2284            if let Some((key, owns_id_json)) = input {
2285                lowered.push(self.session.lower_field(*key, owns_id_json)?);
2286            }
2287        }
2288        Ok(lowered)
2289    }
2290
2291    pub(crate) fn validated_reduce(
2292        &self,
2293        group: Option<BindingKey>,
2294        terms: &[(
2295            type_bridge_orm::match_request::Reduction,
2296            Option<(BindingKey, &'static str)>,
2297        )],
2298    ) -> Result<ValidatedMatchRequest> {
2299        let root = self.session.handle_by_key(self.selection.binding_key())?;
2300        let group_handle = group
2301            .map(|key| self.session.handle_by_key(key))
2302            .transpose()?;
2303        let lineage = self.lineage_with_hidden(group_handle)?;
2304        let lowered_inputs = self.lowered_reduce_terms(terms)?;
2305        let mut inputs = lowered_inputs.iter();
2306        let mut pairs = Vec::with_capacity(terms.len());
2307        for (reduction, input) in terms {
2308            let handle = if input.is_some() {
2309                Some(inputs.next().expect("one lowered handle per input"))
2310            } else {
2311                None
2312            };
2313            pairs.push((*reduction, handle));
2314        }
2315        lineage
2316            .validate_reduce_by(root, group_handle, &pairs)
2317            .map_err(Error::from_orm)
2318    }
2319
2320    fn decoded_reduction_rows(
2321        validated: &ValidatedMatchRequest,
2322        result: &ValidatedMatchResult,
2323    ) -> Result<Vec<type_bridge_orm::match_request::ReductionRow>> {
2324        match result
2325            .for_request(validated)
2326            .map_err(|error| Error::from_orm_hydration(error.into()))?
2327        {
2328            MatchResult::Reduction { rows, .. } => Ok(rows.clone()),
2329            _ => Err(Error::model_validation(
2330                ModelValidationPhase::Hydration,
2331                "wrong_result_shape",
2332                vec![],
2333                "provider returned a non-reduction result for an aggregate",
2334                None,
2335            )),
2336        }
2337    }
2338
2339    /// Reduce the distinct selected stream to one typed tuple of aggregate
2340    /// values.
2341    pub async fn aggregate<T: crate::aggregate::AggregateTuple<S>>(
2342        &self,
2343        terms: T,
2344    ) -> Result<T::Output> {
2345        let term_list = terms.terms();
2346        let validated = self.validated_reduce(None, &term_list)?;
2347        let (validated, result) = self.execute(validated).await?;
2348        let rows = Self::decoded_reduction_rows(&validated, &result)?;
2349        let [row] = rows.as_slice() else {
2350            return Err(Error::model_validation(
2351                ModelValidationPhase::Hydration,
2352                "wrong_result_shape",
2353                vec![],
2354                "ungrouped aggregates require exactly one reduction row",
2355                None,
2356            ));
2357        };
2358        T::decode(row.values())
2359    }
2360
2361    /// Group the distinct selected stream by another attached binding's
2362    /// distinct identities before aggregating.
2363    pub fn group_by<G: Selectable<S>>(&self, group: G) -> Result<GroupedQuery<'s, 'db, S, B, G>> {
2364        self.session.handle_by_key(group.binding_key())?;
2365        Ok(GroupedQuery {
2366            query: self.clone(),
2367            group,
2368        })
2369    }
2370}
2371
2372/// One query lineage grouped by a second attached binding for aggregation.
2373pub struct GroupedQuery<'s, 'db, S: Schema, B: Selectable<S>, G: Selectable<S>> {
2374    query: Query<'s, 'db, S, B>,
2375    group: G,
2376}
2377
2378impl<'s, 'db, S: Schema, B: Selectable<S>, G: Selectable<S>> GroupedQuery<'s, 'db, S, B, G> {
2379    /// Reduce each witnessed distinct group identity to one typed tuple,
2380    /// returning materialized group keys with their aggregate values.
2381    pub async fn aggregate<T: crate::aggregate::AggregateTuple<S>>(
2382        &self,
2383        terms: T,
2384    ) -> Result<Vec<(G::Output, T::Output)>> {
2385        let term_list = terms.terms();
2386        let validated = self
2387            .query
2388            .validated_reduce(Some(self.group.binding_key()), &term_list)?;
2389        let (validated, result) = self.query.execute(validated).await?;
2390        let rows = Query::<S, B>::decoded_reduction_rows(&validated, &result)?;
2391        let mut outputs = Vec::with_capacity(rows.len());
2392        for row in &rows {
2393            let thing = row.group().ok_or_else(|| {
2394                Error::model_validation(
2395                    ModelValidationPhase::Hydration,
2396                    "wrong_result_shape",
2397                    vec![],
2398                    "grouped aggregates require group evidence per row",
2399                    None,
2400                )
2401            })?;
2402            let client_row = self.query.session.client_row_for(thing)?;
2403            let key = G::materialize_output(&client_row).map_err(|error| {
2404                crate::entity_codec::map_validation_error(error, ModelValidationPhase::Hydration)
2405            })?;
2406            outputs.push((key, T::decode(row.values())?));
2407        }
2408        Ok(outputs)
2409    }
2410}