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