use std::net::Ipv4Addr;
use crate::egress::binds::{Bind, SimpleNullKind, check_bindable, encode_bind};
use crate::egress::wire::msg_kind::MsgKind;
use crate::egress::wire::varint;
use crate::error::{Result, fmt};
pub const MAX_SQL_BYTES: usize = 1024 * 1024;
pub const MAX_BINDS: usize = 1024;
pub const QUERY_FLAG_RESET_DICT: u64 = 0x01;
#[derive(Debug, Clone)]
pub struct QueryRequest {
request_id: i64,
sql: String,
initial_credit: u64,
binds: Vec<Bind>,
query_flags: u64,
}
pub const REQUEST_ID_OFFSET: usize = 1;
impl QueryRequest {
pub fn builder<S: Into<String>>(sql: S) -> QueryRequestBuilder {
QueryRequestBuilder {
request_id: 0,
sql: sql.into(),
initial_credit: 0,
binds: Vec::new(),
query_flags: 0,
}
}
pub fn initial_credit(&self) -> u64 {
self.initial_credit
}
pub fn encode(&self, out: &mut Vec<u8>) -> Result<()> {
out.push(MsgKind::QueryRequest.as_u8());
out.extend_from_slice(&self.request_id.to_le_bytes());
varint::encode_u64(self.sql.len() as u64, out);
out.extend_from_slice(self.sql.as_bytes());
varint::encode_u64(self.initial_credit, out);
varint::encode_u64(self.binds.len() as u64, out);
for bind in &self.binds {
encode_bind(bind, out)?;
}
if self.query_flags != 0 {
varint::encode_u64(self.query_flags, out);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct QueryRequestBuilder {
request_id: i64,
sql: String,
initial_credit: u64,
binds: Vec<Bind>,
query_flags: u64,
}
impl QueryRequestBuilder {
pub fn request_id(mut self, id: i64) -> Self {
self.request_id = id;
self
}
pub fn initial_credit(mut self, credit: u64) -> Self {
self.initial_credit = credit;
self
}
pub fn query_flags(mut self, flags: u64) -> Self {
self.query_flags = flags;
self
}
pub fn bind(mut self, value: Bind) -> Self {
self.binds.push(value);
self
}
pub fn bind_null(self, kind: SimpleNullKind) -> Self {
self.bind(Bind::Null(kind))
}
pub fn bind_bool(self, v: bool) -> Self {
self.bind(Bind::Bool(v))
}
pub fn bind_i8(self, v: i8) -> Self {
self.bind(Bind::I8(v))
}
pub fn bind_i16(self, v: i16) -> Self {
self.bind(Bind::I16(v))
}
pub fn bind_i32(self, v: i32) -> Self {
self.bind(Bind::I32(v))
}
pub fn bind_i64(self, v: i64) -> Self {
self.bind(Bind::I64(v))
}
pub fn bind_f32(self, v: f32) -> Self {
self.bind(Bind::F32(v))
}
pub fn bind_f64(self, v: f64) -> Self {
self.bind(Bind::F64(v))
}
pub fn bind_varchar<S: Into<String>>(self, v: S) -> Self {
self.bind(Bind::Varchar(v.into()))
}
pub fn bind_timestamp_micros(self, v: i64) -> Self {
self.bind(Bind::TimestampMicros(v))
}
pub fn bind_timestamp_nanos(self, v: i64) -> Self {
self.bind(Bind::TimestampNanos(v))
}
pub fn bind_date_millis(self, v: i64) -> Self {
self.bind(Bind::DateMillis(v))
}
pub fn bind_uuid(self, v: [u8; 16]) -> Self {
self.bind(Bind::Uuid(v))
}
pub fn bind_long256(self, v: [u8; 32]) -> Self {
self.bind(Bind::Long256(v))
}
pub fn bind_char(self, v: u16) -> Self {
self.bind(Bind::Char(v))
}
pub fn bind_ipv4(self, v: Ipv4Addr) -> Self {
self.bind(Bind::Ipv4(v))
}
pub fn bind_decimal64(self, value: i64, scale: i8) -> Self {
self.bind(Bind::Decimal64 { value, scale })
}
pub fn bind_decimal128(self, value: i128, scale: i8) -> Self {
self.bind(Bind::Decimal128 { value, scale })
}
pub fn bind_decimal256(self, bytes: [u8; 32], scale: i8) -> Self {
self.bind(Bind::Decimal256 { bytes, scale })
}
pub fn bind_geohash(self, value: u64, precision_bits: u8) -> Self {
self.bind(Bind::Geohash {
value,
precision_bits,
})
}
pub fn bind_binary<B: Into<Vec<u8>>>(self, v: B) -> Self {
self.bind(Bind::Binary(v.into()))
}
pub fn bind_null_varchar(self) -> Self {
self.bind(Bind::NullVarchar)
}
pub fn bind_null_binary(self) -> Self {
self.bind(Bind::NullBinary)
}
pub fn bind_null_decimal64(self, scale: i8) -> Self {
self.bind(Bind::NullDecimal64 { scale })
}
pub fn bind_null_decimal128(self, scale: i8) -> Self {
self.bind(Bind::NullDecimal128 { scale })
}
pub fn bind_null_decimal256(self, scale: i8) -> Self {
self.bind(Bind::NullDecimal256 { scale })
}
pub fn bind_null_geohash(self, precision_bits: u8) -> Self {
self.bind(Bind::NullGeohash { precision_bits })
}
pub fn build(self) -> Result<QueryRequest> {
if self.sql.len() > MAX_SQL_BYTES {
return Err(fmt!(
InvalidApiCall,
"SQL too long: {} bytes (max {})",
self.sql.len(),
MAX_SQL_BYTES
));
}
if self.binds.len() > MAX_BINDS {
return Err(fmt!(
InvalidApiCall,
"too many bind parameters: {} (max {})",
self.binds.len(),
MAX_BINDS
));
}
for (i, bind) in self.binds.iter().enumerate() {
check_bindable(bind.kind())
.map_err(|e| fmt!(InvalidBind, "bind ${}: {}", i + 1, e.msg()))?;
}
Ok(QueryRequest {
request_id: self.request_id,
sql: self.sql,
initial_credit: self.initial_credit,
binds: self.binds,
query_flags: self.query_flags,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::ErrorCode;
#[test]
fn request_id_offset_matches_encoding() {
const SENTINEL: i64 = 0x0123_4567_89AB_CDEF;
let req = QueryRequest::builder("S")
.request_id(SENTINEL)
.build()
.unwrap();
let mut buf = Vec::new();
req.encode(&mut buf).unwrap();
assert!(buf.len() >= REQUEST_ID_OFFSET + 8);
let patched = i64::from_le_bytes(
buf[REQUEST_ID_OFFSET..REQUEST_ID_OFFSET + 8]
.try_into()
.unwrap(),
);
assert_eq!(
patched, SENTINEL,
"REQUEST_ID_OFFSET ({}) no longer points at the request_id field — \
update the constant alongside the encoder layout",
REQUEST_ID_OFFSET,
);
}
#[test]
fn no_binds_byte_exact() {
let req = QueryRequest::builder("SELECT 1")
.request_id(0x2A)
.build()
.unwrap();
let mut buf = Vec::new();
req.encode(&mut buf).unwrap();
assert_eq!(buf[0], 0x10);
assert_eq!(&buf[1..9], &0x2Ai64.to_le_bytes());
assert_eq!(buf[9], 0x08); assert_eq!(&buf[10..18], b"SELECT 1");
assert_eq!(buf[18], 0x00); assert_eq!(buf[19], 0x00); assert_eq!(buf.len(), 20);
}
#[test]
fn with_mixed_binds_layout() {
let req = QueryRequest::builder("X")
.request_id(1)
.bind_i64(42)
.bind_varchar("hi")
.bind_null(SimpleNullKind::Boolean)
.build()
.unwrap();
let mut buf = Vec::new();
req.encode(&mut buf).unwrap();
let mut expected = vec![0x10];
expected.extend_from_slice(&1i64.to_le_bytes());
expected.push(0x01); expected.push(b'X');
expected.push(0x00); expected.push(0x03); expected.extend_from_slice(&[0x05, 0x00]);
expected.extend_from_slice(&42i64.to_le_bytes());
expected.extend_from_slice(&[0x0F, 0x00]);
expected.extend_from_slice(&0u32.to_le_bytes());
expected.extend_from_slice(&2u32.to_le_bytes());
expected.extend_from_slice(b"hi");
expected.extend_from_slice(&[0x01, 0x01, 0x01]);
assert_eq!(buf, expected);
}
#[test]
fn initial_credit_serialized() {
let req = QueryRequest::builder("X")
.initial_credit(0x4000)
.build()
.unwrap();
let mut buf = Vec::new();
req.encode(&mut buf).unwrap();
assert_eq!(&buf[11..14], &[0x80, 0x80, 0x01]);
}
#[test]
fn query_flags_trailer() {
let mut baseline = Vec::new();
QueryRequest::builder("X")
.build()
.unwrap()
.encode(&mut baseline)
.unwrap();
let mut with_flag = Vec::new();
QueryRequest::builder("X")
.query_flags(QUERY_FLAG_RESET_DICT)
.build()
.unwrap()
.encode(&mut with_flag)
.unwrap();
assert_eq!(with_flag.len(), baseline.len() + 1);
assert_eq!(&with_flag[..baseline.len()], &baseline[..]);
assert_eq!(*with_flag.last().unwrap(), QUERY_FLAG_RESET_DICT as u8);
}
#[test]
fn sql_too_long_rejected() {
let big = "a".repeat(MAX_SQL_BYTES + 1);
let err = QueryRequest::builder(big).build().unwrap_err();
assert_eq!(err.code(), ErrorCode::InvalidApiCall);
}
#[test]
fn too_many_binds_rejected() {
let mut b = QueryRequest::builder("X");
for _ in 0..(MAX_BINDS + 1) {
b = b.bind_i64(0);
}
let err = b.build().unwrap_err();
assert_eq!(err.code(), ErrorCode::InvalidApiCall);
}
#[test]
fn unsupported_bind_kind_rejected() {
let err = QueryRequest::builder("X")
.bind(Bind::Null(SimpleNullKind::Ipv4))
.build()
.unwrap_err();
assert_eq!(err.code(), ErrorCode::InvalidBind);
assert!(err.msg().contains("$1"));
}
#[test]
fn encode_length_grows_monotonically_with_binds() {
let mut prev = 0usize;
for binds in 0..50 {
let mut b = QueryRequest::builder("SELECT * FROM t");
for _ in 0..binds {
b = b.bind_i64(0);
}
let req = b.build().unwrap();
let mut buf = Vec::new();
req.encode(&mut buf).unwrap();
assert!(
buf.len() > prev || binds == 0,
"binds={} len={} prev={}",
binds,
buf.len(),
prev
);
prev = buf.len();
}
}
}