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