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::{AtomicBool, AtomicU64, Ordering};
8
9use type_bridge_contract::codec::from_canonical_json;
10use type_bridge_contract::decimal::parse_decimal;
11use type_bridge_contract::id::{FunctionId, TypeId, TypeKind};
12use type_bridge_contract::projection::ProjectionHandler;
13use type_bridge_contract::query_plan::CompatibilityValueV2;
14use type_bridge_contract::temporal::{
15    CanonicalDate, CanonicalDateTime, CanonicalDateTimeTz, CanonicalDuration,
16};
17use type_bridge_orm::_descriptor::TypeDescriptorRef;
18use type_bridge_orm::_registry::DescriptorRegistry;
19use type_bridge_orm::match_request::handles::{
20    BindingHandle as OrmBindingHandle, FieldHandle as OrmFieldHandle,
21    FunctionArgumentHandle as OrmFunctionArgumentHandle,
22    FunctionCallHandle as OrmFunctionCallHandle, OrderHandle as OrmOrderHandle,
23    PredicateHandle as OrmPredicateHandle, QueryHandle as OrmQueryHandle,
24    SelectionHandle as OrmSelectionHandle, SessionHandle as OrmSessionHandle,
25    ShapeHandle as OrmShapeHandle,
26};
27use type_bridge_orm::match_request::model::{
28    ComparisonOp, MissingOrder, RowCardinality, SortDirection, Window,
29};
30use type_bridge_orm::match_request::result::{
31    HydratedThing, MatchResult, MatchRow, ReducedValue, SlotValue, ValidatedMatchResult,
32};
33use type_bridge_orm::match_request::validation::ValidatedMatchRequest;
34use type_bridge_orm::{
35    AnswerCancellation, AttributeValue, DynamicEntityRow, DynamicRelationRow, DynamicRolePlayer,
36    InstalledRuntimeProjection, ProjectedAttributeValue, ProjectedQueryOrigin, ProjectedQueryRow,
37    ProjectedQuerySlotValue, ProjectedQueryValue, ProjectedReducedValue, ProjectedReductionGroup,
38    ProjectedThing, QueryExecutionDeadline, QueryExecutionResourceLimits,
39};
40
41use crate::__codegen::{
42    CompleteModel, EncodedScalar, FieldToken, FunctionToken, GroupedQueryValue, HydratedRow,
43    HydrationCapability, Model, QueryValued, RelationModel, RolePlayerBinding, RoleToken,
44    RoleTokenCompatible, SubtypeRootModel, ThingModel, TypeToken, ValidationError,
45};
46use crate::entity_codec::{hydrate_entity, map_validation_error};
47use crate::error::{Error, ModelValidationPhase};
48use crate::projected_codec::projected_to_hydrated_row;
49use crate::relation_codec::hydrate_relation;
50use crate::schema::Schema;
51use crate::{Database, Result};
52
53#[cfg(test)]
54mod tests;
55
56static QUERY_SESSION_NONCE: AtomicU64 = AtomicU64::new(1);
57
58fn schema_not_bound() -> Error {
59    Error::model_validation(
60        ModelValidationPhase::Input,
61        "schema_not_bound",
62        vec![],
63        "database is not schema-bound",
64        None,
65    )
66}
67
68fn cross_session_handle() -> Error {
69    Error::model_validation(
70        ModelValidationPhase::Input,
71        "cross_session_handle",
72        vec![],
73        "binding belongs to a different query session",
74        None,
75    )
76}
77
78fn model_label(type_id_json: &'static str) -> Result<String> {
79    let id = from_canonical_json::<TypeId>(type_id_json.as_bytes()).map_err(|source| {
80        Error::model_validation(
81            ModelValidationPhase::Input,
82            "invalid_model_identity",
83            vec!["type".into()],
84            "generated model identity is not canonical",
85            Some(Box::new(source)),
86        )
87    })?;
88    Ok(id.label().as_str().to_owned())
89}
90
91fn parse_owns_identity(owns_id_json: &'static str) -> Result<(String, String)> {
92    let invalid = || {
93        Error::model_validation(
94            ModelValidationPhase::Input,
95            "invalid_field_identity",
96            vec!["type".into()],
97            "generated field identity is not canonical",
98            None,
99        )
100    };
101    let value: serde_json::Value = serde_json::from_str(owns_id_json).map_err(|_| invalid())?;
102    let attribute = value
103        .get("attribute")
104        .and_then(serde_json::Value::as_str)
105        .ok_or_else(invalid)?;
106    let owner = value
107        .get("owner")
108        .and_then(|owner| owner.get("label"))
109        .and_then(serde_json::Value::as_str)
110        .ok_or_else(invalid)?;
111    Ok((owner.to_owned(), attribute.to_owned()))
112}
113
114fn parse_role_identity(role_id_json: &'static str) -> Result<type_bridge_contract::id::RoleId> {
115    from_canonical_json::<type_bridge_contract::id::RoleId>(role_id_json.as_bytes()).map_err(
116        |source| {
117            Error::model_validation(
118                ModelValidationPhase::Input,
119                "invalid_role_identity",
120                vec!["type".into()],
121                "generated role identity is not canonical",
122                Some(Box::new(source)),
123            )
124        },
125    )
126}
127
128mod mode_sealed {
129    pub trait Sealed {}
130}
131
132/// Sealed marker for a binding's exact-versus-subtypes selection behavior.
133pub trait SelectionMode: mode_sealed::Sealed + 'static {}
134
135/// Exact-match selection: results materialize as the bound concrete model.
136#[derive(Clone, Copy, Debug)]
137pub struct Exact;
138impl mode_sealed::Sealed for Exact {}
139impl SelectionMode for Exact {}
140
141/// Subtype-inclusive selection: results materialize as the generated leaf or
142/// closed family associated with the bound root.
143#[derive(Clone, Copy, Debug)]
144pub struct Subtypes;
145impl mode_sealed::Sealed for Subtypes {}
146impl SelectionMode for Subtypes {}
147
148/// One isolated owner-branded query authoring session.
149///
150/// Every binding call allocates a fresh binding identity; bindings are
151/// lightweight `Copy` values valid only for the session that created them.
152/// Bindings from another session fail before I/O with
153/// `cross_session_handle`.
154pub struct QuerySession<'db, S: Schema> {
155    cancellation: AnswerCancellation,
156    installed: &'db InstalledRuntimeProjection,
157    execution: QueryExecution<'db, S>,
158    resources: QueryExecutionResourceLimits,
159    session: OrmSessionHandle,
160    registry: Arc<DescriptorRegistry>,
161    nonce: u64,
162    bindings: Vec<OrmBindingHandle>,
163    closed: AtomicBool,
164    marker: PhantomData<fn() -> S>,
165}
166
167enum QueryExecution<'db, S: Schema> {
168    Local(&'db Database<S>),
169    Borrowed(&'db type_bridge_orm::session::context::TransactionContext),
170    Remote(&'db crate::remote::RemoteDatabase<S>),
171}
172
173impl<S: Schema> std::fmt::Debug for QuerySession<'_, S> {
174    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175        formatter
176            .debug_struct("QuerySession")
177            .field("session", &self.nonce)
178            .field("bindings", &self.bindings.len())
179            .field("closed", &self.closed.load(Ordering::Acquire))
180            .finish_non_exhaustive()
181    }
182}
183
184/// Opaque session-scoped binding identity carried by `Copy` handles.
185#[doc(hidden)]
186#[derive(Clone, Copy, Debug, PartialEq, Eq)]
187pub struct BindingKey {
188    pub(crate) nonce: u64,
189    pub(crate) index: u32,
190}
191
192/// One schema/model-branded `Copy` binding token allocated by a
193/// [`QuerySession`]. Copying preserves the binding identity; only a fresh
194/// session binding call creates a new occurrence.
195pub struct Binding<S: Schema, M: ThingModel<Schema = S>, Mode: SelectionMode = Exact> {
196    key: BindingKey,
197    #[allow(clippy::type_complexity)]
198    marker: PhantomData<fn() -> (S, M, Mode)>,
199}
200
201impl<S: Schema, M: ThingModel<Schema = S>, Mode: SelectionMode> Copy for Binding<S, M, Mode> {}
202impl<S: Schema, M: ThingModel<Schema = S>, Mode: SelectionMode> Clone for Binding<S, M, Mode> {
203    fn clone(&self) -> Self {
204        *self
205    }
206}
207impl<S: Schema, M: ThingModel<Schema = S>, Mode: SelectionMode> PartialEq for Binding<S, M, Mode> {
208    fn eq(&self, other: &Self) -> bool {
209        self.key == other.key
210    }
211}
212impl<S: Schema, M: ThingModel<Schema = S>, Mode: SelectionMode> Eq for Binding<S, M, Mode> {}
213impl<S: Schema, M: ThingModel<Schema = S>, Mode: SelectionMode> std::fmt::Debug
214    for Binding<S, M, Mode>
215{
216    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217        formatter
218            .debug_struct("Binding")
219            .field("session", &self.key.nonce)
220            .field("index", &self.key.index)
221            .finish()
222    }
223}
224
225impl<S: Schema, M: ThingModel<Schema = S>, Mode: SelectionMode> Binding<S, M, Mode> {
226    pub(crate) fn key(self) -> BindingKey {
227        self.key
228    }
229
230    /// Match this generated binding by one canonical TypeDB thing IID.
231    #[must_use]
232    pub fn iid(self, iid: impl Into<String>) -> Predicate<S> {
233        Predicate::new(PredicateExpr::BindingIid {
234            binding: self.key,
235            iid: iid.into(),
236        })
237    }
238
239    /// Match this generated binding by one of a non-empty bounded IID set.
240    #[must_use]
241    pub fn iid_in(self, iids: impl IntoIterator<Item = impl Into<String>>) -> Predicate<S> {
242        Predicate::new(PredicateExpr::BindingIidIn {
243            binding: self.key,
244            iids: iids.into_iter().map(Into::into).collect(),
245        })
246    }
247
248    /// Select this binding as an owned collection per distinct page root,
249    /// preserving match multiplicity by default.
250    #[must_use]
251    pub fn collect(self) -> Collected<S, Self>
252    where
253        Self: Selectable<S>,
254    {
255        Collected {
256            selection: self,
257            distinct: false,
258            order: Vec::new(),
259        }
260    }
261
262    /// Lower this exact session binding into one generated schema-function
263    /// argument. Nominal generated wrappers call this method; applications do
264    /// not construct untyped function arguments directly.
265    #[doc(hidden)]
266    #[must_use]
267    pub fn __function_argument(self) -> FunctionArgument<S> {
268        FunctionArgument {
269            nonce: self.key.nonce,
270            expression: FunctionArgumentExpr::Binding(self.key),
271            marker: PhantomData,
272        }
273    }
274}
275
276#[derive(Clone, Debug, PartialEq)]
277pub(crate) struct FunctionInputExpr {
278    attribute_type: TypeId,
279    value: AttributeValue,
280}
281
282#[derive(Clone, Debug, PartialEq)]
283pub(crate) enum FunctionArgumentExpr {
284    Binding(BindingKey),
285    Value(FunctionInputExpr),
286    Call(FunctionCallExpr),
287}
288
289#[derive(Clone, Debug, PartialEq)]
290pub(crate) struct FunctionCallExpr {
291    function_id: String,
292    arguments: Vec<FunctionArgumentExpr>,
293}
294
295/// One immutable schema- and session-branded scalar function input.
296///
297/// Generated packages expose nominal domain aliases such as `IntegerInput`
298/// and construct them only from projected attribute wrappers in that domain.
299#[doc(hidden)]
300#[derive(Clone, Debug, PartialEq)]
301pub struct FunctionInput<S: Schema, Domain> {
302    nonce: u64,
303    expression: FunctionInputExpr,
304    marker: PhantomData<fn() -> (S, Domain)>,
305}
306
307impl<S: Schema, Domain> FunctionInput<S, Domain> {
308    /// Use this scalar as one argument of the exact generated function call.
309    #[doc(hidden)]
310    #[must_use]
311    pub fn __function_argument(&self) -> FunctionArgument<S> {
312        FunctionArgument {
313            nonce: self.nonce,
314            expression: FunctionArgumentExpr::Value(self.expression.clone()),
315            marker: PhantomData,
316        }
317    }
318}
319
320/// One immutable schema- and session-branded scalar function call.
321///
322/// Generated packages expose nominal domain aliases such as `IntegerCall`.
323#[doc(hidden)]
324#[derive(Clone, Debug, PartialEq)]
325pub struct FunctionCall<S: Schema, Domain> {
326    nonce: u64,
327    expression: FunctionCallExpr,
328    marker: PhantomData<fn() -> (S, Domain)>,
329}
330
331impl<S: Schema, Domain> FunctionCall<S, Domain> {
332    /// Use this call result as one argument to a later generated function.
333    #[doc(hidden)]
334    #[must_use]
335    pub fn __function_argument(&self) -> FunctionArgument<S> {
336        FunctionArgument {
337            nonce: self.nonce,
338            expression: FunctionArgumentExpr::Call(self.expression.clone()),
339            marker: PhantomData,
340        }
341    }
342
343    fn field_predicate<Owner, Value>(
344        &self,
345        operator: ComparisonOp,
346        field: BoundField<S, Owner, Value>,
347    ) -> Predicate<S>
348    where
349        Owner: Model<Schema = S>,
350        Value: QueryValued<Domain = Domain>,
351    {
352        Predicate::new(PredicateExpr::FunctionField {
353            call: self.expression.clone(),
354            operator,
355            binding: field.key,
356            owns_id_json: field.owns_id_json,
357        })
358    }
359
360    fn value_predicate(
361        &self,
362        operator: ComparisonOp,
363        value: &FunctionInput<S, Domain>,
364    ) -> Result<Predicate<S>> {
365        if self.nonce != value.nonce {
366            return Err(cross_session_handle());
367        }
368        Ok(Predicate::new(PredicateExpr::FunctionValue {
369            call: self.expression.clone(),
370            operator,
371            value: value.expression.clone(),
372        }))
373    }
374
375    fn call_predicate(&self, operator: ComparisonOp, other: &Self) -> Result<Predicate<S>> {
376        if self.nonce != other.nonce {
377            return Err(cross_session_handle());
378        }
379        Ok(Predicate::new(PredicateExpr::FunctionCall {
380            left: self.expression.clone(),
381            operator,
382            right: other.expression.clone(),
383        }))
384    }
385
386    /// Compare this call for equality with one same-domain bound field.
387    #[must_use]
388    pub fn eq_field<Owner, Value>(&self, field: BoundField<S, Owner, Value>) -> Predicate<S>
389    where
390        Owner: Model<Schema = S>,
391        Value: QueryValued<Domain = Domain>,
392    {
393        self.field_predicate(ComparisonOp::Equal, field)
394    }
395
396    /// Compare this call for inequality with one same-domain bound field.
397    #[must_use]
398    pub fn ne_field<Owner, Value>(&self, field: BoundField<S, Owner, Value>) -> Predicate<S>
399    where
400        Owner: Model<Schema = S>,
401        Value: QueryValued<Domain = Domain>,
402    {
403        self.field_predicate(ComparisonOp::NotEqual, field)
404    }
405
406    /// Compare this call as less than one same-domain bound field.
407    #[must_use]
408    pub fn lt_field<Owner, Value>(&self, field: BoundField<S, Owner, Value>) -> Predicate<S>
409    where
410        Owner: Model<Schema = S>,
411        Value: QueryValued<Domain = Domain>,
412    {
413        self.field_predicate(ComparisonOp::LessThan, field)
414    }
415
416    /// Compare this call as less than or equal to one same-domain bound field.
417    #[must_use]
418    pub fn le_field<Owner, Value>(&self, field: BoundField<S, Owner, Value>) -> Predicate<S>
419    where
420        Owner: Model<Schema = S>,
421        Value: QueryValued<Domain = Domain>,
422    {
423        self.field_predicate(ComparisonOp::LessThanOrEqual, field)
424    }
425
426    /// Compare this call as greater than one same-domain bound field.
427    #[must_use]
428    pub fn gt_field<Owner, Value>(&self, field: BoundField<S, Owner, Value>) -> Predicate<S>
429    where
430        Owner: Model<Schema = S>,
431        Value: QueryValued<Domain = Domain>,
432    {
433        self.field_predicate(ComparisonOp::GreaterThan, field)
434    }
435
436    /// Compare this call as greater than or equal to one same-domain bound field.
437    #[must_use]
438    pub fn ge_field<Owner, Value>(&self, field: BoundField<S, Owner, Value>) -> Predicate<S>
439    where
440        Owner: Model<Schema = S>,
441        Value: QueryValued<Domain = Domain>,
442    {
443        self.field_predicate(ComparisonOp::GreaterThanOrEqual, field)
444    }
445
446    /// Compare this call for equality with one same-domain projected input.
447    pub fn eq_value(&self, value: &FunctionInput<S, Domain>) -> Result<Predicate<S>> {
448        self.value_predicate(ComparisonOp::Equal, value)
449    }
450
451    /// Compare this call for inequality with one same-domain projected input.
452    pub fn ne_value(&self, value: &FunctionInput<S, Domain>) -> Result<Predicate<S>> {
453        self.value_predicate(ComparisonOp::NotEqual, value)
454    }
455
456    /// Compare this call as less than one same-domain projected input.
457    pub fn lt_value(&self, value: &FunctionInput<S, Domain>) -> Result<Predicate<S>> {
458        self.value_predicate(ComparisonOp::LessThan, value)
459    }
460
461    /// Compare this call as less than or equal to one same-domain projected input.
462    pub fn le_value(&self, value: &FunctionInput<S, Domain>) -> Result<Predicate<S>> {
463        self.value_predicate(ComparisonOp::LessThanOrEqual, value)
464    }
465
466    /// Compare this call as greater than one same-domain projected input.
467    pub fn gt_value(&self, value: &FunctionInput<S, Domain>) -> Result<Predicate<S>> {
468        self.value_predicate(ComparisonOp::GreaterThan, value)
469    }
470
471    /// Compare this call as greater than or equal to one same-domain projected input.
472    pub fn ge_value(&self, value: &FunctionInput<S, Domain>) -> Result<Predicate<S>> {
473        self.value_predicate(ComparisonOp::GreaterThanOrEqual, value)
474    }
475
476    /// Compare this call for equality with another same-domain call.
477    pub fn eq_call(&self, other: &Self) -> Result<Predicate<S>> {
478        self.call_predicate(ComparisonOp::Equal, other)
479    }
480
481    /// Compare this call for inequality with another same-domain call.
482    pub fn ne_call(&self, other: &Self) -> Result<Predicate<S>> {
483        self.call_predicate(ComparisonOp::NotEqual, other)
484    }
485
486    /// Compare this call as less than another same-domain call.
487    pub fn lt_call(&self, other: &Self) -> Result<Predicate<S>> {
488        self.call_predicate(ComparisonOp::LessThan, other)
489    }
490
491    /// Compare this call as less than or equal to another same-domain call.
492    pub fn le_call(&self, other: &Self) -> Result<Predicate<S>> {
493        self.call_predicate(ComparisonOp::LessThanOrEqual, other)
494    }
495
496    /// Compare this call as greater than another same-domain call.
497    pub fn gt_call(&self, other: &Self) -> Result<Predicate<S>> {
498        self.call_predicate(ComparisonOp::GreaterThan, other)
499    }
500
501    /// Compare this call as greater than or equal to another same-domain call.
502    pub fn ge_call(&self, other: &Self) -> Result<Predicate<S>> {
503        self.call_predicate(ComparisonOp::GreaterThanOrEqual, other)
504    }
505}
506
507mod function_scalar_argument_sealed {
508    pub trait Sealed {}
509}
510
511/// A sealed, exact-domain scalar argument accepted by generated schema
512/// function wrappers.
513///
514/// Implementations are limited to a session-branded projected scalar input
515/// and a prior scalar function call in the same schema and scalar domain.
516/// This permits generated wrappers to build an immutable call DAG without
517/// exposing an untyped argument constructor.
518#[doc(hidden)]
519pub trait FunctionScalarArgument<S: Schema, Domain>:
520    function_scalar_argument_sealed::Sealed
521{
522    /// Lower this branded scalar into one function argument.
523    #[doc(hidden)]
524    fn __function_argument(&self) -> FunctionArgument<S>;
525}
526
527impl<S: Schema, Domain> function_scalar_argument_sealed::Sealed for FunctionInput<S, Domain> {}
528
529impl<S: Schema, Domain> FunctionScalarArgument<S, Domain> for FunctionInput<S, Domain> {
530    fn __function_argument(&self) -> FunctionArgument<S> {
531        FunctionInput::__function_argument(self)
532    }
533}
534
535impl<S: Schema, Domain> function_scalar_argument_sealed::Sealed for FunctionCall<S, Domain> {}
536
537impl<S: Schema, Domain> FunctionScalarArgument<S, Domain> for FunctionCall<S, Domain> {
538    fn __function_argument(&self) -> FunctionArgument<S> {
539        FunctionCall::__function_argument(self)
540    }
541}
542
543/// One opaque argument admitted by a nominal generated schema-function wrapper.
544#[doc(hidden)]
545#[derive(Clone, Debug, PartialEq)]
546pub struct FunctionArgument<S: Schema> {
547    nonce: u64,
548    expression: FunctionArgumentExpr,
549    marker: PhantomData<fn() -> S>,
550}
551
552impl<S: Schema> Database<S> {
553    /// Start one owner-branded query authoring session over this
554    /// schema-bound database.
555    pub fn query(&self) -> Result<QuerySession<'_, S>> {
556        self.query_with_resources(
557            QueryExecutionResourceLimits::default(),
558            AnswerCancellation::default(),
559        )
560    }
561
562    /// Start one owner-branded query authoring session with one common
563    /// tighten-only resource policy and caller-owned cancellation signal.
564    pub fn query_with_resources(
565        &self,
566        resources: QueryExecutionResourceLimits,
567        cancellation: AnswerCancellation,
568    ) -> Result<QuerySession<'_, S>> {
569        let registry = self.match_registry().ok_or_else(schema_not_bound)?;
570        let installed = self
571            .installed_schema()
572            .map(Arc::as_ref)
573            .ok_or_else(schema_not_bound)?;
574        Ok(QuerySession::new(
575            installed,
576            Arc::clone(registry),
577            QueryExecution::Local(self),
578            self.operation_limits(resources),
579            cancellation,
580        ))
581    }
582}
583
584impl<'db, S: Schema> QuerySession<'db, S> {
585    fn new(
586        installed: &'db InstalledRuntimeProjection,
587        registry: Arc<DescriptorRegistry>,
588        execution: QueryExecution<'db, S>,
589        resources: QueryExecutionResourceLimits,
590        cancellation: AnswerCancellation,
591    ) -> Self {
592        Self {
593            cancellation,
594            installed,
595            execution,
596            resources: resources.effective(),
597            session: OrmSessionHandle::new(Arc::clone(&registry)),
598            registry,
599            nonce: QUERY_SESSION_NONCE.fetch_add(1, Ordering::Relaxed),
600            bindings: Vec::new(),
601            closed: AtomicBool::new(false),
602            marker: PhantomData,
603        }
604    }
605
606    pub(crate) fn borrowed(
607        installed: &'db InstalledRuntimeProjection,
608        registry: Arc<DescriptorRegistry>,
609        transaction: &'db type_bridge_orm::session::context::TransactionContext,
610        resources: QueryExecutionResourceLimits,
611        cancellation: AnswerCancellation,
612    ) -> Self {
613        Self::new(
614            installed,
615            registry,
616            QueryExecution::Borrowed(transaction),
617            resources,
618            cancellation,
619        )
620    }
621
622    pub(crate) fn remote(
623        installed: &'db InstalledRuntimeProjection,
624        registry: Arc<DescriptorRegistry>,
625        remote: &'db crate::remote::RemoteDatabase<S>,
626        resources: QueryExecutionResourceLimits,
627        cancellation: AnswerCancellation,
628    ) -> Self {
629        Self::new(
630            installed,
631            registry,
632            QueryExecution::Remote(remote),
633            resources,
634            cancellation,
635        )
636    }
637
638    fn begin_invocation(&self) -> Result<QueryExecutionDeadline> {
639        self.ensure_open(ModelValidationPhase::Input)?;
640        let deadline = QueryExecutionDeadline::for_limits(self.resources);
641        self.check_invocation(deadline, ModelValidationPhase::Input)?;
642        Ok(deadline)
643    }
644
645    fn check_invocation(
646        &self,
647        deadline: QueryExecutionDeadline,
648        phase: ModelValidationPhase,
649    ) -> Result<()> {
650        self.ensure_open(phase)?;
651        deadline
652            .check(&self.cancellation)
653            .map_err(|error| Error::from_sdk_execution(error, phase))
654    }
655
656    fn ensure_open(&self, phase: ModelValidationPhase) -> Result<()> {
657        if self.closed.load(Ordering::Acquire) {
658            Err(Error::from_sdk_execution(
659                type_bridge_orm::query_resource_closed_diagnostic(),
660                phase,
661            ))
662        } else {
663            Ok(())
664        }
665    }
666
667    fn uses_successor_projected_materialization(&self) -> bool {
668        self.installed.projection().generator_handlers() == [ProjectionHandler::rust_v2()]
669    }
670
671    fn projected_query_origin(&self) -> Result<ProjectedQueryOrigin> {
672        match &self.execution {
673            QueryExecution::Local(database) => {
674                Ok(ProjectedQueryOrigin::for_database(database.inner_orm()))
675            }
676            QueryExecution::Borrowed(transaction) => {
677                ProjectedQueryOrigin::for_transaction(transaction).map_err(|error| {
678                    Error::from_sdk_execution(error, ModelValidationPhase::Hydration)
679                })
680            }
681            QueryExecution::Remote(_) => Ok(ProjectedQueryOrigin::remote_unbound()),
682        }
683    }
684
685    fn materialize_projected_query_value(
686        &self,
687        validated: &ValidatedMatchRequest,
688        result: &ValidatedMatchResult,
689        deadline: QueryExecutionDeadline,
690    ) -> Result<ProjectedQueryValue> {
691        self.check_invocation(deadline, ModelValidationPhase::Hydration)?;
692        self.projected_query_origin()?
693            .materialize_borrowed_with_budget(
694                self.installed,
695                &self.registry,
696                validated,
697                result,
698                self.resources.projected(),
699                &self.cancellation,
700                Some(deadline),
701            )
702            .map(|(value, _measure)| value)
703            .map_err(|error| Error::from_sdk_execution(error, ModelValidationPhase::Hydration))
704    }
705}
706
707impl<'db, S: Schema> QuerySession<'db, S> {
708    /// Explicitly close this authoring session.
709    ///
710    /// Closing is idempotent. Existing query lineages retain their immutable
711    /// values but reject later composition and execution before provider I/O.
712    pub fn close(&self) {
713        self.closed.store(true, Ordering::Release);
714    }
715
716    /// Return whether this session was explicitly closed.
717    #[must_use]
718    pub fn is_closed(&self) -> bool {
719        self.closed.load(Ordering::Acquire)
720    }
721
722    fn push_binding<M: ThingModel<Schema = S>, Mode: SelectionMode>(
723        &mut self,
724        handle: OrmBindingHandle,
725    ) -> Result<Binding<S, M, Mode>> {
726        let index = u32::try_from(self.bindings.len()).map_err(|source| {
727            Error::model_validation(
728                ModelValidationPhase::Input,
729                "too_many_bindings",
730                vec![],
731                "query session binding capacity exceeded",
732                Some(Box::new(source)),
733            )
734        })?;
735        self.bindings.push(handle);
736        Ok(Binding {
737            key: BindingKey {
738                nonce: self.nonce,
739                index,
740            },
741            marker: PhantomData,
742        })
743    }
744
745    /// Allocate a fresh exact-match binding for one concrete complete
746    /// generated model. Abstract models have no exact binding constructor.
747    pub fn exact<M>(&mut self) -> Result<Binding<S, M, Exact>>
748    where
749        M: ThingModel<Schema = S> + CompleteModel,
750    {
751        self.ensure_open(ModelValidationPhase::Input)?;
752        let label = model_label(M::TYPE_ID_JSON)?;
753        let handle = self.session.exact(&label).map_err(Error::from_orm)?;
754        self.push_binding(handle)
755    }
756
757    /// Allocate a fresh subtype-inclusive binding for one generated subtype
758    /// root; results materialize as the generated leaf or closed family.
759    pub fn subtypes<M>(&mut self) -> Result<Binding<S, M, Subtypes>>
760    where
761        M: ThingModel<Schema = S> + SubtypeRootModel,
762    {
763        self.ensure_open(ModelValidationPhase::Input)?;
764        let label = model_label(M::TYPE_ID_JSON)?;
765        let handle = self.session.subtypes(&label).map_err(Error::from_orm)?;
766        self.push_binding(handle)
767    }
768
769    pub(crate) fn handle_by_key(&self, key: BindingKey) -> Result<&OrmBindingHandle> {
770        self.ensure_open(ModelValidationPhase::Input)?;
771        if key.nonce != self.nonce {
772            return Err(cross_session_handle());
773        }
774        self.bindings
775            .get(key.index as usize)
776            .ok_or_else(cross_session_handle)
777    }
778
779    /// Brand one generated projected attribute wrapper as a scalar
780    /// schema-function input. Generated packages expose domain-specific
781    /// constructors and keep this generic seam out of their public vocabulary.
782    #[doc(hidden)]
783    pub fn __function_input<Value>(&self, value: &Value) -> Result<FunctionInput<S, Value::Domain>>
784    where
785        Value: Model<Schema = S> + QueryValued,
786    {
787        self.ensure_open(ModelValidationPhase::Input)?;
788        let attribute_type = from_canonical_json::<TypeId>(Value::TYPE_ID_JSON.as_bytes())
789            .map_err(|source| {
790                Error::model_validation(
791                    ModelValidationPhase::Input,
792                    "invalid_function_attribute_identity",
793                    vec!["function".into()],
794                    "generated function input attribute identity is not canonical",
795                    Some(Box::new(source)),
796                )
797            })?;
798        if attribute_type.kind() != TypeKind::Attribute {
799            return Err(Error::model_validation(
800                ModelValidationPhase::Input,
801                "function_input_not_attribute",
802                vec!["function".into()],
803                "generated scalar function input must be a projected attribute wrapper",
804                None,
805            ));
806        }
807        let expression = FunctionInputExpr {
808            attribute_type,
809            value: encoded_query_operand(value.into_encoded_scalar()),
810        };
811        self.lower_function_input(&expression)?;
812        Ok(FunctionInput {
813            nonce: self.nonce,
814            expression,
815            marker: PhantomData,
816        })
817    }
818
819    /// Construct one exact projected scalar function call from a generated
820    /// nominal function token and already session-branded arguments.
821    #[doc(hidden)]
822    pub fn __call_function<Arguments, Output>(
823        &self,
824        function: FunctionToken<S, Arguments, Output>,
825        arguments: impl IntoIterator<Item = FunctionArgument<S>>,
826    ) -> Result<FunctionCall<S, Output>> {
827        self.ensure_open(ModelValidationPhase::Input)?;
828        let arguments = arguments.into_iter().collect::<Vec<_>>();
829        if arguments
830            .iter()
831            .any(|argument| argument.nonce != self.nonce)
832        {
833            return Err(cross_session_handle());
834        }
835        let expression = FunctionCallExpr {
836            function_id: function.__function_id().to_owned(),
837            arguments: arguments
838                .into_iter()
839                .map(|argument| argument.expression)
840                .collect(),
841        };
842        self.lower_function_call(&expression)?;
843        Ok(FunctionCall {
844            nonce: self.nonce,
845            expression,
846            marker: PhantomData,
847        })
848    }
849
850    fn lower_function_input(
851        &self,
852        input: &FunctionInputExpr,
853    ) -> Result<type_bridge_orm::FunctionValueHandle> {
854        let projected = ProjectedAttributeValue::try_from_attribute_value(
855            self.installed,
856            input.attribute_type.clone(),
857            &input.value,
858        )
859        .map_err(|error| Error::from_sdk_execution(error, ModelValidationPhase::Input))?;
860        self.session
861            .function_value(&projected)
862            .map_err(Error::from_orm)
863    }
864
865    fn lower_function_argument(
866        &self,
867        argument: &FunctionArgumentExpr,
868    ) -> Result<OrmFunctionArgumentHandle> {
869        match argument {
870            FunctionArgumentExpr::Binding(binding) => {
871                Ok(self.handle_by_key(*binding)?.function_argument())
872            }
873            FunctionArgumentExpr::Value(value) => {
874                Ok(self.lower_function_input(value)?.function_argument())
875            }
876            FunctionArgumentExpr::Call(call) => {
877                Ok(self.lower_function_call(call)?.function_argument())
878            }
879        }
880    }
881
882    fn lower_function_call(&self, call: &FunctionCallExpr) -> Result<OrmFunctionCallHandle> {
883        let id = FunctionId::new(call.function_id.clone()).map_err(|source| {
884            Error::model_validation(
885                ModelValidationPhase::Input,
886                "invalid_function_identity",
887                vec!["function".into()],
888                "generated function identity is not canonical",
889                Some(Box::new(source)),
890            )
891        })?;
892        let function = self.session.function(&id).map_err(Error::from_orm)?;
893        let arguments = call
894            .arguments
895            .iter()
896            .map(|argument| self.lower_function_argument(argument))
897            .collect::<Result<Vec<_>>>()?;
898        function.call(arguments).map_err(Error::from_orm)
899    }
900
901    fn installed(&self) -> Result<&InstalledRuntimeProjection> {
902        Ok(self.installed)
903    }
904
905    fn field_name_for(&self, owner_label: &str, attribute_label: &str) -> Result<String> {
906        let descriptor = self.registry.get(owner_label).ok_or_else(|| {
907            Error::model_validation(
908                ModelValidationPhase::Input,
909                "unknown_field_owner",
910                vec!["type".into()],
911                format!("field owner '{owner_label}' is not registered in this session"),
912                None,
913            )
914        })?;
915        let found = match &descriptor {
916            TypeDescriptorRef::Entity(entity) => entity
917                .owned_attributes
918                .iter()
919                .find(|attribute| attribute.attr_name == attribute_label)
920                .map(|attribute| attribute.field_name.clone()),
921            TypeDescriptorRef::Relation(relation) => relation
922                .owned_attributes
923                .iter()
924                .find(|attribute| attribute.attr_name == attribute_label)
925                .map(|attribute| attribute.field_name.clone()),
926        };
927        found.ok_or_else(|| {
928            Error::model_validation(
929                ModelValidationPhase::Input,
930                "unknown_field",
931                vec!["type".into()],
932                format!("owner '{owner_label}' has no field for attribute '{attribute_label}'"),
933                None,
934            )
935        })
936    }
937
938    pub(crate) fn lower_field(
939        &self,
940        key: BindingKey,
941        owns_id_json: &'static str,
942    ) -> Result<OrmFieldHandle> {
943        let handle = self.handle_by_key(key)?;
944        let (owner_label, attribute_label) = parse_owns_identity(owns_id_json)?;
945        let field_name = self.field_name_for(&owner_label, &attribute_label)?;
946        handle
947            .field_owned_by(&owner_label, &field_name)
948            .map_err(Error::from_orm)
949    }
950
951    fn lower_order(&self, order: &Order<S>) -> Result<OrmOrderHandle> {
952        let field = self.lower_field(order.key, order.owns_id_json)?;
953        Ok(field.order(order.direction, order.missing))
954    }
955
956    fn lower_predicate(&self, expr: &PredicateExpr) -> Result<OrmPredicateHandle> {
957        match expr {
958            PredicateExpr::FieldValue {
959                binding,
960                owns_id_json,
961                operator,
962                value,
963            } => {
964                let field = self.lower_field(*binding, owns_id_json)?;
965                Ok(field.compare_value(*operator, value.clone()))
966            }
967            PredicateExpr::FieldField {
968                left_binding,
969                left_owns_id_json,
970                operator,
971                right_binding,
972                right_owns_id_json,
973            } => {
974                let left = self.lower_field(*left_binding, left_owns_id_json)?;
975                let right = self.lower_field(*right_binding, right_owns_id_json)?;
976                left.compare_field(*operator, &right)
977                    .map_err(Error::from_orm)
978            }
979            PredicateExpr::FieldPresence {
980                binding,
981                owns_id_json,
982                present,
983            } => Ok(self.lower_field(*binding, owns_id_json)?.presence(*present)),
984            PredicateExpr::FunctionField {
985                call,
986                operator,
987                binding,
988                owns_id_json,
989            } => self
990                .lower_function_call(call)?
991                .compare_field(*operator, &self.lower_field(*binding, owns_id_json)?)
992                .map_err(Error::from_orm),
993            PredicateExpr::FunctionValue {
994                call,
995                operator,
996                value,
997            } => self
998                .lower_function_call(call)?
999                .compare_value(*operator, &self.lower_function_input(value)?)
1000                .map_err(Error::from_orm),
1001            PredicateExpr::FunctionCall {
1002                left,
1003                operator,
1004                right,
1005            } => self
1006                .lower_function_call(left)?
1007                .compare_call(*operator, &self.lower_function_call(right)?)
1008                .map_err(Error::from_orm),
1009            PredicateExpr::BindingIid { binding, iid } => self
1010                .handle_by_key(*binding)?
1011                .iid(iid.clone())
1012                .map_err(Error::from_orm),
1013            PredicateExpr::BindingIidIn { binding, iids } => self
1014                .handle_by_key(*binding)?
1015                .iid_in(iids.clone())
1016                .map_err(Error::from_orm),
1017            PredicateExpr::Connects {
1018                relation,
1019                role_id_json,
1020                player,
1021            } => {
1022                let relation_handle = self.handle_by_key(*relation)?;
1023                let role = parse_role_identity(role_id_json)?;
1024                let role_handle = relation_handle
1025                    .role_owned_by(role.declaring_relation().as_str(), role.label().as_str())
1026                    .map_err(Error::from_orm)?;
1027                let player_handle = self.handle_by_key(*player)?;
1028                role_handle.connects(player_handle).map_err(Error::from_orm)
1029            }
1030            PredicateExpr::Reachable {
1031                relation_type_id_json,
1032                role_from_id_json,
1033                role_to_id_json,
1034                source,
1035                target,
1036                min_depth,
1037                max_depth,
1038            } => {
1039                let relation = model_label(relation_type_id_json)?;
1040                let role_from = parse_role_identity(role_from_id_json)?;
1041                let role_to = parse_role_identity(role_to_id_json)?;
1042                self.session
1043                    .reachable(
1044                        &relation,
1045                        role_from.label().as_str(),
1046                        role_to.label().as_str(),
1047                        self.handle_by_key(*source)?,
1048                        self.handle_by_key(*target)?,
1049                        *min_depth,
1050                        *max_depth,
1051                    )
1052                    .map_err(Error::from_orm)
1053            }
1054            PredicateExpr::And(terms) => self.lower_composed(terms, |left, right| {
1055                left.and(right).map_err(Error::from_orm)
1056            }),
1057            PredicateExpr::Or(terms) => {
1058                self.lower_composed(terms, |left, right| left.or(right).map_err(Error::from_orm))
1059            }
1060            PredicateExpr::Not(inner) => Ok(self.lower_predicate(inner)?.not()),
1061        }
1062    }
1063
1064    fn lower_composed(
1065        &self,
1066        terms: &[PredicateExpr],
1067        combine: impl Fn(&OrmPredicateHandle, &OrmPredicateHandle) -> Result<OrmPredicateHandle>,
1068    ) -> Result<OrmPredicateHandle> {
1069        let mut lowered = terms.iter().map(|term| self.lower_predicate(term));
1070        let mut combined = lowered.next().ok_or_else(|| {
1071            Error::model_validation(
1072                ModelValidationPhase::Input,
1073                "empty_predicate",
1074                vec![],
1075                "boolean composition requires at least one predicate",
1076                None,
1077            )
1078        })??;
1079        for term in lowered {
1080            combined = combine(&combined, &term?)?;
1081        }
1082        Ok(combined)
1083    }
1084
1085    fn client_row_for(&self, thing: &HydratedThing) -> Result<HydratedRow> {
1086        use type_bridge_orm::match_request::model::ThingKind as OrmThingKind;
1087        let installed = self.installed()?;
1088        let type_name = self
1089            .registry
1090            .descriptor_type_name(thing.concrete_descriptor())
1091            .ok_or_else(|| {
1092                Error::model_validation(
1093                    ModelValidationPhase::Hydration,
1094                    "invalid_installed_projection",
1095                    vec!["type".into()],
1096                    "selected concrete descriptor is absent from the installed registry",
1097                    None,
1098                )
1099            })?;
1100        let mut attributes = Vec::new();
1101        for attribute in thing.attributes() {
1102            let provider_name = self
1103                .registry
1104                .provider_attribute_name(attribute.field())
1105                .ok_or_else(|| {
1106                    Error::model_validation(
1107                        ModelValidationPhase::Hydration,
1108                        "invalid_installed_projection",
1109                        vec![],
1110                        "selected field identity has no provider attribute name",
1111                        None,
1112                    )
1113                })?;
1114            for value in attribute.values() {
1115                attributes.push((
1116                    provider_name.clone(),
1117                    canonicalize_selected_value(value.clone())?,
1118                ));
1119            }
1120        }
1121        match thing.kind() {
1122            OrmThingKind::Entity => {
1123                let id = TypeId::new(type_bridge_contract::id::TypeKind::Entity, &type_name)
1124                    .map_err(|source| {
1125                        Error::model_validation(
1126                            ModelValidationPhase::Hydration,
1127                            "invalid_discovered_type",
1128                            vec!["type".into()],
1129                            "selected entity type label is invalid",
1130                            Some(Box::new(source)),
1131                        )
1132                    })?;
1133                hydrate_entity(
1134                    DynamicEntityRow {
1135                        iid: Some(thing.concept_id().as_str().to_owned()),
1136                        type_name: Some(type_name),
1137                        attributes,
1138                    },
1139                    &id,
1140                    installed,
1141                )
1142            }
1143            OrmThingKind::Relation => {
1144                let id = TypeId::new(type_bridge_contract::id::TypeKind::Relation, &type_name)
1145                    .map_err(|source| {
1146                        Error::model_validation(
1147                            ModelValidationPhase::Hydration,
1148                            "invalid_discovered_type",
1149                            vec!["type".into()],
1150                            "selected relation type label is invalid",
1151                            Some(Box::new(source)),
1152                        )
1153                    })?;
1154                let mut role_players = Vec::new();
1155                for role in thing.roles() {
1156                    for player in role.players() {
1157                        let mut raw = Vec::new();
1158                        for attribute in player.attributes() {
1159                            let provider_name = self
1160                                .registry
1161                                .provider_attribute_name(attribute.field())
1162                                .ok_or_else(|| {
1163                                    Error::model_validation(
1164                                        ModelValidationPhase::Hydration,
1165                                        "invalid_installed_projection",
1166                                        vec![],
1167                                        "player field identity has no provider attribute name",
1168                                        None,
1169                                    )
1170                                })?;
1171                            for value in attribute.values() {
1172                                raw.push((
1173                                    provider_name.clone(),
1174                                    plain_json(&canonicalize_selected_value(value.clone())?),
1175                                ));
1176                            }
1177                        }
1178                        let player_type_name = self
1179                            .registry
1180                            .descriptor_type_name(player.concrete_descriptor())
1181                            .ok_or_else(|| {
1182                                Error::model_validation(
1183                                    ModelValidationPhase::Hydration,
1184                                    "invalid_installed_projection",
1185                                    vec!["roles".into(), role.role().name.clone()],
1186                                    "selected role-player descriptor is absent from the installed registry",
1187                                    None,
1188                                )
1189                            })?;
1190                        role_players.push(DynamicRolePlayer {
1191                            role_name: role.role().name.clone(),
1192                            player_iid: Some(player.concept_id().as_str().to_owned()),
1193                            player_type_name: Some(player_type_name),
1194                            attributes: raw,
1195                        });
1196                    }
1197                }
1198                hydrate_relation(
1199                    DynamicRelationRow {
1200                        iid: Some(thing.concept_id().as_str().to_owned()),
1201                        type_name: Some(type_name),
1202                        attributes,
1203                        role_players,
1204                    },
1205                    &id,
1206                    installed,
1207                )
1208            }
1209        }
1210    }
1211
1212    fn client_row_for_projected(&self, thing: &ProjectedThing) -> Result<HydratedRow> {
1213        projected_to_hydrated_row(thing, self.installed()?)
1214    }
1215}
1216
1217fn canonicalize_selected_value(value: AttributeValue) -> Result<AttributeValue> {
1218    let malformed = || {
1219        Error::model_validation(
1220            ModelValidationPhase::Hydration,
1221            "hydrated_attribute_value_type",
1222            vec![],
1223            "selected attribute value is outside its canonical scalar domain",
1224            None,
1225        )
1226    };
1227    match value {
1228        AttributeValue::Date(value) => value
1229            .parse::<CanonicalDate>()
1230            .map(|value| AttributeValue::Date(value.to_string()))
1231            .map_err(|_| malformed()),
1232        AttributeValue::DateTime(value) => normalize_provider_fraction(value)
1233            .parse::<CanonicalDateTime>()
1234            .map(|value| AttributeValue::DateTime(value.to_string()))
1235            .map_err(|_| malformed()),
1236        AttributeValue::DateTimeTZ(value) => normalize_provider_datetime_tz(value)
1237            .parse::<CanonicalDateTimeTz>()
1238            .map(|value| AttributeValue::DateTimeTZ(value.to_string()))
1239            .map_err(|_| malformed()),
1240        AttributeValue::Decimal(value) => parse_decimal(&value)
1241            .map(|value| AttributeValue::Decimal(value.canonical_string()))
1242            .ok_or_else(malformed),
1243        AttributeValue::Duration(value) => {
1244            let value = normalize_provider_fraction(value);
1245            match value.parse::<CanonicalDuration>() {
1246                Ok(value) => Ok(AttributeValue::Duration(value.to_string())),
1247                Err(_) => CompatibilityValueV2::released_duration(value.clone())
1248                    .map(|_| AttributeValue::Duration(value))
1249                    .map_err(|_| malformed()),
1250            }
1251        }
1252        value => Ok(value),
1253    }
1254}
1255
1256fn encoded_group_scalar(value: &AttributeValue) -> Result<EncodedScalar> {
1257    let value = canonicalize_selected_value(value.clone())?;
1258    let map = |error| map_validation_error(error, ModelValidationPhase::Hydration);
1259    match value {
1260        AttributeValue::String(value) => Ok(EncodedScalar::String(value)),
1261        AttributeValue::Long(value) => Ok(EncodedScalar::Long(value)),
1262        AttributeValue::Double(value) => crate::__codegen::CanonicalDouble::try_new(value)
1263            .map(EncodedScalar::Double)
1264            .map_err(map),
1265        AttributeValue::Boolean(value) => Ok(EncodedScalar::Boolean(value)),
1266        AttributeValue::Date(value) => crate::__codegen::Date::try_new(value)
1267            .map(EncodedScalar::Date)
1268            .map_err(map),
1269        AttributeValue::DateTime(value) => crate::__codegen::DateTime::try_new(value)
1270            .map(EncodedScalar::DateTime)
1271            .map_err(map),
1272        AttributeValue::DateTimeTZ(value) => crate::__codegen::DateTimeTz::try_new(value)
1273            .map(EncodedScalar::DateTimeTz)
1274            .map_err(map),
1275        AttributeValue::Decimal(value) => crate::__codegen::Decimal::try_new(value)
1276            .map(EncodedScalar::Decimal)
1277            .map_err(map),
1278        AttributeValue::Duration(value) => crate::__codegen::Duration::try_new(value)
1279            .map(EncodedScalar::Duration)
1280            .map_err(map),
1281    }
1282}
1283
1284fn decoded_projected_reduction_values(values: &[ProjectedReducedValue]) -> Vec<ReducedValue> {
1285    values
1286        .iter()
1287        .map(|value| match value {
1288            ProjectedReducedValue::Count(value) => ReducedValue::Count(*value),
1289            ProjectedReducedValue::Long(value) => ReducedValue::Long(*value),
1290            ProjectedReducedValue::Double(value) => {
1291                ReducedValue::Double(value.map(type_bridge_contract::value::CanonicalDouble::get))
1292            }
1293        })
1294        .collect()
1295}
1296
1297fn normalize_provider_datetime_tz(value: String) -> String {
1298    let mut normalized = normalize_provider_fraction(value);
1299    for zero_offset in ["+00:00:00", "-00:00:00", "+00:00", "-00:00"] {
1300        if normalized.ends_with(zero_offset) {
1301            normalized.truncate(normalized.len() - zero_offset.len());
1302            normalized.push('Z');
1303            break;
1304        }
1305    }
1306    normalized
1307}
1308
1309fn normalize_provider_fraction(value: String) -> String {
1310    let Some(dot) = value.find('.') else {
1311        return value;
1312    };
1313    let fraction_end = value[dot + 1..]
1314        .find(|character: char| !character.is_ascii_digit())
1315        .map_or(value.len(), |offset| dot + 1 + offset);
1316    let trimmed_end = value[dot + 1..fraction_end].trim_end_matches('0').len() + dot + 1;
1317    if trimmed_end == fraction_end {
1318        return value;
1319    }
1320    let mut normalized = String::with_capacity(value.len());
1321    normalized.push_str(
1322        &value[..if trimmed_end == dot + 1 {
1323            dot
1324        } else {
1325            trimmed_end
1326        }],
1327    );
1328    normalized.push_str(&value[fraction_end..]);
1329    normalized
1330}
1331
1332fn plain_json(value: &AttributeValue) -> serde_json::Value {
1333    match value {
1334        AttributeValue::String(value) => serde_json::Value::String(value.clone()),
1335        AttributeValue::Long(value) => serde_json::Value::from(*value),
1336        AttributeValue::Double(value) => {
1337            serde_json::Number::from_f64(*value).map_or(serde_json::Value::Null, Into::into)
1338        }
1339        AttributeValue::Boolean(value) => serde_json::Value::Bool(*value),
1340        AttributeValue::Date(value)
1341        | AttributeValue::DateTime(value)
1342        | AttributeValue::DateTimeTZ(value)
1343        | AttributeValue::Decimal(value)
1344        | AttributeValue::Duration(value) => serde_json::Value::String(value.clone()),
1345    }
1346}
1347
1348/// Sealed conversion from client literals and generated value wrappers into
1349/// canonical query operands.
1350pub trait QueryOperand: operand_sealed::Sealed {
1351    #[doc(hidden)]
1352    type Domain;
1353
1354    #[doc(hidden)]
1355    fn into_operand(self) -> AttributeValue;
1356}
1357
1358/// Sealed marker for canonically ordered query operands.
1359pub trait OrderedOperand: QueryOperand {}
1360
1361mod operand_sealed {
1362    pub trait Sealed {}
1363}
1364
1365macro_rules! operand {
1366    ($ty:ty, $domain:ty, $self_:ident => $convert:expr, ordered: $ordered:tt) => {
1367        impl operand_sealed::Sealed for $ty {}
1368        impl QueryOperand for $ty {
1369            type Domain = $domain;
1370
1371            fn into_operand($self_) -> AttributeValue {
1372                $convert
1373            }
1374        }
1375        operand!(@ordered $ty, $ordered);
1376    };
1377    (@ordered $ty:ty, true) => {
1378        impl OrderedOperand for $ty {}
1379    };
1380    (@ordered $ty:ty, false) => {};
1381}
1382
1383fn encoded_query_operand(value: EncodedScalar) -> AttributeValue {
1384    match value {
1385        EncodedScalar::String(value) => AttributeValue::String(value),
1386        EncodedScalar::Long(value) => AttributeValue::Long(value),
1387        EncodedScalar::Double(value) => AttributeValue::Double(value.get()),
1388        EncodedScalar::Boolean(value) => AttributeValue::Boolean(value),
1389        EncodedScalar::Date(value) => AttributeValue::Date(value.as_str().to_owned()),
1390        EncodedScalar::DateTime(value) => AttributeValue::DateTime(value.as_str().to_owned()),
1391        EncodedScalar::DateTimeTz(value) => AttributeValue::DateTimeTZ(value.as_str().to_owned()),
1392        EncodedScalar::Decimal(value) => AttributeValue::Decimal(value.as_str().to_owned()),
1393        EncodedScalar::Duration(value) => AttributeValue::Duration(value.as_str().to_owned()),
1394    }
1395}
1396
1397impl<T: QueryValued> operand_sealed::Sealed for T {}
1398impl<T: QueryValued> QueryOperand for T {
1399    type Domain = T::Domain;
1400
1401    fn into_operand(self) -> AttributeValue {
1402        encoded_query_operand(self.into_encoded_scalar())
1403    }
1404}
1405
1406impl OrderedOperand for i64 {}
1407
1408operand!(
1409    crate::value::Text,
1410    String,
1411    self => AttributeValue::String(self.into_string()),
1412    ordered: false
1413);
1414operand!(
1415    crate::value::Double,
1416    crate::__codegen::CanonicalDouble,
1417    self => AttributeValue::Double(self.get()),
1418    ordered: true
1419);
1420operand!(
1421    crate::value::Decimal,
1422    crate::__codegen::Decimal,
1423    self => AttributeValue::Decimal(self.into_string()),
1424    ordered: true
1425);
1426operand!(
1427    crate::value::Date,
1428    crate::__codegen::Date,
1429    self => AttributeValue::Date(self.into_string()),
1430    ordered: true
1431);
1432operand!(
1433    crate::value::DateTime,
1434    crate::__codegen::DateTime,
1435    self => AttributeValue::DateTime(self.into_string()),
1436    ordered: true
1437);
1438operand!(
1439    crate::value::DateTimeTz,
1440    crate::__codegen::DateTimeTz,
1441    self => AttributeValue::DateTimeTZ(self.into_string()),
1442    ordered: true
1443);
1444operand!(
1445    crate::value::Duration,
1446    crate::__codegen::Duration,
1447    self => AttributeValue::Duration(self.into_string()),
1448    ordered: true
1449);
1450
1451#[derive(Clone, Debug, PartialEq)]
1452pub(crate) enum PredicateExpr {
1453    FieldValue {
1454        binding: BindingKey,
1455        owns_id_json: &'static str,
1456        operator: ComparisonOp,
1457        value: AttributeValue,
1458    },
1459    FieldField {
1460        left_binding: BindingKey,
1461        left_owns_id_json: &'static str,
1462        operator: ComparisonOp,
1463        right_binding: BindingKey,
1464        right_owns_id_json: &'static str,
1465    },
1466    FieldPresence {
1467        binding: BindingKey,
1468        owns_id_json: &'static str,
1469        present: bool,
1470    },
1471    FunctionField {
1472        call: FunctionCallExpr,
1473        operator: ComparisonOp,
1474        binding: BindingKey,
1475        owns_id_json: &'static str,
1476    },
1477    FunctionValue {
1478        call: FunctionCallExpr,
1479        operator: ComparisonOp,
1480        value: FunctionInputExpr,
1481    },
1482    FunctionCall {
1483        left: FunctionCallExpr,
1484        operator: ComparisonOp,
1485        right: FunctionCallExpr,
1486    },
1487    BindingIid {
1488        binding: BindingKey,
1489        iid: String,
1490    },
1491    BindingIidIn {
1492        binding: BindingKey,
1493        iids: Vec<String>,
1494    },
1495    Connects {
1496        relation: BindingKey,
1497        role_id_json: &'static str,
1498        player: BindingKey,
1499    },
1500    Reachable {
1501        relation_type_id_json: &'static str,
1502        role_from_id_json: &'static str,
1503        role_to_id_json: &'static str,
1504        source: BindingKey,
1505        target: BindingKey,
1506        min_depth: u8,
1507        max_depth: u8,
1508    },
1509    And(Vec<PredicateExpr>),
1510    Or(Vec<PredicateExpr>),
1511    Not(Box<PredicateExpr>),
1512}
1513
1514/// One schema-branded, composable query predicate.
1515///
1516/// Operators are domain-restricted at construction; predicates compose with
1517/// `&`, `|`, and `!` (or the named [`Predicate::and`], [`Predicate::or`],
1518/// and [`Predicate::not`]) and are validated against the owning session
1519/// before any executor invocation.
1520#[derive(Debug, PartialEq)]
1521pub struct Predicate<S: Schema> {
1522    pub(crate) expr: PredicateExpr,
1523    marker: PhantomData<fn() -> S>,
1524}
1525
1526impl<S: Schema> Clone for Predicate<S> {
1527    fn clone(&self) -> Self {
1528        Self {
1529            expr: self.expr.clone(),
1530            marker: PhantomData,
1531        }
1532    }
1533}
1534
1535impl<S: Schema> Predicate<S> {
1536    fn new(expr: PredicateExpr) -> Self {
1537        Self {
1538            expr,
1539            marker: PhantomData,
1540        }
1541    }
1542
1543    /// Conjunction; equivalent to `self & other`.
1544    #[must_use]
1545    pub fn and(self, other: Predicate<S>) -> Predicate<S> {
1546        self & other
1547    }
1548
1549    /// Disjunction; equivalent to `self | other`.
1550    #[must_use]
1551    pub fn or(self, other: Predicate<S>) -> Predicate<S> {
1552        self | other
1553    }
1554
1555    /// Negation; equivalent to `!self`.
1556    #[must_use]
1557    #[allow(clippy::should_implement_trait)]
1558    pub fn not(self) -> Predicate<S> {
1559        !self
1560    }
1561}
1562
1563impl<'db, S: Schema> QuerySession<'db, S> {
1564    /// Require a bounded directed walk between two generated endpoint
1565    /// bindings through one exact generated relation.
1566    ///
1567    /// Each hop follows `role_from -> role_to`. Bounds are inclusive and a
1568    /// zero-hop branch requires identical endpoint concepts. Generated role
1569    /// compatibility and player-union evidence reject inactive roles and
1570    /// invalid endpoint models at compile time; bounds, session ownership,
1571    /// and installed-schema compatibility are validated before provider I/O.
1572    #[allow(clippy::too_many_arguments)]
1573    pub fn reachable<
1574        R,
1575        FromOwner,
1576        FromPlayers,
1577        ToOwner,
1578        ToPlayers,
1579        Source,
1580        SourceMode,
1581        Target,
1582        TargetMode,
1583    >(
1584        &self,
1585        relation: TypeToken<R>,
1586        role_from: RoleToken<FromOwner, FromPlayers>,
1587        role_to: RoleToken<ToOwner, ToPlayers>,
1588        source: Binding<S, Source, SourceMode>,
1589        target: Binding<S, Target, TargetMode>,
1590        min_depth: u8,
1591        max_depth: u8,
1592    ) -> Result<Predicate<S>>
1593    where
1594        R: RelationModel<Schema = S>
1595            + CompleteModel
1596            + RoleTokenCompatible<FromOwner, FromPlayers>
1597            + RoleTokenCompatible<ToOwner, ToPlayers>,
1598        FromOwner: RelationModel<Schema = S>,
1599        ToOwner: RelationModel<Schema = S>,
1600        FromPlayers: RolePlayerBinding<Source, SourceMode>,
1601        ToPlayers: RolePlayerBinding<Target, TargetMode>,
1602        Source: ThingModel<Schema = S>,
1603        SourceMode: SelectionMode,
1604        Target: ThingModel<Schema = S>,
1605        TargetMode: SelectionMode,
1606    {
1607        let predicate = Predicate::new(PredicateExpr::Reachable {
1608            relation_type_id_json: relation.type_id_json(),
1609            role_from_id_json: role_from.role_id_json(),
1610            role_to_id_json: role_to.role_id_json(),
1611            source: source.key,
1612            target: target.key,
1613            min_depth,
1614            max_depth,
1615        });
1616        self.lower_predicate(&predicate.expr)?;
1617        Ok(predicate)
1618    }
1619}
1620
1621impl<S: Schema> std::ops::BitAnd for Predicate<S> {
1622    type Output = Predicate<S>;
1623    fn bitand(self, other: Predicate<S>) -> Predicate<S> {
1624        let mut terms = match self.expr {
1625            PredicateExpr::And(terms) => terms,
1626            expr => vec![expr],
1627        };
1628        match other.expr {
1629            PredicateExpr::And(more) => terms.extend(more),
1630            expr => terms.push(expr),
1631        }
1632        Predicate::new(PredicateExpr::And(terms))
1633    }
1634}
1635
1636impl<S: Schema> std::ops::BitOr for Predicate<S> {
1637    type Output = Predicate<S>;
1638    fn bitor(self, other: Predicate<S>) -> Predicate<S> {
1639        let mut terms = match self.expr {
1640            PredicateExpr::Or(terms) => terms,
1641            expr => vec![expr],
1642        };
1643        match other.expr {
1644            PredicateExpr::Or(more) => terms.extend(more),
1645            expr => terms.push(expr),
1646        }
1647        Predicate::new(PredicateExpr::Or(terms))
1648    }
1649}
1650
1651impl<S: Schema> std::ops::Not for Predicate<S> {
1652    type Output = Predicate<S>;
1653    fn not(self) -> Predicate<S> {
1654        Predicate::new(PredicateExpr::Not(Box::new(self.expr)))
1655    }
1656}
1657
1658/// One generated field resolved against one session binding occurrence.
1659///
1660/// The token retains its declaring owner; owner/binding compatibility is
1661/// enforced against the installed registry when the predicate is lowered,
1662/// before any I/O.
1663pub struct BoundField<S: Schema, Owner: Model<Schema = S>, V> {
1664    key: BindingKey,
1665    owns_id_json: &'static str,
1666    marker: PhantomData<fn() -> (Owner, V)>,
1667}
1668
1669impl<S: Schema, Owner: Model<Schema = S>, V> Copy for BoundField<S, Owner, V> {}
1670impl<S: Schema, Owner: Model<Schema = S>, V> Clone for BoundField<S, Owner, V> {
1671    fn clone(&self) -> Self {
1672        *self
1673    }
1674}
1675
1676impl<S: Schema, M: ThingModel<Schema = S>, Mode: SelectionMode> Binding<S, M, Mode> {
1677    /// Resolve one generated owned field against this binding occurrence.
1678    ///
1679    /// The declaring owner must be this binding's model or a generated
1680    /// nominal ancestor; unrelated same-spelled owners fail to type-check,
1681    /// and the installed registry re-validates the admission at lowering.
1682    #[must_use]
1683    pub fn field<Owner, V>(self, token: FieldToken<Owner, V>) -> BoundField<S, Owner, V>
1684    where
1685        Owner: Model<Schema = S>,
1686        M: crate::__codegen::NominalUpcast<Owner>,
1687    {
1688        BoundField {
1689            key: self.key,
1690            owns_id_json: token.owns_id_json(),
1691            marker: PhantomData,
1692        }
1693    }
1694}
1695
1696impl<S: Schema, M: RelationModel<Schema = S> + ThingModel<Schema = S>, Mode: SelectionMode>
1697    Binding<S, M, Mode>
1698{
1699    /// Resolve one active generated relation role against this relation
1700    /// binding occurrence. Only relation bindings expose roles;
1701    /// specialized-away ancestor tokens have no generated compatibility
1702    /// evidence.
1703    #[must_use]
1704    pub fn role<Owner, Players>(
1705        self,
1706        token: RoleToken<Owner, Players>,
1707    ) -> BoundRole<S, Owner, Players>
1708    where
1709        Owner: RelationModel<Schema = S>,
1710        M: RoleTokenCompatible<Owner, Players>,
1711    {
1712        BoundRole {
1713            key: self.key,
1714            role_id_json: token.role_id_json(),
1715            marker: PhantomData,
1716        }
1717    }
1718}
1719
1720impl<S: Schema, Owner: Model<Schema = S>, V> BoundField<S, Owner, V> {
1721    pub(crate) fn reduction_input(self) -> (BindingKey, &'static str) {
1722        (self.key, self.owns_id_json)
1723    }
1724
1725    fn value_predicate(self, operator: ComparisonOp, value: AttributeValue) -> Predicate<S> {
1726        Predicate::new(PredicateExpr::FieldValue {
1727            binding: self.key,
1728            owns_id_json: self.owns_id_json,
1729            operator,
1730            value,
1731        })
1732    }
1733
1734    /// Equality against a canonical literal of the field's scalar domain.
1735    #[must_use]
1736    pub fn eq<O>(self, operand: O) -> Predicate<S>
1737    where
1738        V: QueryValued,
1739        O: QueryOperand<Domain = V::Domain>,
1740    {
1741        self.value_predicate(ComparisonOp::Equal, operand.into_operand())
1742    }
1743
1744    /// Inequality against a canonical literal of the field's scalar domain.
1745    #[must_use]
1746    pub fn ne<O>(self, operand: O) -> Predicate<S>
1747    where
1748        V: QueryValued,
1749        O: QueryOperand<Domain = V::Domain>,
1750    {
1751        self.value_predicate(ComparisonOp::NotEqual, operand.into_operand())
1752    }
1753
1754    /// Strictly-less ordering against a canonically ordered literal;
1755    /// admitted only for canonically ordered field domains.
1756    #[must_use]
1757    pub fn lt(self, operand: impl OrderedOperand) -> Predicate<S>
1758    where
1759        V: crate::__codegen::OrderedValued,
1760    {
1761        self.value_predicate(ComparisonOp::LessThan, operand.into_operand())
1762    }
1763
1764    /// Less-or-equal ordering against a canonically ordered literal;
1765    /// admitted only for canonically ordered field domains.
1766    #[must_use]
1767    pub fn le(self, operand: impl OrderedOperand) -> Predicate<S>
1768    where
1769        V: crate::__codegen::OrderedValued,
1770    {
1771        self.value_predicate(ComparisonOp::LessThanOrEqual, operand.into_operand())
1772    }
1773
1774    /// Strictly-greater ordering against a canonically ordered literal;
1775    /// admitted only for canonically ordered field domains.
1776    #[must_use]
1777    pub fn gt(self, operand: impl OrderedOperand) -> Predicate<S>
1778    where
1779        V: crate::__codegen::OrderedValued,
1780    {
1781        self.value_predicate(ComparisonOp::GreaterThan, operand.into_operand())
1782    }
1783
1784    /// Greater-or-equal ordering against a canonically ordered literal;
1785    /// admitted only for canonically ordered field domains.
1786    #[must_use]
1787    pub fn ge(self, operand: impl OrderedOperand) -> Predicate<S>
1788    where
1789        V: crate::__codegen::OrderedValued,
1790    {
1791        self.value_predicate(ComparisonOp::GreaterThanOrEqual, operand.into_operand())
1792    }
1793
1794    /// Text containment against bounded canonical text; admitted only for
1795    /// text field domains.
1796    #[must_use]
1797    pub fn contains(self, text: crate::value::Text) -> Predicate<S>
1798    where
1799        V: crate::__codegen::TextValued,
1800    {
1801        self.value_predicate(
1802            ComparisonOp::Contains,
1803            AttributeValue::String(text.into_string()),
1804        )
1805    }
1806
1807    /// Anchored text prefix against bounded canonical text; admitted only
1808    /// for text field domains.
1809    #[must_use]
1810    pub fn starts_with(self, text: crate::value::Text) -> Predicate<S>
1811    where
1812        V: crate::__codegen::TextValued,
1813    {
1814        self.value_predicate(
1815            ComparisonOp::StartsWith,
1816            AttributeValue::String(text.into_string()),
1817        )
1818    }
1819
1820    /// Anchored text suffix against bounded canonical text; admitted only
1821    /// for text field domains.
1822    #[must_use]
1823    pub fn ends_with(self, text: crate::value::Text) -> Predicate<S>
1824    where
1825        V: crate::__codegen::TextValued,
1826    {
1827        self.value_predicate(
1828            ComparisonOp::EndsWith,
1829            AttributeValue::String(text.into_string()),
1830        )
1831    }
1832
1833    /// Regular-expression match against a client-owned validated pattern;
1834    /// admitted only for text field domains.
1835    #[must_use]
1836    pub fn regex(self, pattern: crate::value::Regex) -> Predicate<S>
1837    where
1838        V: crate::__codegen::TextValued,
1839    {
1840        self.value_predicate(
1841            ComparisonOp::Regex,
1842            AttributeValue::String(pattern.into_string()),
1843        )
1844    }
1845
1846    /// Require at least one owned value for this generated field.
1847    #[must_use]
1848    pub fn is_present(self) -> Predicate<S> {
1849        Predicate::new(PredicateExpr::FieldPresence {
1850            binding: self.key,
1851            owns_id_json: self.owns_id_json,
1852            present: true,
1853        })
1854    }
1855
1856    /// Require no owned value for this generated field.
1857    #[must_use]
1858    pub fn is_missing(self) -> Predicate<S> {
1859        Predicate::new(PredicateExpr::FieldPresence {
1860            binding: self.key,
1861            owns_id_json: self.owns_id_json,
1862            present: false,
1863        })
1864    }
1865
1866    fn field_predicate<Owner2, V2>(
1867        self,
1868        operator: ComparisonOp,
1869        other: BoundField<S, Owner2, V2>,
1870    ) -> Predicate<S>
1871    where
1872        Owner2: Model<Schema = S>,
1873    {
1874        Predicate::new(PredicateExpr::FieldField {
1875            left_binding: self.key,
1876            left_owns_id_json: self.owns_id_json,
1877            operator,
1878            right_binding: other.key,
1879            right_owns_id_json: other.owns_id_json,
1880        })
1881    }
1882
1883    /// Compare for equality against another bound field in the same canonical
1884    /// scalar domain; the comparison carries no literal.
1885    #[must_use]
1886    pub fn eq_field<Owner2, V2>(self, other: BoundField<S, Owner2, V2>) -> Predicate<S>
1887    where
1888        Owner2: Model<Schema = S>,
1889        V: QueryValued,
1890        V2: QueryValued<Domain = V::Domain>,
1891    {
1892        self.field_predicate(ComparisonOp::Equal, other)
1893    }
1894
1895    /// Compare for inequality against another bound field in the same
1896    /// canonical scalar domain.
1897    #[must_use]
1898    pub fn ne_field<Owner2, V2>(self, other: BoundField<S, Owner2, V2>) -> Predicate<S>
1899    where
1900        Owner2: Model<Schema = S>,
1901        V: QueryValued,
1902        V2: QueryValued<Domain = V::Domain>,
1903    {
1904        self.field_predicate(ComparisonOp::NotEqual, other)
1905    }
1906
1907    /// Compare as less than another bound field in the same ordered canonical
1908    /// scalar domain.
1909    #[must_use]
1910    pub fn lt_field<Owner2, V2>(self, other: BoundField<S, Owner2, V2>) -> Predicate<S>
1911    where
1912        Owner2: Model<Schema = S>,
1913        V: QueryValued + crate::__codegen::OrderedValued,
1914        V2: QueryValued<Domain = V::Domain> + crate::__codegen::OrderedValued,
1915    {
1916        self.field_predicate(ComparisonOp::LessThan, other)
1917    }
1918
1919    /// Compare as less than or equal to another bound field in the same
1920    /// ordered canonical scalar domain.
1921    #[must_use]
1922    pub fn le_field<Owner2, V2>(self, other: BoundField<S, Owner2, V2>) -> Predicate<S>
1923    where
1924        Owner2: Model<Schema = S>,
1925        V: QueryValued + crate::__codegen::OrderedValued,
1926        V2: QueryValued<Domain = V::Domain> + crate::__codegen::OrderedValued,
1927    {
1928        self.field_predicate(ComparisonOp::LessThanOrEqual, other)
1929    }
1930
1931    /// Compare as greater than another bound field in the same ordered
1932    /// canonical scalar domain.
1933    #[must_use]
1934    pub fn gt_field<Owner2, V2>(self, other: BoundField<S, Owner2, V2>) -> Predicate<S>
1935    where
1936        Owner2: Model<Schema = S>,
1937        V: QueryValued + crate::__codegen::OrderedValued,
1938        V2: QueryValued<Domain = V::Domain> + crate::__codegen::OrderedValued,
1939    {
1940        self.field_predicate(ComparisonOp::GreaterThan, other)
1941    }
1942
1943    /// Compare as greater than or equal to another bound field in the same
1944    /// ordered canonical scalar domain.
1945    #[must_use]
1946    pub fn ge_field<Owner2, V2>(self, other: BoundField<S, Owner2, V2>) -> Predicate<S>
1947    where
1948        Owner2: Model<Schema = S>,
1949        V: QueryValued + crate::__codegen::OrderedValued,
1950        V2: QueryValued<Domain = V::Domain> + crate::__codegen::OrderedValued,
1951    {
1952        self.field_predicate(ComparisonOp::GreaterThanOrEqual, other)
1953    }
1954
1955    /// Order ascending by this bound field; missing keys fail closed unless
1956    /// an explicit missing-value policy is admitted.
1957    #[must_use]
1958    pub fn asc(self) -> Order<S> {
1959        Order {
1960            key: self.key,
1961            owns_id_json: self.owns_id_json,
1962            direction: SortDirection::Ascending,
1963            missing: MissingOrder::Reject,
1964            marker: PhantomData,
1965        }
1966    }
1967
1968    /// Order descending by this bound field; missing keys fail closed unless
1969    /// an explicit missing-value policy is admitted.
1970    #[must_use]
1971    pub fn desc(self) -> Order<S> {
1972        Order {
1973            key: self.key,
1974            owns_id_json: self.owns_id_json,
1975            direction: SortDirection::Descending,
1976            missing: MissingOrder::Reject,
1977            marker: PhantomData,
1978        }
1979    }
1980}
1981
1982/// One stable public ordering term over a bound field.
1983#[derive(Debug)]
1984pub struct Order<S: Schema> {
1985    key: BindingKey,
1986    owns_id_json: &'static str,
1987    direction: SortDirection,
1988    missing: MissingOrder,
1989    marker: PhantomData<fn() -> S>,
1990}
1991
1992impl<S: Schema> Copy for Order<S> {}
1993impl<S: Schema> Clone for Order<S> {
1994    fn clone(&self) -> Self {
1995        *self
1996    }
1997}
1998
1999impl<S: Schema> Order<S> {
2000    /// Admit missing keys and place them before all present keys.
2001    #[must_use]
2002    pub fn missing_first(mut self) -> Self {
2003        self.missing = MissingOrder::First;
2004        self
2005    }
2006
2007    /// Admit missing keys and place them after all present keys.
2008    #[must_use]
2009    pub fn missing_last(mut self) -> Self {
2010        self.missing = MissingOrder::Last;
2011        self
2012    }
2013}
2014
2015/// One typed collection selection used inside a distinct-root page shape.
2016pub struct Collected<S: Schema, B: Selectable<S>> {
2017    selection: B,
2018    distinct: bool,
2019    order: Vec<Order<S>>,
2020}
2021
2022impl<S: Schema, B: Selectable<S>> Clone for Collected<S, B> {
2023    fn clone(&self) -> Self {
2024        Self {
2025            selection: self.selection,
2026            distinct: self.distinct,
2027            order: self.order.clone(),
2028        }
2029    }
2030}
2031
2032impl<S: Schema, B: Selectable<S>> Collected<S, B> {
2033    /// Deduplicate collection members by TypeDB concept identity.
2034    #[must_use]
2035    pub fn distinct(mut self) -> Self {
2036        self.distinct = true;
2037        self
2038    }
2039
2040    /// Append one stable order term owned by this collected binding.
2041    pub fn order_by(mut self, order: Order<S>) -> Result<Self> {
2042        if order.key != self.selection.binding_key() {
2043            return Err(Error::model_validation(
2044                ModelValidationPhase::Input,
2045                "collection_order_binding_mismatch",
2046                vec![],
2047                "collection ordering must reference the collected binding",
2048                None,
2049            ));
2050        }
2051        self.order.push(order);
2052        Ok(self)
2053    }
2054}
2055
2056/// One generated relation role resolved against one relation binding
2057/// occurrence.
2058pub struct BoundRole<S: Schema, Owner: Model<Schema = S>, Players> {
2059    key: BindingKey,
2060    role_id_json: &'static str,
2061    marker: PhantomData<fn() -> (Owner, Players)>,
2062}
2063
2064impl<S: Schema, Owner: Model<Schema = S>, Players> Copy for BoundRole<S, Owner, Players> {}
2065impl<S: Schema, Owner: Model<Schema = S>, Players> Clone for BoundRole<S, Owner, Players> {
2066    fn clone(&self) -> Self {
2067        *self
2068    }
2069}
2070
2071impl<S: Schema, Owner: Model<Schema = S>, Players> BoundRole<S, Owner, Players> {
2072    /// Require this relation role to connect an admitted generated player
2073    /// binding.
2074    #[must_use]
2075    pub fn connects<MP: ThingModel<Schema = S>, ModeP: SelectionMode>(
2076        self,
2077        player: Binding<S, MP, ModeP>,
2078    ) -> Predicate<S>
2079    where
2080        Players: RolePlayerBinding<MP, ModeP>,
2081    {
2082        Predicate::new(PredicateExpr::Connects {
2083            relation: self.key,
2084            role_id_json: self.role_id_json,
2085            player: player.key,
2086        })
2087    }
2088}
2089
2090mod selectable_sealed {
2091    pub trait Sealed {}
2092}
2093
2094/// Sealed resolution from one selected binding to its typed query output.
2095pub trait Selectable<S: Schema>: selectable_sealed::Sealed + Copy {
2096    /// The materialized output type for one selected row.
2097    type Output;
2098    #[doc(hidden)]
2099    fn binding_key(self) -> BindingKey;
2100    #[doc(hidden)]
2101    fn materialize_output(row: &HydratedRow) -> std::result::Result<Self::Output, ValidationError>;
2102
2103    #[doc(hidden)]
2104    fn __materialize_projected_output(
2105        session: &QuerySession<'_, S>,
2106        thing: &ProjectedThing,
2107    ) -> Result<Self::Output> {
2108        let row = session.client_row_for_projected(thing)?;
2109        Self::materialize_output(&row)
2110            .map_err(|error| map_validation_error(error, ModelValidationPhase::Hydration))
2111    }
2112
2113    #[doc(hidden)]
2114    fn __selection_handle(self, session: &QuerySession<'_, S>) -> Result<OrmSelectionHandle> {
2115        Ok(session.handle_by_key(self.binding_key())?.one())
2116    }
2117
2118    #[doc(hidden)]
2119    fn __materialize_slot(
2120        self,
2121        session: &QuerySession<'_, S>,
2122        slot: &SlotValue,
2123    ) -> Result<Self::Output> {
2124        let SlotValue::One(thing) = slot else {
2125            return Err(Error::model_validation(
2126                ModelValidationPhase::Hydration,
2127                "wrong_result_shape",
2128                vec![],
2129                "provider returned a collection slot for a singular selection",
2130                None,
2131            ));
2132        };
2133        let row = session.client_row_for(thing)?;
2134        Self::materialize_output(&row)
2135            .map_err(|error| map_validation_error(error, ModelValidationPhase::Hydration))
2136    }
2137
2138    #[doc(hidden)]
2139    fn __materialize_slot_with_checkpoint(
2140        self,
2141        session: &QuerySession<'_, S>,
2142        slot: &SlotValue,
2143        checkpoint: &dyn Fn() -> Result<()>,
2144    ) -> Result<Self::Output> {
2145        checkpoint()?;
2146        let output = self.__materialize_slot(session, slot)?;
2147        checkpoint()?;
2148        Ok(output)
2149    }
2150}
2151
2152impl<S: Schema, M: ThingModel<Schema = S> + CompleteModel> selectable_sealed::Sealed
2153    for Binding<S, M, Exact>
2154{
2155}
2156impl<S: Schema, M: ThingModel<Schema = S> + CompleteModel> Selectable<S> for Binding<S, M, Exact> {
2157    type Output = M;
2158    fn binding_key(self) -> BindingKey {
2159        self.key()
2160    }
2161    fn materialize_output(row: &HydratedRow) -> std::result::Result<M, ValidationError> {
2162        M::materialize(row, &HydrationCapability::new())
2163    }
2164}
2165
2166impl<S: Schema, M: ThingModel<Schema = S> + SubtypeRootModel> selectable_sealed::Sealed
2167    for Binding<S, M, Subtypes>
2168{
2169}
2170impl<S: Schema, M: ThingModel<Schema = S> + SubtypeRootModel> Selectable<S>
2171    for Binding<S, M, Subtypes>
2172{
2173    type Output = M::Subtypes;
2174    fn binding_key(self) -> BindingKey {
2175        self.key()
2176    }
2177    fn materialize_output(row: &HydratedRow) -> std::result::Result<M::Subtypes, ValidationError> {
2178        M::__tb_dispatch_subtype(row, &HydrationCapability::new())
2179    }
2180}
2181
2182mod selected_slot_sealed {
2183    pub trait Sealed<S> {}
2184}
2185
2186/// Sealed resolution from one singular or collected selection to its typed
2187/// slot output.
2188#[doc(hidden)]
2189pub trait SelectedSlot<S: Schema>: selected_slot_sealed::Sealed<S> + Clone {
2190    type Output;
2191
2192    fn __selection_handle(&self, session: &QuerySession<'_, S>) -> Result<OrmSelectionHandle>;
2193
2194    fn __materialize_slot(
2195        &self,
2196        session: &QuerySession<'_, S>,
2197        slot: &SlotValue,
2198    ) -> Result<Self::Output>;
2199
2200    #[doc(hidden)]
2201    fn __materialize_projected_slot(
2202        &self,
2203        session: &QuerySession<'_, S>,
2204        slot: &ProjectedQuerySlotValue,
2205    ) -> Result<Self::Output>;
2206
2207    #[doc(hidden)]
2208    fn __materialize_slot_with_checkpoint(
2209        &self,
2210        session: &QuerySession<'_, S>,
2211        slot: &SlotValue,
2212        checkpoint: &dyn Fn() -> Result<()>,
2213    ) -> Result<Self::Output> {
2214        checkpoint()?;
2215        let output = self.__materialize_slot(session, slot)?;
2216        checkpoint()?;
2217        Ok(output)
2218    }
2219
2220    #[doc(hidden)]
2221    fn __materialize_projected_slot_with_checkpoint(
2222        &self,
2223        session: &QuerySession<'_, S>,
2224        slot: &ProjectedQuerySlotValue,
2225        checkpoint: &dyn Fn() -> Result<()>,
2226    ) -> Result<Self::Output> {
2227        checkpoint()?;
2228        let output = self.__materialize_projected_slot(session, slot)?;
2229        checkpoint()?;
2230        Ok(output)
2231    }
2232}
2233
2234impl<S: Schema, B: Selectable<S>> selected_slot_sealed::Sealed<S> for B {}
2235impl<S: Schema, B: Selectable<S>> SelectedSlot<S> for B {
2236    type Output = B::Output;
2237
2238    fn __selection_handle(&self, session: &QuerySession<'_, S>) -> Result<OrmSelectionHandle> {
2239        (*self).__selection_handle(session)
2240    }
2241
2242    fn __materialize_slot(
2243        &self,
2244        session: &QuerySession<'_, S>,
2245        slot: &SlotValue,
2246    ) -> Result<Self::Output> {
2247        (*self).__materialize_slot(session, slot)
2248    }
2249
2250    fn __materialize_projected_slot(
2251        &self,
2252        session: &QuerySession<'_, S>,
2253        slot: &ProjectedQuerySlotValue,
2254    ) -> Result<Self::Output> {
2255        let ProjectedQuerySlotValue::One(thing) = slot else {
2256            return Err(Error::model_validation(
2257                ModelValidationPhase::Hydration,
2258                "wrong_result_shape",
2259                vec![],
2260                "provider returned a collection slot for a singular selection",
2261                None,
2262            ));
2263        };
2264        B::__materialize_projected_output(session, thing)
2265    }
2266
2267    fn __materialize_slot_with_checkpoint(
2268        &self,
2269        session: &QuerySession<'_, S>,
2270        slot: &SlotValue,
2271        checkpoint: &dyn Fn() -> Result<()>,
2272    ) -> Result<Self::Output> {
2273        (*self).__materialize_slot_with_checkpoint(session, slot, checkpoint)
2274    }
2275
2276    fn __materialize_projected_slot_with_checkpoint(
2277        &self,
2278        session: &QuerySession<'_, S>,
2279        slot: &ProjectedQuerySlotValue,
2280        checkpoint: &dyn Fn() -> Result<()>,
2281    ) -> Result<Self::Output> {
2282        checkpoint()?;
2283        let output = self.__materialize_projected_slot(session, slot)?;
2284        checkpoint()?;
2285        Ok(output)
2286    }
2287}
2288
2289impl<S: Schema, B: Selectable<S>> selected_slot_sealed::Sealed<S> for Collected<S, B> {}
2290impl<S: Schema, B: Selectable<S>> SelectedSlot<S> for Collected<S, B> {
2291    type Output = Vec<B::Output>;
2292
2293    fn __selection_handle(&self, session: &QuerySession<'_, S>) -> Result<OrmSelectionHandle> {
2294        let binding = session.handle_by_key(self.selection.binding_key())?;
2295        let mut selection = binding
2296            .collect()
2297            .distinct(self.distinct)
2298            .map_err(Error::from_orm)?;
2299        for order in &self.order {
2300            selection = selection
2301                .order_by(session.lower_order(order)?)
2302                .map_err(Error::from_orm)?;
2303        }
2304        Ok(selection)
2305    }
2306
2307    fn __materialize_slot(
2308        &self,
2309        session: &QuerySession<'_, S>,
2310        slot: &SlotValue,
2311    ) -> Result<Self::Output> {
2312        let SlotValue::Many(things) = slot else {
2313            return Err(Error::model_validation(
2314                ModelValidationPhase::Hydration,
2315                "wrong_result_shape",
2316                vec![],
2317                "provider returned a singular slot for a collection selection",
2318                None,
2319            ));
2320        };
2321        let mut outputs = Vec::with_capacity(things.len());
2322        for thing in things {
2323            let row = session.client_row_for(thing)?;
2324            outputs.push(
2325                B::materialize_output(&row).map_err(|error| {
2326                    map_validation_error(error, ModelValidationPhase::Hydration)
2327                })?,
2328            );
2329        }
2330        Ok(outputs)
2331    }
2332
2333    fn __materialize_projected_slot(
2334        &self,
2335        session: &QuerySession<'_, S>,
2336        slot: &ProjectedQuerySlotValue,
2337    ) -> Result<Self::Output> {
2338        let ProjectedQuerySlotValue::Many(things) = slot else {
2339            return Err(Error::model_validation(
2340                ModelValidationPhase::Hydration,
2341                "wrong_result_shape",
2342                vec![],
2343                "provider returned a singular slot for a collection selection",
2344                None,
2345            ));
2346        };
2347        things
2348            .iter()
2349            .map(|thing| B::__materialize_projected_output(session, thing))
2350            .collect()
2351    }
2352
2353    fn __materialize_slot_with_checkpoint(
2354        &self,
2355        session: &QuerySession<'_, S>,
2356        slot: &SlotValue,
2357        checkpoint: &dyn Fn() -> Result<()>,
2358    ) -> Result<Self::Output> {
2359        let SlotValue::Many(things) = slot else {
2360            return Err(Error::model_validation(
2361                ModelValidationPhase::Hydration,
2362                "wrong_result_shape",
2363                vec![],
2364                "provider returned a singular slot for a collection selection",
2365                None,
2366            ));
2367        };
2368        checkpoint()?;
2369        let mut outputs = Vec::with_capacity(things.len());
2370        for thing in things {
2371            checkpoint()?;
2372            let row = session.client_row_for(thing)?;
2373            outputs.push(
2374                B::materialize_output(&row).map_err(|error| {
2375                    map_validation_error(error, ModelValidationPhase::Hydration)
2376                })?,
2377            );
2378        }
2379        checkpoint()?;
2380        Ok(outputs)
2381    }
2382
2383    fn __materialize_projected_slot_with_checkpoint(
2384        &self,
2385        session: &QuerySession<'_, S>,
2386        slot: &ProjectedQuerySlotValue,
2387        checkpoint: &dyn Fn() -> Result<()>,
2388    ) -> Result<Self::Output> {
2389        let ProjectedQuerySlotValue::Many(things) = slot else {
2390            return Err(Error::model_validation(
2391                ModelValidationPhase::Hydration,
2392                "wrong_result_shape",
2393                vec![],
2394                "provider returned a singular slot for a collection selection",
2395                None,
2396            ));
2397        };
2398        checkpoint()?;
2399        let mut outputs = Vec::with_capacity(things.len());
2400        for thing in things {
2401            checkpoint()?;
2402            outputs.push(B::__materialize_projected_output(session, thing)?);
2403        }
2404        checkpoint()?;
2405        Ok(outputs)
2406    }
2407}
2408
2409mod selected_shape_sealed {
2410    pub trait Sealed<S> {}
2411}
2412
2413/// Sealed typed selected-output shape accepted by the one query facade.
2414///
2415/// Implementations are supplied for one binding, positional tuples through
2416/// the canonical sixteen-slot ceiling, and derive-backed named rows.
2417pub trait SelectedShape<S: Schema>: selected_shape_sealed::Sealed<S> + Clone {
2418    /// One fully materialized public row.
2419    type Output;
2420
2421    #[doc(hidden)]
2422    fn __shape_handle(&self, session: &QuerySession<'_, S>) -> Result<OrmShapeHandle>;
2423
2424    #[doc(hidden)]
2425    fn __materialize_row(
2426        &self,
2427        session: &QuerySession<'_, S>,
2428        row: &MatchRow,
2429    ) -> Result<Self::Output>;
2430
2431    #[doc(hidden)]
2432    fn __materialize_projected_row(
2433        &self,
2434        session: &QuerySession<'_, S>,
2435        row: &ProjectedQueryRow,
2436    ) -> Result<Self::Output>;
2437
2438    #[doc(hidden)]
2439    fn __materialize_row_with_checkpoint(
2440        &self,
2441        session: &QuerySession<'_, S>,
2442        row: &MatchRow,
2443        checkpoint: &dyn Fn() -> Result<()>,
2444    ) -> Result<Self::Output> {
2445        checkpoint()?;
2446        let output = self.__materialize_row(session, row)?;
2447        checkpoint()?;
2448        Ok(output)
2449    }
2450
2451    #[doc(hidden)]
2452    fn __materialize_projected_row_with_checkpoint(
2453        &self,
2454        session: &QuerySession<'_, S>,
2455        row: &ProjectedQueryRow,
2456        checkpoint: &dyn Fn() -> Result<()>,
2457    ) -> Result<Self::Output> {
2458        checkpoint()?;
2459        let output = self.__materialize_projected_row(session, row)?;
2460        checkpoint()?;
2461        Ok(output)
2462    }
2463}
2464
2465impl<S: Schema, B: Selectable<S>> selected_shape_sealed::Sealed<S> for B {}
2466impl<S: Schema, B: Selectable<S>> SelectedShape<S> for B {
2467    type Output = B::Output;
2468
2469    fn __shape_handle(&self, session: &QuerySession<'_, S>) -> Result<OrmShapeHandle> {
2470        session
2471            .session
2472            .positional([SelectedSlot::__selection_handle(self, session)?])
2473            .map_err(Error::from_orm)
2474    }
2475
2476    fn __materialize_row(
2477        &self,
2478        session: &QuerySession<'_, S>,
2479        row: &MatchRow,
2480    ) -> Result<Self::Output> {
2481        let [slot] = row.slots() else {
2482            return Err(selected_shape_arity_error(1, row.slots().len()));
2483        };
2484        SelectedSlot::__materialize_slot(self, session, slot)
2485    }
2486
2487    fn __materialize_projected_row(
2488        &self,
2489        session: &QuerySession<'_, S>,
2490        row: &ProjectedQueryRow,
2491    ) -> Result<Self::Output> {
2492        let [slot] = row.slots() else {
2493            return Err(selected_shape_arity_error(1, row.slots().len()));
2494        };
2495        SelectedSlot::__materialize_projected_slot(self, session, slot.value())
2496    }
2497}
2498
2499impl<S: Schema, B: Selectable<S>> selected_shape_sealed::Sealed<S> for Collected<S, B> {}
2500impl<S: Schema, B: Selectable<S>> SelectedShape<S> for Collected<S, B> {
2501    type Output = Vec<B::Output>;
2502
2503    fn __shape_handle(&self, session: &QuerySession<'_, S>) -> Result<OrmShapeHandle> {
2504        session
2505            .session
2506            .positional([SelectedSlot::__selection_handle(self, session)?])
2507            .map_err(Error::from_orm)
2508    }
2509
2510    fn __materialize_row(
2511        &self,
2512        session: &QuerySession<'_, S>,
2513        row: &MatchRow,
2514    ) -> Result<Self::Output> {
2515        let [slot] = row.slots() else {
2516            return Err(selected_shape_arity_error(1, row.slots().len()));
2517        };
2518        SelectedSlot::__materialize_slot(self, session, slot)
2519    }
2520
2521    fn __materialize_projected_row(
2522        &self,
2523        session: &QuerySession<'_, S>,
2524        row: &ProjectedQueryRow,
2525    ) -> Result<Self::Output> {
2526        let [slot] = row.slots() else {
2527            return Err(selected_shape_arity_error(1, row.slots().len()));
2528        };
2529        SelectedSlot::__materialize_projected_slot(self, session, slot.value())
2530    }
2531
2532    fn __materialize_row_with_checkpoint(
2533        &self,
2534        session: &QuerySession<'_, S>,
2535        row: &MatchRow,
2536        checkpoint: &dyn Fn() -> Result<()>,
2537    ) -> Result<Self::Output> {
2538        let [slot] = row.slots() else {
2539            return Err(selected_shape_arity_error(1, row.slots().len()));
2540        };
2541        SelectedSlot::__materialize_slot_with_checkpoint(self, session, slot, checkpoint)
2542    }
2543
2544    fn __materialize_projected_row_with_checkpoint(
2545        &self,
2546        session: &QuerySession<'_, S>,
2547        row: &ProjectedQueryRow,
2548        checkpoint: &dyn Fn() -> Result<()>,
2549    ) -> Result<Self::Output> {
2550        let [slot] = row.slots() else {
2551            return Err(selected_shape_arity_error(1, row.slots().len()));
2552        };
2553        SelectedSlot::__materialize_projected_slot_with_checkpoint(
2554            self,
2555            session,
2556            slot.value(),
2557            checkpoint,
2558        )
2559    }
2560}
2561
2562mod singular_selected_shape_sealed {
2563    pub trait Sealed<S> {}
2564}
2565
2566/// Sealed marker for a selected shape containing singular slots only.
2567pub trait SingularSelectedShape<S: Schema>:
2568    SelectedShape<S> + singular_selected_shape_sealed::Sealed<S>
2569{
2570}
2571
2572impl<S: Schema, B: Selectable<S>> singular_selected_shape_sealed::Sealed<S> for B {}
2573impl<S: Schema, B: Selectable<S>> SingularSelectedShape<S> for B {}
2574
2575#[doc(hidden)]
2576pub trait SelectedTuple<S: Schema>: Clone {
2577    const ARITY: usize;
2578
2579    type Output;
2580
2581    fn __selection_handles(&self, session: &QuerySession<'_, S>)
2582    -> Result<Vec<OrmSelectionHandle>>;
2583
2584    fn __materialize_slots(
2585        &self,
2586        session: &QuerySession<'_, S>,
2587        slots: &[SlotValue],
2588    ) -> Result<Self::Output>;
2589
2590    #[doc(hidden)]
2591    fn __materialize_projected_slots(
2592        &self,
2593        session: &QuerySession<'_, S>,
2594        slots: &[type_bridge_orm::ProjectedQuerySlot],
2595    ) -> Result<Self::Output>;
2596
2597    #[doc(hidden)]
2598    fn __materialize_slots_with_checkpoint(
2599        &self,
2600        session: &QuerySession<'_, S>,
2601        slots: &[SlotValue],
2602        checkpoint: &dyn Fn() -> Result<()>,
2603    ) -> Result<Self::Output> {
2604        checkpoint()?;
2605        let output = self.__materialize_slots(session, slots)?;
2606        checkpoint()?;
2607        Ok(output)
2608    }
2609
2610    #[doc(hidden)]
2611    fn __materialize_projected_slots_with_checkpoint(
2612        &self,
2613        session: &QuerySession<'_, S>,
2614        slots: &[type_bridge_orm::ProjectedQuerySlot],
2615        checkpoint: &dyn Fn() -> Result<()>,
2616    ) -> Result<Self::Output> {
2617        checkpoint()?;
2618        let output = self.__materialize_projected_slots(session, slots)?;
2619        checkpoint()?;
2620        Ok(output)
2621    }
2622}
2623
2624#[doc(hidden)]
2625pub trait SingularSelectedTuple<S: Schema>: SelectedTuple<S> {}
2626
2627macro_rules! selected_tuple {
2628    ($length:literal; $(($type:ident, $index:tt)),+ $(,)?) => {
2629        impl<S: Schema, $($type: SelectedSlot<S>),+> SelectedTuple<S> for ($($type,)+) {
2630            const ARITY: usize = $length;
2631
2632            type Output = ($(<$type as SelectedSlot<S>>::Output,)+);
2633
2634            fn __selection_handles(
2635                &self,
2636                session: &QuerySession<'_, S>,
2637            ) -> Result<Vec<OrmSelectionHandle>> {
2638                Ok(vec![$(SelectedSlot::__selection_handle(&self.$index, session)?),+])
2639            }
2640
2641            fn __materialize_slots(
2642                &self,
2643                session: &QuerySession<'_, S>,
2644                slots: &[SlotValue],
2645            ) -> Result<Self::Output> {
2646                let slots: &[SlotValue; $length] = slots
2647                    .try_into()
2648                    .map_err(|_| selected_shape_arity_error($length, slots.len()))?;
2649                Ok(($(SelectedSlot::__materialize_slot(
2650                    &self.$index,
2651                    session,
2652                    &slots[$index],
2653                )?,)+))
2654            }
2655
2656            fn __materialize_projected_slots(
2657                &self,
2658                session: &QuerySession<'_, S>,
2659                slots: &[type_bridge_orm::ProjectedQuerySlot],
2660            ) -> Result<Self::Output> {
2661                let slots: &[type_bridge_orm::ProjectedQuerySlot; $length] = slots
2662                    .try_into()
2663                    .map_err(|_| selected_shape_arity_error($length, slots.len()))?;
2664                Ok(($(SelectedSlot::__materialize_projected_slot(
2665                    &self.$index,
2666                    session,
2667                    slots[$index].value(),
2668                )?,)+))
2669            }
2670
2671            fn __materialize_slots_with_checkpoint(
2672                &self,
2673                session: &QuerySession<'_, S>,
2674                slots: &[SlotValue],
2675                checkpoint: &dyn Fn() -> Result<()>,
2676            ) -> Result<Self::Output> {
2677                let slots: &[SlotValue; $length] = slots
2678                    .try_into()
2679                    .map_err(|_| selected_shape_arity_error($length, slots.len()))?;
2680                Ok(($(SelectedSlot::__materialize_slot_with_checkpoint(
2681                    &self.$index,
2682                    session,
2683                    &slots[$index],
2684                    checkpoint,
2685                )?,)+))
2686            }
2687
2688
2689            fn __materialize_projected_slots_with_checkpoint(
2690                &self,
2691                session: &QuerySession<'_, S>,
2692                slots: &[type_bridge_orm::ProjectedQuerySlot],
2693                checkpoint: &dyn Fn() -> Result<()>,
2694            ) -> Result<Self::Output> {
2695                let slots: &[type_bridge_orm::ProjectedQuerySlot; $length] = slots
2696                    .try_into()
2697                    .map_err(|_| selected_shape_arity_error($length, slots.len()))?;
2698                Ok(($(SelectedSlot::__materialize_projected_slot_with_checkpoint(
2699                    &self.$index,
2700                    session,
2701                    slots[$index].value(),
2702                    checkpoint,
2703                )?,)+))
2704            }
2705        }
2706
2707        impl<S: Schema, $($type: SelectedSlot<S>),+> selected_shape_sealed::Sealed<S>
2708            for ($($type,)+)
2709        {
2710        }
2711
2712        impl<S: Schema, $($type: SelectedSlot<S>),+> SelectedShape<S> for ($($type,)+) {
2713            type Output = <Self as SelectedTuple<S>>::Output;
2714
2715            fn __shape_handle(
2716                &self,
2717                session: &QuerySession<'_, S>,
2718            ) -> Result<OrmShapeHandle> {
2719                session
2720                    .session
2721                    .positional(self.__selection_handles(session)?)
2722                    .map_err(Error::from_orm)
2723            }
2724
2725            fn __materialize_row(
2726                &self,
2727                session: &QuerySession<'_, S>,
2728                row: &MatchRow,
2729            ) -> Result<Self::Output> {
2730                self.__materialize_slots(session, row.slots())
2731            }
2732
2733            fn __materialize_projected_row(
2734                &self,
2735                session: &QuerySession<'_, S>,
2736                row: &ProjectedQueryRow,
2737            ) -> Result<Self::Output> {
2738                self.__materialize_projected_slots(session, row.slots())
2739            }
2740
2741
2742            fn __materialize_row_with_checkpoint(
2743                &self,
2744                session: &QuerySession<'_, S>,
2745                row: &MatchRow,
2746                checkpoint: &dyn Fn() -> Result<()>,
2747            ) -> Result<Self::Output> {
2748                self.__materialize_slots_with_checkpoint(session, row.slots(), checkpoint)
2749            }
2750
2751
2752            fn __materialize_projected_row_with_checkpoint(
2753                &self,
2754                session: &QuerySession<'_, S>,
2755                row: &ProjectedQueryRow,
2756                checkpoint: &dyn Fn() -> Result<()>,
2757            ) -> Result<Self::Output> {
2758                self.__materialize_projected_slots_with_checkpoint(
2759                    session,
2760                    row.slots(),
2761                    checkpoint,
2762                )
2763            }
2764        }
2765
2766        impl<S: Schema, $($type: Selectable<S>),+> singular_selected_shape_sealed::Sealed<S>
2767            for ($($type,)+)
2768        {
2769        }
2770
2771        impl<S: Schema, $($type: Selectable<S>),+> SingularSelectedShape<S>
2772            for ($($type,)+)
2773        {
2774        }
2775
2776        impl<S: Schema, $($type: Selectable<S>),+> SingularSelectedTuple<S>
2777            for ($($type,)+)
2778        {
2779        }
2780    };
2781}
2782
2783selected_tuple!(1; (A, 0));
2784selected_tuple!(2; (A, 0), (B, 1));
2785selected_tuple!(3; (A, 0), (B, 1), (C, 2));
2786selected_tuple!(4; (A, 0), (B, 1), (C, 2), (D, 3));
2787selected_tuple!(5; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4));
2788selected_tuple!(6; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5));
2789selected_tuple!(7; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6));
2790selected_tuple!(8; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6), (H, 7));
2791selected_tuple!(9; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6), (H, 7), (I, 8));
2792selected_tuple!(10; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6), (H, 7), (I, 8), (J, 9));
2793selected_tuple!(11; (A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6), (H, 7), (I, 8), (J, 9), (K, 10));
2794selected_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));
2795selected_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));
2796selected_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));
2797selected_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));
2798selected_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));
2799
2800/// Construction contract generated by `#[derive(type_bridge::SelectedRow)]`.
2801#[doc(hidden)]
2802pub trait SelectedRowSpec<Outputs>: Sized {
2803    fn __from_selected_outputs(outputs: Outputs) -> Self;
2804}
2805
2806/// A declaration-ordered named selected shape produced by `SelectedRow`.
2807pub struct NamedSelection<S: Schema, Row, Slots> {
2808    slots: Slots,
2809    names: &'static [&'static str],
2810    marker: PhantomData<fn() -> (S, Row)>,
2811}
2812
2813impl<S: Schema, Row, Slots: Clone> Clone for NamedSelection<S, Row, Slots> {
2814    fn clone(&self) -> Self {
2815        Self {
2816            slots: self.slots.clone(),
2817            names: self.names,
2818            marker: PhantomData,
2819        }
2820    }
2821}
2822
2823impl<S: Schema, Row, Slots: SelectedTuple<S>> NamedSelection<S, Row, Slots> {
2824    #[doc(hidden)]
2825    pub fn __new(slots: Slots, names: &'static [&'static str]) -> Result<Self> {
2826        if names.len() != Slots::ARITY {
2827            return Err(Error::model_validation(
2828                ModelValidationPhase::Input,
2829                "invalid_selected_shape",
2830                vec![],
2831                format!(
2832                    "named selected shape has {} names for {} slots",
2833                    names.len(),
2834                    Slots::ARITY
2835                ),
2836                None,
2837            ));
2838        }
2839        Ok(Self {
2840            slots,
2841            names,
2842            marker: PhantomData,
2843        })
2844    }
2845}
2846
2847impl<S: Schema, Row, Slots> selected_shape_sealed::Sealed<S> for NamedSelection<S, Row, Slots> {}
2848
2849impl<S, Row, Slots> SelectedShape<S> for NamedSelection<S, Row, Slots>
2850where
2851    S: Schema,
2852    Slots: SelectedTuple<S>,
2853    Row: SelectedRowSpec<Slots::Output>,
2854{
2855    type Output = Row;
2856
2857    fn __shape_handle(&self, session: &QuerySession<'_, S>) -> Result<OrmShapeHandle> {
2858        let selections = self.slots.__selection_handles(session)?;
2859        session
2860            .session
2861            .named(
2862                self.names
2863                    .iter()
2864                    .copied()
2865                    .zip(selections)
2866                    .map(|(name, selection)| (name.to_owned(), selection)),
2867            )
2868            .map_err(Error::from_orm)
2869    }
2870
2871    fn __materialize_row(
2872        &self,
2873        session: &QuerySession<'_, S>,
2874        row: &MatchRow,
2875    ) -> Result<Self::Output> {
2876        Ok(Row::__from_selected_outputs(
2877            self.slots.__materialize_slots(session, row.slots())?,
2878        ))
2879    }
2880
2881    fn __materialize_projected_row(
2882        &self,
2883        session: &QuerySession<'_, S>,
2884        row: &ProjectedQueryRow,
2885    ) -> Result<Self::Output> {
2886        Ok(Row::__from_selected_outputs(
2887            self.slots
2888                .__materialize_projected_slots(session, row.slots())?,
2889        ))
2890    }
2891
2892    fn __materialize_row_with_checkpoint(
2893        &self,
2894        session: &QuerySession<'_, S>,
2895        row: &MatchRow,
2896        checkpoint: &dyn Fn() -> Result<()>,
2897    ) -> Result<Self::Output> {
2898        Ok(Row::__from_selected_outputs(
2899            self.slots
2900                .__materialize_slots_with_checkpoint(session, row.slots(), checkpoint)?,
2901        ))
2902    }
2903
2904    fn __materialize_projected_row_with_checkpoint(
2905        &self,
2906        session: &QuerySession<'_, S>,
2907        row: &ProjectedQueryRow,
2908        checkpoint: &dyn Fn() -> Result<()>,
2909    ) -> Result<Self::Output> {
2910        Ok(Row::__from_selected_outputs(
2911            self.slots.__materialize_projected_slots_with_checkpoint(
2912                session,
2913                row.slots(),
2914                checkpoint,
2915            )?,
2916        ))
2917    }
2918}
2919
2920impl<S, Row, Slots> singular_selected_shape_sealed::Sealed<S> for NamedSelection<S, Row, Slots>
2921where
2922    S: Schema,
2923    Slots: SingularSelectedTuple<S>,
2924    Row: SelectedRowSpec<Slots::Output>,
2925{
2926}
2927
2928impl<S, Row, Slots> SingularSelectedShape<S> for NamedSelection<S, Row, Slots>
2929where
2930    S: Schema,
2931    Slots: SingularSelectedTuple<S>,
2932    Row: SelectedRowSpec<Slots::Output>,
2933{
2934}
2935
2936fn selected_shape_arity_error(expected: usize, actual: usize) -> Error {
2937    Error::model_validation(
2938        ModelValidationPhase::Hydration,
2939        "wrong_result_shape",
2940        vec![],
2941        format!("selected row has {actual} slots; expected {expected}"),
2942        None,
2943    )
2944}
2945
2946/// Bounded options for one ordered row fetch.
2947#[derive(Debug)]
2948pub struct RowsOptions<S: Schema> {
2949    limit: u64,
2950    offset: u64,
2951    order: Vec<Order<S>>,
2952}
2953
2954impl<S: Schema> Clone for RowsOptions<S> {
2955    fn clone(&self) -> Self {
2956        Self {
2957            limit: self.limit,
2958            offset: self.offset,
2959            order: self.order.clone(),
2960        }
2961    }
2962}
2963
2964impl<S: Schema> RowsOptions<S> {
2965    /// Create resource-bounded row options with a nonzero limit.
2966    #[must_use]
2967    pub fn new(limit: u64) -> Self {
2968        Self {
2969            limit,
2970            offset: 0,
2971            order: Vec::new(),
2972        }
2973    }
2974
2975    /// Skip the first `offset` distinct rows.
2976    #[must_use]
2977    pub fn offset(mut self, offset: u64) -> Self {
2978        self.offset = offset;
2979        self
2980    }
2981
2982    /// Append one stable public ordering term.
2983    #[must_use]
2984    pub fn order_by(mut self, order: Order<S>) -> Self {
2985        self.order.push(order);
2986        self
2987    }
2988}
2989
2990/// Bounded options for one ordered distinct-root page.
2991#[derive(Debug)]
2992pub struct PageOptions<S: Schema> {
2993    limit: u64,
2994    offset: u64,
2995    include_total: bool,
2996    order: Vec<Order<S>>,
2997}
2998
2999impl<S: Schema> Clone for PageOptions<S> {
3000    fn clone(&self) -> Self {
3001        Self {
3002            limit: self.limit,
3003            offset: self.offset,
3004            include_total: self.include_total,
3005            order: self.order.clone(),
3006        }
3007    }
3008}
3009
3010impl<S: Schema> PageOptions<S> {
3011    /// Create resource-bounded page options with a nonzero terminal limit.
3012    #[must_use]
3013    pub fn new(limit: u64) -> Self {
3014        Self {
3015            limit,
3016            offset: 0,
3017            include_total: false,
3018            order: Vec::new(),
3019        }
3020    }
3021
3022    /// Skip the first `offset` distinct roots.
3023    #[must_use]
3024    pub fn offset(mut self, offset: u64) -> Self {
3025        self.offset = offset;
3026        self
3027    }
3028
3029    /// Request a same-snapshot total distinct-root count.
3030    #[must_use]
3031    pub fn include_total(mut self, include_total: bool) -> Self {
3032        self.include_total = include_total;
3033        self
3034    }
3035
3036    /// Append one stable root-ordering term.
3037    #[must_use]
3038    pub fn order_by(mut self, order: Order<S>) -> Self {
3039        self.order.push(order);
3040        self
3041    }
3042}
3043
3044/// One immutable owned distinct-root page.
3045#[derive(Clone, Debug)]
3046pub struct Page<T> {
3047    items: Vec<T>,
3048    offset: u64,
3049    limit: u64,
3050    total: Option<u64>,
3051}
3052
3053impl<T> Page<T> {
3054    /// Borrow page items in stable root order.
3055    #[must_use]
3056    pub fn items(&self) -> &[T] {
3057        &self.items
3058    }
3059
3060    /// Return the requested root offset.
3061    #[must_use]
3062    pub const fn offset(&self) -> u64 {
3063        self.offset
3064    }
3065
3066    /// Return the requested root limit.
3067    #[must_use]
3068    pub const fn limit(&self) -> u64 {
3069        self.limit
3070    }
3071
3072    /// Return the same-snapshot total when it was requested.
3073    #[must_use]
3074    pub const fn total(&self) -> Option<u64> {
3075        self.total
3076    }
3077
3078    /// Consume the page and return its owned items.
3079    #[must_use]
3080    pub fn into_items(self) -> Vec<T> {
3081        self.items
3082    }
3083}
3084
3085/// One persistent, reusable singular-shape query lineage.
3086///
3087/// Each authoring method returns a new lineage and leaves its ancestor
3088/// usable.
3089pub struct Query<'s, 'db, S: Schema, Shape: SelectedShape<S>> {
3090    session: &'s QuerySession<'db, S>,
3091    selection: Shape,
3092    hidden: Vec<BindingKey>,
3093    predicates: Vec<Predicate<S>>,
3094    allowed_cross_joins: Vec<(BindingKey, BindingKey)>,
3095    closed: AtomicBool,
3096}
3097
3098impl<'s, 'db, S: Schema, Shape: SelectedShape<S>> Clone for Query<'s, 'db, S, Shape> {
3099    fn clone(&self) -> Self {
3100        Self {
3101            session: self.session,
3102            selection: self.selection.clone(),
3103            hidden: self.hidden.clone(),
3104            predicates: self.predicates.clone(),
3105            allowed_cross_joins: self.allowed_cross_joins.clone(),
3106            closed: AtomicBool::new(self.closed.load(Ordering::Acquire)),
3107        }
3108    }
3109}
3110
3111impl<'db, S: Schema> QuerySession<'db, S> {
3112    /// Begin one persistent query lineage from a singular selected shape.
3113    pub fn query<Shape: SelectedShape<S>>(
3114        &self,
3115        selection: Shape,
3116    ) -> Result<Query<'_, 'db, S, Shape>> {
3117        selection.__shape_handle(self)?;
3118        Ok(Query {
3119            session: self,
3120            selection,
3121            hidden: Vec::new(),
3122            predicates: Vec::new(),
3123            allowed_cross_joins: Vec::new(),
3124            closed: AtomicBool::new(false),
3125        })
3126    }
3127}
3128
3129impl<'s, 'db, S: Schema, Shape: SelectedShape<S>> Query<'s, 'db, S, Shape> {
3130    /// Explicitly close this immutable query handle.
3131    ///
3132    /// Closing is idempotent and affects only this handle. Clones, ancestors,
3133    /// descendants, siblings, and the authoring session have independent
3134    /// query lifecycles.
3135    pub fn close(&self) {
3136        self.closed.store(true, Ordering::Release);
3137    }
3138
3139    /// Return whether this query handle was explicitly closed.
3140    #[must_use]
3141    pub fn is_closed(&self) -> bool {
3142        self.closed.load(Ordering::Acquire)
3143    }
3144
3145    fn ensure_open(&self, phase: ModelValidationPhase) -> Result<()> {
3146        self.session.ensure_open(phase)?;
3147        if self.closed.load(Ordering::Acquire) {
3148            Err(Error::from_sdk_execution(
3149                type_bridge_orm::query_resource_closed_diagnostic(),
3150                phase,
3151            ))
3152        } else {
3153            Ok(())
3154        }
3155    }
3156
3157    /// Attach one generated binding for predicates without selecting it.
3158    pub fn match_<M: ThingModel<Schema = S>, Mode: SelectionMode>(
3159        &self,
3160        binding: Binding<S, M, Mode>,
3161    ) -> Result<Self> {
3162        self.ensure_open(ModelValidationPhase::Input)?;
3163        self.session.handle_by_key(binding.key())?;
3164        let mut next = self.clone();
3165        if !next.hidden.contains(&binding.key()) {
3166            next.hidden.push(binding.key());
3167        }
3168        Ok(next)
3169    }
3170
3171    /// Attach one predicate; repeated calls form a conjunction in call order.
3172    pub fn where_(&self, predicate: Predicate<S>) -> Result<Self> {
3173        self.ensure_open(ModelValidationPhase::Input)?;
3174        let mut next = self.clone();
3175        next.predicates.push(predicate);
3176        Ok(next)
3177    }
3178
3179    /// Attach predicates as one implicit conjunction in source order.
3180    pub fn where_all(&self, predicates: impl IntoIterator<Item = Predicate<S>>) -> Result<Self> {
3181        self.ensure_open(ModelValidationPhase::Input)?;
3182        let mut next = self.clone();
3183        next.predicates.extend(predicates);
3184        Ok(next)
3185    }
3186
3187    /// Explicitly permit one topology-level cross join between two attached
3188    /// generated bindings. The returned lineage is immutable and reusable.
3189    pub fn allow_cross_join<L: Selectable<S>, R: Selectable<S>>(
3190        &self,
3191        left: L,
3192        right: R,
3193    ) -> Result<Self> {
3194        self.ensure_open(ModelValidationPhase::Input)?;
3195        let left = left.binding_key();
3196        let right = right.binding_key();
3197        self.session.handle_by_key(left)?;
3198        self.session.handle_by_key(right)?;
3199        if left == right {
3200            return Err(Error::model_validation(
3201                ModelValidationPhase::Input,
3202                "self_cross_join",
3203                vec![],
3204                "cross-join permission requires two distinct generated bindings",
3205                None,
3206            ));
3207        }
3208        let pair = if left.index < right.index {
3209            (left, right)
3210        } else {
3211            (right, left)
3212        };
3213        let mut next = self.clone();
3214        if !next.allowed_cross_joins.contains(&pair) {
3215            next.allowed_cross_joins.push(pair);
3216        }
3217        Ok(next)
3218    }
3219
3220    fn lineage(&self) -> Result<OrmQueryHandle> {
3221        self.lineage_with_hidden(&[])
3222    }
3223
3224    fn lineage_with_hidden(&self, hidden: &[BindingKey]) -> Result<OrmQueryHandle> {
3225        self.ensure_open(ModelValidationPhase::Input)?;
3226        let shape = self.selection.__shape_handle(self.session)?;
3227        let mut query = self.session.session.query(shape).map_err(Error::from_orm)?;
3228        let mut hidden_keys = self.hidden.clone();
3229        for key in hidden {
3230            if !hidden_keys.contains(key) {
3231                hidden_keys.push(*key);
3232            }
3233        }
3234        for key in hidden_keys {
3235            let hidden = self.session.handle_by_key(key)?;
3236            query = query.add_hidden(hidden.clone()).map_err(Error::from_orm)?;
3237        }
3238        for (left, right) in &self.allowed_cross_joins {
3239            let left = self.session.handle_by_key(*left)?;
3240            let right = self.session.handle_by_key(*right)?;
3241            query = query
3242                .allow_cross_join(left, right)
3243                .map_err(Error::from_orm)?;
3244        }
3245        for predicate in &self.predicates {
3246            let lowered = self.session.lower_predicate(&predicate.expr)?;
3247            query = query.where_predicate(lowered).map_err(Error::from_orm)?;
3248        }
3249        Ok(query)
3250    }
3251
3252    pub(crate) fn validated_rows(
3253        &self,
3254        order: &[Order<S>],
3255        window: Window,
3256    ) -> Result<ValidatedMatchRequest> {
3257        let lineage = self.lineage()?;
3258        let mut lowered_orders = Vec::with_capacity(order.len());
3259        for term in order {
3260            lowered_orders.push(self.session.lower_order(term)?);
3261        }
3262        lineage
3263            .validate_fetch_rows(&lowered_orders, window, RowCardinality::BoundedMany)
3264            .map_err(Error::from_orm)
3265    }
3266
3267    pub(crate) fn validated_one(&self) -> Result<ValidatedMatchRequest> {
3268        self.lineage()?
3269            .validate_fetch_rows(
3270                &[],
3271                Window {
3272                    offset: 0,
3273                    limit: 1,
3274                },
3275                RowCardinality::ExactlyOne,
3276            )
3277            .map_err(Error::from_orm)
3278    }
3279
3280    pub(crate) fn validated_page<R: Selectable<S>>(
3281        &self,
3282        root: R,
3283        order: &[Order<S>],
3284        window: Window,
3285        include_total: bool,
3286    ) -> Result<ValidatedMatchRequest> {
3287        let root = self.session.handle_by_key(root.binding_key())?;
3288        let lineage = self.lineage()?;
3289        let mut lowered_orders = Vec::with_capacity(order.len());
3290        for term in order {
3291            lowered_orders.push(self.session.lower_order(term)?);
3292        }
3293        lineage
3294            .validate_page_by(root, &lowered_orders, window, include_total)
3295            .map_err(Error::from_orm)
3296    }
3297
3298    pub(crate) fn validated_count_by<R: Selectable<S>>(
3299        &self,
3300        root: R,
3301    ) -> Result<ValidatedMatchRequest> {
3302        let root = self.session.handle_by_key(root.binding_key())?;
3303        self.lineage()?
3304            .validate_count_by(root)
3305            .map_err(Error::from_orm)
3306    }
3307
3308    pub(crate) fn validated_exists_by<R: Selectable<S>>(
3309        &self,
3310        root: R,
3311    ) -> Result<ValidatedMatchRequest> {
3312        let root = self.session.handle_by_key(root.binding_key())?;
3313        self.lineage()?
3314            .validate_exists_by(root)
3315            .map_err(Error::from_orm)
3316    }
3317
3318    fn materialize_rows(
3319        &self,
3320        rows: &[MatchRow],
3321        deadline: QueryExecutionDeadline,
3322    ) -> Result<Vec<Shape::Output>> {
3323        let checkpoint = || {
3324            self.session
3325                .check_invocation(deadline, ModelValidationPhase::Hydration)
3326        };
3327        checkpoint()?;
3328        let mut outputs = Vec::with_capacity(rows.len());
3329        for row in rows {
3330            checkpoint()?;
3331            outputs.push(self.selection.__materialize_row_with_checkpoint(
3332                self.session,
3333                row,
3334                &checkpoint,
3335            )?);
3336        }
3337        checkpoint()?;
3338        Ok(outputs)
3339    }
3340
3341    fn materialize_projected_rows(
3342        &self,
3343        rows: &[ProjectedQueryRow],
3344        deadline: QueryExecutionDeadline,
3345    ) -> Result<Vec<Shape::Output>> {
3346        let checkpoint = || {
3347            self.session
3348                .check_invocation(deadline, ModelValidationPhase::Hydration)
3349        };
3350        checkpoint()?;
3351        let mut outputs = Vec::with_capacity(rows.len());
3352        for row in rows {
3353            checkpoint()?;
3354            outputs.push(self.selection.__materialize_projected_row_with_checkpoint(
3355                self.session,
3356                row,
3357                &checkpoint,
3358            )?);
3359        }
3360        checkpoint()?;
3361        Ok(outputs)
3362    }
3363
3364    pub(crate) fn outputs_from_rows(
3365        &self,
3366        validated: &ValidatedMatchRequest,
3367        result: &ValidatedMatchResult,
3368        deadline: QueryExecutionDeadline,
3369    ) -> Result<Vec<Shape::Output>> {
3370        self.session
3371            .check_invocation(deadline, ModelValidationPhase::Hydration)?;
3372        if self.session.uses_successor_projected_materialization() {
3373            let projected = self
3374                .session
3375                .materialize_projected_query_value(validated, result, deadline)?;
3376            let ProjectedQueryValue::Rows { rows } = projected else {
3377                return Err(Error::model_validation(
3378                    ModelValidationPhase::Hydration,
3379                    "wrong_result_shape",
3380                    vec![],
3381                    "provider returned a non-row result for a row fetch",
3382                    None,
3383                ));
3384            };
3385            return self.materialize_projected_rows(&rows, deadline);
3386        }
3387        let rows = match result
3388            .for_request(validated)
3389            .map_err(|error| Error::from_orm_hydration(error.into()))?
3390        {
3391            MatchResult::Rows { rows } => rows,
3392            _ => {
3393                return Err(Error::model_validation(
3394                    ModelValidationPhase::Hydration,
3395                    "wrong_result_shape",
3396                    vec![],
3397                    "provider returned a non-row result for a row fetch",
3398                    None,
3399                ));
3400            }
3401        };
3402        self.materialize_rows(rows, deadline)
3403    }
3404
3405    pub(crate) fn output_page(
3406        &self,
3407        validated: &ValidatedMatchRequest,
3408        result: &ValidatedMatchResult,
3409        deadline: QueryExecutionDeadline,
3410    ) -> Result<Page<Shape::Output>> {
3411        self.session
3412            .check_invocation(deadline, ModelValidationPhase::Hydration)?;
3413        if self.session.uses_successor_projected_materialization() {
3414            let projected = self
3415                .session
3416                .materialize_projected_query_value(validated, result, deadline)?;
3417            let ProjectedQueryValue::Page {
3418                entries,
3419                window,
3420                total,
3421                ..
3422            } = projected
3423            else {
3424                return Err(Error::model_validation(
3425                    ModelValidationPhase::Hydration,
3426                    "wrong_result_shape",
3427                    vec![],
3428                    "provider returned a non-page result for a page fetch",
3429                    None,
3430                ));
3431            };
3432            return Ok(Page {
3433                items: self.materialize_projected_rows(&entries, deadline)?,
3434                offset: window.offset,
3435                limit: window.limit,
3436                total,
3437            });
3438        }
3439        let (entries, window, total) = match result
3440            .for_request(validated)
3441            .map_err(|error| Error::from_orm_hydration(error.into()))?
3442        {
3443            MatchResult::Page {
3444                entries,
3445                window,
3446                total,
3447                ..
3448            } => (entries, *window, *total),
3449            _ => {
3450                return Err(Error::model_validation(
3451                    ModelValidationPhase::Hydration,
3452                    "wrong_result_shape",
3453                    vec![],
3454                    "provider returned a non-page result for a page fetch",
3455                    None,
3456                ));
3457            }
3458        };
3459        Ok(Page {
3460            items: self.materialize_rows(entries, deadline)?,
3461            offset: window.offset,
3462            limit: window.limit,
3463            total,
3464        })
3465    }
3466
3467    async fn execute(
3468        &self,
3469        validated: ValidatedMatchRequest,
3470        deadline: QueryExecutionDeadline,
3471    ) -> Result<(ValidatedMatchRequest, ValidatedMatchResult)> {
3472        self.ensure_open(ModelValidationPhase::Input)?;
3473        self.session
3474            .check_invocation(deadline, ModelValidationPhase::Input)?;
3475        let result = match &self.session.execution {
3476            QueryExecution::Borrowed(transaction) => {
3477                transaction
3478                    .execute_match_with_limits(
3479                        &self.session.registry,
3480                        &validated,
3481                        self.session
3482                            .resources
3483                            .direct_with_deadline(self.session.cancellation.clone(), deadline),
3484                    )
3485                    .await
3486            }
3487            QueryExecution::Local(database) => {
3488                database
3489                    .inner_orm()
3490                    .execute_match_with_limits(
3491                        &self.session.registry,
3492                        &validated,
3493                        self.session
3494                            .resources
3495                            .direct_with_deadline(self.session.cancellation.clone(), deadline),
3496                    )
3497                    .await
3498            }
3499            QueryExecution::Remote(remote) => {
3500                let result = remote
3501                    .execute_match(
3502                        &self.session.registry,
3503                        validated,
3504                        self.session.resources,
3505                        self.session.cancellation.clone(),
3506                        deadline,
3507                    )
3508                    .await?;
3509                self.ensure_open(ModelValidationPhase::Hydration)?;
3510                return Ok(result);
3511            }
3512        };
3513        self.session
3514            .check_invocation(deadline, ModelValidationPhase::Hydration)?;
3515        self.ensure_open(ModelValidationPhase::Hydration)?;
3516        Ok((validated, result.map_err(Error::from_orm_hydration)?))
3517    }
3518}
3519
3520impl<'s, 'db, S, Shape> Query<'s, 'db, S, Shape>
3521where
3522    S: Schema,
3523    Shape: SingularSelectedShape<S>,
3524{
3525    /// Return exactly one distinct selected identity, failing `no_result` on
3526    /// an empty stream and `not_unique` on more than one.
3527    pub async fn one(&self) -> Result<Shape::Output> {
3528        let deadline = self.session.begin_invocation()?;
3529        let validated = self.validated_one()?;
3530        let (validated, result) = self.execute(validated, deadline).await?;
3531        let mut outputs = self.outputs_from_rows(&validated, &result, deadline)?;
3532        match outputs.len() {
3533            0 => Err(Error::model_validation(
3534                ModelValidationPhase::Hydration,
3535                "no_result",
3536                vec![],
3537                "query selected no distinct identity",
3538                None,
3539            )),
3540            1 => Ok(outputs.remove(0)),
3541            _ => Err(Error::model_validation(
3542                ModelValidationPhase::Hydration,
3543                "not_unique",
3544                vec![],
3545                "query selected more than one distinct identity",
3546                None,
3547            )),
3548        }
3549    }
3550
3551    /// Return a resource-bounded ordered sequence of distinct selected
3552    /// identities; the limit must be nonzero.
3553    pub async fn rows(&self, options: RowsOptions<S>) -> Result<Vec<Shape::Output>> {
3554        let deadline = self.session.begin_invocation()?;
3555        if options.limit == 0 {
3556            return Err(Error::model_validation(
3557                ModelValidationPhase::Input,
3558                "zero_limit",
3559                vec![],
3560                "row fetches require a nonzero limit",
3561                None,
3562            ));
3563        }
3564        let validated = self.validated_rows(
3565            &options.order,
3566            Window {
3567                offset: options.offset,
3568                limit: options.limit,
3569            },
3570        )?;
3571        let (validated, result) = self.execute(validated, deadline).await?;
3572        self.outputs_from_rows(&validated, &result, deadline)
3573    }
3574
3575    /// Return the first distinct selected identity under a stable order.
3576    pub async fn first(&self, order: Order<S>) -> Result<Option<Shape::Output>> {
3577        let deadline = self.session.begin_invocation()?;
3578        let validated = self.validated_rows(
3579            &[order],
3580            Window {
3581                offset: 0,
3582                limit: 1,
3583            },
3584        )?;
3585        let (validated, result) = self.execute(validated, deadline).await?;
3586        Ok(self.outputs_from_rows(&validated, &result, deadline)?.pop())
3587    }
3588}
3589
3590impl<'s, 'db, S: Schema, Shape: SelectedShape<S>> Query<'s, 'db, S, Shape> {
3591    /// Return one resource-bounded page grouped by distinct root identity.
3592    pub async fn page_by<R: Selectable<S>>(
3593        &self,
3594        root: R,
3595        options: PageOptions<S>,
3596    ) -> Result<Page<Shape::Output>> {
3597        let deadline = self.session.begin_invocation()?;
3598        if options.limit == 0 {
3599            return Err(Error::model_validation(
3600                ModelValidationPhase::Input,
3601                "zero_limit",
3602                vec![],
3603                "page fetches require a nonzero limit",
3604                None,
3605            ));
3606        }
3607        let validated = self.validated_page(
3608            root,
3609            &options.order,
3610            Window {
3611                offset: options.offset,
3612                limit: options.limit,
3613            },
3614            options.include_total,
3615        )?;
3616        let (validated, result) = self.execute(validated, deadline).await?;
3617        self.output_page(&validated, &result, deadline)
3618    }
3619
3620    /// Count distinct identities of one selected root binding.
3621    pub async fn count_by<R: Selectable<S>>(&self, root: R) -> Result<u64> {
3622        let deadline = self.session.begin_invocation()?;
3623        let validated = self.validated_count_by(root)?;
3624        let (validated, result) = self.execute(validated, deadline).await?;
3625        self.session
3626            .check_invocation(deadline, ModelValidationPhase::Hydration)?;
3627        match result
3628            .for_request(&validated)
3629            .map_err(|error| Error::from_orm_hydration(error.into()))?
3630        {
3631            MatchResult::Count { value, .. } => Ok(*value),
3632            _ => Err(Error::model_validation(
3633                ModelValidationPhase::Hydration,
3634                "wrong_result_shape",
3635                vec![],
3636                "provider returned a non-count result for a count",
3637                None,
3638            )),
3639        }
3640    }
3641
3642    /// Test whether any distinct identity of one selected root binding
3643    /// exists.
3644    pub async fn exists_by<R: Selectable<S>>(&self, root: R) -> Result<bool> {
3645        let deadline = self.session.begin_invocation()?;
3646        let validated = self.validated_exists_by(root)?;
3647        let (validated, result) = self.execute(validated, deadline).await?;
3648        self.session
3649            .check_invocation(deadline, ModelValidationPhase::Hydration)?;
3650        match result
3651            .for_request(&validated)
3652            .map_err(|error| Error::from_orm_hydration(error.into()))?
3653        {
3654            MatchResult::Exists { value, .. } => Ok(*value),
3655            _ => Err(Error::model_validation(
3656                ModelValidationPhase::Hydration,
3657                "wrong_result_shape",
3658                vec![],
3659                "provider returned a non-existence result for an existence test",
3660                None,
3661            )),
3662        }
3663    }
3664}
3665
3666impl<'s, 'db, S: Schema, B: Selectable<S>> Query<'s, 'db, S, B> {
3667    /// Count distinct selected identities.
3668    pub async fn count(&self) -> Result<u64> {
3669        self.count_by(self.selection).await
3670    }
3671
3672    /// Test whether any distinct selected identity exists.
3673    pub async fn exists(&self) -> Result<bool> {
3674        self.exists_by(self.selection).await
3675    }
3676
3677    fn lowered_reduce_terms(
3678        &self,
3679        terms: &[(
3680            type_bridge_orm::match_request::Reduction,
3681            Option<(BindingKey, &'static str)>,
3682        )],
3683    ) -> Result<Vec<OrmFieldHandle>> {
3684        let mut lowered = Vec::new();
3685        for (_, input) in terms {
3686            if let Some((key, owns_id_json)) = input {
3687                lowered.push(self.session.lower_field(*key, owns_id_json)?);
3688            }
3689        }
3690        Ok(lowered)
3691    }
3692
3693    pub(crate) fn validated_reduce(
3694        &self,
3695        group: Option<BindingKey>,
3696        terms: &[(
3697            type_bridge_orm::match_request::Reduction,
3698            Option<(BindingKey, &'static str)>,
3699        )],
3700    ) -> Result<ValidatedMatchRequest> {
3701        let root = self.session.handle_by_key(self.selection.binding_key())?;
3702        let hidden_groups = group
3703            .filter(|key| *key != self.selection.binding_key())
3704            .into_iter()
3705            .collect::<Vec<_>>();
3706        let lineage = self.lineage_with_hidden(&hidden_groups)?;
3707        let group_handle = group
3708            .map(|key| self.session.handle_by_key(key))
3709            .transpose()?;
3710        let lowered_inputs = self.lowered_reduce_terms(terms)?;
3711        let mut inputs = lowered_inputs.iter();
3712        let mut pairs = Vec::with_capacity(terms.len());
3713        for (reduction, input) in terms {
3714            let handle = if input.is_some() {
3715                Some(inputs.next().expect("one lowered handle per input"))
3716            } else {
3717                None
3718            };
3719            pairs.push((*reduction, handle));
3720        }
3721        lineage
3722            .validate_reduce_by(root, group_handle, &pairs)
3723            .map_err(Error::from_orm)
3724    }
3725
3726    fn validated_reduce_by_field<Owner: Model<Schema = S>, V>(
3727        &self,
3728        group: BoundField<S, Owner, V>,
3729        terms: &[(
3730            type_bridge_orm::match_request::Reduction,
3731            Option<(BindingKey, &'static str)>,
3732        )],
3733    ) -> Result<ValidatedMatchRequest> {
3734        let root = self.session.handle_by_key(self.selection.binding_key())?;
3735        let hidden_groups = (group.key != self.selection.binding_key())
3736            .then_some(group.key)
3737            .into_iter()
3738            .collect::<Vec<_>>();
3739        let lineage = self.lineage_with_hidden(&hidden_groups)?;
3740        let group = self.session.lower_field(group.key, group.owns_id_json)?;
3741        let lowered_inputs = self.lowered_reduce_terms(terms)?;
3742        let mut inputs = lowered_inputs.iter();
3743        let mut pairs = Vec::with_capacity(terms.len());
3744        for (reduction, input) in terms {
3745            let handle = if input.is_some() {
3746                Some(inputs.next().expect("one lowered handle per input"))
3747            } else {
3748                None
3749            };
3750            pairs.push((*reduction, handle));
3751        }
3752        lineage
3753            .validate_reduce_by_field(root, &group, &pairs)
3754            .map_err(Error::from_orm)
3755    }
3756
3757    fn validated_reduce_by_fields(
3758        &self,
3759        groups: &[(BindingKey, &'static str)],
3760        terms: &[(
3761            type_bridge_orm::match_request::Reduction,
3762            Option<(BindingKey, &'static str)>,
3763        )],
3764    ) -> Result<ValidatedMatchRequest> {
3765        let root = self.session.handle_by_key(self.selection.binding_key())?;
3766        let mut hidden_keys = Vec::new();
3767        for (key, _) in groups {
3768            if *key != self.selection.binding_key() && !hidden_keys.contains(key) {
3769                hidden_keys.push(*key);
3770            }
3771        }
3772        let lineage = self.lineage_with_hidden(&hidden_keys)?;
3773        let lowered_groups = groups
3774            .iter()
3775            .map(|(key, owns_id_json)| self.session.lower_field(*key, owns_id_json))
3776            .collect::<Result<Vec<_>>>()?;
3777        let group_refs = lowered_groups.iter().collect::<Vec<_>>();
3778        let lowered_inputs = self.lowered_reduce_terms(terms)?;
3779        let mut inputs = lowered_inputs.iter();
3780        let mut pairs = Vec::with_capacity(terms.len());
3781        for (reduction, input) in terms {
3782            let handle = if input.is_some() {
3783                Some(inputs.next().expect("one lowered handle per input"))
3784            } else {
3785                None
3786            };
3787            pairs.push((*reduction, handle));
3788        }
3789        lineage
3790            .validate_reduce_by_fields(root, &group_refs, &pairs)
3791            .map_err(Error::from_orm)
3792    }
3793
3794    fn decoded_reduction_rows<'result>(
3795        &self,
3796        validated: &ValidatedMatchRequest,
3797        result: &'result ValidatedMatchResult,
3798        deadline: QueryExecutionDeadline,
3799    ) -> Result<&'result [type_bridge_orm::match_request::ReductionRow]> {
3800        self.session
3801            .check_invocation(deadline, ModelValidationPhase::Hydration)?;
3802        match result
3803            .for_request(validated)
3804            .map_err(|error| Error::from_orm_hydration(error.into()))?
3805        {
3806            MatchResult::Reduction { rows, .. }
3807            | MatchResult::FieldReduction { rows, .. }
3808            | MatchResult::FieldTupleReduction { rows, .. } => Ok(rows),
3809            _ => Err(Error::model_validation(
3810                ModelValidationPhase::Hydration,
3811                "wrong_result_shape",
3812                vec![],
3813                "provider returned a non-reduction result for an aggregate",
3814                None,
3815            )),
3816        }
3817    }
3818
3819    /// Reduce the distinct selected stream to one typed tuple of aggregate
3820    /// values.
3821    pub async fn aggregate<T: crate::aggregate::AggregateTuple<S>>(
3822        &self,
3823        terms: T,
3824    ) -> Result<T::Output> {
3825        let deadline = self.session.begin_invocation()?;
3826        let term_list = terms.terms();
3827        let validated = self.validated_reduce(None, &term_list)?;
3828        let (validated, result) = self.execute(validated, deadline).await?;
3829        let rows = self.decoded_reduction_rows(&validated, &result, deadline)?;
3830        let [row] = rows else {
3831            return Err(Error::model_validation(
3832                ModelValidationPhase::Hydration,
3833                "wrong_result_shape",
3834                vec![],
3835                "ungrouped aggregates require exactly one reduction row",
3836                None,
3837            ));
3838        };
3839        self.session
3840            .check_invocation(deadline, ModelValidationPhase::Hydration)?;
3841        let output = T::decode(row.values())?;
3842        self.session
3843            .check_invocation(deadline, ModelValidationPhase::Hydration)?;
3844        Ok(output)
3845    }
3846
3847    /// Group the distinct selected stream by another attached binding's
3848    /// distinct identities before aggregating.
3849    pub fn group_by<G: Selectable<S>>(&self, group: G) -> Result<GroupedQuery<'s, 'db, S, B, G>> {
3850        self.ensure_open(ModelValidationPhase::Input)?;
3851        self.session.handle_by_key(group.binding_key())?;
3852        Ok(GroupedQuery {
3853            query: self.clone(),
3854            group,
3855        })
3856    }
3857
3858    /// Group the distinct selected stream by each witnessed value of one
3859    /// generated owned field before aggregating.
3860    pub fn group_by_field<Owner, V>(
3861        &self,
3862        group: BoundField<S, Owner, V>,
3863    ) -> Result<FieldGroupedQuery<'s, 'db, S, B, Owner, V>>
3864    where
3865        Owner: Model<Schema = S>,
3866        V: GroupedQueryValue,
3867    {
3868        self.ensure_open(ModelValidationPhase::Input)?;
3869        self.session.lower_field(group.key, group.owns_id_json)?;
3870        Ok(FieldGroupedQuery {
3871            query: self.clone(),
3872            group,
3873        })
3874    }
3875
3876    /// Group the distinct selected stream by the Cartesian tuple of multiple
3877    /// generated owned fields' witnessed values before aggregating.
3878    pub fn group_by_fields<G>(&self, groups: G) -> Result<FieldTupleGroupedQuery<'s, 'db, S, B, G>>
3879    where
3880        G: FieldGroupTuple<S>,
3881    {
3882        self.ensure_open(ModelValidationPhase::Input)?;
3883        for (key, owns_id_json) in groups.fields() {
3884            self.session.lower_field(key, owns_id_json)?;
3885        }
3886        Ok(FieldTupleGroupedQuery {
3887            query: self.clone(),
3888            groups,
3889        })
3890    }
3891}
3892
3893/// One query lineage grouped by a second attached binding for aggregation.
3894pub struct GroupedQuery<'s, 'db, S: Schema, B: Selectable<S>, G: Selectable<S>> {
3895    query: Query<'s, 'db, S, B>,
3896    group: G,
3897}
3898
3899impl<'s, 'db, S: Schema, B: Selectable<S>, G: Selectable<S>> GroupedQuery<'s, 'db, S, B, G> {
3900    /// Explicitly close this independently owned grouped-query lineage.
3901    pub fn close(&self) {
3902        self.query.close();
3903    }
3904
3905    /// Return whether this grouped query was explicitly closed.
3906    #[must_use]
3907    pub fn is_closed(&self) -> bool {
3908        self.query.is_closed()
3909    }
3910
3911    /// Reduce each witnessed distinct group identity to one typed tuple,
3912    /// returning materialized group keys with their aggregate values.
3913    pub async fn aggregate<T: crate::aggregate::AggregateTuple<S>>(
3914        &self,
3915        terms: T,
3916    ) -> Result<Vec<(G::Output, T::Output)>> {
3917        let deadline = self.query.session.begin_invocation()?;
3918        let term_list = terms.terms();
3919        let validated = self
3920            .query
3921            .validated_reduce(Some(self.group.binding_key()), &term_list)?;
3922        let (validated, result) = self.query.execute(validated, deadline).await?;
3923        if self
3924            .query
3925            .session
3926            .uses_successor_projected_materialization()
3927        {
3928            let projected = self
3929                .query
3930                .session
3931                .materialize_projected_query_value(&validated, &result, deadline)?;
3932            let ProjectedQueryValue::Reduction { rows, .. } = projected else {
3933                return Err(Error::model_validation(
3934                    ModelValidationPhase::Hydration,
3935                    "wrong_result_shape",
3936                    vec![],
3937                    "provider returned a non-reduction result for an aggregate",
3938                    None,
3939                ));
3940            };
3941            let mut outputs = Vec::with_capacity(rows.len());
3942            for row in rows {
3943                self.query
3944                    .session
3945                    .check_invocation(deadline, ModelValidationPhase::Hydration)?;
3946                let Some(ProjectedReductionGroup::Thing(thing)) = row.group() else {
3947                    return Err(Error::model_validation(
3948                        ModelValidationPhase::Hydration,
3949                        "wrong_result_shape",
3950                        vec![],
3951                        "grouped aggregates require group evidence per row",
3952                        None,
3953                    ));
3954                };
3955                let key = G::__materialize_projected_output(self.query.session, thing)?;
3956                let values = decoded_projected_reduction_values(row.values());
3957                outputs.push((key, T::decode(&values)?));
3958            }
3959            self.query
3960                .session
3961                .check_invocation(deadline, ModelValidationPhase::Hydration)?;
3962            return Ok(outputs);
3963        }
3964        let rows = self
3965            .query
3966            .decoded_reduction_rows(&validated, &result, deadline)?;
3967        let mut outputs = Vec::with_capacity(rows.len());
3968        for row in rows {
3969            self.query
3970                .session
3971                .check_invocation(deadline, ModelValidationPhase::Hydration)?;
3972            let thing = row.group().ok_or_else(|| {
3973                Error::model_validation(
3974                    ModelValidationPhase::Hydration,
3975                    "wrong_result_shape",
3976                    vec![],
3977                    "grouped aggregates require group evidence per row",
3978                    None,
3979                )
3980            })?;
3981            let client_row = self.query.session.client_row_for(thing)?;
3982            let key = G::materialize_output(&client_row).map_err(|error| {
3983                crate::entity_codec::map_validation_error(error, ModelValidationPhase::Hydration)
3984            })?;
3985            outputs.push((key, T::decode(row.values())?));
3986        }
3987        self.query
3988            .session
3989            .check_invocation(deadline, ModelValidationPhase::Hydration)?;
3990        Ok(outputs)
3991    }
3992}
3993
3994/// One query lineage grouped by a generated owned field value for
3995/// aggregation.
3996pub struct FieldGroupedQuery<
3997    's,
3998    'db,
3999    S: Schema,
4000    B: Selectable<S>,
4001    Owner: Model<Schema = S>,
4002    V: GroupedQueryValue,
4003> {
4004    query: Query<'s, 'db, S, B>,
4005    group: BoundField<S, Owner, V>,
4006}
4007
4008impl<'s, 'db, S, B, Owner, V> FieldGroupedQuery<'s, 'db, S, B, Owner, V>
4009where
4010    S: Schema,
4011    B: Selectable<S>,
4012    Owner: Model<Schema = S>,
4013    V: GroupedQueryValue,
4014{
4015    /// Explicitly close this independently owned grouped-query lineage.
4016    pub fn close(&self) {
4017        self.query.close();
4018    }
4019
4020    /// Return whether this grouped query was explicitly closed.
4021    #[must_use]
4022    pub fn is_closed(&self) -> bool {
4023        self.query.is_closed()
4024    }
4025
4026    /// Reduce each witnessed distinct field value to one typed tuple,
4027    /// returning its exact generated attribute wrapper with the aggregates.
4028    pub async fn aggregate<T: crate::aggregate::AggregateTuple<S>>(
4029        &self,
4030        terms: T,
4031    ) -> Result<Vec<(V, T::Output)>> {
4032        let deadline = self.query.session.begin_invocation()?;
4033        let term_list = terms.terms();
4034        let validated = self
4035            .query
4036            .validated_reduce_by_field(self.group, &term_list)?;
4037        let (validated, result) = self.query.execute(validated, deadline).await?;
4038        let rows = self
4039            .query
4040            .decoded_reduction_rows(&validated, &result, deadline)?;
4041        let mut outputs = Vec::with_capacity(rows.len());
4042        for row in rows {
4043            self.query
4044                .session
4045                .check_invocation(deadline, ModelValidationPhase::Hydration)?;
4046            let value = row.field_group().ok_or_else(|| {
4047                Error::model_validation(
4048                    ModelValidationPhase::Hydration,
4049                    "wrong_result_shape",
4050                    vec![],
4051                    "field-grouped aggregates require scalar group evidence per row",
4052                    None,
4053                )
4054            })?;
4055            let key = V::from_group_scalar(encoded_group_scalar(value)?)
4056                .map_err(|error| map_validation_error(error, ModelValidationPhase::Hydration))?;
4057            outputs.push((key, T::decode(row.values())?));
4058        }
4059        self.query
4060            .session
4061            .check_invocation(deadline, ModelValidationPhase::Hydration)?;
4062        Ok(outputs)
4063    }
4064}
4065
4066/// A sealed tuple of two through sixteen generated owned fields used as one
4067/// typed grouped-reduction key.
4068pub trait FieldGroupTuple<S: Schema>: field_group_tuple_sealed::Sealed<S> + Copy {
4069    /// Exact generated attribute-wrapper tuple returned for each group row.
4070    type Output;
4071
4072    #[doc(hidden)]
4073    fn fields(&self) -> Vec<(BindingKey, &'static str)>;
4074
4075    #[doc(hidden)]
4076    fn decode(values: &[AttributeValue]) -> Result<Self::Output>;
4077}
4078
4079mod field_group_tuple_sealed {
4080    pub trait Sealed<S> {}
4081}
4082
4083macro_rules! field_group_tuple {
4084    ($(($owner:ident, $value:ident, $index:tt)),+) => {
4085        impl<S, $($owner, $value),+> field_group_tuple_sealed::Sealed<S>
4086            for ($(BoundField<S, $owner, $value>,)+)
4087        where
4088            S: Schema,
4089            $($owner: Model<Schema = S>, $value: GroupedQueryValue),+
4090        {
4091        }
4092
4093        impl<S, $($owner, $value),+> FieldGroupTuple<S>
4094            for ($(BoundField<S, $owner, $value>,)+)
4095        where
4096            S: Schema,
4097            $($owner: Model<Schema = S>, $value: GroupedQueryValue),+
4098        {
4099            type Output = ($($value,)+);
4100
4101            fn fields(&self) -> Vec<(BindingKey, &'static str)> {
4102                vec![$((self.$index.key, self.$index.owns_id_json)),+]
4103            }
4104
4105            fn decode(values: &[AttributeValue]) -> Result<Self::Output> {
4106                let expected = [$(stringify!($owner)),+].len();
4107                if values.len() != expected {
4108                    return Err(Error::model_validation(
4109                        ModelValidationPhase::Hydration,
4110                        "wrong_result_shape",
4111                        vec![],
4112                        "tuple-field-grouped aggregate key has the wrong arity",
4113                        None,
4114                    ));
4115                }
4116                Ok(($(
4117                    $value::from_group_scalar(encoded_group_scalar(&values[$index])?)
4118                        .map_err(|error| {
4119                            map_validation_error(error, ModelValidationPhase::Hydration)
4120                        })?,
4121                )+))
4122            }
4123        }
4124    };
4125}
4126
4127field_group_tuple!((O1, V1, 0), (O2, V2, 1));
4128field_group_tuple!((O1, V1, 0), (O2, V2, 1), (O3, V3, 2));
4129field_group_tuple!((O1, V1, 0), (O2, V2, 1), (O3, V3, 2), (O4, V4, 3));
4130field_group_tuple!(
4131    (O1, V1, 0),
4132    (O2, V2, 1),
4133    (O3, V3, 2),
4134    (O4, V4, 3),
4135    (O5, V5, 4)
4136);
4137field_group_tuple!(
4138    (O1, V1, 0),
4139    (O2, V2, 1),
4140    (O3, V3, 2),
4141    (O4, V4, 3),
4142    (O5, V5, 4),
4143    (O6, V6, 5)
4144);
4145field_group_tuple!(
4146    (O1, V1, 0),
4147    (O2, V2, 1),
4148    (O3, V3, 2),
4149    (O4, V4, 3),
4150    (O5, V5, 4),
4151    (O6, V6, 5),
4152    (O7, V7, 6)
4153);
4154field_group_tuple!(
4155    (O1, V1, 0),
4156    (O2, V2, 1),
4157    (O3, V3, 2),
4158    (O4, V4, 3),
4159    (O5, V5, 4),
4160    (O6, V6, 5),
4161    (O7, V7, 6),
4162    (O8, V8, 7)
4163);
4164field_group_tuple!(
4165    (O1, V1, 0),
4166    (O2, V2, 1),
4167    (O3, V3, 2),
4168    (O4, V4, 3),
4169    (O5, V5, 4),
4170    (O6, V6, 5),
4171    (O7, V7, 6),
4172    (O8, V8, 7),
4173    (O9, V9, 8)
4174);
4175field_group_tuple!(
4176    (O1, V1, 0),
4177    (O2, V2, 1),
4178    (O3, V3, 2),
4179    (O4, V4, 3),
4180    (O5, V5, 4),
4181    (O6, V6, 5),
4182    (O7, V7, 6),
4183    (O8, V8, 7),
4184    (O9, V9, 8),
4185    (O10, V10, 9)
4186);
4187field_group_tuple!(
4188    (O1, V1, 0),
4189    (O2, V2, 1),
4190    (O3, V3, 2),
4191    (O4, V4, 3),
4192    (O5, V5, 4),
4193    (O6, V6, 5),
4194    (O7, V7, 6),
4195    (O8, V8, 7),
4196    (O9, V9, 8),
4197    (O10, V10, 9),
4198    (O11, V11, 10)
4199);
4200field_group_tuple!(
4201    (O1, V1, 0),
4202    (O2, V2, 1),
4203    (O3, V3, 2),
4204    (O4, V4, 3),
4205    (O5, V5, 4),
4206    (O6, V6, 5),
4207    (O7, V7, 6),
4208    (O8, V8, 7),
4209    (O9, V9, 8),
4210    (O10, V10, 9),
4211    (O11, V11, 10),
4212    (O12, V12, 11)
4213);
4214field_group_tuple!(
4215    (O1, V1, 0),
4216    (O2, V2, 1),
4217    (O3, V3, 2),
4218    (O4, V4, 3),
4219    (O5, V5, 4),
4220    (O6, V6, 5),
4221    (O7, V7, 6),
4222    (O8, V8, 7),
4223    (O9, V9, 8),
4224    (O10, V10, 9),
4225    (O11, V11, 10),
4226    (O12, V12, 11),
4227    (O13, V13, 12)
4228);
4229field_group_tuple!(
4230    (O1, V1, 0),
4231    (O2, V2, 1),
4232    (O3, V3, 2),
4233    (O4, V4, 3),
4234    (O5, V5, 4),
4235    (O6, V6, 5),
4236    (O7, V7, 6),
4237    (O8, V8, 7),
4238    (O9, V9, 8),
4239    (O10, V10, 9),
4240    (O11, V11, 10),
4241    (O12, V12, 11),
4242    (O13, V13, 12),
4243    (O14, V14, 13)
4244);
4245field_group_tuple!(
4246    (O1, V1, 0),
4247    (O2, V2, 1),
4248    (O3, V3, 2),
4249    (O4, V4, 3),
4250    (O5, V5, 4),
4251    (O6, V6, 5),
4252    (O7, V7, 6),
4253    (O8, V8, 7),
4254    (O9, V9, 8),
4255    (O10, V10, 9),
4256    (O11, V11, 10),
4257    (O12, V12, 11),
4258    (O13, V13, 12),
4259    (O14, V14, 13),
4260    (O15, V15, 14)
4261);
4262field_group_tuple!(
4263    (O1, V1, 0),
4264    (O2, V2, 1),
4265    (O3, V3, 2),
4266    (O4, V4, 3),
4267    (O5, V5, 4),
4268    (O6, V6, 5),
4269    (O7, V7, 6),
4270    (O8, V8, 7),
4271    (O9, V9, 8),
4272    (O10, V10, 9),
4273    (O11, V11, 10),
4274    (O12, V12, 11),
4275    (O13, V13, 12),
4276    (O14, V14, 13),
4277    (O15, V15, 14),
4278    (O16, V16, 15)
4279);
4280
4281/// One query lineage grouped by a generated owned-field tuple for
4282/// aggregation.
4283pub struct FieldTupleGroupedQuery<'s, 'db, S: Schema, B: Selectable<S>, G: FieldGroupTuple<S>> {
4284    query: Query<'s, 'db, S, B>,
4285    groups: G,
4286}
4287
4288impl<'s, 'db, S, B, G> FieldTupleGroupedQuery<'s, 'db, S, B, G>
4289where
4290    S: Schema,
4291    B: Selectable<S>,
4292    G: FieldGroupTuple<S>,
4293{
4294    /// Explicitly close this independently owned grouped-query lineage.
4295    pub fn close(&self) {
4296        self.query.close();
4297    }
4298
4299    /// Return whether this grouped query was explicitly closed.
4300    #[must_use]
4301    pub fn is_closed(&self) -> bool {
4302        self.query.is_closed()
4303    }
4304
4305    /// Reduce each witnessed distinct field-value tuple to one typed tuple,
4306    /// returning exact generated attribute wrappers with the aggregates.
4307    pub async fn aggregate<T: crate::aggregate::AggregateTuple<S>>(
4308        &self,
4309        terms: T,
4310    ) -> Result<Vec<(G::Output, T::Output)>> {
4311        let deadline = self.query.session.begin_invocation()?;
4312        let term_list = terms.terms();
4313        let group_fields = self.groups.fields();
4314        let validated = self
4315            .query
4316            .validated_reduce_by_fields(&group_fields, &term_list)?;
4317        let (validated, result) = self.query.execute(validated, deadline).await?;
4318        let rows = self
4319            .query
4320            .decoded_reduction_rows(&validated, &result, deadline)?;
4321        let mut outputs = Vec::with_capacity(rows.len());
4322        for row in rows {
4323            self.query
4324                .session
4325                .check_invocation(deadline, ModelValidationPhase::Hydration)?;
4326            let values = row.field_groups().ok_or_else(|| {
4327                Error::model_validation(
4328                    ModelValidationPhase::Hydration,
4329                    "wrong_result_shape",
4330                    vec![],
4331                    "tuple-field-grouped aggregates require tuple group evidence per row",
4332                    None,
4333                )
4334            })?;
4335            outputs.push((G::decode(values)?, T::decode(row.values())?));
4336        }
4337        self.query
4338            .session
4339            .check_invocation(deadline, ModelValidationPhase::Hydration)?;
4340        Ok(outputs)
4341    }
4342}