modelvault_core/record/
row_value.rs1use std::collections::BTreeMap;
4
5use crate::error::{DbError, FormatError};
6use crate::record::scalar::{
7 decode_tagged_scalar, decode_tagged_string, encode_tagged_scalar, Cursor, ScalarValue,
8};
9use crate::schema::{FieldDef, Type};
10
11#[derive(Debug, Clone, PartialEq)]
13pub enum RowValue {
14 Bool(bool),
15 Int64(i64),
16 Uint64(u64),
17 Float64(f64),
18 String(String),
19 Bytes(Vec<u8>),
20 Uuid([u8; 16]),
21 Timestamp(i64),
22 None,
24 List(Vec<RowValue>),
25 Object(BTreeMap<String, RowValue>),
26}
27
28impl RowValue {
29 pub fn from_scalar(s: ScalarValue) -> Self {
31 match s {
32 ScalarValue::Bool(b) => RowValue::Bool(b),
33 ScalarValue::Int64(n) => RowValue::Int64(n),
34 ScalarValue::Uint64(n) => RowValue::Uint64(n),
35 ScalarValue::Float64(n) => RowValue::Float64(n),
36 ScalarValue::String(x) => RowValue::String(x),
37 ScalarValue::Bytes(b) => RowValue::Bytes(b),
38 ScalarValue::Uuid(u) => RowValue::Uuid(u),
39 ScalarValue::Timestamp(t) => RowValue::Timestamp(t),
40 }
41 }
42
43 pub fn as_scalar(&self) -> Option<ScalarValue> {
45 Some(match self {
46 RowValue::Bool(b) => ScalarValue::Bool(*b),
47 RowValue::Int64(n) => ScalarValue::Int64(*n),
48 RowValue::Uint64(n) => ScalarValue::Uint64(*n),
49 RowValue::Float64(n) => ScalarValue::Float64(*n),
50 RowValue::String(s) => ScalarValue::String(s.clone()),
51 RowValue::Bytes(b) => ScalarValue::Bytes(b.clone()),
52 RowValue::Uuid(u) => ScalarValue::Uuid(*u),
53 RowValue::Timestamp(t) => ScalarValue::Timestamp(*t),
54 _ => return None,
55 })
56 }
57
58 pub fn into_scalar(self) -> Result<ScalarValue, DbError> {
60 self.as_scalar()
61 .ok_or(DbError::Format(FormatError::RecordPayloadTypeMismatch))
62 }
63
64 #[inline]
65 pub(crate) fn as_object_map(&self) -> Option<&BTreeMap<String, RowValue>> {
66 match self {
67 RowValue::Object(m) => Some(m),
68 _ => None,
69 }
70 }
71}
72
73pub fn encode_row_value(out: &mut Vec<u8>, v: &RowValue, ty: &Type) -> Result<(), DbError> {
75 match ty {
76 Type::Bool
77 | Type::Int64
78 | Type::Uint64
79 | Type::Float64
80 | Type::String
81 | Type::Bytes
82 | Type::Uuid
83 | Type::Timestamp => {
84 let s = v
85 .as_scalar()
86 .ok_or(DbError::Format(FormatError::RecordPayloadTypeMismatch))?;
87 encode_tagged_scalar(out, &s, ty)
88 }
89 Type::Optional(inner) => {
90 if matches!(v, RowValue::None) {
91 out.push(0);
92 Ok(())
93 } else {
94 out.push(1);
95 encode_row_value(out, v, inner)
96 }
97 }
98 Type::List(inner) => {
99 let RowValue::List(items) = v else {
100 return Err(DbError::Format(FormatError::RecordPayloadTypeMismatch));
101 };
102 out.extend_from_slice(&(items.len() as u32).to_le_bytes());
103 for item in items {
104 encode_row_value(out, item, inner)?;
105 }
106 Ok(())
107 }
108 Type::Object(fields) => {
109 let RowValue::Object(map) = v else {
110 return Err(DbError::Format(FormatError::RecordPayloadTypeMismatch));
111 };
112 for def in fields {
113 let key = def.path.0[0].as_ref();
114 let fv = map
115 .get(key)
116 .ok_or(DbError::Format(FormatError::TruncatedRecordPayload))?;
117 encode_row_value(out, fv, &def.ty)?;
118 }
119 Ok(())
120 }
121 Type::Enum(_) => {
122 let RowValue::String(s) = v else {
123 return Err(DbError::Format(FormatError::RecordPayloadTypeMismatch));
124 };
125 encode_tagged_scalar(out, &ScalarValue::String(s.clone()), &Type::String)
126 }
127 }
128}
129
130pub fn decode_row_value(cur: &mut Cursor<'_>, ty: &Type) -> Result<RowValue, DbError> {
132 Ok(match ty {
133 Type::Bool
134 | Type::Int64
135 | Type::Uint64
136 | Type::Float64
137 | Type::String
138 | Type::Bytes
139 | Type::Uuid
140 | Type::Timestamp => RowValue::from_scalar(decode_tagged_scalar(cur, ty)?),
141 Type::Optional(inner) => {
142 let pres = cur.take_u8()?;
143 match pres {
144 0 => RowValue::None,
145 1 => decode_row_value(cur, inner)?,
146 _ => return Err(DbError::Format(FormatError::RecordPayloadTypeMismatch)),
147 }
148 }
149 Type::List(inner) => {
150 let n = cur.take_u32()? as usize;
151 let mut items = Vec::with_capacity(n.min(1_048_576));
152 for _ in 0..n {
153 items.push(decode_row_value(cur, inner)?);
154 }
155 RowValue::List(items)
156 }
157 Type::Object(fields) => {
158 let mut map = BTreeMap::new();
159 for def in fields {
160 let key = def.path.0[0].to_string();
161 let val = decode_row_value(cur, &def.ty)?;
162 map.insert(key, val);
163 }
164 RowValue::Object(map)
165 }
166 Type::Enum(_) => RowValue::String(decode_tagged_string(cur)?),
167 })
168}
169
170pub fn non_pk_defs_in_order<'a>(fields: &'a [FieldDef], pk_name: &str) -> Vec<&'a FieldDef> {
172 fields
173 .iter()
174 .filter(|f| f.path.0.len() == 1 && f.path.0[0] != pk_name)
175 .collect()
176}