1use std::error::Error;
4use std::fmt;
5
6use crate::{
7 BoxFuture, Checkpoint, ChunkCounts, ExecutionContext, FailureCategory, FaultProgress,
8 JobExecutionId, SkipCounts, StepExecutionId, StopToken,
9};
10
11#[derive(Clone, Copy, Debug)]
13pub struct ReadContext<'a> {
14 stop: &'a StopToken,
15}
16
17impl<'a> ReadContext<'a> {
18 #[must_use]
20 pub const fn new(stop: &'a StopToken) -> Self {
21 Self { stop }
22 }
23
24 #[must_use]
26 pub const fn stop_token(self) -> &'a StopToken {
27 self.stop
28 }
29}
30
31#[derive(Clone, Debug, Eq, PartialEq)]
33#[non_exhaustive]
34pub enum ReadOutcome<I> {
35 Item(I),
37 EndOfInput,
39 Stopped,
41}
42
43pub trait ItemReader<I>: Send {
45 fn read<'a>(
47 &'a mut self,
48 context: ReadContext<'a>,
49 ) -> BoxFuture<'a, Result<ReadOutcome<I>, ReaderError>>;
50}
51
52#[derive(Clone, Copy, Debug)]
54pub struct ProcessContext<'a> {
55 stop: &'a StopToken,
56}
57
58impl<'a> ProcessContext<'a> {
59 #[must_use]
61 pub const fn new(stop: &'a StopToken) -> Self {
62 Self { stop }
63 }
64
65 #[must_use]
67 pub const fn stop_token(self) -> &'a StopToken {
68 self.stop
69 }
70}
71
72#[derive(Clone, Debug, Eq, PartialEq)]
74#[non_exhaustive]
75pub enum ProcessOutcome<O> {
76 Item(O),
78 Filtered,
80 Stopped,
82}
83
84pub trait ItemProcessor<I, O>: Send + Sync {
86 fn process<'a>(
88 &'a self,
89 item: &'a I,
90 context: ProcessContext<'a>,
91 ) -> BoxFuture<'a, Result<ProcessOutcome<O>, ProcessorError>>;
92}
93
94#[derive(Clone, Copy, Eq, PartialEq)]
96pub struct BusinessValue<'a>(BusinessValueInner<'a>);
97
98#[derive(Clone, Copy, Eq, PartialEq)]
99enum BusinessValueInner<'a> {
100 Text(&'a str),
101 Bytes(&'a [u8]),
102 I64(i64),
103 Bool(bool),
104 Null,
105}
106
107impl<'a> BusinessValue<'a> {
108 #[must_use]
110 pub const fn text(value: &'a str) -> Self {
111 Self(BusinessValueInner::Text(value))
112 }
113
114 #[must_use]
116 pub const fn bytes(value: &'a [u8]) -> Self {
117 Self(BusinessValueInner::Bytes(value))
118 }
119
120 #[must_use]
122 pub const fn i64(value: i64) -> Self {
123 Self(BusinessValueInner::I64(value))
124 }
125
126 #[must_use]
128 pub const fn boolean(value: bool) -> Self {
129 Self(BusinessValueInner::Bool(value))
130 }
131
132 #[must_use]
134 pub const fn null() -> Self {
135 Self(BusinessValueInner::Null)
136 }
137
138 #[must_use]
140 pub const fn kind(self) -> BusinessValueKind {
141 match self.0 {
142 BusinessValueInner::Text(_) => BusinessValueKind::Text,
143 BusinessValueInner::Bytes(_) => BusinessValueKind::Bytes,
144 BusinessValueInner::I64(_) => BusinessValueKind::I64,
145 BusinessValueInner::Bool(_) => BusinessValueKind::Bool,
146 BusinessValueInner::Null => BusinessValueKind::Null,
147 }
148 }
149
150 #[must_use]
152 pub const fn as_text(self) -> Option<&'a str> {
153 match self.0 {
154 BusinessValueInner::Text(value) => Some(value),
155 _ => None,
156 }
157 }
158
159 #[must_use]
161 pub const fn as_bytes(self) -> Option<&'a [u8]> {
162 match self.0 {
163 BusinessValueInner::Bytes(value) => Some(value),
164 _ => None,
165 }
166 }
167
168 #[must_use]
170 pub const fn as_i64(self) -> Option<i64> {
171 match self.0 {
172 BusinessValueInner::I64(value) => Some(value),
173 _ => None,
174 }
175 }
176
177 #[must_use]
179 pub const fn as_bool(self) -> Option<bool> {
180 match self.0 {
181 BusinessValueInner::Bool(value) => Some(value),
182 _ => None,
183 }
184 }
185}
186
187impl fmt::Debug for BusinessValue<'_> {
188 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
189 formatter
190 .debug_struct("BusinessValue")
191 .field("kind", &self.kind())
192 .field("value", &"<redacted>")
193 .finish()
194 }
195}
196
197#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
199#[non_exhaustive]
200pub enum BusinessValueKind {
201 Text,
203 Bytes,
205 I64,
207 Bool,
209 Null,
211}
212
213pub struct BusinessStatement<'a> {
215 text: &'a str,
216 values: &'a [BusinessValue<'a>],
217}
218
219impl<'a> BusinessStatement<'a> {
220 #[must_use]
225 pub const fn new(text: &'a str, values: &'a [BusinessValue<'a>]) -> Self {
226 Self { text, values }
227 }
228
229 #[must_use]
231 pub const fn text(&self) -> &'a str {
232 self.text
233 }
234
235 #[must_use]
237 pub const fn values(&self) -> &'a [BusinessValue<'a>] {
238 self.values
239 }
240}
241
242impl fmt::Debug for BusinessStatement<'_> {
243 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
244 formatter
245 .debug_struct("BusinessStatement")
246 .field("text", &"<redacted>")
247 .field("value_count", &self.values.len())
248 .finish()
249 }
250}
251
252#[derive(Clone, Copy, Debug, Eq, PartialEq)]
254pub struct BusinessWriteResult {
255 rows_affected: u64,
256}
257
258impl BusinessWriteResult {
259 #[must_use]
261 pub const fn new(rows_affected: u64) -> Self {
262 Self { rows_affected }
263 }
264
265 #[must_use]
267 pub const fn rows_affected(self) -> u64 {
268 self.rows_affected
269 }
270}
271
272pub trait BusinessTransaction: Send {
278 fn execute<'a>(
280 &'a mut self,
281 statement: BusinessStatement<'a>,
282 ) -> BoxFuture<'a, Result<BusinessWriteResult, BusinessTransactionError>>;
283}
284
285#[derive(Clone, Debug, Eq, PartialEq)]
290pub struct ChunkCommitReceipt {
291 checkpoint: Checkpoint,
292 execution_context: ExecutionContext,
293}
294
295impl ChunkCommitReceipt {
296 #[must_use]
298 pub const fn new(checkpoint: Checkpoint, execution_context: ExecutionContext) -> Self {
299 Self {
300 checkpoint,
301 execution_context,
302 }
303 }
304
305 #[must_use]
307 pub const fn checkpoint(&self) -> &Checkpoint {
308 &self.checkpoint
309 }
310
311 #[must_use]
313 pub const fn execution_context(&self) -> &ExecutionContext {
314 &self.execution_context
315 }
316}
317
318#[derive(Clone, Copy, Debug, Eq, PartialEq)]
320#[non_exhaustive]
321pub enum ChunkTransactionError {
322 NotCommitted,
324 CommitOutcomeUnknown,
326}
327
328impl fmt::Display for ChunkTransactionError {
329 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
330 formatter.write_str(match self {
331 Self::NotCommitted => "chunk transaction did not commit",
332 Self::CommitOutcomeUnknown => "chunk transaction commit outcome is unknown",
333 })
334 }
335}
336
337impl Error for ChunkTransactionError {}
338
339#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
345pub struct ChunkFaultProgress {
346 skips: SkipCounts,
347 no_rollbacks: u64,
348}
349
350impl ChunkFaultProgress {
351 pub const NONE: Self = Self {
353 skips: SkipCounts::ZERO,
354 no_rollbacks: 0,
355 };
356
357 #[must_use]
359 pub const fn new(skips: SkipCounts, no_rollbacks: u64) -> Self {
360 Self {
361 skips,
362 no_rollbacks,
363 }
364 }
365
366 #[must_use]
368 pub const fn skips(self) -> SkipCounts {
369 self.skips
370 }
371
372 #[must_use]
374 pub const fn no_rollbacks(self) -> u64 {
375 self.no_rollbacks
376 }
377}
378
379pub trait ChunkTransaction: Send {
385 fn business_transaction(&mut self) -> Option<&mut dyn BusinessTransaction>;
388
389 fn commit(
397 &mut self,
398 counts: ChunkCounts,
399 fault: ChunkFaultProgress,
400 ) -> BoxFuture<'_, Result<ChunkCommitReceipt, ChunkTransactionError>>;
401
402 fn rollback(&mut self) -> BoxFuture<'_, Result<(), ChunkTransactionError>>;
404}
405
406#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
412pub struct ChunkTransactionContext {
413 job_execution_id: JobExecutionId,
414 step_execution_id: StepExecutionId,
415}
416
417impl ChunkTransactionContext {
418 #[must_use]
420 pub const fn new(job_execution_id: JobExecutionId, step_execution_id: StepExecutionId) -> Self {
421 Self {
422 job_execution_id,
423 step_execution_id,
424 }
425 }
426
427 #[must_use]
429 pub const fn job_execution_id(self) -> JobExecutionId {
430 self.job_execution_id
431 }
432
433 #[must_use]
435 pub const fn step_execution_id(self) -> StepExecutionId {
436 self.step_execution_id
437 }
438}
439
440#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
446pub struct InheritedStepProgress {
447 read_ordinal: u64,
448 checkpoint_digest: [u8; 32],
449 fault: FaultProgress,
450}
451
452impl InheritedStepProgress {
453 pub const NONE: Self = Self {
455 read_ordinal: 0,
456 checkpoint_digest: [0; 32],
457 fault: FaultProgress::NONE,
458 };
459
460 #[must_use]
462 pub const fn new(read_ordinal: u64, checkpoint_digest: [u8; 32], fault: FaultProgress) -> Self {
463 Self {
464 read_ordinal,
465 checkpoint_digest,
466 fault,
467 }
468 }
469
470 #[must_use]
472 pub const fn read_ordinal(self) -> u64 {
473 self.read_ordinal
474 }
475
476 #[must_use]
481 pub const fn checkpoint_digest(self) -> [u8; 32] {
482 self.checkpoint_digest
483 }
484
485 #[must_use]
487 pub const fn fault(self) -> FaultProgress {
488 self.fault
489 }
490}
491
492pub trait ChunkTransactionManager: Send + Sync {
494 fn begin(&self)
496 -> BoxFuture<'_, Result<Box<dyn ChunkTransaction + '_>, ChunkTransactionError>>;
497
498 fn inherited_progress(
504 &self,
505 _context: ChunkTransactionContext,
506 ) -> BoxFuture<'_, Result<InheritedStepProgress, ChunkTransactionError>> {
507 Box::pin(std::future::ready(Ok(InheritedStepProgress::NONE)))
508 }
509
510 fn begin_for(
516 &self,
517 _context: ChunkTransactionContext,
518 ) -> BoxFuture<'_, Result<Box<dyn ChunkTransaction + '_>, ChunkTransactionError>> {
519 self.begin()
520 }
521}
522
523#[derive(Clone, Copy, Debug, Eq, PartialEq)]
525#[non_exhaustive]
526pub enum BusinessTransactionError {
527 Infrastructure,
529 Rejected,
531 Cancelled,
533}
534
535impl fmt::Display for BusinessTransactionError {
536 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
537 formatter.write_str(match self {
538 Self::Infrastructure => "business transaction infrastructure failed",
539 Self::Rejected => "business transaction statement was rejected",
540 Self::Cancelled => "business transaction operation was cancelled",
541 })
542 }
543}
544
545impl Error for BusinessTransactionError {}
546
547pub struct WriteContext<'a> {
549 stop: &'a StopToken,
550 transaction: Option<&'a mut dyn BusinessTransaction>,
551}
552
553impl<'a> WriteContext<'a> {
554 #[must_use]
556 pub const fn non_transactional(stop: &'a StopToken) -> Self {
557 Self {
558 stop,
559 transaction: None,
560 }
561 }
562
563 #[must_use]
565 pub fn enlisted(stop: &'a StopToken, transaction: &'a mut dyn BusinessTransaction) -> Self {
566 Self {
567 stop,
568 transaction: Some(transaction),
569 }
570 }
571
572 #[must_use]
574 pub const fn stop_token(&self) -> &'a StopToken {
575 self.stop
576 }
577
578 #[must_use]
580 pub fn transaction(&mut self) -> Option<&mut (dyn BusinessTransaction + 'a)> {
581 self.transaction.as_deref_mut()
582 }
583
584 #[must_use]
586 pub const fn is_enlisted(&self) -> bool {
587 self.transaction.is_some()
588 }
589}
590
591impl fmt::Debug for WriteContext<'_> {
592 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
593 formatter
594 .debug_struct("WriteContext")
595 .field("stop_requested", &self.stop.is_stop_requested())
596 .field("enlisted", &self.transaction.is_some())
597 .finish()
598 }
599}
600
601#[derive(Clone, Copy, Debug, Eq, PartialEq)]
603#[non_exhaustive]
604pub enum WriteOutcome {
605 Written,
607 Stopped,
609}
610
611pub trait ItemWriter<I>: Send + Sync {
613 fn write<'a>(
619 &'a self,
620 items: &'a [I],
621 context: WriteContext<'a>,
622 ) -> BoxFuture<'a, Result<WriteOutcome, WriterError>>;
623}
624
625#[derive(Clone, Copy, Debug)]
627pub struct ChunkCompletionContext<'a> {
628 checkpoint: &'a Checkpoint,
629 execution_context: &'a ExecutionContext,
630 counts: ChunkCounts,
631 stop: &'a StopToken,
632}
633
634impl<'a> ChunkCompletionContext<'a> {
635 #[must_use]
637 pub const fn new(
638 checkpoint: &'a Checkpoint,
639 execution_context: &'a ExecutionContext,
640 counts: ChunkCounts,
641 stop: &'a StopToken,
642 ) -> Self {
643 Self {
644 checkpoint,
645 execution_context,
646 counts,
647 stop,
648 }
649 }
650
651 #[must_use]
653 pub const fn checkpoint(self) -> &'a Checkpoint {
654 self.checkpoint
655 }
656
657 #[must_use]
659 pub const fn execution_context(self) -> &'a ExecutionContext {
660 self.execution_context
661 }
662
663 #[must_use]
665 pub const fn counts(self) -> ChunkCounts {
666 self.counts
667 }
668
669 #[must_use]
671 pub const fn stop_token(self) -> &'a StopToken {
672 self.stop
673 }
674}
675
676#[derive(Clone, Copy, Debug, Eq, PartialEq)]
678#[non_exhaustive]
679pub enum ChunkCompletionOutcome {
680 Acknowledged,
682 StoppedAfterCommit,
684}
685
686pub trait ChunkCompletion: Send + Sync {
688 fn after_commit<'a>(
690 &'a self,
691 context: ChunkCompletionContext<'a>,
692 ) -> BoxFuture<'a, Result<ChunkCompletionOutcome, ChunkCompletionError>>;
693}
694
695macro_rules! component_error {
696 (
697 $name:ident,
698 $message:literal
699 $(, $field:ident : $field_type:ty = $field_default:expr, $field_docs:literal)* $(,)?
700 ) => {
701 #[doc = $message]
702 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
707 pub struct $name {
708 category: FailureCategory,
709 $(
710 #[doc = $field_docs]
711 $field: $field_type,
712 )*
713 }
714
715 impl $name {
716 #[must_use]
719 pub const fn new() -> Self {
720 Self {
721 category: FailureCategory::UserComponent,
722 $($field: $field_default,)*
723 }
724 }
725
726 #[must_use]
731 pub const fn with_category(category: FailureCategory) -> Self {
732 Self {
733 category,
734 $($field: $field_default,)*
735 }
736 }
737
738 #[must_use]
741 pub fn from_error(error: impl Error + Send + Sync + 'static) -> Self {
742 drop(error);
743 Self::new()
744 }
745
746 #[must_use]
748 pub const fn category(self) -> FailureCategory {
749 self.category
750 }
751 }
752
753 impl Default for $name {
754 fn default() -> Self {
755 Self::new()
756 }
757 }
758
759 impl fmt::Display for $name {
760 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
761 formatter.write_str($message)
762 }
763 }
764
765 impl Error for $name {}
766 };
767}
768
769component_error!(
770 ReaderError,
771 "item reader failed",
772 checkpoint_advanced: bool = false,
773 "Whether the reader proved its checkpoint moved past one failed input.",
774);
775component_error!(ProcessorError, "item processor failed");
776component_error!(
777 WriterError,
778 "item writer failed",
779 rolled_back_output: Option<usize> = None,
780 "The located, known-rolled-back output index, when the writer supplied one.",
781);
782component_error!(ChunkCompletionError, "chunk completion callback failed");
783
784impl ReaderError {
785 #[must_use]
791 pub const fn with_checkpoint_advanced(mut self, advanced: bool) -> Self {
792 self.checkpoint_advanced = advanced;
793 self
794 }
795
796 #[must_use]
798 pub const fn has_checkpoint_advanced(self) -> bool {
799 self.checkpoint_advanced
800 }
801}
802
803impl WriterError {
804 #[must_use]
810 pub const fn with_rolled_back_output(mut self, index: usize) -> Self {
811 self.rolled_back_output = Some(index);
812 self
813 }
814
815 #[must_use]
817 pub const fn rolled_back_output(self) -> Option<usize> {
818 self.rolled_back_output
819 }
820}