Skip to main content

icydb_core/db/query/fluent/load/
terminals.rs

1//! Module: query::fluent::load::terminals
2//! Responsibility: fluent load terminal APIs and terminal-plan explanation entrypoints.
3//! Does not own: planner semantic validation or executor runtime routing decisions.
4//! Boundary: delegates to session planning/execution and returns typed query results.
5//!
6//! Terminal Execution Model
7//!
8//! Fluent terminals are concrete descriptors with a 1:1 mapping to session
9//! execution and explain entrypoints. This module may orchestrate descriptor
10//! construction, non-paged gating, and public output shaping, but it must not
11//! carry terminal-kind enums, transport output enums, or match-based execution
12//! dispatch. Adding a new terminal means adding a new descriptor type and one
13//! direct `TerminalStrategyDriver` implementation for that descriptor.
14
15mod aggregate;
16mod driver;
17mod output;
18mod projection;
19mod read_intent;
20mod support;
21
22#[cfg(feature = "diagnostics")]
23use crate::db::QueryExecutionAttribution;
24use crate::{
25    db::{
26        DbSession, PersistedRow,
27        query::{
28            api::ResponseCardinalityExt,
29            builder::{FirstIdTerminal, LastIdTerminal},
30            explain::{ExplainAggregateTerminalPlan, ExplainExecutionNodeDescriptor},
31            fluent::load::{FluentLoadQuery, LoadQueryResult},
32            intent::QueryError,
33        },
34        response::EntityResponse,
35    },
36    traits::EntityValue,
37    types::Id,
38};
39
40impl<E> FluentLoadQuery<'_, E>
41where
42    E: PersistedRow,
43{
44    // ------------------------------------------------------------------
45    // Execution (single semantic boundary)
46    // ------------------------------------------------------------------
47
48    /// Execute this query using the session's policy settings.
49    pub fn execute(&self) -> Result<LoadQueryResult<E>, QueryError>
50    where
51        E: EntityValue,
52    {
53        self.with_admitted_non_paged(DbSession::execute_query_result)
54    }
55
56    /// Execute this query with diagnostics attribution through the same
57    /// admitted non-paged boundary as `execute()`.
58    #[cfg(feature = "diagnostics")]
59    #[doc(hidden)]
60    pub fn execute_with_attribution(
61        &self,
62    ) -> Result<(LoadQueryResult<E>, QueryExecutionAttribution), QueryError>
63    where
64        E: EntityValue,
65    {
66        self.with_admitted_non_paged(DbSession::execute_query_result_with_attribution)
67    }
68
69    /// Execute this query through the scalar rows-only session boundary.
70    pub fn execute_rows(&self) -> Result<EntityResponse<E>, QueryError>
71    where
72        E: EntityValue,
73    {
74        self.with_admitted_non_paged(DbSession::execute_scalar_query_rows)
75    }
76
77    /// Explain scalar load execution shape without executing the query.
78    pub fn explain_execution(&self) -> Result<ExplainExecutionNodeDescriptor, QueryError>
79    where
80        E: EntityValue,
81    {
82        self.explain_execution_descriptor()
83    }
84
85    /// Explain scalar load execution shape as deterministic text.
86    pub fn explain_execution_text(&self) -> Result<String, QueryError>
87    where
88        E: EntityValue,
89    {
90        self.render_execution_descriptor(|descriptor| descriptor.render_text_tree())
91    }
92
93    /// Explain scalar load execution shape as canonical JSON.
94    pub fn explain_execution_json(&self) -> Result<String, QueryError>
95    where
96        E: EntityValue,
97    {
98        self.with_non_paged(DbSession::explain_query_execution_json_with_visible_indexes)
99    }
100
101    /// Explain scalar load execution shape as verbose text with diagnostics.
102    pub fn explain_execution_verbose(&self) -> Result<String, QueryError>
103    where
104        E: EntityValue,
105    {
106        self.with_non_paged(DbSession::explain_query_execution_verbose_with_visible_indexes)
107    }
108
109    /// Execute and return the total persisted payload bytes for the effective
110    /// result window.
111    pub fn bytes(&self) -> Result<u64, QueryError>
112    where
113        E: EntityValue,
114    {
115        self.with_admitted_non_paged(DbSession::execute_fluent_bytes)
116    }
117
118    /// Execute and return the total serialized bytes for `field` over the
119    /// effective result window.
120    pub fn bytes_by(&self, field: impl AsRef<str>) -> Result<u64, QueryError>
121    where
122        E: EntityValue,
123    {
124        self.with_admitted_non_paged_slot(field, |session, query, target_slot| {
125            session.execute_fluent_bytes_by_slot(query, target_slot)
126        })
127    }
128
129    /// Explain `bytes_by(field)` routing without executing the terminal.
130    pub fn explain_bytes_by(
131        &self,
132        field: impl AsRef<str>,
133    ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
134    where
135        E: EntityValue,
136    {
137        self.with_non_paged_slot(field, |session, query, target_slot| {
138            session.explain_query_bytes_by_with_visible_indexes(query, target_slot.field())
139        })
140    }
141
142    /// Execute and return the first matching identifier in response order, if any.
143    pub fn first(&self) -> Result<Option<Id<E>>, QueryError>
144    where
145        E: EntityValue,
146    {
147        self.execute_terminal(FirstIdTerminal::new())
148    }
149
150    /// Explain scalar `first()` routing without executing the terminal.
151    pub fn explain_first(&self) -> Result<ExplainAggregateTerminalPlan, QueryError>
152    where
153        E: EntityValue,
154    {
155        self.explain_terminal(&FirstIdTerminal::new())
156    }
157
158    /// Execute and return the last matching identifier in response order, if any.
159    pub fn last(&self) -> Result<Option<Id<E>>, QueryError>
160    where
161        E: EntityValue,
162    {
163        self.execute_terminal(LastIdTerminal::new())
164    }
165
166    /// Explain scalar `last()` routing without executing the terminal.
167    pub fn explain_last(&self) -> Result<ExplainAggregateTerminalPlan, QueryError>
168    where
169        E: EntityValue,
170    {
171        self.explain_terminal(&LastIdTerminal::new())
172    }
173
174    /// Execute and require exactly one matching row.
175    pub fn require_one(&self) -> Result<(), QueryError>
176    where
177        E: EntityValue,
178    {
179        self.execute_rows()?.require_one()?;
180        Ok(())
181    }
182
183    /// Execute and require at least one matching row.
184    pub fn require_some(&self) -> Result<(), QueryError>
185    where
186        E: EntityValue,
187    {
188        self.execute_rows()?.require_some()?;
189        Ok(())
190    }
191}