1use reddb_types::Value;
16
17pub const MSG_QUERY: u8 = 0x01;
19pub const MSG_RESULT: u8 = 0x02;
20pub const MSG_ERROR: u8 = 0x03;
21pub const MSG_BULK_INSERT: u8 = 0x04;
22pub const MSG_BULK_OK: u8 = 0x05;
23pub const MSG_BULK_INSERT_BINARY: u8 = 0x06;
24pub const MSG_QUERY_BINARY: u8 = 0x07;
25pub const MSG_BULK_INSERT_PREVALIDATED: u8 = 0x08;
26pub const MSG_BULK_STREAM_START: u8 = 0x09;
27pub const MSG_BULK_STREAM_ROWS: u8 = 0x0A;
28pub const MSG_BULK_STREAM_COMMIT: u8 = 0x0B;
29pub const MSG_BULK_STREAM_ACK: u8 = 0x0C;
30pub const MSG_PREPARE: u8 = 0x0D;
31pub const MSG_PREPARED_OK: u8 = 0x0E;
32pub const MSG_EXECUTE_PREPARED: u8 = 0x0F;
33pub const MSG_DEALLOCATE: u8 = 0x10;
34pub const MSG_DECLARE_CURSOR: u8 = 0x11;
35pub const MSG_CURSOR_OK: u8 = 0x12;
36pub const MSG_FETCH: u8 = 0x13;
37pub const MSG_CURSOR_BATCH: u8 = 0x14;
38pub const MSG_CLOSE_CURSOR: u8 = 0x15;
39
40pub const VAL_NULL: u8 = 0;
42pub const VAL_I64: u8 = 1;
43pub const VAL_F64: u8 = 2;
44pub const VAL_TEXT: u8 = 3;
45pub const VAL_BOOL: u8 = 4;
46pub const VAL_U64: u8 = 5;
47
48#[derive(Debug, Clone, PartialEq)]
49pub enum WireValue {
50 Null,
51 I64(i64),
52 U64(u64),
53 F64(f64),
54 Text(String),
55 Bool(bool),
56 Bytes(Vec<u8>),
57 Timestamp(u64),
58}
59
60impl From<&Value> for WireValue {
67 fn from(value: &Value) -> Self {
68 match value {
69 Value::Null => WireValue::Null,
70 Value::Integer(n) => WireValue::I64(*n),
71 Value::UnsignedInteger(n) => WireValue::U64(*n),
72 Value::Float(f) => WireValue::F64(*f),
73 Value::Text(s) => WireValue::Text(s.to_string()),
74 Value::Blob(bytes) => WireValue::Bytes(bytes.clone()),
75 Value::Boolean(b) => WireValue::Bool(*b),
76 Value::Timestamp(t) => WireValue::Timestamp(*t as u64),
77 _ => WireValue::Null,
78 }
79 }
80}
81
82impl From<Value> for WireValue {
83 fn from(value: Value) -> Self {
84 match value {
85 Value::Null => WireValue::Null,
86 Value::Integer(n) => WireValue::I64(n),
87 Value::UnsignedInteger(n) => WireValue::U64(n),
88 Value::Float(f) => WireValue::F64(f),
89 Value::Text(s) => WireValue::Text(s.to_string()),
90 Value::Blob(bytes) => WireValue::Bytes(bytes),
91 Value::Boolean(b) => WireValue::Bool(b),
92 Value::Timestamp(t) => WireValue::Timestamp(t as u64),
93 _ => WireValue::Null,
94 }
95 }
96}
97
98impl TryFrom<WireValue> for Value {
99 type Error = &'static str;
100
101 fn try_from(value: WireValue) -> Result<Self, Self::Error> {
102 match value {
103 WireValue::Null => Ok(Value::Null),
104 WireValue::I64(n) => Ok(Value::Integer(n)),
105 WireValue::U64(n) => Ok(Value::UnsignedInteger(n)),
106 WireValue::F64(f) => Ok(Value::Float(f)),
107 WireValue::Text(s) => Ok(Value::text(s)),
108 WireValue::Bool(b) => Ok(Value::Boolean(b)),
109 WireValue::Bytes(bytes) => Ok(Value::Blob(bytes)),
110 WireValue::Timestamp(t) => {
111 let timestamp = i64::try_from(t).map_err(|_| "timestamp exceeds i64 range")?;
112 Ok(Value::Timestamp(timestamp))
113 }
114 }
115 }
116}
117
118#[inline]
120pub fn write_frame_header(buf: &mut Vec<u8>, msg_type: u8, payload_len: u32) {
121 let total = payload_len + 1; buf.extend_from_slice(&total.to_le_bytes());
123 buf.push(msg_type);
124}
125
126pub fn build_legacy_frame(msg_type: u8, payload: &[u8]) -> Vec<u8> {
127 let mut out = Vec::with_capacity(5 + payload.len());
128 write_frame_header(&mut out, msg_type, payload.len() as u32);
129 out.extend_from_slice(payload);
130 out
131}
132
133pub fn build_legacy_result_frame(payload: &[u8]) -> Vec<u8> {
134 build_legacy_frame(MSG_RESULT, payload)
135}
136
137pub fn build_legacy_result_frame_with(
141 payload_capacity: usize,
142 encode: impl FnOnce(&mut Vec<u8>),
143) -> Vec<u8> {
144 let mut out = Vec::with_capacity(payload_capacity.saturating_add(5));
145 write_frame_header(&mut out, MSG_RESULT, 0);
146 encode(&mut out);
147 assert!(
148 out.len() >= 5,
149 "encoder must preserve the legacy frame header"
150 );
151 let total = u32::try_from(out.len() - 4).expect("legacy frame length fits u32");
152 out[..4].copy_from_slice(&total.to_le_bytes());
153 out
154}
155
156pub fn build_legacy_result_frame_in_place(out: &mut [u8]) -> Result<(), &'static str> {
159 if out.len() < 5 {
160 return Err("truncated legacy result buffer");
161 }
162 let total = u32::try_from(out.len() - 4).map_err(|_| "legacy result exceeds frame length")?;
163 out[..4].copy_from_slice(&total.to_le_bytes());
164 out[4] = MSG_RESULT;
165 Ok(())
166}
167
168pub fn build_legacy_error_frame(message: &[u8]) -> Vec<u8> {
169 build_legacy_frame(MSG_ERROR, message)
170}
171
172pub fn build_legacy_bulk_ok_frame(payload: &[u8]) -> Vec<u8> {
173 build_legacy_frame(MSG_BULK_OK, payload)
174}
175
176pub fn build_legacy_bulk_stream_ack_frame() -> Vec<u8> {
177 build_legacy_frame(MSG_BULK_STREAM_ACK, &[])
178}
179
180pub fn build_legacy_prepared_ok_frame(payload: &[u8]) -> Vec<u8> {
181 build_legacy_frame(MSG_PREPARED_OK, payload)
182}
183
184pub fn build_legacy_cursor_ok_frame(payload: &[u8]) -> Vec<u8> {
185 build_legacy_frame(MSG_CURSOR_OK, payload)
186}
187
188pub fn build_legacy_cursor_batch_frame(payload: &[u8]) -> Vec<u8> {
189 build_legacy_frame(MSG_CURSOR_BATCH, payload)
190}
191
192#[inline]
193pub fn encode_value(buf: &mut Vec<u8>, value: &WireValue) {
194 match value {
195 WireValue::Null | WireValue::Bytes(_) => buf.push(VAL_NULL),
196 WireValue::I64(n) => {
197 buf.push(VAL_I64);
198 buf.extend_from_slice(&n.to_le_bytes());
199 }
200 WireValue::U64(n) | WireValue::Timestamp(n) => {
201 buf.push(VAL_U64);
202 buf.extend_from_slice(&n.to_le_bytes());
203 }
204 WireValue::F64(f) => {
205 buf.push(VAL_F64);
206 buf.extend_from_slice(&f.to_le_bytes());
207 }
208 WireValue::Text(s) => {
209 buf.push(VAL_TEXT);
210 let bytes = s.as_bytes();
211 buf.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
212 buf.extend_from_slice(bytes);
213 }
214 WireValue::Bool(b) => {
215 buf.push(VAL_BOOL);
216 buf.push(*b as u8);
217 }
218 }
219}
220
221#[inline]
224pub fn encode_native_value(buf: &mut Vec<u8>, value: &Value) {
225 match value {
226 Value::Text(text) => {
227 buf.push(VAL_TEXT);
228 buf.extend_from_slice(&(text.len() as u32).to_le_bytes());
229 buf.extend_from_slice(text.as_bytes());
230 }
231 Value::Integer(value) => encode_value(buf, &WireValue::I64(*value)),
232 Value::UnsignedInteger(value) => encode_value(buf, &WireValue::U64(*value)),
233 Value::Float(value) => encode_value(buf, &WireValue::F64(*value)),
234 Value::Boolean(value) => encode_value(buf, &WireValue::Bool(*value)),
235 Value::Timestamp(value) => encode_value(buf, &WireValue::Timestamp(*value as u64)),
236 _ => buf.push(VAL_NULL),
237 }
238}
239
240#[inline]
242pub fn native_value_encoded_len(value: &Value) -> usize {
243 match value {
244 Value::Text(text) => 5 + text.len(),
245 Value::Integer(_) | Value::UnsignedInteger(_) | Value::Float(_) | Value::Timestamp(_) => 9,
246 Value::Boolean(_) => 2,
247 _ => 1,
248 }
249}
250
251#[inline]
252pub fn decode_value(data: &[u8], pos: &mut usize) -> WireValue {
253 try_decode_value(data, pos).unwrap_or(WireValue::Null)
254}
255
256#[inline]
257pub fn try_decode_value(data: &[u8], pos: &mut usize) -> Result<WireValue, &'static str> {
258 if *pos >= data.len() {
259 return Err("missing value tag");
260 }
261
262 let tag = data[*pos];
263 *pos += 1;
264
265 match tag {
266 VAL_NULL => Ok(WireValue::Null),
267 VAL_I64 => Ok(WireValue::I64(i64::from_le_bytes(read_array::<8>(
268 data,
269 pos,
270 "truncated i64 value",
271 )?))),
272 VAL_U64 => Ok(WireValue::U64(u64::from_le_bytes(read_array::<8>(
273 data,
274 pos,
275 "truncated u64 value",
276 )?))),
277 VAL_F64 => Ok(WireValue::F64(f64::from_le_bytes(read_array::<8>(
278 data,
279 pos,
280 "truncated f64 value",
281 )?))),
282 VAL_TEXT => {
283 let len =
284 u32::from_le_bytes(read_array::<4>(data, pos, "truncated text length")?) as usize;
285 let bytes = read_bytes(data, pos, len, "truncated text value")?;
286 let cow = std::string::String::from_utf8_lossy(bytes);
287 Ok(WireValue::Text(cow.into_owned()))
288 }
289 VAL_BOOL => {
290 let bytes = read_bytes(data, pos, 1, "truncated bool value")?;
291 Ok(WireValue::Bool(bytes[0] != 0))
292 }
293 _ => Err("unknown value tag"),
294 }
295}
296
297#[inline]
298fn read_bytes<'a>(
299 data: &'a [u8],
300 pos: &mut usize,
301 len: usize,
302 err: &'static str,
303) -> Result<&'a [u8], &'static str> {
304 let end = pos.saturating_add(len);
305 if end > data.len() {
306 return Err(err);
307 }
308 let bytes = &data[*pos..end];
309 *pos = end;
310 Ok(bytes)
311}
312
313#[inline]
314fn read_array<const N: usize>(
315 data: &[u8],
316 pos: &mut usize,
317 err: &'static str,
318) -> Result<[u8; N], &'static str> {
319 let bytes = read_bytes(data, pos, N, err)?;
320 let mut array = [0u8; N];
321 array.copy_from_slice(bytes);
322 Ok(array)
323}
324
325#[inline]
326pub fn encode_column_name(buf: &mut Vec<u8>, name: &str) {
327 let bytes = name.as_bytes();
328 buf.extend_from_slice(&(bytes.len() as u16).to_le_bytes());
329 buf.extend_from_slice(bytes);
330}
331
332pub fn encode_result_payload_header<'a, I>(buf: &mut Vec<u8>, columns: I, row_count: u32) -> usize
333where
334 I: IntoIterator<Item = &'a str>,
335 I::IntoIter: ExactSizeIterator,
336{
337 let columns = columns.into_iter();
338 buf.extend_from_slice(&(columns.len() as u16).to_le_bytes());
339 for column in columns {
340 encode_column_name(buf, column);
341 }
342 let row_count_offset = buf.len();
343 buf.extend_from_slice(&row_count.to_le_bytes());
344 row_count_offset
345}
346
347pub fn set_result_payload_row_count(
348 buf: &mut [u8],
349 row_count_offset: usize,
350 row_count: u32,
351) -> Result<(), &'static str> {
352 let end = row_count_offset.saturating_add(4);
353 if end > buf.len() {
354 return Err("result payload row-count offset out of bounds");
355 }
356 buf[row_count_offset..end].copy_from_slice(&row_count.to_le_bytes());
357 Ok(())
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 #[test]
365 fn admitted_in_place_result_matches_canonical_envelope() {
366 for payload in [b"".as_slice(), b"hello", &[0, 255, 1, 2]] {
367 let expected = build_legacy_result_frame(payload);
368 let mut actual = vec![0; 5 + payload.len()];
369 actual[5..].copy_from_slice(payload);
370 build_legacy_result_frame_in_place(&mut actual).unwrap();
371 assert_eq!(actual, expected);
372 }
373 for length in 0..5 {
374 let mut truncated = vec![0; length];
375 assert!(build_legacy_result_frame_in_place(&mut truncated).is_err());
376 }
377 }
378
379 #[test]
380 fn borrowed_native_encoding_matches_owned_conversion_and_exact_length() {
381 let values = [
382 Value::Null,
383 Value::Integer(i64::MIN),
384 Value::UnsignedInteger(u64::MAX),
385 Value::Float(-1.25),
386 Value::Boolean(true),
387 Value::Boolean(false),
388 Value::Timestamp(-1),
389 Value::text(""),
390 Value::text("quotes\"雪\n"),
391 Value::text("x".repeat(192 * 1024)),
392 Value::Blob(vec![1; 192 * 1024]),
393 ];
394 for value in values {
395 let mut expected = Vec::new();
396 encode_value(&mut expected, &WireValue::from(&value));
397 let mut actual = Vec::with_capacity(native_value_encoded_len(&value));
398 encode_native_value(&mut actual, &value);
399 assert_eq!(actual, expected);
400 assert_eq!(actual.len(), native_value_encoded_len(&value));
401 assert_eq!(actual.len(), actual.capacity());
402 }
403 }
404
405 #[test]
410 fn value_wirevalue_field_mapping_round_trips() {
411 let cases = [
412 (Value::Null, WireValue::Null),
413 (Value::Integer(-7), WireValue::I64(-7)),
414 (Value::UnsignedInteger(9), WireValue::U64(9)),
415 (Value::Float(1.5), WireValue::F64(1.5)),
416 (Value::text("hi"), WireValue::Text("hi".to_string())),
417 (Value::Blob(vec![1, 2, 3]), WireValue::Bytes(vec![1, 2, 3])),
418 (Value::Boolean(true), WireValue::Bool(true)),
419 (Value::Timestamp(42), WireValue::Timestamp(42)),
420 ];
421 for (value, wire) in cases {
422 assert_eq!(WireValue::from(value.clone()), wire);
424 assert_eq!(WireValue::from(&value), wire);
425 assert_eq!(Value::try_from(wire.clone()), Ok(value));
427 }
428 }
429
430 #[test]
431 fn wirevalue_timestamp_rejects_i64_overflow() {
432 let overflow = WireValue::Timestamp(u64::MAX);
433 assert_eq!(
434 Value::try_from(overflow),
435 Err("timestamp exceeds i64 range")
436 );
437 }
438
439 #[test]
440 fn frame_header_keeps_legacy_length_shape() {
441 let mut out = Vec::new();
442 write_frame_header(&mut out, MSG_RESULT, 10);
443 assert_eq!(out, [11, 0, 0, 0, MSG_RESULT]);
444 }
445
446 #[test]
447 fn legacy_frame_builders_wrap_payloads() {
448 assert_eq!(
449 build_legacy_result_frame(b"ok"),
450 [3, 0, 0, 0, MSG_RESULT, b'o', b'k']
451 );
452 assert_eq!(
453 build_legacy_error_frame(b"no"),
454 [3, 0, 0, 0, MSG_ERROR, b'n', b'o']
455 );
456 assert_eq!(
457 build_legacy_bulk_ok_frame(b"\x02\0\0\0\0\0\0\0"),
458 [9, 0, 0, 0, MSG_BULK_OK, 2, 0, 0, 0, 0, 0, 0, 0]
459 );
460 assert_eq!(
461 build_legacy_bulk_stream_ack_frame(),
462 [1, 0, 0, 0, MSG_BULK_STREAM_ACK]
463 );
464 assert_eq!(
465 build_legacy_prepared_ok_frame(b"p"),
466 [2, 0, 0, 0, MSG_PREPARED_OK, b'p']
467 );
468 assert_eq!(
469 build_legacy_cursor_ok_frame(b"c"),
470 [2, 0, 0, 0, MSG_CURSOR_OK, b'c']
471 );
472 assert_eq!(
473 build_legacy_cursor_batch_frame(b"b"),
474 [2, 0, 0, 0, MSG_CURSOR_BATCH, b'b']
475 );
476 }
477
478 #[test]
479 fn direct_result_frame_encoder_matches_legacy_bytes_across_growth() {
480 for size in [0, 1, 255, 1024, 192 * 1024] {
481 let payload = vec![0x71; size];
482 let frame = build_legacy_result_frame_with(8, |out| {
483 out.extend_from_slice(&payload[..size / 2]);
484 out.extend_from_slice(&payload[size / 2..]);
485 });
486 assert_eq!(frame, build_legacy_result_frame(&payload));
487 }
488 }
489
490 #[test]
491 fn wire_values_round_trip_legacy_tags() {
492 let values = [
493 WireValue::Null,
494 WireValue::I64(-7),
495 WireValue::U64(42),
496 WireValue::F64(3.5),
497 WireValue::Text("hello".to_string()),
498 WireValue::Bool(true),
499 WireValue::Timestamp(1234),
500 ];
501
502 let mut bytes = Vec::new();
503 for value in &values {
504 encode_value(&mut bytes, value);
505 }
506
507 let mut pos = 0;
508 assert_eq!(try_decode_value(&bytes, &mut pos), Ok(WireValue::Null));
509 assert_eq!(try_decode_value(&bytes, &mut pos), Ok(WireValue::I64(-7)));
510 assert_eq!(try_decode_value(&bytes, &mut pos), Ok(WireValue::U64(42)));
511 assert_eq!(try_decode_value(&bytes, &mut pos), Ok(WireValue::F64(3.5)));
512 assert_eq!(
513 try_decode_value(&bytes, &mut pos),
514 Ok(WireValue::Text("hello".to_string()))
515 );
516 assert_eq!(
517 try_decode_value(&bytes, &mut pos),
518 Ok(WireValue::Bool(true))
519 );
520 assert_eq!(try_decode_value(&bytes, &mut pos), Ok(WireValue::U64(1234)));
521 assert_eq!(pos, bytes.len());
522 }
523
524 #[test]
525 fn result_payload_header_encodes_columns_and_row_count() {
526 let mut bytes = Vec::new();
527 let row_count_offset = encode_result_payload_header(&mut bytes, ["id", "name"], 3);
528
529 assert_eq!(
530 bytes,
531 [
532 2, 0, 2, 0, b'i', b'd', 4, 0, b'n', b'a', b'm', b'e', 3, 0, 0, 0, ]
537 );
538 assert_eq!(row_count_offset, bytes.len() - 4);
539 set_result_payload_row_count(&mut bytes, row_count_offset, 5).unwrap();
540 assert_eq!(
541 &bytes[row_count_offset..row_count_offset + 4],
542 &[5, 0, 0, 0]
543 );
544 }
545}