Skip to main content

reifydb_sdk/operator/column/
writer.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright (c) 2026 ReifyDB
3
4use core::marker::PhantomData;
5
6use reifydb_abi::data::column::ColumnTypeCode;
7use reifydb_value::value::{date::Date, datetime::DateTime, duration::Duration, time::Time};
8
9use crate::{
10	error::SdkError,
11	operator::builder::{ColumnBuilder, ColumnsBuilder, CommittedColumn},
12};
13
14pub struct ScalarWriter<'a, T: Copy> {
15	inner: ColumnBuilder<'a>,
16	cursor: usize,
17	capacity: usize,
18	defined: Option<Vec<bool>>,
19	_t: PhantomData<T>,
20}
21
22impl<'a, T: Copy> ScalarWriter<'a, T> {
23	fn new(inner: ColumnBuilder<'a>, capacity: usize) -> Self {
24		Self {
25			inner,
26			cursor: 0,
27			capacity,
28			defined: None,
29			_t: PhantomData,
30		}
31	}
32
33	#[inline]
34	pub fn push(&mut self, v: T) {
35		debug_assert!(self.cursor < self.capacity, "ScalarWriter::push past capacity");
36		unsafe {
37			let data = self.inner.data_ptr() as *mut T;
38			core::ptr::write_unaligned(data.add(self.cursor), v);
39		}
40		if let Some(d) = self.defined.as_mut() {
41			d.push(true);
42		}
43		self.cursor += 1;
44	}
45
46	#[inline]
47	pub fn push_none(&mut self)
48	where
49		T: Default,
50	{
51		debug_assert!(self.cursor < self.capacity, "ScalarWriter::push_none past capacity");
52		unsafe {
53			let data = self.inner.data_ptr() as *mut T;
54			core::ptr::write_unaligned(data.add(self.cursor), T::default());
55		}
56		let d = self.defined.get_or_insert_with(|| vec![true; self.cursor]);
57		d.push(false);
58		self.cursor += 1;
59	}
60
61	#[inline]
62	pub fn len(&self) -> usize {
63		self.cursor
64	}
65
66	#[inline]
67	pub fn is_empty(&self) -> bool {
68		self.cursor == 0
69	}
70
71	pub fn finish(self) -> Result<CommittedColumn, SdkError> {
72		if let Some(d) = &self.defined {
73			self.inner.set_defined(d);
74		}
75		self.inner.commit(self.cursor)
76	}
77}
78
79pub struct BoolWriter<'a> {
80	inner: ColumnBuilder<'a>,
81	values: Vec<bool>,
82	defined: Option<Vec<bool>>,
83}
84
85impl<'a> BoolWriter<'a> {
86	fn new(inner: ColumnBuilder<'a>, capacity: usize) -> Self {
87		Self {
88			inner,
89			values: Vec::with_capacity(capacity),
90			defined: None,
91		}
92	}
93
94	#[inline]
95	pub fn push(&mut self, v: bool) {
96		self.values.push(v);
97		if let Some(d) = self.defined.as_mut() {
98			d.push(true);
99		}
100	}
101
102	#[inline]
103	pub fn push_none(&mut self) {
104		self.values.push(false);
105		let d = self.defined.get_or_insert_with(|| vec![true; self.values.len() - 1]);
106		d.push(false);
107	}
108
109	#[inline]
110	pub fn len(&self) -> usize {
111		self.values.len()
112	}
113
114	#[inline]
115	pub fn is_empty(&self) -> bool {
116		self.values.is_empty()
117	}
118
119	pub fn finish(self) -> Result<CommittedColumn, SdkError> {
120		if let Some(d) = &self.defined {
121			self.inner.set_defined(d);
122		}
123		self.inner.write_bool(&self.values)
124	}
125}
126
127pub struct VarLenWriter<'a> {
128	inner: ColumnBuilder<'a>,
129	item_cursor: usize,
130	byte_cursor: usize,
131	data_capacity: usize,
132	capacity: usize,
133	defined: Option<Vec<bool>>,
134	type_code: ColumnTypeCode,
135}
136
137impl<'a> VarLenWriter<'a> {
138	fn new(inner: ColumnBuilder<'a>, capacity: usize, expected_bytes: usize) -> Result<Self, SdkError> {
139		let type_code = inner.type_code();
140		debug_assert!(
141			matches!(type_code, ColumnTypeCode::Utf8 | ColumnTypeCode::Blob | ColumnTypeCode::Decimal),
142			"VarLenWriter requires Utf8, Blob, or Decimal",
143		);
144		let initial = expected_bytes.max(capacity);
145		if initial > 0 {
146			inner.grow(initial)?;
147		}
148		unsafe {
149			core::ptr::write(inner.offsets_ptr(), 0u64);
150		}
151		Ok(Self {
152			inner,
153			item_cursor: 0,
154			byte_cursor: 0,
155			data_capacity: initial,
156			capacity,
157			defined: None,
158			type_code,
159		})
160	}
161
162	#[inline]
163	fn ensure_capacity(&mut self, need: usize) -> Result<(), SdkError> {
164		if self.byte_cursor + need <= self.data_capacity {
165			return Ok(());
166		}
167		let extra = (self.byte_cursor + need - self.data_capacity).max(self.data_capacity.max(64));
168		self.inner.grow(extra)?;
169		self.data_capacity += extra;
170		Ok(())
171	}
172
173	#[inline]
174	fn push_bytes_internal(&mut self, bytes: &[u8]) -> Result<(), SdkError> {
175		debug_assert!(self.item_cursor < self.capacity, "VarLenWriter::push past capacity");
176		self.ensure_capacity(bytes.len())?;
177		unsafe {
178			let data = self.inner.data_ptr();
179			let offsets = self.inner.offsets_ptr();
180			if !bytes.is_empty() {
181				core::ptr::copy_nonoverlapping(bytes.as_ptr(), data.add(self.byte_cursor), bytes.len());
182			}
183			self.byte_cursor += bytes.len();
184			core::ptr::write(offsets.add(self.item_cursor + 1), self.byte_cursor as u64);
185		}
186		if let Some(d) = self.defined.as_mut() {
187			d.push(true);
188		}
189		self.item_cursor += 1;
190		Ok(())
191	}
192
193	pub fn push_str(&mut self, s: &str) -> Result<(), SdkError> {
194		debug_assert_eq!(self.type_code, ColumnTypeCode::Utf8);
195		self.push_bytes_internal(s.as_bytes())
196	}
197
198	pub fn push_bytes(&mut self, b: &[u8]) -> Result<(), SdkError> {
199		debug_assert!(matches!(self.type_code, ColumnTypeCode::Blob | ColumnTypeCode::Decimal));
200		self.push_bytes_internal(b)
201	}
202
203	pub fn push_none(&mut self) -> Result<(), SdkError> {
204		debug_assert!(self.item_cursor < self.capacity, "VarLenWriter::push_none past capacity");
205		unsafe {
206			let offsets = self.inner.offsets_ptr();
207			core::ptr::write(offsets.add(self.item_cursor + 1), self.byte_cursor as u64);
208		}
209		let d = self.defined.get_or_insert_with(|| vec![true; self.item_cursor]);
210		d.push(false);
211		self.item_cursor += 1;
212		Ok(())
213	}
214
215	#[inline]
216	pub fn len(&self) -> usize {
217		self.item_cursor
218	}
219
220	#[inline]
221	pub fn is_empty(&self) -> bool {
222		self.item_cursor == 0
223	}
224
225	pub fn finish(self) -> Result<CommittedColumn, SdkError> {
226		if let Some(d) = &self.defined {
227			self.inner.set_defined(d);
228		}
229		self.inner.commit(self.item_cursor)
230	}
231}
232
233pub type U8Writer<'a> = ScalarWriter<'a, u8>;
234pub type U16Writer<'a> = ScalarWriter<'a, u16>;
235pub type U32Writer<'a> = ScalarWriter<'a, u32>;
236pub type U64Writer<'a> = ScalarWriter<'a, u64>;
237pub type U128Writer<'a> = ScalarWriter<'a, u128>;
238pub type I8Writer<'a> = ScalarWriter<'a, i8>;
239pub type I16Writer<'a> = ScalarWriter<'a, i16>;
240pub type I32Writer<'a> = ScalarWriter<'a, i32>;
241pub type I64Writer<'a> = ScalarWriter<'a, i64>;
242pub type I128Writer<'a> = ScalarWriter<'a, i128>;
243pub type F32Writer<'a> = ScalarWriter<'a, f32>;
244pub type F64Writer<'a> = ScalarWriter<'a, f64>;
245pub type DateWriter<'a> = ScalarWriter<'a, Date>;
246pub type DateTimeWriter<'a> = ScalarWriter<'a, DateTime>;
247pub type TimeWriter<'a> = ScalarWriter<'a, Time>;
248pub type DurationWriter<'a> = ScalarWriter<'a, Duration>;
249pub type Utf8Writer<'a> = VarLenWriter<'a>;
250pub type BlobWriter<'a> = VarLenWriter<'a>;
251pub type DecimalWriter<'a> = VarLenWriter<'a>;
252
253impl<'a> ColumnsBuilder<'a> {
254	pub fn u8_writer(&mut self, capacity: usize) -> Result<U8Writer<'_>, SdkError> {
255		Ok(ScalarWriter::new(self.acquire(ColumnTypeCode::Uint1, capacity)?, capacity))
256	}
257	pub fn u16_writer(&mut self, capacity: usize) -> Result<U16Writer<'_>, SdkError> {
258		Ok(ScalarWriter::new(self.acquire(ColumnTypeCode::Uint2, capacity)?, capacity))
259	}
260	pub fn u32_writer(&mut self, capacity: usize) -> Result<U32Writer<'_>, SdkError> {
261		Ok(ScalarWriter::new(self.acquire(ColumnTypeCode::Uint4, capacity)?, capacity))
262	}
263	pub fn u64_writer(&mut self, capacity: usize) -> Result<U64Writer<'_>, SdkError> {
264		Ok(ScalarWriter::new(self.acquire(ColumnTypeCode::Uint8, capacity)?, capacity))
265	}
266	pub fn u128_writer(&mut self, capacity: usize) -> Result<U128Writer<'_>, SdkError> {
267		Ok(ScalarWriter::new(self.acquire(ColumnTypeCode::Uint16, capacity)?, capacity))
268	}
269	pub fn i8_writer(&mut self, capacity: usize) -> Result<I8Writer<'_>, SdkError> {
270		Ok(ScalarWriter::new(self.acquire(ColumnTypeCode::Int1, capacity)?, capacity))
271	}
272	pub fn i16_writer(&mut self, capacity: usize) -> Result<I16Writer<'_>, SdkError> {
273		Ok(ScalarWriter::new(self.acquire(ColumnTypeCode::Int2, capacity)?, capacity))
274	}
275	pub fn i32_writer(&mut self, capacity: usize) -> Result<I32Writer<'_>, SdkError> {
276		Ok(ScalarWriter::new(self.acquire(ColumnTypeCode::Int4, capacity)?, capacity))
277	}
278	pub fn i64_writer(&mut self, capacity: usize) -> Result<I64Writer<'_>, SdkError> {
279		Ok(ScalarWriter::new(self.acquire(ColumnTypeCode::Int8, capacity)?, capacity))
280	}
281	pub fn i128_writer(&mut self, capacity: usize) -> Result<I128Writer<'_>, SdkError> {
282		Ok(ScalarWriter::new(self.acquire(ColumnTypeCode::Int16, capacity)?, capacity))
283	}
284	pub fn f32_writer(&mut self, capacity: usize) -> Result<F32Writer<'_>, SdkError> {
285		Ok(ScalarWriter::new(self.acquire(ColumnTypeCode::Float4, capacity)?, capacity))
286	}
287	pub fn f64_writer(&mut self, capacity: usize) -> Result<F64Writer<'_>, SdkError> {
288		Ok(ScalarWriter::new(self.acquire(ColumnTypeCode::Float8, capacity)?, capacity))
289	}
290	pub fn date_writer(&mut self, capacity: usize) -> Result<DateWriter<'_>, SdkError> {
291		Ok(ScalarWriter::new(self.acquire(ColumnTypeCode::Date, capacity)?, capacity))
292	}
293	pub fn datetime_writer(&mut self, capacity: usize) -> Result<DateTimeWriter<'_>, SdkError> {
294		Ok(ScalarWriter::new(self.acquire(ColumnTypeCode::DateTime, capacity)?, capacity))
295	}
296	pub fn time_writer(&mut self, capacity: usize) -> Result<TimeWriter<'_>, SdkError> {
297		Ok(ScalarWriter::new(self.acquire(ColumnTypeCode::Time, capacity)?, capacity))
298	}
299	pub fn duration_writer(&mut self, capacity: usize) -> Result<DurationWriter<'_>, SdkError> {
300		Ok(ScalarWriter::new(self.acquire(ColumnTypeCode::Duration, capacity)?, capacity))
301	}
302	pub fn bool_writer(&mut self, capacity: usize) -> Result<BoolWriter<'_>, SdkError> {
303		Ok(BoolWriter::new(self.acquire(ColumnTypeCode::Bool, capacity)?, capacity))
304	}
305	pub fn utf8_writer(&mut self, capacity: usize, expected_bytes: usize) -> Result<Utf8Writer<'_>, SdkError> {
306		VarLenWriter::new(self.acquire(ColumnTypeCode::Utf8, capacity)?, capacity, expected_bytes)
307	}
308	pub fn blob_writer(&mut self, capacity: usize, expected_bytes: usize) -> Result<BlobWriter<'_>, SdkError> {
309		VarLenWriter::new(self.acquire(ColumnTypeCode::Blob, capacity)?, capacity, expected_bytes)
310	}
311	pub fn decimal_writer(
312		&mut self,
313		capacity: usize,
314		expected_bytes: usize,
315	) -> Result<DecimalWriter<'_>, SdkError> {
316		VarLenWriter::new(self.acquire(ColumnTypeCode::Decimal, capacity)?, capacity, expected_bytes)
317	}
318}
319
320#[cfg(test)]
321mod tests {
322	use reifydb_abi::operator::capabilities::OperatorCapability;
323	use reifydb_core::interface::catalog::flow::FlowNodeId;
324	use reifydb_value::value::{
325		date::Date, datetime::DateTime, decimal::Decimal, duration::Duration, row_number::RowNumber, time::Time,
326	};
327
328	use crate::{
329		config::Config,
330		error::Result,
331		operator::{
332			FFIOperator, OperatorMetadata,
333			change::BorrowedChange,
334			column::{batch::InsertBatch, operator::OperatorColumn},
335			context::ffi::FFIOperatorContext,
336		},
337		row,
338		testing::{builders::TestChangeBuilder, harness::FFIOperatorHarnessBuilder},
339	};
340
341	struct U8Row {
342		v: u8,
343	}
344	row!(U8Row {
345		v: u8
346	});
347
348	struct OpU8;
349	impl OperatorMetadata for OpU8 {
350		const NAME: &'static str = "writer_u8";
351		const API: u32 = 1;
352		const VERSION: &'static str = "1.0.0";
353		const DESCRIPTION: &'static str = "test fixture";
354		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
355		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
356		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
357	}
358	impl FFIOperator for OpU8 {
359		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
360			Ok(Self)
361		}
362		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
363			let mut batch = InsertBatch::<U8Row, _>::new(ctx, 3)?;
364			for (i, &v) in [0u8, 1, u8::MAX].iter().enumerate() {
365				batch.push(
366					RowNumber(i as u64 + 1),
367					&U8Row {
368						v,
369					},
370				)?;
371			}
372			batch.finish()
373		}
374	}
375
376	#[test]
377	fn scalar_u8_roundtrip() {
378		let mut h = FFIOperatorHarnessBuilder::<OpU8>::new().build().expect("harness");
379		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
380		let post = out.diffs[0].post().expect("post");
381		assert_eq!(post.row_count(), 3);
382		assert_eq!(post.row_ref(0).expect("r0").u8("v"), Some(0));
383		assert_eq!(post.row_ref(1).expect("r1").u8("v"), Some(1));
384		assert_eq!(post.row_ref(2).expect("r2").u8("v"), Some(u8::MAX));
385	}
386
387	struct U16Row {
388		v: u16,
389	}
390	row!(U16Row {
391		v: u16
392	});
393
394	struct OpU16;
395	impl OperatorMetadata for OpU16 {
396		const NAME: &'static str = "writer_u16";
397		const API: u32 = 1;
398		const VERSION: &'static str = "1.0.0";
399		const DESCRIPTION: &'static str = "test fixture";
400		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
401		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
402		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
403	}
404	impl FFIOperator for OpU16 {
405		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
406			Ok(Self)
407		}
408		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
409			let mut batch = InsertBatch::<U16Row, _>::new(ctx, 3)?;
410			for (i, &v) in [0u16, 1, u16::MAX].iter().enumerate() {
411				batch.push(
412					RowNumber(i as u64 + 1),
413					&U16Row {
414						v,
415					},
416				)?;
417			}
418			batch.finish()
419		}
420	}
421
422	#[test]
423	fn scalar_u16_roundtrip() {
424		let mut h = FFIOperatorHarnessBuilder::<OpU16>::new().build().expect("harness");
425		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
426		let post = out.diffs[0].post().expect("post");
427		assert_eq!(post.row_count(), 3);
428		assert_eq!(post.row_ref(0).expect("r0").u16("v"), Some(0));
429		assert_eq!(post.row_ref(1).expect("r1").u16("v"), Some(1));
430		assert_eq!(post.row_ref(2).expect("r2").u16("v"), Some(u16::MAX));
431	}
432
433	struct U32Row {
434		v: u32,
435	}
436	row!(U32Row {
437		v: u32
438	});
439
440	struct OpU32;
441	impl OperatorMetadata for OpU32 {
442		const NAME: &'static str = "writer_u32";
443		const API: u32 = 1;
444		const VERSION: &'static str = "1.0.0";
445		const DESCRIPTION: &'static str = "test fixture";
446		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
447		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
448		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
449	}
450	impl FFIOperator for OpU32 {
451		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
452			Ok(Self)
453		}
454		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
455			let mut batch = InsertBatch::<U32Row, _>::new(ctx, 3)?;
456			for (i, &v) in [0u32, 1, u32::MAX].iter().enumerate() {
457				batch.push(
458					RowNumber(i as u64 + 1),
459					&U32Row {
460						v,
461					},
462				)?;
463			}
464			batch.finish()
465		}
466	}
467
468	#[test]
469	fn scalar_u32_roundtrip() {
470		let mut h = FFIOperatorHarnessBuilder::<OpU32>::new().build().expect("harness");
471		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
472		let post = out.diffs[0].post().expect("post");
473		assert_eq!(post.row_count(), 3);
474		assert_eq!(post.row_ref(0).expect("r0").u32("v"), Some(0));
475		assert_eq!(post.row_ref(1).expect("r1").u32("v"), Some(1));
476		assert_eq!(post.row_ref(2).expect("r2").u32("v"), Some(u32::MAX));
477	}
478
479	struct U64Row {
480		v: u64,
481	}
482	row!(U64Row {
483		v: u64
484	});
485
486	struct OpU64;
487	impl OperatorMetadata for OpU64 {
488		const NAME: &'static str = "writer_u64";
489		const API: u32 = 1;
490		const VERSION: &'static str = "1.0.0";
491		const DESCRIPTION: &'static str = "test fixture";
492		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
493		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
494		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
495	}
496	impl FFIOperator for OpU64 {
497		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
498			Ok(Self)
499		}
500		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
501			let mut batch = InsertBatch::<U64Row, _>::new(ctx, 3)?;
502			for (i, &v) in [0u64, 1, u64::MAX].iter().enumerate() {
503				batch.push(
504					RowNumber(i as u64 + 1),
505					&U64Row {
506						v,
507					},
508				)?;
509			}
510			batch.finish()
511		}
512	}
513
514	#[test]
515	fn scalar_u64_roundtrip() {
516		let mut h = FFIOperatorHarnessBuilder::<OpU64>::new().build().expect("harness");
517		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
518		let post = out.diffs[0].post().expect("post");
519		assert_eq!(post.row_count(), 3);
520		assert_eq!(post.row_ref(0).expect("r0").u64("v"), Some(0));
521		assert_eq!(post.row_ref(1).expect("r1").u64("v"), Some(1));
522		assert_eq!(post.row_ref(2).expect("r2").u64("v"), Some(u64::MAX));
523	}
524
525	struct I8Row {
526		v: i8,
527	}
528	row!(I8Row {
529		v: i8
530	});
531
532	struct OpI8;
533	impl OperatorMetadata for OpI8 {
534		const NAME: &'static str = "writer_i8";
535		const API: u32 = 1;
536		const VERSION: &'static str = "1.0.0";
537		const DESCRIPTION: &'static str = "test fixture";
538		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
539		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
540		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
541	}
542	impl FFIOperator for OpI8 {
543		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
544			Ok(Self)
545		}
546		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
547			let mut batch = InsertBatch::<I8Row, _>::new(ctx, 3)?;
548			for (i, &v) in [i8::MIN, 0_i8, i8::MAX].iter().enumerate() {
549				batch.push(
550					RowNumber(i as u64 + 1),
551					&I8Row {
552						v,
553					},
554				)?;
555			}
556			batch.finish()
557		}
558	}
559
560	#[test]
561	fn scalar_i8_roundtrip() {
562		let mut h = FFIOperatorHarnessBuilder::<OpI8>::new().build().expect("harness");
563		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
564		let post = out.diffs[0].post().expect("post");
565		assert_eq!(post.row_count(), 3);
566		assert_eq!(post.row_ref(0).expect("r0").i8("v"), Some(i8::MIN));
567		assert_eq!(post.row_ref(1).expect("r1").i8("v"), Some(0));
568		assert_eq!(post.row_ref(2).expect("r2").i8("v"), Some(i8::MAX));
569	}
570
571	struct I16Row {
572		v: i16,
573	}
574	row!(I16Row {
575		v: i16
576	});
577
578	struct OpI16;
579	impl OperatorMetadata for OpI16 {
580		const NAME: &'static str = "writer_i16";
581		const API: u32 = 1;
582		const VERSION: &'static str = "1.0.0";
583		const DESCRIPTION: &'static str = "test fixture";
584		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
585		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
586		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
587	}
588	impl FFIOperator for OpI16 {
589		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
590			Ok(Self)
591		}
592		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
593			let mut batch = InsertBatch::<I16Row, _>::new(ctx, 3)?;
594			for (i, &v) in [i16::MIN, 0_i16, i16::MAX].iter().enumerate() {
595				batch.push(
596					RowNumber(i as u64 + 1),
597					&I16Row {
598						v,
599					},
600				)?;
601			}
602			batch.finish()
603		}
604	}
605
606	#[test]
607	fn scalar_i16_roundtrip() {
608		let mut h = FFIOperatorHarnessBuilder::<OpI16>::new().build().expect("harness");
609		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
610		let post = out.diffs[0].post().expect("post");
611		assert_eq!(post.row_count(), 3);
612		assert_eq!(post.row_ref(0).expect("r0").i16("v"), Some(i16::MIN));
613		assert_eq!(post.row_ref(1).expect("r1").i16("v"), Some(0));
614		assert_eq!(post.row_ref(2).expect("r2").i16("v"), Some(i16::MAX));
615	}
616
617	struct I32Row {
618		v: i32,
619	}
620	row!(I32Row {
621		v: i32
622	});
623
624	struct OpI32;
625	impl OperatorMetadata for OpI32 {
626		const NAME: &'static str = "writer_i32";
627		const API: u32 = 1;
628		const VERSION: &'static str = "1.0.0";
629		const DESCRIPTION: &'static str = "test fixture";
630		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
631		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
632		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
633	}
634	impl FFIOperator for OpI32 {
635		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
636			Ok(Self)
637		}
638		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
639			let mut batch = InsertBatch::<I32Row, _>::new(ctx, 3)?;
640			for (i, &v) in [i32::MIN, 0_i32, i32::MAX].iter().enumerate() {
641				batch.push(
642					RowNumber(i as u64 + 1),
643					&I32Row {
644						v,
645					},
646				)?;
647			}
648			batch.finish()
649		}
650	}
651
652	#[test]
653	fn scalar_i32_roundtrip() {
654		let mut h = FFIOperatorHarnessBuilder::<OpI32>::new().build().expect("harness");
655		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
656		let post = out.diffs[0].post().expect("post");
657		assert_eq!(post.row_count(), 3);
658		assert_eq!(post.row_ref(0).expect("r0").i32("v"), Some(i32::MIN));
659		assert_eq!(post.row_ref(1).expect("r1").i32("v"), Some(0));
660		assert_eq!(post.row_ref(2).expect("r2").i32("v"), Some(i32::MAX));
661	}
662
663	struct I64Row {
664		v: i64,
665	}
666	row!(I64Row {
667		v: i64
668	});
669
670	struct OpI64;
671	impl OperatorMetadata for OpI64 {
672		const NAME: &'static str = "writer_i64";
673		const API: u32 = 1;
674		const VERSION: &'static str = "1.0.0";
675		const DESCRIPTION: &'static str = "test fixture";
676		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
677		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
678		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
679	}
680	impl FFIOperator for OpI64 {
681		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
682			Ok(Self)
683		}
684		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
685			let mut batch = InsertBatch::<I64Row, _>::new(ctx, 3)?;
686			for (i, &v) in [i64::MIN, 0_i64, i64::MAX].iter().enumerate() {
687				batch.push(
688					RowNumber(i as u64 + 1),
689					&I64Row {
690						v,
691					},
692				)?;
693			}
694			batch.finish()
695		}
696	}
697
698	#[test]
699	fn scalar_i64_roundtrip() {
700		let mut h = FFIOperatorHarnessBuilder::<OpI64>::new().build().expect("harness");
701		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
702		let post = out.diffs[0].post().expect("post");
703		assert_eq!(post.row_count(), 3);
704		assert_eq!(post.row_ref(0).expect("r0").i64("v"), Some(i64::MIN));
705		assert_eq!(post.row_ref(1).expect("r1").i64("v"), Some(0));
706		assert_eq!(post.row_ref(2).expect("r2").i64("v"), Some(i64::MAX));
707	}
708
709	struct F32Row {
710		v: f32,
711	}
712	row!(F32Row {
713		v: f32
714	});
715
716	struct OpF32;
717	impl OperatorMetadata for OpF32 {
718		const NAME: &'static str = "writer_f32";
719		const API: u32 = 1;
720		const VERSION: &'static str = "1.0.0";
721		const DESCRIPTION: &'static str = "test fixture";
722		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
723		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
724		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
725	}
726	impl FFIOperator for OpF32 {
727		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
728			Ok(Self)
729		}
730		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
731			let mut batch = InsertBatch::<F32Row, _>::new(ctx, 3)?;
732			for (i, &v) in [0.0_f32, -1.5_f32, f32::MAX].iter().enumerate() {
733				batch.push(
734					RowNumber(i as u64 + 1),
735					&F32Row {
736						v,
737					},
738				)?;
739			}
740			batch.finish()
741		}
742	}
743
744	#[test]
745	fn scalar_f32_roundtrip() {
746		let mut h = FFIOperatorHarnessBuilder::<OpF32>::new().build().expect("harness");
747		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
748		let post = out.diffs[0].post().expect("post");
749		assert_eq!(post.row_count(), 3);
750		assert_eq!(post.row_ref(0).expect("r0").f32("v"), Some(0.0_f32));
751		assert_eq!(post.row_ref(1).expect("r1").f32("v"), Some(-1.5_f32));
752		assert_eq!(post.row_ref(2).expect("r2").f32("v"), Some(f32::MAX));
753	}
754
755	struct F64Row {
756		v: f64,
757	}
758	row!(F64Row {
759		v: f64
760	});
761
762	struct OpF64;
763	impl OperatorMetadata for OpF64 {
764		const NAME: &'static str = "writer_f64";
765		const API: u32 = 1;
766		const VERSION: &'static str = "1.0.0";
767		const DESCRIPTION: &'static str = "test fixture";
768		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
769		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
770		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
771	}
772	impl FFIOperator for OpF64 {
773		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
774			Ok(Self)
775		}
776		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
777			let mut batch = InsertBatch::<F64Row, _>::new(ctx, 3)?;
778			for (i, &v) in [0.0_f64, -1.5_f64, f64::MAX].iter().enumerate() {
779				batch.push(
780					RowNumber(i as u64 + 1),
781					&F64Row {
782						v,
783					},
784				)?;
785			}
786			batch.finish()
787		}
788	}
789
790	#[test]
791	fn scalar_f64_roundtrip() {
792		let mut h = FFIOperatorHarnessBuilder::<OpF64>::new().build().expect("harness");
793		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
794		let post = out.diffs[0].post().expect("post");
795		assert_eq!(post.row_count(), 3);
796		assert_eq!(post.row_ref(0).expect("r0").f64("v"), Some(0.0_f64));
797		assert_eq!(post.row_ref(1).expect("r1").f64("v"), Some(-1.5_f64));
798		assert_eq!(post.row_ref(2).expect("r2").f64("v"), Some(f64::MAX));
799	}
800
801	struct BoolRow {
802		v: bool,
803	}
804	row!(BoolRow {
805		v: bool
806	});
807
808	struct OpBool;
809	impl OperatorMetadata for OpBool {
810		const NAME: &'static str = "writer_bool";
811		const API: u32 = 1;
812		const VERSION: &'static str = "1.0.0";
813		const DESCRIPTION: &'static str = "test fixture";
814		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
815		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
816		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
817	}
818	impl FFIOperator for OpBool {
819		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
820			Ok(Self)
821		}
822		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
823			let mut batch = InsertBatch::<BoolRow, _>::new(ctx, 3)?;
824			for (i, &v) in [true, false, true].iter().enumerate() {
825				batch.push(
826					RowNumber(i as u64 + 1),
827					&BoolRow {
828						v,
829					},
830				)?;
831			}
832			batch.finish()
833		}
834	}
835
836	#[test]
837	fn bool_roundtrip() {
838		let mut h = FFIOperatorHarnessBuilder::<OpBool>::new().build().expect("harness");
839		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
840		let post = out.diffs[0].post().expect("post");
841		assert_eq!(post.row_count(), 3);
842		assert_eq!(post.row_ref(0).expect("r0").bool("v"), Some(true));
843		assert_eq!(post.row_ref(1).expect("r1").bool("v"), Some(false));
844		assert_eq!(post.row_ref(2).expect("r2").bool("v"), Some(true));
845	}
846
847	struct Utf8Row {
848		s: String,
849	}
850	row!(Utf8Row {
851		s: String
852	});
853
854	struct OpUtf8;
855	impl OperatorMetadata for OpUtf8 {
856		const NAME: &'static str = "writer_utf8";
857		const API: u32 = 1;
858		const VERSION: &'static str = "1.0.0";
859		const DESCRIPTION: &'static str = "test fixture";
860		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
861		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
862		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
863	}
864	impl FFIOperator for OpUtf8 {
865		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
866			Ok(Self)
867		}
868		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
869			let values = ["", "hello", "こんにちは"];
870			let mut batch = InsertBatch::<Utf8Row, _>::new(ctx, values.len())?;
871			for (i, &s) in values.iter().enumerate() {
872				batch.push(
873					RowNumber(i as u64 + 1),
874					&Utf8Row {
875						s: s.to_string(),
876					},
877				)?;
878			}
879			batch.finish()
880		}
881	}
882
883	#[test]
884	fn utf8_roundtrip() {
885		let mut h = FFIOperatorHarnessBuilder::<OpUtf8>::new().build().expect("harness");
886		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
887		let post = out.diffs[0].post().expect("post");
888		assert_eq!(post.row_count(), 3);
889		assert_eq!(post.row_ref(0).expect("r0").utf8("s").as_deref(), Some(""));
890		assert_eq!(post.row_ref(1).expect("r1").utf8("s").as_deref(), Some("hello"));
891		assert_eq!(post.row_ref(2).expect("r2").utf8("s").as_deref(), Some("こんにちは"));
892	}
893
894	struct OpUtf8Growth;
895	impl OperatorMetadata for OpUtf8Growth {
896		const NAME: &'static str = "writer_utf8_growth";
897		const API: u32 = 1;
898		const VERSION: &'static str = "1.0.0";
899		const DESCRIPTION: &'static str = "test fixture";
900		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
901		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
902		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
903	}
904	impl FFIOperator for OpUtf8Growth {
905		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
906			Ok(Self)
907		}
908		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
909			// AVG_BYTES for String is 24; 20 rows * 24 = 480 bytes pre-allocated.
910			// Each string is 100 bytes so total 2000 bytes forces VarLenWriter::ensure_capacity.
911			let mut batch = InsertBatch::<Utf8Row, _>::new(ctx, 20)?;
912			for i in 0..20u64 {
913				batch.push(
914					RowNumber(i + 1),
915					&Utf8Row {
916						s: "x".repeat(100),
917					},
918				)?;
919			}
920			batch.finish()
921		}
922	}
923
924	#[test]
925	fn utf8_capacity_growth() {
926		let mut h = FFIOperatorHarnessBuilder::<OpUtf8Growth>::new().build().expect("harness");
927		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
928		let post = out.diffs[0].post().expect("post");
929		assert_eq!(post.row_count(), 20);
930		let expected = "x".repeat(100);
931		for i in 0..20usize {
932			assert_eq!(
933				post.row_ref(i).expect("row").utf8("s").as_deref(),
934				Some(expected.as_str()),
935				"row {i}"
936			);
937		}
938	}
939
940	struct BlobRow {
941		b: Vec<u8>,
942	}
943	row!(BlobRow { b: Vec<u8> });
944
945	struct OpBlob;
946	impl OperatorMetadata for OpBlob {
947		const NAME: &'static str = "writer_blob";
948		const API: u32 = 1;
949		const VERSION: &'static str = "1.0.0";
950		const DESCRIPTION: &'static str = "test fixture";
951		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
952		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
953		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
954	}
955	impl FFIOperator for OpBlob {
956		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
957			Ok(Self)
958		}
959		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
960			let rows = [
961				BlobRow {
962					b: vec![],
963				},
964				BlobRow {
965					b: vec![0u8, 1, 127, 255],
966				},
967				BlobRow {
968					b: vec![42u8; 1000],
969				},
970			];
971			let mut batch = InsertBatch::<BlobRow, _>::new(ctx, rows.len())?;
972			for (i, row) in rows.iter().enumerate() {
973				batch.push(RowNumber(i as u64 + 1), row)?;
974			}
975			batch.finish()
976		}
977	}
978
979	#[test]
980	fn blob_roundtrip() {
981		let mut h = FFIOperatorHarnessBuilder::<OpBlob>::new().build().expect("harness");
982		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
983		let post = out.diffs[0].post().expect("post");
984		assert_eq!(post.row_count(), 3);
985		assert_eq!(post.row_ref(0).expect("r0").blob("b"), Some(vec![]));
986		assert_eq!(post.row_ref(1).expect("r1").blob("b"), Some(vec![0u8, 1, 127, 255]));
987		assert_eq!(post.row_ref(2).expect("r2").blob("b"), Some(vec![42u8; 1000]));
988	}
989
990	struct DecimalRow {
991		d: Decimal,
992	}
993	row!(DecimalRow {
994		d: Decimal
995	});
996
997	struct OpDecimal;
998	impl OperatorMetadata for OpDecimal {
999		const NAME: &'static str = "writer_decimal";
1000		const API: u32 = 1;
1001		const VERSION: &'static str = "1.0.0";
1002		const DESCRIPTION: &'static str = "test fixture";
1003		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
1004		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
1005		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
1006	}
1007	impl FFIOperator for OpDecimal {
1008		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
1009			Ok(Self)
1010		}
1011		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
1012			let mut batch = InsertBatch::<DecimalRow, _>::new(ctx, 3)?;
1013			batch.push(
1014				RowNumber(1),
1015				&DecimalRow {
1016					d: Decimal::zero(),
1017				},
1018			)?;
1019			batch.push(
1020				RowNumber(2),
1021				&DecimalRow {
1022					d: Decimal::from_i64(1234),
1023				},
1024			)?;
1025			batch.push(
1026				RowNumber(3),
1027				&DecimalRow {
1028					d: Decimal::from_i64(-5678),
1029				},
1030			)?;
1031			batch.finish()
1032		}
1033	}
1034
1035	#[test]
1036	fn decimal_roundtrip() {
1037		let mut h = FFIOperatorHarnessBuilder::<OpDecimal>::new().build().expect("harness");
1038		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
1039		let post = out.diffs[0].post().expect("post");
1040		assert_eq!(post.row_count(), 3);
1041		assert_eq!(post.row_ref(0).expect("r0").decimal("d"), Some(Decimal::zero()));
1042		assert_eq!(post.row_ref(1).expect("r1").decimal("d"), Some(Decimal::from_i64(1234)));
1043		assert_eq!(post.row_ref(2).expect("r2").decimal("d"), Some(Decimal::from_i64(-5678)));
1044	}
1045
1046	struct WideRow {
1047		a: u128,
1048		b: i128,
1049	}
1050	row!(WideRow {
1051		a: u128,
1052		b: i128
1053	});
1054
1055	struct OpWide;
1056	impl OperatorMetadata for OpWide {
1057		const NAME: &'static str = "writer_wide";
1058		const API: u32 = 1;
1059		const VERSION: &'static str = "1.0.0";
1060		const DESCRIPTION: &'static str = "test fixture";
1061		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
1062		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
1063		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
1064	}
1065	impl FFIOperator for OpWide {
1066		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
1067			Ok(Self)
1068		}
1069		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
1070			let mut batch = InsertBatch::<WideRow, _>::new(ctx, 1)?;
1071			batch.push(
1072				RowNumber(1),
1073				&WideRow {
1074					a: u128::MAX,
1075					b: i128::MIN,
1076				},
1077			)?;
1078			batch.finish()
1079		}
1080	}
1081
1082	#[test]
1083	fn wide_integers_roundtrip() {
1084		let mut h = FFIOperatorHarnessBuilder::<OpWide>::new().build().expect("harness");
1085		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
1086		let post = out.diffs[0].post().expect("post");
1087		assert_eq!(post.row_count(), 1);
1088		assert_eq!(post.row_ref(0).expect("r0").u128("a"), Some(u128::MAX));
1089		assert_eq!(post.row_ref(0).expect("r0").i128("b"), Some(i128::MIN));
1090	}
1091
1092	struct DateRow {
1093		v: Date,
1094	}
1095	row!(DateRow {
1096		v: Date
1097	});
1098
1099	struct OpDate;
1100	impl OperatorMetadata for OpDate {
1101		const NAME: &'static str = "writer_date";
1102		const API: u32 = 1;
1103		const VERSION: &'static str = "1.0.0";
1104		const DESCRIPTION: &'static str = "test fixture";
1105		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
1106		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
1107		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
1108	}
1109	impl FFIOperator for OpDate {
1110		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
1111			Ok(Self)
1112		}
1113		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
1114			let values = [
1115				Date::default(),
1116				Date::new(2024, 3, 15).expect("date"),
1117				Date::new(2554, 1, 1).expect("date"),
1118			];
1119			let mut batch = InsertBatch::<DateRow, _>::new(ctx, values.len())?;
1120			for (i, &v) in values.iter().enumerate() {
1121				batch.push(
1122					RowNumber(i as u64 + 1),
1123					&DateRow {
1124						v,
1125					},
1126				)?;
1127			}
1128			batch.finish()
1129		}
1130	}
1131
1132	#[test]
1133	fn scalar_date_roundtrip() {
1134		let mut h = FFIOperatorHarnessBuilder::<OpDate>::new().build().expect("harness");
1135		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
1136		let post = out.diffs[0].post().expect("post");
1137		assert_eq!(post.row_count(), 3);
1138		assert_eq!(post.row_ref(0).expect("r0").date("v"), Some(Date::default()));
1139		assert_eq!(post.row_ref(1).expect("r1").date("v"), Date::new(2024, 3, 15));
1140		assert_eq!(post.row_ref(2).expect("r2").date("v"), Date::new(2554, 1, 1));
1141	}
1142
1143	struct DateTimeRow {
1144		v: DateTime,
1145	}
1146	row!(DateTimeRow {
1147		v: DateTime
1148	});
1149
1150	struct OpDateTime;
1151	impl OperatorMetadata for OpDateTime {
1152		const NAME: &'static str = "writer_datetime";
1153		const API: u32 = 1;
1154		const VERSION: &'static str = "1.0.0";
1155		const DESCRIPTION: &'static str = "test fixture";
1156		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
1157		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
1158		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
1159	}
1160	impl FFIOperator for OpDateTime {
1161		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
1162			Ok(Self)
1163		}
1164		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
1165			let values = [
1166				DateTime::from_nanos(0),
1167				DateTime::from_nanos(1_700_000_000_000_000_000),
1168				DateTime::from_nanos(u64::MAX),
1169			];
1170			let mut batch = InsertBatch::<DateTimeRow, _>::new(ctx, values.len())?;
1171			for (i, &v) in values.iter().enumerate() {
1172				batch.push(
1173					RowNumber(i as u64 + 1),
1174					&DateTimeRow {
1175						v,
1176					},
1177				)?;
1178			}
1179			batch.finish()
1180		}
1181	}
1182
1183	#[test]
1184	fn scalar_datetime_roundtrip() {
1185		let mut h = FFIOperatorHarnessBuilder::<OpDateTime>::new().build().expect("harness");
1186		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
1187		let post = out.diffs[0].post().expect("post");
1188		assert_eq!(post.row_count(), 3);
1189		assert_eq!(post.row_ref(0).expect("r0").datetime("v"), Some(DateTime::from_nanos(0)));
1190		assert_eq!(
1191			post.row_ref(1).expect("r1").datetime("v"),
1192			Some(DateTime::from_nanos(1_700_000_000_000_000_000))
1193		);
1194		assert_eq!(post.row_ref(2).expect("r2").datetime("v"), Some(DateTime::from_nanos(u64::MAX)));
1195	}
1196
1197	struct TimeRow {
1198		v: Time,
1199	}
1200	row!(TimeRow {
1201		v: Time
1202	});
1203
1204	struct OpTime;
1205	impl OperatorMetadata for OpTime {
1206		const NAME: &'static str = "writer_time";
1207		const API: u32 = 1;
1208		const VERSION: &'static str = "1.0.0";
1209		const DESCRIPTION: &'static str = "test fixture";
1210		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
1211		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
1212		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
1213	}
1214	impl FFIOperator for OpTime {
1215		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
1216			Ok(Self)
1217		}
1218		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
1219			let values = [
1220				Time::default(),
1221				Time::new(14, 30, 45, 123_456_789).expect("time"),
1222				Time::new(23, 59, 59, 999_999_999).expect("time"),
1223			];
1224			let mut batch = InsertBatch::<TimeRow, _>::new(ctx, values.len())?;
1225			for (i, &v) in values.iter().enumerate() {
1226				batch.push(
1227					RowNumber(i as u64 + 1),
1228					&TimeRow {
1229						v,
1230					},
1231				)?;
1232			}
1233			batch.finish()
1234		}
1235	}
1236
1237	#[test]
1238	fn scalar_time_roundtrip() {
1239		let mut h = FFIOperatorHarnessBuilder::<OpTime>::new().build().expect("harness");
1240		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
1241		let post = out.diffs[0].post().expect("post");
1242		assert_eq!(post.row_count(), 3);
1243		assert_eq!(post.row_ref(0).expect("r0").time("v"), Some(Time::default()));
1244		assert_eq!(post.row_ref(1).expect("r1").time("v"), Time::new(14, 30, 45, 123_456_789));
1245		assert_eq!(post.row_ref(2).expect("r2").time("v"), Time::new(23, 59, 59, 999_999_999));
1246	}
1247
1248	struct DurationRow {
1249		v: Duration,
1250	}
1251	row!(DurationRow {
1252		v: Duration
1253	});
1254
1255	struct OpDuration;
1256	impl OperatorMetadata for OpDuration {
1257		const NAME: &'static str = "writer_duration";
1258		const API: u32 = 1;
1259		const VERSION: &'static str = "1.0.0";
1260		const DESCRIPTION: &'static str = "test fixture";
1261		const INPUT_COLUMNS: &'static [OperatorColumn] = &[];
1262		const OUTPUT_COLUMNS: &'static [OperatorColumn] = &[];
1263		const CAPABILITIES: &'static [OperatorCapability] = OperatorCapability::STANDARD;
1264	}
1265	impl FFIOperator for OpDuration {
1266		fn new(_: FlowNodeId, _: &Config) -> Result<Self> {
1267			Ok(Self)
1268		}
1269		fn apply(&mut self, ctx: &mut FFIOperatorContext, _: BorrowedChange<'_>) -> Result<()> {
1270			let values = [
1271				Duration::default(),
1272				Duration::new(13, 5, 3_600_000_000_000).expect("duration"),
1273				Duration::from_seconds(-30).expect("duration"),
1274			];
1275			let mut batch = InsertBatch::<DurationRow, _>::new(ctx, values.len())?;
1276			for (i, &v) in values.iter().enumerate() {
1277				batch.push(
1278					RowNumber(i as u64 + 1),
1279					&DurationRow {
1280						v,
1281					},
1282				)?;
1283			}
1284			batch.finish()
1285		}
1286	}
1287
1288	#[test]
1289	fn scalar_duration_roundtrip() {
1290		let mut h = FFIOperatorHarnessBuilder::<OpDuration>::new().build().expect("harness");
1291		let out = h.apply(TestChangeBuilder::new().build()).expect("apply");
1292		let post = out.diffs[0].post().expect("post");
1293		assert_eq!(post.row_count(), 3);
1294		assert_eq!(post.row_ref(0).expect("r0").duration("v"), Some(Duration::default()));
1295		assert_eq!(post.row_ref(1).expect("r1").duration("v"), Duration::new(13, 5, 3_600_000_000_000).ok());
1296		assert_eq!(post.row_ref(2).expect("r2").duration("v"), Duration::from_seconds(-30).ok());
1297	}
1298}