1use std::collections::BTreeSet;
9use std::fmt;
10
11use crate::query_with_params::{decode_value, encode_value, ParamCodecError, ParamValue};
12
13pub const FEATURE_TYPED_MUTATIONS: u32 = 0x0000_0004;
14pub const TYPED_INSERT_VERSION: u8 = 1;
15
16#[derive(Debug, Clone, PartialEq)]
17pub struct TypedInsertField {
18 pub name: String,
19 pub value: ParamValue,
20}
21
22#[derive(Debug, Clone, PartialEq)]
23pub struct TypedInsertRequest {
24 pub collection: String,
25 pub fields: Vec<TypedInsertField>,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum TypedInsertError {
30 UnsupportedVersion(u8),
31 LengthOverflow(&'static str),
32 Truncated(&'static str),
33 InvalidUtf8(&'static str),
34 EmptyCollection,
35 EmptyFieldName,
36 DuplicateField(String),
37 Value(ParamCodecError),
38 TrailingBytes(usize),
39}
40
41impl fmt::Display for TypedInsertError {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 match self {
44 Self::UnsupportedVersion(version) => {
45 write!(f, "unsupported typed insert version {version}")
46 }
47 Self::LengthOverflow(field) => write!(f, "{field} is too large for typed insert v1"),
48 Self::Truncated(field) => write!(f, "truncated {field}"),
49 Self::InvalidUtf8(field) => write!(f, "{field} must be valid UTF-8"),
50 Self::EmptyCollection => write!(f, "collection cannot be empty"),
51 Self::EmptyFieldName => write!(f, "field name cannot be empty"),
52 Self::DuplicateField(name) => write!(f, "duplicate field '{name}'"),
53 Self::Value(error) => write!(f, "invalid field value: {error}"),
54 Self::TrailingBytes(count) => write!(f, "{count} trailing bytes after typed insert"),
55 }
56 }
57}
58
59impl std::error::Error for TypedInsertError {}
60
61pub fn encode_typed_insert(request: &TypedInsertRequest) -> Result<Vec<u8>, TypedInsertError> {
62 validate_name(&request.collection, "collection")?;
63 let collection_len = u16::try_from(request.collection.len())
64 .map_err(|_| TypedInsertError::LengthOverflow("collection"))?;
65 let field_count = u16::try_from(request.fields.len())
66 .map_err(|_| TypedInsertError::LengthOverflow("fields"))?;
67
68 let mut names = BTreeSet::new();
69 let mut out = Vec::new();
70 out.push(TYPED_INSERT_VERSION);
71 out.extend_from_slice(&collection_len.to_le_bytes());
72 out.extend_from_slice(request.collection.as_bytes());
73 out.extend_from_slice(&field_count.to_le_bytes());
74 for field in &request.fields {
75 validate_name(&field.name, "field name")?;
76 if !names.insert(field.name.as_str()) {
77 return Err(TypedInsertError::DuplicateField(field.name.clone()));
78 }
79 let name_len = u16::try_from(field.name.len())
80 .map_err(|_| TypedInsertError::LengthOverflow("field name"))?;
81 out.extend_from_slice(&name_len.to_le_bytes());
82 out.extend_from_slice(field.name.as_bytes());
83 encode_value(&field.value, &mut out).map_err(TypedInsertError::Value)?;
84 }
85 Ok(out)
86}
87
88pub fn decode_typed_insert(payload: &[u8]) -> Result<TypedInsertRequest, TypedInsertError> {
89 let mut pos = 0;
90 let version = read_bytes(payload, &mut pos, 1, "version")?[0];
91 if version != TYPED_INSERT_VERSION {
92 return Err(TypedInsertError::UnsupportedVersion(version));
93 }
94
95 let collection_len = read_u16(payload, &mut pos, "collection_len")? as usize;
96 let collection = read_string(payload, &mut pos, collection_len, "collection")?;
97 validate_name(&collection, "collection")?;
98
99 let field_count = read_u16(payload, &mut pos, "field_count")? as usize;
100 let minimum_field_bytes = field_count
101 .checked_mul(3)
102 .ok_or(TypedInsertError::Truncated("fields"))?;
103 if payload.len().saturating_sub(pos) < minimum_field_bytes {
104 return Err(TypedInsertError::Truncated("fields"));
105 }
106
107 let mut names = BTreeSet::new();
108 let mut fields = Vec::with_capacity(field_count);
109 for _ in 0..field_count {
110 let name_len = read_u16(payload, &mut pos, "field_name_len")? as usize;
111 let name = read_string(payload, &mut pos, name_len, "field name")?;
112 validate_name(&name, "field name")?;
113 if !names.insert(name.clone()) {
114 return Err(TypedInsertError::DuplicateField(name));
115 }
116 let value = decode_value(payload, &mut pos).map_err(TypedInsertError::Value)?;
117 fields.push(TypedInsertField { name, value });
118 }
119 if pos != payload.len() {
120 return Err(TypedInsertError::TrailingBytes(payload.len() - pos));
121 }
122 Ok(TypedInsertRequest { collection, fields })
123}
124
125fn validate_name(value: &str, field: &'static str) -> Result<(), TypedInsertError> {
126 if value.trim().is_empty() {
127 return Err(if field == "collection" {
128 TypedInsertError::EmptyCollection
129 } else {
130 TypedInsertError::EmptyFieldName
131 });
132 }
133 Ok(())
134}
135
136fn read_u16(payload: &[u8], pos: &mut usize, field: &'static str) -> Result<u16, TypedInsertError> {
137 let bytes = read_bytes(payload, pos, 2, field)?;
138 Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
139}
140
141fn read_string(
142 payload: &[u8],
143 pos: &mut usize,
144 len: usize,
145 field: &'static str,
146) -> Result<String, TypedInsertError> {
147 let bytes = read_bytes(payload, pos, len, field)?;
148 std::str::from_utf8(bytes)
149 .map(str::to_owned)
150 .map_err(|_| TypedInsertError::InvalidUtf8(field))
151}
152
153fn read_bytes<'a>(
154 payload: &'a [u8],
155 pos: &mut usize,
156 len: usize,
157 field: &'static str,
158) -> Result<&'a [u8], TypedInsertError> {
159 let end = pos
160 .checked_add(len)
161 .ok_or(TypedInsertError::Truncated(field))?;
162 if end > payload.len() {
163 return Err(TypedInsertError::Truncated(field));
164 }
165 let bytes = &payload[*pos..end];
166 *pos = end;
167 Ok(bytes)
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 fn all_values_request() -> TypedInsertRequest {
175 TypedInsertRequest {
176 collection: "events".to_string(),
177 fields: vec![
178 TypedInsertField {
179 name: "null".into(),
180 value: ParamValue::Null,
181 },
182 TypedInsertField {
183 name: "bool".into(),
184 value: ParamValue::Bool(true),
185 },
186 TypedInsertField {
187 name: "int".into(),
188 value: ParamValue::Int(-7),
189 },
190 TypedInsertField {
191 name: "float".into(),
192 value: ParamValue::Float(1.5),
193 },
194 TypedInsertField {
195 name: "text".into(),
196 value: ParamValue::Text("olá".into()),
197 },
198 TypedInsertField {
199 name: "bytes".into(),
200 value: ParamValue::Bytes(vec![0, 255]),
201 },
202 TypedInsertField {
203 name: "vector".into(),
204 value: ParamValue::Vector(vec![1.0, -2.0]),
205 },
206 TypedInsertField {
207 name: "json".into(),
208 value: ParamValue::Json(br#"{"ok":true}"#.to_vec()),
209 },
210 TypedInsertField {
211 name: "timestamp".into(),
212 value: ParamValue::Timestamp(123),
213 },
214 TypedInsertField {
215 name: "uuid".into(),
216 value: ParamValue::Uuid([0xAB; 16]),
217 },
218 ],
219 }
220 }
221
222 #[test]
223 fn round_trips_all_parameter_value_variants() {
224 let request = all_values_request();
225 let encoded = encode_typed_insert(&request).expect("encode");
226 assert_eq!(decode_typed_insert(&encoded).expect("decode"), request);
227 }
228
229 #[test]
230 fn simple_request_bytes_are_pinned_for_other_drivers() {
231 let request = TypedInsertRequest {
232 collection: "users".into(),
233 fields: vec![
234 TypedInsertField {
235 name: "name".into(),
236 value: ParamValue::Text("Ada".into()),
237 },
238 TypedInsertField {
239 name: "age".into(),
240 value: ParamValue::Int(37),
241 },
242 ],
243 };
244 assert_eq!(
245 encode_typed_insert(&request).expect("encode"),
246 vec![
247 0x01, 0x05, 0x00, b'u', b's', b'e', b'r', b's', 0x02, 0x00, 0x04, 0x00, b'n', b'a',
248 b'm', b'e', 0x04, 0x03, 0x00, 0x00, 0x00, b'A', b'd', b'a', 0x03, 0x00, b'a', b'g',
249 b'e', 0x02, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
250 ]
251 );
252 }
253
254 #[test]
255 fn preserves_empty_field_set_for_default_only_rows() {
256 let request = TypedInsertRequest {
257 collection: "defaults".into(),
258 fields: Vec::new(),
259 };
260 let encoded = encode_typed_insert(&request).expect("encode");
261 assert_eq!(decode_typed_insert(&encoded).expect("decode"), request);
262 }
263
264 #[test]
265 fn rejects_duplicate_empty_and_trailing_fields() {
266 let duplicate = TypedInsertRequest {
267 collection: "events".into(),
268 fields: vec![
269 TypedInsertField {
270 name: "id".into(),
271 value: ParamValue::Int(1),
272 },
273 TypedInsertField {
274 name: "id".into(),
275 value: ParamValue::Int(2),
276 },
277 ],
278 };
279 assert!(
280 matches!(encode_typed_insert(&duplicate), Err(TypedInsertError::DuplicateField(name)) if name == "id")
281 );
282
283 let empty = TypedInsertRequest {
284 collection: "events".into(),
285 fields: vec![TypedInsertField {
286 name: " ".into(),
287 value: ParamValue::Null,
288 }],
289 };
290 assert_eq!(
291 encode_typed_insert(&empty),
292 Err(TypedInsertError::EmptyFieldName)
293 );
294
295 let mut encoded = encode_typed_insert(&all_values_request()).expect("encode");
296 encoded.push(0);
297 assert_eq!(
298 decode_typed_insert(&encoded),
299 Err(TypedInsertError::TrailingBytes(1))
300 );
301 }
302
303 #[test]
304 fn rejects_bad_version_utf8_and_truncation() {
305 assert_eq!(
306 decode_typed_insert(&[2]),
307 Err(TypedInsertError::UnsupportedVersion(2))
308 );
309 assert_eq!(
310 decode_typed_insert(&[1, 1, 0, 0xFF, 0, 0]),
311 Err(TypedInsertError::InvalidUtf8("collection"))
312 );
313
314 let encoded = encode_typed_insert(&all_values_request()).expect("encode");
315 for len in 0..encoded.len() {
316 assert!(
317 decode_typed_insert(&encoded[..len]).is_err(),
318 "prefix {len} unexpectedly decoded"
319 );
320 }
321 }
322}