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