delta_funnel/report/delta/
source.rs1use std::fmt;
2
3use crate::{
4 DeltaProtocolReport, DeltaProviderReadStatsSnapshot, DeltaProviderReaderBackend,
5 DeltaProviderScanExecutionOptions, FileCount, PhaseTimingReport, QueryOptions,
6 ReportReasonCode, support::sanitize_uri_for_display,
7};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum SourceUsageStatus {
12 Used,
14 NotUsed,
16 Unknown,
18}
19
20impl SourceUsageStatus {
21 #[must_use]
23 pub const fn as_str(self) -> &'static str {
24 match self {
25 Self::Used => "used",
26 Self::NotUsed => "not_used",
27 Self::Unknown => "unknown",
28 }
29 }
30}
31
32impl fmt::Display for SourceUsageStatus {
33 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
34 formatter.write_str(self.as_str())
35 }
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct DeltaSourceReport {
41 source_name: String,
42 source_uri: String,
43 snapshot_version: u64,
44 protocol: DeltaProtocolReport,
45 scheduling: DeltaProviderSchedulingReport,
46 file_count: FileCount,
47 file_count_reason: Option<ReportReasonCode>,
48 scan_metadata_exhausted: bool,
49 usage_status: SourceUsageStatus,
50 used_by_output_names: Vec<String>,
51 provider_read_stats: Option<DeltaProviderReadStatsSnapshot>,
52 provider_stats_reason: Option<ReportReasonCode>,
53 phase_timings: Vec<PhaseTimingReport>,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub struct DeltaProviderSchedulingReport {
59 query_target_partitions: Option<u64>,
60 reader_backend: DeltaProviderReaderBackend,
61 max_concurrent_file_reads_per_scan: Option<u64>,
62 max_concurrent_file_reads_per_partition: u64,
63 output_buffer_capacity_per_partition: u64,
64 native_async_prefetch_file_count_per_partition: u64,
65}
66
67impl DeltaProviderSchedulingReport {
68 pub(crate) fn from_options(
69 query_options: QueryOptions,
70 scan_options: DeltaProviderScanExecutionOptions,
71 ) -> Self {
72 Self {
73 query_target_partitions: query_options
74 .target_partitions
75 .map(crate::usize_to_u64_saturating),
76 reader_backend: scan_options.reader_backend,
77 max_concurrent_file_reads_per_scan: scan_options
78 .max_concurrent_file_reads_per_scan
79 .map(crate::usize_to_u64_saturating),
80 max_concurrent_file_reads_per_partition: crate::usize_to_u64_saturating(
81 scan_options.max_concurrent_file_reads_per_partition,
82 ),
83 output_buffer_capacity_per_partition: crate::usize_to_u64_saturating(
84 scan_options.output_buffer_capacity_per_partition,
85 ),
86 native_async_prefetch_file_count_per_partition: crate::usize_to_u64_saturating(
87 scan_options.native_async_prefetch_file_count_per_partition,
88 ),
89 }
90 }
91
92 #[must_use]
94 pub const fn query_target_partitions(&self) -> Option<u64> {
95 self.query_target_partitions
96 }
97
98 #[must_use]
100 pub const fn reader_backend(&self) -> DeltaProviderReaderBackend {
101 self.reader_backend
102 }
103
104 #[must_use]
109 pub const fn max_concurrent_file_reads_per_scan(&self) -> Option<u64> {
110 self.max_concurrent_file_reads_per_scan
111 }
112
113 #[must_use]
115 pub const fn max_concurrent_file_reads_per_partition(&self) -> u64 {
116 self.max_concurrent_file_reads_per_partition
117 }
118
119 #[must_use]
121 pub const fn output_buffer_capacity_per_partition(&self) -> u64 {
122 self.output_buffer_capacity_per_partition
123 }
124
125 #[must_use]
127 pub const fn native_async_prefetch_file_count_per_partition(&self) -> u64 {
128 self.native_async_prefetch_file_count_per_partition
129 }
130}
131
132impl DeltaSourceReport {
133 pub(crate) fn metadata_only(
134 source_name: impl Into<String>,
135 source_uri: impl Into<String>,
136 snapshot_version: u64,
137 protocol: DeltaProtocolReport,
138 scheduling: DeltaProviderSchedulingReport,
139 ) -> Self {
140 Self {
141 source_name: source_name.into(),
142 source_uri: sanitize_uri_for_display(&source_uri.into()),
143 snapshot_version,
144 protocol,
145 scheduling,
146 file_count: FileCount::unavailable(),
147 file_count_reason: Some(ReportReasonCode::CostAvoidance),
148 scan_metadata_exhausted: false,
149 usage_status: SourceUsageStatus::Unknown,
150 used_by_output_names: Vec::new(),
151 provider_read_stats: None,
152 provider_stats_reason: Some(ReportReasonCode::NotExecuted),
153 phase_timings: Vec::new(),
154 }
155 }
156
157 pub(crate) fn with_phase_timings(mut self, phase_timings: Vec<PhaseTimingReport>) -> Self {
158 self.phase_timings = phase_timings;
159 self
160 }
161
162 pub(crate) fn with_usage(
163 mut self,
164 usage_status: SourceUsageStatus,
165 used_by_output_names: Vec<String>,
166 ) -> Self {
167 self.usage_status = usage_status;
168 self.used_by_output_names = used_by_output_names;
169 self
170 }
171
172 pub(crate) fn with_provider_read_stats(
173 mut self,
174 stats: DeltaProviderReadStatsSnapshot,
175 ) -> Self {
176 self.scan_metadata_exhausted = stats.scan_metadata_exhausted.unwrap_or(false);
177 self.file_count = match stats.scan_metadata_exhausted {
178 Some(true) => FileCount::exact(stats.files_planned),
179 Some(false) => FileCount::estimated(stats.files_planned),
180 None => FileCount::unavailable(),
181 };
182 self.file_count_reason = match self.file_count {
183 FileCount::Exact(_) | FileCount::Estimated(_) => None,
184 FileCount::Unavailable => Some(ReportReasonCode::CapabilityUnavailable),
185 FileCount::Skipped | FileCount::NotExecuted => Some(ReportReasonCode::NotExecuted),
186 };
187 self.provider_read_stats = Some(stats);
188 self.provider_stats_reason = None;
189 self
190 }
191
192 pub(crate) fn with_provider_stats_reason(mut self, reason: ReportReasonCode) -> Self {
193 self.provider_read_stats = None;
194 self.provider_stats_reason = Some(reason);
195 self
196 }
197
198 #[must_use]
200 pub fn source_name(&self) -> &str {
201 &self.source_name
202 }
203
204 #[must_use]
206 pub fn source_uri(&self) -> &str {
207 &self.source_uri
208 }
209
210 #[must_use]
212 pub const fn snapshot_version(&self) -> u64 {
213 self.snapshot_version
214 }
215
216 #[must_use]
218 pub const fn protocol(&self) -> &DeltaProtocolReport {
219 &self.protocol
220 }
221
222 #[must_use]
224 pub const fn scheduling(&self) -> &DeltaProviderSchedulingReport {
225 &self.scheduling
226 }
227
228 #[must_use]
230 pub const fn file_count(&self) -> FileCount {
231 self.file_count
232 }
233
234 #[must_use]
236 pub const fn file_count_reason(&self) -> Option<ReportReasonCode> {
237 self.file_count_reason
238 }
239
240 #[must_use]
242 pub const fn scan_metadata_exhausted(&self) -> bool {
243 self.scan_metadata_exhausted
244 }
245
246 #[must_use]
248 pub const fn usage_status(&self) -> SourceUsageStatus {
249 self.usage_status
250 }
251
252 #[must_use]
254 pub fn used_by_output_names(&self) -> &[String] {
255 &self.used_by_output_names
256 }
257
258 #[must_use]
260 pub const fn provider_read_stats(&self) -> Option<&DeltaProviderReadStatsSnapshot> {
261 self.provider_read_stats.as_ref()
262 }
263
264 #[must_use]
266 pub const fn provider_stats_reason(&self) -> Option<ReportReasonCode> {
267 self.provider_stats_reason
268 }
269
270 #[must_use]
272 pub fn phase_timings(&self) -> &[PhaseTimingReport] {
273 &self.phase_timings
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::SourceUsageStatus;
280
281 #[test]
282 fn source_usage_status_exposes_stable_codes() {
283 assert_eq!(SourceUsageStatus::Used.as_str(), "used");
284 assert_eq!(SourceUsageStatus::NotUsed.as_str(), "not_used");
285 assert_eq!(SourceUsageStatus::Unknown.as_str(), "unknown");
286 assert_eq!(SourceUsageStatus::Unknown.to_string(), "unknown");
287 }
288}