1use std::num::NonZeroUsize;
18use std::time::{Duration, UNIX_EPOCH};
19use arrow_array::{
21 Array, BinaryArray, BooleanArray, Float32Array, Float64Array, Int32Array, Int64Array, ListArray, StringArray, StructArray,
22 TimestampNanosecondArray, UInt32Array, UInt64Array,
23};
24use super::DecodedBatch;
26use crate::schema::{
27 Cardinality,
28 graph::{Field, Kind, Schema},
29};
30use crate::storage::{ObjectKey, ObjectValue, PayloadEncoding, RecordInput, StreamInput};
31use crate::{Error, Identifier, Result};
32
33const MAX_DEPTH: usize = 64;
36
37pub fn encode_payloads(schema: &Schema, payload: &StructArray, encoding: PayloadEncoding, max_bytes: NonZeroUsize) -> Result<Vec<Vec<u8>>> {
52 let metadata = payload
53 .len()
54 .checked_mul(std::mem::size_of::<Vec<u8>>())
55 .and_then(|bytes| bytes.checked_add(std::mem::size_of::<Vec<Vec<u8>>>()))
56 .ok_or_else(exhausted)?;
57 let mut count = Counter { bytes: metadata, limit: max_bytes.get() };
58 if metadata > max_bytes.get() {
59 return Err(exhausted());
60 }
61 for row in 0..payload.len() {
62 encode_message(schema, schema.root, payload, row, encoding, 0, &mut count)?;
63 }
64 let mut rows = Vec::with_capacity(payload.len());
65 for row in 0..payload.len() {
66 let mut size = Counter { bytes: 0, limit: max_bytes.get() };
67 encode_message(schema, schema.root, payload, row, encoding, 0, &mut size)?;
68 let mut bytes = Vec::with_capacity(size.bytes);
69 encode_message(schema, schema.root, payload, row, encoding, 0, &mut bytes)?;
70 rows.push(bytes);
71 }
72 Ok(rows)
73}
74
75pub fn records(scope: Option<&Identifier>, batch: &DecodedBatch, schema: &Schema) -> Result<Vec<RecordInput>> {
76 let data = batch.data.as_record_batch();
77 let metadata = struct_column(data.column_by_name("_storage").ok_or_else(invalid)?.as_ref())?;
78 let payload = struct_column(data.column_by_name("payload").ok_or_else(invalid)?.as_ref())?;
79 (0..data.num_rows())
80 .map(|row| {
81 if metadata.is_null(row) {
82 return Err(invalid());
83 }
84 let authored_scope = optional_id(metadata, "scope", row)?;
85 if scope.is_some_and(|expected| authored_scope.as_ref().is_some_and(|actual| actual != expected)) {
86 return Err(invalid());
87 }
88 let source_time = metadata
89 .column_by_name("source_time")
90 .filter(|column| !column.is_null(row))
91 .map(|column| {
92 let nanos = typed::<TimestampNanosecondArray>(column.as_ref())?.value(row);
93 if nanos >= 0 {
94 UNIX_EPOCH.checked_add(Duration::from_nanos(nanos as u64))
95 } else {
96 UNIX_EPOCH.checked_sub(Duration::from_nanos(nanos.unsigned_abs()))
97 }
98 .ok_or_else(invalid)
99 })
100 .transpose()?;
101 let mut bytes = Vec::new();
102 encode_message(schema, schema.root, payload, row, batch.schema.payload_encoding, 0, &mut bytes)?;
103 Ok(RecordInput {
104 schema: batch.schema.clone(),
105 scope: scope.cloned().or(authored_scope),
106 item: required_id(metadata, "item", row)?,
107 source: optional_id(metadata, "source", row)?,
108 source_time,
109 payload: bytes,
110 })
111 })
112 .collect()
113}
114
115pub fn streams(scope: Option<&Identifier>, batch: &DecodedBatch, schema: &Schema) -> Result<Vec<StreamInput>> {
116 let records = records(scope, batch, schema)?;
117 let metadata = struct_column(batch.data.as_record_batch().column_by_name("_storage").ok_or_else(invalid)?.as_ref())?;
118 let sequence = typed::<UInt64Array>(metadata.column_by_name("sequence").ok_or_else(invalid)?.as_ref())?;
119 records
120 .into_iter()
121 .enumerate()
122 .map(|(row, record)| {
123 if sequence.is_null(row) {
124 return Err(invalid());
125 }
126 if optional_id(metadata, "stream", row)?.is_some_and(|stream| stream != batch.destination) {
127 return Err(invalid());
128 }
129 Ok(StreamInput { record, stream: batch.destination.clone(), sequence: sequence.value(row) })
130 })
131 .collect()
132}
133
134pub fn objects(batch: &DecodedBatch, schema: &Schema, max_key_bytes: NonZeroUsize) -> Result<Vec<ObjectValue>> {
135 let data = batch.data.as_record_batch();
136 let metadata = struct_column(data.column_by_name("_storage").ok_or_else(invalid)?.as_ref())?;
137 let items = typed::<StringArray>(metadata.column_by_name("item").ok_or_else(invalid)?.as_ref())?;
138 let payload = struct_column(data.column_by_name("payload").ok_or_else(invalid)?.as_ref())?;
139 (0..data.num_rows())
140 .map(|row| {
141 if metadata.is_null(row) || items.is_null(row) {
142 return Err(invalid());
143 }
144 let mut bytes = Vec::new();
145 encode_message(schema, schema.root, payload, row, batch.schema.payload_encoding, 0, &mut bytes)?;
146 Ok(ObjectValue { schema: batch.schema.clone(), item: ObjectKey::new(items.value(row), max_key_bytes)?, payload: bytes })
147 })
148 .collect()
149}
150
151trait Sink {
154 fn append(&mut self, value: &[u8]) -> Result<()>;
155}
156struct Counter {
157 bytes: usize,
158 limit: usize,
159}
160impl Sink for Counter {
161 fn append(&mut self, value: &[u8]) -> Result<()> {
162 self.bytes = self.bytes.checked_add(value.len()).filter(|bytes| *bytes <= self.limit).ok_or_else(exhausted)?;
163 Ok(())
164 }
165}
166impl Sink for Vec<u8> {
167 fn append(&mut self, value: &[u8]) -> Result<()> {
168 self.extend_from_slice(value);
169 Ok(())
170 }
171}
172fn scalar<T: serde::Serialize>(value: T, output: &mut impl Sink) -> Result<()> {
173 let mut bytes = [0u8; 16];
174 let encoded = postcard::to_slice(&value, &mut bytes).map_err(|_| invalid())?;
175 output.append(encoded)
176}
177fn encode_message(
178 schema: &Schema, definition: usize, values: &StructArray, row: usize, encoding: PayloadEncoding, depth: usize, output: &mut impl Sink,
179) -> Result<()> {
180 if depth > MAX_DEPTH || values.is_null(row) {
181 return Err(invalid());
182 }
183 let fields = schema.definitions.get(definition).ok_or_else(invalid)?;
184 if fields.iter().any(|field| field.cardinality == Cardinality::Variant) {
185 let mut selected = None;
186 for (index, field) in fields.iter().enumerate() {
187 if let Some(value) = values.column_by_name(&field.name)
188 && !value.is_null(row)
189 {
190 if selected.is_some() {
191 return Err(invalid());
192 }
193 selected = Some((index, field, value));
194 }
195 }
196 let (index, field, value) = selected.ok_or_else(invalid)?;
197 let discriminant = typed::<UInt32Array>(values.column_by_name("__variant").ok_or_else(invalid)?.as_ref())?;
198 if discriminant.is_null(row) || discriminant.value(row) as usize != index {
199 return Err(invalid());
200 }
201 if encoding == PayloadEncoding::PostcardV1 {
202 scalar(index as u32, output)?;
203 }
204 return encode_value(schema, field, value.as_ref(), row, encoding, depth + 1, output);
205 }
206 for field in fields {
207 let value = values.column_by_name(&field.name).ok_or_else(invalid)?;
208 match field.cardinality {
209 Cardinality::Optional => {
210 if encoding == PayloadEncoding::PostcardV1 {
211 scalar(!value.is_null(row), output)?;
212 }
213 if value.is_null(row) {
214 continue;
215 }
216 }
217 Cardinality::Repeated => {
218 let lists = typed::<ListArray>(value.as_ref())?;
219 if lists.is_null(row) {
220 return Err(invalid());
221 }
222 let start = lists.value_offsets()[row] as usize;
223 let end = lists.value_offsets()[row + 1] as usize;
224 if encoding == PayloadEncoding::PostcardV1 {
225 scalar((end - start) as u64, output)?;
226 }
227 for index in start..end {
228 encode_value(schema, field, lists.values().as_ref(), index, encoding, depth + 1, output)?;
229 }
230 continue;
231 }
232 Cardinality::Required | Cardinality::Variant => {}
233 }
234 encode_value(schema, field, value.as_ref(), row, encoding, depth + 1, output)?;
235 }
236 Ok(())
237}
238fn encode_value(
239 schema: &Schema, field: &Field, array: &dyn Array, row: usize, encoding: PayloadEncoding, depth: usize, output: &mut impl Sink,
240) -> Result<()> {
241 if array.is_null(row) {
242 return Err(invalid());
243 }
244 let protobuf = encoding == PayloadEncoding::ProtobufV1;
245 macro_rules! number {
246 ($array:ty, $wire:expr, $encode:expr) => {{
247 let value = typed::<$array>(array)?.value(row);
248 if protobuf {
249 varint((u64::from(field.tag) << 3) | $wire, output)?;
250 ($encode)(value, output)?;
251 } else {
252 scalar(value, output)?;
253 }
254 }};
255 }
256 match field.kind {
257 Kind::Bool => number!(BooleanArray, 0, |value: bool, out: &mut _| varint(u64::from(value), out)),
258 Kind::U32 => number!(UInt32Array, 0, |value: u32, out: &mut _| varint(u64::from(value), out)),
259 Kind::U64 => number!(UInt64Array, 0, |value: u64, out: &mut _| varint(value, out)),
260 Kind::I32 => number!(Int32Array, 0, |value: i32, out: &mut _| varint(value as i64 as u64, out)),
261 Kind::I64 => number!(Int64Array, 0, |value: i64, out: &mut _| varint(value as u64, out)),
262 Kind::F32 => number!(Float32Array, 5, |value: f32, out: &mut _| Sink::append(out, &value.to_le_bytes())),
263 Kind::F64 => number!(Float64Array, 1, |value: f64, out: &mut _| Sink::append(out, &value.to_le_bytes())),
264 Kind::String => {
265 let value = typed::<StringArray>(array)?.value(row);
266 if protobuf {
267 header(field.tag, value.len(), output)?;
268 } else {
269 scalar(value.len() as u64, output)?;
270 }
271 output.append(value.as_bytes())?;
272 }
273 Kind::Bytes => {
274 let value = typed::<BinaryArray>(array)?.value(row);
275 if protobuf {
276 header(field.tag, value.len(), output)?;
277 } else {
278 scalar(value.len() as u64, output)?;
279 }
280 output.append(value)?;
281 }
282 Kind::Message { definition } => {
283 let values = struct_column(array)?;
284 if protobuf {
285 let mut count = Counter { bytes: 0, limit: usize::MAX };
286 encode_message(schema, definition, values, row, encoding, depth + 1, &mut count)?;
287 header(field.tag, count.bytes, output)?;
288 }
289 encode_message(schema, definition, values, row, encoding, depth + 1, output)?;
290 }
291 }
292 Ok(())
293}
294
295fn typed<T: Array + 'static>(array: &dyn Array) -> Result<&T> {
296 array.as_any().downcast_ref().ok_or_else(invalid)
297}
298fn struct_column(array: &dyn Array) -> Result<&StructArray> {
299 typed(array)
300}
301fn optional_id(values: &StructArray, name: &str, row: usize) -> Result<Option<Identifier>> {
302 let Some(column) = values.column_by_name(name) else {
303 return Ok(None);
304 };
305 let strings = typed::<StringArray>(column.as_ref())?;
306 if strings.is_null(row) {
307 return Ok(None);
308 }
309 Identifier::new(strings.value(row)).map(Some).map_err(|_| invalid())
310}
311fn required_id(values: &StructArray, name: &str, row: usize) -> Result<Identifier> {
312 optional_id(values, name, row)?.ok_or_else(invalid)
313}
314fn varint(mut value: u64, output: &mut impl Sink) -> Result<()> {
315 let mut bytes = [0u8; 10];
316 let mut count = 0;
317 while value >= 128 {
318 bytes[count] = (value as u8 & 127) | 128;
319 value >>= 7;
320 count += 1;
321 }
322 bytes[count] = value as u8;
323 output.append(&bytes[..=count])
324}
325fn header(tag: u32, length: usize, output: &mut impl Sink) -> Result<()> {
326 varint((u64::from(tag) << 3) | 2, output)?;
327 varint(length as u64, output)
328}
329fn exhausted() -> Error {
330 Error::ResourceExhausted { resource: "Arrow payload encoding bytes" }
331}
332fn invalid() -> Error {
333 Error::InvalidInput { field: "write Arrow batch", reason: "batch metadata or typed payload does not match its schema" }
334}
335
336