1use std::{any::type_name, fmt, mem, result::Result as StdResult, sync::Arc};
2
3use base64::Engine as _;
4use bytes::Bytes;
5use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Utc};
6
7use crate::{
8 error::{
9 CellDecodeError, DuplicateColumnNameError, InvalidColumnIndexError, PlanBuildResult,
10 RowDecodeResult, SchemaError,
11 },
12 result_table::{
13 CellConversionError, CellDecodeResult, FromRow, RowPlanContext,
14 cell::CellRef,
15 decode::{
16 Vector, decode_hex, decode_json_payload, parse_time_seconds_and_nanos,
17 parse_timestamp_epoch, parse_timestamp_tz_with_offset, parse_vector_f32_payload,
18 parse_vector_i32_payload,
19 },
20 row::RowRef,
21 schema::{ColumnType, Schema},
22 },
23};
24
25#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct DecimalValue {
32 raw: Box<str>,
33}
34
35impl DecimalValue {
36 pub(crate) fn new(raw: impl Into<Box<str>>) -> Self {
37 Self { raw: raw.into() }
38 }
39
40 pub fn raw(&self) -> &str {
42 &self.raw
43 }
44}
45
46impl fmt::Display for DecimalValue {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 f.write_str(&self.raw)
49 }
50}
51
52#[derive(Clone, Debug, PartialEq, Eq, Hash)]
54pub struct BinaryValue(Bytes);
55
56impl BinaryValue {
57 pub(crate) fn new(bytes: impl Into<Bytes>) -> Self {
58 Self(bytes.into())
59 }
60
61 pub fn as_bytes(&self) -> &[u8] {
63 &self.0
64 }
65
66 pub fn into_bytes(self) -> Bytes {
68 self.0
69 }
70}
71
72impl From<Bytes> for BinaryValue {
73 fn from(bytes: Bytes) -> Self {
74 Self(bytes)
75 }
76}
77
78impl From<Vec<u8>> for BinaryValue {
79 fn from(bytes: Vec<u8>) -> Self {
80 Self(bytes.into())
81 }
82}
83
84impl AsRef<[u8]> for BinaryValue {
85 fn as_ref(&self) -> &[u8] {
86 self.as_bytes()
87 }
88}
89
90#[derive(Clone, Debug, PartialEq)]
97pub enum VectorValue {
98 Int(Vector<i32>),
100 Float(Vector<f32>),
102}
103
104impl VectorValue {
105 fn into_json_value(self) -> serde_json::Value {
111 match self {
112 VectorValue::Int(vector) => {
113 serde_json::Value::Array(vector.into_vec().into_iter().map(Into::into).collect())
114 }
115 VectorValue::Float(vector) => serde_json::Value::Array(
116 vector
117 .into_vec()
118 .into_iter()
119 .map(f32_element_to_json_value)
120 .collect(),
121 ),
122 }
123 }
124}
125
126fn f32_element_to_json_value(value: f32) -> serde_json::Value {
127 if value.is_nan() {
128 serde_json::Value::String("nan".to_string())
129 } else if value.is_infinite() {
130 let token = if value.is_sign_positive() {
131 "inf"
132 } else {
133 "-inf"
134 };
135 serde_json::Value::String(token.to_string())
136 } else {
137 match serde_json::Number::from_f64(f64::from(value)) {
138 Some(number) => serde_json::Value::Number(number),
139 None => serde_json::Value::String(value.to_string()),
140 }
141 }
142}
143
144#[derive(Clone, Debug, PartialEq)]
161#[non_exhaustive]
162pub enum CellValue {
163 Null,
165 Boolean(bool),
167 Integer(i128),
169 Float(f64),
171 Decimal(DecimalValue),
173 String(String),
175 Date(NaiveDate),
177 Time(NaiveTime),
179 TimestampNtz(NaiveDateTime),
181 TimestampLtz(DateTime<Utc>),
183 TimestampTz(DateTime<FixedOffset>),
185 Json(serde_json::Value),
190 Binary(BinaryValue),
192 Vector(VectorValue),
194}
195
196impl CellValue {
197 pub fn is_null(&self) -> bool {
208 matches!(self, CellValue::Null)
209 }
210}
211
212#[derive(Clone, Debug, PartialEq)]
216pub struct DynamicRow {
217 schema: Arc<Schema>,
218 values: Box<[CellValue]>,
219}
220
221impl DynamicRow {
222 pub fn schema(&self) -> &Schema {
224 &self.schema
225 }
226
227 pub fn values(&self) -> &[CellValue] {
229 &self.values
230 }
231
232 pub fn value(&self, name: &str) -> StdResult<&CellValue, SchemaError> {
238 let idx = self.schema.column_index(name)?;
239 self.value_at(idx)
240 }
241
242 pub fn value_at(&self, index: usize) -> StdResult<&CellValue, SchemaError> {
248 self.values.get(index).ok_or_else(|| {
249 SchemaError::InvalidColumnIndex(InvalidColumnIndexError::new(index, self.schema.len()))
250 })
251 }
252
253 pub fn take(&mut self, name: &str) -> StdResult<CellValue, SchemaError> {
260 let idx = self.schema.column_index(name)?;
261 self.take_at(idx)
262 }
263
264 pub fn take_at(&mut self, index: usize) -> StdResult<CellValue, SchemaError> {
271 let column_count = self.schema.len();
272 let slot = self.values.get_mut(index).ok_or_else(|| {
273 SchemaError::InvalidColumnIndex(InvalidColumnIndexError::new(index, column_count))
274 })?;
275
276 Ok(mem::replace(slot, CellValue::Null))
277 }
278
279 pub fn into_parts(self) -> (Arc<Schema>, Box<[CellValue]>) {
281 (self.schema, self.values)
282 }
283
284 pub fn into_json_object(
290 self,
291 ) -> StdResult<serde_json::Map<String, serde_json::Value>, SchemaError> {
292 let DynamicRow { schema, values } = self;
293
294 let mut map = serde_json::Map::new();
295 for (col, value) in schema.columns().iter().zip(values.into_vec()) {
296 if map.contains_key(col.name()) {
297 return Err(SchemaError::DuplicateColumnName(
298 DuplicateColumnNameError::new(col.name()),
299 ));
300 }
301 map.insert(col.name().to_string(), value.into_json_value());
302 }
303
304 Ok(map)
305 }
306}
307
308impl CellValue {
309 pub fn into_json_value(self) -> serde_json::Value {
315 match self {
316 CellValue::Null => serde_json::Value::Null,
317 CellValue::Boolean(b) => serde_json::Value::Bool(b),
318 CellValue::Integer(i) => match serde_json::Number::from_i128(i) {
319 Some(n) => serde_json::Value::Number(n),
320 None => serde_json::Value::String(i.to_string()),
321 },
322 CellValue::Float(f) => match serde_json::Number::from_f64(f) {
323 Some(n) => serde_json::Value::Number(n),
324 None => serde_json::Value::String(f.to_string()),
325 },
326 CellValue::Decimal(d) => serde_json::Value::String(d.raw().to_string()),
327 CellValue::String(s) => serde_json::Value::String(s),
328 CellValue::Date(d) => serde_json::Value::String(d.format("%Y-%m-%d").to_string()),
329 CellValue::Time(t) => serde_json::Value::String(t.format("%H:%M:%S%.f").to_string()),
330 CellValue::TimestampNtz(dt) => {
331 serde_json::Value::String(dt.format("%Y-%m-%dT%H:%M:%S%.f").to_string())
332 }
333 CellValue::TimestampLtz(dt) => serde_json::Value::String(dt.to_rfc3339()),
334 CellValue::TimestampTz(dt) => serde_json::Value::String(dt.to_rfc3339()),
335 CellValue::Json(v) => v,
336 CellValue::Binary(bytes) => serde_json::Value::String(
337 base64::engine::general_purpose::STANDARD.encode(bytes.as_bytes()),
338 ),
339 CellValue::Vector(vector) => vector.into_json_value(),
340 }
341 }
342}
343
344impl FromRow for DynamicRow {
345 type Plan = Arc<Schema>;
346
347 fn build_plan(ctx: RowPlanContext<'_>) -> PlanBuildResult<Self::Plan> {
348 Ok(ctx.shared_schema())
349 }
350
351 fn from_row_with_plan(row: RowRef<'_>, plan: &Self::Plan) -> RowDecodeResult<Self> {
352 let mut values = Vec::with_capacity(plan.len());
353 for (offset, col) in plan.columns().iter().enumerate() {
354 let cell = row.cell_at_offset(col, offset);
355 values.push(decode_dynamic(cell).map_err(|issue| {
356 CellDecodeError::new(
357 cell.row_index(),
358 cell.column().index(),
359 cell.column().name(),
360 type_name::<CellValue>(),
361 cell.column().ty().clone(),
362 cell.raw(),
363 issue,
364 )
365 })?);
366 }
367
368 Ok(DynamicRow {
369 schema: Arc::clone(plan),
370 values: values.into_boxed_slice(),
371 })
372 }
373}
374
375fn decode_dynamic(cell: CellRef<'_>) -> CellDecodeResult<CellValue> {
376 if cell.is_null() {
377 return Ok(CellValue::Null);
378 }
379
380 let raw = cell.raw().expect("non-null checked above");
381 let ty = cell.column().ty();
382
383 match ty {
384 ColumnType::Boolean => {
385 if raw == "1" || raw.eq_ignore_ascii_case("true") {
386 Ok(CellValue::Boolean(true))
387 } else if raw == "0" || raw.eq_ignore_ascii_case("false") {
388 Ok(CellValue::Boolean(false))
389 } else {
390 Err(CellConversionError::builder(format!("'{raw}' is not bool")).build())
391 }
392 }
393 ColumnType::Fixed { scale, .. } => {
394 if scale.unwrap_or(0) == 0 {
395 if let Ok(v) = raw.parse::<i128>() {
396 return Ok(CellValue::Integer(v));
397 }
398 }
399 Ok(CellValue::Decimal(DecimalValue::new(raw)))
400 }
401 ColumnType::Real => raw.parse::<f64>().map(CellValue::Float).map_err(|e| {
402 CellConversionError::builder(format!("parse error: {e}"))
403 .source(e)
404 .build()
405 }),
406 ColumnType::Text { .. } => Ok(CellValue::String(raw.to_string())),
407 ColumnType::Date => {
408 let days = raw.parse::<i64>().map_err(|e| {
409 CellConversionError::builder(format!("'{raw}' not Date"))
410 .source(e)
411 .build()
412 })?;
413 let epoch = NaiveDate::from_ymd_opt(1970, 1, 1).expect("epoch");
414 let date = if days >= 0 {
415 epoch.checked_add_days(chrono::Days::new(days as u64))
416 } else {
417 epoch.checked_sub_days(chrono::Days::new(days.unsigned_abs()))
418 }
419 .ok_or_else(|| CellConversionError::builder(format!("'{raw}' not Date")).build())?;
420 Ok(CellValue::Date(date))
421 }
422 ColumnType::Time { scale } => {
423 let scale = match scale {
424 None => 0usize,
425 Some(s) if (0..=9).contains(s) => *s as usize,
426 Some(s) => {
427 return Err(
428 CellConversionError::builder(format!("invalid time scale: {s}")).build(),
429 );
430 }
431 };
432 let (secs, nsec) = parse_time_seconds_and_nanos(raw, scale)
433 .map_err(|m| CellConversionError::builder(m).build())?;
434 let t = NaiveTime::from_num_seconds_from_midnight_opt(secs, nsec).ok_or_else(|| {
435 CellConversionError::builder(format!("invalid time: {raw}")).build()
436 })?;
437 Ok(CellValue::Time(t))
438 }
439 ColumnType::TimestampNtz { scale } => {
440 let scale = scale.unwrap_or(9);
441 let dt = parse_timestamp_epoch(raw, scale)
442 .map_err(|m| CellConversionError::builder(m).build())?;
443 Ok(CellValue::TimestampNtz(dt.naive_utc()))
444 }
445 ColumnType::TimestampLtz { scale } => {
446 let scale = scale.unwrap_or(9);
447 let dt = parse_timestamp_epoch(raw, scale)
448 .map_err(|m| CellConversionError::builder(m).build())?;
449 Ok(CellValue::TimestampLtz(dt))
450 }
451 ColumnType::TimestampTz { scale } => {
452 let scale = scale.unwrap_or(9);
453 let dt = parse_timestamp_tz_with_offset(raw, scale)
454 .map_err(|m| CellConversionError::builder(m).build())?;
455 Ok(CellValue::TimestampTz(dt))
456 }
457 ColumnType::Variant | ColumnType::Object | ColumnType::Array => {
458 Ok(CellValue::Json(decode_json_payload(raw)?))
459 }
460 ColumnType::Binary { .. } => {
461 let bytes = decode_hex(raw).map_err(|m| CellConversionError::builder(m).build())?;
462 Ok(CellValue::Binary(BinaryValue::new(bytes)))
463 }
464 ColumnType::Vector => decode_vector_dynamic(raw).map(CellValue::Vector),
465 ColumnType::Geography | ColumnType::Geometry | ColumnType::Unknown { .. } => {
466 Ok(CellValue::String(raw.to_string()))
467 }
468 }
469}
470
471fn decode_vector_dynamic(raw: &str) -> CellDecodeResult<VectorValue> {
476 if let Ok(ints) = parse_vector_i32_payload(raw) {
477 return Ok(VectorValue::Int(Vector::from_vec(ints)));
478 }
479 parse_vector_f32_payload(raw)
480 .map(|floats| VectorValue::Float(Vector::from_vec(floats)))
481 .map_err(|m| CellConversionError::builder(m).build())
482}
483
484#[cfg(test)]
485mod tests {
486 use std::ptr;
487
488 use super::*;
489 use crate::result_table::{
490 ColumnType,
491 test_data::{make_result_table_from_rows, make_schema},
492 };
493
494 fn one_cell_row(ty: ColumnType, value: &str) -> DynamicRow {
495 let schema = make_schema(vec![("PAYLOAD".to_string(), ty, true)]);
496 let table =
497 make_result_table_from_rows(schema, vec![vec![Some(value.to_string())]]).unwrap();
498 table.dynamic_rows().unwrap().next().unwrap().unwrap()
499 }
500
501 #[test]
502 fn dynamic_row_keeps_text_cells_as_strings() {
503 for value in [r#"{"a":1}"#, "plain text"] {
504 let row = one_cell_row(ColumnType::Text { length: None }, value);
505 match row.value("PAYLOAD").unwrap() {
506 CellValue::String(actual) => assert_eq!(actual, value),
507 other => panic!("expected String, got {other:?}"),
508 }
509 }
510 }
511
512 #[test]
513 fn dynamic_row_decodes_variant_cells_as_json() {
514 let row = one_cell_row(ColumnType::Variant, r#"{"a":1}"#);
515 match row.value("PAYLOAD").unwrap() {
516 CellValue::Json(value) => assert_eq!(value["a"], 1),
517 other => panic!("expected Json, got {other:?}"),
518 }
519 }
520
521 #[test]
522 fn dynamic_row_decode_failure_reports_contextual_error() {
523 let schema = make_schema(vec![("PAYLOAD".to_string(), ColumnType::Boolean, false)]);
524 let table =
525 make_result_table_from_rows(schema, vec![vec![Some("maybe".to_string())]]).unwrap();
526
527 let err = table.dynamic_rows().unwrap().next().unwrap().unwrap_err();
528 let decode = err
529 .as_cell_decode_error()
530 .expect("dynamic row decode should yield CellDecodeError");
531
532 assert_eq!(decode.row_index(), 0);
533 assert_eq!(decode.column_name(), "PAYLOAD");
534 assert_eq!(decode.conversion_error().reason(), "'maybe' is not bool");
535 assert!(decode.target_type_name().ends_with("CellValue"));
536 assert_eq!(decode.raw_value_preview(), Some("maybe"));
537 }
538
539 #[test]
540 fn dynamic_row_decodes_integer_vector() {
541 let row = one_cell_row(ColumnType::Vector, "[1,2,3]");
542 match row.value("PAYLOAD").unwrap() {
543 CellValue::Vector(VectorValue::Int(vector)) => {
544 assert_eq!(vector.as_slice(), &[1, 2, 3]);
545 }
546 other => panic!("expected Vector(Int), got {other:?}"),
547 }
548 }
549
550 #[test]
551 fn dynamic_row_decodes_float_vector_from_decimal_payload() {
552 let row = one_cell_row(ColumnType::Vector, "[1.500000,-2.250000,0.000000]");
553 match row.value("PAYLOAD").unwrap() {
554 CellValue::Vector(VectorValue::Float(vector)) => {
555 assert_eq!(vector.as_slice(), &[1.5f32, -2.25, 0.0]);
556 }
557 other => panic!("expected Vector(Float), got {other:?}"),
558 }
559 }
560
561 #[test]
562 fn dynamic_row_decodes_float_vector_from_non_finite_payload() {
563 let row = one_cell_row(ColumnType::Vector, "[1.000000,nan,-inf]");
564 match row.value("PAYLOAD").unwrap() {
565 CellValue::Vector(VectorValue::Float(vector)) => {
566 let slice = vector.as_slice();
567 assert_eq!(slice[0], 1.0f32);
568 assert!(slice[1].is_nan());
569 assert!(slice[2].is_infinite() && slice[2].is_sign_negative());
570 }
571 other => panic!("expected Vector(Float), got {other:?}"),
572 }
573 }
574
575 #[test]
576 fn dynamic_row_malformed_vector_reports_contextual_error() {
577 let schema = make_schema(vec![("PAYLOAD".to_string(), ColumnType::Vector, true)]);
578 let table =
579 make_result_table_from_rows(schema, vec![vec![Some("[abc]".to_string())]]).unwrap();
580
581 let err = table.dynamic_rows().unwrap().next().unwrap().unwrap_err();
582 let decode = err
583 .as_cell_decode_error()
584 .expect("malformed vector should yield CellDecodeError");
585 assert_eq!(decode.column_name(), "PAYLOAD");
586 assert!(
587 decode
588 .conversion_error()
589 .reason()
590 .contains("invalid VECTOR(FLOAT) element"),
591 "actual: {}",
592 decode.conversion_error().reason()
593 );
594 }
595
596 #[test]
597 fn float_vector_into_json_value_stringifies_non_finite_elements() {
598 let value = CellValue::Vector(VectorValue::Float(Vector::from_vec(vec![
599 1.5,
600 f32::INFINITY,
601 f32::NEG_INFINITY,
602 f32::NAN,
603 ])));
604 assert_eq!(
605 value.into_json_value(),
606 serde_json::json!([1.5, "inf", "-inf", "nan"])
607 );
608 }
609
610 #[test]
611 fn integer_vector_into_json_value_is_number_array() {
612 let value = CellValue::Vector(VectorValue::Int(Vector::from_vec(vec![1, -2, 3])));
613 assert_eq!(value.into_json_value(), serde_json::json!([1, -2, 3]));
614 }
615
616 #[test]
617 fn dynamic_row_value_at_rejects_invalid_indices() {
618 let row = one_cell_row(ColumnType::Text { length: None }, "value");
619 let index = 1;
620 assert!(matches!(
621 row.value_at(index),
622 Err(SchemaError::InvalidColumnIndex(error))
623 if error.index() == index && error.column_count() == 1
624 ));
625 }
626
627 #[test]
628 fn dynamic_row_value_returns_exact_label_match() {
629 let schema = make_schema(vec![
630 (
631 "ID".to_string(),
632 ColumnType::Fixed {
633 precision: None,
634 scale: Some(0),
635 },
636 false,
637 ),
638 (
639 "id".to_string(),
640 ColumnType::Fixed {
641 precision: None,
642 scale: Some(0),
643 },
644 false,
645 ),
646 ]);
647 let table = make_result_table_from_rows(
648 schema,
649 vec![vec![Some("1".to_string()), Some("2".to_string())]],
650 )
651 .unwrap();
652 let row = table.dynamic_rows().unwrap().next().unwrap().unwrap();
653
654 assert_eq!(row.value("ID").unwrap(), &CellValue::Integer(1));
655 assert_eq!(row.value("id").unwrap(), &CellValue::Integer(2));
656 }
657
658 #[test]
659 fn dynamic_row_take_at_replaces_slots_with_null() {
660 let mut row = one_cell_row(ColumnType::Text { length: None }, "value");
661 let index = row.schema().column_index("PAYLOAD").unwrap();
662
663 assert_eq!(
664 row.take_at(index).unwrap(),
665 CellValue::String("value".into())
666 );
667 assert_eq!(row.value_at(index).unwrap(), &CellValue::Null);
668 assert_eq!(row.take_at(index).unwrap(), CellValue::Null);
669 }
670
671 #[test]
672 fn dynamic_row_take_at_rejects_invalid_indices() {
673 let mut row = one_cell_row(ColumnType::Text { length: None }, "value");
674 let index = 1;
675 assert!(matches!(
676 row.take_at(index),
677 Err(SchemaError::InvalidColumnIndex(error))
678 if error.index() == index && error.column_count() == 1
679 ));
680 }
681
682 #[test]
683 fn dynamic_row_take_resolves_label_and_replaces_slot() {
684 let mut row = one_cell_row(ColumnType::Text { length: None }, "value");
685
686 assert_eq!(
687 row.take("PAYLOAD").unwrap(),
688 CellValue::String("value".into())
689 );
690 assert_eq!(row.value("PAYLOAD").unwrap(), &CellValue::Null,);
691 assert_eq!(row.take("PAYLOAD").unwrap(), CellValue::Null);
692 }
693
694 #[test]
695 fn dynamic_row_take_reports_missing_column_for_unknown_label() {
696 let mut row = one_cell_row(ColumnType::Text { length: None }, "value");
697 assert!(matches!(
698 row.take("missing"),
699 Err(SchemaError::MissingColumn(error)) if error.name() == "missing"
700 ));
701 }
702
703 #[test]
704 fn dynamic_row_into_parts_preserves_schema_and_supports_walk() {
705 let schema = make_schema(vec![
706 (
707 "ID".to_string(),
708 ColumnType::Fixed {
709 precision: None,
710 scale: Some(0),
711 },
712 false,
713 ),
714 (
715 "PAYLOAD".to_string(),
716 ColumnType::Text { length: None },
717 true,
718 ),
719 ]);
720 let table = make_result_table_from_rows(
721 schema,
722 vec![vec![Some("1".to_string()), Some("value".to_string())]],
723 )
724 .unwrap();
725 let row = table.dynamic_rows().unwrap().next().unwrap().unwrap();
726
727 let (schema, values) = row.into_parts();
728 assert!(ptr::eq(schema.as_ref(), table.schema()));
729 let walked = schema
730 .columns()
731 .iter()
732 .zip(values.into_vec())
733 .map(|(column, value)| (column.name().to_string(), value))
734 .collect::<Vec<_>>();
735 assert_eq!(
736 walked,
737 vec![
738 ("ID".to_string(), CellValue::Integer(1)),
739 (
740 "PAYLOAD".to_string(),
741 CellValue::String("value".to_string())
742 ),
743 ]
744 );
745 }
746
747 #[test]
748 fn dynamic_row_into_json_object_rejects_duplicate_labels() {
749 let schema = make_schema(vec![
750 (
751 "id".to_string(),
752 ColumnType::Fixed {
753 precision: None,
754 scale: Some(0),
755 },
756 false,
757 ),
758 (
759 "id".to_string(),
760 ColumnType::Fixed {
761 precision: None,
762 scale: Some(0),
763 },
764 false,
765 ),
766 ]);
767 let table = make_result_table_from_rows(
768 schema,
769 vec![vec![Some("1".to_string()), Some("2".to_string())]],
770 )
771 .unwrap();
772 let row = table.dynamic_rows().unwrap().next().unwrap().unwrap();
773
774 assert!(matches!(
775 row.into_json_object(),
776 Err(SchemaError::DuplicateColumnName(error)) if error.name() == "id"
777 ));
778 }
779
780 #[test]
781 fn dynamic_row_paths_share_the_table_schema() {
782 let schema = make_schema(vec![(
783 "PAYLOAD".to_string(),
784 ColumnType::Text { length: None },
785 true,
786 )]);
787 let table =
788 make_result_table_from_rows(schema, vec![vec![Some("value".to_string())]]).unwrap();
789
790 let generic = table.rows::<DynamicRow>().unwrap().next().unwrap().unwrap();
791 let alias = table.dynamic_rows().unwrap().next().unwrap().unwrap();
792
793 assert!(ptr::eq(generic.schema(), table.schema()));
794 assert!(ptr::eq(alias.schema(), table.schema()));
795 assert!(ptr::eq(generic.schema(), alias.schema()));
796 }
797}