Skip to main content

nodedb_types/
array_cell.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Typed payload for `Value::ArrayCell`.
4//!
5//! An `ArrayCell` carries a single N-dimensional array cell across the
6//! SQL / wire boundary: its coordinates (one `Value` per dimension) and
7//! its attributes (one `Value` per attribute column). The array engine
8//! converts between its own typed `CoordValue` / `CellValue` and this
9//! generic carrier at engine boundaries.
10//!
11//! Using `nodedb_types::Value` for both coords and attrs keeps
12//! `nodedb-types` free of any dependency on `nodedb-array`.
13//!
14//! Two cells are equal when their coords and attrs are structurally
15//! equal. Ordering is lexicographic on coords first, then attrs — this
16//! matches N-d array semantics where cells are ordered by coordinate.
17
18use serde::{Deserialize, Serialize};
19
20use crate::value::Value;
21
22/// One N-dimensional array cell — coordinates plus attribute values.
23#[derive(
24    Debug,
25    Clone,
26    PartialEq,
27    Serialize,
28    Deserialize,
29    zerompk::ToMessagePack,
30    zerompk::FromMessagePack,
31)]
32pub struct ArrayCell {
33    /// One `Value` per dimension. Length = rank of the array.
34    pub coords: Vec<Value>,
35    /// One `Value` per attribute column.
36    pub attrs: Vec<Value>,
37    /// System-time of this cell version in milliseconds since Unix epoch.
38    ///
39    /// `Some` only on audit-log reads (`AS OF SYSTEM TIME NULL`), where each
40    /// row represents one system-time version of the cell. `None` on live and
41    /// point-in-time reads — those return the ceiling-resolved state with no
42    /// per-row version column.
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub system_time: Option<i64>,
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    fn sample_cell() -> ArrayCell {
52        ArrayCell {
53            coords: vec![Value::Integer(1), Value::Integer(2)],
54            attrs: vec![Value::Float(3.5), Value::String("label".into())],
55            system_time: None,
56        }
57    }
58
59    fn sample_cell_with_system_time(ts: i64) -> ArrayCell {
60        ArrayCell {
61            coords: vec![Value::Integer(1), Value::Integer(2)],
62            attrs: vec![Value::Float(3.5), Value::String("label".into())],
63            system_time: Some(ts),
64        }
65    }
66
67    #[test]
68    fn zerompk_roundtrip() {
69        let cell = sample_cell();
70        let bytes = zerompk::to_msgpack_vec(&cell).expect("encode");
71        let decoded: ArrayCell = zerompk::from_msgpack(&bytes).expect("decode");
72        assert_eq!(decoded, cell);
73    }
74
75    #[test]
76    fn json_roundtrip() {
77        let cell = sample_cell();
78        let s = sonic_rs::to_string(&cell).expect("json encode");
79        let decoded: ArrayCell = sonic_rs::from_str(&s).expect("json decode");
80        assert_eq!(decoded, cell);
81    }
82
83    #[test]
84    fn equality_structural() {
85        let a = sample_cell();
86        let b = sample_cell();
87        assert_eq!(a, b);
88
89        let mut c = sample_cell();
90        c.coords[0] = Value::Integer(999);
91        assert_ne!(a, c);
92    }
93
94    #[test]
95    fn zerompk_roundtrip_with_system_time() {
96        let cell = sample_cell_with_system_time(1_700_000_000_000);
97        let bytes = zerompk::to_msgpack_vec(&cell).expect("encode");
98        let decoded: ArrayCell = zerompk::from_msgpack(&bytes).expect("decode");
99        assert_eq!(decoded, cell);
100        assert_eq!(decoded.system_time, Some(1_700_000_000_000));
101    }
102
103    #[test]
104    fn system_time_none_is_absent_in_json() {
105        let cell = sample_cell();
106        let s = sonic_rs::to_string(&cell).expect("json encode");
107        assert!(
108            !s.contains("system_time"),
109            "system_time must be absent when None: {s}"
110        );
111    }
112
113    #[test]
114    fn system_time_some_roundtrips_through_json() {
115        let cell = sample_cell_with_system_time(1_700_000_000_000);
116        let s = sonic_rs::to_string(&cell).expect("json encode");
117        let decoded: ArrayCell = sonic_rs::from_str(&s).expect("json decode");
118        assert_eq!(decoded.system_time, Some(1_700_000_000_000));
119    }
120}