use serde::{Deserialize, Serialize};
use crate::error::{ArrayError, ArrayResult};
use crate::sync::hlc::Hlc;
use crate::types::cell_value::value::CellValue;
use crate::types::coord::value::CoordValue;
#[derive(
Copy,
Clone,
Debug,
PartialEq,
Eq,
Serialize,
Deserialize,
zerompk::ToMessagePack,
zerompk::FromMessagePack,
)]
pub enum ArrayOpKind {
Put,
Delete,
Erase,
}
#[derive(
Clone,
Debug,
PartialEq,
Serialize,
Deserialize,
zerompk::ToMessagePack,
zerompk::FromMessagePack,
)]
pub struct ArrayOpHeader {
pub array: String,
pub hlc: Hlc,
pub schema_hlc: Hlc,
pub valid_from_ms: i64,
pub valid_until_ms: i64,
pub system_from_ms: i64,
}
#[derive(
Clone,
Debug,
PartialEq,
Serialize,
Deserialize,
zerompk::ToMessagePack,
zerompk::FromMessagePack,
)]
pub struct ArrayOp {
pub header: ArrayOpHeader,
pub kind: ArrayOpKind,
pub coord: Vec<CoordValue>,
pub attrs: Option<Vec<CellValue>>,
}
impl ArrayOp {
pub fn validate_shape(&self) -> ArrayResult<()> {
match self.kind {
ArrayOpKind::Put => {
if self.attrs.is_none() {
return Err(ArrayError::InvalidOp {
detail: "Put op must carry attrs".into(),
});
}
}
ArrayOpKind::Delete | ArrayOpKind::Erase => {
if self.attrs.is_some() {
return Err(ArrayError::InvalidOp {
detail: format!("{:?} op must not carry attrs", self.kind),
});
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sync::hlc::Hlc;
use crate::sync::replica_id::ReplicaId;
use crate::types::cell_value::value::CellValue;
use crate::types::coord::value::CoordValue;
fn dummy_hlc() -> Hlc {
Hlc::new(1_000, 0, ReplicaId::new(1)).unwrap()
}
fn dummy_header(array: &str) -> ArrayOpHeader {
ArrayOpHeader {
array: array.into(),
hlc: dummy_hlc(),
schema_hlc: dummy_hlc(),
valid_from_ms: 0,
valid_until_ms: -1,
system_from_ms: 1_000,
}
}
fn dummy_coord() -> Vec<CoordValue> {
vec![CoordValue::Int64(0)]
}
fn dummy_attrs() -> Option<Vec<CellValue>> {
Some(vec![CellValue::Null])
}
#[test]
fn put_requires_attrs() {
let op = ArrayOp {
header: dummy_header("t"),
kind: ArrayOpKind::Put,
coord: dummy_coord(),
attrs: None,
};
assert!(matches!(
op.validate_shape(),
Err(ArrayError::InvalidOp { .. })
));
}
#[test]
fn delete_rejects_attrs() {
let op = ArrayOp {
header: dummy_header("t"),
kind: ArrayOpKind::Delete,
coord: dummy_coord(),
attrs: dummy_attrs(),
};
assert!(matches!(
op.validate_shape(),
Err(ArrayError::InvalidOp { .. })
));
}
#[test]
fn erase_rejects_attrs() {
let op = ArrayOp {
header: dummy_header("t"),
kind: ArrayOpKind::Erase,
coord: dummy_coord(),
attrs: dummy_attrs(),
};
assert!(matches!(
op.validate_shape(),
Err(ArrayError::InvalidOp { .. })
));
}
#[test]
fn valid_put_passes() {
let op = ArrayOp {
header: dummy_header("t"),
kind: ArrayOpKind::Put,
coord: dummy_coord(),
attrs: dummy_attrs(),
};
assert!(op.validate_shape().is_ok());
}
#[test]
fn valid_delete_passes() {
let op = ArrayOp {
header: dummy_header("t"),
kind: ArrayOpKind::Delete,
coord: dummy_coord(),
attrs: None,
};
assert!(op.validate_shape().is_ok());
}
#[test]
fn serialize_roundtrip() {
let op = ArrayOp {
header: dummy_header("test_array"),
kind: ArrayOpKind::Put,
coord: dummy_coord(),
attrs: dummy_attrs(),
};
let bytes = zerompk::to_msgpack_vec(&op).expect("serialize");
let back: ArrayOp = zerompk::from_msgpack(&bytes).expect("deserialize");
assert_eq!(op, back);
}
}