blob: 760c033eb5f0e42c095d37c9373a106258ade533 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//! Row-oriented value-buffer serialization for the `insertRecord(s)` RPC
//! family. Unlike the tablet's column-major layout (spec §3.2), a record
//! interleaves type and value per field: each cell is a 1-byte TSDataType
//! marker followed by its big-endian payload.
use super::tablet::write_binary;
use super::value::Value;
/// Java `SessionUtils.TYPE_NULL` (`-2`): a null cell is this bare marker
/// byte with no payload — the server's `InsertRowNode` reads `-2` as
/// null-without-type.
const TYPE_NULL: u8 = 0xFE;
/// Serializes one record row for `insertRecord`/`insertRecords`/
/// `insertRecordsOfOneDevice`: per value a 1-byte type-code marker plus the
/// big-endian payload (TEXT/STRING/BLOB carry a 4-byte BE length prefix;
/// DATE is i32 `yyyyMMdd`; BOOLEAN is one byte). [`Value::Null`] cells are
/// the bare [`TYPE_NULL`] marker, matching the Java client.
pub fn serialize_record_values(values: &[Value]) -> Vec<u8> {
let mut buf = Vec::new();
for value in values {
match value {
Value::Null => buf.push(TYPE_NULL),
other => {
let code = other.type_code().expect("non-null value carries a type");
buf.push(code as u8);
match other {
Value::Boolean(b) => buf.push(u8::from(*b)),
Value::Int32(v) | Value::Date(v) => buf.extend_from_slice(&v.to_be_bytes()),
Value::Int64(v) | Value::Timestamp(v) => {
buf.extend_from_slice(&v.to_be_bytes())
}
Value::Float(v) => buf.extend_from_slice(&v.to_be_bytes()),
Value::Double(v) => buf.extend_from_slice(&v.to_be_bytes()),
Value::Text(s) | Value::String(s) => write_binary(&mut buf, s.as_bytes()),
Value::Blob(b) => write_binary(&mut buf, b),
Value::Null => unreachable!("handled above"),
}
}
}
}
buf
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_types_known_bytes() {
let values = [
Value::Boolean(true),
Value::Int32(7),
Value::Int64(-2),
Value::Float(1.5),
Value::Double(-0.5),
Value::Text("ab".into()),
Value::Timestamp(3),
Value::Date(20260713),
Value::Blob(vec![0xDE, 0xAD]),
Value::String("é".into()),
];
let mut expected: Vec<u8> = Vec::new();
expected.extend_from_slice(&[0x00, 0x01]); // BOOLEAN true
expected.push(0x01);
expected.extend_from_slice(&7i32.to_be_bytes());
expected.push(0x02);
expected.extend_from_slice(&(-2i64).to_be_bytes());
expected.push(0x03);
expected.extend_from_slice(&[0x3F, 0xC0, 0x00, 0x00]); // 1.5f32 IEEE 754 BE
expected.push(0x04);
expected.extend_from_slice(&(-0.5f64).to_be_bytes());
expected.extend_from_slice(&[0x05, 0, 0, 0, 2, b'a', b'b']);
expected.push(0x08);
expected.extend_from_slice(&3i64.to_be_bytes());
expected.push(0x09);
expected.extend_from_slice(&20260713i32.to_be_bytes()); // yyyyMMdd
expected.extend_from_slice(&[0x0A, 0, 0, 0, 2, 0xDE, 0xAD]);
expected.extend_from_slice(&[0x0B, 0, 0, 0, 2, 0xC3, 0xA9]); // "é" UTF-8
assert_eq!(serialize_record_values(&values), expected);
}
#[test]
fn null_is_bare_type_marker() {
assert_eq!(serialize_record_values(&[Value::Null]), [0xFE]);
// Nulls interleave with typed cells without payload bytes.
let buf = serialize_record_values(&[
Value::Int32(5),
Value::Null,
Value::Boolean(false),
Value::Null,
]);
assert_eq!(buf, [0x01, 0, 0, 0, 5, 0xFE, 0x00, 0x00, 0xFE]);
}
#[test]
fn empty_row_is_empty_buffer() {
assert_eq!(serialize_record_values(&[]), Vec::<u8>::new());
}
}