1use serde::{Deserialize, Serialize};
10
11use crate::error::{ArrayError, ArrayResult};
12use crate::sync::hlc::Hlc;
13use crate::types::cell_value::value::CellValue;
14use crate::types::coord::value::CoordValue;
15
16#[derive(
18 Copy,
19 Clone,
20 Debug,
21 PartialEq,
22 Eq,
23 Serialize,
24 Deserialize,
25 zerompk::ToMessagePack,
26 zerompk::FromMessagePack,
27)]
28pub enum ArrayOpKind {
29 Put,
31 Delete,
33 Erase,
35}
36
37#[derive(
43 Clone,
44 Debug,
45 PartialEq,
46 Serialize,
47 Deserialize,
48 zerompk::ToMessagePack,
49 zerompk::FromMessagePack,
50)]
51pub struct ArrayOpHeader {
52 pub array: String,
54 pub hlc: Hlc,
56 pub schema_hlc: Hlc,
60 pub valid_from_ms: i64,
62 pub valid_until_ms: i64,
64 pub system_from_ms: i64,
66}
67
68#[derive(
76 Clone,
77 Debug,
78 PartialEq,
79 Serialize,
80 Deserialize,
81 zerompk::ToMessagePack,
82 zerompk::FromMessagePack,
83)]
84pub struct ArrayOp {
85 pub header: ArrayOpHeader,
87 pub kind: ArrayOpKind,
89 pub coord: Vec<CoordValue>,
91 pub attrs: Option<Vec<CellValue>>,
93}
94
95impl ArrayOp {
96 pub fn validate_shape(&self) -> ArrayResult<()> {
100 match self.kind {
101 ArrayOpKind::Put => {
102 if self.attrs.is_none() {
103 return Err(ArrayError::InvalidOp {
104 detail: "Put op must carry attrs".into(),
105 });
106 }
107 }
108 ArrayOpKind::Delete | ArrayOpKind::Erase => {
109 if self.attrs.is_some() {
110 return Err(ArrayError::InvalidOp {
111 detail: format!("{:?} op must not carry attrs", self.kind),
112 });
113 }
114 }
115 }
116 Ok(())
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use crate::sync::hlc::Hlc;
124 use crate::sync::replica_id::ReplicaId;
125 use crate::types::cell_value::value::CellValue;
126 use crate::types::coord::value::CoordValue;
127
128 fn dummy_hlc() -> Hlc {
129 Hlc::new(1_000, 0, ReplicaId::new(1)).unwrap()
130 }
131
132 fn dummy_header(array: &str) -> ArrayOpHeader {
133 ArrayOpHeader {
134 array: array.into(),
135 hlc: dummy_hlc(),
136 schema_hlc: dummy_hlc(),
137 valid_from_ms: 0,
138 valid_until_ms: -1,
139 system_from_ms: 1_000,
140 }
141 }
142
143 fn dummy_coord() -> Vec<CoordValue> {
144 vec![CoordValue::Int64(0)]
145 }
146
147 fn dummy_attrs() -> Option<Vec<CellValue>> {
148 Some(vec![CellValue::Null])
149 }
150
151 #[test]
152 fn put_requires_attrs() {
153 let op = ArrayOp {
154 header: dummy_header("t"),
155 kind: ArrayOpKind::Put,
156 coord: dummy_coord(),
157 attrs: None,
158 };
159 assert!(matches!(
160 op.validate_shape(),
161 Err(ArrayError::InvalidOp { .. })
162 ));
163 }
164
165 #[test]
166 fn delete_rejects_attrs() {
167 let op = ArrayOp {
168 header: dummy_header("t"),
169 kind: ArrayOpKind::Delete,
170 coord: dummy_coord(),
171 attrs: dummy_attrs(),
172 };
173 assert!(matches!(
174 op.validate_shape(),
175 Err(ArrayError::InvalidOp { .. })
176 ));
177 }
178
179 #[test]
180 fn erase_rejects_attrs() {
181 let op = ArrayOp {
182 header: dummy_header("t"),
183 kind: ArrayOpKind::Erase,
184 coord: dummy_coord(),
185 attrs: dummy_attrs(),
186 };
187 assert!(matches!(
188 op.validate_shape(),
189 Err(ArrayError::InvalidOp { .. })
190 ));
191 }
192
193 #[test]
194 fn valid_put_passes() {
195 let op = ArrayOp {
196 header: dummy_header("t"),
197 kind: ArrayOpKind::Put,
198 coord: dummy_coord(),
199 attrs: dummy_attrs(),
200 };
201 assert!(op.validate_shape().is_ok());
202 }
203
204 #[test]
205 fn valid_delete_passes() {
206 let op = ArrayOp {
207 header: dummy_header("t"),
208 kind: ArrayOpKind::Delete,
209 coord: dummy_coord(),
210 attrs: None,
211 };
212 assert!(op.validate_shape().is_ok());
213 }
214
215 #[test]
216 fn serialize_roundtrip() {
217 let op = ArrayOp {
218 header: dummy_header("test_array"),
219 kind: ArrayOpKind::Put,
220 coord: dummy_coord(),
221 attrs: dummy_attrs(),
222 };
223 let bytes = zerompk::to_msgpack_vec(&op).expect("serialize");
224 let back: ArrayOp = zerompk::from_msgpack(&bytes).expect("deserialize");
225 assert_eq!(op, back);
226 }
227}