1use super::*;
2
3#[cfg(unix)]
8#[derive(Debug)]
9pub struct ForwardingEventStore {
10 pub(super) namespace: String,
11 pub(super) client: Arc<EventsSplitClient>,
12}
13
14#[cfg(unix)]
15impl ForwardingEventStore {
16 pub fn new(namespace: impl Into<String>, client: Arc<EventsSplitClient>) -> Self {
17 Self {
18 namespace: namespace.into(),
19 client,
20 }
21 }
22
23 pub(super) fn unexpected_count(
24 &self,
25 op: &'static str,
26 response: EventsResponse,
27 ) -> StorageError {
28 match restore_count_read_failure(response) {
29 Err(error) => error,
30 Ok(other) => self.unexpected(op, other),
31 }
32 }
33
34 pub(super) fn unexpected(&self, op: &'static str, response: EventsResponse) -> StorageError {
35 match response {
36 EventsResponse::Error {
37 message,
38 retryable,
39 writer_task_failure,
40 sqlite_write_failure,
41 read_failure,
42 } => {
43 if read_failure.is_some() {
44 return events_count_read_budget::malformed_read_failure(
45 "non-count response carries read failure evidence",
46 );
47 }
48 let source = if retryable {
51 StorageError::Pool {
52 operation: op.into(),
53 message,
54 }
55 } else {
56 StorageError::InvalidInput {
57 capability: khive_storage::StorageCapability::Events,
58 operation: op.into(),
59 message,
60 }
61 };
62 let source = match sqlite_write_failure {
63 Some(failure) => source.with_sqlite_write_failure(failure),
64 None => source,
65 };
66 match writer_task_failure {
67 Some(WireWriterTaskFailure::RequestFailed { request_state }) => {
68 StorageError::WriterTaskRequestFailed {
69 request_state: request_state.into(),
70 source: Box::new(source),
71 }
72 }
73 Some(WireWriterTaskFailure::TaskTerminated {
74 request_state,
75 sqlite_full_codes,
76 }) => {
77 let terminal = StorageError::WriterTaskTerminated {
78 request_state: request_state.into(),
79 sqlite_full_codes,
80 };
81 match sqlite_write_failure {
82 Some(failure) => terminal.with_sqlite_write_failure(failure),
83 None => terminal,
84 }
85 }
86 None => source,
87 }
88 }
89 other => StorageError::Serialization {
90 capability: khive_storage::StorageCapability::Events,
91 message: format!("events daemon returned mismatched response for {op}: {other:?}"),
92 },
93 }
94 }
95}
96
97#[cfg(unix)]
98#[async_trait]
99impl EventStore for ForwardingEventStore {
100 async fn append_event(&self, event: Event) -> StorageResult<()> {
101 self.client.enqueue(&self.namespace, vec![event]);
102 Ok(())
103 }
104
105 async fn append_events(&self, events: Vec<Event>) -> StorageResult<BatchWriteSummary> {
106 let attempted = events.len() as u64;
107 self.client.enqueue(&self.namespace, events);
108 Ok(BatchWriteSummary {
111 attempted,
112 affected: attempted,
113 ..BatchWriteSummary::default()
114 })
115 }
116
117 async fn get_event(&self, id: Uuid) -> StorageResult<Option<Event>> {
118 let request = EventsRequest::GetEvent {
119 protocol_version: EVENTS_PROTOCOL_VERSION,
120 namespace: self.namespace.clone(),
121 id,
122 };
123 match self.client.round_trip(&request).await? {
124 EventsResponse::Event { event } => Ok(event),
125 other => Err(self.unexpected("get_event", other)),
126 }
127 }
128
129 async fn query_events(
130 &self,
131 filter: EventFilter,
132 page: PageRequest,
133 ) -> StorageResult<Page<Event>> {
134 let request = EventsRequest::QueryEvents {
135 protocol_version: EVENTS_PROTOCOL_VERSION,
136 namespace: self.namespace.clone(),
137 filter,
138 page,
139 };
140 match self.client.round_trip(&request).await? {
141 EventsResponse::Pageful { page } => Ok(page),
142 other => Err(self.unexpected("query_events", other)),
143 }
144 }
145
146 async fn count_events(&self, filter: EventFilter) -> StorageResult<u64> {
147 let budget = CountReadBudget::capture();
148 let request = EventsRequest::CountEvents {
149 protocol_version: EVENTS_PROTOCOL_VERSION,
150 namespace: self.namespace.clone(),
151 operator_read_budget_secs: Some(budget.operator_read_budget_secs),
152 filter,
153 };
154 match self.client.round_trip_count(&request, &budget).await? {
155 EventsResponse::Count { count } => Ok(count),
156 other => Err(self.unexpected_count("count_events", other)),
157 }
158 }
159
160 async fn count_events_grouped(
161 &self,
162 filter: EventFilter,
163 group_by: EventGroupBy,
164 ) -> StorageResult<BTreeMap<String, u64>> {
165 let budget = CountReadBudget::capture();
166 let request = EventsRequest::CountEventsGrouped {
167 protocol_version: EVENTS_PROTOCOL_VERSION,
168 namespace: self.namespace.clone(),
169 operator_read_budget_secs: Some(budget.operator_read_budget_secs),
170 filter,
171 group_by,
172 };
173 match self.client.round_trip_count(&request, &budget).await? {
174 EventsResponse::GroupedCount { counts } => Ok(counts),
175 other => Err(self.unexpected_count("count_events_grouped", other)),
176 }
177 }
178
179 async fn query_event_page(&self, query: EventPageQuery) -> StorageResult<EventPageWindow> {
180 crate::event_page::validate_page_query(&query)?;
181 let request = EventsRequest::QueryEventPage {
182 protocol_version: EVENTS_PROTOCOL_VERSION,
183 namespace: self.namespace.clone(),
184 query: query.clone(),
185 };
186 match self.client.round_trip(&request).await? {
187 EventsResponse::EventPageWindow { window } => {
188 crate::event_page::validate_window(&query, Some(&self.namespace), &window)?;
189 Ok(window)
190 }
191 error @ EventsResponse::Error { .. } => Err(self.unexpected("query_event_page", error)),
192 _ => Err(crate::event_page::page_error(
193 "events page response kind mismatch",
194 )),
195 }
196 }
197
198 fn preflight_event(&self, event: &Event) -> StorageResult<()> {
199 self.client.preflight_store.preflight_event(event)
201 }
202
203 async fn append_events_idempotent(
204 &self,
205 events: Vec<Event>,
206 ) -> StorageResult<IdempotentEventBatchResult> {
207 let request = EventsRequest::AppendEventsIdempotent {
208 protocol_version: EVENTS_PROTOCOL_VERSION,
209 namespace: self.namespace.clone(),
210 events,
211 };
212 match self.client.round_trip(&request).await? {
213 EventsResponse::Idempotent { result } => Ok(result),
214 other => Err(self.unexpected("append_events_idempotent", other)),
215 }
216 }
217
218 fn supports_idempotent_audit_batch(&self) -> bool {
219 true
220 }
221}
222
223pub struct SplitEventStore {
240 pub(super) legacy: Arc<dyn EventStore>,
241 pub(super) lane: Arc<dyn EventStore>,
242}
243
244impl std::fmt::Debug for SplitEventStore {
245 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
246 f.debug_struct("SplitEventStore").finish_non_exhaustive()
247 }
248}
249
250impl SplitEventStore {
251 pub const MAX_MERGED_WINDOW_ROWS: u64 = 100_000;
259
260 pub fn new(legacy: Arc<dyn EventStore>, lane: Arc<dyn EventStore>) -> Self {
261 Self { legacy, lane }
262 }
263}
264
265#[async_trait]
266impl EventStore for SplitEventStore {
267 async fn append_event(&self, event: Event) -> StorageResult<()> {
268 self.legacy.append_event(event).await
269 }
270
271 async fn append_events(&self, events: Vec<Event>) -> StorageResult<BatchWriteSummary> {
272 self.legacy.append_events(events).await
273 }
274
275 async fn get_event(&self, id: Uuid) -> StorageResult<Option<Event>> {
276 if let Some(event) = self.legacy.get_event(id).await? {
279 return Ok(Some(event));
280 }
281 self.lane.get_event(id).await
282 }
283
284 async fn query_events(
285 &self,
286 filter: EventFilter,
287 page: PageRequest,
288 ) -> StorageResult<Page<Event>> {
289 let window = page.offset.saturating_add(u64::from(page.limit));
303 if window > Self::MAX_MERGED_WINDOW_ROWS {
304 return Err(StorageError::InvalidInput {
305 capability: khive_storage::StorageCapability::Events,
306 operation: "query_events".into(),
307 message: format!(
308 "offset+limit ({window}) exceeds the merged event plane's window bound \
309 of {}; page deep windows with a `before` cursor at offset 0, or narrow \
310 the filter",
311 Self::MAX_MERGED_WINDOW_ROWS
312 ),
313 });
314 }
315 let prefix = PageRequest {
316 offset: 0,
317 limit: window.min(u64::from(u32::MAX)) as u32,
318 };
319 let legacy = self
320 .legacy
321 .query_events(filter.clone(), prefix.clone())
322 .await?;
323 let lane = self.lane.query_events(filter, prefix).await?;
324 let total = match (legacy.total, lane.total) {
325 (Some(a), Some(b)) => Some(a + b),
326 _ => None,
327 };
328 let mut items = legacy.items;
329 items.extend(lane.items);
330 items.sort_by(|a, b| {
331 b.created_at
332 .cmp(&a.created_at)
333 .then_with(|| b.id.cmp(&a.id))
334 });
335 let items = items
336 .into_iter()
337 .skip(page.offset as usize)
338 .take(page.limit as usize)
339 .collect();
340 Ok(Page { items, total })
341 }
342
343 async fn count_events(&self, filter: EventFilter) -> StorageResult<u64> {
344 let budget = CountReadBudget::capture();
345 budget
346 .scope(async {
347 let legacy = self
348 .legacy
349 .count_events(filter.clone())
350 .await
351 .map_err(|error| budget.attribute(error))?;
352 let lane = self.lane.count_events(filter).await?;
353 Ok(legacy + lane)
354 })
355 .await
356 }
357
358 async fn count_events_grouped(
359 &self,
360 filter: EventFilter,
361 group_by: EventGroupBy,
362 ) -> StorageResult<BTreeMap<String, u64>> {
363 let budget = CountReadBudget::capture();
364 budget
365 .scope(async {
366 let mut counts = self
367 .legacy
368 .count_events_grouped(filter.clone(), group_by)
369 .await
370 .map_err(|error| budget.attribute(error))?;
371 for (key, count) in self.lane.count_events_grouped(filter, group_by).await? {
372 let total = counts.entry(key).or_default();
373 *total =
374 total
375 .checked_add(count)
376 .ok_or_else(|| StorageError::InvalidInput {
377 capability: khive_storage::StorageCapability::Events,
378 operation: "count_events_grouped".into(),
379 message: "grouped event count overflow".into(),
380 })?;
381 }
382 Ok(counts)
383 })
384 .await
385 }
386
387 async fn query_event_page(&self, query: EventPageQuery) -> StorageResult<EventPageWindow> {
388 crate::event_page::split_page(self.legacy.as_ref(), self.lane.as_ref(), query).await
389 }
390
391 fn preflight_event(&self, event: &Event) -> StorageResult<()> {
392 self.lane.preflight_event(event)
395 }
396
397 async fn append_events_idempotent(
398 &self,
399 events: Vec<Event>,
400 ) -> StorageResult<IdempotentEventBatchResult> {
401 if events.is_empty() {
411 return self.lane.append_events_idempotent(events).await;
412 }
413 let ids: Vec<Uuid> = events.iter().map(|event| event.id).collect();
414 let probe_limit = u32::try_from(ids.len()).map_err(|_| StorageError::InvalidInput {
415 capability: khive_storage::StorageCapability::Events,
416 operation: "append_events_idempotent".into(),
417 message: format!(
418 "batch of {} rows exceeds the legacy probe window",
419 ids.len()
420 ),
421 })?;
422 let existing = self
423 .legacy
424 .query_events(
425 EventFilter {
426 ids,
427 ..EventFilter::default()
428 },
429 PageRequest {
430 offset: 0,
431 limit: probe_limit,
432 },
433 )
434 .await?;
435 let legacy_resident: std::collections::HashSet<Uuid> =
436 existing.items.iter().map(|event| event.id).collect();
437 if legacy_resident.is_empty() {
438 return self.lane.append_events_idempotent(events).await;
439 }
440 let mut legacy_rows = Vec::new();
441 let mut lane_rows = Vec::new();
442 let mut routed_to_legacy = Vec::with_capacity(events.len());
443 for event in events {
444 if legacy_resident.contains(&event.id) {
445 routed_to_legacy.push(true);
446 legacy_rows.push(event);
447 } else {
448 routed_to_legacy.push(false);
449 lane_rows.push(event);
450 }
451 }
452 let legacy_expected = legacy_rows.len();
453 let lane_expected = lane_rows.len();
454 let legacy_result = self.legacy.append_events_idempotent(legacy_rows).await?;
455 let lane_result = if lane_expected == 0 {
456 IdempotentEventBatchResult { rows: Vec::new() }
457 } else {
458 self.lane.append_events_idempotent(lane_rows).await?
459 };
460 if legacy_result.rows.len() != legacy_expected || lane_result.rows.len() != lane_expected {
461 return Err(StorageError::Driver {
462 capability: khive_storage::StorageCapability::Events,
463 operation: "append_events_idempotent".into(),
464 source: format!(
465 "idempotent sub-batch result length mismatch: legacy {}/{}, lane {}/{}",
466 legacy_result.rows.len(),
467 legacy_expected,
468 lane_result.rows.len(),
469 lane_expected
470 )
471 .into(),
472 });
473 }
474 let mut legacy_iter = legacy_result.rows.into_iter();
475 let mut lane_iter = lane_result.rows.into_iter();
476 let rows = routed_to_legacy
477 .into_iter()
478 .map(|to_legacy| {
479 if to_legacy {
480 legacy_iter.next().expect("length checked above")
481 } else {
482 lane_iter.next().expect("length checked above")
483 }
484 })
485 .collect();
486 Ok(IdempotentEventBatchResult { rows })
487 }
488
489 fn supports_idempotent_audit_batch(&self) -> bool {
490 self.lane.supports_idempotent_audit_batch() && self.legacy.supports_idempotent_audit_batch()
494 }
495}