Skip to main content

delta_funnel/orchestrator/session/
handles.rs

1use std::fmt;
2
3use crate::{
4    MssqlTargetConfig, MssqlTargetOutputPlan, PhaseTimingReport, ResolvedMssqlTarget,
5    support::sanitize_text_for_display,
6};
7
8/// Query-load action mode requested by a caller.
9#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
10pub enum RunMode {
11    /// Plan and execute the selected output workflow.
12    #[default]
13    Execute,
14    /// Reuse planning paths without row production or SQL Server write effects.
15    DryRun,
16}
17
18/// Lazy table identity owned by a query-load session.
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct LazyTable {
21    id: LazyTableId,
22    kind: LazyTableKind,
23    name: String,
24}
25
26impl LazyTable {
27    /// Creates a placeholder lazy table handle for future registration slices.
28    #[cfg(test)]
29    #[must_use]
30    pub(crate) fn placeholder(id: u64, kind: LazyTableKind) -> Self {
31        Self {
32            id: LazyTableId(id),
33            kind,
34            name: format!("table_{id}"),
35        }
36    }
37
38    pub(super) fn delta_source(id: u64, name: String) -> Self {
39        Self {
40            id: LazyTableId(id),
41            kind: LazyTableKind::DeltaSource,
42            name,
43        }
44    }
45
46    pub(super) fn derived_sql(id: u64) -> Self {
47        Self {
48            id: LazyTableId(id),
49            kind: LazyTableKind::DerivedSql,
50            name: format!("table_{id}"),
51        }
52    }
53
54    pub(super) fn with_name(&self, name: String) -> Self {
55        Self {
56            id: self.id,
57            kind: self.kind,
58            name,
59        }
60    }
61
62    /// Returns the stable session-local table id.
63    #[must_use]
64    pub const fn id(&self) -> u64 {
65        self.id.0
66    }
67
68    /// Returns the lazy table kind.
69    #[must_use]
70    pub const fn kind(&self) -> LazyTableKind {
71        self.kind
72    }
73
74    /// Returns the session-owned table name for this lazy table.
75    #[must_use]
76    pub fn name(&self) -> &str {
77        &self.name
78    }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82struct LazyTableId(u64);
83
84/// Kind of lazy table represented by a session handle.
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum LazyTableKind {
87    /// Registered Delta source table.
88    DeltaSource,
89    /// SQL-derived table.
90    DerivedSql,
91}
92
93/// MSSQL output target selected from a lazy table.
94#[derive(Clone, PartialEq, Eq)]
95pub struct MssqlOutputTarget {
96    output_name: String,
97    target: MssqlTargetConfig,
98    run_mode: RunMode,
99}
100
101impl MssqlOutputTarget {
102    /// Creates an MSSQL output target request.
103    #[must_use]
104    pub fn new(
105        output_name: impl Into<String>,
106        target: MssqlTargetConfig,
107        run_mode: RunMode,
108    ) -> Self {
109        Self {
110            output_name: output_name.into(),
111            target,
112            run_mode,
113        }
114    }
115
116    /// Returns the selected output name.
117    #[must_use]
118    pub fn output_name(&self) -> &str {
119        &self.output_name
120    }
121
122    /// Returns the SQL Server target config.
123    #[must_use]
124    pub const fn target(&self) -> &MssqlTargetConfig {
125        &self.target
126    }
127
128    /// Returns the requested run mode.
129    #[must_use]
130    pub const fn run_mode(&self) -> RunMode {
131        self.run_mode
132    }
133}
134
135impl fmt::Debug for MssqlOutputTarget {
136    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
137        formatter
138            .debug_struct("MssqlOutputTarget")
139            .field("output_name", &sanitize_text_for_display(&self.output_name))
140            .field("target", &self.target)
141            .field("run_mode", &self.run_mode)
142            .finish()
143    }
144}
145
146/// Planned output write request before schema planning or execution.
147#[derive(Debug, Clone, PartialEq, Eq)]
148pub struct OutputWritePlan {
149    table: LazyTable,
150    target: MssqlOutputTarget,
151}
152
153impl OutputWritePlan {
154    /// Creates an output write request for a lazy table.
155    #[must_use]
156    pub const fn new(table: LazyTable, target: MssqlOutputTarget) -> Self {
157        Self { table, target }
158    }
159
160    /// Returns the selected lazy table.
161    #[must_use]
162    pub const fn table(&self) -> &LazyTable {
163        &self.table
164    }
165
166    /// Returns the selected MSSQL output target.
167    #[must_use]
168    pub const fn target(&self) -> &MssqlOutputTarget {
169        &self.target
170    }
171}
172
173/// Planned MSSQL output request for one selected lazy table.
174#[derive(Debug, Clone, PartialEq, Eq)]
175pub struct PlannedMssqlOutput {
176    request: OutputWritePlan,
177    resolved_target: ResolvedMssqlTarget,
178    output_plan: MssqlTargetOutputPlan,
179    phase_timings: Vec<PhaseTimingReport>,
180}
181
182impl PlannedMssqlOutput {
183    pub(super) fn new(
184        request: OutputWritePlan,
185        resolved_target: ResolvedMssqlTarget,
186        output_plan: MssqlTargetOutputPlan,
187        phase_timings: Vec<PhaseTimingReport>,
188    ) -> Self {
189        Self {
190            request,
191            resolved_target,
192            output_plan,
193            phase_timings,
194        }
195    }
196
197    /// Returns the original lazy-table output request.
198    #[must_use]
199    pub const fn request(&self) -> &OutputWritePlan {
200        &self.request
201    }
202
203    /// Returns the selected lazy table.
204    #[must_use]
205    pub const fn table(&self) -> &LazyTable {
206        self.request.table()
207    }
208
209    /// Returns the selected MSSQL target request.
210    #[must_use]
211    pub const fn target(&self) -> &MssqlOutputTarget {
212        self.request.target()
213    }
214
215    /// Returns the resolved SQL Server target, including the private connection config.
216    #[must_use]
217    pub const fn resolved_target(&self) -> &ResolvedMssqlTarget {
218        &self.resolved_target
219    }
220
221    /// Returns the complete SQL Server target output plan.
222    #[must_use]
223    pub const fn output_plan(&self) -> &MssqlTargetOutputPlan {
224        &self.output_plan
225    }
226
227    /// Returns durable phase timings captured while planning this output.
228    #[must_use]
229    pub fn phase_timings(&self) -> &[PhaseTimingReport] {
230        &self.phase_timings
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use crate::{
237        DeltaFunnelError, LoadMode, MssqlConnectionConfig, MssqlTargetConfig, MssqlTargetTable,
238    };
239
240    use super::{LazyTable, LazyTableKind, MssqlOutputTarget, OutputWritePlan, RunMode};
241
242    #[test]
243    fn output_request_shapes_preserve_table_target_and_run_mode() -> Result<(), DeltaFunnelError> {
244        let table = LazyTable::placeholder(7, LazyTableKind::DerivedSql);
245        let target_config = MssqlTargetConfig::new(MssqlTargetTable::new("dbo", "orders")?)
246            .with_load_mode(LoadMode::CreateAndLoad)
247            .with_connection(
248                MssqlConnectionConfig::new(
249                    "server=tcp:sql.example.com;database=warehouse;user=admin;password=secret-token",
250                )?
251                .with_display_label("warehouse-primary"),
252            );
253        let target = MssqlOutputTarget::new("orders_output", target_config, RunMode::DryRun);
254        let plan = OutputWritePlan::new(table.clone(), target.clone());
255
256        assert_eq!(table.id(), 7);
257        assert_eq!(table.kind(), LazyTableKind::DerivedSql);
258        assert_eq!(target.output_name(), "orders_output");
259        assert_eq!(target.run_mode(), RunMode::DryRun);
260        assert_eq!(target.target().load_mode(), LoadMode::CreateAndLoad);
261        assert_eq!(plan.table(), &table);
262        assert_eq!(plan.target(), &target);
263
264        let debug = format!("{target:?}");
265        assert!(debug.contains("orders_output"));
266        assert!(!debug.contains("secret-token"));
267        assert!(!debug.contains("password"));
268        assert!(!debug.contains("server=tcp"));
269        Ok(())
270    }
271}