somatize_data/frame.rs
1//! A batch of columns, which is what a source answers with.
2
3use arrow_array::RecordBatch;
4use arrow_ipc::reader::StreamReader;
5use arrow_ipc::writer::StreamWriter;
6use arrow_schema::SchemaRef;
7use arrow_select::concat::concat_batches;
8use somatize_core::Value;
9use std::fmt;
10
11/// Rows and columns, together with what each column is called and holds.
12///
13/// A type of ours rather than a `RecordBatch` in an `Opaque` for two reasons
14/// that are not style: a codec is an `impl` of somebody else's trait and the
15/// orphan rule wants one of the two types to be ours, and this is the word the
16/// design is written in — *the difference between training and deploying is how
17/// many rows the frame brings*.
18///
19/// It is not a tensor and does not want to be. Numbers and fixed-size lists of
20/// them are contiguous Arrow buffers, so converting is a reshape and not a copy;
21/// text is not a tensor until something tokenizes it. **The conversion is a
22/// node**, and the same node whether the frame came from a file or a topic.
23#[derive(Debug, Clone)]
24pub struct Frame(RecordBatch);
25
26impl Frame {
27 /// What gets written beside the bytes. Named after the **format** and not
28 /// the language: what is on disk is an Arrow IPC stream, and whoever reads
29 /// it back may be a `polars` on the other side of the wall.
30 pub const KIND: &'static str = "arrow.RecordBatch";
31
32 /// This batch, as a frame.
33 pub fn new(batch: RecordBatch) -> Self {
34 Self(batch)
35 }
36
37 /// How many rows it brings. The only number a caller usually wants: it is
38 /// what says whether a span was short because the dataset ended.
39 pub fn rows(&self) -> usize {
40 self.0.num_rows()
41 }
42
43 /// What the columns are called and what they hold — **without reading a
44 /// value**, which is the half of a virtual table worth having.
45 pub fn schema(&self) -> &SchemaRef {
46 self.0.schema_ref()
47 }
48
49 /// The batch itself, for whoever brought their own engine.
50 pub fn batch(&self) -> &RecordBatch {
51 &self.0
52 }
53
54 /// As a value, which is how it crosses an edge. `Opaque` and not a variant
55 /// of its own: the core has no dependencies and is not going to learn what a
56 /// column is.
57 pub fn value(self) -> Value {
58 Value::opaque(self)
59 }
60
61 /// The frame this value carries, if it carries one.
62 pub fn of(value: &Value) -> Option<&Self> {
63 value.downcast::<Self>()
64 }
65
66 /// What it weighs in bytes: Arrow IPC, buffers and all. No encoding pass and
67 /// no per-value work, which is the reason Arrow is the type that crosses an
68 /// edge rather than something converted to at the edges.
69 pub fn written(&self) -> Result<Vec<u8>, FrameError> {
70 let mut out = Vec::new();
71 let mut writer = StreamWriter::try_new(&mut out, self.schema())
72 .map_err(|e| FrameError(format!("a frame could not be written down: {e}")))?;
73 writer
74 .write(&self.0)
75 .map_err(|e| FrameError(format!("a frame could not be written down: {e}")))?;
76 writer
77 .finish()
78 .map_err(|e| FrameError(format!("a frame could not be written down: {e}")))?;
79 drop(writer);
80 Ok(out)
81 }
82
83 /// And back.
84 ///
85 /// One frame, however many batches the stream holds: what was written down
86 /// was a frame, and a frame is what comes back.
87 pub fn read(bytes: &[u8]) -> Result<Self, FrameError> {
88 let reader = StreamReader::try_new(bytes, None)
89 .map_err(|e| FrameError(format!("those bytes are not a frame: {e}")))?;
90 let schema = reader.schema();
91 let batches: Vec<RecordBatch> = reader
92 .collect::<Result<_, _>>()
93 .map_err(|e| FrameError(format!("those bytes are not a frame: {e}")))?;
94 let whole = concat_batches(&schema, batches.iter())
95 .map_err(|e| FrameError(format!("those rows would not join up: {e}")))?;
96 Ok(Self(whole))
97 }
98}
99
100/// Why that could not be written down, or read back.
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct FrameError(String);
103
104impl FrameError {
105 /// The message.
106 pub fn message(&self) -> &str {
107 &self.0
108 }
109}
110
111impl fmt::Display for FrameError {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 f.write_str(&self.0)
114 }
115}
116
117impl std::error::Error for FrameError {}