1use anyhow::{Result, anyhow};
11use arrow_array::builder::{
12 BinaryBuilder, BooleanBufferBuilder, BooleanBuilder, Date32Builder, DurationMicrosecondBuilder,
13 FixedSizeBinaryBuilder, FixedSizeListBuilder, Float32Builder, Float64Builder, Int32Builder,
14 Int64Builder, IntervalMonthDayNanoBuilder, LargeBinaryBuilder, ListBuilder, PrimitiveBuilder,
15 StringBuilder, StructBuilder, Time64MicrosecondBuilder, Time64NanosecondBuilder,
16 TimestampNanosecondBuilder, UInt8Builder, UInt32Builder,
17};
18use arrow_array::types::{
19 ArrowPrimitiveType, Float32Type, Float64Type, Int32Type, Int64Type, UInt64Type,
20};
21use arrow_array::{
22 Array, ArrayRef, BinaryArray, BooleanArray, Date32Array, FixedSizeBinaryArray,
23 FixedSizeListArray, Float32Array, Float64Array, Int32Array, Int64Array,
24 IntervalMonthDayNanoArray, LargeBinaryArray, LargeStringArray, ListArray, StringArray,
25 StructArray, Time64NanosecondArray, TimestampNanosecondArray, UInt8Array, UInt32Array,
26 UInt64Array,
27};
28use arrow_schema::{DataType as ArrowDataType, Field};
29use std::collections::HashMap;
30use std::sync::Arc;
31use uni_common::DataType;
32use uni_common::Value;
33use uni_common::core::id::{Eid, Vid};
34use uni_common::core::schema;
35use uni_common::core::schema::PointType;
36use uni_crdt::Crdt;
37
38fn build_timestamp_column_from_id_map<K, I>(
43 ids: I,
44 timestamps: Option<&HashMap<K, i64>>,
45) -> ArrayRef
46where
47 K: Eq + std::hash::Hash,
48 I: IntoIterator<Item = K>,
49{
50 let mut builder = TimestampNanosecondBuilder::new().with_timezone("UTC");
51 for id in ids {
52 match timestamps.and_then(|m| m.get(&id)) {
53 Some(&ts) => builder.append_value(ts),
54 None => builder.append_null(),
55 }
56 }
57 Arc::new(builder.finish())
58}
59
60pub fn build_timestamp_column_from_vid_map<I>(
61 ids: I,
62 timestamps: Option<&HashMap<Vid, i64>>,
63) -> ArrayRef
64where
65 I: IntoIterator<Item = Vid>,
66{
67 build_timestamp_column_from_id_map(ids, timestamps)
68}
69
70pub fn build_timestamp_column_from_eid_map<I>(
71 ids: I,
72 timestamps: Option<&HashMap<Eid, i64>>,
73) -> ArrayRef
74where
75 I: IntoIterator<Item = Eid>,
76{
77 build_timestamp_column_from_id_map(ids, timestamps)
78}
79
80pub fn build_timestamp_column<I>(timestamps: I) -> ArrayRef
84where
85 I: IntoIterator<Item = Option<i64>>,
86{
87 let mut builder = TimestampNanosecondBuilder::new().with_timezone("UTC");
88 for ts in timestamps {
89 builder.append_option(ts);
90 }
91 Arc::new(builder.finish())
92}
93
94pub fn labels_from_list_array(list_arr: &ListArray, row: usize) -> Vec<String> {
100 if list_arr.is_null(row) {
101 return Vec::new();
102 }
103 let values = list_arr.value(row);
104 let Some(str_arr) = values.as_any().downcast_ref::<StringArray>() else {
105 return Vec::new();
106 };
107 (0..str_arr.len())
108 .filter(|&j| !str_arr.is_null(j))
109 .map(|j| str_arr.value(j).to_string())
110 .collect()
111}
112
113fn parse_datetime_to_nanos(s: &str) -> Option<i64> {
118 chrono::DateTime::parse_from_rfc3339(s)
119 .map(|dt| {
120 dt.with_timezone(&chrono::Utc)
121 .timestamp_nanos_opt()
122 .unwrap_or(0)
123 })
124 .or_else(|_| {
125 chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S")
126 .map(|ndt| ndt.and_utc().timestamp_nanos_opt().unwrap_or(0))
127 })
128 .or_else(|_| {
129 chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%SZ")
130 .map(|ndt| ndt.and_utc().timestamp_nanos_opt().unwrap_or(0))
131 })
132 .or_else(|_| {
133 chrono::DateTime::parse_from_str(s, "%Y-%m-%dT%H:%M%:z").map(|dt| {
134 dt.with_timezone(&chrono::Utc)
135 .timestamp_nanos_opt()
136 .unwrap_or(0)
137 })
138 })
139 .ok()
140 .or_else(|| {
141 s.strip_suffix('Z')
142 .and_then(|base| chrono::NaiveDateTime::parse_from_str(base, "%Y-%m-%dT%H:%M").ok())
143 .map(|ndt| ndt.and_utc().timestamp_nanos_opt().unwrap_or(0))
144 })
145}
146
147pub(crate) fn try_reconstruct_map(arr: &ArrayRef) -> Option<HashMap<String, Value>> {
153 let structs = arr.as_any().downcast_ref::<StructArray>()?;
154 let fields = structs.fields();
155 if fields.len() != 2 || fields[0].name() != "key" || fields[1].name() != "value" {
156 return None;
157 }
158 let value_hint = raw_bytes_hint(fields[1].metadata());
161 let key_col = structs.column(0);
162 let val_col = structs.column(1);
163 let mut map = HashMap::new();
164 for i in 0..structs.len() {
165 if let Value::String(k) = arrow_to_value(key_col.as_ref(), i, None) {
166 map.insert(k, arrow_to_value(val_col.as_ref(), i, value_hint));
167 }
168 }
169 Some(map)
170}
171
172fn array_to_value_list(arr: &ArrayRef, elem_type: Option<&DataType>) -> Vec<Value> {
177 (0..arr.len())
178 .map(|i| arrow_to_value(arr.as_ref(), i, elem_type))
179 .collect()
180}
181
182fn raw_bytes_hint(metadata: &HashMap<String, String>) -> Option<&'static DataType> {
186 if metadata.get("uni_raw_bytes").is_some_and(|v| v == "true") {
187 Some(&DataType::Bytes)
188 } else {
189 None
190 }
191}
192
193fn list_child_bytes_hint(dt: &ArrowDataType) -> Option<&'static DataType> {
195 match dt {
196 ArrowDataType::List(f)
197 | ArrowDataType::LargeList(f)
198 | ArrowDataType::FixedSizeList(f, _) => raw_bytes_hint(f.metadata()),
199 _ => None,
200 }
201}
202
203pub fn arrow_to_value(col: &dyn Array, row: usize, data_type: Option<&DataType>) -> Value {
210 if col.is_null(row) {
211 return Value::Null;
212 }
213
214 if let Some(dt) = data_type {
216 match dt {
217 DataType::DateTime => {
218 if let Some(struct_arr) = col.as_any().downcast_ref::<StructArray>()
220 && let (Some(nanos_col), Some(offset_col), Some(tz_col)) = (
221 struct_arr.column_by_name("nanos_since_epoch"),
222 struct_arr.column_by_name("offset_seconds"),
223 struct_arr.column_by_name("timezone_name"),
224 )
225 && let (Some(nanos_arr), Some(offset_arr), Some(tz_arr)) = (
226 nanos_col
227 .as_any()
228 .downcast_ref::<TimestampNanosecondArray>(),
229 offset_col.as_any().downcast_ref::<Int32Array>(),
230 tz_col.as_any().downcast_ref::<StringArray>(),
231 )
232 {
233 if nanos_arr.is_null(row) {
234 return Value::Null;
235 }
236 let nanos = nanos_arr.value(row);
237 if offset_arr.is_null(row) {
238 return Value::Temporal(uni_common::TemporalValue::LocalDateTime {
240 nanos_since_epoch: nanos,
241 });
242 }
243 let offset = offset_arr.value(row);
244 let tz_name = (!tz_arr.is_null(row)).then(|| tz_arr.value(row).to_string());
245 return Value::Temporal(uni_common::TemporalValue::DateTime {
246 nanos_since_epoch: nanos,
247 offset_seconds: offset,
248 timezone_name: tz_name,
249 });
250 }
251 if let Some(ts) = col.as_any().downcast_ref::<TimestampNanosecondArray>() {
253 let nanos = ts.value(row);
254 let tz_name = ts.timezone().map(|s| s.to_string());
255 return Value::Temporal(uni_common::TemporalValue::DateTime {
256 nanos_since_epoch: nanos,
257 offset_seconds: 0,
258 timezone_name: tz_name,
259 });
260 }
261 }
262 DataType::Time => {
263 if let Some(struct_arr) = col.as_any().downcast_ref::<StructArray>()
265 && let (Some(nanos_col), Some(offset_col)) = (
266 struct_arr.column_by_name("nanos_since_midnight"),
267 struct_arr.column_by_name("offset_seconds"),
268 )
269 && let (Some(nanos_arr), Some(offset_arr)) = (
270 nanos_col.as_any().downcast_ref::<Time64NanosecondArray>(),
271 offset_col.as_any().downcast_ref::<Int32Array>(),
272 )
273 {
274 if nanos_arr.is_null(row) || offset_arr.is_null(row) {
276 return Value::Null;
277 }
278 let nanos = nanos_arr.value(row);
279 let offset = offset_arr.value(row);
280 return Value::Temporal(uni_common::TemporalValue::Time {
281 nanos_since_midnight: nanos,
282 offset_seconds: offset,
283 });
284 }
285 if let Some(t) = col.as_any().downcast_ref::<Time64NanosecondArray>() {
287 let nanos = t.value(row);
288 return Value::Temporal(uni_common::TemporalValue::Time {
289 nanos_since_midnight: nanos,
290 offset_seconds: 0,
291 });
292 }
293 }
294 DataType::Point(pt) => {
295 if let Some(struct_arr) = col.as_any().downcast_ref::<StructArray>() {
302 let f64_at = |name: &str| -> Option<f64> {
303 struct_arr
304 .column_by_name(name)
305 .and_then(|c| c.as_any().downcast_ref::<Float64Array>())
306 .filter(|a| !a.is_null(row))
307 .map(|a| a.value(row))
308 };
309 let crs = struct_arr
310 .column_by_name("crs")
311 .and_then(|c| c.as_any().downcast_ref::<StringArray>())
312 .filter(|a| !a.is_null(row))
313 .map(|a| a.value(row).to_string());
314
315 match pt {
316 PointType::Geographic => {
317 if let (Some(lat), Some(lon)) =
318 (f64_at("latitude"), f64_at("longitude"))
319 {
320 return Value::Map(HashMap::from([
321 ("type".to_string(), Value::String("Point".into())),
322 (
323 "crs".to_string(),
324 Value::String(crs.unwrap_or_else(|| "WGS84".into())),
325 ),
326 ("latitude".to_string(), Value::Float(lat)),
327 ("longitude".to_string(), Value::Float(lon)),
328 ]));
329 }
330 }
331 PointType::Cartesian2D => {
332 if let (Some(x), Some(y)) = (f64_at("x"), f64_at("y")) {
333 return Value::Map(HashMap::from([
334 ("type".to_string(), Value::String("Point".into())),
335 (
336 "crs".to_string(),
337 Value::String(crs.unwrap_or_else(|| "cartesian".into())),
338 ),
339 ("x".to_string(), Value::Float(x)),
340 ("y".to_string(), Value::Float(y)),
341 ]));
342 }
343 }
344 PointType::Cartesian3D => {
345 if let (Some(x), Some(y), Some(z)) =
346 (f64_at("x"), f64_at("y"), f64_at("z"))
347 {
348 return Value::Map(HashMap::from([
349 ("type".to_string(), Value::String("Point".into())),
350 (
351 "crs".to_string(),
352 Value::String(crs.unwrap_or_else(|| "cartesian-3d".into())),
353 ),
354 ("x".to_string(), Value::Float(x)),
355 ("y".to_string(), Value::Float(y)),
356 ("z".to_string(), Value::Float(z)),
357 ]));
358 }
359 }
360 }
361 }
362 return Value::Null;
364 }
365 DataType::Bytes => {
366 let Some(arr) = col.as_any().downcast_ref::<LargeBinaryArray>() else {
367 log::warn!("Bytes column is not LargeBinaryArray");
368 return Value::Null;
369 };
370 if arr.is_null(row) {
371 return Value::Null;
372 }
373 return Value::Bytes(arr.value(row).to_vec());
374 }
375 DataType::Btic => {
376 let Some(fsb) = col.as_any().downcast_ref::<FixedSizeBinaryArray>() else {
377 log::warn!("BTIC column is not FixedSizeBinaryArray");
378 return Value::Null;
379 };
380 let bytes = fsb.value(row);
381 return match uni_btic::encode::decode_slice(bytes) {
382 Ok(btic) => Value::Temporal(uni_common::TemporalValue::Btic {
383 lo: btic.lo(),
384 hi: btic.hi(),
385 meta: btic.meta(),
386 }),
387 Err(e) => {
388 log::warn!("BTIC decode error: {}", e);
389 Value::Null
390 }
391 };
392 }
393 DataType::SparseVector { .. } => {
394 let Some(struct_arr) = col.as_any().downcast_ref::<StructArray>() else {
395 log::warn!("SparseVector column is not StructArray");
396 return Value::Null;
397 };
398 if struct_arr.is_null(row) {
399 return Value::Null;
400 }
401 let (Some(indices_list), Some(values_list)) = (
402 struct_arr
403 .column_by_name("indices")
404 .and_then(|c| c.as_any().downcast_ref::<ListArray>()),
405 struct_arr
406 .column_by_name("values")
407 .and_then(|c| c.as_any().downcast_ref::<ListArray>()),
408 ) else {
409 log::warn!("SparseVector struct missing indices/values list columns");
410 return Value::Null;
411 };
412 let idx_vals = indices_list.value(row);
413 let Some(idx_arr) = idx_vals.as_any().downcast_ref::<UInt32Array>() else {
414 log::warn!("SparseVector 'indices' inner not UInt32");
415 return Value::Null;
416 };
417 let w_vals = values_list.value(row);
418 let Some(w_arr) = w_vals.as_any().downcast_ref::<Float32Array>() else {
419 log::warn!("SparseVector 'values' inner not Float32");
420 return Value::Null;
421 };
422 let indices: Vec<u32> = (0..idx_arr.len()).map(|i| idx_arr.value(i)).collect();
423 let values: Vec<f32> = (0..w_arr.len()).map(|i| w_arr.value(i)).collect();
424 return Value::SparseVector { indices, values };
425 }
426 _ => {}
427 }
428 }
429
430 if let Some(s) = col.as_any().downcast_ref::<StringArray>() {
432 return Value::String(s.value(row).to_string());
433 }
434 if let Some(s) = col.as_any().downcast_ref::<LargeStringArray>() {
443 return Value::String(s.value(row).to_string());
444 }
445
446 if let Some(u) = col.as_any().downcast_ref::<UInt64Array>() {
448 return Value::Int(u.value(row) as i64);
449 }
450 if let Some(i) = col.as_any().downcast_ref::<Int64Array>() {
451 return Value::Int(i.value(row));
452 }
453 if let Some(i) = col.as_any().downcast_ref::<Int32Array>() {
454 return Value::Int(i.value(row) as i64);
455 }
456
457 if let Some(f) = col.as_any().downcast_ref::<Float64Array>() {
459 return Value::Float(f.value(row));
460 }
461 if let Some(f) = col.as_any().downcast_ref::<Float32Array>() {
462 return Value::Float(f.value(row) as f64);
463 }
464
465 if let Some(b) = col.as_any().downcast_ref::<BooleanArray>() {
467 return Value::Bool(b.value(row));
468 }
469
470 if let Some(list) = col.as_any().downcast_ref::<FixedSizeListArray>() {
477 let inner = list.value(row);
478 if let Some(floats) = inner.as_any().downcast_ref::<Float32Array>() {
479 return Value::Vector((0..floats.len()).map(|i| floats.value(i)).collect());
480 }
481 if let Some(bytes) = inner.as_any().downcast_ref::<UInt8Array>() {
484 return Value::BinaryVector((0..bytes.len()).map(|i| bytes.value(i)).collect());
485 }
486 let elem_hint = list_child_bytes_hint(list.data_type());
487 return Value::List(array_to_value_list(&inner, elem_hint));
488 }
489
490 if let Some(list) = col.as_any().downcast_ref::<ListArray>() {
492 let arr = list.value(row);
493
494 if let Some(obj) = try_reconstruct_map(&arr) {
496 return Value::Map(obj);
497 }
498
499 let elem_hint = list_child_bytes_hint(list.data_type());
500 return Value::List(array_to_value_list(&arr, elem_hint));
501 }
502
503 if let Some(list) = col.as_any().downcast_ref::<arrow_array::LargeListArray>() {
505 let elem_hint = list_child_bytes_hint(list.data_type());
506 return Value::List(array_to_value_list(&list.value(row), elem_hint));
507 }
508
509 if let Some(s) = col.as_any().downcast_ref::<StructArray>() {
511 if schema::is_sparse_vector_struct(col.data_type()) {
515 if s.is_null(row) {
516 return Value::Null;
517 }
518 if let (Some(idx_list), Some(val_list)) = (
519 s.column_by_name("indices")
520 .and_then(|c| c.as_any().downcast_ref::<ListArray>()),
521 s.column_by_name("values")
522 .and_then(|c| c.as_any().downcast_ref::<ListArray>()),
523 ) {
524 let idx_vals = idx_list.value(row);
525 let val_vals = val_list.value(row);
526 if let (Some(ia), Some(va)) = (
527 idx_vals.as_any().downcast_ref::<UInt32Array>(),
528 val_vals.as_any().downcast_ref::<Float32Array>(),
529 ) {
530 let indices = (0..ia.len()).map(|i| ia.value(i)).collect();
531 let values = (0..va.len()).map(|i| va.value(i)).collect();
532 return Value::SparseVector { indices, values };
533 }
534 }
535 }
536
537 let field_names: Vec<&str> = s.fields().iter().map(|f| f.name().as_str()).collect();
538
539 if field_names.contains(&"nanos_since_epoch")
541 && field_names.contains(&"offset_seconds")
542 && field_names.contains(&"timezone_name")
543 && let (Some(nanos_col), Some(offset_col), Some(tz_col)) = (
544 s.column_by_name("nanos_since_epoch"),
545 s.column_by_name("offset_seconds"),
546 s.column_by_name("timezone_name"),
547 )
548 {
549 let nanos_opt = nanos_col
551 .as_any()
552 .downcast_ref::<TimestampNanosecondArray>()
553 .map(|a| {
554 if a.is_null(row) {
555 None
556 } else {
557 Some(a.value(row))
558 }
559 })
560 .or_else(|| {
561 nanos_col.as_any().downcast_ref::<Int64Array>().map(|a| {
562 if a.is_null(row) {
563 None
564 } else {
565 Some(a.value(row))
566 }
567 })
568 });
569 let offset_opt = offset_col.as_any().downcast_ref::<Int32Array>().map(|a| {
570 if a.is_null(row) {
571 None
572 } else {
573 Some(a.value(row))
574 }
575 });
576
577 if let Some(Some(nanos)) = nanos_opt {
578 match offset_opt {
579 Some(Some(offset)) => {
580 let tz_name = tz_col.as_any().downcast_ref::<StringArray>().and_then(|a| {
581 if a.is_null(row) {
582 None
583 } else {
584 Some(a.value(row).to_string())
585 }
586 });
587 return Value::Temporal(uni_common::TemporalValue::DateTime {
588 nanos_since_epoch: nanos,
589 offset_seconds: offset,
590 timezone_name: tz_name,
591 });
592 }
593 _ => {
594 return Value::Temporal(uni_common::TemporalValue::LocalDateTime {
596 nanos_since_epoch: nanos,
597 });
598 }
599 }
600 }
601 }
602
603 if field_names.contains(&"nanos_since_midnight")
605 && field_names.contains(&"offset_seconds")
606 && let (Some(nanos_col), Some(offset_col)) = (
607 s.column_by_name("nanos_since_midnight"),
608 s.column_by_name("offset_seconds"),
609 )
610 {
611 let nanos_opt = nanos_col
613 .as_any()
614 .downcast_ref::<Time64NanosecondArray>()
615 .map(|a| {
616 if a.is_null(row) {
617 None
618 } else {
619 Some(a.value(row))
620 }
621 })
622 .or_else(|| {
623 nanos_col.as_any().downcast_ref::<Int64Array>().map(|a| {
624 if a.is_null(row) {
625 None
626 } else {
627 Some(a.value(row))
628 }
629 })
630 });
631 let offset_opt = offset_col.as_any().downcast_ref::<Int32Array>().map(|a| {
632 if a.is_null(row) {
633 None
634 } else {
635 Some(a.value(row))
636 }
637 });
638
639 if let (Some(Some(nanos)), Some(Some(offset))) = (nanos_opt, offset_opt) {
640 return Value::Temporal(uni_common::TemporalValue::Time {
641 nanos_since_midnight: nanos,
642 offset_seconds: offset,
643 });
644 }
645 }
646
647 let mut map = HashMap::new();
649 for (field, child) in s.fields().iter().zip(s.columns()) {
650 map.insert(
651 field.name().clone(),
652 arrow_to_value(child.as_ref(), row, None),
653 );
654 }
655 return Value::Map(map);
656 }
657
658 if let Some(d) = col.as_any().downcast_ref::<Date32Array>() {
660 let days = d.value(row);
661 return Value::Temporal(uni_common::TemporalValue::Date {
662 days_since_epoch: days,
663 });
664 }
665
666 if let Some(ts) = col.as_any().downcast_ref::<TimestampNanosecondArray>() {
668 let nanos = ts.value(row);
669 return match ts.timezone() {
670 Some(tz) => Value::Temporal(uni_common::TemporalValue::DateTime {
671 nanos_since_epoch: nanos,
672 offset_seconds: 0,
673 timezone_name: Some(tz.to_string()),
674 }),
675 None => Value::Temporal(uni_common::TemporalValue::LocalDateTime {
676 nanos_since_epoch: nanos,
677 }),
678 };
679 }
680
681 if let Some(t) = col.as_any().downcast_ref::<Time64NanosecondArray>() {
683 let nanos = t.value(row);
684 return Value::Temporal(uni_common::TemporalValue::LocalTime {
685 nanos_since_midnight: nanos,
686 });
687 }
688
689 if let Some(t) = col
691 .as_any()
692 .downcast_ref::<arrow_array::Time64MicrosecondArray>()
693 {
694 let micros = t.value(row);
695 return Value::Temporal(uni_common::TemporalValue::LocalTime {
696 nanos_since_midnight: micros * 1000,
697 });
698 }
699
700 if let Some(d) = col
702 .as_any()
703 .downcast_ref::<arrow_array::DurationMicrosecondArray>()
704 {
705 let micros = d.value(row);
706 let total_nanos = micros * 1000;
707 let seconds = total_nanos / 1_000_000_000;
708 let remaining_nanos = total_nanos % 1_000_000_000;
709 return Value::Temporal(uni_common::TemporalValue::Duration {
710 months: 0,
711 days: 0,
712 nanos: seconds * 1_000_000_000 + remaining_nanos,
713 });
714 }
715
716 if let Some(interval) = col.as_any().downcast_ref::<IntervalMonthDayNanoArray>() {
718 let val = interval.value(row);
719 return Value::Temporal(uni_common::TemporalValue::Duration {
720 months: val.months as i64,
721 days: val.days as i64,
722 nanos: val.nanoseconds,
723 });
724 }
725
726 if let Some(b) = col.as_any().downcast_ref::<LargeBinaryArray>() {
728 let bytes = b.value(row);
729 if bytes.is_empty() {
730 return Value::Null;
731 }
732 return uni_common::cypher_value_codec::decode(bytes).unwrap_or_else(|e| {
733 eprintln!("CypherValue decode error: {}", e);
734 Value::Null
735 });
736 }
737
738 if let Some(fsb) = col.as_any().downcast_ref::<FixedSizeBinaryArray>()
740 && fsb.value_length() == 24
741 {
742 let bytes = fsb.value(row);
743 return match uni_btic::encode::decode_slice(bytes) {
744 Ok(btic) => Value::Temporal(uni_common::TemporalValue::Btic {
745 lo: btic.lo(),
746 hi: btic.hi(),
747 meta: btic.meta(),
748 }),
749 Err(e) => {
750 log::warn!("BTIC decode error: {}", e);
751 Value::Null
752 }
753 };
754 }
755
756 if let Some(b) = col.as_any().downcast_ref::<BinaryArray>() {
758 let bytes = b.value(row);
759 return Crdt::from_msgpack(bytes)
760 .ok()
761 .and_then(|crdt| serde_json::to_value(&crdt).ok())
762 .map(Value::from)
763 .unwrap_or(Value::Null);
764 }
765
766 if *col.data_type() == ArrowDataType::Null {
770 return Value::Null;
771 }
772
773 if matches!(col.data_type(), ArrowDataType::FixedSizeBinary(_)) {
780 return Value::Null;
781 }
782
783 log::warn!(
794 "arrow_to_value: no decoder for Arrow type {:?}; returning Null. \
795 This is a silent wrong answer if the column holds real data — \
796 add a downcast arm for it.",
797 col.data_type()
798 );
799 Value::Null
800}
801
802fn values_to_primitive<T: ArrowPrimitiveType>(
805 values: &[Value],
806 extract: impl Fn(&Value) -> Option<T::Native>,
807) -> ArrayRef {
808 let mut builder = PrimitiveBuilder::<T>::with_capacity(values.len());
809 for v in values {
810 match extract(v) {
811 Some(n) => builder.append_value(n),
812 None => builder.append_null(),
813 }
814 }
815 Arc::new(builder.finish())
816}
817
818fn values_to_uint64_array(values: &[Value]) -> ArrayRef {
819 values_to_primitive::<UInt64Type>(values, Value::as_u64)
820}
821
822fn values_to_int64_array(values: &[Value]) -> ArrayRef {
823 values_to_primitive::<Int64Type>(values, Value::as_i64)
824}
825
826fn values_to_int32_array(values: &[Value]) -> ArrayRef {
827 values_to_primitive::<Int32Type>(values, |v| v.as_i64().map(|n| n as i32))
830}
831
832fn values_to_string_array(values: &[Value]) -> ArrayRef {
833 let mut builder = StringBuilder::with_capacity(values.len(), values.len() * 10);
834 for v in values {
835 if let Some(s) = v.as_str() {
836 builder.append_value(s);
837 } else if v.is_null() {
838 builder.append_null();
839 } else {
840 builder.append_value(v.to_string());
841 }
842 }
843 Arc::new(builder.finish())
844}
845
846fn values_to_bool_array(values: &[Value]) -> ArrayRef {
847 let mut builder = BooleanBuilder::with_capacity(values.len());
848 for v in values {
849 if let Some(b) = v.as_bool() {
850 builder.append_value(b);
851 } else {
852 builder.append_null();
853 }
854 }
855 Arc::new(builder.finish())
856}
857
858fn values_to_float32_array(values: &[Value]) -> ArrayRef {
859 values_to_primitive::<Float32Type>(values, |v| v.as_f64().map(|n| n as f32))
860}
861
862fn values_to_float64_array(values: &[Value]) -> ArrayRef {
863 values_to_primitive::<Float64Type>(values, Value::as_f64)
864}
865
866fn values_to_fixed_size_binary_array(values: &[Value], size: i32) -> Result<ArrayRef> {
867 let mut builder = FixedSizeBinaryBuilder::with_capacity(values.len(), size);
868 for v in values {
869 match v {
870 Value::Temporal(uni_common::TemporalValue::Btic { lo, hi, meta }) if size == 24 => {
871 let btic = uni_btic::Btic::new(*lo, *hi, *meta)
872 .map_err(|e| anyhow!("invalid BTIC value: {}", e))?;
873 builder.append_value(uni_btic::encode::encode(&btic))?;
874 }
875 Value::String(s) if size == 24 => match uni_btic::parse::parse_btic_literal(s) {
876 Ok(b) => builder.append_value(uni_btic::encode::encode(&b))?,
877 Err(_) => builder.append_null(),
878 },
879 Value::List(bytes) => {
880 let b: Vec<u8> = bytes
881 .iter()
882 .map(|bv| bv.as_u64().unwrap_or(0) as u8)
883 .collect();
884 if b.len() as i32 == size {
885 builder.append_value(&b)?;
886 } else {
887 builder.append_null();
888 }
889 }
890 _ => builder.append_null(),
891 }
892 }
893 Ok(Arc::new(builder.finish()))
894}
895
896pub fn extract_vector_f32_values(
911 val: Option<&Value>,
912 is_deleted: bool,
913 dimensions: usize,
914) -> (Vec<f32>, bool) {
915 let zeros = || vec![0.0_f32; dimensions];
916
917 if is_deleted {
919 return (zeros(), true);
920 }
921
922 match val {
923 Some(Value::Vector(v)) if v.len() == dimensions => (v.clone(), true),
925 Some(Value::Vector(_)) => (zeros(), false), Some(Value::List(arr)) if arr.len() == dimensions => {
928 let values: Vec<f32> = arr
929 .iter()
930 .map(|v| v.as_f64().unwrap_or(0.0) as f32)
931 .collect();
932 (values, true)
933 }
934 Some(Value::List(_)) => (zeros(), false), _ => (zeros(), false), }
937}
938
939pub fn extract_vector_f32_values_strict(
953 val: Option<&Value>,
954 is_deleted: bool,
955 dimensions: usize,
956) -> Result<(Vec<f32>, bool)> {
957 if !is_deleted && let Some(v) = val {
958 uni_common::core::schema::check_dense_vector_value(v, dimensions)?;
959 }
960 Ok(extract_vector_f32_values(val, is_deleted, dimensions))
961}
962
963pub fn extract_binary_vector_values(
974 val: Option<&Value>,
975 is_deleted: bool,
976 dimensions: usize,
977) -> (Vec<u8>, bool) {
978 let zeros = || vec![0_u8; dimensions];
979
980 if is_deleted {
981 return (zeros(), true);
982 }
983
984 match val {
985 Some(Value::BinaryVector(b)) if b.len() == dimensions => (b.clone(), true),
986 Some(Value::BinaryVector(_)) => (zeros(), false), Some(Value::List(arr)) if arr.len() == dimensions => {
988 let mut bytes = Vec::with_capacity(dimensions);
989 for v in arr {
990 match v.as_i64() {
991 Some(n @ 0..=255) => bytes.push(n as u8),
992 _ => return (zeros(), false), }
994 }
995 (bytes, true)
996 }
997 Some(Value::List(_)) => (zeros(), false), _ => (zeros(), false), }
1000}
1001
1002pub fn extract_binary_vector_values_strict(
1010 val: Option<&Value>,
1011 is_deleted: bool,
1012 dimensions: usize,
1013) -> Result<(Vec<u8>, bool)> {
1014 if !is_deleted && let Some(v) = val {
1015 uni_common::core::schema::check_binary_vector_value(v, dimensions)?;
1016 }
1017 Ok(extract_binary_vector_values(val, is_deleted, dimensions))
1018}
1019
1020fn values_to_fixed_size_list_f32_array(values: &[Value], size: i32) -> ArrayRef {
1021 let mut builder = FixedSizeListBuilder::new(Float32Builder::new(), size);
1022 for v in values {
1023 let (vals, valid) = extract_vector_f32_values(Some(v), false, size as usize);
1024 for val in vals {
1025 builder.values().append_value(val);
1026 }
1027 builder.append(valid);
1028 }
1029 Arc::new(builder.finish())
1030}
1031
1032fn values_to_timestamp_array(values: &[Value], tz: Option<&Arc<str>>) -> ArrayRef {
1033 let mut builder = TimestampNanosecondBuilder::with_capacity(values.len());
1034 for v in values {
1035 if v.is_null() {
1036 builder.append_null();
1037 } else if let Value::Temporal(tv) = v {
1038 match tv {
1039 uni_common::TemporalValue::DateTime {
1040 nanos_since_epoch, ..
1041 }
1042 | uni_common::TemporalValue::LocalDateTime {
1043 nanos_since_epoch, ..
1044 } => builder.append_value(*nanos_since_epoch),
1045 _ => builder.append_null(),
1046 }
1047 } else if let Some(n) = v.as_i64() {
1048 builder.append_value(n);
1049 } else if let Some(s) = v.as_str() {
1050 match parse_datetime_to_nanos(s) {
1051 Some(nanos) => builder.append_value(nanos),
1052 None => builder.append_null(),
1053 }
1054 } else {
1055 builder.append_null();
1056 }
1057 }
1058
1059 let arr = builder.finish();
1060 if let Some(tz) = tz {
1061 Arc::new(arr.with_timezone(tz.as_ref()))
1062 } else {
1063 Arc::new(arr)
1064 }
1065}
1066
1067fn values_to_datetime_struct_array(values: &[Value]) -> ArrayRef {
1072 let mut nanos_builder = TimestampNanosecondBuilder::with_capacity(values.len());
1073 let mut offset_builder = Int32Builder::with_capacity(values.len());
1074 let mut tz_builder = StringBuilder::with_capacity(values.len(), values.len() * 20);
1075 let mut null_buffer = BooleanBufferBuilder::new(values.len());
1076
1077 for v in values {
1078 match v {
1079 Value::Temporal(uni_common::TemporalValue::DateTime {
1080 nanos_since_epoch,
1081 offset_seconds,
1082 timezone_name,
1083 }) => {
1084 nanos_builder.append_value(*nanos_since_epoch);
1085 offset_builder.append_value(*offset_seconds);
1086 tz_builder.append_option(timezone_name.as_deref());
1087 null_buffer.append(true);
1088 }
1089 Value::Temporal(uni_common::TemporalValue::LocalDateTime { nanos_since_epoch }) => {
1090 nanos_builder.append_value(*nanos_since_epoch);
1091 offset_builder.append_null();
1092 tz_builder.append_null();
1093 null_buffer.append(true);
1094 }
1095 _ => {
1096 nanos_builder.append_null();
1097 offset_builder.append_null();
1098 tz_builder.append_null();
1099 null_buffer.append(false);
1100 }
1101 }
1102 }
1103
1104 let struct_arr = StructArray::new(
1105 schema::datetime_struct_fields(),
1106 vec![
1107 Arc::new(nanos_builder.finish()) as ArrayRef,
1108 Arc::new(offset_builder.finish()) as ArrayRef,
1109 Arc::new(tz_builder.finish()) as ArrayRef,
1110 ],
1111 Some(null_buffer.finish().into()),
1112 );
1113 Arc::new(struct_arr)
1114}
1115
1116fn values_to_time_struct_array(values: &[Value]) -> ArrayRef {
1121 let mut nanos_builder = Time64NanosecondBuilder::with_capacity(values.len());
1122 let mut offset_builder = Int32Builder::with_capacity(values.len());
1123 let mut null_buffer = BooleanBufferBuilder::new(values.len());
1124
1125 for v in values {
1126 match v {
1127 Value::Temporal(uni_common::TemporalValue::Time {
1128 nanos_since_midnight,
1129 offset_seconds,
1130 }) => {
1131 nanos_builder.append_value(*nanos_since_midnight);
1132 offset_builder.append_value(*offset_seconds);
1133 null_buffer.append(true);
1134 }
1135 Value::Temporal(uni_common::TemporalValue::LocalTime {
1136 nanos_since_midnight,
1137 }) => {
1138 nanos_builder.append_value(*nanos_since_midnight);
1139 offset_builder.append_null();
1140 null_buffer.append(true);
1141 }
1142 _ => {
1143 nanos_builder.append_null();
1144 offset_builder.append_null();
1145 null_buffer.append(false);
1146 }
1147 }
1148 }
1149
1150 let struct_arr = StructArray::new(
1151 schema::time_struct_fields(),
1152 vec![
1153 Arc::new(nanos_builder.finish()) as ArrayRef,
1154 Arc::new(offset_builder.finish()) as ArrayRef,
1155 ],
1156 Some(null_buffer.finish().into()),
1157 );
1158 Arc::new(struct_arr)
1159}
1160
1161pub(crate) fn values_to_point_struct_array(values: &[Value], point_type: PointType) -> ArrayRef {
1170 use arrow_array::builder::Float64Builder;
1171
1172 let n = values.len();
1173 let fields = match schema::DataType::Point(point_type).to_arrow() {
1174 ArrowDataType::Struct(f) => f,
1175 _ => unreachable!("Point maps to an Arrow struct"),
1177 };
1178
1179 let coord = |v: &Value, key: &str| -> Option<f64> {
1181 v.as_object()
1182 .and_then(|m| m.get(key))
1183 .and_then(Value::as_f64)
1184 };
1185 let crs_of = |v: &Value, default: &str| -> String {
1186 v.as_object()
1187 .and_then(|m| m.get("crs"))
1188 .and_then(|c| c.as_str())
1189 .unwrap_or(default)
1190 .to_string()
1191 };
1192
1193 let mut null_buffer = BooleanBufferBuilder::new(n);
1194 let mut crs_b = StringBuilder::with_capacity(n, n * 8);
1195
1196 let (keys, default_crs): (&[&str], &str) = match point_type {
1198 PointType::Geographic => (&["latitude", "longitude"], "WGS84"),
1199 PointType::Cartesian2D => (&["x", "y"], "cartesian"),
1200 PointType::Cartesian3D => (&["x", "y", "z"], "cartesian-3d"),
1201 };
1202 let mut coord_builders: Vec<Float64Builder> = keys
1203 .iter()
1204 .map(|_| Float64Builder::with_capacity(n))
1205 .collect();
1206
1207 for v in values {
1208 let coords: Option<Vec<f64>> = keys.iter().map(|k| coord(v, k)).collect();
1209 match coords {
1210 Some(cs) => {
1211 for (b, c) in coord_builders.iter_mut().zip(cs) {
1212 b.append_value(c);
1213 }
1214 crs_b.append_value(crs_of(v, default_crs));
1215 null_buffer.append(true);
1216 }
1217 None => {
1218 for b in &mut coord_builders {
1219 b.append_value(0.0);
1220 }
1221 crs_b.append_value(default_crs);
1222 null_buffer.append(false);
1223 }
1224 }
1225 }
1226
1227 let mut columns: Vec<ArrayRef> = coord_builders
1228 .into_iter()
1229 .map(|mut b| Arc::new(b.finish()) as ArrayRef)
1230 .collect();
1231 columns.push(Arc::new(crs_b.finish()) as ArrayRef);
1232
1233 Arc::new(StructArray::new(
1234 fields,
1235 columns,
1236 Some(null_buffer.finish().into()),
1237 ))
1238}
1239
1240fn values_to_large_binary_array(values: &[Value]) -> ArrayRef {
1241 let mut builder =
1242 arrow_array::builder::LargeBinaryBuilder::with_capacity(values.len(), values.len() * 64);
1243 for v in values {
1244 if v.is_null() {
1245 builder.append_null();
1246 } else {
1247 let cv_bytes = uni_common::cypher_value_codec::encode(v);
1249 builder.append_value(&cv_bytes);
1250 }
1251 }
1252 Arc::new(builder.finish())
1253}
1254
1255pub fn values_to_array(values: &[Value], dt: &ArrowDataType) -> Result<ArrayRef> {
1264 match dt {
1265 ArrowDataType::UInt64 => Ok(values_to_uint64_array(values)),
1266 ArrowDataType::Int64 => Ok(values_to_int64_array(values)),
1267 ArrowDataType::Int32 => Ok(values_to_int32_array(values)),
1268 ArrowDataType::Utf8 => Ok(values_to_string_array(values)),
1269 ArrowDataType::Boolean => Ok(values_to_bool_array(values)),
1270 ArrowDataType::Float32 => Ok(values_to_float32_array(values)),
1271 ArrowDataType::Float64 => Ok(values_to_float64_array(values)),
1272 ArrowDataType::FixedSizeBinary(size) => values_to_fixed_size_binary_array(values, *size),
1273 ArrowDataType::FixedSizeList(inner, size) => {
1274 if inner.data_type() == &ArrowDataType::Float32 {
1275 Ok(values_to_fixed_size_list_f32_array(values, *size))
1276 } else {
1277 Err(anyhow!("Unsupported FixedSizeList inner type"))
1278 }
1279 }
1280 ArrowDataType::Timestamp(arrow_schema::TimeUnit::Nanosecond, tz) => {
1281 Ok(values_to_timestamp_array(values, tz.as_ref()))
1282 }
1283 ArrowDataType::Timestamp(arrow_schema::TimeUnit::Microsecond, tz) => {
1284 Ok(values_to_timestamp_array(values, tz.as_ref()))
1285 }
1286 ArrowDataType::Date32 => {
1287 let mut builder = Date32Builder::with_capacity(values.len());
1288 for v in values {
1289 if v.is_null() {
1290 builder.append_null();
1291 } else if let Value::Temporal(uni_common::TemporalValue::Date {
1292 days_since_epoch,
1293 }) = v
1294 {
1295 builder.append_value(*days_since_epoch);
1296 } else if let Some(n) = v.as_i64() {
1297 builder.append_value(n as i32);
1298 } else {
1299 builder.append_null();
1300 }
1301 }
1302 Ok(Arc::new(builder.finish()))
1303 }
1304 ArrowDataType::Time64(arrow_schema::TimeUnit::Nanosecond) => {
1305 let mut builder = Time64NanosecondBuilder::with_capacity(values.len());
1306 for v in values {
1307 if v.is_null() {
1308 builder.append_null();
1309 } else if let Value::Temporal(tv) = v {
1310 match tv {
1311 uni_common::TemporalValue::LocalTime {
1312 nanos_since_midnight,
1313 }
1314 | uni_common::TemporalValue::Time {
1315 nanos_since_midnight,
1316 ..
1317 } => builder.append_value(*nanos_since_midnight),
1318 _ => builder.append_null(),
1319 }
1320 } else if let Some(n) = v.as_i64() {
1321 builder.append_value(n);
1322 } else {
1323 builder.append_null();
1324 }
1325 }
1326 Ok(Arc::new(builder.finish()))
1327 }
1328 ArrowDataType::Time64(arrow_schema::TimeUnit::Microsecond) => {
1329 let mut builder = Time64MicrosecondBuilder::with_capacity(values.len());
1330 for v in values {
1331 if v.is_null() {
1332 builder.append_null();
1333 } else if let Value::Temporal(tv) = v {
1334 match tv {
1335 uni_common::TemporalValue::LocalTime {
1336 nanos_since_midnight,
1337 }
1338 | uni_common::TemporalValue::Time {
1339 nanos_since_midnight,
1340 ..
1341 } => builder.append_value(*nanos_since_midnight / 1_000), _ => builder.append_null(),
1343 }
1344 } else if let Some(n) = v.as_i64() {
1345 builder.append_value(n);
1346 } else {
1347 builder.append_null();
1348 }
1349 }
1350 Ok(Arc::new(builder.finish()))
1351 }
1352 ArrowDataType::Interval(arrow_schema::IntervalUnit::MonthDayNano) => {
1353 let mut builder = IntervalMonthDayNanoBuilder::with_capacity(values.len());
1354 for v in values {
1355 if v.is_null() {
1356 builder.append_null();
1357 } else if let Value::Temporal(uni_common::TemporalValue::Duration {
1358 months,
1359 days,
1360 nanos,
1361 }) = v
1362 {
1363 builder.append_value(arrow::datatypes::IntervalMonthDayNano {
1364 months: *months as i32,
1365 days: *days as i32,
1366 nanoseconds: *nanos,
1367 });
1368 } else {
1369 builder.append_null();
1370 }
1371 }
1372 Ok(Arc::new(builder.finish()))
1373 }
1374 ArrowDataType::Duration(arrow_schema::TimeUnit::Microsecond) => {
1375 let mut builder = DurationMicrosecondBuilder::with_capacity(values.len());
1376 for v in values {
1377 if v.is_null() {
1378 builder.append_null();
1379 } else if let Value::Temporal(uni_common::TemporalValue::Duration {
1380 months,
1381 days,
1382 nanos,
1383 }) = v
1384 {
1385 let total_micros =
1386 months * 30 * 86_400_000_000i64 + days * 86_400_000_000i64 + nanos / 1_000;
1387 builder.append_value(total_micros);
1388 } else if let Some(n) = v.as_i64() {
1389 builder.append_value(n);
1390 } else {
1391 builder.append_null();
1392 }
1393 }
1394 Ok(Arc::new(builder.finish()))
1395 }
1396 ArrowDataType::LargeBinary => Ok(values_to_large_binary_array(values)),
1397 ArrowDataType::List(field) => {
1398 if field.data_type() == &ArrowDataType::Utf8 {
1399 let mut builder = ListBuilder::new(StringBuilder::new());
1400 for v in values {
1401 if let Value::List(arr) = v {
1402 for item in arr {
1403 if let Some(s) = item.as_str() {
1404 builder.values().append_value(s);
1405 } else {
1406 builder.values().append_null();
1407 }
1408 }
1409 builder.append(true);
1410 } else {
1411 builder.append_null();
1412 }
1413 }
1414 Ok(Arc::new(builder.finish()))
1415 } else {
1416 Err(anyhow!(
1417 "Unsupported List inner type: {:?}",
1418 field.data_type()
1419 ))
1420 }
1421 }
1422 ArrowDataType::Struct(_) if schema::is_datetime_struct(dt) => {
1423 Ok(values_to_datetime_struct_array(values))
1424 }
1425 ArrowDataType::Struct(_) if schema::is_time_struct(dt) => {
1426 Ok(values_to_time_struct_array(values))
1427 }
1428 _ => Err(anyhow!("Unsupported type for conversion: {:?}", dt)),
1429 }
1430}
1431
1432pub struct PropertyExtractor<'a> {
1434 name: &'a str,
1436 data_type: &'a DataType,
1437}
1438
1439fn sparse_pair_from_value(v: &Value) -> Option<(Vec<u32>, Vec<f32>)> {
1448 match v {
1449 Value::SparseVector { indices, values } => {
1450 if indices.len() != values.len() {
1456 return None;
1457 }
1458 Some((indices.clone(), values.clone()))
1459 }
1460 Value::Map(m) => {
1461 let idx = match m.get("indices") {
1462 Some(Value::List(l)) => l,
1463 _ => return None,
1464 };
1465 let vals = match m.get("values") {
1466 Some(Value::List(l)) => l,
1467 _ => return None,
1468 };
1469 let indices: Vec<u32> = idx
1470 .iter()
1471 .map(|x| x.as_u64().map(|n| n as u32))
1472 .collect::<Option<_>>()?;
1473 let values: Vec<f32> = vals
1474 .iter()
1475 .map(|x| x.as_f64().map(|n| n as f32))
1476 .collect::<Option<_>>()?;
1477 if indices.len() != values.len() {
1478 return None;
1479 }
1480 Some((indices, values))
1481 }
1482 _ => None,
1483 }
1484}
1485
1486pub fn build_sparse_vector_array(values: &[Option<Value>]) -> ArrayRef {
1492 let mut indices_builder = ListBuilder::new(UInt32Builder::new());
1493 let mut values_builder = ListBuilder::new(Float32Builder::new());
1494 let mut null_buffer = BooleanBufferBuilder::new(values.len());
1495 for v in values {
1496 match v.as_ref().and_then(sparse_pair_from_value) {
1497 Some((indices, vals)) => {
1498 for ix in indices {
1499 indices_builder.values().append_value(ix);
1500 }
1501 indices_builder.append(true);
1502 for w in vals {
1503 values_builder.values().append_value(w);
1504 }
1505 values_builder.append(true);
1506 null_buffer.append(true);
1507 }
1508 None => {
1509 indices_builder.append(true);
1510 values_builder.append(true);
1511 null_buffer.append(false);
1512 }
1513 }
1514 }
1515 let struct_arr = StructArray::new(
1516 schema::sparse_vector_struct_fields(),
1517 vec![
1518 Arc::new(indices_builder.finish()) as ArrayRef,
1519 Arc::new(values_builder.finish()) as ArrayRef,
1520 ],
1521 Some(null_buffer.finish().into()),
1522 );
1523 Arc::new(struct_arr)
1524}
1525
1526pub fn build_multivector_array(values: &[Option<Value>], dimensions: usize) -> ArrayRef {
1540 let dim = dimensions as i32;
1541 let mut builder = ListBuilder::new(FixedSizeListBuilder::new(Float32Builder::new(), dim));
1542 for v in values {
1543 match v.as_ref().and_then(|v| v.as_array()) {
1544 Some(arr) => {
1545 for tok in arr {
1549 let (vals, valid) = extract_vector_f32_values(Some(tok), false, dimensions);
1550 for f in vals {
1551 builder.values().values().append_value(f);
1552 }
1553 builder.values().append(valid);
1554 }
1555 builder.append(true);
1556 }
1557 None => builder.append_null(),
1558 }
1559 }
1560 Arc::new(builder.finish())
1561}
1562
1563fn collect_scalar<T: Copy>(
1569 len: usize,
1570 deleted: &[bool],
1571 extract: impl Fn(usize) -> Option<T>,
1572 deleted_placeholder: T,
1573) -> Vec<Option<T>> {
1574 let mut values = Vec::with_capacity(len);
1575 for (i, &is_deleted) in deleted.iter().enumerate().take(len) {
1576 values.push(extract(i).or(is_deleted.then_some(deleted_placeholder)));
1577 }
1578 values
1579}
1580
1581impl<'a> PropertyExtractor<'a> {
1582 pub fn new(name: &'a str, data_type: &'a DataType) -> Self {
1583 Self { name, data_type }
1584 }
1585
1586 pub fn build_column<F>(&self, len: usize, deleted: &[bool], get_props: F) -> Result<ArrayRef>
1589 where
1590 F: Fn(usize) -> Option<&'a Value>,
1591 {
1592 match self.data_type {
1593 DataType::String => self.build_string_column(len, deleted, get_props),
1594 DataType::Int32 => self.build_int32_column(len, deleted, get_props),
1595 DataType::Int64 => self.build_int64_column(len, deleted, get_props),
1596 DataType::Float32 => self.build_float32_column(len, deleted, get_props),
1597 DataType::Float64 => self.build_float64_column(len, deleted, get_props),
1598 DataType::Bool => self.build_bool_column(len, deleted, get_props),
1599 DataType::Vector { dimensions } => {
1600 self.build_vector_column(len, deleted, get_props, *dimensions)
1601 }
1602 DataType::SparseVector { .. } => {
1603 self.build_sparse_vector_column(len, deleted, get_props)
1604 }
1605 DataType::BinaryVector { dimensions } => {
1606 self.build_binary_vector_column(len, deleted, get_props, *dimensions)
1607 }
1608 DataType::CypherValue => self.build_json_column(len, deleted, get_props),
1609 DataType::Bytes => self.build_bytes_column(len, deleted, get_props),
1610 DataType::List(inner) => self.build_list_column(len, deleted, get_props, inner),
1611 DataType::Map(key, value) => self.build_map_column(len, deleted, get_props, key, value),
1612 DataType::Crdt(_) => self.build_crdt_column(len, deleted, get_props),
1613 DataType::DateTime => self.build_datetime_struct_column(len, deleted, get_props),
1614 DataType::Timestamp => self.build_timestamp_column(len, deleted, get_props),
1615 DataType::Date => self.build_date32_column(len, deleted, get_props),
1616 DataType::Time => self.build_time_struct_column(len, deleted, get_props),
1617 DataType::Point(pt) => self.build_point_struct_column(len, deleted, get_props, *pt),
1618 DataType::Duration => self.build_duration_column(len, deleted, get_props),
1619 DataType::Btic => self.build_btic_column(len, deleted, get_props),
1620 _ => Err(anyhow!(
1621 "Unsupported data type for arrow conversion: {:?}",
1622 self.data_type
1623 )),
1624 }
1625 }
1626
1627 fn build_string_column<F>(&self, len: usize, deleted: &[bool], get_props: F) -> Result<ArrayRef>
1628 where
1629 F: Fn(usize) -> Option<&'a Value>,
1630 {
1631 let mut builder = arrow_array::builder::StringBuilder::with_capacity(len, len * 32);
1632 for (i, &is_deleted) in deleted.iter().enumerate().take(len) {
1633 let prop = get_props(i);
1634 if let Some(s) = prop.and_then(|v| v.as_str()) {
1635 builder.append_value(s);
1636 } else if let Some(Value::Temporal(tv)) = prop {
1637 builder.append_value(tv.to_string());
1638 } else if is_deleted {
1639 builder.append_value("");
1640 } else {
1641 builder.append_null();
1642 }
1643 }
1644 Ok(Arc::new(builder.finish()))
1645 }
1646
1647 fn build_int32_column<F>(&self, len: usize, deleted: &[bool], get_props: F) -> Result<ArrayRef>
1648 where
1649 F: Fn(usize) -> Option<&'a Value>,
1650 {
1651 let values = collect_scalar(
1652 len,
1653 deleted,
1654 |i| {
1657 get_props(i)
1658 .and_then(|v| v.as_i64())
1659 .and_then(|v| i32::try_from(v).ok())
1660 },
1661 0,
1662 );
1663 Ok(Arc::new(Int32Array::from(values)))
1664 }
1665
1666 fn build_int64_column<F>(&self, len: usize, deleted: &[bool], get_props: F) -> Result<ArrayRef>
1667 where
1668 F: Fn(usize) -> Option<&'a Value>,
1669 {
1670 let values = collect_scalar(len, deleted, |i| get_props(i).and_then(|v| v.as_i64()), 0);
1671 Ok(Arc::new(Int64Array::from(values)))
1672 }
1673
1674 fn build_timestamp_column<F>(
1675 &self,
1676 len: usize,
1677 deleted: &[bool],
1678 get_props: F,
1679 ) -> Result<ArrayRef>
1680 where
1681 F: Fn(usize) -> Option<&'a Value>,
1682 {
1683 let mut values = Vec::with_capacity(len);
1684 for (i, &is_deleted) in deleted.iter().enumerate().take(len) {
1685 let val = get_props(i);
1686 let ts = if let Some(Value::Temporal(tv)) = val {
1691 match tv {
1692 uni_common::TemporalValue::DateTime {
1693 nanos_since_epoch, ..
1694 }
1695 | uni_common::TemporalValue::LocalDateTime {
1696 nanos_since_epoch, ..
1697 } => Some(*nanos_since_epoch),
1698 _ => None,
1699 }
1700 } else if let Some(v) = val.and_then(|v| v.as_i64()) {
1701 Some(v)
1702 } else if let Some(s) = val.and_then(|v| v.as_str()) {
1703 parse_datetime_to_nanos(s)
1704 } else {
1705 None
1706 };
1707
1708 if is_deleted {
1709 values.push(Some(0));
1710 } else {
1711 values.push(ts);
1712 }
1713 }
1714 let arr = TimestampNanosecondArray::from(values).with_timezone("UTC");
1715 Ok(Arc::new(arr))
1716 }
1717
1718 fn build_datetime_struct_column<F>(
1719 &self,
1720 len: usize,
1721 deleted: &[bool],
1722 get_props: F,
1723 ) -> Result<ArrayRef>
1724 where
1725 F: Fn(usize) -> Option<&'a Value>,
1726 {
1727 let values = self.collect_values_or_null(len, deleted, &get_props);
1728 Ok(values_to_datetime_struct_array(&values))
1729 }
1730
1731 fn build_time_struct_column<F>(
1732 &self,
1733 len: usize,
1734 deleted: &[bool],
1735 get_props: F,
1736 ) -> Result<ArrayRef>
1737 where
1738 F: Fn(usize) -> Option<&'a Value>,
1739 {
1740 let values = self.collect_values_or_null(len, deleted, &get_props);
1741 Ok(values_to_time_struct_array(&values))
1742 }
1743
1744 fn build_point_struct_column<F>(
1745 &self,
1746 len: usize,
1747 deleted: &[bool],
1748 get_props: F,
1749 point_type: PointType,
1750 ) -> Result<ArrayRef>
1751 where
1752 F: Fn(usize) -> Option<&'a Value>,
1753 {
1754 let values = self.collect_values_or_null(len, deleted, &get_props);
1755 Ok(values_to_point_struct_array(&values, point_type))
1756 }
1757
1758 fn collect_values_or_null<F>(&self, len: usize, deleted: &[bool], get_props: &F) -> Vec<Value>
1760 where
1761 F: Fn(usize) -> Option<&'a Value>,
1762 {
1763 deleted
1764 .iter()
1765 .enumerate()
1766 .take(len)
1767 .map(|(i, &is_deleted)| {
1768 if is_deleted {
1769 Value::Null
1770 } else {
1771 get_props(i).cloned().unwrap_or(Value::Null)
1772 }
1773 })
1774 .collect()
1775 }
1776
1777 fn build_date32_column<F>(&self, len: usize, deleted: &[bool], get_props: F) -> Result<ArrayRef>
1778 where
1779 F: Fn(usize) -> Option<&'a Value>,
1780 {
1781 let mut builder = Date32Builder::with_capacity(len);
1782 let epoch = chrono::NaiveDate::from_ymd_opt(1970, 1, 1).unwrap();
1783
1784 for (i, &is_deleted) in deleted.iter().enumerate().take(len) {
1785 let val = get_props(i);
1786 let days = if let Some(Value::Temporal(uni_common::TemporalValue::Date {
1789 days_since_epoch,
1790 })) = val
1791 {
1792 Some(*days_since_epoch)
1793 } else if let Some(v) = val.and_then(|v| v.as_i64()) {
1794 i32::try_from(v).ok()
1797 } else if let Some(s) = val.and_then(|v| v.as_str()) {
1798 match chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d") {
1799 Ok(date) => Some(date.signed_duration_since(epoch).num_days() as i32),
1800 Err(_) => None,
1801 }
1802 } else {
1803 None
1804 };
1805
1806 if is_deleted {
1807 builder.append_value(0);
1808 } else if let Some(v) = days {
1809 builder.append_value(v);
1810 } else {
1811 builder.append_null();
1812 }
1813 }
1814 Ok(Arc::new(builder.finish()))
1815 }
1816
1817 fn build_duration_column<F>(
1818 &self,
1819 len: usize,
1820 deleted: &[bool],
1821 get_props: F,
1822 ) -> Result<ArrayRef>
1823 where
1824 F: Fn(usize) -> Option<&'a Value>,
1825 {
1826 let mut builder = LargeBinaryBuilder::with_capacity(len, len * 32);
1828 for (i, &is_deleted) in deleted.iter().enumerate().take(len) {
1829 let raw_val = get_props(i);
1830 if let Some(val @ Value::Temporal(uni_common::TemporalValue::Duration { .. })) = raw_val
1831 {
1832 let encoded = uni_common::cypher_value_codec::encode(val);
1833 builder.append_value(&encoded);
1834 } else if is_deleted {
1835 let zero = Value::Temporal(uni_common::TemporalValue::Duration {
1836 months: 0,
1837 days: 0,
1838 nanos: 0,
1839 });
1840 let encoded = uni_common::cypher_value_codec::encode(&zero);
1841 builder.append_value(&encoded);
1842 } else {
1843 builder.append_null();
1844 }
1845 }
1846 Ok(Arc::new(builder.finish()))
1847 }
1848
1849 fn build_btic_column<F>(&self, len: usize, deleted: &[bool], get_props: F) -> Result<ArrayRef>
1850 where
1851 F: Fn(usize) -> Option<&'a Value>,
1852 {
1853 const ENCODED_LEN: i32 = 24;
1854 let mut builder = FixedSizeBinaryBuilder::with_capacity(len, ENCODED_LEN);
1855 for (i, &is_deleted) in deleted.iter().enumerate().take(len) {
1856 let raw_val = get_props(i);
1857 let btic = match raw_val {
1858 Some(Value::Temporal(uni_common::TemporalValue::Btic { lo, hi, meta })) => Some(
1859 uni_btic::Btic::new(*lo, *hi, *meta)
1860 .map_err(|e| anyhow!("invalid BTIC value: {}", e))?,
1861 ),
1862 Some(Value::String(s)) => Some(
1863 uni_btic::parse::parse_btic_literal(s)
1864 .map_err(|e| anyhow!("BTIC parse error for '{}': {}", s, e))?,
1865 ),
1866 _ => None,
1867 };
1868
1869 if let Some(b) = btic {
1870 builder.append_value(uni_btic::encode::encode(&b))?;
1871 } else if is_deleted {
1872 builder.append_value([0u8; ENCODED_LEN as usize])?;
1873 } else {
1874 builder.append_null();
1875 }
1876 }
1877 Ok(Arc::new(builder.finish()))
1878 }
1879
1880 fn build_float32_column<F>(
1881 &self,
1882 len: usize,
1883 deleted: &[bool],
1884 get_props: F,
1885 ) -> Result<ArrayRef>
1886 where
1887 F: Fn(usize) -> Option<&'a Value>,
1888 {
1889 let values = collect_scalar(
1890 len,
1891 deleted,
1892 |i| get_props(i).and_then(|v| v.as_f64()).map(|v| v as f32),
1893 0.0,
1894 );
1895 Ok(Arc::new(Float32Array::from(values)))
1896 }
1897
1898 fn build_float64_column<F>(
1899 &self,
1900 len: usize,
1901 deleted: &[bool],
1902 get_props: F,
1903 ) -> Result<ArrayRef>
1904 where
1905 F: Fn(usize) -> Option<&'a Value>,
1906 {
1907 let values = collect_scalar(len, deleted, |i| get_props(i).and_then(|v| v.as_f64()), 0.0);
1908 Ok(Arc::new(Float64Array::from(values)))
1909 }
1910
1911 fn build_bool_column<F>(&self, len: usize, deleted: &[bool], get_props: F) -> Result<ArrayRef>
1912 where
1913 F: Fn(usize) -> Option<&'a Value>,
1914 {
1915 let values = collect_scalar(
1916 len,
1917 deleted,
1918 |i| get_props(i).and_then(|v| v.as_bool()),
1919 false,
1920 );
1921 Ok(Arc::new(BooleanArray::from(values)))
1922 }
1923
1924 fn build_vector_column<F>(
1925 &self,
1926 len: usize,
1927 deleted: &[bool],
1928 get_props: F,
1929 dimensions: usize,
1930 ) -> Result<ArrayRef>
1931 where
1932 F: Fn(usize) -> Option<&'a Value>,
1933 {
1934 let mut builder = FixedSizeListBuilder::new(Float32Builder::new(), dimensions as i32);
1935
1936 for (i, &is_deleted) in deleted.iter().enumerate().take(len) {
1937 let val = get_props(i);
1938 let (values, valid) = extract_vector_f32_values_strict(val, is_deleted, dimensions)
1942 .map_err(|e| {
1943 anyhow!(
1944 "flush: property '{}' row {}: {}; wrong-dimension vectors are rejected \
1945 at write time as of issue #137 — this value was likely written by an \
1946 older version",
1947 self.name,
1948 i,
1949 e
1950 )
1951 })?;
1952 for v in values {
1953 builder.values().append_value(v);
1954 }
1955 builder.append(valid);
1956 }
1957 Ok(Arc::new(builder.finish()))
1958 }
1959
1960 fn build_binary_vector_column<F>(
1968 &self,
1969 len: usize,
1970 deleted: &[bool],
1971 get_props: F,
1972 dimensions: usize,
1973 ) -> Result<ArrayRef>
1974 where
1975 F: Fn(usize) -> Option<&'a Value>,
1976 {
1977 let mut builder = FixedSizeListBuilder::new(UInt8Builder::new(), dimensions as i32);
1978
1979 for (i, &is_deleted) in deleted.iter().enumerate().take(len) {
1980 let val = get_props(i);
1981 let (values, valid) = extract_binary_vector_values_strict(val, is_deleted, dimensions)
1982 .map_err(|e| {
1983 anyhow!(
1984 "flush: property '{}' row {}: {}; wrong-shape binary vectors are \
1985 rejected at write time — this value was likely written by an older \
1986 version",
1987 self.name,
1988 i,
1989 e
1990 )
1991 })?;
1992 for v in values {
1993 builder.values().append_value(v);
1994 }
1995 builder.append(valid);
1996 }
1997 Ok(Arc::new(builder.finish()))
1998 }
1999
2000 fn build_sparse_vector_column<F>(
2006 &self,
2007 len: usize,
2008 deleted: &[bool],
2009 get_props: F,
2010 ) -> Result<ArrayRef>
2011 where
2012 F: Fn(usize) -> Option<&'a Value>,
2013 {
2014 let mut indices_builder = ListBuilder::new(UInt32Builder::new());
2015 let mut values_builder = ListBuilder::new(Float32Builder::new());
2016 let mut null_buffer = BooleanBufferBuilder::new(len);
2017
2018 for (i, &is_deleted) in deleted.iter().enumerate().take(len) {
2019 let pair = if is_deleted {
2020 None
2021 } else {
2022 get_props(i).and_then(sparse_pair_from_value)
2023 };
2024 match pair {
2025 Some((indices, values)) => {
2026 for ix in indices {
2027 indices_builder.values().append_value(ix);
2028 }
2029 indices_builder.append(true);
2030 for w in values {
2031 values_builder.values().append_value(w);
2032 }
2033 values_builder.append(true);
2034 null_buffer.append(true);
2035 }
2036 None => {
2037 indices_builder.append(true);
2038 values_builder.append(true);
2039 null_buffer.append(false);
2040 }
2041 }
2042 }
2043
2044 let struct_arr = StructArray::new(
2045 schema::sparse_vector_struct_fields(),
2046 vec![
2047 Arc::new(indices_builder.finish()) as ArrayRef,
2048 Arc::new(values_builder.finish()) as ArrayRef,
2049 ],
2050 Some(null_buffer.finish().into()),
2051 );
2052 Ok(Arc::new(struct_arr))
2053 }
2054
2055 fn build_json_column<F>(&self, len: usize, deleted: &[bool], get_props: F) -> Result<ArrayRef>
2056 where
2057 F: Fn(usize) -> Option<&'a Value>,
2058 {
2059 let null_val = Value::Null;
2060 let mut builder = arrow_array::builder::LargeBinaryBuilder::with_capacity(len, len * 64);
2061 for (i, &is_deleted) in deleted.iter().enumerate().take(len) {
2062 let val = get_props(i);
2063 let uni_val = if val.is_none() && is_deleted {
2064 &null_val
2065 } else {
2066 val.unwrap_or(&null_val)
2067 };
2068 let cv_bytes = uni_common::cypher_value_codec::encode(uni_val);
2070 builder.append_value(&cv_bytes);
2071 }
2072 Ok(Arc::new(builder.finish()))
2073 }
2074
2075 fn build_bytes_column<F>(&self, len: usize, deleted: &[bool], get_props: F) -> Result<ArrayRef>
2076 where
2077 F: Fn(usize) -> Option<&'a Value>,
2078 {
2079 let mut builder = LargeBinaryBuilder::with_capacity(len, len * 64);
2080 for (i, &is_deleted) in deleted.iter().enumerate().take(len) {
2081 let val = get_props(i);
2082 if let Some(Value::Bytes(b)) = val {
2083 builder.append_value(b);
2084 } else if is_deleted {
2085 builder.append_value(&[][..]);
2086 } else {
2087 builder.append_null();
2088 }
2089 }
2090 Ok(Arc::new(builder.finish()))
2091 }
2092
2093 fn build_list_column<F>(
2094 &self,
2095 len: usize,
2096 deleted: &[bool],
2097 get_props: F,
2098 inner: &DataType,
2099 ) -> Result<ArrayRef>
2100 where
2101 F: Fn(usize) -> Option<&'a Value>,
2102 {
2103 match inner {
2104 DataType::String => {
2105 self.build_typed_list(len, deleted, &get_props, StringBuilder::new(), |v, b| {
2106 if let Some(s) = v.as_str() {
2107 b.append_value(s);
2108 } else {
2109 b.append_null();
2110 }
2111 })
2112 }
2113 DataType::Int64 => {
2114 self.build_typed_list(len, deleted, &get_props, Int64Builder::new(), |v, b| {
2115 if let Some(n) = v.as_i64() {
2116 b.append_value(n);
2117 } else {
2118 b.append_null();
2119 }
2120 })
2121 }
2122 DataType::Float64 => {
2123 self.build_typed_list(len, deleted, &get_props, Float64Builder::new(), |v, b| {
2124 if let Some(f) = v.as_f64() {
2125 b.append_value(f);
2126 } else {
2127 b.append_null();
2128 }
2129 })
2130 }
2131 DataType::Bytes => {
2132 let item_field = Arc::new(
2136 Field::new("item", ArrowDataType::LargeBinary, true)
2137 .with_metadata(schema::raw_bytes_field_metadata()),
2138 );
2139 let mut builder =
2140 ListBuilder::new(LargeBinaryBuilder::new()).with_field(item_field);
2141 for (i, &is_deleted) in deleted.iter().enumerate().take(len) {
2142 let val_array = get_props(i).and_then(|v| v.as_array());
2143 if val_array.is_none() && is_deleted {
2144 builder.append_null();
2145 } else if let Some(arr) = val_array {
2146 for v in arr {
2147 if let Value::Bytes(b) = v {
2148 builder.values().append_value(b);
2149 } else {
2150 builder.values().append_null();
2151 }
2152 }
2153 builder.append(true);
2154 } else {
2155 builder.append_null();
2156 }
2157 }
2158 Ok(Arc::new(builder.finish()))
2159 }
2160 DataType::Vector { dimensions } => {
2161 let dim = *dimensions as i32;
2172 let mut builder =
2173 ListBuilder::new(FixedSizeListBuilder::new(Float32Builder::new(), dim));
2174 for (i, &is_deleted) in deleted.iter().enumerate().take(len) {
2175 let raw = get_props(i);
2176 if !is_deleted && let Some(v) = raw {
2182 self.data_type.check_vector_dims(v).map_err(|e| {
2183 anyhow!(
2184 "flush: property '{}' row {}: {}; wrong-dimension vectors \
2185 are rejected at write time as of issue #137 — this value \
2186 was likely written by an older version",
2187 self.name,
2188 i,
2189 e
2190 )
2191 })?;
2192 }
2193 let val_array = raw.and_then(|v| v.as_array());
2194 if val_array.is_none() && is_deleted {
2195 builder.append_null();
2196 } else if let Some(arr) = val_array {
2197 for tok in arr {
2202 let (vals, valid) =
2203 extract_vector_f32_values(Some(tok), false, *dimensions);
2204 for v in vals {
2205 builder.values().values().append_value(v);
2206 }
2207 builder.values().append(valid);
2208 }
2209 builder.append(true);
2210 } else {
2211 builder.append_null();
2212 }
2213 }
2214 Ok(Arc::new(builder.finish()))
2215 }
2216 _ => Err(anyhow!("Unsupported inner type for List: {:?}", inner)),
2217 }
2218 }
2219
2220 fn build_typed_list<F, B, A>(
2222 &self,
2223 len: usize,
2224 deleted: &[bool],
2225 get_props: &F,
2226 inner_builder: B,
2227 mut append_value: A,
2228 ) -> Result<ArrayRef>
2229 where
2230 F: Fn(usize) -> Option<&'a Value>,
2231 B: arrow_array::builder::ArrayBuilder,
2232 A: FnMut(&Value, &mut B),
2233 {
2234 let mut builder = ListBuilder::new(inner_builder);
2235 for (i, &is_deleted) in deleted.iter().enumerate().take(len) {
2236 let val_array = get_props(i).and_then(|v| v.as_array());
2237 if val_array.is_none() && is_deleted {
2238 builder.append_null();
2239 } else if let Some(arr) = val_array {
2240 for v in arr {
2241 append_value(v, builder.values());
2242 }
2243 builder.append(true);
2244 } else {
2245 builder.append_null();
2246 }
2247 }
2248 Ok(Arc::new(builder.finish()))
2249 }
2250
2251 fn build_map_column<F>(
2252 &self,
2253 len: usize,
2254 deleted: &[bool],
2255 get_props: F,
2256 key: &DataType,
2257 value: &DataType,
2258 ) -> Result<ArrayRef>
2259 where
2260 F: Fn(usize) -> Option<&'a Value>,
2261 {
2262 if !matches!(key, DataType::String) {
2263 return Err(anyhow!("Map keys must be String (JSON limitation)"));
2264 }
2265
2266 match value {
2267 DataType::String => self.build_typed_map(
2268 len,
2269 deleted,
2270 &get_props,
2271 StringBuilder::new(),
2272 arrow_schema::DataType::Utf8,
2273 None,
2274 |v, b: &mut StringBuilder| {
2275 if let Some(s) = v.as_str() {
2276 b.append_value(s);
2277 } else {
2278 b.append_null();
2279 }
2280 },
2281 ),
2282 DataType::Int64 => self.build_typed_map(
2283 len,
2284 deleted,
2285 &get_props,
2286 Int64Builder::new(),
2287 arrow_schema::DataType::Int64,
2288 None,
2289 |v, b: &mut Int64Builder| {
2290 if let Some(n) = v.as_i64() {
2291 b.append_value(n);
2292 } else {
2293 b.append_null();
2294 }
2295 },
2296 ),
2297 DataType::Int32 => self.build_typed_map(
2298 len,
2299 deleted,
2300 &get_props,
2301 Int32Builder::new(),
2302 arrow_schema::DataType::Int32,
2303 None,
2304 |v, b: &mut Int32Builder| match v.as_i64().and_then(|n| i32::try_from(n).ok()) {
2305 Some(n) => b.append_value(n),
2306 None => b.append_null(),
2307 },
2308 ),
2309 DataType::Float64 => self.build_typed_map(
2310 len,
2311 deleted,
2312 &get_props,
2313 Float64Builder::new(),
2314 arrow_schema::DataType::Float64,
2315 None,
2316 |v, b: &mut Float64Builder| match v.as_f64() {
2317 Some(f) => b.append_value(f),
2318 None => b.append_null(),
2319 },
2320 ),
2321 DataType::Float32 => self.build_typed_map(
2322 len,
2323 deleted,
2324 &get_props,
2325 Float32Builder::new(),
2326 arrow_schema::DataType::Float32,
2327 None,
2328 |v, b: &mut Float32Builder| match v.as_f64() {
2329 Some(f) => b.append_value(f as f32),
2330 None => b.append_null(),
2331 },
2332 ),
2333 DataType::Bool => self.build_typed_map(
2334 len,
2335 deleted,
2336 &get_props,
2337 BooleanBuilder::new(),
2338 arrow_schema::DataType::Boolean,
2339 None,
2340 |v, b: &mut BooleanBuilder| match v.as_bool() {
2341 Some(x) => b.append_value(x),
2342 None => b.append_null(),
2343 },
2344 ),
2345 DataType::Bytes => self.build_typed_map(
2346 len,
2347 deleted,
2348 &get_props,
2349 LargeBinaryBuilder::new(),
2350 arrow_schema::DataType::LargeBinary,
2351 Some(schema::raw_bytes_field_metadata()),
2354 |v, b: &mut LargeBinaryBuilder| {
2355 if let Value::Bytes(bytes) = v {
2356 b.append_value(bytes);
2357 } else {
2358 b.append_null();
2359 }
2360 },
2361 ),
2362 _ => self.build_typed_map(
2368 len,
2369 deleted,
2370 &get_props,
2371 LargeBinaryBuilder::new(),
2372 arrow_schema::DataType::LargeBinary,
2373 None,
2374 |v, b: &mut LargeBinaryBuilder| {
2375 if v.is_null() {
2376 b.append_null();
2377 } else {
2378 b.append_value(uni_common::cypher_value_codec::encode(v));
2379 }
2380 },
2381 ),
2382 }
2383 }
2384
2385 #[expect(
2387 clippy::too_many_arguments,
2388 reason = "builder plumbing: value type + optional child metadata are distinct knobs"
2389 )]
2390 fn build_typed_map<F, B, A>(
2391 &self,
2392 len: usize,
2393 deleted: &[bool],
2394 get_props: &F,
2395 value_builder: B,
2396 value_arrow_type: arrow_schema::DataType,
2397 value_metadata: Option<HashMap<String, String>>,
2398 mut append_value: A,
2399 ) -> Result<ArrayRef>
2400 where
2401 F: Fn(usize) -> Option<&'a Value>,
2402 B: arrow_array::builder::ArrayBuilder,
2403 A: FnMut(&Value, &mut B),
2404 {
2405 let key_builder = Box::new(StringBuilder::new());
2406 let value_builder = Box::new(value_builder);
2407 let value_field = match value_metadata {
2408 Some(meta) => Field::new("value", value_arrow_type, true).with_metadata(meta),
2409 None => Field::new("value", value_arrow_type, true),
2410 };
2411 let struct_builder = StructBuilder::new(
2412 vec![
2413 Field::new("key", arrow_schema::DataType::Utf8, false),
2414 value_field,
2415 ],
2416 vec![key_builder, value_builder],
2417 );
2418 let mut builder = ListBuilder::new(struct_builder);
2419
2420 for (i, &is_deleted) in deleted.iter().enumerate().take(len) {
2421 self.append_map_entry(&mut builder, get_props(i), is_deleted, &mut append_value);
2422 }
2423 Ok(Arc::new(builder.finish()))
2424 }
2425
2426 fn append_map_entry<B, A>(
2428 &self,
2429 builder: &mut ListBuilder<StructBuilder>,
2430 val: Option<&'a Value>,
2431 is_deleted: bool,
2432 append_value: &mut A,
2433 ) where
2434 B: arrow_array::builder::ArrayBuilder,
2435 A: FnMut(&Value, &mut B),
2436 {
2437 let val_obj = val.and_then(|v| v.as_object());
2438 if val_obj.is_none() && is_deleted {
2439 builder.append(false);
2440 } else if let Some(obj) = val_obj {
2441 let struct_b = builder.values();
2442 for (k, v) in obj {
2443 struct_b
2444 .field_builder::<StringBuilder>(0)
2445 .unwrap()
2446 .append_value(k);
2447 let value_b = struct_b.field_builder::<B>(1).unwrap();
2449 append_value(v, value_b);
2450 struct_b.append(true);
2451 }
2452 builder.append(true);
2453 } else {
2454 builder.append(false);
2455 }
2456 }
2457
2458 fn build_crdt_column<F>(&self, len: usize, deleted: &[bool], get_props: F) -> Result<ArrayRef>
2459 where
2460 F: Fn(usize) -> Option<&'a Value>,
2461 {
2462 let mut builder = BinaryBuilder::new();
2463 for (i, &is_deleted) in deleted.iter().enumerate().take(len) {
2464 if is_deleted {
2465 builder.append_null();
2466 continue;
2467 }
2468 if let Some(val) = get_props(i) {
2469 let crdt_result = if let Some(s) = val.as_str() {
2472 serde_json::from_str::<Crdt>(s)
2473 } else {
2474 let json_val: serde_json::Value = val.clone().into();
2476 serde_json::from_value::<Crdt>(json_val)
2477 };
2478
2479 if let Ok(crdt) = crdt_result {
2480 if let Ok(bytes) = crdt.to_msgpack() {
2481 builder.append_value(&bytes);
2482 } else {
2483 builder.append_null();
2484 }
2485 } else {
2486 builder.append_null();
2487 }
2488 } else {
2489 builder.append_null();
2490 }
2491 }
2492 Ok(Arc::new(builder.finish()))
2493 }
2494}
2495
2496pub fn build_edge_column<'a>(
2498 name: &'a str,
2499 data_type: &'a DataType,
2500 len: usize,
2501 get_props: impl Fn(usize) -> Option<&'a Value>,
2502) -> Result<ArrayRef> {
2503 let deleted = vec![false; len];
2505 let extractor = PropertyExtractor::new(name, data_type);
2506 extractor.build_column(len, &deleted, get_props)
2507}
2508
2509#[cfg(test)]
2510mod tests {
2511 use super::*;
2512 use arrow_array::{
2513 Array, DurationMicrosecondArray,
2514 builder::{BinaryBuilder, Time64MicrosecondBuilder, TimestampNanosecondBuilder},
2515 };
2516 use std::collections::HashMap;
2517 use uni_common::TemporalValue;
2518 use uni_crdt::{Crdt, GCounter};
2519
2520 #[test]
2521 fn test_sparse_vector_columnar_roundtrip_and_no_silent_null() {
2522 use crate::storage::value_codec::{CrdtDecodeMode, decode_column_value, value_from_column};
2523
2524 let dt = DataType::SparseVector { dimensions: 100 };
2525 let v0 = Value::SparseVector {
2526 indices: vec![1, 5, 9],
2527 values: vec![0.5, -1.0, 2.0],
2528 };
2529 let v1 = Value::SparseVector {
2531 indices: vec![],
2532 values: vec![],
2533 };
2534 let props = [Some(v0.clone()), Some(v1.clone()), None];
2536 let deleted = [false, false, true];
2537
2538 let extractor = PropertyExtractor::new("emb", &dt);
2539 let col = extractor
2540 .build_column(3, &deleted, |i| props[i].as_ref())
2541 .unwrap();
2542
2543 assert_eq!(
2545 decode_column_value(&col, &dt, 0, CrdtDecodeMode::Strict).unwrap(),
2546 v0
2547 );
2548 assert_eq!(
2549 decode_column_value(&col, &dt, 1, CrdtDecodeMode::Strict).unwrap(),
2550 v1
2551 );
2552 assert_eq!(
2553 decode_column_value(&col, &dt, 2, CrdtDecodeMode::Strict).unwrap(),
2554 Value::Null
2555 );
2556
2557 let json0 = value_from_column(&col, &dt, 0, CrdtDecodeMode::Strict).unwrap();
2560 assert!(
2561 json0.is_object(),
2562 "sparse column was silently nulled by value_from_column: {json0:?}"
2563 );
2564 assert_eq!(json0["indices"], serde_json::json!([1u32, 5u32, 9u32]));
2565 assert_eq!(
2566 json0["values"],
2567 serde_json::json!([0.5f32, -1.0f32, 2.0f32])
2568 );
2569 }
2570
2571 #[test]
2572 fn test_arrow_to_value_string() {
2573 let arr = StringArray::from(vec![Some("hello"), None, Some("world")]);
2574 assert_eq!(
2575 arrow_to_value(&arr, 0, None),
2576 Value::String("hello".to_string())
2577 );
2578 assert_eq!(arrow_to_value(&arr, 1, None), Value::Null);
2579 assert_eq!(
2580 arrow_to_value(&arr, 2, None),
2581 Value::String("world".to_string())
2582 );
2583 }
2584
2585 #[test]
2595 fn test_arrow_to_value_large_string() {
2596 let arr = LargeStringArray::from(vec![Some("hello"), None, Some("world")]);
2597 assert_eq!(
2598 arrow_to_value(&arr, 0, None),
2599 Value::String("hello".to_string())
2600 );
2601 assert_eq!(arrow_to_value(&arr, 1, None), Value::Null);
2602 assert_eq!(
2603 arrow_to_value(&arr, 2, None),
2604 Value::String("world".to_string())
2605 );
2606 }
2607
2608 #[test]
2609 fn test_arrow_to_value_int64() {
2610 let arr = Int64Array::from(vec![Some(42), None, Some(-10)]);
2611 assert_eq!(arrow_to_value(&arr, 0, None), Value::Int(42));
2612 assert_eq!(arrow_to_value(&arr, 1, None), Value::Null);
2613 assert_eq!(arrow_to_value(&arr, 2, None), Value::Int(-10));
2614 }
2615
2616 #[test]
2617 #[allow(clippy::approx_constant)]
2618 fn test_arrow_to_value_float64() {
2619 let arr = Float64Array::from(vec![Some(3.14), None]);
2620 assert_eq!(arrow_to_value(&arr, 0, None), Value::Float(3.14));
2621 assert_eq!(arrow_to_value(&arr, 1, None), Value::Null);
2622 }
2623
2624 #[test]
2625 fn test_arrow_to_value_bool() {
2626 let arr = BooleanArray::from(vec![Some(true), Some(false), None]);
2627 assert_eq!(arrow_to_value(&arr, 0, None), Value::Bool(true));
2628 assert_eq!(arrow_to_value(&arr, 1, None), Value::Bool(false));
2629 assert_eq!(arrow_to_value(&arr, 2, None), Value::Null);
2630 }
2631
2632 #[test]
2633 fn test_values_to_array_int64() {
2634 let values = vec![Value::Int(1), Value::Int(2), Value::Null, Value::Int(4)];
2635 let arr = values_to_array(&values, &ArrowDataType::Int64).unwrap();
2636 assert_eq!(arr.len(), 4);
2637
2638 let int_arr = arr.as_any().downcast_ref::<Int64Array>().unwrap();
2639 assert_eq!(int_arr.value(0), 1);
2640 assert_eq!(int_arr.value(1), 2);
2641 assert!(int_arr.is_null(2));
2642 assert_eq!(int_arr.value(3), 4);
2643 }
2644
2645 #[test]
2646 fn test_values_to_array_string() {
2647 let values = vec![
2648 Value::String("a".to_string()),
2649 Value::String("b".to_string()),
2650 Value::Null,
2651 ];
2652 let arr = values_to_array(&values, &ArrowDataType::Utf8).unwrap();
2653 assert_eq!(arr.len(), 3);
2654
2655 let str_arr = arr.as_any().downcast_ref::<StringArray>().unwrap();
2656 assert_eq!(str_arr.value(0), "a");
2657 assert_eq!(str_arr.value(1), "b");
2658 assert!(str_arr.is_null(2));
2659 }
2660
2661 #[test]
2662 fn test_property_extractor_string() {
2663 let props: Vec<HashMap<String, Value>> = vec![
2664 [("name".to_string(), Value::String("Alice".to_string()))]
2665 .into_iter()
2666 .collect(),
2667 [("name".to_string(), Value::String("Bob".to_string()))]
2668 .into_iter()
2669 .collect(),
2670 HashMap::new(),
2671 ];
2672 let deleted = vec![false, false, true];
2673
2674 let extractor = PropertyExtractor::new("name", &DataType::String);
2675 let arr = extractor
2676 .build_column(3, &deleted, |i| props[i].get("name"))
2677 .unwrap();
2678
2679 let str_arr = arr.as_any().downcast_ref::<StringArray>().unwrap();
2680 assert_eq!(str_arr.value(0), "Alice");
2681 assert_eq!(str_arr.value(1), "Bob");
2682 assert_eq!(str_arr.value(2), ""); }
2684
2685 #[test]
2686 fn test_property_extractor_int64() {
2687 let props: Vec<HashMap<String, Value>> = vec![
2688 [("age".to_string(), Value::Int(25))].into_iter().collect(),
2689 [("age".to_string(), Value::Int(30))].into_iter().collect(),
2690 HashMap::new(),
2691 ];
2692 let deleted = vec![false, false, true];
2693
2694 let extractor = PropertyExtractor::new("age", &DataType::Int64);
2695 let arr = extractor
2696 .build_column(3, &deleted, |i| props[i].get("age"))
2697 .unwrap();
2698
2699 let int_arr = arr.as_any().downcast_ref::<Int64Array>().unwrap();
2700 assert_eq!(int_arr.value(0), 25);
2701 assert_eq!(int_arr.value(1), 30);
2702 assert_eq!(int_arr.value(2), 0); }
2704
2705 #[test]
2706 fn test_property_extractor_bytes_roundtrip() {
2707 let blob = vec![0u8, 1, 2, 255];
2708 let props: Vec<HashMap<String, Value>> = vec![
2709 [("blob".to_string(), Value::Bytes(blob.clone()))]
2710 .into_iter()
2711 .collect(),
2712 [("blob".to_string(), Value::Bytes(Vec::new()))]
2713 .into_iter()
2714 .collect(),
2715 HashMap::new(),
2716 ];
2717 let deleted = vec![false, false, false];
2718
2719 let extractor = PropertyExtractor::new("blob", &DataType::Bytes);
2720 let arr = extractor
2721 .build_column(3, &deleted, |i| props[i].get("blob"))
2722 .unwrap();
2723
2724 assert_eq!(
2726 arrow_to_value(arr.as_ref(), 0, Some(&DataType::Bytes)),
2727 Value::Bytes(blob)
2728 );
2729 assert_eq!(
2730 arrow_to_value(arr.as_ref(), 1, Some(&DataType::Bytes)),
2731 Value::Bytes(Vec::new())
2732 );
2733 assert_eq!(
2735 arrow_to_value(arr.as_ref(), 2, Some(&DataType::Bytes)),
2736 Value::Null
2737 );
2738 }
2739
2740 #[test]
2741 fn test_bytes_vs_cypher_value_disambiguation() {
2742 let raw = vec![0xDEu8, 0xAD, 0xBE, 0xEF];
2746 let props: Vec<HashMap<String, Value>> = vec![
2747 [("blob".to_string(), Value::Bytes(raw.clone()))]
2748 .into_iter()
2749 .collect(),
2750 ];
2751 let extractor = PropertyExtractor::new("blob", &DataType::Bytes);
2752 let arr = extractor
2753 .build_column(1, &[false], |i| props[i].get("blob"))
2754 .unwrap();
2755 assert_eq!(
2757 arrow_to_value(arr.as_ref(), 0, Some(&DataType::Bytes)),
2758 Value::Bytes(raw)
2759 );
2760 }
2761
2762 #[test]
2763 fn test_data_type_bytes_to_arrow() {
2764 assert_eq!(DataType::Bytes.to_arrow(), ArrowDataType::LargeBinary);
2765 }
2766
2767 #[test]
2768 fn test_arrow_to_value_time64() {
2769 let mut builder = Time64MicrosecondBuilder::new();
2771 builder.append_value(37_845_000_000);
2773 builder.append_value(0);
2775 builder.append_value(86_399_123_456);
2777 builder.append_null();
2778
2779 let arr = builder.finish();
2780 assert_eq!(arrow_to_value(&arr, 0, None).to_string(), "10:30:45");
2782 assert_eq!(arrow_to_value(&arr, 1, None).to_string(), "00:00");
2783 assert_eq!(arrow_to_value(&arr, 2, None).to_string(), "23:59:59.123456");
2784 assert_eq!(arrow_to_value(&arr, 3, None), Value::Null);
2785 }
2786
2787 #[test]
2788 fn test_arrow_to_value_duration() {
2789 let arr = DurationMicrosecondArray::from(vec![
2792 Some(1_000_000), Some(3_600_000_000), Some(86_400_000_000), None,
2796 ]);
2797
2798 assert_eq!(arrow_to_value(&arr, 0, None).to_string(), "PT1S");
2799 assert_eq!(arrow_to_value(&arr, 1, None).to_string(), "PT1H");
2800 assert_eq!(arrow_to_value(&arr, 2, None).to_string(), "PT24H");
2801 assert_eq!(arrow_to_value(&arr, 3, None), Value::Null);
2802 }
2803
2804 #[test]
2805 fn test_arrow_to_value_binary_crdt() {
2806 let mut builder = BinaryBuilder::new();
2808
2809 let mut counter = GCounter::new();
2811 counter.increment("actor1", 5);
2812 let crdt = Crdt::GCounter(counter);
2813 let bytes = crdt.to_msgpack().unwrap();
2814 builder.append_value(&bytes);
2815
2816 builder.append_null();
2818
2819 let arr = builder.finish();
2820
2821 let result = arrow_to_value(&arr, 0, None);
2823 assert!(result.as_object().is_some());
2824 let obj = result.as_object().unwrap();
2825 assert_eq!(obj.get("t"), Some(&Value::String("gc".to_string())));
2827
2828 assert_eq!(arrow_to_value(&arr, 1, None), Value::Null);
2830 }
2831
2832 #[test]
2833 fn test_datetime_struct_encode_decode_roundtrip() {
2834 let values = vec![
2836 Value::Temporal(TemporalValue::DateTime {
2837 nanos_since_epoch: 441763200000000000, offset_seconds: 3600, timezone_name: Some("Europe/Paris".to_string()),
2840 }),
2841 Value::Temporal(TemporalValue::DateTime {
2842 nanos_since_epoch: 1704067200000000000, offset_seconds: -18000, timezone_name: None,
2845 }),
2846 Value::Temporal(TemporalValue::DateTime {
2847 nanos_since_epoch: 0, offset_seconds: 0,
2849 timezone_name: Some("UTC".to_string()),
2850 }),
2851 ];
2852
2853 let arr_ref = values_to_datetime_struct_array(&values);
2855 let arr = arr_ref.as_any().downcast_ref::<StructArray>().unwrap();
2856 assert_eq!(arr.len(), 3);
2857
2858 let decoded_0 = arrow_to_value(arr_ref.as_ref(), 0, Some(&DataType::DateTime));
2860 let decoded_1 = arrow_to_value(arr_ref.as_ref(), 1, Some(&DataType::DateTime));
2861 let decoded_2 = arrow_to_value(arr_ref.as_ref(), 2, Some(&DataType::DateTime));
2862
2863 assert_eq!(decoded_0, values[0]);
2865 assert_eq!(decoded_1, values[1]);
2866 assert_eq!(decoded_2, values[2]);
2867
2868 if let Value::Temporal(TemporalValue::DateTime {
2870 nanos_since_epoch,
2871 offset_seconds,
2872 timezone_name,
2873 }) = decoded_0
2874 {
2875 assert_eq!(nanos_since_epoch, 441763200000000000);
2876 assert_eq!(offset_seconds, 3600);
2877 assert_eq!(timezone_name, Some("Europe/Paris".to_string()));
2878 } else {
2879 panic!("Expected DateTime value");
2880 }
2881 }
2882
2883 #[test]
2884 fn test_datetime_struct_null_handling() {
2885 let values = vec![
2887 Value::Temporal(TemporalValue::DateTime {
2888 nanos_since_epoch: 441763200000000000,
2889 offset_seconds: 3600,
2890 timezone_name: Some("Europe/Paris".to_string()),
2891 }),
2892 Value::Null,
2893 Value::Temporal(TemporalValue::DateTime {
2894 nanos_since_epoch: 0,
2895 offset_seconds: 0,
2896 timezone_name: None,
2897 }),
2898 ];
2899
2900 let arr_ref = values_to_datetime_struct_array(&values);
2901 let arr = arr_ref.as_any().downcast_ref::<StructArray>().unwrap();
2902 assert_eq!(arr.len(), 3);
2903
2904 let decoded_0 = arrow_to_value(arr_ref.as_ref(), 0, Some(&DataType::DateTime));
2906 assert_eq!(decoded_0, values[0]);
2907
2908 assert!(arr.is_null(1));
2910 let decoded_1 = arrow_to_value(arr_ref.as_ref(), 1, Some(&DataType::DateTime));
2911 assert_eq!(decoded_1, Value::Null);
2912
2913 let decoded_2 = arrow_to_value(arr_ref.as_ref(), 2, Some(&DataType::DateTime));
2915 assert_eq!(decoded_2, values[2]);
2916 }
2917
2918 #[test]
2919 fn test_datetime_struct_boundary_values() {
2920 let values = vec![
2922 Value::Temporal(TemporalValue::DateTime {
2923 nanos_since_epoch: 441763200000000000,
2924 offset_seconds: 0, timezone_name: None,
2926 }),
2927 Value::Temporal(TemporalValue::DateTime {
2928 nanos_since_epoch: 441763200000000000,
2929 offset_seconds: 43200, timezone_name: None,
2931 }),
2932 Value::Temporal(TemporalValue::DateTime {
2933 nanos_since_epoch: 441763200000000000,
2934 offset_seconds: -43200, timezone_name: None,
2936 }),
2937 ];
2938
2939 let arr_ref = values_to_datetime_struct_array(&values);
2940 let arr = arr_ref.as_any().downcast_ref::<StructArray>().unwrap();
2941 assert_eq!(arr.len(), 3);
2942
2943 for (i, expected) in values.iter().enumerate() {
2945 let decoded = arrow_to_value(arr_ref.as_ref(), i, Some(&DataType::DateTime));
2946 assert_eq!(&decoded, expected);
2947 }
2948 }
2949
2950 #[test]
2951 fn test_datetime_old_schema_migration() {
2952 let mut builder = TimestampNanosecondBuilder::new().with_timezone("UTC");
2954 builder.append_value(441763200000000000); builder.append_value(1704067200000000000); builder.append_null();
2957
2958 let arr = builder.finish();
2959
2960 let decoded_0 = arrow_to_value(&arr, 0, Some(&DataType::DateTime));
2962 let _decoded_1 = arrow_to_value(&arr, 1, Some(&DataType::DateTime));
2963 let decoded_2 = arrow_to_value(&arr, 2, Some(&DataType::DateTime));
2964
2965 if let Value::Temporal(TemporalValue::DateTime {
2967 nanos_since_epoch,
2968 offset_seconds,
2969 timezone_name,
2970 }) = decoded_0
2971 {
2972 assert_eq!(nanos_since_epoch, 441763200000000000);
2973 assert_eq!(offset_seconds, 0);
2974 assert_eq!(timezone_name, Some("UTC".to_string()));
2975 } else {
2976 panic!("Expected DateTime value");
2977 }
2978
2979 assert_eq!(decoded_2, Value::Null);
2981 }
2982
2983 #[test]
2984 fn test_time_struct_encode_decode_roundtrip() {
2985 let values = vec![
2987 Value::Temporal(TemporalValue::Time {
2988 nanos_since_midnight: 37845000000000, offset_seconds: 3600, }),
2991 Value::Temporal(TemporalValue::Time {
2992 nanos_since_midnight: 0, offset_seconds: 0,
2994 }),
2995 Value::Temporal(TemporalValue::Time {
2996 nanos_since_midnight: 86399999999999, offset_seconds: -18000, }),
2999 ];
3000
3001 let arr_ref = values_to_time_struct_array(&values);
3003 let arr = arr_ref.as_any().downcast_ref::<StructArray>().unwrap();
3004 assert_eq!(arr.len(), 3);
3005
3006 let decoded_0 = arrow_to_value(arr_ref.as_ref(), 0, Some(&DataType::Time));
3008 let decoded_1 = arrow_to_value(arr_ref.as_ref(), 1, Some(&DataType::Time));
3009 let decoded_2 = arrow_to_value(arr_ref.as_ref(), 2, Some(&DataType::Time));
3010
3011 assert_eq!(decoded_0, values[0]);
3013 assert_eq!(decoded_1, values[1]);
3014 assert_eq!(decoded_2, values[2]);
3015
3016 if let Value::Temporal(TemporalValue::Time {
3018 nanos_since_midnight,
3019 offset_seconds,
3020 }) = decoded_0
3021 {
3022 assert_eq!(nanos_since_midnight, 37845000000000);
3023 assert_eq!(offset_seconds, 3600);
3024 } else {
3025 panic!("Expected Time value");
3026 }
3027 }
3028
3029 #[test]
3030 fn test_time_struct_null_handling() {
3031 let values = vec![
3033 Value::Temporal(TemporalValue::Time {
3034 nanos_since_midnight: 37845000000000,
3035 offset_seconds: 3600,
3036 }),
3037 Value::Null,
3038 Value::Temporal(TemporalValue::Time {
3039 nanos_since_midnight: 0,
3040 offset_seconds: 0,
3041 }),
3042 ];
3043
3044 let arr_ref = values_to_time_struct_array(&values);
3045 let arr = arr_ref.as_any().downcast_ref::<StructArray>().unwrap();
3046 assert_eq!(arr.len(), 3);
3047
3048 let decoded_0 = arrow_to_value(arr_ref.as_ref(), 0, Some(&DataType::Time));
3050 assert_eq!(decoded_0, values[0]);
3051
3052 assert!(arr.is_null(1));
3054 let decoded_1 = arrow_to_value(arr_ref.as_ref(), 1, Some(&DataType::Time));
3055 assert_eq!(decoded_1, Value::Null);
3056
3057 let decoded_2 = arrow_to_value(arr_ref.as_ref(), 2, Some(&DataType::Time));
3059 assert_eq!(decoded_2, values[2]);
3060 }
3061
3062 #[test]
3065 fn test_extract_vector_f32_values_valid_vector() {
3066 let v = vec![1.0, 2.0, 3.0];
3067 let val = Value::Vector(v.clone());
3068 let (result, valid) = extract_vector_f32_values(Some(&val), false, 3);
3069 assert_eq!(result, v);
3070 assert!(valid);
3071 }
3072
3073 #[test]
3074 fn test_extract_vector_f32_values_vector_wrong_dims() {
3075 let v = vec![1.0, 2.0];
3079 let val = Value::Vector(v);
3080 let (result, valid) = extract_vector_f32_values(Some(&val), false, 3);
3081 assert_eq!(result, vec![0.0, 0.0, 0.0]);
3082 assert!(!valid);
3083 }
3084
3085 #[test]
3086 fn test_extract_vector_f32_values_valid_list() {
3087 let v = vec![Value::Float(1.0), Value::Float(2.0), Value::Float(3.0)];
3088 let val = Value::List(v);
3089 let (result, valid) = extract_vector_f32_values(Some(&val), false, 3);
3090 assert_eq!(result, vec![1.0, 2.0, 3.0]);
3091 assert!(valid);
3092 }
3093
3094 #[test]
3095 fn test_extract_vector_f32_values_list_wrong_dims() {
3096 let v = vec![Value::Float(1.0), Value::Float(2.0)];
3098 let val = Value::List(v);
3099 let (result, valid) = extract_vector_f32_values(Some(&val), false, 3);
3100 assert_eq!(result, vec![0.0, 0.0, 0.0]);
3101 assert!(!valid);
3102 }
3103
3104 #[test]
3105 fn test_extract_vector_f32_values_list_int_coercion() {
3106 let v = vec![Value::Int(1), Value::Int(2), Value::Int(3)];
3107 let val = Value::List(v);
3108 let (result, valid) = extract_vector_f32_values(Some(&val), false, 3);
3109 assert_eq!(result, vec![1.0, 2.0, 3.0]);
3110 assert!(valid);
3111 }
3112
3113 #[test]
3114 fn test_extract_vector_f32_values_none() {
3115 let (result, valid) = extract_vector_f32_values(None, false, 3);
3116 assert_eq!(result, vec![0.0, 0.0, 0.0]);
3117 assert!(!valid);
3118 }
3119
3120 #[test]
3121 fn test_extract_vector_f32_values_null() {
3122 let val = Value::Null;
3123 let (result, valid) = extract_vector_f32_values(Some(&val), false, 3);
3124 assert_eq!(result, vec![0.0, 0.0, 0.0]);
3125 assert!(!valid);
3126 }
3127
3128 #[test]
3129 fn test_extract_vector_f32_values_unsupported_type() {
3130 let val = Value::String("not a vector".to_string());
3131 let (result, valid) = extract_vector_f32_values(Some(&val), false, 3);
3132 assert_eq!(result, vec![0.0, 0.0, 0.0]);
3133 assert!(!valid);
3134 }
3135
3136 #[test]
3137 fn test_extract_vector_f32_values_deleted_with_none() {
3138 let (result, valid) = extract_vector_f32_values(None, true, 3);
3139 assert_eq!(result, vec![0.0, 0.0, 0.0]);
3140 assert!(valid); }
3142
3143 #[test]
3144 fn test_extract_vector_f32_values_deleted_with_null() {
3145 let val = Value::Null;
3146 let (result, valid) = extract_vector_f32_values(Some(&val), true, 3);
3147 assert_eq!(result, vec![0.0, 0.0, 0.0]);
3148 assert!(valid); }
3150
3151 #[test]
3154 fn test_values_to_fixed_size_list_vector_with_nulls() {
3155 let values = vec![
3156 Value::Vector(vec![1.0, 2.0]),
3157 Value::Null,
3158 Value::Vector(vec![3.0, 4.0]),
3159 Value::String("invalid".to_string()),
3160 ];
3161 let arr_ref = values_to_array(
3162 &values,
3163 &ArrowDataType::FixedSizeList(
3164 Arc::new(Field::new("item", ArrowDataType::Float32, false)),
3165 2,
3166 ),
3167 )
3168 .unwrap();
3169
3170 let arr = arr_ref
3171 .as_any()
3172 .downcast_ref::<FixedSizeListArray>()
3173 .unwrap();
3174
3175 assert_eq!(arr.len(), 4);
3176 assert!(arr.is_valid(0));
3177 assert!(!arr.is_valid(1)); assert!(arr.is_valid(2));
3179 assert!(!arr.is_valid(3)); }
3181
3182 #[test]
3183 fn test_values_to_fixed_size_list_from_list() {
3184 let values = vec![
3185 Value::List(vec![Value::Float(1.0), Value::Float(2.0)]),
3186 Value::List(vec![Value::Int(3), Value::Int(4)]),
3187 ];
3188 let arr_ref = values_to_array(
3189 &values,
3190 &ArrowDataType::FixedSizeList(
3191 Arc::new(Field::new("item", ArrowDataType::Float32, false)),
3192 2,
3193 ),
3194 )
3195 .unwrap();
3196
3197 let arr = arr_ref
3198 .as_any()
3199 .downcast_ref::<FixedSizeListArray>()
3200 .unwrap();
3201
3202 assert_eq!(arr.len(), 2);
3203 assert!(arr.is_valid(0));
3204 assert!(arr.is_valid(1));
3205
3206 let child = arr
3208 .values()
3209 .as_any()
3210 .downcast_ref::<Float32Array>()
3211 .unwrap();
3212 assert_eq!(child.value(0), 1.0);
3213 assert_eq!(child.value(1), 2.0);
3214 assert_eq!(child.value(2), 3.0);
3215 assert_eq!(child.value(3), 4.0);
3216 }
3217
3218 #[test]
3219 fn test_values_to_fixed_size_list_wrong_dimensions() {
3220 let values = vec![
3221 Value::Vector(vec![1.0, 2.0, 3.0]), Value::List(vec![Value::Float(4.0)]), ];
3224 let arr_ref = values_to_array(
3225 &values,
3226 &ArrowDataType::FixedSizeList(
3227 Arc::new(Field::new("item", ArrowDataType::Float32, false)),
3228 2,
3229 ),
3230 )
3231 .unwrap();
3232
3233 let arr = arr_ref
3234 .as_any()
3235 .downcast_ref::<FixedSizeListArray>()
3236 .unwrap();
3237
3238 assert_eq!(arr.len(), 2);
3239 assert!(!arr.is_valid(0)); assert!(!arr.is_valid(1)); let child = arr
3244 .values()
3245 .as_any()
3246 .downcast_ref::<Float32Array>()
3247 .unwrap();
3248 assert_eq!(child.value(0), 0.0);
3249 assert_eq!(child.value(1), 0.0);
3250 assert_eq!(child.value(2), 0.0);
3251 assert_eq!(child.value(3), 0.0);
3252 }
3253
3254 #[test]
3255 fn test_values_to_fixed_size_list_all_nulls() {
3256 let values = vec![Value::Null, Value::Null, Value::Null];
3257 let arr_ref = values_to_array(
3258 &values,
3259 &ArrowDataType::FixedSizeList(
3260 Arc::new(Field::new("item", ArrowDataType::Float32, false)),
3261 3,
3262 ),
3263 )
3264 .unwrap();
3265
3266 let arr = arr_ref
3267 .as_any()
3268 .downcast_ref::<FixedSizeListArray>()
3269 .unwrap();
3270
3271 assert_eq!(arr.len(), 3);
3272 assert!(!arr.is_valid(0));
3273 assert!(!arr.is_valid(1));
3274 assert!(!arr.is_valid(2));
3275
3276 let child = arr
3278 .values()
3279 .as_any()
3280 .downcast_ref::<Float32Array>()
3281 .unwrap();
3282 assert_eq!(child.len(), 9);
3283 }
3284
3285 #[test]
3286 fn test_values_to_fixed_size_list_mixed_types() {
3287 let values = vec![
3288 Value::Vector(vec![1.0, 2.0]),
3289 Value::List(vec![Value::Float(3.0), Value::Float(4.0)]),
3290 Value::Null,
3291 Value::String("invalid".to_string()),
3292 ];
3293 let arr_ref = values_to_array(
3294 &values,
3295 &ArrowDataType::FixedSizeList(
3296 Arc::new(Field::new("item", ArrowDataType::Float32, false)),
3297 2,
3298 ),
3299 )
3300 .unwrap();
3301
3302 let arr = arr_ref
3303 .as_any()
3304 .downcast_ref::<FixedSizeListArray>()
3305 .unwrap();
3306
3307 assert_eq!(arr.len(), 4);
3308 assert!(arr.is_valid(0)); assert!(arr.is_valid(1)); assert!(!arr.is_valid(2)); assert!(!arr.is_valid(3)); let child = arr
3315 .values()
3316 .as_any()
3317 .downcast_ref::<Float32Array>()
3318 .unwrap();
3319 assert_eq!(child.value(0), 1.0);
3320 assert_eq!(child.value(1), 2.0);
3321 assert_eq!(child.value(2), 3.0);
3322 assert_eq!(child.value(3), 4.0);
3323 }
3324
3325 #[test]
3328 fn test_build_vector_column_with_nulls_and_deleted() {
3329 let data_type = DataType::Vector { dimensions: 3 };
3330 let extractor = PropertyExtractor::new("test_vec", &data_type);
3331
3332 let props = [
3333 Some(Value::Vector(vec![1.0, 2.0, 3.0])),
3334 None, Some(Value::Null), Some(Value::Vector(vec![4.0, 5.0, 6.0])),
3337 ];
3338 let deleted = [false, false, false, true]; let arr_ref = extractor
3341 .build_vector_column(4, &deleted, |i| props[i].as_ref(), 3)
3342 .unwrap();
3343
3344 let arr = arr_ref
3345 .as_any()
3346 .downcast_ref::<FixedSizeListArray>()
3347 .unwrap();
3348
3349 assert_eq!(arr.len(), 4);
3350 assert!(arr.is_valid(0)); assert!(!arr.is_valid(1)); assert!(!arr.is_valid(2)); assert!(arr.is_valid(3)); let child = arr
3357 .values()
3358 .as_any()
3359 .downcast_ref::<Float32Array>()
3360 .unwrap();
3361 assert_eq!(child.value(0), 1.0);
3362 assert_eq!(child.value(1), 2.0);
3363 assert_eq!(child.value(2), 3.0);
3364 assert_eq!(child.value(9), 0.0);
3368 assert_eq!(child.value(10), 0.0);
3369 assert_eq!(child.value(11), 0.0);
3370 }
3371
3372 #[test]
3373 fn test_build_vector_column_with_list_input() {
3374 let data_type = DataType::Vector { dimensions: 2 };
3375 let extractor = PropertyExtractor::new("test_vec", &data_type);
3376
3377 let props = [
3378 Some(Value::List(vec![Value::Float(1.0), Value::Float(2.0)])),
3379 Some(Value::List(vec![Value::Int(3), Value::Int(4)])),
3380 Some(Value::Vector(vec![5.0, 6.0])),
3381 ];
3382 let deleted = [false, false, false];
3383
3384 let arr_ref = extractor
3385 .build_vector_column(3, &deleted, |i| props[i].as_ref(), 2)
3386 .unwrap();
3387
3388 let arr = arr_ref
3389 .as_any()
3390 .downcast_ref::<FixedSizeListArray>()
3391 .unwrap();
3392
3393 assert_eq!(arr.len(), 3);
3394 assert!(arr.is_valid(0));
3395 assert!(arr.is_valid(1));
3396 assert!(arr.is_valid(2));
3397
3398 let child = arr
3400 .values()
3401 .as_any()
3402 .downcast_ref::<Float32Array>()
3403 .unwrap();
3404 assert_eq!(child.value(0), 1.0);
3405 assert_eq!(child.value(1), 2.0);
3406 assert_eq!(child.value(2), 3.0);
3407 assert_eq!(child.value(3), 4.0);
3408 assert_eq!(child.value(4), 5.0);
3409 assert_eq!(child.value(5), 6.0);
3410 }
3411
3412 #[test]
3413 fn test_build_binary_vector_column_roundtrip() {
3414 let data_type = DataType::BinaryVector { dimensions: 3 };
3418 let extractor = PropertyExtractor::new("bits", &data_type);
3419
3420 let props = [
3421 Some(Value::BinaryVector(vec![0x00, 0xFF, 0xA5])),
3422 Some(Value::List(vec![
3423 Value::Int(1),
3424 Value::Int(2),
3425 Value::Int(255),
3426 ])),
3427 None, None, ];
3430 let deleted = [false, false, true, false];
3431
3432 let arr_ref = extractor
3433 .build_binary_vector_column(4, &deleted, |i| props[i].as_ref(), 3)
3434 .unwrap();
3435
3436 let arr = arr_ref
3437 .as_any()
3438 .downcast_ref::<FixedSizeListArray>()
3439 .unwrap();
3440 assert_eq!(arr.len(), 4);
3441 assert!(arr.is_valid(0));
3442 assert!(arr.is_valid(1));
3443 assert!(arr.is_valid(2)); assert!(!arr.is_valid(3)); let child = arr.values().as_any().downcast_ref::<UInt8Array>().unwrap();
3447 assert_eq!(child.value(0), 0x00);
3448 assert_eq!(child.value(1), 0xFF);
3449 assert_eq!(child.value(2), 0xA5);
3450 assert_eq!(child.value(3), 1);
3451 assert_eq!(child.value(5), 255);
3452
3453 assert_eq!(
3455 arrow_to_value(&arr_ref, 0, Some(&data_type)),
3456 Value::BinaryVector(vec![0x00, 0xFF, 0xA5])
3457 );
3458 assert_eq!(
3459 arrow_to_value(&arr_ref, 1, Some(&data_type)),
3460 Value::BinaryVector(vec![1, 2, 255])
3461 );
3462 assert_eq!(arrow_to_value(&arr_ref, 3, Some(&data_type)), Value::Null);
3463 }
3464
3465 #[test]
3466 fn test_build_binary_vector_column_wrong_length_fails_closed() {
3467 let data_type = DataType::BinaryVector { dimensions: 3 };
3469 let extractor = PropertyExtractor::new("bits", &data_type);
3470 let props = [Some(Value::BinaryVector(vec![1, 2]))]; let deleted = [false];
3472 let err = extractor
3473 .build_binary_vector_column(1, &deleted, |i| props[i].as_ref(), 3)
3474 .unwrap_err();
3475 assert!(err.to_string().contains("bits"), "got: {err}");
3476 }
3477
3478 #[test]
3481 fn test_build_multivector_list_column_roundtrip() {
3482 let data_type = DataType::List(Box::new(DataType::Vector { dimensions: 3 }));
3488 let extractor = PropertyExtractor::new("tokens", &data_type);
3489
3490 let props = [
3491 Some(Value::List(vec![
3493 Value::Vector(vec![1.0, 2.0, 3.0]),
3494 Value::Vector(vec![4.0, 5.0, 6.0]),
3495 ])),
3496 Some(Value::List(vec![
3498 Value::Vector(vec![7.0, 8.0, 9.0]),
3499 Value::Vector(vec![10.0, 11.0, 12.0]),
3500 Value::Vector(vec![13.0, 14.0, 15.0]),
3501 ])),
3502 Some(Value::List(vec![])),
3504 None,
3506 ];
3507 let deleted = [false, false, false, true];
3508
3509 let arr_ref = extractor
3510 .build_column(4, &deleted, |i| props[i].as_ref())
3511 .unwrap();
3512
3513 let outer = arr_ref.as_any().downcast_ref::<ListArray>().unwrap();
3515 assert_eq!(outer.len(), 4);
3516 assert!(outer.is_valid(0));
3517 assert!(outer.is_valid(1));
3518 assert!(outer.is_valid(2)); assert!(!outer.is_valid(3)); assert_eq!(outer.value(0).len(), 2);
3523 assert_eq!(outer.value(1).len(), 3);
3524 assert_eq!(outer.value(2).len(), 0);
3525
3526 let row0 = arrow_to_value(arr_ref.as_ref(), 0, Some(&data_type));
3529 assert_eq!(
3530 row0,
3531 Value::List(vec![
3532 Value::Vector(vec![1.0, 2.0, 3.0]),
3533 Value::Vector(vec![4.0, 5.0, 6.0]),
3534 ])
3535 );
3536
3537 let row1 = arrow_to_value(arr_ref.as_ref(), 1, Some(&data_type));
3538 let Value::List(tokens) = row1 else {
3539 panic!("row1 should decode to a list of tokens");
3540 };
3541 assert_eq!(tokens.len(), 3);
3542 assert_eq!(tokens[2], Value::Vector(vec![13.0, 14.0, 15.0]));
3543 }
3544
3545 #[test]
3546 fn test_build_multivector_invalid_inner_tokens() {
3547 let data_type = DataType::List(Box::new(DataType::Vector { dimensions: 2 }));
3551 let extractor = PropertyExtractor::new("tokens", &data_type);
3552
3553 let props = [Some(Value::List(vec![
3554 Value::Vector(vec![1.0, 2.0]), Value::Vector(vec![9.0, 9.0, 9.0]), ]))];
3557 let deleted = [false];
3558
3559 let err = extractor
3560 .build_column(1, &deleted, |i| props[i].as_ref())
3561 .unwrap_err()
3562 .to_string();
3563 assert!(err.contains("'tokens'"), "message: {err}");
3564 assert!(err.contains("token 1"), "message: {err}");
3565
3566 let props = [Some(Value::List(vec![Value::String("nope".to_string())]))];
3568 let err = extractor
3569 .build_column(1, &deleted, |i| props[i].as_ref())
3570 .unwrap_err()
3571 .to_string();
3572 assert!(err.contains("token 0"), "message: {err}");
3573 }
3574
3575 #[test]
3576 fn test_build_multivector_valid_tokens_and_null_rows() {
3577 let data_type = DataType::List(Box::new(DataType::Vector { dimensions: 2 }));
3581 let extractor = PropertyExtractor::new("tokens", &data_type);
3582
3583 let props = [
3584 Some(Value::List(vec![
3585 Value::Vector(vec![1.0, 2.0]),
3586 Value::List(vec![Value::Float(3.0), Value::Float(4.0)]),
3587 ])),
3588 None,
3589 Some(Value::Null),
3590 Some(Value::List(vec![])),
3591 ];
3592 let deleted = [false; 4];
3593
3594 let arr_ref = extractor
3595 .build_column(4, &deleted, |i| props[i].as_ref())
3596 .unwrap();
3597 let outer = arr_ref.as_any().downcast_ref::<ListArray>().unwrap();
3598 assert!(outer.is_valid(0));
3599 assert!(!outer.is_valid(1)); assert!(!outer.is_valid(2)); assert!(outer.is_valid(3)); let inner_row = outer.value(0);
3603 let inner = inner_row
3604 .as_any()
3605 .downcast_ref::<FixedSizeListArray>()
3606 .unwrap();
3607 assert_eq!(inner.len(), 2);
3608 assert!(inner.is_valid(0));
3609 assert!(inner.is_valid(1));
3610 assert_eq!(outer.value(3).len(), 0);
3611 }
3612
3613 #[test]
3614 fn test_build_vector_column_wrong_dims_is_error() {
3615 let data_type = DataType::Vector { dimensions: 3 };
3618 let extractor = PropertyExtractor::new("embedding", &data_type);
3619
3620 let props = [
3621 Some(Value::Vector(vec![1.0, 2.0, 3.0])),
3622 Some(Value::Vector(vec![1.0, 2.0])),
3623 ];
3624 let deleted = [false; 2];
3625
3626 let err = extractor
3627 .build_column(2, &deleted, |i| props[i].as_ref())
3628 .unwrap_err()
3629 .to_string();
3630 assert!(err.contains("'embedding'"), "message: {err}");
3631 assert!(err.contains("row 1"), "message: {err}");
3632
3633 let props = [
3635 Some(Value::Vector(vec![1.0, 2.0, 3.0])),
3636 None,
3637 Some(Value::Null),
3638 ];
3639 let deleted = [false, false, true];
3640 let arr_ref = extractor
3641 .build_column(3, &deleted, |i| props[i].as_ref())
3642 .unwrap();
3643 let arr = arr_ref
3644 .as_any()
3645 .downcast_ref::<FixedSizeListArray>()
3646 .unwrap();
3647 assert!(arr.is_valid(0));
3648 assert!(!arr.is_valid(1));
3649 }
3650
3651 #[test]
3652 fn test_extract_vector_f32_values_strict() {
3653 assert!(
3655 extract_vector_f32_values_strict(Some(&Value::Vector(vec![1.0, 2.0])), false, 3)
3656 .is_err()
3657 );
3658 assert!(
3659 extract_vector_f32_values_strict(Some(&Value::String("x".into())), false, 3).is_err()
3660 );
3661 let (vals, valid) = extract_vector_f32_values_strict(None, false, 3).unwrap();
3662 assert_eq!((vals, valid), (vec![0.0, 0.0, 0.0], false));
3663 let (vals, valid) = extract_vector_f32_values_strict(Some(&Value::Null), false, 3).unwrap();
3664 assert_eq!((vals, valid), (vec![0.0, 0.0, 0.0], false));
3665 let (vals, valid) =
3666 extract_vector_f32_values_strict(Some(&Value::Vector(vec![1.0, 2.0])), true, 3)
3667 .unwrap();
3668 assert_eq!((vals, valid), (vec![0.0, 0.0, 0.0], true));
3669 let (vals, valid) =
3670 extract_vector_f32_values_strict(Some(&Value::Vector(vec![1.0, 2.0, 3.0])), false, 3)
3671 .unwrap();
3672 assert_eq!((vals, valid), (vec![1.0, 2.0, 3.0], true));
3673 }
3674
3675 #[test]
3676 fn test_values_to_array_multivector_schemaless_deferred() {
3677 let values = vec![Value::List(vec![Value::Vector(vec![1.0, 2.0])])];
3682 let dt = ArrowDataType::List(Arc::new(Field::new(
3683 "item",
3684 ArrowDataType::FixedSizeList(
3685 Arc::new(Field::new("item", ArrowDataType::Float32, true)),
3686 2,
3687 ),
3688 true,
3689 )));
3690 assert!(values_to_array(&values, &dt).is_err());
3691 }
3692
3693 #[test]
3696 fn test_int32_and_date32_columns_null_out_of_range() {
3697 let dt = DataType::Int64;
3698 let extractor = PropertyExtractor::new("x", &dt);
3699
3700 let over = Value::Int(i64::from(i32::MAX) + 1);
3701 let ok = Value::Int(42);
3702 let vals = [over, ok];
3703 let deleted = [false, false];
3704
3705 let arr = extractor
3706 .build_int32_column(2, &deleted, |i| Some(&vals[i]))
3707 .unwrap();
3708 let arr = arr.as_any().downcast_ref::<Int32Array>().unwrap();
3709 assert!(
3710 arr.is_null(0),
3711 "out-of-range i64 must be NULL in an int32 column, not wrapped"
3712 );
3713 assert_eq!(arr.value(1), 42);
3714
3715 let arr = extractor
3716 .build_date32_column(2, &deleted, |i| Some(&vals[i]))
3717 .unwrap();
3718 let arr = arr.as_any().downcast_ref::<Date32Array>().unwrap();
3719 assert!(
3720 arr.is_null(0),
3721 "out-of-range day count must be NULL in a date32 column, not wrapped"
3722 );
3723 assert_eq!(arr.value(1), 42);
3724 }
3725}