diff --git a/.cargo/config.toml b/.cargo/config.toml index e29cb912..5410a360 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -44,7 +44,6 @@ rustflags = [ "-Wclippy::unnecessary_struct_initialization", "-Wclippy::unused_peekable", "-Wclippy::unused_rounding", - "-Wclippy::useless_let_if_seq", "-Wclippy::while_float", # Pedantic Group diff --git a/taos-query/Cargo.toml b/taos-query/Cargo.toml index 3773785f..b8037d71 100644 --- a/taos-query/Cargo.toml +++ b/taos-query/Cargo.toml @@ -68,6 +68,11 @@ tokio = { version = "1", features = [ lazy_static = "1.4" derive_builder = "0.12.0" tracing = { version = "0.1", features = ["log"] } +uuid = { version = "1.11.0", features = [ + "v4", + "fast-rng", + "macro-diagnostics", +] } [dev-dependencies] flate2 = "1" diff --git a/taos-query/src/common/raw/mod.rs b/taos-query/src/common/raw/mod.rs index a392712e..06a45b04 100644 --- a/taos-query/src/common/raw/mod.rs +++ b/taos-query/src/common/raw/mod.rs @@ -241,18 +241,6 @@ impl RawBlock { unsafe { (v as *const u64).read_unaligned() == 0x7FFFFF0000000000 } } - // const BOOL_NULL: u8 = 0x2; - // const TINY_INT_NULL: i8 = i8::MIN; - // const SMALL_INT_NULL: i16 = i16::MIN; - // const INT_NULL: i32 = i32::MIN; - // const BIG_INT_NULL: i64 = i64::MIN; - // const FLOAT_NULL: f32 = 0x7FF00000i32 as f32; - // const DOUBLE_NULL: f64 = 0x7FFFFF0000000000i64 as f64; - // const U_TINY_INT_NULL: u8 = u8::MAX; - // const U_SMALL_INT_NULL: u16 = u16::MAX; - // const U_INT_NULL: u32 = u32::MAX; - // const U_BIG_INT_NULL: u64 = u64::MAX; - let layout = Rc::new(RefCell::new(Layout::INLINE_DEFAULT.with_schema_changed())); let bytes = bytes.into(); @@ -273,8 +261,7 @@ impl RawBlock { for (i, (field, length)) in fields.iter().zip(lengths).enumerate() { macro_rules! _primitive_view { - ($ty:ident, $prim:ty) => { - { + ($ty:ident, $prim:ty) => {{ debug_assert_eq!(field.bytes(), *length); // column start let start = offset; @@ -283,31 +270,17 @@ impl RawBlock { // byte slice from start to end: `[start, end)`. let data = bytes.slice(start..offset); let nulls = NullBits::from_iter((0..rows).map(|row| unsafe { - paste::paste!{ [<$ty:snake _is_null>] ( + paste::paste! { [<$ty:snake _is_null>] ( data .as_ptr() .offset(row as isize * std::mem::size_of::<$prim>() as isize) as *const $prim, ) } })); - // value as target type - // let value_slice = unsafe { - // std::slice::from_raw_parts( - // transmute::<*const u8, *const $prim>(data.as_ptr()), - // rows, - // ) - // }; + // Set data lengths for v3-compatible block. data_lengths[i] = data.len() as u32; - // generate nulls bitmap. - // let nulls = NullsMut::from_bools( - // value_slice - // .iter() - // .map(|v| paste::paste!{ [<$ty:snake _is_null>](v as _) }) - // // .map(|b| *b as u64 == paste::paste! { [<$ty:snake:upper _NULL>] }), - // ) - // .into_nulls(); // build column view let column = paste::paste! { ColumnView::$ty([<$ty View>] { nulls, data }) }; columns.push(column); @@ -831,10 +804,6 @@ impl RawBlock { self.columns.get_unchecked(col).get_ref_unchecked(row) } - // unsafe fn get_col_unchecked(&self, col: usize) -> &ColumnView { - // self.columns.get_unchecked(col) - // } - pub fn to_values(&self) -> Vec> { self.rows().map(RowView::into_values).collect_vec() } @@ -859,13 +828,6 @@ impl RawBlock { PrettyBlock::new(self) } - // pub fn fields_iter(&self) -> impl Iterator + '_ { - // self.schemas() - // .iter() - // .zip(self.field_names()) - // .map(|(schema, name)| Field::new(name, schema.ty, schema.len)) - // } - pub fn to_create(&self) -> Option { self.table_name().map(|table_name| MetaCreate::Normal { table_name: table_name.to_string(), @@ -1029,6 +991,7 @@ impl crate::prelude::sync::Inlinable for InlineBlock { Ok(self.0.len()) } } + #[async_trait::async_trait] impl crate::prelude::AsyncInlinable for InlineBlock { async fn read_inlined( diff --git a/taos-query/src/common/raw/views/big_int_unsigned_view.rs b/taos-query/src/common/raw/views/big_int_unsigned_view.rs index 42947e6a..85d0e204 100644 --- a/taos-query/src/common/raw/views/big_int_unsigned_view.rs +++ b/taos-query/src/common/raw/views/big_int_unsigned_view.rs @@ -257,26 +257,6 @@ impl ExactSizeIterator for UBigIntViewIter<'_> { } } -impl>> FromIterator for UBigIntView { - fn from_iter>(iter: T) -> Self { - let (nulls, mut values): (Vec, Vec<_>) = iter - .into_iter() - .map(|v| match v.into() { - Some(v) => (false, v), - None => (true, Item::default()), - }) - .unzip(); - Self { - nulls: NullBits::from_iter(nulls), - data: Bytes::from({ - let (ptr, len, cap) = (values.as_mut_ptr(), values.len(), values.capacity()); - std::mem::forget(values); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * ITEM_SIZE, cap * ITEM_SIZE) } - }), - } - } -} - #[test] fn test_slice() { let data = [0, 1, Item::MIN, Item::MAX]; diff --git a/taos-query/src/common/raw/views/big_int_view.rs b/taos-query/src/common/raw/views/big_int_view.rs index fc9c503d..a90a6a81 100644 --- a/taos-query/src/common/raw/views/big_int_view.rs +++ b/taos-query/src/common/raw/views/big_int_view.rs @@ -258,26 +258,6 @@ impl ExactSizeIterator for BigIntViewIter<'_> { } } -impl>> FromIterator for View { - fn from_iter>(iter: T) -> Self { - let (nulls, mut values): (Vec, Vec<_>) = iter - .into_iter() - .map(|v| match v.into() { - Some(v) => (false, v), - None => (true, Item::default()), - }) - .unzip(); - Self { - nulls: NullBits::from_iter(nulls), - data: Bytes::from({ - let (ptr, len, cap) = (values.as_mut_ptr(), values.len(), values.capacity()); - std::mem::forget(values); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * ITEM_SIZE, cap * ITEM_SIZE) } - }), - } - } -} - #[test] fn test_slice() { let data = [0, 1, 2, i64::MAX]; diff --git a/taos-query/src/common/raw/views/bool_view.rs b/taos-query/src/common/raw/views/bool_view.rs index fcc1d9b8..ab15b7f4 100644 --- a/taos-query/src/common/raw/views/bool_view.rs +++ b/taos-query/src/common/raw/views/bool_view.rs @@ -251,26 +251,6 @@ impl ExactSizeIterator for BoolViewIter<'_> { } } -impl>> FromIterator for BoolView { - fn from_iter>(iter: T) -> Self { - let (nulls, mut values): (Vec, Vec<_>) = iter - .into_iter() - .map(|v| match v.into() { - Some(v) => (false, v), - None => (true, false), - }) - .unzip(); - Self { - nulls: NullBits::from_iter(nulls), - data: Bytes::from({ - let (ptr, len, cap) = (values.as_mut_ptr(), values.len(), values.capacity()); - std::mem::forget(values); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len, cap) } - }), - } - } -} - #[test] fn test_bool_slice() { let data = [true, false, false, true]; diff --git a/taos-query/src/common/raw/views/double_view.rs b/taos-query/src/common/raw/views/double_view.rs index 86c90e51..4d961db2 100644 --- a/taos-query/src/common/raw/views/double_view.rs +++ b/taos-query/src/common/raw/views/double_view.rs @@ -258,27 +258,6 @@ impl ExactSizeIterator for DoubleViewIter<'_> { } } -impl>> FromIterator for View { - fn from_iter>(iter: T) -> Self { - let (nulls, mut values): (Vec, Vec<_>) = iter - .into_iter() - .map(|v| match v.into() { - Some(v) => (false, v), - None => (true, Item::default()), - }) - .unzip(); - // dbg!() - Self { - nulls: NullBits::from_iter(nulls), - data: Bytes::from({ - let (ptr, len, cap) = (values.as_mut_ptr(), values.len(), values.capacity()); - std::mem::forget(values); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * ITEM_SIZE, cap * ITEM_SIZE) } - }), - } - } -} - #[test] fn test_slice() { let data = [0., 1., Item::MIN, Item::MAX]; diff --git a/taos-query/src/common/raw/views/float_view.rs b/taos-query/src/common/raw/views/float_view.rs index 82a428b8..17561365 100644 --- a/taos-query/src/common/raw/views/float_view.rs +++ b/taos-query/src/common/raw/views/float_view.rs @@ -258,26 +258,6 @@ impl ExactSizeIterator for FloatViewIter<'_> { } } -impl>> FromIterator for View { - fn from_iter>(iter: T) -> Self { - let (nulls, mut values): (Vec, Vec<_>) = iter - .into_iter() - .map(|v| match v.into() { - Some(v) => (false, v), - None => (true, Item::default()), - }) - .unzip(); - Self { - nulls: NullBits::from_iter(nulls), - data: Bytes::from({ - let (ptr, len, cap) = (values.as_mut_ptr(), values.len(), values.capacity()); - std::mem::forget(values); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * ITEM_SIZE, cap * ITEM_SIZE) } - }), - } - } -} - #[test] fn test_slice() { let data = [0., 1., Item::MIN, Item::MAX]; diff --git a/taos-query/src/common/raw/views/int_unsigned_view.rs b/taos-query/src/common/raw/views/int_unsigned_view.rs index f5baf33a..bee9064a 100644 --- a/taos-query/src/common/raw/views/int_unsigned_view.rs +++ b/taos-query/src/common/raw/views/int_unsigned_view.rs @@ -253,26 +253,6 @@ impl ExactSizeIterator for UIntViewIter<'_> { } } -impl>> FromIterator for View { - fn from_iter>(iter: T) -> Self { - let (nulls, mut values): (Vec, Vec<_>) = iter - .into_iter() - .map(|v| match v.into() { - Some(v) => (false, v), - None => (true, Item::default()), - }) - .unzip(); - Self { - nulls: NullBits::from_iter(nulls), - data: Bytes::from({ - let (ptr, len, cap) = (values.as_mut_ptr(), values.len(), values.capacity()); - std::mem::forget(values); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * ITEM_SIZE, cap * ITEM_SIZE) } - }), - } - } -} - #[test] fn test_slice() { let data = [0, 1, Item::MIN, Item::MAX]; diff --git a/taos-query/src/common/raw/views/int_view.rs b/taos-query/src/common/raw/views/int_view.rs index defffcc4..95f6852f 100644 --- a/taos-query/src/common/raw/views/int_view.rs +++ b/taos-query/src/common/raw/views/int_view.rs @@ -259,40 +259,59 @@ impl ExactSizeIterator for IntViewIter<'_> { } } -impl>> FromIterator for View { - fn from_iter>(iter: T) -> Self { - let (nulls, mut values): (Vec, Vec<_>) = iter - .into_iter() - .map(|v| match v.into() { - Some(v) => (false, v), - None => (true, Item::default()), - }) - .unzip(); - Self { - nulls: NullBits::from_iter(nulls), - data: Bytes::from({ - let (ptr, len, cap) = (values.as_mut_ptr(), values.len(), values.capacity()); - std::mem::forget(values); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * ITEM_SIZE, cap * ITEM_SIZE) } - }), +#[cfg(test)] +mod tests { + use byteorder::{BigEndian, ByteOrder}; + + use crate::views::int_view::{Item, View, ITEM_SIZE}; + + #[test] + fn test_slice() { + let data = [0, 1, Item::MIN, Item::MAX]; + let view = View::from_iter(data); + dbg!(&view); + let slice = view.slice(1..3); + dbg!(&slice); + + let data = [None, Some(Item::MIN), Some(Item::MAX), None]; + let view = View::from_iter(data); + dbg!(&view); + let range = 1..4; + let slice = view.slice(range.clone()).unwrap(); + for (v, i) in slice.iter().zip(range) { + assert_eq!(v, data[i]); } } -} -#[test] -fn test_slice() { - let data = [0, 1, Item::MIN, Item::MAX]; - let view = View::from_iter(data); - dbg!(&view); - let slice = view.slice(1..3); - dbg!(&slice); - - let data = [None, Some(Item::MIN), Some(Item::MAX), None]; - let view = View::from_iter(data); - dbg!(&view); - let range = 1..4; - let slice = view.slice(range.clone()).unwrap(); - for (v, i) in slice.iter().zip(range) { - assert_eq!(v, data[i]); + #[cfg(target_endian = "little")] + #[test] + fn test_from_iterator() { + let data = [0x12345678]; + let view = View::from_iter(data); + let bytes = view.data; + assert_eq!(bytes.to_vec(), vec![0x78, 0x56, 0x34, 0x12]); + } + + #[cfg(target_endian = "little")] + #[test] + fn test_from_iterator_mock_big_endian() { + let mut bytes = [0u8; 16]; + BigEndian::write_i32(&mut bytes, 0x12345678); + BigEndian::write_i32(&mut bytes[4..], 0x78563412); + BigEndian::write_i32(&mut bytes[8..], 0x12131415); + BigEndian::write_i32(&mut bytes[12..], 0x51413121); + + for i in (0..bytes.len()).step_by(ITEM_SIZE) { + let j = i + ITEM_SIZE; + let val = Item::from_be_bytes(bytes[i..j].try_into().unwrap()); + bytes[i..j].copy_from_slice(&val.to_le_bytes()); + } + + let expect = vec![ + 0x78, 0x56, 0x34, 0x12, 0x12, 0x34, 0x56, 0x78, 0x15, 0x14, 0x13, 0x12, 0x21, 0x31, + 0x41, 0x51, + ]; + + assert_eq!(bytes.to_vec(), expect); } } diff --git a/taos-query/src/common/raw/views/mod.rs b/taos-query/src/common/raw/views/mod.rs index 5ec3908a..c42c0a28 100644 --- a/taos-query/src/common/raw/views/mod.rs +++ b/taos-query/src/common/raw/views/mod.rs @@ -1,11 +1,9 @@ -mod bool_view; +use itertools::Itertools; +mod bool_view; pub use bool_view::BoolView; mod tinyint_view; - -// use bytes::Bytes; -use itertools::Itertools; pub use tinyint_view::TinyIntView; mod smallint_view; @@ -1307,6 +1305,66 @@ impl From for ColumnView { } } +macro_rules! _impl_from_iter { + ($(($view:ident, $item:ty)),+ $(,)?) => { + $( + impl>> FromIterator for $view { + fn from_iter>(iter: T) -> Self { + let (nulls, mut values): (Vec, Vec<_>) = iter + .into_iter() + .map(|v| match v.into() { + Some(v) => (false, v), + None => (true, <$item>::default()), + }) + .unzip(); + + Self { + nulls: NullBits::from_iter(nulls), + data: bytes::Bytes::from({ + let (ptr, len, cap) = (values.as_mut_ptr(), values.len(), values.capacity()); + std::mem::forget(values); + + let item_size = std::mem::size_of::<$item>(); + + #[cfg(target_endian = "little")] + unsafe { + Vec::from_raw_parts(ptr as *mut u8, len * item_size, cap * item_size) + } + + #[cfg(target_endian = "big")] + { + let mut bytes = unsafe { + Vec::from_raw_parts(ptr as *mut u8, len * item_size, cap * item_size) + }; + for i in (0..bytes.len()).step_by(item_size) { + let j = i + item_size; + let val = <$item>::from_ne_bytes(&bytes[i..j].try_into().expect("slice with incorrect length")); + bytes[i..j].copy_from_slice(&val.to_le_bytes()); + } + bytes + } + }), + } + } + } + )+ + }; +} + +_impl_from_iter!( + (BoolView, bool), + (TinyIntView, i8), + (SmallIntView, i16), + (IntView, i32), + (BigIntView, i64), + (UTinyIntView, u8), + (USmallIntView, u16), + (UIntView, u32), + (UBigIntView, u64), + (FloatView, f32), + (DoubleView, f64), +); + #[cfg(test)] mod tests { use super::*; diff --git a/taos-query/src/common/raw/views/small_int_unsigned_view.rs b/taos-query/src/common/raw/views/small_int_unsigned_view.rs index e9adcaeb..80a6eb9f 100644 --- a/taos-query/src/common/raw/views/small_int_unsigned_view.rs +++ b/taos-query/src/common/raw/views/small_int_unsigned_view.rs @@ -257,26 +257,6 @@ impl ExactSizeIterator for USmallIntViewIter<'_> { } } -impl>> FromIterator for View { - fn from_iter>(iter: T) -> Self { - let (nulls, mut values): (Vec, Vec<_>) = iter - .into_iter() - .map(|v| match v.into() { - Some(v) => (false, v), - None => (true, Item::default()), - }) - .unzip(); - Self { - nulls: NullBits::from_iter(nulls), - data: Bytes::from({ - let (ptr, len, cap) = (values.as_mut_ptr(), values.len(), values.capacity()); - std::mem::forget(values); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * ITEM_SIZE, cap * ITEM_SIZE) } - }), - } - } -} - #[test] fn test_slice() { let data = [0, 1, Item::MIN, Item::MAX]; diff --git a/taos-query/src/common/raw/views/smallint_view.rs b/taos-query/src/common/raw/views/smallint_view.rs index 91069a72..544c3aba 100644 --- a/taos-query/src/common/raw/views/smallint_view.rs +++ b/taos-query/src/common/raw/views/smallint_view.rs @@ -32,6 +32,7 @@ impl std::ops::Add for View { &self + &rhs } } + impl std::ops::Add for &View { type Output = View; @@ -257,26 +258,6 @@ impl ExactSizeIterator for SmallIntViewIter<'_> { } } -impl>> FromIterator for View { - fn from_iter>(iter: T) -> Self { - let (nulls, mut values): (Vec, Vec<_>) = iter - .into_iter() - .map(|v| match v.into() { - Some(v) => (false, v), - None => (true, Item::default()), - }) - .unzip(); - Self { - nulls: NullBits::from_iter(nulls), - data: Bytes::from({ - let (ptr, len, cap) = (values.as_mut_ptr(), values.len(), values.capacity()); - std::mem::forget(values); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * ITEM_SIZE, cap * ITEM_SIZE) } - }), - } - } -} - #[test] fn test_slice() { let data = [0, 1, Item::MIN, Item::MAX]; diff --git a/taos-query/src/common/raw/views/timestamp_view.rs b/taos-query/src/common/raw/views/timestamp_view.rs index de1f07b0..03453310 100644 --- a/taos-query/src/common/raw/views/timestamp_view.rs +++ b/taos-query/src/common/raw/views/timestamp_view.rs @@ -293,26 +293,6 @@ impl TimestampMillisecondView { } } -impl>> FromIterator for TimestampMillisecondView { - fn from_iter>(iter: T) -> Self { - let (nulls, mut values): (Vec, Vec<_>) = iter - .into_iter() - .map(|v| match v.into() { - Some(v) => (false, v), - None => (true, Item::default()), - }) - .unzip(); - Self(View { - nulls: NullBits::from_iter(nulls), - data: Bytes::from({ - let (ptr, len, cap) = (values.as_mut_ptr(), values.len(), values.capacity()); - std::mem::forget(values); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * ITEM_SIZE, cap * ITEM_SIZE) } - }), - precision: Precision::Millisecond, - }) - } -} pub struct TimestampMicrosecondView(View); impl TimestampMicrosecondView { pub fn into_inner(self) -> View { @@ -320,26 +300,6 @@ impl TimestampMicrosecondView { } } -impl>> FromIterator for TimestampMicrosecondView { - fn from_iter>(iter: T) -> Self { - let (nulls, mut values): (Vec, Vec<_>) = iter - .into_iter() - .map(|v| match v.into() { - Some(v) => (false, v), - None => (true, Item::default()), - }) - .unzip(); - Self(View { - nulls: NullBits::from_iter(nulls), - data: Bytes::from({ - let (ptr, len, cap) = (values.as_mut_ptr(), values.len(), values.capacity()); - std::mem::forget(values); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * ITEM_SIZE, cap * ITEM_SIZE) } - }), - precision: Precision::Microsecond, - }) - } -} pub struct TimestampNanosecondView(View); impl TimestampNanosecondView { pub fn into_inner(self) -> View { @@ -347,27 +307,57 @@ impl TimestampNanosecondView { } } -impl>> FromIterator for TimestampNanosecondView { - fn from_iter>(iter: T) -> Self { - let (nulls, mut values): (Vec, Vec<_>) = iter - .into_iter() - .map(|v| match v.into() { - Some(v) => (false, v), - None => (true, Item::default()), - }) - .unzip(); - Self(View { - nulls: NullBits::from_iter(nulls), - data: Bytes::from({ - let (ptr, len, cap) = (values.as_mut_ptr(), values.len(), values.capacity()); - std::mem::forget(values); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * ITEM_SIZE, cap * ITEM_SIZE) } - }), - precision: Precision::Nanosecond, - }) - } +macro_rules! _impl_from_iter { + ($(($view:ident, $precision:expr)),+ $(,)?) => { + $( + impl>> FromIterator for $view { + fn from_iter>(iter: T) -> Self { + let (nulls, mut values): (Vec, Vec<_>) = iter + .into_iter() + .map(|v| match v.into() { + Some(v) => (false, v), + None => (true, Item::default()), + }) + .unzip(); + + Self (View{ + nulls: NullBits::from_iter(nulls), + data: bytes::Bytes::from({ + let (ptr, len, cap) = (values.as_mut_ptr(), values.len(), values.capacity()); + std::mem::forget(values); + + #[cfg(target_endian = "little")] + unsafe { + Vec::from_raw_parts(ptr as *mut u8, len * ITEM_SIZE, cap * ITEM_SIZE) + } + + #[cfg(target_endian = "big")] + { + let mut bytes = unsafe { + Vec::from_raw_parts(ptr as *mut u8, len * ITEM_SIZE, cap * ITEM_SIZE) + }; + for i in (0..bytes.len()).step_by(ITEM_SIZE) { + let j = i + ITEM_SIZE; + let val = Item::from_ne_bytes(&bytes[i..j].try_into().expect("slice with incorrect length")); + bytes[i..j].copy_from_slice(&val.to_le_bytes()); + } + bytes + } + }), + precision: $precision, + }) + } + } + )+ + }; } +_impl_from_iter!( + (TimestampMillisecondView, Precision::Millisecond), + (TimestampMicrosecondView, Precision::Microsecond), + (TimestampNanosecondView, Precision::Nanosecond), +); + #[test] fn test_slice() { let data = [0, 1, Item::MIN, Item::MAX]; diff --git a/taos-query/src/common/raw/views/tinyint_unsigned_view.rs b/taos-query/src/common/raw/views/tinyint_unsigned_view.rs index 10da62ed..d34f2cd9 100644 --- a/taos-query/src/common/raw/views/tinyint_unsigned_view.rs +++ b/taos-query/src/common/raw/views/tinyint_unsigned_view.rs @@ -211,26 +211,6 @@ impl ExactSizeIterator for UTinyIntViewIter<'_> { } } -impl>> FromIterator for View { - fn from_iter>(iter: T) -> Self { - let (nulls, mut values): (Vec, Vec<_>) = iter - .into_iter() - .map(|v| match v.into() { - Some(v) => (false, v), - None => (true, Item::default()), - }) - .unzip(); - Self { - nulls: NullBits::from_iter(nulls), - data: Bytes::from({ - let (ptr, len, cap) = (values.as_mut_ptr(), values.len(), values.capacity()); - std::mem::forget(values); - unsafe { Vec::from_raw_parts(ptr, len * ITEM_SIZE, cap * ITEM_SIZE) } - }), - } - } -} - #[test] fn test_slice() { let data = [0, 1, Item::MIN, Item::MAX]; diff --git a/taos-query/src/common/raw/views/tinyint_view.rs b/taos-query/src/common/raw/views/tinyint_view.rs index 86dfc3fe..fff04d82 100644 --- a/taos-query/src/common/raw/views/tinyint_view.rs +++ b/taos-query/src/common/raw/views/tinyint_view.rs @@ -212,26 +212,6 @@ impl ExactSizeIterator for TinyIntViewIter<'_> { } } -impl>> FromIterator for View { - fn from_iter>(iter: T) -> Self { - let (nulls, mut values): (Vec, Vec<_>) = iter - .into_iter() - .map(|v| match v.into() { - Some(v) => (false, v), - None => (true, Item::default()), - }) - .unzip(); - Self { - nulls: NullBits::from_iter(nulls), - data: Bytes::from({ - let (ptr, len, cap) = (values.as_mut_ptr(), values.len(), values.capacity()); - std::mem::forget(values); - unsafe { Vec::from_raw_parts(ptr as *mut u8, len * ITEM_SIZE, cap * ITEM_SIZE) } - }), - } - } -} - #[test] fn test_slice() { let data = [0, 1, Item::MIN, Item::MAX]; diff --git a/taos-query/src/common/value.rs b/taos-query/src/common/value.rs index ed19f27f..6c5f6bc0 100644 --- a/taos-query/src/common/value.rs +++ b/taos-query/src/common/value.rs @@ -437,7 +437,6 @@ impl Display for BorrowedValue<'_> { unsafe impl Send for BorrowedValue<'_> {} -// #[derive(Debug, Clone)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub enum Value { Null(Ty), // 0 diff --git a/taos-query/src/lib.rs b/taos-query/src/lib.rs index 91049ae1..10caa907 100644 --- a/taos-query/src/lib.rs +++ b/taos-query/src/lib.rs @@ -27,6 +27,7 @@ pub mod helpers; mod iter; pub mod prelude; pub mod stmt; +pub mod stmt2; pub mod tmq; pub mod util; diff --git a/taos-query/src/prelude.rs b/taos-query/src/prelude.rs index 78729f47..4da7f863 100644 --- a/taos-query/src/prelude.rs +++ b/taos-query/src/prelude.rs @@ -44,6 +44,7 @@ pub mod sync { use crate::common::*; use crate::helpers::*; pub use crate::stmt::Bindable; + pub use crate::stmt2::{Stmt2BindParam, Stmt2Bindable}; pub use crate::tmq::{AsConsumer, IsData, IsMeta}; #[cfg(feature = "r2d2")] pub use crate::{Pool, PoolBuilder}; @@ -307,6 +308,7 @@ mod r#async { use crate::common::*; use crate::helpers::*; pub use crate::stmt::AsyncBindable; + pub use crate::stmt2::{Stmt2AsyncBindable, Stmt2BindParam}; pub use crate::util::{AsyncInlinable, AsyncInlinableRead, AsyncInlinableWrite}; pub use crate::RawResult; diff --git a/taos-query/src/stmt2/mod.rs b/taos-query/src/stmt2/mod.rs new file mode 100644 index 00000000..8cae369f --- /dev/null +++ b/taos-query/src/stmt2/mod.rs @@ -0,0 +1,71 @@ +use crate::{AsyncQueryable, ColumnView, Queryable, RawResult, Value}; + +pub trait Stmt2Bindable +where + Q: Queryable, + Self: Sized, +{ + fn init(taos: &Q) -> RawResult; + + fn prepare(&mut self, sql: &str) -> RawResult<&mut Self>; + + fn bind(&mut self, params: &[Stmt2BindParam]) -> RawResult<&mut Self>; + + fn exec(&mut self) -> RawResult; + + fn affected_rows(&self) -> usize; + + fn result_set(&self) -> RawResult; +} + +#[async_trait::async_trait] +pub trait Stmt2AsyncBindable +where + Q: AsyncQueryable, + Self: Sized, +{ + async fn init(taos: &Q) -> RawResult; + + async fn prepare(&mut self, sql: &str) -> RawResult<&mut Self>; + + async fn bind(&mut self, params: &[Stmt2BindParam]) -> RawResult<&mut Self>; + + async fn exec(&mut self) -> RawResult; + + async fn affected_rows(&self) -> usize; + + async fn result_set(&self) -> RawResult; +} + +#[derive(Clone, Debug)] +pub struct Stmt2BindParam { + table_name: Option, + tags: Option>, + columns: Option>, +} + +impl Stmt2BindParam { + pub fn new( + table_name: Option, + tags: Option>, + columns: Option>, + ) -> Self { + Self { + table_name, + tags, + columns, + } + } + + pub fn table_name(&self) -> Option<&String> { + self.table_name.as_ref() + } + + pub fn tags(&self) -> Option<&Vec> { + self.tags.as_ref() + } + + pub fn columns(&self) -> Option<&Vec> { + self.columns.as_ref() + } +} diff --git a/taos-query/src/util/inline_str.rs b/taos-query/src/util/inline_str.rs index 5945dbe0..01cacf48 100644 --- a/taos-query/src/util/inline_str.rs +++ b/taos-query/src/util/inline_str.rs @@ -1,6 +1,5 @@ use std::fmt; -// use super::{Inlinable, AsyncInlinable}; use tokio::io::*; #[repr(C)] @@ -9,10 +8,10 @@ pub struct InlineStr { len: T, data: [u8; 0], } + macro_rules! _impl_inline_str { ($($ty:ty) *) => { $( - impl fmt::Debug for InlineStr<$ty> { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -83,6 +82,7 @@ macro_rules! _impl_inline_str { pub const fn as_ptr(&self) -> *const u8 { self.data.as_ptr() } + #[inline] pub fn as_mut_ptr(&mut self) -> *mut u8 { self.data.as_mut_ptr() @@ -125,6 +125,7 @@ macro_rules! _impl_inline_str { )* }; } + _impl_inline_str!(u8 u16 u32 u64 usize); macro_rules! _impl_test_inline_str { diff --git a/taos-query/src/util/mod.rs b/taos-query/src/util/mod.rs index 26c35041..ba0ed122 100644 --- a/taos-query/src/util/mod.rs +++ b/taos-query/src/util/mod.rs @@ -7,6 +7,8 @@ pub mod hex; mod inline_read; mod inline_write; +pub mod req_id; + use std::collections::BTreeMap; use std::io::{Read, Write}; @@ -16,6 +18,7 @@ pub use inline_nchar::InlineNChar; pub use inline_read::AsyncInlinableRead; pub use inline_str::InlineStr; pub use inline_write::AsyncInlinableWrite; +pub use req_id::generate_req_id; use tokio::io::{AsyncRead, AsyncWrite}; use crate::{RawError, RawResult}; diff --git a/taos-query/src/util/req_id.rs b/taos-query/src/util/req_id.rs new file mode 100644 index 00000000..633e8342 --- /dev/null +++ b/taos-query/src/util/req_id.rs @@ -0,0 +1,104 @@ +use std::sync::atomic::{AtomicU64, Ordering}; +use std::time::{SystemTime, UNIX_EPOCH}; + +use once_cell::sync::Lazy; + +static HASH_ID: Lazy = Lazy::new(|| { + let uuid = uuid::Uuid::new_v4().into_bytes(); + let hash_id = murmur_hash3_32(&uuid) as u64; + (hash_id & 0x0FFF) << 52 +}); + +static PID: Lazy = Lazy::new(|| { + let pid = std::process::id() as u64; + (pid & 0x0FFF) << 40 +}); + +static SERIAL_NO: AtomicU64 = AtomicU64::new(0); + +/// The request id is an unsigned integer format of 64bit. +/// +/// ```text +/// +-------+-------+-----------+---------------+ +/// | uuid | pid | timestamp | serial number | +/// +-------+-------+-----------+---------------+ +/// | 12bit | 12bit | 24bit | 16bit | +/// +-------+-------+-----------+---------------+ +/// ``` +#[inline] +pub fn generate_req_id() -> u64 { + let now = SystemTime::now(); + let ts = now.duration_since(UNIX_EPOCH).unwrap().as_millis() as u64; + let val = SERIAL_NO.fetch_add(1, Ordering::SeqCst); + *HASH_ID | *PID | ((ts & 0xFFFFFF) << 16) | (val & 0xFFFF) +} + +fn murmur_hash3_32(key: &[u8]) -> u32 { + const C1: u32 = 0xcc9e2d51; + const C2: u32 = 0x1b873593; + + let mut h1: u32 = 0x12345678; + let mut blocks = key.chunks_exact(4); + for block in blocks.by_ref() { + assert!(block.len() == 4); + let mut k1 = u32::from_le_bytes([block[0], block[1], block[2], block[3]]); + k1 = k1.wrapping_mul(C1); + k1 = k1.rotate_left(15); + k1 = k1.wrapping_mul(C2); + + h1 ^= k1; + h1 = h1.rotate_left(13); + h1 = h1.wrapping_mul(5); + h1 = h1.wrapping_add(0xe6546b64); + } + + let mut k1 = 0u32; + let tail = blocks.remainder(); + for (i, &b) in tail.iter().enumerate() { + k1 ^= (b as u32) << (i * 8); + } + + if k1 != 0 { + k1 = k1.wrapping_mul(C1); + k1 = k1.rotate_left(15); + k1 = k1.wrapping_mul(C2); + h1 ^= k1; + } + + h1 ^= key.len() as u32; + fmix32(h1) +} + +fn fmix32(mut h: u32) -> u32 { + h ^= h >> 16; + h = h.wrapping_mul(0x85ebca6b); + h ^= h >> 13; + h = h.wrapping_mul(0xc2b2ae35); + h ^= h >> 16; + h +} + +#[cfg(test)] +mod tests { + use super::murmur_hash3_32; + + #[test] + fn test_murmur_hash3_32() { + let a = "a".repeat(1000); + let test_cases = vec![ + ("02c9f9bb-5da4-412e-94f3-ad36439073ce", 0x7bfed446), + ("hello", 0xc7e66d96), + ("hell", 0xc9212d1a), + ("hel", 0x28568542), + ("he", 0xd2f4855c), + ("h", 0x894ac50a), + ("", 0xe37cd1bc), + ("中文", 0xafb2cd2a), + (&a, 0x176450c7), + ]; + + for (key, expected) in test_cases { + assert_eq!(murmur_hash3_32(key.as_bytes()), expected); + } + } +} diff --git a/taos-ws/Cargo.toml b/taos-ws/Cargo.toml index ba17947c..914914db 100644 --- a/taos-ws/Cargo.toml +++ b/taos-ws/Cargo.toml @@ -16,6 +16,7 @@ repository.workspace = true anyhow = "1" async-trait = { version = "0.1.80" } bytes = "1.1.0" +byteorder = "1.5.0" dashmap = "6.1.0" futures = { version = "0.3" } itertools = "0.13.0" diff --git a/taos-ws/src/lib.rs b/taos-ws/src/lib.rs index 9aace162..3b307b6c 100644 --- a/taos-ws/src/lib.rs +++ b/taos-ws/src/lib.rs @@ -13,6 +13,9 @@ use tracing::warn; pub mod stmt; pub use stmt::Stmt; +pub mod stmt2; +pub use stmt2::Stmt2; + // pub mod tmq; pub mod consumer; pub use consumer::{Consumer, Offset, TmqBuilder}; diff --git a/taos-ws/src/query/asyn.rs b/taos-ws/src/query/asyn.rs index 86360d54..f8fd11d9 100644 --- a/taos-ws/src/query/asyn.rs +++ b/taos-ws/src/query/asyn.rs @@ -4,19 +4,22 @@ use std::io::Write; use std::mem::transmute; use std::ops::ControlFlow; use std::pin::Pin; -// use std::io::Write; use std::sync::atomic::AtomicU64; use std::sync::Arc; use std::task::Poll; use std::time::{Duration, Instant}; use anyhow::bail; +use byteorder::{ByteOrder, LittleEndian}; +use flume::Sender; +use futures::channel::oneshot; use futures::stream::SplitStream; use futures::{FutureExt, SinkExt, StreamExt, TryStreamExt}; use itertools::Itertools; +use oneshot::channel as query_channel; use taos_query::common::{Field, Precision, RawBlock, RawMeta, SmlData}; use taos_query::prelude::{Code, RawError, RawResult}; -use taos_query::util::InlinableWrite; +use taos_query::util::{generate_req_id, InlinableWrite}; use taos_query::{AsyncFetchable, AsyncQueryable, DeError, DsnError, IntoDsn}; use thiserror::Error; use tokio::net::TcpStream; @@ -30,14 +33,9 @@ use tracing::{instrument, trace, Instrument}; use super::infra::*; use super::TaosBuilder; -// type WsSender = flume::Sender>; type WsSender = flume::Sender; -use futures::channel::oneshot; -use oneshot::channel as query_channel; type QueryChannelSender = oneshot::Sender>; -// use tokio::sync::mpsc::unbounded_channel as query_channel; -// type QueryChannelSender = tokio::sync::mpsc::UnboundedSender>; type QueryInner = scc::HashMap; type QueryAgent = Arc; type QueryResMapper = scc::HashMap; @@ -50,19 +48,15 @@ struct Version { is_support_binary_sql: bool, } -// impl Version { -// pub fn is_v3(&self) -> bool { -// !self.0.starts_with("2") -// } -// } #[derive(Debug, Clone)] -struct WsQuerySender { +pub(crate) struct WsQuerySender { version: Version, req_id: Arc, results: Arc, sender: WsSender, queries: QueryAgent, } + const SEND_TIMEOUT: Duration = Duration::from_millis(1000); impl WsQuerySender { @@ -70,6 +64,7 @@ impl WsQuerySender { self.req_id .fetch_add(1, std::sync::atomic::Ordering::SeqCst) } + fn req_id_ref(&self) -> &Arc { &self.req_id } @@ -96,7 +91,6 @@ impl WsQuerySender { .await .map_err(Error::from)? .map_err(Error::from)?; - // } WsSend::Binary(bytes) => { timeout(SEND_TIMEOUT, self.sender.send_async(Message::Binary(bytes))) @@ -121,6 +115,7 @@ impl WsQuerySender { tracing::trace!("[req id: {req_id}] message received: {res:?}"); Ok(res) } + async fn send_only(&self, msg: WsSend) -> RawResult<()> { timeout(SEND_TIMEOUT, self.sender.send_async(msg.to_msg())) .await @@ -140,38 +135,39 @@ pub struct WsTaos { close_signal: watch::Sender, sender: WsQuerySender, } + impl Drop for WsTaos { fn drop(&mut self) { - tracing::trace!("dropping connection"); - // send close signal to reader/writer spawned tasks. + trace!("dropping connection"); + // Send close signal to reader/writer spawned tasks. let _ = self.close_signal.send(true); } } #[derive(Debug, Clone, Default)] -struct QueryMetrics { - num_of_fetches: usize, - time_cost_in_fetch: Duration, - time_cost_in_block_parse: Duration, - time_cost_in_flume: Duration, +pub(crate) struct QueryMetrics { + pub(crate) num_of_fetches: usize, + pub(crate) time_cost_in_fetch: Duration, + pub(crate) time_cost_in_block_parse: Duration, + pub(crate) time_cost_in_flume: Duration, } type BlockFuture = Pin>> + Send>>; pub struct ResultSet { - sender: WsQuerySender, - args: WsResArgs, - fields: Option>, - fields_count: usize, - affected_rows: usize, - precision: Precision, - summary: (usize, usize), - timing: Duration, - block_future: Option, - closer: Option>, - completed: bool, - metrics: QueryMetrics, - blocks_buffer: Option>>, + pub(crate) sender: WsQuerySender, + pub(crate) args: WsResArgs, + pub(crate) fields: Option>, + pub(crate) fields_count: usize, + pub(crate) affected_rows: usize, + pub(crate) precision: Precision, + pub(crate) summary: (usize, usize), + pub(crate) timing: Duration, + pub(crate) block_future: Option, + pub(crate) closer: Option>, + pub(crate) completed: bool, + pub(crate) metrics: QueryMetrics, + pub(crate) blocks_buffer: Option>>, } unsafe impl Sync for ResultSet {} @@ -394,7 +390,19 @@ async fn read_queries( tracing::warn!("req_id {req_id} not detected, message might be lost"); } } - + WsRecvData::Stmt2Init { .. } + | WsRecvData::Stmt2Prepare { .. } + | WsRecvData::Stmt2Bind { .. } + | WsRecvData::Stmt2Exec { .. } + | WsRecvData::Stmt2Result { .. } + | WsRecvData::Stmt2Close { .. } => match queries_sender.remove(&req_id) { + Some((_, sender)) => { + let _ = sender.send(ok.map(|_| data)); + } + None => { + tracing::warn!("req_id {req_id} not detected, message might be lost"); + } + }, // Block type is for binary. _ => unreachable!(), } @@ -852,6 +860,7 @@ impl WsTaos { } } } + async fn s_write_raw_block(&self, raw: &RawBlock) -> RawResult<()> { let req_id = self.sender.req_id(); self.s_write_raw_block_with_req_id(raw, req_id) @@ -974,88 +983,34 @@ impl WsTaos { if resp.fields_count > 0 { let names = resp.fields_names.unwrap(); let types = resp.fields_types.unwrap(); - let bytes = resp.fields_lengths.unwrap(); - let fields: Vec<_> = names - .into_iter() + let lens = resp.fields_lengths.unwrap(); + let fields: Vec = names + .iter() .zip(types) - .zip(bytes) - .map(|((name, ty), bytes)| Field::new(name, ty, bytes)) + .zip(lens) + .map(|((name, ty), len)| Field::new(name, ty, len)) .collect(); // Start query. let req_id_ref = self.sender.req_id_ref().clone(); let sender = self.sender.clone(); - let fields_ref = fields.clone(); - let query_id = resp.id; + let res_id = resp.id; let precision = resp.precision; let (tx, rx) = flume::bounded(64); if sender.version.is_support_binary_sql { - tokio::spawn( - async move { - let mut metrics = QueryMetrics::default(); - let field_names = fields_ref.iter().map(Field::name).collect_vec(); - let mut req_vec = Vec::with_capacity(8 * 3 + 2); - - loop { - let id = req_id_ref.fetch_add(1, std::sync::atomic::Ordering::SeqCst); - req_vec.clear(); - req_vec.write_u64_le(id).map_err(Error::from).unwrap(); - req_vec.write_u64_le(query_id).map_err(Error::from).unwrap(); //ResultID - req_vec.write_u64_le(7).map_err(Error::from).unwrap(); //ActionID, 7 for fetch - req_vec.write_u16_le(1).map_err(Error::from).unwrap(); //Version - let now = Instant::now(); - match sender.send_recv(WsSend::Binary(req_vec.clone())).await { - Ok(WsRecvData::BlockNew { - block_code, - block_message, - timing, - finished, - raw, - .. - }) => { - metrics.num_of_fetches += 1; - metrics.time_cost_in_fetch += now.elapsed(); - if block_code != 0 { - return Err(RawError::new(block_code, block_message)); - } - - if finished { - drop(tx); - break; - } - let now = Instant::now(); - let mut raw = RawBlock::parse_from_raw_block(raw, precision); - metrics.time_cost_in_block_parse += now.elapsed(); - raw.with_field_names(&field_names); - if tx.send_async(Ok((raw, timing))).await.is_err() { - break; - } - } - Ok(_) => {} - Err(err) => { - if tx.send_async(Err(err)).await.is_err() { - break; - } - } - } - } - trace!("Spawn metrics: {:?}", metrics); - Ok(()) - } - .in_current_span(), - ); + tokio::spawn(fetch(sender, res_id, tx, precision, names).in_current_span()); } else { // Start query. + let fields = fields.clone(); tokio::spawn( async move { let mut metrics = QueryMetrics::default(); - let field_names = fields_ref.iter().map(Field::name).collect_vec(); loop { let now = Instant::now(); let args = WsResArgs { req_id: req_id_ref .fetch_add(1, std::sync::atomic::Ordering::SeqCst), - id: query_id, + id: res_id, }; let fetch = WsSend::Fetch(args); let fetch_resp = match sender.send_recv(fetch).await { @@ -1073,7 +1028,7 @@ impl WsTaos { let args = WsResArgs { req_id: req_id_ref .fetch_add(1, std::sync::atomic::Ordering::SeqCst), - id: query_id, + id: res_id, }; let fetch_block = WsSend::FetchBlock(args); @@ -1081,7 +1036,7 @@ impl WsTaos { Ok(WsRecvData::Block { timing, raw }) => { metrics.time_cost_in_fetch += now.elapsed(); let mut raw = RawBlock::parse_from_raw_block(raw, precision); - raw.with_field_names(&field_names); + raw.with_field_names(&names); if tx.send_async(Ok((raw, timing))).await.is_err() { break; } @@ -1090,13 +1045,13 @@ impl WsTaos { metrics.time_cost_in_fetch += now.elapsed(); let mut raw = RawBlock::parse_from_raw_block_v2( raw, - &fields_ref, + &fields, fetch_resp.lengths.as_ref().unwrap(), fetch_resp.rows, precision, ); - raw.with_field_names(&field_names); + raw.with_field_names(&names); if tx.send_async(Ok((raw, timing))).await.is_err() { break; } @@ -1149,7 +1104,7 @@ impl WsTaos { timing: resp.timing, block_future: None, closer: Some(closer), - completed: false, + completed: true, metrics: QueryMetrics::default(), blocks_buffer: None, }) @@ -1201,6 +1156,85 @@ impl WsTaos { pub fn get_req_id(&self) -> ReqId { self.sender.req_id() } + + pub(crate) async fn send_request(&self, req: WsSend) -> RawResult { + self.sender.send_recv(req).await + } + + pub(crate) fn sender(&self) -> WsQuerySender { + self.sender.clone() + } +} + +pub(crate) async fn fetch( + sender: WsQuerySender, + res_id: ResId, + raw_block_tx: Sender>, + precision: Precision, + field_names: Vec, +) -> RawResult<()> { + const ACTION: u64 = 7; + const VERSION: u16 = 1; + + let mut bytes = vec![0u8; 26]; + LittleEndian::write_u64(&mut bytes[8..], res_id); + LittleEndian::write_u64(&mut bytes[16..], ACTION); + LittleEndian::write_u16(&mut bytes[24..], VERSION); + + let mut metrics = QueryMetrics::default(); + + loop { + LittleEndian::write_u64(&mut bytes, generate_req_id()); + + let fetch_start = Instant::now(); + match sender.send_recv(WsSend::Binary(bytes.clone())).await { + Ok(WsRecvData::BlockNew { + block_code, + block_message, + timing, + finished, + raw, + .. + }) => { + metrics.num_of_fetches += 1; + metrics.time_cost_in_fetch += fetch_start.elapsed(); + + if block_code != 0 { + return Err(RawError::new(block_code, block_message)); + } + + if finished { + tracing::trace!("Finished processing result:{res_id}"); + drop(raw_block_tx); + break; + } + + let parse_start = Instant::now(); + let mut raw_block = RawBlock::parse_from_raw_block(raw, precision); + metrics.time_cost_in_block_parse += parse_start.elapsed(); + + raw_block.with_field_names(&field_names); + if raw_block_tx + .send_async(Ok((raw_block, timing))) + .await + .is_err() + { + tracing::error!("Failed to send raw block; receiver may be closed"); + break; + } + } + Ok(_) => tracing::warn!("Unexpected response for result:{res_id}"), + Err(err) => { + if raw_block_tx.send_async(Err(err)).await.is_err() { + tracing::error!("Failed to send err; receiver may be closed"); + break; + } + } + } + } + + tracing::trace!("Metrics for result:{res_id}: {metrics:?}"); + Ok(()) } impl ResultSet { diff --git a/taos-ws/src/query/infra.rs b/taos-ws/src/query/infra.rs index ba478a5b..c9fb4b18 100644 --- a/taos-ws/src/query/infra.rs +++ b/taos-ws/src/query/infra.rs @@ -6,6 +6,7 @@ use taos_query::common::{Precision, Ty}; use taos_query::prelude::RawError; pub type ReqId = u64; +pub type StmtId = u64; /// Type for result ID. pub type ResId = u64; @@ -23,7 +24,7 @@ pub struct WsConnReq { impl WsConnReq { #[cfg(test)] - pub fn new(user: impl Into, password: impl Into) -> Self { + fn new(user: impl Into, password: impl Into) -> Self { Self { user: Some(user.into()), password: Some(password.into()), @@ -31,10 +32,6 @@ impl WsConnReq { mode: None, } } - // pub fn with_database(mut self, db: impl Into) -> Self { - // self.db = Some(db.into()); - // self - // } } #[derive(Debug, Serialize, Clone, Copy)] @@ -48,7 +45,6 @@ pub struct WsResArgs { #[serde(rename_all = "snake_case")] pub enum WsSend { Version, - // Pong(Vec), Conn { req_id: ReqId, #[serde(flatten)] @@ -69,17 +65,46 @@ pub enum WsSend { FetchBlock(WsResArgs), Binary(Vec), FreeResult(WsResArgs), + Stmt2Init { + req_id: ReqId, + single_stb_insert: bool, + single_table_bind_once: bool, + }, + Stmt2Prepare { + req_id: ReqId, + stmt_id: StmtId, + sql: String, + get_fields: bool, + }, + Stmt2Exec { + req_id: ReqId, + stmt_id: StmtId, + }, + Stmt2Result { + req_id: ReqId, + stmt_id: StmtId, + }, + Stmt2Close { + req_id: ReqId, + stmt_id: StmtId, + }, } impl WsSend { pub(crate) fn req_id(&self) -> ReqId { match self { - WsSend::Conn { req_id, .. } | WsSend::Query { req_id, .. } => *req_id, + WsSend::Conn { req_id, .. } + | WsSend::Query { req_id, .. } + | WsSend::Stmt2Init { req_id, .. } + | WsSend::Stmt2Prepare { req_id, .. } + | WsSend::Stmt2Exec { req_id, .. } + | WsSend::Stmt2Result { req_id, .. } + | WsSend::Stmt2Close { req_id, .. } => *req_id, WsSend::Insert { req_id, .. } => req_id.unwrap_or(0), + WsSend::Binary(bytes) => unsafe { *(bytes.as_ptr() as *const u64) as _ }, WsSend::Fetch(args) | WsSend::FetchBlock(args) | WsSend::FreeResult(args) => { args.req_id } - WsSend::Binary(bytes) => unsafe { *(bytes.as_ptr() as *const u64) as _ }, WsSend::Version => unreachable!(), } } @@ -88,31 +113,6 @@ impl WsSend { unsafe impl Send for WsSend {} unsafe impl Sync for WsSend {} -#[test] -fn test_serde_send() { - let s = WsSend::Conn { - req_id: 1, - req: WsConnReq::new("root", "taosdata"), - }; - let v = serde_json::to_value(s).unwrap(); - let j = serde_json::json!({ - "action": "conn", - "args": { - "req_id": 1, - "user": "root", - "password": "taosdata", - "db": "", - } - }); - assert_eq!(v, j); -} - -#[derive(Debug, Serialize)] -pub struct WsFetchArgs { - req_id: ReqId, - id: ResId, -} - #[serde_as] #[derive(Debug, Deserialize, Default, Clone)] #[serde(default)] @@ -149,6 +149,35 @@ pub struct WsFetchResp { pub timing: Duration, } +#[serde_as] +#[derive(Debug, Deserialize)] +pub struct WsRecv { + pub code: i32, + #[serde_as(as = "NoneAsEmptyString")] + pub message: Option, + #[serde(default)] + pub req_id: ReqId, + #[serde(flatten)] + pub data: WsRecvData, +} + +impl WsRecv { + pub(crate) fn ok(self) -> (ReqId, WsRecvData, Result<(), RawError>) { + ( + self.req_id, + self.data, + if self.code == 0 { + Ok(()) + } else if self.message.as_deref() == Some("success") { + Err(RawError::from_code(self.code)) + } else { + Err(RawError::new(self.code, self.message.unwrap_or_default())) + }, + ) + } +} + +#[allow(dead_code)] #[derive(Debug, Deserialize, Clone)] #[serde_as] #[serde(tag = "action")] @@ -194,46 +223,125 @@ pub enum WsRecvData { WriteRaw, WriteRawBlock, WriteRawBlockWithFields, + Stmt2Init { + #[serde(default)] + stmt_id: StmtId, + #[serde(default)] + timing: u64, + }, + Stmt2Prepare { + #[serde(default)] + stmt_id: StmtId, + #[serde(default)] + is_insert: bool, + #[serde(default)] + fields: Option>, + #[serde(default)] + fields_count: usize, + #[serde(default)] + timing: u64, + }, + Stmt2Bind { + #[serde(default)] + stmt_id: StmtId, + #[serde(default)] + timing: u64, + }, + Stmt2Exec { + #[serde(default)] + stmt_id: StmtId, + #[serde(default)] + affected: usize, + #[serde(default)] + timing: u64, + }, + Stmt2Result { + #[serde(default)] + stmt_id: StmtId, + #[serde(default)] + id: u64, + #[serde(default)] + fields_count: u64, + #[serde(default)] + fields_names: Vec, + #[serde(default)] + fields_types: Vec, + #[serde(default)] + fields_lengths: Vec, + #[serde(default)] + precision: Precision, + #[serde(default)] + timing: u64, + }, + Stmt2Close { + #[serde(default)] + stmt_id: StmtId, + #[serde(default)] + timing: u64, + }, } -#[serde_as] -#[derive(Debug, Deserialize)] -pub struct WsRecv { - pub code: i32, - #[serde_as(as = "NoneAsEmptyString")] - pub message: Option, - #[serde(default)] - pub req_id: ReqId, - #[serde(flatten)] - pub data: WsRecvData, +#[allow(dead_code)] +#[derive(Clone, Debug, Deserialize)] +pub struct Stmt2Field { + pub name: String, + pub field_type: i8, + pub precision: u8, + pub scale: u8, + pub bytes: i32, + pub bind_type: BindType, } -impl WsRecv { - pub(crate) fn ok(self) -> (ReqId, WsRecvData, Result<(), RawError>) { - ( - self.req_id, - self.data, - if self.code == 0 { - Ok(()) - } else if self.message.as_deref() == Some("success") { - Err(RawError::from_code(self.code)) - } else { - Err(RawError::new(self.code, self.message.unwrap_or_default())) - }, - ) - } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum BindType { + Column, + Tag, + TableName, } -#[test] -fn test_serde_recv_data() { - let json = r#"{ - "code": 0, - "message": "", - "action": "conn", - "req_id": 1 - }"#; - let d: WsRecv = serde_json::from_str(json).unwrap(); - dbg!(d); +impl<'de> Deserialize<'de> for BindType { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct BindTypeVisitor; + + impl serde::de::Visitor<'_> for BindTypeVisitor { + type Value = BindType; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a valid number for BindType") + } + + fn visit_u8(self, v: u8) -> Result + where + E: serde::de::Error, + { + Ok(match v { + 1 => BindType::Column, + 2 => BindType::Tag, + 4 => BindType::TableName, + _ => return Err(E::custom(format!("Invalid bind type: {v}"))), + }) + } + + fn visit_i64(self, v: i64) -> Result + where + E: serde::de::Error, + { + self.visit_u8(v as _) + } + + fn visit_u64(self, v: u64) -> Result + where + E: serde::de::Error, + { + self.visit_u8(v as _) + } + } + + deserializer.deserialize_any(BindTypeVisitor) + } } pub trait ToMessage: Serialize { @@ -246,11 +354,72 @@ impl ToMessage for WsSend {} #[cfg(test)] mod tests { + use super::BindType; + use crate::query::infra::{WsRecv, WsSend}; + use crate::query::WsConnReq; + use crate::TaosBuilder; - use crate::*; + #[test] + fn test_serde_send() { + let s = WsSend::Conn { + req_id: 1, + req: WsConnReq::new("root", "taosdata"), + }; + let v = serde_json::to_value(s).unwrap(); + let j = serde_json::json!({ + "action": "conn", + "args": { + "req_id": 1, + "user": "root", + "password": "taosdata", + "db": "", + } + }); + assert_eq!(v, j); + } + + #[test] + fn test_serde_recv_data() { + let json = r#"{ + "code": 0, + "message": "", + "action": "conn", + "req_id": 1 + }"#; + let d: WsRecv = serde_json::from_str(json).unwrap(); + dbg!(d); + } #[test] fn dsn_error() { let _ = TaosBuilder::from_dsn("").unwrap_err(); } + + #[test] + fn test_bind_type_deserialize() { + let valid_cases = vec![ + (1, BindType::Column), + (2, BindType::Tag), + (4, BindType::TableName), + ]; + for (val, expected) in valid_cases { + let res: BindType = serde_json::from_value(serde_json::json!(val as i64)).unwrap(); + assert_eq!(res, expected); + + let res: BindType = serde_json::from_value(serde_json::json!(val as u64)).unwrap(); + assert_eq!(res, expected); + } + + let invalid_cases = vec![0, 3, 255]; + for val in invalid_cases { + let res: Result = serde_json::from_value(serde_json::json!(val as i64)); + assert!(res.is_err()); + + let res: Result = serde_json::from_value(serde_json::json!(val as u64)); + assert!(res.is_err()); + } + + let res: Result = serde_json::from_value(serde_json::json!("invalid")); + assert!(res.is_err()); + } } diff --git a/taos-ws/src/query/mod.rs b/taos-ws/src/query/mod.rs index 6b0cdaa4..c99d745a 100644 --- a/taos-ws/src/query/mod.rs +++ b/taos-ws/src/query/mod.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use taos_query::common::{RawMeta, SmlData}; use taos_query::prelude::RawResult; use taos_query::AsyncQueryable; @@ -15,7 +17,7 @@ use crate::TaosBuilder; #[derive(Debug)] pub struct Taos { pub(crate) dsn: TaosBuilder, - pub(crate) async_client: WsTaos, + pub(crate) async_client: Arc, } impl Taos { @@ -26,7 +28,7 @@ impl Taos { Ok(client) => { return Ok(Self { dsn, - async_client: client, + async_client: Arc::new(client), }) } Err(err) => { @@ -39,16 +41,17 @@ impl Taos { } } } + pub fn version(&self) -> &str { - self.client().version() + self.async_client.version() } pub fn get_req_id(&self) -> u64 { - self.client().get_req_id() + self.async_client.get_req_id() } - fn client(&self) -> &WsTaos { - &self.async_client + pub(crate) fn client(&self) -> Arc { + self.async_client.clone() } } diff --git a/taos-ws/src/stmt2/bind.rs b/taos-ws/src/stmt2/bind.rs new file mode 100644 index 00000000..022c900d --- /dev/null +++ b/taos-ws/src/stmt2/bind.rs @@ -0,0 +1,3241 @@ +use byteorder::{ByteOrder, LittleEndian}; +use taos_query::common::{BorrowedValue, ColumnView, Value}; +use taos_query::stmt2::Stmt2BindParam; +use taos_query::RawResult; + +use crate::query::infra::{BindType, ReqId, Stmt2Field, StmtId}; + +const REQ_ID_POS: usize = 0; +const STMT_ID_POS: usize = REQ_ID_POS + 8; +const ACTION_POS: usize = STMT_ID_POS + 8; +const VERSION_POS: usize = ACTION_POS + 8; +const COL_IDX_POS: usize = VERSION_POS + 2; +const FIXED_HEADER_LEN: usize = COL_IDX_POS + 4; + +const TOTAL_LENGTH_POS: usize = 0; +const TABLE_COUNT_POS: usize = TOTAL_LENGTH_POS + 4; +const TAG_COUNT_POS: usize = TABLE_COUNT_POS + 4; +const COL_COUNT_POS: usize = TAG_COUNT_POS + 4; +const TABLE_NAMES_OFFSET_POS: usize = COL_COUNT_POS + 4; +const TAGS_OFFSET_POS: usize = TABLE_NAMES_OFFSET_POS + 4; +const COLS_OFFSET_POS: usize = TAGS_OFFSET_POS + 4; +const DATA_POS: usize = COLS_OFFSET_POS + 4; + +// `TC` (Tag and Column) is an abbreviation used to represent tag and column. +const TC_DATA_TOTAL_LENGTH_POS: usize = 0; +const TC_DATA_TYPE_POS: usize = TC_DATA_TOTAL_LENGTH_POS + 4; +const TC_DATA_NUM_POS: usize = TC_DATA_TYPE_POS + 4; +const TC_DATA_IS_NULL_POS: usize = TC_DATA_NUM_POS + 4; + +const ACTION: u64 = 9; +const VERSION: u16 = 1; +const COL_IDX: i32 = -1; + +pub(super) fn bind_params_to_bytes( + params: &[Stmt2BindParam], + req_id: ReqId, + stmt_id: StmtId, + is_insert: bool, + fields: Option<&Vec>, + fields_count: usize, +) -> RawResult> { + if params.is_empty() { + return Err("No params to bind".into()); + } + + let mut need_tbnames = false; + let mut need_tags = false; + let mut need_cols = false; + + let table_cnt = params.len(); + let mut tag_cnt = 0; + let mut col_cnt = 0; + + if is_insert { + if fields.is_none() || fields.unwrap().is_empty() { + return Err("fields is empty".into()); + } + + for field in fields.unwrap() { + match field.bind_type { + BindType::TableName => { + need_tbnames = true; + } + BindType::Tag => { + need_tags = true; + tag_cnt += 1; + } + BindType::Column => { + need_cols = true; + col_cnt += 1; + } + } + } + } else { + need_cols = true; + col_cnt = fields_count; + } + + tracing::trace!("need_tbnames: {need_tbnames}, need_tags: {need_tags}, need_cols: {need_cols}"); + tracing::trace!("table_cnt: {table_cnt}, tag_cnt: {tag_cnt}, col_cnt: {col_cnt}"); + + let mut tbname_lens = vec![]; + let mut tbname_buf_len = 0; + if need_tbnames { + tbname_lens = get_tbname_lens(params)?; + tbname_buf_len = tbname_lens.iter().map(|&x| x as usize).sum(); + } + + let mut tag_lens = vec![]; + let mut tag_buf_len = 0; + if need_tags { + tag_lens = get_tag_lens(params, tag_cnt)?; + tag_buf_len = tag_lens.iter().map(|&x| x as usize).sum(); + } + + let mut col_lens = vec![]; + let mut col_buf_len = 0; + if need_cols { + col_lens = get_col_lens(params, col_cnt)?; + col_buf_len = col_lens.iter().map(|&x| x as usize).sum(); + } + + let tbname_total_len = tbname_lens.len() * 2 + tbname_buf_len; + let tag_total_len = tag_lens.len() * 4 + tag_buf_len; + let col_total_len = col_lens.len() * 4 + col_buf_len; + let total_len = DATA_POS + tbname_total_len + tag_total_len + col_total_len; + + tracing::trace!("tbname_total_len: {tbname_total_len}, tbname_buf_len: {tbname_buf_len}"); + tracing::trace!("tag_total_len: {tag_total_len}, tag_buf_len: {tag_buf_len}"); + tracing::trace!("col_total_len: {col_total_len}, col_buf_len: {col_buf_len}"); + tracing::trace!("total_len: {total_len}"); + + let mut data = vec![0u8; FIXED_HEADER_LEN + total_len]; + write_fixed_headers(&mut data, req_id, stmt_id); + + let bytes = &mut data[FIXED_HEADER_LEN..]; + LittleEndian::write_u32(&mut bytes[TOTAL_LENGTH_POS..], total_len as _); + LittleEndian::write_i32(&mut bytes[TABLE_COUNT_POS..], table_cnt as _); + LittleEndian::write_i32(&mut bytes[TAG_COUNT_POS..], tag_cnt as _); + LittleEndian::write_i32(&mut bytes[COL_COUNT_POS..], col_cnt as _); + + if need_tbnames { + LittleEndian::write_u32(&mut bytes[TABLE_NAMES_OFFSET_POS..], DATA_POS as _); + write_tbnames(&mut bytes[DATA_POS..], params, &tbname_lens); + } + + if need_tags { + let tags_offset = DATA_POS + tbname_total_len; + LittleEndian::write_u32(&mut bytes[TAGS_OFFSET_POS..], tags_offset as _); + write_tags(&mut bytes[tags_offset..], params, &tag_lens); + } + + if need_cols { + let cols_offset = DATA_POS + tbname_total_len + tag_total_len; + LittleEndian::write_u32(&mut bytes[COLS_OFFSET_POS..], cols_offset as _); + write_cols(&mut bytes[cols_offset..], params, &col_lens); + } + + Ok(data) +} + +fn get_tbname_lens(params: &[Stmt2BindParam]) -> RawResult> { + let mut tbname_lens = vec![0u16; params.len()]; + for (i, param) in params.iter().enumerate() { + if param.table_name().map_or(true, |s| s.is_empty()) { + return Err("table name is empty".into()); + } + let tbname = param.table_name().unwrap(); + // Add 1 because the table name ends with '\0' + tbname_lens[i] = (tbname.len() + 1) as _; + } + Ok(tbname_lens) +} + +fn get_tag_lens(params: &[Stmt2BindParam], tag_cnt: usize) -> RawResult> { + let mut tag_lens = vec![0u32; params.len()]; + for (i, param) in params.iter().enumerate() { + if param.tags().is_none() { + return Err("tags is empty".into()); + } + + let tags = param.tags().unwrap(); + if tags.len() != tag_cnt { + return Err("tags len mismatch".into()); + } + + let mut len = 0; + for tag in tags { + let have_len = tag.ty().fixed_length() == 0; + len += get_tc_header_len(1, have_len); + len += get_tag_data_len(tag); + } + tag_lens[i] = len as _; + } + Ok(tag_lens) +} + +fn get_col_lens(params: &[Stmt2BindParam], col_cnt: usize) -> RawResult> { + let mut col_lens = vec![0u32; params.len()]; + for (i, param) in params.iter().enumerate() { + if param.columns().is_none() { + return Err("columns is empty".into()); + } + + let cols = param.columns().unwrap(); + if cols.len() != col_cnt { + return Err("columns len mismatch".into()); + } + + let mut len = 0; + for col in cols { + let have_len = col.as_ty().fixed_length() == 0; + len += get_tc_header_len(col.len(), have_len); + len += get_col_data_len(col); + } + col_lens[i] = len as _; + } + Ok(col_lens) +} + +fn get_tc_header_len(num: usize, have_len: bool) -> usize { + // TotalLength(4) + Type(4) + Num(4) + HaveLength(1) + BufferLength(4) + IsNull(num) + let mut len = 17 + num; + if have_len { + // Length(num * 4) + len += num * 4; + } + len +} + +fn get_tag_data_len(tag: &Value) -> usize { + use Value::*; + match tag { + Null(_) => 0, + VarChar(v) | NChar(v) => v.len(), + Blob(v) | MediumBlob(v) => v.len(), + VarBinary(v) | Geometry(v) => v.len(), + Json(v) => serde_json::to_vec(v).unwrap().len(), + _ => tag.ty().fixed_length(), + } +} + +fn get_col_data_len(col: &ColumnView) -> usize { + if check_col_is_null(col) { + return 0; + } + + let mut len = 0; + + macro_rules! view_iter { + ($view:ident) => { + for val in $view.iter() { + if let Some(v) = val { + len += v.len(); + } + } + }; + } + + use ColumnView::*; + match col { + VarChar(view) => view_iter!(view), + NChar(view) => view_iter!(view), + VarBinary(view) => view_iter!(view), + Geometry(view) => view_iter!(view), + Json(_) => panic!("column does not support json type"), + _ => len = col.as_ty().fixed_length() * col.len(), + } + + len +} + +fn check_col_is_null(col: &ColumnView) -> bool { + for val in col.iter() { + if !val.is_null() { + return false; + } + } + true +} + +fn write_fixed_headers(bytes: &mut [u8], req_id: ReqId, stmt_id: StmtId) { + LittleEndian::write_u64(&mut bytes[REQ_ID_POS..], req_id); + LittleEndian::write_u64(&mut bytes[STMT_ID_POS..], stmt_id); + LittleEndian::write_u64(&mut bytes[ACTION_POS..], ACTION); + LittleEndian::write_u16(&mut bytes[VERSION_POS..], VERSION); + LittleEndian::write_i32(&mut bytes[COL_IDX_POS..], COL_IDX); +} + +fn write_tbnames(bytes: &mut [u8], params: &[Stmt2BindParam], tbname_lens: &[u16]) { + // Write TableNameLength + let mut offset = 0; + for len in tbname_lens { + LittleEndian::write_u16(&mut bytes[offset..], *len); + offset += 2; + } + + // Write TableNameBuffer + for param in params { + let tbname = param.table_name().unwrap(); + let len = tbname.len(); + bytes[offset..offset + len].copy_from_slice(tbname.as_bytes()); + // Add 1 because the table name end with '\0' + offset += len + 1; + } +} + +fn write_tags(bytes: &mut [u8], params: &[Stmt2BindParam], tag_lens: &[u32]) { + // Write TagsDataLength + let mut offset = 0; + for len in tag_lens { + LittleEndian::write_u32(&mut bytes[offset..], *len); + offset += 4; + } + + // Write TagsBuffer + for param in params { + for tag in param.tags().unwrap() { + offset += write_tag(&mut bytes[offset..], tag); + } + } +} + +fn write_tag(bytes: &mut [u8], tag: &Value) -> usize { + let ty = tag.ty(); + let is_null = tag.is_null(); + let have_len = ty.fixed_length() == 0; + let header_len = get_tc_header_len(1, have_len); + + let offset = header_len; + let mut buf_len = 0; + if !is_null { + buf_len = ty.fixed_length(); + let val = tag.to_borrowed_value(); + + // Write Buffer + use BorrowedValue::*; + match val { + Bool(v) => bytes[offset] = v as _, + TinyInt(v) => bytes[offset] = v as _, + UTinyInt(v) => bytes[offset] = v, + SmallInt(v) => LittleEndian::write_i16(&mut bytes[offset..], v as _), + USmallInt(v) => LittleEndian::write_u16(&mut bytes[offset..], v), + Int(v) => LittleEndian::write_i32(&mut bytes[offset..], v as _), + UInt(v) => LittleEndian::write_u32(&mut bytes[offset..], v), + BigInt(v) => LittleEndian::write_i64(&mut bytes[offset..], v as _), + UBigInt(v) => LittleEndian::write_u64(&mut bytes[offset..], v), + Float(v) => LittleEndian::write_f32(&mut bytes[offset..], v), + Double(v) => LittleEndian::write_f64(&mut bytes[offset..], v), + Timestamp(v) => LittleEndian::write_i64(&mut bytes[offset..], v.as_raw_i64() as _), + VarChar(v) => { + buf_len = v.len(); + bytes[offset..offset + v.len()].copy_from_slice(v.as_bytes()); + } + NChar(v) => { + buf_len = v.len(); + bytes[offset..offset + v.len()].copy_from_slice(v.as_bytes()); + } + Json(v) | VarBinary(v) | Geometry(v) => { + buf_len = v.len(); + bytes[offset..offset + v.len()].copy_from_slice(&v); + } + _ => unreachable!("Unsupported Type"), + }; + + // Write BufferLength + LittleEndian::write_u32(&mut bytes[header_len - 4..], buf_len as _); + } + + let total_len = header_len + buf_len; + + LittleEndian::write_u32(&mut bytes[TC_DATA_TOTAL_LENGTH_POS..], total_len as _); + LittleEndian::write_u32(&mut bytes[TC_DATA_TYPE_POS..], ty as _); + LittleEndian::write_u32(&mut bytes[TC_DATA_NUM_POS..], 1); + + if is_null { + bytes[TC_DATA_IS_NULL_POS] = 1; + } + + if have_len { + // Write HaveLength + bytes[TC_DATA_IS_NULL_POS + 1] = 1; + // Write Length + LittleEndian::write_u32(&mut bytes[TC_DATA_IS_NULL_POS + 2..], buf_len as _); + } + + total_len +} + +fn write_cols(bytes: &mut [u8], params: &[Stmt2BindParam], col_lens: &[u32]) { + // Write ColDataLength + let mut offset = 0; + for len in col_lens { + LittleEndian::write_u32(&mut bytes[offset..], *len); + offset += 4; + } + + // Write ColBuffer + for param in params { + let cols = param.columns().unwrap(); + for col in cols { + offset += write_col(&mut bytes[offset..], col); + } + } +} + +fn write_col(bytes: &mut [u8], col: &ColumnView) -> usize { + let num = col.len(); + let ty = col.as_ty(); + let is_null = check_col_is_null(col); + let have_len = ty.fixed_length() == 0; + let header_len = get_tc_header_len(num, have_len); + let mut buf_offset = header_len; + + if is_null { + // Write IsNull + for b in bytes.iter_mut().skip(TC_DATA_IS_NULL_POS).take(num) { + *b = 1; + } + } else { + // Write IsNull, Length and Buffer + let mut is_null_offset = TC_DATA_IS_NULL_POS; + // TC_DATA_IS_NULL_POS + IsNull(num) + HaveLength(1) + let mut len_offset = is_null_offset + num + 1; + + macro_rules! fixed_view_iter { + ($view:ident, $ty:ty) => { + for val in $view.iter() { + if val.is_none() { + bytes[is_null_offset] = 1; + } + let size = std::mem::size_of::<$ty>(); + bytes[buf_offset..buf_offset + size] + .copy_from_slice(&val.unwrap_or_default().to_le_bytes()); + buf_offset += size; + is_null_offset += 1; + } + }; + } + + macro_rules! variable_view_iter { + ($view:ident) => { + for val in $view.iter() { + let mut len = 0; + match val { + Some(v) => { + len = v.len(); + bytes[buf_offset..buf_offset + len].copy_from_slice(v.as_bytes()); + buf_offset += len; + } + None => bytes[is_null_offset] = 1, + } + LittleEndian::write_i32(&mut bytes[len_offset..], len as _); + is_null_offset += 1; + len_offset += 4; + } + }; + } + + use ColumnView::*; + match col { + Bool(view) => { + for val in view.iter() { + match val { + Some(b) => bytes[buf_offset] = b as _, + None => bytes[is_null_offset] = 1, + } + buf_offset += 1; + is_null_offset += 1; + } + } + Timestamp(view) => { + for val in view.iter() { + let ts = match val { + Some(ts) => ts.as_raw_i64(), + None => { + bytes[is_null_offset] = 1; + 0 + } + }; + LittleEndian::write_i64(&mut bytes[buf_offset..], ts); + buf_offset += 8; + is_null_offset += 1; + } + } + TinyInt(view) => fixed_view_iter!(view, i8), + UTinyInt(view) => fixed_view_iter!(view, u8), + SmallInt(view) => fixed_view_iter!(view, i16), + USmallInt(view) => fixed_view_iter!(view, u16), + Int(view) => fixed_view_iter!(view, i32), + UInt(view) => fixed_view_iter!(view, u32), + BigInt(view) => fixed_view_iter!(view, i64), + UBigInt(view) => fixed_view_iter!(view, u64), + Float(view) => fixed_view_iter!(view, f32), + Double(view) => fixed_view_iter!(view, f64), + VarChar(view) => variable_view_iter!(view), + NChar(view) => variable_view_iter!(view), + Json(view) => variable_view_iter!(view), + VarBinary(view) => variable_view_iter!(view), + Geometry(view) => variable_view_iter!(view), + } + } + + let total_len = buf_offset; + + LittleEndian::write_u32(&mut bytes[TC_DATA_TOTAL_LENGTH_POS..], total_len as _); + LittleEndian::write_u32(&mut bytes[TC_DATA_TYPE_POS..], ty as _); + LittleEndian::write_u32(&mut bytes[TC_DATA_NUM_POS..], num as _); + + if have_len { + // Write HaveLenght + bytes[TC_DATA_IS_NULL_POS + num] = 1; + } + + if !is_null { + // Write BufferLength + let buf_len = total_len - header_len; + LittleEndian::write_u32(&mut bytes[header_len - 4..], buf_len as _); + } + + total_len +} + +#[cfg(test)] +mod tests { + use bytes::Bytes; + use taos_query::common::{ColumnView, Timestamp, Ty, Value}; + + use super::{bind_params_to_bytes, Stmt2BindParam}; + use crate::query::infra::BindType; + use crate::stmt2::Stmt2Field; + + #[test] + fn test_bind_params_to_bytes_with_tbnames() -> anyhow::Result<()> { + let param1 = Stmt2BindParam::new(Some("test1".to_owned()), None, None); + let param2 = Stmt2BindParam::new(Some("test2".to_owned()), None, None); + let param3 = Stmt2BindParam::new(Some("test3".to_owned()), None, None); + let params = [param1, param2, param3]; + + let fields = vec![Stmt2Field { + name: "".to_string(), + field_type: 1, + precision: 0, + scale: 0, + bytes: 131584, + bind_type: BindType::TableName, + }]; + + let res = bind_params_to_bytes(¶ms, 100, 200, true, Some(&fields), 0)?; + + #[rustfmt::skip] + let expected = [ + // fixed headers + 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // req_id + 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // stmt_id + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // action + 0x01, 0x00, // version + 0xff, 0xff, 0xff, 0xff, // col_idx + + // data + 0x34, 0x00, 0x00, 0x00, // TotalLength + 0x03, 0x00, 0x00, 0x00, // TableCount + 0x00, 0x00, 0x00, 0x00, // TagCount + 0x00, 0x00, 0x00, 0x00, // ColCount + 0x1c, 0x00, 0x00, 0x00, // TableNamesOffset + 0x00, 0x00, 0x00, 0x00, // TagsOffset + 0x00, 0x00, 0x00, 0x00, // ColsOffset + + // table names + // TableNameLength + 0x06, 0x00, + 0x06, 0x00, + 0x06, 0x00, + // TableNameBuffer + 0x74, 0x65, 0x73, 0x74, 0x31, 0x00, + 0x74, 0x65, 0x73, 0x74, 0x32, 0x00, + 0x74, 0x65, 0x73, 0x74, 0x33, 0x00, + ]; + + assert_eq!(res, expected); + + Ok(()) + } + + #[test] + fn test_bind_params_to_bytes_with_tags() -> anyhow::Result<()> { + let tags = vec![ + Value::Timestamp(Timestamp::Milliseconds(1726803356466)), + Value::Bool(true), + Value::TinyInt(1), + Value::SmallInt(2), + Value::Int(3), + Value::BigInt(4), + Value::Float(5.5), + Value::Double(6.6), + Value::UTinyInt(7), + Value::USmallInt(8), + Value::UInt(9), + Value::UBigInt(10), + Value::VarChar("varchar".to_string()), + Value::NChar("nchar".to_string()), + Value::Json(serde_json::json!({"key": "value"})), + Value::VarBinary(Bytes::from("varbinary".as_bytes())), + Value::Geometry(Bytes::from(vec![ + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, + ])), + ]; + + let param = Stmt2BindParam::new(None, Some(tags), None); + let params = [param]; + + let fields = vec![ + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + ]; + + let res = bind_params_to_bytes(¶ms, 100, 200, true, Some(&fields), 0)?; + + #[rustfmt::skip] + let expected = [ + // fixed headers + 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // req_id + 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // stmt_id + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // action + 0x01, 0x00, // version + 0xff, 0xff, 0xff, 0xff, // col_idx + + // data + 0xd2, 0x01, 0x00, 0x00, // TotalLength + 0x01, 0x00, 0x00, 0x00, // TableCount + 0x11, 0x00, 0x00, 0x00, // TagCount + 0x00, 0x00, 0x00, 0x00, // ColCount + 0x00, 0x00, 0x00, 0x00, // TableNamesOffset + 0x1c, 0x00, 0x00, 0x00, // TagsOffset + 0x00, 0x00, 0x00, 0x00, // ColsOffset + + // tags + // TagsDataLength + 0xb2, 0x01, 0x00, 0x00, + // TagsBuffer + // table 0 tags + // tag 0 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x09, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x32, 0x2b, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, // Buffer + + // tag 1 + 0x13, 0x00, 0x00, 0x00, // TotalLength + 0x01, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x01, 0x00, 0x00, 0x00, // BufferLength + 0x01, // Buffer + + // tag 2 + 0x13, 0x00, 0x00, 0x00, // TotalLength + 0x02, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x01, 0x00, 0x00, 0x00, // BufferLength + 0x01, // Buffer + + // tag 3 + 0x14, 0x00, 0x00, 0x00, // TotalLength + 0x03, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x02, 0x00, 0x00, 0x00, // BufferLength + 0x02, 0x00, // Buffer + + // tag 4 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x04, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x04, 0x00, 0x00, 0x00, // BufferLength + 0x03, 0x00, 0x00, 0x00, // Buffer + + // tag 5 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x05, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Buffer + + // tag 6 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x06, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x04, 0x00, 0x00, 0x00, // BufferLength + 0x00, 0x00, 0xb0, 0x40, // Buffer + + // tag 7 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x07, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x1a, 0x40, // Buffer + + // tag 8 + 0x13, 0x00, 0x00, 0x00, // TotalLength + 0x0b, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x01, 0x00, 0x00, 0x00, // BufferLength + 0x07, // Buffer + + // tag 9 + 0x14, 0x00, 0x00, 0x00, // TotalLength + 0x0c, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x02, 0x00, 0x00, 0x00, // BufferLength + 0x08, 0x00, // Buffer + + // tag 10 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x0d, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x04, 0x00, 0x00, 0x00, // BufferLength + 0x09, 0x00, 0x00, 0x00, // Buffer + + // tag 11 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x0e, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Buffer + + // tag 12 + 0x1d, 0x00, 0x00, 0x00, // TotalLength + 0x08, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x07, 0x00, 0x00, 0x00, // Length + 0x07, 0x00, 0x00, 0x00, // BufferLength + 0x76, 0x61, 0x72, 0x63, 0x68, 0x61, 0x72, // Buffer + + // tag 13 + 0x1b, 0x00, 0x00, 0x00, // TotalLength + 0x0a, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x05, 0x00, 0x00, 0x00, // Length + 0x05, 0x00, 0x00, 0x00, // BufferLength + 0x6e, 0x63, 0x68, 0x61, 0x72, // Buffer + + // tag 14 + 0x25, 0x00, 0x00, 0x00, // TotalLength + 0x0f, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x0f, 0x00, 0x00, 0x00, // Length + 0x0f, 0x00, 0x00, 0x00, // BufferLength + 0x7b, 0x22, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x22, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x7d, // Buffer + + // tag 15 + 0x1f, 0x00, 0x00, 0x00, // TotalLength + 0x10, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x09, 0x00, 0x00, 0x00, // Length + 0x09, 0x00, 0x00, 0x00, // BufferLength + 0x76, 0x61, 0x72, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, // Buffer + + // tag 16 + 0x2b, 0x00, 0x00, 0x00, // TotalLength + 0x14, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x15, 0x00, 0x00, 0x00, // Length + 0x15, 0x00, 0x00, 0x00, // BufferLength + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, // Buffer + ]; + + assert_eq!(res, expected); + + Ok(()) + } + + #[test] + fn test_bind_params_to_bytes_with_null_tags() -> anyhow::Result<()> { + let tags = vec![ + Value::Null(Ty::Timestamp), + Value::Null(Ty::Bool), + Value::Null(Ty::TinyInt), + Value::Null(Ty::SmallInt), + Value::Null(Ty::Int), + Value::Null(Ty::BigInt), + Value::Null(Ty::Float), + Value::Null(Ty::Double), + Value::Null(Ty::UTinyInt), + Value::Null(Ty::USmallInt), + Value::Null(Ty::UInt), + Value::Null(Ty::UBigInt), + Value::Null(Ty::VarChar), + Value::Null(Ty::NChar), + Value::Null(Ty::Json), + Value::Null(Ty::VarBinary), + Value::Null(Ty::Geometry), + ]; + + let param = Stmt2BindParam::new(None, Some(tags), None); + let params = [param]; + + let fields = vec![ + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + ]; + + let res = bind_params_to_bytes(¶ms, 100, 200, true, Some(&fields), 0)?; + + #[rustfmt::skip] + let expected = [ + // fixed headers + 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // req_id + 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // stmt_id + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // action + 0x01, 0x00, // version + 0xff, 0xff, 0xff, 0xff, // col_idx + + // data + 0x66, 0x01, 0x00, 0x00, // TotalLength + 0x01, 0x00, 0x00, 0x00, // TableCount + 0x11, 0x00, 0x00, 0x00, // TagCount + 0x00, 0x00, 0x00, 0x00, // ColCount + 0x00, 0x00, 0x00, 0x00, // TableNamesOffset + 0x1c, 0x00, 0x00, 0x00, // TagsOffset + 0x00, 0x00, 0x00, 0x00, // ColsOffset + + // tags + // TagsDataLength + 0x46, 0x01, 0x00, 0x00, + // TagsBuffer + // table 0 tags + // tag 0 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x09, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 1 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x01, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 2 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x02, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 3 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x03, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 4 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x04, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 5 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x05, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 6 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x06, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 7 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x07, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 8 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x0b, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 9 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x0c, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 10 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x0d, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 11 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x0e, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 12 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x08, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x01, // HaveLength + 0x00, 0x00, 0x00, 0x00, // Length + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 13 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x0a, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x01, // HaveLength + 0x00, 0x00, 0x00, 0x00, // Length + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 14 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x0f, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x01, // HaveLength + 0x00, 0x00, 0x00, 0x00, // Length + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 15 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x10, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x01, // HaveLength + 0x00, 0x00, 0x00, 0x00, // Length + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 16 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x14, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x01, // HaveLength + 0x00, 0x00, 0x00, 0x00, // Length + 0x00, 0x00, 0x00, 0x00, // BufferLength + ]; + + assert_eq!(res, expected); + + Ok(()) + } + + #[test] + fn test_bind_params_to_bytes_with_tbnames_and_tags() -> anyhow::Result<()> { + let tags1 = vec![ + Value::Timestamp(Timestamp::Milliseconds(1726803356466)), + Value::Bool(true), + Value::TinyInt(1), + Value::SmallInt(2), + Value::Int(3), + Value::BigInt(4), + Value::Float(5.5), + Value::Double(6.6), + Value::UTinyInt(7), + Value::USmallInt(8), + Value::UInt(9), + Value::UBigInt(10), + Value::VarChar("varchar".to_string()), + Value::NChar("nchar".to_string()), + Value::Json(serde_json::json!({"key": "value"})), + Value::VarBinary(Bytes::from("varbinary".as_bytes())), + Value::Geometry(Bytes::from(vec![ + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, + ])), + ]; + + let param1 = Stmt2BindParam::new(Some("test1".to_owned()), Some(tags1), None); + + let tags2 = vec![ + Value::Null(Ty::Timestamp), + Value::Null(Ty::Bool), + Value::Null(Ty::TinyInt), + Value::Null(Ty::SmallInt), + Value::Null(Ty::Int), + Value::Null(Ty::BigInt), + Value::Null(Ty::Float), + Value::Null(Ty::Double), + Value::Null(Ty::UTinyInt), + Value::Null(Ty::USmallInt), + Value::Null(Ty::UInt), + Value::Null(Ty::UBigInt), + Value::Null(Ty::VarChar), + Value::Null(Ty::NChar), + Value::Null(Ty::Json), + Value::Null(Ty::VarBinary), + Value::Null(Ty::Geometry), + ]; + + let param2 = Stmt2BindParam::new(Some("testnil".to_owned()), Some(tags2), None); + + let tags3 = vec![ + Value::Timestamp(Timestamp::Milliseconds(1726803356466)), + Value::Bool(true), + Value::TinyInt(1), + Value::SmallInt(2), + Value::Int(3), + Value::BigInt(4), + Value::Float(5.5), + Value::Double(6.6), + Value::UTinyInt(7), + Value::USmallInt(8), + Value::UInt(9), + Value::UBigInt(10), + Value::VarChar("varchar".to_string()), + Value::NChar("nchar".to_string()), + Value::Json(serde_json::json!({"key": "value"})), + Value::VarBinary(Bytes::from("varbinary".as_bytes())), + Value::Geometry(Bytes::from(vec![ + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, + ])), + ]; + + let param3 = Stmt2BindParam::new(Some("test2".to_owned()), Some(tags3), None); + + let params = [param1, param2, param3]; + + let fields = vec![ + Stmt2Field { + name: "".to_string(), + field_type: 1, + precision: 0, + scale: 0, + bytes: 131584, + bind_type: BindType::TableName, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + ]; + + let res = bind_params_to_bytes(¶ms, 100, 200, true, Some(&fields), 0)?; + + #[rustfmt::skip] + let expected = [ + // fixed headers + 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // req_id + 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // stmt_id + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // action + 0x01, 0x00, // version + 0xff, 0xff, 0xff, 0xff, // col_idx + + // data + 0xec, 0x04, 0x00, 0x00, // TotalLength + 0x03, 0x00, 0x00, 0x00, // TableCount + 0x11, 0x00, 0x00, 0x00, // TagCount + 0x00, 0x00, 0x00, 0x00, // ColCount + 0x1c, 0x00, 0x00, 0x00, // TableNamesOffset + 0x36, 0x00, 0x00, 0x00, // TagsOffset + 0x00, 0x00, 0x00, 0x00, // ColsOffset + + // table names + // TableNameLength + 0x06, 0x00, + 0x08, 0x00, + 0x06, 0x00, + // TableNameBuffer + 0x74, 0x65, 0x73, 0x74, 0x31, 0x00, + 0x74, 0x65, 0x73, 0x74, 0x6e, 0x69, 0x6c, 0x00, + 0x74, 0x65, 0x73, 0x74, 0x32, 0x00, + + // tags + // TagsDataLength + 0xb2, 0x01, 0x00, 0x00, + 0x46, 0x01, 0x00, 0x00, + 0xb2, 0x01, 0x00, 0x00, + // TagsBuffer + // table 0 tags + // tag 0 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x09, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x32, 0x2b, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, // Buffer + + // tag 1 + 0x13, 0x00, 0x00, 0x00, // TotalLength + 0x01, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x01, 0x00, 0x00, 0x00, // BufferLength + 0x01, // Buffer + + // tag 2 + 0x13, 0x00, 0x00, 0x00, // TotalLength + 0x02, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x01, 0x00, 0x00, 0x00, // BufferLength + 0x01, // Buffer + + // tag 3 + 0x14, 0x00, 0x00, 0x00, // TotalLength + 0x03, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x02, 0x00, 0x00, 0x00, // BufferLength + 0x02, 0x00, // Buffer + + // tag 4 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x04, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x04, 0x00, 0x00, 0x00, // BufferLength + 0x03, 0x00, 0x00, 0x00, // Buffer + + // tag 5 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x05, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Buffer + + // tag 6 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x06, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x04, 0x00, 0x00, 0x00, // BufferLength + 0x00, 0x00, 0xb0, 0x40, // Buffer + + // tag 7 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x07, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x1a, 0x40, // Buffer + + // tag 8 + 0x13, 0x00, 0x00, 0x00, // TotalLength + 0x0b, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x01, 0x00, 0x00, 0x00, // BufferLength + 0x07, // Buffer + + // tag 9 + 0x14, 0x00, 0x00, 0x00, // TotalLength + 0x0c, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x02, 0x00, 0x00, 0x00, // BufferLength + 0x08, 0x00, // Buffer + + // tag 10 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x0d, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x04, 0x00, 0x00, 0x00, // BufferLength + 0x09, 0x00, 0x00, 0x00, // Buffer + + // tag 11 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x0e, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Buffer + + // tag 12 + 0x1d, 0x00, 0x00, 0x00, // TotalLength + 0x08, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x07, 0x00, 0x00, 0x00, // Length + 0x07, 0x00, 0x00, 0x00, // BufferLength + 0x76, 0x61, 0x72, 0x63, 0x68, 0x61, 0x72, // Buffer + + // tag 13 + 0x1b, 0x00, 0x00, 0x00, // TotalLength + 0x0a, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x05, 0x00, 0x00, 0x00, // Length + 0x05, 0x00, 0x00, 0x00, // BufferLength + 0x6e, 0x63, 0x68, 0x61, 0x72, // Buffer + + // tag 14 + 0x25, 0x00, 0x00, 0x00, // TotalLength + 0x0f, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x0f, 0x00, 0x00, 0x00, // Length + 0x0f, 0x00, 0x00, 0x00, // BufferLength + 0x7b, 0x22, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x22, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x7d, // Buffer + + // tag 15 + 0x1f, 0x00, 0x00, 0x00, // TotalLength + 0x10, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x09, 0x00, 0x00, 0x00, // Length + 0x09, 0x00, 0x00, 0x00, // BufferLength + 0x76, 0x61, 0x72, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, // Buffer + + // tag 16 + 0x2b, 0x00, 0x00, 0x00, // TotalLength + 0x14, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x15, 0x00, 0x00, 0x00, // Length + 0x15, 0x00, 0x00, 0x00, // BufferLength + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, // Buffer + + // table 1 tags + // tag 0 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x09, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 1 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x01, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 2 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x02, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 3 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x03, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 4 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x04, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 5 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x05, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 6 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x06, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 7 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x07, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 8 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x0b, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 9 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x0c, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 10 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x0d, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 11 + 0x12, 0x00, 0x00, 0x00, // TotalLength + 0x0e, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 12 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x08, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x01, // HaveLength + 0x00, 0x00, 0x00, 0x00, // Length + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 13 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x0a, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x01, // HaveLength + 0x00, 0x00, 0x00, 0x00, // Length + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 14 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x0f, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x01, // HaveLength + 0x00, 0x00, 0x00, 0x00, // Length + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 15 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x10, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x01, // HaveLength + 0x00, 0x00, 0x00, 0x00, // Length + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // tag 16 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x14, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x01, // IsNull + 0x01, // HaveLength + 0x00, 0x00, 0x00, 0x00, // Length + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // table 2 tags + // tag 0 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x09, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x32, 0x2b, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, // Buffer + + // tag 1 + 0x13, 0x00, 0x00, 0x00, // TotalLength + 0x01, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x01, 0x00, 0x00, 0x00, // BufferLength + 0x01, // Buffer + + // tag 2 + 0x13, 0x00, 0x00, 0x00, // TotalLength + 0x02, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x01, 0x00, 0x00, 0x00, // BufferLength + 0x01, // Buffer + + // tag 3 + 0x14, 0x00, 0x00, 0x00, // TotalLength + 0x03, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x02, 0x00, 0x00, 0x00, // BufferLength + 0x02, 0x00, // Buffer + + // tag 4 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x04, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x04, 0x00, 0x00, 0x00, // BufferLength + 0x03, 0x00, 0x00, 0x00, // Buffer + + // tag 5 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x05, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Buffer + + // tag 6 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x06, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x04, 0x00, 0x00, 0x00, // BufferLength + 0x00, 0x00, 0xb0, 0x40, // Buffer + + // tag 7 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x07, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x1a, 0x40, // Buffer + + // tag 8 + 0x13, 0x00, 0x00, 0x00, // TotalLength + 0x0b, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x01, 0x00, 0x00, 0x00, // BufferLength + 0x07, // Buffer + + // tag 9 + 0x14, 0x00, 0x00, 0x00, // TotalLength + 0x0c, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x02, 0x00, 0x00, 0x00, // BufferLength + 0x08, 0x00, // Buffer + + // tag 10 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x0d, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x04, 0x00, 0x00, 0x00, // BufferLength + 0x09, 0x00, 0x00, 0x00, // Buffer + + // tag 11 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x0e, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Buffer + + // tag 12 + 0x1d, 0x00, 0x00, 0x00, // TotalLength + 0x08, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x07, 0x00, 0x00, 0x00, // Length + 0x07, 0x00, 0x00, 0x00, // BufferLength + 0x76, 0x61, 0x72, 0x63, 0x68, 0x61, 0x72, // Buffer + + // tag 13 + 0x1b, 0x00, 0x00, 0x00, // TotalLength + 0x0a, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x05, 0x00, 0x00, 0x00, // Length + 0x05, 0x00, 0x00, 0x00, // BufferLength + 0x6e, 0x63, 0x68, 0x61, 0x72, // Buffer + + // tag 14 + 0x25, 0x00, 0x00, 0x00, // TotalLength + 0x0f, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x0f, 0x00, 0x00, 0x00, // Length + 0x0f, 0x00, 0x00, 0x00, // BufferLength + 0x7b, 0x22, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x22, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x7d, // Buffer + + // tag 15 + 0x1f, 0x00, 0x00, 0x00, // TotalLength + 0x10, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x09, 0x00, 0x00, 0x00, // Length + 0x09, 0x00, 0x00, 0x00, // BufferLength + 0x76, 0x61, 0x72, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, // Buffer + + // tag 16 + 0x2b, 0x00, 0x00, 0x00, // TotalLength + 0x14, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x15, 0x00, 0x00, 0x00, // Length + 0x15, 0x00, 0x00, 0x00, // BufferLength + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, // Buffer + ]; + + assert_eq!(res, expected); + + Ok(()) + } + + #[test] + fn test_bind_params_to_bytes_with_cols() -> anyhow::Result<()> { + let cols = vec![ + ColumnView::from_millis_timestamp(vec![1726803356466]), + ColumnView::from_bools(vec![true]), + ColumnView::from_tiny_ints(vec![1]), + ColumnView::from_small_ints(vec![2]), + ColumnView::from_ints(vec![3]), + ColumnView::from_big_ints(vec![4]), + ColumnView::from_floats(vec![5.5]), + ColumnView::from_doubles(vec![6.6]), + ColumnView::from_unsigned_tiny_ints(vec![7]), + ColumnView::from_unsigned_small_ints(vec![8]), + ColumnView::from_unsigned_ints(vec![9]), + ColumnView::from_unsigned_big_ints(vec![10]), + ColumnView::from_varchar(vec!["varchar"]), + ColumnView::from_nchar(vec!["nchar"]), + ColumnView::from_bytes(vec!["varbinary".as_bytes()]), + ColumnView::from_geobytes(vec![vec![ + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, + ]]), + ]; + + let fields_cnt = cols.len(); + let param = Stmt2BindParam::new(None, None, Some(cols)); + let res = bind_params_to_bytes(&[param], 100, 200, false, None, fields_cnt)?; + + #[rustfmt::skip] + let expected = [ + // fixed headers + 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // req_id + 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // stmt_id + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // action + 0x01, 0x00, // version + 0xff, 0xff, 0xff, 0xff, // col_idx + + // data + 0xad, 0x01, 0x00, 0x00, // TotalLength + 0x01, 0x00, 0x00, 0x00, // TableCount + 0x00, 0x00, 0x00, 0x00, // TagCount + 0x10, 0x00, 0x00, 0x00, // ColCount + 0x00, 0x00, 0x00, 0x00, // TableNamesOffset + 0x00, 0x00, 0x00, 0x00, // TagsOffset + 0x1c, 0x00, 0x00, 0x00, // ColsOffset + + // cols + // ColDataLength + 0x8d, 0x01, 0x00, 0x00, + // ColBuffer + // table 0 cols + // col 0 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x09, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x32, 0x2b, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, // Buffer + + // col 1 + 0x13, 0x00, 0x00, 0x00, // TotalLength + 0x01, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x01, 0x00, 0x00, 0x00, // BufferLength + 0x01, // Buffer + + // col 2 + 0x13, 0x00, 0x00, 0x00, // TotalLength + 0x02, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x01, 0x00, 0x00, 0x00, // BufferLength + 0x01, // Buffer + + // col 3 + 0x14, 0x00, 0x00, 0x00, // TotalLength + 0x03, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x02, 0x00, 0x00, 0x00, // BufferLength + 0x02, 0x00, // Buffer + + // col 4 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x04, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x04, 0x00, 0x00, 0x00, // BufferLength + 0x03, 0x00, 0x00, 0x00, // Buffer + + // col 5 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x05, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Buffer + + // col 6 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x06, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x04, 0x00, 0x00, 0x00, // BufferLength + 0x00, 0x00, 0xb0, 0x40, // Buffer + + // col 7 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x07, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x1a, 0x40, // Buffer + + // col 8 + 0x13, 0x00, 0x00, 0x00, // TotalLength + 0x0b, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x01, 0x00, 0x00, 0x00, // BufferLength + 0x07, // Buffer + + // col 9 + 0x14, 0x00, 0x00, 0x00, // TotalLength + 0x0c, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x02, 0x00, 0x00, 0x00, // BufferLength + 0x08, 0x00, // Buffer + + // col 10 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x0d, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x04, 0x00, 0x00, 0x00, // BufferLength + 0x09, 0x00, 0x00, 0x00, // Buffer + + // col 11 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x0e, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Buffer + + // col 12 + 0x1d, 0x00, 0x00, 0x00, // TotalLength + 0x08, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + // Length + 0x07, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, // BufferLength + 0x76, 0x61, 0x72, 0x63, 0x68, 0x61, 0x72, // Buffer + + // col 13 + 0x1b, 0x00, 0x00, 0x00, // TotalLength + 0x0a, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + // Length + 0x05, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, // BufferLength + 0x6e, 0x63, 0x68, 0x61, 0x72, // Buffer + + // col 14 + 0x1f, 0x00, 0x00, 0x00, // TotalLength + 0x10, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + // Length + 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, // BufferLength + 0x76, 0x61, 0x72, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, // Buffer + + // col 15 + 0x2b, 0x00, 0x00, 0x00, // TotalLength + 0x14, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + // Length + 0x15, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, // BufferLength + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, // Buffer + ]; + + assert_eq!(res, expected); + + Ok(()) + } + + #[test] + fn test_bind_params_to_bytes_with_null_cols() -> anyhow::Result<()> { + let cols = vec![ + ColumnView::null(5, Ty::Timestamp), + ColumnView::null(5, Ty::Bool), + ColumnView::null(5, Ty::TinyInt), + ColumnView::null(5, Ty::SmallInt), + ColumnView::null(5, Ty::Int), + ColumnView::null(5, Ty::BigInt), + ColumnView::null(5, Ty::Float), + ColumnView::null(5, Ty::Double), + ColumnView::null(5, Ty::UTinyInt), + ColumnView::null(5, Ty::USmallInt), + ColumnView::null(5, Ty::UInt), + ColumnView::null(5, Ty::UBigInt), + ColumnView::null(5, Ty::VarChar), + ColumnView::null(5, Ty::NChar), + ColumnView::from_bytes::<&[u8], _, _, _>(vec![None, None, None, None, None]), + ColumnView::from_geobytes::<&[u8], _, _, _>(vec![None, None, None, None, None]), + ]; + + let fields_cnt = cols.len(); + let param = Stmt2BindParam::new(None, None, Some(cols)); + let res = bind_params_to_bytes(&[param], 100, 200, false, None, fields_cnt)?; + + #[rustfmt::skip] + let expected = [ + // fixed headers + 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // req_id + 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // stmt_id + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // action + 0x01, 0x00, // version + 0xff, 0xff, 0xff, 0xff, // col_idx + + // data + 0xd0, 0x01, 0x00, 0x00, // TotalLength + 0x01, 0x00, 0x00, 0x00, // TableCount + 0x00, 0x00, 0x00, 0x00, // TagCount + 0x10, 0x00, 0x00, 0x00, // ColCount + 0x00, 0x00, 0x00, 0x00, // TableNamesOffset + 0x00, 0x00, 0x00, 0x00, // TagsOffset + 0x1c, 0x00, 0x00, 0x00, // ColsOffset + + // cols + // ColDataLength + 0xb0, 0x01, 0x00, 0x00, + // ColBuffer + // table 0 cols + // col 0 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x09, 0x00, 0x00, 0x00, // Type + 0x05, 0x00, 0x00, 0x00, // Num + 0x01, 0x01, 0x01, 0x01, 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // col 1 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x01, 0x00, 0x00, 0x00, // Type + 0x05, 0x00, 0x00, 0x00, // Num + 0x01, 0x01, 0x01, 0x01, 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // col 2 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x02, 0x00, 0x00, 0x00, // Type + 0x05, 0x00, 0x00, 0x00, // Num + 0x01, 0x01, 0x01, 0x01, 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // col 3 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x03, 0x00, 0x00, 0x00, // Type + 0x05, 0x00, 0x00, 0x00, // Num + 0x01, 0x01, 0x01, 0x01, 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // col 4 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x04, 0x00, 0x00, 0x00, // Type + 0x05, 0x00, 0x00, 0x00, // Num + 0x01, 0x01, 0x01, 0x01, 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // col 5 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x05, 0x00, 0x00, 0x00, // Type + 0x05, 0x00, 0x00, 0x00, // Num + 0x01, 0x01, 0x01, 0x01, 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // col 6 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x06, 0x00, 0x00, 0x00, // Type + 0x05, 0x00, 0x00, 0x00, // Num + 0x01, 0x01, 0x01, 0x01, 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // col 7 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x07, 0x00, 0x00, 0x00, // Type + 0x05, 0x00, 0x00, 0x00, // Num + 0x01, 0x01, 0x01, 0x01, 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // col 8 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x0b, 0x00, 0x00, 0x00, // Type + 0x05, 0x00, 0x00, 0x00, // Num + 0x01, 0x01, 0x01, 0x01, 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // col 9 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x0c, 0x00, 0x00, 0x00, // Type + 0x05, 0x00, 0x00, 0x00, // Num + 0x01, 0x01, 0x01, 0x01, 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // col 10 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x0d, 0x00, 0x00, 0x00, // Type + 0x05, 0x00, 0x00, 0x00, // Num + 0x01, 0x01, 0x01, 0x01, 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // col 11 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x0e, 0x00, 0x00, 0x00, // Type + 0x05, 0x00, 0x00, 0x00, // Num + 0x01, 0x01, 0x01, 0x01, 0x01, // IsNull + 0x00, // HaveLength + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // col 12 + 0x2a, 0x00, 0x00, 0x00, // TotalLength + 0x08, 0x00, 0x00, 0x00, // Type + 0x05, 0x00, 0x00, 0x00, // Num + 0x01, 0x01, 0x01, 0x01, 0x01, // IsNull + 0x01, // HaveLength + // Length + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // col 13 + 0x2a, 0x00, 0x00, 0x00, // TotalLength + 0x0a, 0x00, 0x00, 0x00, // Type + 0x05, 0x00, 0x00, 0x00, // Num + 0x01, 0x01, 0x01, 0x01, 0x01, // IsNull + 0x01, // HaveLength + // Length + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // col 14 + 0x2a, 0x00, 0x00, 0x00, // TotalLength + 0x10, 0x00, 0x00, 0x00, // Type + 0x05, 0x00, 0x00, 0x00, // Num + 0x01, 0x01, 0x01, 0x01, 0x01, // IsNull + 0x01, // HaveLength + // Length + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, // BufferLength + + // col 15 + 0x2a, 0x00, 0x00, 0x00, // TotalLength + 0x14, 0x00, 0x00, 0x00, // Type + 0x05, 0x00, 0x00, 0x00, // Num + 0x01, 0x01, 0x01, 0x01, 0x01, // IsNull + 0x01, // HaveLength + // Length + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, // BufferLength + ]; + + assert_eq!(res, expected); + + Ok(()) + } + + #[test] + fn test_bind_params_to_bytes_with_tbnames_tags_and_cols() -> anyhow::Result<()> { + let tags = vec![ + Value::Timestamp(Timestamp::Milliseconds(1726803356466)), + Value::Bool(true), + Value::TinyInt(1), + Value::SmallInt(2), + Value::Int(3), + Value::BigInt(4), + Value::Float(5.5), + Value::Double(6.6), + Value::UTinyInt(7), + Value::USmallInt(8), + Value::UInt(9), + Value::UBigInt(10), + Value::VarChar("varchar".to_string()), + Value::NChar("nchar".to_string()), + Value::Json(serde_json::json!({"key": "value"})), + Value::VarBinary(Bytes::from("varbinary".as_bytes())), + Value::Geometry(Bytes::from(vec![ + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, + ])), + ]; + + let cols = vec![ + ColumnView::from_millis_timestamp(vec![Some(1726803356466), None, Some(1726803358466)]), + ColumnView::from_bools(vec![Some(true), None, Some(false)]), + ColumnView::from_tiny_ints(vec![Some(11), None, Some(12)]), + ColumnView::from_small_ints(vec![Some(11), None, Some(12)]), + ColumnView::from_ints(vec![Some(11), None, Some(12)]), + ColumnView::from_big_ints(vec![Some(11), None, Some(12)]), + ColumnView::from_floats(vec![Some(11.2), None, Some(12.2)]), + ColumnView::from_doubles(vec![Some(11.2), None, Some(12.2)]), + ColumnView::from_unsigned_tiny_ints(vec![Some(11), None, Some(12)]), + ColumnView::from_unsigned_small_ints(vec![Some(11), None, Some(12)]), + ColumnView::from_unsigned_ints(vec![Some(11), None, Some(12)]), + ColumnView::from_unsigned_big_ints(vec![Some(11), None, Some(12)]), + ColumnView::from_varchar::<&str, _, _, _>(vec![ + Some("varchar1"), + None, + Some("varchar2"), + ]), + ColumnView::from_nchar::<&str, _, _, _>(vec![Some("nchar1"), None, Some("nchar2")]), + ColumnView::from_bytes::<&[u8], _, _, _>(vec![ + Some("varbinary1".as_bytes()), + None, + Some("varbinary2".as_bytes()), + ]), + ColumnView::from_geobytes::, _, _, _>(vec![ + Some(vec![ + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, + ]), + None, + Some(vec![ + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, + ]), + ]), + ]; + + let param = Stmt2BindParam::new(Some("test1".to_owned()), Some(tags), Some(cols)); + + let fields = vec![ + Stmt2Field { + name: "".to_string(), + field_type: 1, + precision: 0, + scale: 0, + bytes: 131584, + bind_type: BindType::TableName, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "ts".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "a".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Column, + }, + Stmt2Field { + name: "a".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Column, + }, + Stmt2Field { + name: "a".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Column, + }, + Stmt2Field { + name: "a".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Column, + }, + Stmt2Field { + name: "a".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Column, + }, + Stmt2Field { + name: "a".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Column, + }, + Stmt2Field { + name: "a".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Column, + }, + Stmt2Field { + name: "a".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Column, + }, + Stmt2Field { + name: "a".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Column, + }, + Stmt2Field { + name: "a".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Column, + }, + Stmt2Field { + name: "a".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Column, + }, + Stmt2Field { + name: "a".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Column, + }, + Stmt2Field { + name: "a".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Column, + }, + Stmt2Field { + name: "a".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Column, + }, + Stmt2Field { + name: "a".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Column, + }, + Stmt2Field { + name: "a".to_string(), + field_type: 9, + precision: 0, + scale: 0, + bytes: 8, + bind_type: BindType::Column, + }, + ]; + + let res = bind_params_to_bytes(&[param], 100, 200, true, Some(&fields), 0)?; + + #[rustfmt::skip] + let expected = [ + // fixed headers + 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // req_id + 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // stmt_id + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // action + 0x01, 0x00, // version + 0xff, 0xff, 0xff, 0xff, // col_id + + // data + 0x41, 0x04, 0x00, 0x00, // TotalLength + 0x01, 0x00, 0x00, 0x00, // TableCount + 0x11, 0x00, 0x00, 0x00, // TagCount + 0x10, 0x00, 0x00, 0x00, // ColCount + 0x1c, 0x00, 0x00, 0x00, // TableNamesOffset + 0x24, 0x00, 0x00, 0x00, // TagsOffset + 0xda, 0x01, 0x00, 0x00, // ColsOffset + + // table names + // TableNameLength + 0x06, 0x00, + // TableNameBuffer + 0x74, 0x65, 0x73, 0x74, 0x31, 0x00, + + // tags + // TagsDataLength + 0xb2, 0x01, 0x00, 0x00, + // TagsBuffer + // table 0 tags + // tag 0 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x09, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x32, 0x2b, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, // Buffer + + // tag 1 + 0x13, 0x00, 0x00, 0x00, // TotalLength + 0x01, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x01, 0x00, 0x00, 0x00, // BufferLength + 0x01, // Buffer + + // tag 2 + 0x13, 0x00, 0x00, 0x00, // TotalLength + 0x02, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x01, 0x00, 0x00, 0x00, // BufferLength + 0x01, // Buffer + + // tag 3 + 0x14, 0x00, 0x00, 0x00, // TotalLength + 0x03, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x02, 0x00, 0x00, 0x00, // BufferLength + 0x02, 0x00, // Buffer + + // tag 4 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x04, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x04, 0x00, 0x00, 0x00, // BufferLength + 0x03, 0x00, 0x00, 0x00, // Buffer + + // tag 5 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x05, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Buffer + + // tag 6 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x06, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x04, 0x00, 0x00, 0x00, // BufferLength + 0x00, 0x00, 0xb0, 0x40, // Buffer + + // tag 7 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x07, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x1a, 0x40, // Buffer + + // tag 8 + 0x13, 0x00, 0x00, 0x00, // TotalLength + 0x0b, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x01, 0x00, 0x00, 0x00, // BufferLength + 0x07, // Buffer + + // tag 9 + 0x14, 0x00, 0x00, 0x00, // TotalLength + 0x0c, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x02, 0x00, 0x00, 0x00, // BufferLength + 0x08, 0x00, // Buffer + + // tag 10 + 0x16, 0x00, 0x00, 0x00, // TotalLength + 0x0d, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x04, 0x00, 0x00, 0x00, // BufferLength + 0x09, 0x00, 0x00, 0x00, // Buffer + + // tag 11 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x0e, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x00, // HaveLength + 0x08, 0x00, 0x00, 0x00, // BufferLength + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Buffer + + // tag 12 + 0x1d, 0x00, 0x00, 0x00, // TotalLength + 0x08, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x07, 0x00, 0x00, 0x00, // Length + 0x07, 0x00, 0x00, 0x00, // BufferLength + 0x76, 0x61, 0x72, 0x63, 0x68, 0x61, 0x72, // Buffer + + // tag 13 + 0x1b, 0x00, 0x00, 0x00, // TotalLength + 0x0a, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x05, 0x00, 0x00, 0x00, // Length + 0x05, 0x00, 0x00, 0x00, // BufferLength + 0x6e, 0x63, 0x68, 0x61, 0x72, // Buffer + + // tag 14 + 0x25, 0x00, 0x00, 0x00, // TotalLength + 0x0f, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x0f, 0x00, 0x00, 0x00, // Length + 0x0f, 0x00, 0x00, 0x00, // BufferLength + 0x7b, 0x22, 0x6b, 0x65, 0x79, 0x22, 0x3a, 0x22, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x7d, // Buffer + + // tag 15 + 0x1f, 0x00, 0x00, 0x00, // TotalLength + 0x10, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x09, 0x00, 0x00, 0x00, // Length + 0x09, 0x00, 0x00, 0x00, // BufferLength + 0x76, 0x61, 0x72, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, // Buffer + + // tag 16 + 0x2b, 0x00, 0x00, 0x00, // TotalLength + 0x14, 0x00, 0x00, 0x00, // Type + 0x01, 0x00, 0x00, 0x00, // Num + 0x00, // IsNull + 0x01, // HaveLength + 0x15, 0x00, 0x00, 0x00, // Length + 0x15, 0x00, 0x00, 0x00, // BufferLength + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, // Buffer + + + // cols + // ColDataLength + 0x63, 0x02, 0x00, 0x00, + // ColBuffer + // table 0 cols + // col 0 + 0x2c, 0x00, 0x00, 0x00, // TotalLength + 0x09, 0x00, 0x00, 0x00, // Type + 0x03, 0x00, 0x00, 0x00, // Num + 0x00, 0x01, 0x00, // IsNull + 0x00, // HaveLength + 0x18, 0x00, 0x00, 0x00, // BufferLength + 0x32, 0x2b, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x33, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, // Buffer + + // col 1 + 0x17, 0x00, 0x00, 0x00, // TotalLength + 0x01, 0x00, 0x00, 0x00, // Type + 0x03, 0x00, 0x00, 0x00, // Num + 0x00, 0x01, 0x00, // IsNull + 0x00, // HaveLength + 0x03, 0x00, 0x00, 0x00, // BufferLength + 0x01, 0x00, 0x00, // Buffer + + // col 2 + 0x17, 0x00, 0x00, 0x00, // TotalLength + 0x02, 0x00, 0x00, 0x00, // Type + 0x03, 0x00, 0x00, 0x00, // Num + 0x00, 0x01, 0x00, // IsNull + 0x00, // HaveLength + 0x03, 0x00, 0x00, 0x00, // BufferLength + 0x0b, 0x00, 0x0c, // Buffer + + // col 3 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x03, 0x00, 0x00, 0x00, // Type + 0x03, 0x00, 0x00, 0x00, // Num + 0x00, 0x01, 0x00, // IsNull + 0x00, // HaveLength + 0x06, 0x00, 0x00, 0x00, // BufferLength + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, // Buffer + + // col 4 + 0x20, 0x00, 0x00, 0x00, // TotalLength + 0x04, 0x00, 0x00, 0x00, // Type + 0x03, 0x00, 0x00, 0x00, // Num + 0x00, 0x01, 0x00, // IsNull + 0x00, // HaveLength + 0x0c, 0x00, 0x00, 0x00, // BufferLength + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, // Buffer + + // col 5 + 0x2c, 0x00, 0x00, 0x00, // TotalLength + 0x05, 0x00, 0x00, 0x00, // Type + 0x03, 0x00, 0x00, 0x00, // Num + 0x00, 0x01, 0x00, // IsNull + 0x00, // HaveLength + 0x18, 0x00, 0x00, 0x00, // BufferLength + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Buffer + + // col 6 + 0x20, 0x00, 0x00, 0x00, // TotalLength + 0x06, 0x00, 0x00, 0x00, // Type + 0x03, 0x00, 0x00, 0x00, // Num + 0x00, 0x01, 0x00, // IsNull + 0x00, // HaveLength + 0x0c, 0x00, 0x00, 0x00, // BufferLength + 0x33, 0x33, 0x33, 0x41, 0x00, 0x00, 0x00, 0x00, 0x33, 0x33, 0x43, 0x41, // Buffer + + // col 7 + 0x2c, 0x00, 0x00, 0x00, // TotalLength + 0x07, 0x00, 0x00, 0x00, // Type + 0x03, 0x00, 0x00, 0x00, // Num + 0x00, 0x01, 0x00, // IsNull + 0x00, // HaveLength + 0x18, 0x00, 0x00, 0x00, // BufferLength + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x26, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x28, 0x40, // Buffer + + // col 8 + 0x17, 0x00, 0x00, 0x00, // TotalLength + 0x0b, 0x00, 0x00, 0x00, // Type + 0x03, 0x00, 0x00, 0x00, // Num + 0x00, 0x01, 0x00, // IsNull + 0x00, // HaveLength + 0x03, 0x00, 0x00, 0x00, // BufferLength + 0x0b, 0x00, 0x0c, // Buffer + + // col 9 + 0x1a, 0x00, 0x00, 0x00, // TotalLength + 0x0c, 0x00, 0x00, 0x00, // Type + 0x03, 0x00, 0x00, 0x00, // Num + 0x00, 0x01, 0x00, // IsNull + 0x00, // HaveLength + 0x06, 0x00, 0x00, 0x00, // BufferLength + 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, // Buffer + + // col 10 + 0x20, 0x00, 0x00, 0x00, // TotalLength + 0x0d, 0x00, 0x00, 0x00, // Type + 0x03, 0x00, 0x00, 0x00, // Num + 0x00, 0x01, 0x00, // IsNull + 0x00, // HaveLength + 0x0c, 0x00, 0x00, 0x00, // BufferLength + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, // Buffer + + // col 11 + 0x2c, 0x00, 0x00, 0x00, // TotalLength + 0x0e, 0x00, 0x00, 0x00, // Type + 0x03, 0x00, 0x00, 0x00, // Num + 0x00, 0x01, 0x00, // IsNull + 0x00, // HaveLength + 0x18, 0x00, 0x00, 0x00, // BufferLength + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Buffer + + // col 12 + 0x30, 0x00, 0x00, 0x00, // TotalLength + 0x08, 0x00, 0x00, 0x00, // Type + 0x03, 0x00, 0x00, 0x00, // Num + 0x00, 0x01, 0x00, // IsNull + 0x01, // HaveLength + // Length + 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, // BufferLength + 0x76, 0x61, 0x72, 0x63, 0x68, 0x61, 0x72, 0x31, 0x76, 0x61, 0x72, 0x63, 0x68, 0x61, 0x72, 0x32, // Buffer + + // col 13 + 0x2c, 0x00, 0x00, 0x00, // TotalLength + 0x0a, 0x00, 0x00, 0x00, // Type + 0x03, 0x00, 0x00, 0x00, // Num + 0x00, 0x01, 0x00, // IsNull + 0x01, // HaveLength + // Length + 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, // BufferLength + 0x6e, 0x63, 0x68, 0x61, 0x72, 0x31, 0x6e, 0x63, 0x68, 0x61, 0x72, 0x32, // Buffer + + // col 14 + 0x34, 0x00, 0x00, 0x00, // TotalLength + 0x10, 0x00, 0x00, 0x00, // Type + 0x03, 0x00, 0x00, 0x00, // Num + 0x00, 0x01, 0x00, // IsNull + 0x01, // HaveLength + // Length + 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, // BufferLength + 0x76, 0x61, 0x72, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x31, 0x76, 0x61, 0x72, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x32, // Buffer + + // col 15 + 0x4a, 0x00, 0x00, 0x00, // TotalLength + 0x14, 0x00, 0x00, 0x00, // Type + 0x03, 0x00, 0x00, 0x00, // Num + 0x00, 0x01, 0x00, // IsNull + 0x01, // HaveLength + // Length + 0x15, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, // BufferLength + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, // Buffer + ]; + + assert_eq!(res, expected); + + Ok(()) + } + + #[test] + #[should_panic = "No params to bind"] + fn test_bind_params_to_bytes_without_params() { + let _ = bind_params_to_bytes(&[], 100, 200, true, None, 0).unwrap(); + } + + #[test] + fn test_bind_params_to_bytes_without_fields() { + let fields = vec![]; + let test_cases = vec![None, Some(&fields)]; + for fields in test_cases { + let param = Stmt2BindParam::new(None, None, None); + let res = bind_params_to_bytes(&[param], 100, 200, true, fields, 0); + assert!(res.is_err()); + } + } + + #[test] + fn test_bind_params_to_bytes_without_tbnames() { + let test_cases = vec![None, Some(String::new())]; + for tbname in test_cases { + let param = Stmt2BindParam::new(tbname, None, None); + + let fields = vec![Stmt2Field { + name: "".to_string(), + field_type: 1, + precision: 0, + scale: 0, + bytes: 131584, + bind_type: BindType::TableName, + }]; + + let res = bind_params_to_bytes(&[param], 100, 200, true, Some(&fields), 0); + assert!(res.is_err()); + } + } + + #[test] + #[should_panic = "tags is empty"] + fn test_bind_params_to_bytes_without_tags() { + let param = Stmt2BindParam::new(None, None, None); + + let fields = vec![Stmt2Field { + name: "".to_string(), + field_type: 1, + precision: 0, + scale: 0, + bytes: 131584, + bind_type: BindType::Tag, + }]; + + let _ = bind_params_to_bytes(&[param], 100, 200, true, Some(&fields), 0).unwrap(); + } + + #[test] + #[should_panic = "tags len mismatch"] + fn test_bind_params_to_bytes_tags_len_mismatch() { + let tags = vec![Value::Int(1)]; + let param = Stmt2BindParam::new(None, Some(tags), None); + + let fields = vec![ + Stmt2Field { + name: "".to_string(), + field_type: 1, + precision: 0, + scale: 0, + bytes: 131584, + bind_type: BindType::Tag, + }, + Stmt2Field { + name: "".to_string(), + field_type: 1, + precision: 0, + scale: 0, + bytes: 131584, + bind_type: BindType::Tag, + }, + ]; + + let _ = bind_params_to_bytes(&[param], 100, 200, true, Some(&fields), 0).unwrap(); + } + + #[test] + #[should_panic = "columns is empty"] + fn test_bind_params_to_bytes_without_cols() { + let param = Stmt2BindParam::new(None, None, None); + + let fields = vec![Stmt2Field { + name: "".to_string(), + field_type: 1, + precision: 0, + scale: 0, + bytes: 131584, + bind_type: BindType::Column, + }]; + + let _ = bind_params_to_bytes(&[param], 100, 200, true, Some(&fields), 0).unwrap(); + } + + #[test] + #[should_panic = "columns len mismatch"] + fn test_bind_params_to_bytes_cols_len_mismatch() { + let cols = vec![ColumnView::from_ints(vec![1])]; + let param = Stmt2BindParam::new(None, None, Some(cols)); + + let fields = vec![ + Stmt2Field { + name: "".to_string(), + field_type: 1, + precision: 0, + scale: 0, + bytes: 131584, + bind_type: BindType::Column, + }, + Stmt2Field { + name: "".to_string(), + field_type: 1, + precision: 0, + scale: 0, + bytes: 131584, + bind_type: BindType::Column, + }, + ]; + + let _ = bind_params_to_bytes(&[param], 100, 200, true, Some(&fields), 0).unwrap(); + } + + #[test] + #[should_panic = "column does not support json type"] + fn test_bind_params_to_bypes_with_json_col() { + let cols = vec![ColumnView::from_json(vec!["{\"key\":\"value\"}"])]; + let param = Stmt2BindParam::new(None, None, Some(cols)); + + let fields = vec![Stmt2Field { + name: "".to_string(), + field_type: 1, + precision: 0, + scale: 0, + bytes: 131584, + bind_type: BindType::Column, + }]; + + let _ = bind_params_to_bytes(&[param], 100, 200, true, Some(&fields), 0).unwrap(); + } +} diff --git a/taos-ws/src/stmt2/mod.rs b/taos-ws/src/stmt2/mod.rs new file mode 100644 index 00000000..884f2ea0 --- /dev/null +++ b/taos-ws/src/stmt2/mod.rs @@ -0,0 +1,775 @@ +use std::fmt::Debug; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use futures::channel::oneshot; +use taos_query::common::{Field, Precision}; +use taos_query::prelude::RawResult; +use taos_query::stmt2::{Stmt2AsyncBindable, Stmt2BindParam, Stmt2Bindable}; +use taos_query::util::generate_req_id; +use taos_query::{block_in_place_or_global, AsyncQueryable, Queryable}; +use tracing::Instrument; + +use crate::query::asyn::QueryMetrics; +use crate::query::infra::{Stmt2Field, StmtId, WsRecvData, WsResArgs, WsSend}; +use crate::query::WsTaos; +use crate::{ResultSet, Taos}; + +mod bind; + +#[derive(Debug)] +pub struct Stmt2 { + client: Arc, + stmt_id: Option, + is_insert: Option, + fields: Option>, + fields_count: Option, + affected_rows: usize, + affected_rows_once: usize, +} + +impl Stmt2 { + fn new(client: Arc) -> Self { + Self { + client, + stmt_id: None, + is_insert: None, + fields: None, + fields_count: None, + affected_rows: 0, + affected_rows_once: 0, + } + } + + async fn init(&mut self) -> RawResult<()> { + let req = WsSend::Stmt2Init { + req_id: generate_req_id(), + single_stb_insert: true, + single_table_bind_once: false, + }; + let resp = self.client.send_request(req).await?; + if let WsRecvData::Stmt2Init { stmt_id, .. } = resp { + self.stmt_id = Some(stmt_id); + return Ok(()); + } + unreachable!() + } + + async fn prepare + Send>(&mut self, sql: S) -> RawResult<()> { + let req = WsSend::Stmt2Prepare { + req_id: generate_req_id(), + stmt_id: self.stmt_id.unwrap(), + sql: sql.as_ref().to_string(), + get_fields: true, + }; + let resp = self.client.send_request(req).await?; + if let WsRecvData::Stmt2Prepare { + is_insert, + fields, + fields_count, + .. + } = resp + { + self.is_insert = Some(is_insert); + self.fields = fields; + self.fields_count = Some(fields_count); + return Ok(()); + } + unreachable!() + } + + async fn bind(&self, params: &[Stmt2BindParam]) -> RawResult<()> { + let bytes = bind::bind_params_to_bytes( + params, + generate_req_id(), + self.stmt_id.unwrap(), + self.is_insert.unwrap(), + self.fields.as_ref(), + self.fields_count.unwrap(), + )?; + let req = WsSend::Binary(bytes); + let resp = self.client.send_request(req).await?; + if let WsRecvData::Stmt2Bind { .. } = resp { + return Ok(()); + } + unreachable!() + } + + async fn exec(&mut self) -> RawResult { + let req = WsSend::Stmt2Exec { + req_id: generate_req_id(), + stmt_id: self.stmt_id.unwrap(), + }; + let resp = self.client.send_request(req).await?; + if let WsRecvData::Stmt2Exec { affected, .. } = resp { + self.affected_rows += affected; + self.affected_rows_once = affected; + return Ok(affected); + } + unreachable!() + } + + fn close(&self) { + let req = WsSend::Stmt2Close { + req_id: generate_req_id(), + stmt_id: self.stmt_id.unwrap(), + }; + let resp = block_in_place_or_global(self.client.send_request(req)); + match resp { + Ok(WsRecvData::Stmt2Close { .. }) => tracing::trace!("Stmt2 closed successfully"), + Err(err) => tracing::error!("Failed to close Stmt2: {err:?}"), + _ => unreachable!(), + } + } + + async fn result_set(&self) -> RawResult { + if self.is_insert.unwrap_or(false) { + return Err("Only query can use result".into()); + } + + let req_id = generate_req_id(); + let req = WsSend::Stmt2Result { + req_id, + stmt_id: self.stmt_id.unwrap(), + }; + + let resp = self.client.send_request(req).await?; + if let WsRecvData::Stmt2Result { + id, + precision, + fields_count, + fields_names, + fields_types, + fields_lengths, + timing, + .. + } = resp + { + let (close_tx, close_rx) = oneshot::channel(); + tokio::spawn( + async move { + let start = Instant::now(); + let _ = close_rx.await; + tracing::trace!("stmt2 result:{} lived {:?}", id, start.elapsed()); + } + .in_current_span(), + ); + + let fields: Vec = fields_names + .iter() + .zip(fields_types) + .zip(fields_lengths) + .map(|((name, ty), len)| Field::new(name, ty, len as _)) + .collect(); + + let (raw_block_tx, raw_block_rx) = flume::bounded(64); + + tokio::spawn( + crate::query::asyn::fetch( + self.client.sender(), + id, + raw_block_tx, + precision, + fields_names, + ) + .in_current_span(), + ); + + let timing = match precision { + Precision::Millisecond => Duration::from_millis(timing), + Precision::Microsecond => Duration::from_micros(timing), + Precision::Nanosecond => Duration::from_nanos(timing), + }; + + return Ok(ResultSet { + sender: self.client.sender(), + args: WsResArgs { req_id, id }, + fields: Some(fields), + fields_count: fields_count as _, + affected_rows: self.affected_rows_once, + precision, + summary: (0, 0), + timing, + block_future: None, + closer: Some(close_tx), + completed: false, + metrics: QueryMetrics::default(), + blocks_buffer: Some(raw_block_rx), + }); + } + + unreachable!() + } +} + +impl Drop for Stmt2 { + fn drop(&mut self) { + self.close(); + } +} + +impl Stmt2Bindable for Stmt2 { + fn init(taos: &super::Taos) -> RawResult { + let mut stmt2 = Self::new(taos.client()); + block_in_place_or_global(stmt2.init())?; + Ok(stmt2) + } + + fn prepare(&mut self, sql: &str) -> RawResult<&mut Self> { + block_in_place_or_global(self.prepare(sql))?; + Ok(self) + } + + fn bind(&mut self, params: &[Stmt2BindParam]) -> RawResult<&mut Self> { + block_in_place_or_global(Stmt2::bind(self, params))?; + Ok(self) + } + + fn exec(&mut self) -> RawResult { + block_in_place_or_global(self.exec()) + } + + fn affected_rows(&self) -> usize { + self.affected_rows + } + + fn result_set(&self) -> RawResult<::ResultSet> { + block_in_place_or_global(self.result_set()) + } +} + +#[async_trait::async_trait] +impl Stmt2AsyncBindable for Stmt2 { + async fn init(taos: &super::Taos) -> RawResult { + let mut stmt2 = Self::new(taos.client()); + stmt2.init().await?; + Ok(stmt2) + } + + async fn prepare(&mut self, sql: &str) -> RawResult<&mut Self> { + self.prepare(sql).await?; + Ok(self) + } + + async fn bind(&mut self, params: &[Stmt2BindParam]) -> RawResult<&mut Self> { + Stmt2::bind(self, params).await?; + Ok(self) + } + + async fn exec(&mut self) -> RawResult { + self.exec().await + } + + async fn affected_rows(&self) -> usize { + self.affected_rows + } + + async fn result_set(&self) -> RawResult<::AsyncResultSet> { + self.result_set().await + } +} + +#[cfg(test)] +mod tests { + use futures::TryStreamExt; + use serde::Deserialize; + use taos_query::common::{ColumnView, Ty, Value}; + use taos_query::stmt2::Stmt2BindParam; + use taos_query::{AsyncFetchable, AsyncQueryable, AsyncTBuilder}; + + use crate::stmt2::Stmt2; + use crate::TaosBuilder; + + #[tokio::test(flavor = "multi_thread")] + async fn test_stmt2_insert_single_row() -> anyhow::Result<()> { + let db = "stmt2_202411231503"; + let dsn = "ws://localhost:6041"; + + let taos = TaosBuilder::from_dsn(dsn)?.build().await?; + taos.exec_many([ + &format!("drop database if exists {db}"), + &format!("create database {db} keep 36500"), + &format!("use {db}"), + "create table t0 (ts timestamp, c1 bool, c2 tinyint, c3 smallint, c4 int, c5 bigint, + c6 tinyint unsigned, c7 smallint unsigned, c8 int unsigned, c9 bigint unsigned, + c10 float, c11 double, c12 varchar(100), c13 nchar(100))", + ]) + .await?; + + let mut stmt2 = Stmt2::new(taos.client()); + stmt2.init().await?; + stmt2 + .prepare("insert into t0 values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") + .await?; + + let cols = vec![ + ColumnView::from_millis_timestamp(vec![1726803356466]), + ColumnView::from_bools(vec![true]), + ColumnView::from_tiny_ints(vec![None]), + ColumnView::from_small_ints(vec![i16::MAX]), + ColumnView::from_ints(vec![i32::MAX]), + ColumnView::from_big_ints(vec![i64::MAX]), + ColumnView::from_unsigned_tiny_ints(vec![u8::MAX]), + ColumnView::from_unsigned_small_ints(vec![u16::MAX]), + ColumnView::from_unsigned_ints(vec![u32::MAX]), + ColumnView::from_unsigned_big_ints(vec![u64::MAX]), + ColumnView::from_floats(vec![f32::MAX]), + ColumnView::from_doubles(vec![f64::MAX]), + ColumnView::from_varchar(vec!["hello"]), + ColumnView::from_nchar(vec!["中文"]), + ]; + + let param = Stmt2BindParam::new(None, None, Some(cols)); + stmt2.bind(&[param]).await?; + + let affected = stmt2.exec().await?; + assert_eq!(affected, 1); + + #[derive(Debug, Deserialize)] + struct Row { + ts: i64, + c1: bool, + c2: Option, + c3: i16, + c4: i32, + c5: i64, + c6: u8, + c7: u16, + c8: u32, + c9: u64, + c10: Option, + c11: f64, + c12: String, + c13: String, + } + + let rows: Vec = taos + .query("select * from t0") + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(rows.len(), 1); + + let row = &rows[0]; + + assert_eq!(row.ts, 1726803356466); + assert_eq!(row.c1, true); + assert_eq!(row.c2, None); + assert_eq!(row.c3, i16::MAX); + assert_eq!(row.c4, i32::MAX); + assert_eq!(row.c5, i64::MAX); + assert_eq!(row.c6, u8::MAX); + assert_eq!(row.c7, u16::MAX); + assert_eq!(row.c8, u32::MAX); + assert_eq!(row.c9, u64::MAX); + assert_eq!(row.c10, Some(f32::MAX)); + assert_eq!(row.c11, f64::MAX); + assert_eq!(row.c12, "hello"); + assert_eq!(row.c13, "中文"); + + taos.exec(format!("drop database {db}")).await?; + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_stmt2_insert_multi_row() -> anyhow::Result<()> { + let db = "stmt2_202411231455"; + let dsn = "ws://localhost:6041"; + + let taos = TaosBuilder::from_dsn(dsn)?.build().await?; + taos.exec_many(vec![ + &format!("drop database if exists {db}"), + &format!("create database {db}"), + &format!("use {db}"), + "create table t0 (ts timestamp, c1 int)", + ]) + .await?; + + let mut stmt2 = Stmt2::new(taos.client()); + stmt2.init().await?; + stmt2.prepare("insert into t0 values(?, ?)").await?; + + let cols = vec![ + ColumnView::from_millis_timestamp(vec![ + 1726803356466, + 1726803357466, + 1726803358466, + 1726803359466, + ]), + ColumnView::from_ints(vec![99, 100, 101, 102]), + ]; + + let param = Stmt2BindParam::new(None, None, Some(cols)); + stmt2.bind(&[param]).await?; + + let affected = stmt2.exec().await?; + assert_eq!(affected, 4); + + #[derive(Debug, Deserialize)] + struct Row { + ts: i64, + c1: i32, + } + + let rows: Vec = taos + .query("select * from t0") + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(rows.len(), 4); + + assert_eq!(rows[0].ts, 1726803356466); + assert_eq!(rows[1].ts, 1726803357466); + assert_eq!(rows[2].ts, 1726803358466); + assert_eq!(rows[3].ts, 1726803359466); + + assert_eq!(rows[0].c1, 99); + assert_eq!(rows[1].c1, 100); + assert_eq!(rows[2].c1, 101); + assert_eq!(rows[3].c1, 102); + + taos.exec(format!("drop database {db}")).await?; + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_stmt2_query_single_row() -> anyhow::Result<()> { + let db = "stmt2_202411231501"; + let dsn = "ws://localhost:6041"; + + let taos = TaosBuilder::from_dsn(dsn)?.build().await?; + taos.exec_many(vec![ + &format!("drop database if exists {db}"), + &format!("create database {db}"), + &format!("use {db}"), + "create table t0 (ts timestamp, c1 bool, c2 tinyint, c3 smallint, c4 int, c5 bigint, + c6 tinyint unsigned, c7 smallint unsigned, c8 int unsigned, c9 bigint unsigned, + c10 float, c11 double, c12 varchar(100), c13 nchar(100))", + &format!( + "insert into t0 values(1726803356466, 1, NULL, 2, 3, 4, 5, 6, 7, 8, 1.1, 2.2, 'hello', '中文')" + ), + ]) + .await?; + + let mut stmt2 = Stmt2::new(taos.client()); + stmt2.init().await?; + stmt2 + .prepare("select * from t0 where c8 > ? and c10 > ? and c12 = ?") + .await?; + + let cols = vec![ + ColumnView::from_ints(vec![0]), + ColumnView::from_floats(vec![0f32]), + ColumnView::from_varchar(vec!["hello"]), + ]; + + let param = Stmt2BindParam::new(None, None, Some(cols)); + stmt2.bind(&[param]).await?; + + let affected = stmt2.exec().await?; + assert_eq!(affected, 0); + + #[derive(Debug, Deserialize)] + struct Row { + ts: i64, + c1: bool, + c2: Option, + c3: i16, + c4: i32, + c5: i64, + c6: u8, + c7: u16, + c8: u32, + c9: u64, + c10: Option, + c11: f64, + c12: String, + c13: String, + } + + let rows: Vec = stmt2 + .result_set() + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(rows.len(), 1); + + let row = &rows[0]; + + assert_eq!(row.ts, 1726803356466); + assert_eq!(row.c1, true); + assert_eq!(row.c2, None); + assert_eq!(row.c3, 2); + assert_eq!(row.c4, 3); + assert_eq!(row.c5, 4); + assert_eq!(row.c6, 5); + assert_eq!(row.c7, 6); + assert_eq!(row.c8, 7); + assert_eq!(row.c9, 8); + assert_eq!(row.c10, Some(1.1)); + assert_eq!(row.c11, 2.2); + assert_eq!(row.c12, "hello"); + assert_eq!(row.c13, "中文"); + + taos.exec(format!("drop database {db}")).await?; + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_stmt2_query_multi_row() -> anyhow::Result<()> { + let db = "stmt2_202411281646"; + let dsn = "ws://localhost:6041"; + + let taos = TaosBuilder::from_dsn(dsn)?.build().await?; + taos.exec_many(vec![ + &format!("drop database if exists {db}"), + &format!("create database {db}"), + &format!("use {db}"), + "create table t0 (ts timestamp, c1 int)", + "insert into t0 values(1726803356466, 99)", + "insert into t0 values(1726803357466, 100)", + "insert into t0 values(1726803358466, 101)", + "insert into t0 values(1726803359466, 102)", + ]) + .await?; + + let mut stmt2 = Stmt2::new(taos.client()); + stmt2.init().await?; + stmt2.prepare("select * from t0 where c1 > ?").await?; + + let cols = vec![ColumnView::from_ints(vec![100])]; + let param = Stmt2BindParam::new(None, None, Some(cols)); + stmt2.bind(&[param]).await?; + + let affected = stmt2.exec().await?; + assert_eq!(affected, 0); + + #[derive(Debug, Deserialize)] + struct Row { + ts: i64, + c1: i32, + } + + let rows: Vec = stmt2 + .result_set() + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(rows.len(), 2); + + assert_eq!(rows[0].ts, 1726803358466); + assert_eq!(rows[1].ts, 1726803359466); + + assert_eq!(rows[0].c1, 101); + assert_eq!(rows[1].c1, 102); + + taos.exec(format!("drop database {db}")).await?; + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_stmt2_insert_with_subtable_names() -> anyhow::Result<()> { + let db = "stmt2_202412021600"; + let dsn = "ws://localhost:6041"; + + let taos = TaosBuilder::from_dsn(dsn)?.build().await?; + taos.exec_many(vec![ + &format!("drop database if exists {db}"), + &format!("create database {db}"), + &format!("use {db}"), + "create stable s0 (ts timestamp, c1 int) tags(t1 int)", + ]) + .await?; + + let mut stmt2 = Stmt2::new(taos.client()); + stmt2.init().await?; + stmt2 + .prepare("insert into ? using s0 tags(?) values(?, ?)") + .await?; + + let tbname = "d0"; + let tags = vec![Value::Int(100)]; + let cols = vec![ + ColumnView::from_millis_timestamp(vec![1726803356466, 1726803357466]), + ColumnView::from_ints(vec![100, 200]), + ]; + let param = Stmt2BindParam::new(Some(tbname.to_owned()), Some(tags), Some(cols)); + + stmt2.bind(&[param]).await?; + + let affected = stmt2.exec().await?; + assert_eq!(affected, 2); + + #[derive(Debug, Deserialize)] + struct Row { + ts: i64, + c1: i32, + } + + let rows: Vec = taos + .query(format!("select * from {tbname}")) + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(rows.len(), 2); + + assert_eq!(rows[0].ts, 1726803356466); + assert_eq!(rows[1].ts, 1726803357466); + + assert_eq!(rows[0].c1, 100); + assert_eq!(rows[1].c1, 200); + + #[derive(Debug, Deserialize)] + struct TagInfo { + table_name: String, + db_name: String, + stable_name: String, + tag_name: String, + tag_type: Ty, + tag_value: String, + } + + let tag_infos: Vec = taos + .query(format!("show tags from {tbname}")) + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(tag_infos.len(), 1); + + assert_eq!(tag_infos[0].table_name, tbname); + assert_eq!(tag_infos[0].db_name, db); + assert_eq!(tag_infos[0].stable_name, "s0"); + assert_eq!(tag_infos[0].tag_name, "t1"); + assert_eq!(tag_infos[0].tag_type, Ty::Int); + assert_eq!(tag_infos[0].tag_value, "100"); + + taos.exec(format!("drop database {db}")).await?; + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_stmt2_insert_with_col_tbname() -> anyhow::Result<()> { + let db = "stmt2_202412061117"; + let dsn = "ws://localhost:6041"; + + let taos = TaosBuilder::from_dsn(dsn)?.build().await?; + taos.exec_many(vec![ + &format!("drop database if exists {db}"), + &format!("create database {db}"), + &format!("use {db}"), + "create stable s0 (ts timestamp, c1 int) tags(t1 int)", + ]) + .await?; + + let mut stmt2 = Stmt2::new(taos.client()); + stmt2.init().await?; + stmt2 + .prepare("insert into s0 (tbname, ts, c1, t1) values(?, ?, ?, ?)") + .await?; + + let tbname = "d0"; + let tags = vec![Value::Int(100)]; + let cols = vec![ + ColumnView::from_millis_timestamp(vec![1726803356466, 1726803357466]), + ColumnView::from_ints(vec![100, 200]), + ]; + let param = Stmt2BindParam::new(Some(tbname.to_owned()), Some(tags), Some(cols)); + + stmt2.bind(&[param]).await?; + + let affected = stmt2.exec().await?; + assert_eq!(affected, 2); + + #[derive(Debug, Deserialize)] + struct Row { + ts: i64, + c1: i32, + } + + let rows: Vec = taos + .query(format!("select * from {tbname}")) + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(rows.len(), 2); + + assert_eq!(rows[0].ts, 1726803356466); + assert_eq!(rows[1].ts, 1726803357466); + + assert_eq!(rows[0].c1, 100); + assert_eq!(rows[1].c1, 200); + + #[derive(Debug, Deserialize)] + struct TagInfo { + table_name: String, + db_name: String, + stable_name: String, + tag_name: String, + tag_type: Ty, + tag_value: String, + } + + let tag_infos: Vec = taos + .query(format!("show tags from {tbname}")) + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(tag_infos.len(), 1); + + assert_eq!(tag_infos[0].table_name, tbname); + assert_eq!(tag_infos[0].db_name, db); + assert_eq!(tag_infos[0].stable_name, "s0"); + assert_eq!(tag_infos[0].tag_name, "t1"); + assert_eq!(tag_infos[0].tag_type, Ty::Int); + assert_eq!(tag_infos[0].tag_value, "100"); + + taos.exec(format!("drop database {db}")).await?; + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_stmt2_result() -> anyhow::Result<()> { + let db = "stmt2_202412021700"; + let dsn = "ws://localhost:6041"; + + let taos = TaosBuilder::from_dsn(dsn)?.build().await?; + taos.exec_many(vec![ + &format!("drop database if exists {db}"), + &format!("create database {db}"), + &format!("use {db}"), + "create table t0 (ts timestamp, c1 int)", + ]) + .await?; + + let mut stmt2 = Stmt2::new(taos.client()); + stmt2.init().await?; + stmt2.prepare("insert into t0 values(?, ?)").await?; + + let res = stmt2.result_set().await; + assert!(res.is_err()); + + Ok(()) + } +} diff --git a/taos/Cargo.toml b/taos/Cargo.toml index 396c5012..2bb63cf0 100644 --- a/taos/Cargo.toml +++ b/taos/Cargo.toml @@ -35,8 +35,10 @@ async-trait = "0.1.68" chrono = "0.4" clap = { version = "4", features = ["derive"] } criterion = { version = "0.5", features = ["html_reports", "stable"] } +flume = "0.11" itertools = "0.13.0" pretty_env_logger = "0.5.0" +rand = "0.8.5" serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["rt-multi-thread"] } @@ -49,6 +51,30 @@ name = "benchmark" harness = false name = "query" +[[bench]] +harness = false +name = "sql" + +[[bench]] +harness = false +name = "stmt" + +[[bench]] +harness = false +name = "stmt2" + +[[bench]] +harness = false +name = "sql_interlace" + +[[bench]] +harness = false +name = "stmt_interlace" + +[[bench]] +harness = false +name = "stmt2_interlace" + [features] deadpool = ["taos-query/deadpool"] default = ["ws-rustls", "optin", "deadpool"] @@ -59,6 +85,14 @@ ws = ["taos-ws"] ws-native-tls = ["ws", "taos-ws/native-tls"] ws-native-tls-vendored = ["ws", "taos-ws/native-tls-vendored"] ws-rustls = ["ws", "taos-ws/rustls"] -ws-rustls-ring-crypto-provider = ["ws", "taos-ws/rustls", "taos-ws/rustls-ring-crypto-provider"] -ws-rustls-aws-lc-crypto-provider = ["ws", "taos-ws/rustls", "taos-ws/rustls-aws-lc-crypto-provider"] +ws-rustls-ring-crypto-provider = [ + "ws", + "taos-ws/rustls", + "taos-ws/rustls-ring-crypto-provider", +] +ws-rustls-aws-lc-crypto-provider = [ + "ws", + "taos-ws/rustls", + "taos-ws/rustls-aws-lc-crypto-provider", +] deflate = ["ws", "taos-ws/deflate"] diff --git a/taos/benches/sql.rs b/taos/benches/sql.rs new file mode 100644 index 00000000..74d93556 --- /dev/null +++ b/taos/benches/sql.rs @@ -0,0 +1,201 @@ +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; + +use chrono::Local; +use flume::{Receiver, Sender}; +use rand::Rng; +use taos::*; + +const DSN: &str = "ws://localhost:6041"; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + println!( + "SQL: One million subtables, each with one thousand records, \ + a total of one billion records." + ); + + // One million subtables + let subtable_cnt = 1000000; + // One thousand records per subtable + let record_cnt = 1000; + + let ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + + let db = &format!("db_{}", ts); + + let taos = TaosBuilder::from_dsn(DSN)?.build().await?; + taos.exec_many([ + &format!("drop database if exists {db}"), + &format!("create database {db} vgroups 10"), + &format!("use {db}"), + "create stable s0 (ts timestamp, c1 int, c2 float, c3 float) tags(t1 int)", + ]) + .await?; + + create_subtables(db, subtable_cnt).await; + + let thread_cnt = 4; + let mut senders = vec![]; + let mut receivers = vec![]; + + for _ in 0..thread_cnt { + let (sender, receiver) = flume::bounded(64); + senders.push(sender); + receivers.push(receiver); + } + + produce_sqls(senders, subtable_cnt, record_cnt).await; + + tokio::time::sleep(Duration::from_millis(200)).await; + + consume_sqls(db, receivers).await; + + check_count(&taos, subtable_cnt * record_cnt).await?; + + taos.exec(format!("drop database {db}")).await?; + + Ok(()) +} + +async fn create_subtables(db: &str, subtable_cnt: usize) { + println!("Creating subtables start"); + + let start = Instant::now(); + let batch_cnt = 10000; + let thread_cnt = 10; + let thread_subtable_cnt = subtable_cnt / thread_cnt; + let mut tasks = vec![]; + + for i in (0..subtable_cnt).step_by(thread_subtable_cnt) { + let db = db.to_owned(); + + let task = tokio::spawn(async move { + let taos = TaosBuilder::from_dsn(DSN).unwrap().build().await.unwrap(); + taos.exec(format!("use {db}")).await.unwrap(); + + for j in (0..thread_subtable_cnt).step_by(batch_cnt) { + // creata table d0 using s0 tags(0) d1 using s0 tags(0) ... + let mut sql = String::with_capacity(25 * batch_cnt); + sql.push_str("create table "); + for k in 0..batch_cnt { + sql.push_str(&format!("d{} using s0 tags(0) ", i + j + k)); + } + taos.exec(sql).await.unwrap(); + } + }); + + tasks.push(task); + } + + for task in tasks { + task.await.unwrap(); + } + + println!("Creating subtables end, elapsed = {:?}", start.elapsed()); +} + +async fn produce_sqls(senders: Vec>, subtable_cnt: usize, record_cnt: usize) { + let batch_cnt = 10000; + let batch_subtable_cnt = batch_cnt / record_cnt; + let thread_cnt = senders.len(); + let thread_subtable_cnt = subtable_cnt / thread_cnt; + + for i in (0..subtable_cnt).step_by(thread_subtable_cnt) { + let idx = i / thread_subtable_cnt; + let sender = senders[idx].clone(); + tokio::spawn(async move { + println!("Producer thread[{idx}] starts producing data"); + + let mut rng = rand::thread_rng(); + + for j in (0..thread_subtable_cnt).step_by(batch_subtable_cnt) { + let mut sql = String::with_capacity(100 * batch_cnt); + sql.push_str("insert into "); + + for k in 0..batch_subtable_cnt { + sql.push_str(&format!("d{} values ", i + j + k)); + + let ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as i64; + + for l in 0..record_cnt { + let ts = ts + l as i64; + let c1: i32 = rng.gen(); + let c2: f32 = rng.gen_range(0.0..10000000.); + let c2: f32 = (c2 * 100.0).round() / 100.0; + let c3: f32 = rng.gen_range(0.0..10000000.); + let c3: f32 = (c3 * 100.0).round() / 100.0; + sql.push_str(&format!("({ts},{c1},{c2},{c3}),")); + } + } + + sender.send(sql).unwrap(); + } + + println!("Producer thread[{idx}] ends producing data"); + }); + } +} + +async fn consume_sqls(db: &str, mut receivers: Vec>) { + let now = Local::now(); + let time = now.format("%Y-%m-%d %H:%M:%S").to_string(); + println!("Consuming sqls start, time = {time}"); + + let start = Instant::now(); + let mut tasks = vec![]; + + for i in 0..receivers.len() { + let db = db.to_owned(); + let receiver = receivers.pop().unwrap(); + + let task = tokio::spawn(async move { + let taos = TaosBuilder::from_dsn(DSN).unwrap().build().await.unwrap(); + taos.exec(format!("use {db}")).await.unwrap(); + + println!("Consumer thread[{i}] starts consuming data"); + + let start = Instant::now(); + while let Ok(sql) = receiver.recv_async().await { + taos.exec(sql).await.unwrap(); + } + + println!( + "Consumer thread[{i}] ends consuming data, elapsed = {:?}", + start.elapsed() + ); + }); + + tasks.push(task); + } + + for task in tasks { + task.await.unwrap(); + } + + println!("Consuming sqls end, elapsed = {:?}\n", start.elapsed()); +} + +async fn check_count(taos: &Taos, cnt: usize) -> anyhow::Result<()> { + #[derive(Debug, serde::Deserialize)] + struct Record { + cnt: usize, + } + + let res: Vec = taos + .query("select count(*) as cnt from s0") + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(res.len(), 1); + assert_eq!(res[0].cnt, cnt); + + Ok(()) +} diff --git a/taos/benches/sql_interlace.rs b/taos/benches/sql_interlace.rs new file mode 100644 index 00000000..139cfc24 --- /dev/null +++ b/taos/benches/sql_interlace.rs @@ -0,0 +1,205 @@ +use std::ops::Add; +use std::time::{Instant, SystemTime, UNIX_EPOCH}; + +use chrono::Local; +use rand::Rng; +use taos::*; + +const DSN: &str = "ws://localhost:6041"; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + println!( + "SQL: interlace=1, one million subtables, each with one hundred records, \ + a total of one hundred million records." + ); + + // One million subtables + let subtable_cnt = 1000000; + // One hundred records per subtable + let record_cnt = 100; + + let ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + + let db = &format!("db_{}", ts); + + let taos = TaosBuilder::from_dsn(DSN)?.build().await?; + taos.exec_many([ + &format!("drop database if exists {db}"), + &format!("create database {db} vgroups 10"), + &format!("use {db}"), + "create stable s0 (ts timestamp, c1 int, c2 float, c3 float) tags(t1 int)", + ]) + .await?; + + create_subtables(db, subtable_cnt).await; + + let sqls = produce_sqls(subtable_cnt, record_cnt).await; + + consume_sqls(db, sqls).await; + + check_count(&taos, subtable_cnt * record_cnt).await?; + + taos.exec(format!("drop database {db}")).await?; + + Ok(()) +} + +async fn create_subtables(db: &str, subtable_cnt: usize) { + println!("Creating subtables start"); + + let start = Instant::now(); + let batch_cnt = 10000; + let thread_cnt = 10; + let thread_subtable_cnt = subtable_cnt / thread_cnt; + let mut tasks = vec![]; + + for i in (0..subtable_cnt).step_by(thread_subtable_cnt) { + let db = db.to_owned(); + + let task = tokio::spawn(async move { + let taos = TaosBuilder::from_dsn(DSN).unwrap().build().await.unwrap(); + taos.exec(format!("use {db}")).await.unwrap(); + + for j in (0..thread_subtable_cnt).step_by(batch_cnt) { + // creata table d0 using s0 tags(0) d1 using s0 tags(0) ... + let mut sql = String::with_capacity(25 * batch_cnt); + sql.push_str("create table "); + for k in 0..batch_cnt { + sql.push_str(&format!("d{} using s0 tags(0) ", i + j + k)); + } + taos.exec(sql).await.unwrap(); + } + }); + + tasks.push(task); + } + + for task in tasks { + task.await.unwrap(); + } + + println!("Creating subtables end, elapsed = {:?}", start.elapsed()); +} + +async fn produce_sqls(subtable_cnt: usize, record_cnt: usize) -> Vec { + println!("Producing sqls start"); + + let start = Instant::now(); + let batch_cnt = 10000; + let thread_cnt = 10; + let thread_record_cnt = record_cnt / thread_cnt; + let mut tasks = Vec::with_capacity(thread_cnt); + + for i in 0..thread_cnt { + let task = tokio::spawn(async move { + println!("Producer thread[{i}] starts producing data"); + + let mut rng = rand::thread_rng(); + let mut sqls = Vec::with_capacity(subtable_cnt * thread_record_cnt); + + for _ in 0..thread_record_cnt { + let ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() + .add((i * 200) as u128) as i64; + + for j in (0..subtable_cnt).step_by(batch_cnt) { + // insert into d0 values() d1 values() ... + let mut sql = String::with_capacity(100 * batch_cnt); + sql.push_str("insert into "); + for k in 0..batch_cnt { + let tbname = format!("d{}", j + k); + let c1 = rng.gen::(); + let c2: f32 = rng.gen_range(0.0..10000000.); + let c2 = (c2 * 100.0).round() / 100.0; + let c3: f32 = rng.gen_range(0.0..10000000.); + let c3 = (c3 * 100.0).round() / 100.0; + sql.push_str(&format!("{tbname} values({ts},{c1},{c2},{c3}) ")); + } + sqls.push(sql); + } + } + + println!("Producer thread[{i}] ends producing data"); + + sqls + }); + + tasks.push(task); + } + + let mut sqls = Vec::with_capacity(subtable_cnt * record_cnt); + for task in tasks { + sqls.extend(task.await.unwrap()); + } + + println!("Producing {} sqls", sqls.len()); + println!("Producing sqls end, elapsed = {:?}", start.elapsed()); + + sqls +} + +async fn consume_sqls(db: &str, sqls: Vec) { + let now = Local::now(); + let time = now.format("%Y-%m-%d %H:%M:%S").to_string(); + println!("Consuming data start, time = {time}"); + + let start = Instant::now(); + let thread_cnt = 4; + let thread_sql_cnt = sqls.len() / thread_cnt; + let mut tasks = vec![]; + + for (i, sqls) in sqls.chunks(thread_sql_cnt).enumerate() { + let db = db.to_owned(); + let sqls = sqls.to_vec(); + + let task = tokio::spawn(async move { + let taos = TaosBuilder::from_dsn(DSN).unwrap().build().await.unwrap(); + taos.exec(format!("use {db}")).await.unwrap(); + + println!("Consumer thread[{i}] starts consuming data"); + + let start = Instant::now(); + for sql in sqls { + taos.exec(sql).await.unwrap(); + } + + println!( + "Consumer thread[{i}] ends consuming data, elapsed = {:?}", + start.elapsed() + ); + }); + + tasks.push(task); + } + + for task in tasks { + task.await.unwrap(); + } + + println!("Consuming data end, elapsed = {:?}\n", start.elapsed()); +} + +async fn check_count(taos: &Taos, cnt: usize) -> anyhow::Result<()> { + #[derive(Debug, serde::Deserialize)] + struct Record { + cnt: usize, + } + + let res: Vec = taos + .query("select count(*) as cnt from s0") + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(res.len(), 1); + assert_eq!(res[0].cnt, cnt); + + Ok(()) +} diff --git a/taos/benches/stmt.rs b/taos/benches/stmt.rs new file mode 100644 index 00000000..862de37e --- /dev/null +++ b/taos/benches/stmt.rs @@ -0,0 +1,231 @@ +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; + +use chrono::Local; +use flume::{Receiver, Sender}; +use rand::Rng; +use taos::*; + +const DSN: &str = "ws://localhost:6041"; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + println!( + "Stmt: One million subtables, each with one thousand records, \ + a total of one billion records." + ); + + // One million subtables + let subtable_cnt = 1000000; + // One thousand records per subtable + let record_cnt = 1000; + + let ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + + let db = &format!("db_{}", ts); + + let taos = TaosBuilder::from_dsn(DSN)?.build().await?; + taos.exec_many([ + &format!("drop database if exists {db}"), + &format!("create database {db} vgroups 10"), + &format!("use {db}"), + "create stable s0 (ts timestamp, c1 int, c2 float, c3 float) tags(t1 int)", + ]) + .await?; + + create_subtables(db, subtable_cnt).await; + + let thread_cnt = 4; + let mut senders = vec![]; + let mut receivers = vec![]; + + for _ in 0..thread_cnt { + let (sender, receiver) = flume::bounded(64); + senders.push(sender); + receivers.push(receiver); + } + + produce_data(senders, subtable_cnt, record_cnt).await; + + tokio::time::sleep(Duration::from_millis(200)).await; + + consume_data(db, receivers).await; + + check_count(&taos, subtable_cnt * record_cnt).await?; + + taos.exec(format!("drop database {db}")).await?; + + Ok(()) +} + +async fn create_subtables(db: &str, subtable_cnt: usize) { + println!("Creating subtables start"); + + let start = Instant::now(); + let batch_cnt = 10000; + let thread_cnt = 10; + let thread_subtable_cnt = subtable_cnt / thread_cnt; + let mut tasks = vec![]; + + for i in (0..subtable_cnt).step_by(thread_subtable_cnt) { + let db = db.to_owned(); + + let task = tokio::spawn(async move { + let taos = TaosBuilder::from_dsn(DSN).unwrap().build().await.unwrap(); + taos.exec(format!("use {db}")).await.unwrap(); + + for j in (0..thread_subtable_cnt).step_by(batch_cnt) { + // creata table d0 using s0 tags(0) d1 using s0 tags(0) ... + let mut sql = String::with_capacity(25 * batch_cnt); + sql.push_str("create table "); + for k in 0..batch_cnt { + sql.push_str(&format!("d{} using s0 tags(0) ", i + j + k)); + } + taos.exec(sql).await.unwrap(); + } + }); + + tasks.push(task); + } + + for task in tasks { + task.await.unwrap(); + } + + println!("Creating subtables end, elapsed = {:?}", start.elapsed()); +} + +async fn produce_data( + senders: Vec)>>>, + subtable_cnt: usize, + record_cnt: usize, +) { + let batch_cnt = 10000; + let batch_subtable_cnt = batch_cnt / record_cnt; + let thread_cnt = senders.len(); + let thread_subtable_cnt = subtable_cnt / thread_cnt; + + for i in (0..subtable_cnt).step_by(thread_subtable_cnt) { + let idx = i / thread_subtable_cnt; + let sender = senders[idx].clone(); + tokio::spawn(async move { + println!("Producer thread[{idx}] starts producing data"); + + let mut rng = rand::thread_rng(); + + for j in (0..thread_subtable_cnt).step_by(batch_subtable_cnt) { + let mut datas = Vec::with_capacity(batch_subtable_cnt); + + for k in 0..batch_subtable_cnt { + let mut tss = Vec::with_capacity(record_cnt); + let mut c1s = Vec::with_capacity(record_cnt); + let mut c2s = Vec::with_capacity(record_cnt); + let mut c3s = Vec::with_capacity(record_cnt); + + let ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as i64; + + for l in 0..record_cnt { + let ts = ts + l as i64; + let c1 = rng.gen::(); + let c2: f32 = rng.gen_range(0.0..10000000.); + let c2 = (c2 * 100.0).round() / 100.0; + let c3: f32 = rng.gen_range(0.0..10000000.); + let c3 = (c3 * 100.0).round() / 100.0; + + tss.push(ts); + c1s.push(c1); + c2s.push(c2); + c3s.push(c3); + } + + let tbname = format!("d{}", i + j + k); + let cols = vec![ + ColumnView::from_millis_timestamp(tss), + ColumnView::from_ints(c1s), + ColumnView::from_floats(c2s), + ColumnView::from_floats(c3s), + ]; + + datas.push((tbname, cols)); + } + + sender.send(datas).unwrap(); + } + + println!("Producer thread[{idx}] ends producing data"); + }); + } +} + +async fn consume_data(db: &str, mut receivers: Vec)>>>) { + let now = Local::now(); + let time = now.format("%Y-%m-%d %H:%M:%S").to_string(); + println!("Consuming data start, time = {time}"); + + let start = Instant::now(); + let mut tasks = vec![]; + + for i in 0..receivers.len() { + let db = db.to_owned(); + let receiver = receivers.pop().unwrap(); + + let task = tokio::spawn(async move { + let taos = TaosBuilder::from_dsn(DSN).unwrap().build().await.unwrap(); + taos.exec(format!("use {db}")).await.unwrap(); + + let mut stmt = Stmt::init(&taos).await.unwrap(); + + let sql = "insert into ? values(?, ?, ?, ?)"; + stmt.prepare(sql).await.unwrap(); + + println!("Consumer thread[{i}] starts consuming data"); + + let start = Instant::now(); + while let Ok(datas) = receiver.recv_async().await { + for (tbname, cols) in datas { + stmt.set_tbname(&tbname).await.unwrap(); + stmt.bind(&cols).await.unwrap(); + stmt.add_batch().await.unwrap(); + } + stmt.execute().await.unwrap(); + } + + println!( + "Consumer thread[{i}] ends consuming data, elapsed = {:?}", + start.elapsed() + ); + }); + + tasks.push(task); + } + + for task in tasks { + task.await.unwrap(); + } + + println!("Consuming data end, elapsed = {:?}\n", start.elapsed()); +} + +async fn check_count(taos: &Taos, cnt: usize) -> anyhow::Result<()> { + #[derive(Debug, serde::Deserialize)] + struct Record { + cnt: usize, + } + + let res: Vec = taos + .query("select count(*) as cnt from s0") + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(res.len(), 1); + assert_eq!(res[0].cnt, cnt); + + Ok(()) +} diff --git a/taos/benches/stmt2.rs b/taos/benches/stmt2.rs new file mode 100644 index 00000000..4f683453 --- /dev/null +++ b/taos/benches/stmt2.rs @@ -0,0 +1,228 @@ +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; + +use chrono::Local; +use flume::{Receiver, Sender}; +use rand::Rng; +use taos::*; + +const DSN: &str = "ws://localhost:6041"; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + println!( + "Stmt2: One million subtables, each with one thousand records, \ + a total of one billion records." + ); + + // One million subtables + let subtable_cnt = 1000000; + // One thousand records per subtable + let record_cnt = 1000; + + let ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + + let db = &format!("db_{}", ts); + + let taos = TaosBuilder::from_dsn(DSN)?.build().await?; + taos.exec_many([ + &format!("drop database if exists {db}"), + &format!("create database {db} vgroups 10"), + &format!("use {db}"), + "create stable s0 (ts timestamp, c1 int, c2 float, c3 float) tags(t1 int)", + ]) + .await?; + + create_subtables(db, subtable_cnt).await; + + let thread_cnt = 4; + let mut senders = vec![]; + let mut receivers = vec![]; + + for _ in 0..thread_cnt { + let (sender, receiver) = flume::bounded(64); + senders.push(sender); + receivers.push(receiver); + } + + produce_data(senders, subtable_cnt, record_cnt).await; + + tokio::time::sleep(Duration::from_millis(200)).await; + + consume_data(db, receivers).await; + + check_count(&taos, subtable_cnt * record_cnt).await?; + + taos.exec(format!("drop database {db}")).await?; + + Ok(()) +} + +async fn create_subtables(db: &str, subtable_cnt: usize) { + println!("Creating subtables start"); + + let start = Instant::now(); + let batch_cnt = 10000; + let thread_cnt = 10; + let thread_subtable_cnt = subtable_cnt / thread_cnt; + let mut tasks = vec![]; + + for i in (0..subtable_cnt).step_by(thread_subtable_cnt) { + let db = db.to_owned(); + + let task = tokio::spawn(async move { + let taos = TaosBuilder::from_dsn(DSN).unwrap().build().await.unwrap(); + taos.exec(format!("use {db}")).await.unwrap(); + + for j in (0..thread_subtable_cnt).step_by(batch_cnt) { + // creata table d0 using s0 tags(0) d1 using s0 tags(0) ... + let mut sql = String::with_capacity(25 * batch_cnt); + sql.push_str("create table "); + for k in 0..batch_cnt { + sql.push_str(&format!("d{} using s0 tags(0) ", i + j + k)); + } + taos.exec(sql).await.unwrap(); + } + }); + + tasks.push(task); + } + + for task in tasks { + task.await.unwrap(); + } + + println!("Creating subtables end, elapsed = {:?}", start.elapsed()); +} + +async fn produce_data( + senders: Vec>>, + subtable_cnt: usize, + record_cnt: usize, +) { + let batch_cnt = 10000; + let batch_subtable_cnt = batch_cnt / record_cnt; + let thread_cnt = senders.len(); + let thread_subtable_cnt = subtable_cnt / thread_cnt; + + for i in (0..subtable_cnt).step_by(thread_subtable_cnt) { + let idx = i / thread_subtable_cnt; + let sender = senders[idx].clone(); + tokio::spawn(async move { + println!("Producer thread[{idx}] starts producing data"); + + let mut rng = rand::thread_rng(); + + for j in (0..thread_subtable_cnt).step_by(batch_subtable_cnt) { + let mut params = Vec::with_capacity(batch_subtable_cnt); + + for k in 0..batch_subtable_cnt { + let mut tss = Vec::with_capacity(record_cnt); + let mut c1s = Vec::with_capacity(record_cnt); + let mut c2s = Vec::with_capacity(record_cnt); + let mut c3s = Vec::with_capacity(record_cnt); + + let ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as i64; + + for l in 0..record_cnt { + let ts = ts + l as i64; + let c1 = rng.gen::(); + let c2: f32 = rng.gen_range(0.0..10000000.); + let c2 = (c2 * 100.0).round() / 100.0; + let c3: f32 = rng.gen_range(0.0..10000000.); + let c3 = (c3 * 100.0).round() / 100.0; + + tss.push(ts); + c1s.push(c1); + c2s.push(c2); + c3s.push(c3); + } + + let tbname = format!("d{}", i + j + k); + let cols = vec![ + ColumnView::from_millis_timestamp(tss), + ColumnView::from_ints(c1s), + ColumnView::from_floats(c2s), + ColumnView::from_floats(c3s), + ]; + + let param = Stmt2BindParam::new(Some(tbname), None, Some(cols)); + params.push(param); + } + + sender.send(params).unwrap(); + } + + println!("Producer thread[{idx}] ends producing data"); + }); + } +} + +async fn consume_data(db: &str, mut receivers: Vec>>) { + let now = Local::now(); + let time = now.format("%Y-%m-%d %H:%M:%S").to_string(); + println!("Consuming data start, time = {time}"); + + let start = Instant::now(); + let mut tasks = vec![]; + + for i in 0..receivers.len() { + let db = db.to_owned(); + let receiver = receivers.pop().unwrap(); + + let task = tokio::spawn(async move { + let taos = TaosBuilder::from_dsn(DSN).unwrap().build().await.unwrap(); + taos.exec(format!("use {db}")).await.unwrap(); + + let mut stmt2 = Stmt2::init(&taos).await.unwrap(); + + let sql = "insert into s0 (tbname, ts, c1, c2, c3) values(?, ?, ?, ?, ?)"; + stmt2.prepare(sql).await.unwrap(); + + println!("Consumer thread[{i}] starts consuming data"); + + let start = Instant::now(); + while let Ok(params) = receiver.recv_async().await { + stmt2.bind(¶ms).await.unwrap(); + stmt2.exec().await.unwrap(); + } + + println!( + "Consumer thread[{i}] ends consuming data, elapsed = {:?}", + start.elapsed() + ); + }); + + tasks.push(task); + } + + for task in tasks { + task.await.unwrap(); + } + + println!("Consuming data end, elapsed = {:?}\n", start.elapsed()); +} + +async fn check_count(taos: &Taos, cnt: usize) -> anyhow::Result<()> { + #[derive(Debug, serde::Deserialize)] + struct Record { + cnt: usize, + } + + let res: Vec = taos + .query("select count(*) as cnt from s0") + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(res.len(), 1); + assert_eq!(res[0].cnt, cnt); + + Ok(()) +} diff --git a/taos/benches/stmt2_interlace.rs b/taos/benches/stmt2_interlace.rs new file mode 100644 index 00000000..a9193d3b --- /dev/null +++ b/taos/benches/stmt2_interlace.rs @@ -0,0 +1,217 @@ +use std::ops::Add; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; + +use chrono::Local; +use flume::{Receiver, Sender}; +use rand::Rng; +use taos::*; + +const DSN: &str = "ws://localhost:6041"; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + println!( + "Stmt2: interlace=1, one million subtables, each with one hundred records, \ + a total of one hundred million records." + ); + + // One million subtables + let subtable_cnt = 1000000; + // One hundred records per subtable + let record_cnt = 100; + + let ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + + let db = &format!("db_{}", ts); + + let taos = TaosBuilder::from_dsn(DSN)?.build().await?; + taos.exec_many([ + &format!("drop database if exists {db}"), + &format!("create database {db} vgroups 10"), + &format!("use {db}"), + "create stable s0 (ts timestamp, c1 int, c2 float, c3 float) tags(t1 int)", + ]) + .await?; + + create_subtables(db, subtable_cnt).await; + + let thread_cnt = 4; + let mut senders = vec![]; + let mut receivers = vec![]; + + for _ in 0..thread_cnt { + let (sender, receiver) = flume::bounded(64); + senders.push(sender); + receivers.push(receiver); + } + + produce_data(senders, subtable_cnt, record_cnt).await; + + tokio::time::sleep(Duration::from_millis(200)).await; + + consume_data(db, receivers).await; + + check_count(&taos, subtable_cnt * record_cnt).await?; + + taos.exec(format!("drop database {db}")).await?; + + Ok(()) +} + +async fn create_subtables(db: &str, subtable_cnt: usize) { + println!("Creating subtables start"); + + let start = Instant::now(); + let batch_cnt = 10000; + let thread_cnt = 10; + let thread_subtable_cnt = subtable_cnt / thread_cnt; + let mut tasks = vec![]; + + for i in (0..subtable_cnt).step_by(thread_subtable_cnt) { + let db = db.to_owned(); + + let task = tokio::spawn(async move { + let taos = TaosBuilder::from_dsn(DSN).unwrap().build().await.unwrap(); + taos.exec(format!("use {db}")).await.unwrap(); + + for j in (0..thread_subtable_cnt).step_by(batch_cnt) { + // creata table d0 using s0 tags(0) d1 using s0 tags(0) ... + let mut sql = String::with_capacity(25 * batch_cnt); + sql.push_str("create table "); + for k in 0..batch_cnt { + sql.push_str(&format!("d{} using s0 tags(0) ", i + j + k)); + } + taos.exec(sql).await.unwrap(); + } + }); + + tasks.push(task); + } + + for task in tasks { + task.await.unwrap(); + } + + println!("Creating subtables end, elapsed = {:?}", start.elapsed()); +} + +async fn produce_data( + senders: Vec>>, + subtable_cnt: usize, + record_cnt: usize, +) { + let batch_cnt = 10000; + let thread_cnt = senders.len(); + let thread_record_cnt = record_cnt / thread_cnt; + + for i in 0..thread_cnt { + let sender = senders[i].clone(); + tokio::spawn(async move { + println!("Producer thread[{i}] starts producing data"); + + let mut rng = rand::thread_rng(); + + for _ in 0..thread_record_cnt { + let ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() + .add((i * 200) as u128) as i64; + + for j in (0..subtable_cnt).step_by(batch_cnt) { + let mut params = Vec::with_capacity(batch_cnt); + + for k in 0..batch_cnt { + let c1 = rng.gen::(); + let c2: f32 = rng.gen_range(0.0..10000000.); + let c2 = (c2 * 100.0).round() / 100.0; + let c3: f32 = rng.gen_range(0.0..10000000.); + let c3 = (c3 * 100.0).round() / 100.0; + + let tbname = format!("d{}", j + k); + let cols = vec![ + ColumnView::from_millis_timestamp(vec![ts]), + ColumnView::from_ints(vec![c1]), + ColumnView::from_floats(vec![c2]), + ColumnView::from_floats(vec![c3]), + ]; + + let param = Stmt2BindParam::new(Some(tbname), None, Some(cols)); + params.push(param); + } + + sender.send(params).unwrap(); + } + } + + println!("Producer thread[{i}] ends producing data"); + }); + } +} + +async fn consume_data(db: &str, mut receivers: Vec>>) { + let now = Local::now(); + let time = now.format("%Y-%m-%d %H:%M:%S").to_string(); + println!("Consuming data start, time = {time}"); + + let start = Instant::now(); + let mut tasks = vec![]; + + for i in 0..receivers.len() { + let db = db.to_owned(); + let receiver = receivers.pop().unwrap(); + + let task = tokio::spawn(async move { + let taos = TaosBuilder::from_dsn(DSN).unwrap().build().await.unwrap(); + taos.exec(format!("use {db}")).await.unwrap(); + + let mut stmt2 = Stmt2::init(&taos).await.unwrap(); + + let sql = "insert into s0 (tbname, ts, c1, c2, c3) values(?, ?, ?, ?, ?)"; + stmt2.prepare(sql).await.unwrap(); + + println!("Consumer thread[{i}] starts consuming data"); + + let start = Instant::now(); + while let Ok(params) = receiver.recv_async().await { + stmt2.bind(¶ms).await.unwrap(); + stmt2.exec().await.unwrap(); + } + + println!( + "Consumer thread[{i}] ends consuming data, elapsed = {:?}", + start.elapsed() + ); + }); + + tasks.push(task); + } + + for task in tasks { + task.await.unwrap(); + } + + println!("Consuming data end, elapsed = {:?}\n", start.elapsed()); +} + +async fn check_count(taos: &Taos, cnt: usize) -> anyhow::Result<()> { + #[derive(Debug, serde::Deserialize)] + struct Record { + cnt: usize, + } + + let res: Vec = taos + .query("select count(*) as cnt from s0") + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(res.len(), 1); + assert_eq!(res[0].cnt, cnt); + + Ok(()) +} diff --git a/taos/benches/stmt_interlace.rs b/taos/benches/stmt_interlace.rs new file mode 100644 index 00000000..0ef6c6e2 --- /dev/null +++ b/taos/benches/stmt_interlace.rs @@ -0,0 +1,220 @@ +use std::ops::Add; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; + +use chrono::Local; +use flume::{Receiver, Sender}; +use rand::Rng; +use taos::*; + +const DSN: &str = "ws://localhost:6041"; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + println!( + "Stmt: interlace=1, one million subtables, each with one hundred records, \ + a total of one hundred million records." + ); + + // One million subtables + let subtable_cnt = 1000000; + // One hundred records per subtable + let record_cnt = 100; + + let ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + + let db = &format!("db_{}", ts); + + let taos = TaosBuilder::from_dsn(DSN)?.build().await?; + taos.exec_many([ + &format!("drop database if exists {db}"), + &format!("create database {db} vgroups 10"), + &format!("use {db}"), + "create stable s0 (ts timestamp, c1 int, c2 float, c3 float) tags(t1 int)", + ]) + .await?; + + create_subtables(db, subtable_cnt).await; + + let thread_cnt = 4; + let mut senders = vec![]; + let mut receivers = vec![]; + + for _ in 0..thread_cnt { + let (sender, receiver) = flume::bounded(64); + senders.push(sender); + receivers.push(receiver); + } + + produce_data(senders, subtable_cnt, record_cnt).await; + + tokio::time::sleep(Duration::from_millis(200)).await; + + consume_data(db, receivers).await; + + check_count(&taos, subtable_cnt * record_cnt).await?; + + taos.exec(format!("drop database {db}")).await?; + + Ok(()) +} + +async fn create_subtables(db: &str, subtable_cnt: usize) { + println!("Creating subtables start"); + + let start = Instant::now(); + let batch_cnt = 10000; + let thread_cnt = 10; + let thread_subtable_cnt = subtable_cnt / thread_cnt; + let mut tasks = vec![]; + + for i in (0..subtable_cnt).step_by(thread_subtable_cnt) { + let db = db.to_owned(); + + let task = tokio::spawn(async move { + let taos = TaosBuilder::from_dsn(DSN).unwrap().build().await.unwrap(); + taos.exec(format!("use {db}")).await.unwrap(); + + for j in (0..thread_subtable_cnt).step_by(batch_cnt) { + // creata table d0 using s0 tags(0) d1 using s0 tags(0) ... + let mut sql = String::with_capacity(25 * batch_cnt); + sql.push_str("create table "); + for k in 0..batch_cnt { + sql.push_str(&format!("d{} using s0 tags(0) ", i + j + k)); + } + taos.exec(sql).await.unwrap(); + } + }); + + tasks.push(task); + } + + for task in tasks { + task.await.unwrap(); + } + + println!("Creating subtables end, elapsed = {:?}", start.elapsed()); +} + +async fn produce_data( + senders: Vec)>>>, + subtable_cnt: usize, + record_cnt: usize, +) { + let batch_cnt = 10000; + let thread_cnt = senders.len(); + let thread_record_cnt = record_cnt / thread_cnt; + + for i in 0..thread_cnt { + let sender = senders[i].clone(); + tokio::spawn(async move { + println!("Producer thread[{i}] starts producing data"); + + let mut rng = rand::thread_rng(); + + for _ in 0..thread_record_cnt { + let ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() + .add((i * 200) as u128) as i64; + + for j in (0..subtable_cnt).step_by(batch_cnt) { + let mut datas = Vec::with_capacity(batch_cnt); + + for k in 0..batch_cnt { + let c1 = rng.gen::(); + let c2: f32 = rng.gen_range(0.0..10000000.); + let c2 = (c2 * 100.0).round() / 100.0; + let c3: f32 = rng.gen_range(0.0..10000000.); + let c3 = (c3 * 100.0).round() / 100.0; + + let tbname = format!("d{}", j + k); + let cols = vec![ + ColumnView::from_millis_timestamp(vec![ts]), + ColumnView::from_ints(vec![c1]), + ColumnView::from_floats(vec![c2]), + ColumnView::from_floats(vec![c3]), + ]; + + datas.push((tbname, cols)); + } + + sender.send(datas).unwrap(); + } + } + + println!("Producer thread[{i}] ends producing data"); + }); + } +} + +async fn consume_data(db: &str, mut receivers: Vec)>>>) { + let now = Local::now(); + let time = now.format("%Y-%m-%d %H:%M:%S").to_string(); + println!("Consuming data start, time = {time}"); + + let start = Instant::now(); + let mut tasks = vec![]; + + for i in 0..receivers.len() { + let db = db.to_owned(); + let receiver = receivers.pop().unwrap(); + + let task = tokio::spawn(async move { + let taos = TaosBuilder::from_dsn(DSN).unwrap().build().await.unwrap(); + taos.exec(format!("use {db}")).await.unwrap(); + + let mut stmt = Stmt::init(&taos).await.unwrap(); + + let sql = "insert into ? values(?, ?, ?, ?)"; + stmt.prepare(sql).await.unwrap(); + + println!("Consumer thread[{i}] starts consuming data"); + + let start = Instant::now(); + while let Ok(datas) = receiver.recv_async().await { + for (tbname, cols) in datas { + stmt.set_tbname(&tbname).await.unwrap(); + stmt.bind(&cols).await.unwrap(); + stmt.add_batch().await.unwrap(); + } + stmt.execute().await.unwrap(); + } + + println!( + "Consumer thread[{i}] ends consuming data, elapsed = {:?}", + start.elapsed() + ); + }); + + tasks.push(task); + } + + for task in tasks { + task.await.unwrap(); + } + + println!("Consuming data end, elapsed = {:?}\n", start.elapsed()); +} + +async fn check_count(taos: &Taos, cnt: usize) -> anyhow::Result<()> { + #[derive(Debug, serde::Deserialize)] + struct Record { + cnt: usize, + } + + let res: Vec = taos + .query("select count(*) as cnt from s0") + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(res.len(), 1); + assert_eq!(res[0].cnt, cnt); + + Ok(()) +} diff --git a/taos/src/lib.rs b/taos/src/lib.rs index aa28f028..0370d00d 100644 --- a/taos/src/lib.rs +++ b/taos/src/lib.rs @@ -7,7 +7,7 @@ pub type TaosPool = taos_query::prelude::Pool; pub mod sync { pub use taos_query::prelude::sync::*; - pub use super::{Consumer, MessageSet, Offset, Stmt, Taos, TaosBuilder, TmqBuilder}; + pub use super::{Consumer, MessageSet, Offset, Stmt, Stmt2, Taos, TaosBuilder, TmqBuilder}; } #[cfg(all(feature = "ws", feature = "optin"))] @@ -15,6 +15,11 @@ mod stmt; #[cfg(all(feature = "ws", feature = "optin"))] pub use stmt::Stmt; +#[cfg(all(feature = "ws", feature = "optin"))] +mod stmt2; +#[cfg(all(feature = "ws", feature = "optin"))] +pub use stmt2::Stmt2; + #[cfg(all(feature = "ws", feature = "optin"))] mod tmq; #[cfg(all(feature = "ws", feature = "optin"))] diff --git a/taos/src/query.rs b/taos/src/query.rs index 9258b257..8df24dac 100644 --- a/taos/src/query.rs +++ b/taos/src/query.rs @@ -16,7 +16,7 @@ pub enum TaosInner { Ws(taos_ws::Taos), } -enum ResultSetInner { +pub enum ResultSetInner { Native(crate::sys::ResultSet), Ws(taos_ws::ResultSet), } @@ -38,7 +38,7 @@ impl Taos { matches!(&self.0, TaosInner::Ws(_)) } } -pub struct ResultSet(ResultSetInner); +pub struct ResultSet(pub(super) ResultSetInner); impl taos_query::TBuilder for TaosBuilder { type Target = Taos; diff --git a/taos/src/stmt2.rs b/taos/src/stmt2.rs new file mode 100644 index 00000000..4fda32e0 --- /dev/null +++ b/taos/src/stmt2.rs @@ -0,0 +1,700 @@ +use taos_query::stmt2::Stmt2BindParam; +use taos_query::RawResult; +use taos_ws::Stmt2 as WsStmt2; + +use crate::{ResultSet, ResultSetInner, TaosInner}; + +#[derive(Debug)] +enum Stmt2Inner { + Ws(WsStmt2), +} + +#[derive(Debug)] +pub struct Stmt2(Stmt2Inner); + +impl taos_query::stmt2::Stmt2Bindable for Stmt2 { + fn init(taos: &super::Taos) -> RawResult { + match &taos.0 { + TaosInner::Native(_) => todo!(), + TaosInner::Ws(taos) => WsStmt2::init(taos) + .map(Stmt2Inner::Ws) + .map(Stmt2) + .map_err(Into::into), + } + } + + fn prepare(&mut self, sql: &str) -> RawResult<&mut Self> { + match &mut self.0 { + Stmt2Inner::Ws(stmt2) => { + stmt2.prepare(sql)?; + } + } + Ok(self) + } + + fn bind(&mut self, params: &[Stmt2BindParam]) -> RawResult<&mut Self> { + match &mut self.0 { + Stmt2Inner::Ws(stmt2) => { + stmt2.bind(params)?; + } + } + Ok(self) + } + + fn exec(&mut self) -> RawResult { + match &mut self.0 { + Stmt2Inner::Ws(stmt2) => Ok(stmt2.exec()?), + } + } + + fn affected_rows(&self) -> usize { + match &self.0 { + Stmt2Inner::Ws(stmt2) => stmt2.affected_rows(), + } + } + + fn result_set(&self) -> RawResult { + match &self.0 { + Stmt2Inner::Ws(stmt2) => stmt2 + .result_set() + .map(ResultSetInner::Ws) + .map(ResultSet) + .map_err(Into::into), + } + } +} + +#[async_trait::async_trait] +impl taos_query::stmt2::Stmt2AsyncBindable for Stmt2 { + async fn init(taos: &super::Taos) -> RawResult { + match &taos.0 { + TaosInner::Native(_) => todo!(), + TaosInner::Ws(taos) => WsStmt2::init(taos) + .await + .map(Stmt2Inner::Ws) + .map(Stmt2) + .map_err(Into::into), + } + } + + async fn prepare(&mut self, sql: &str) -> RawResult<&mut Self> { + match &mut self.0 { + Stmt2Inner::Ws(stmt2) => { + stmt2.prepare(sql).await?; + } + } + Ok(self) + } + + async fn bind(&mut self, params: &[Stmt2BindParam]) -> RawResult<&mut Self> { + match &mut self.0 { + Stmt2Inner::Ws(stmt2) => { + stmt2.bind(params).await?; + } + } + Ok(self) + } + + async fn exec(&mut self) -> RawResult { + match &mut self.0 { + Stmt2Inner::Ws(stmt2) => Ok(stmt2.exec().await?), + } + } + + async fn affected_rows(&self) -> usize { + match &self.0 { + Stmt2Inner::Ws(stmt2) => stmt2.affected_rows().await, + } + } + + async fn result_set(&self) -> RawResult { + match &self.0 { + Stmt2Inner::Ws(stmt2) => stmt2 + .result_set() + .await + .map(ResultSetInner::Ws) + .map(ResultSet) + .map_err(Into::into), + } + } +} + +#[cfg(test)] +mod tests { + use serde::Deserialize; + use taos_query::common::ColumnView; + use taos_query::stmt2::{Stmt2BindParam, Stmt2Bindable}; + use taos_query::{Queryable, TBuilder}; + + use super::Stmt2; + use crate::sync::*; + use crate::TaosBuilder; + + #[test] + fn test_stmt2_insert_single_row() -> anyhow::Result<()> { + let db = "stmt2_202411231337"; + let dsn = "ws://localhost:6041"; + + let taos = TaosBuilder::from_dsn(dsn)?.build()?; + taos.exec_many([ + &format!("drop database if exists {db}"), + &format!("create database {db} keep 36500"), + &format!("use {db}"), + "create table t0 (ts timestamp, c1 bool, c2 tinyint, c3 smallint, c4 int, c5 bigint, + c6 tinyint unsigned, c7 smallint unsigned, c8 int unsigned, c9 bigint unsigned, + c10 float, c11 double, c12 varchar(100), c13 nchar(100))", + ])?; + + let mut stmt2 = Stmt2::init(&taos)?; + stmt2.prepare("insert into t0 values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")?; + + let cols = vec![ + ColumnView::from_millis_timestamp(vec![1726803356466]), + ColumnView::from_bools(vec![true]), + ColumnView::from_tiny_ints(vec![None]), + ColumnView::from_small_ints(vec![i16::MAX]), + ColumnView::from_ints(vec![i32::MAX]), + ColumnView::from_big_ints(vec![i64::MAX]), + ColumnView::from_unsigned_tiny_ints(vec![u8::MAX]), + ColumnView::from_unsigned_small_ints(vec![u16::MAX]), + ColumnView::from_unsigned_ints(vec![u32::MAX]), + ColumnView::from_unsigned_big_ints(vec![u64::MAX]), + ColumnView::from_floats(vec![f32::MAX]), + ColumnView::from_doubles(vec![f64::MAX]), + ColumnView::from_varchar(vec!["hello"]), + ColumnView::from_nchar(vec!["中文"]), + ]; + + let param = Stmt2BindParam::new(None, None, Some(cols)); + let affected = stmt2.bind(&[param])?.exec()?; + assert_eq!(affected, 1); + assert_eq!(stmt2.affected_rows(), 1); + + #[derive(Debug, Deserialize)] + struct Row { + ts: i64, + c1: bool, + c2: Option, + c3: i16, + c4: i32, + c5: i64, + c6: u8, + c7: u16, + c8: u32, + c9: u64, + c10: Option, + c11: f64, + c12: String, + c13: String, + } + + let rows: Vec = taos + .query("select * from t0")? + .deserialize() + .try_collect()?; + + assert_eq!(rows.len(), 1); + + let row = &rows[0]; + + assert_eq!(row.ts, 1726803356466); + assert_eq!(row.c1, true); + assert_eq!(row.c2, None); + assert_eq!(row.c3, i16::MAX); + assert_eq!(row.c4, i32::MAX); + assert_eq!(row.c5, i64::MAX); + assert_eq!(row.c6, u8::MAX); + assert_eq!(row.c7, u16::MAX); + assert_eq!(row.c8, u32::MAX); + assert_eq!(row.c9, u64::MAX); + assert_eq!(row.c10, Some(f32::MAX)); + assert_eq!(row.c11, f64::MAX); + assert_eq!(row.c12, "hello"); + assert_eq!(row.c13, "中文"); + + taos.exec(format!("drop database {db}"))?; + + Ok(()) + } + + #[test] + fn test_stmt2_insert_multi_row() -> anyhow::Result<()> { + let db = "stmt2_202411222151"; + let dsn = "ws://localhost:6041"; + + let taos = TaosBuilder::from_dsn(dsn)?.build()?; + taos.exec_many(vec![ + &format!("drop database if exists {db}"), + &format!("create database {db}"), + &format!("use {db}"), + "create table t0 (ts timestamp, c1 int)", + ])?; + + let mut stmt2 = Stmt2::init(&taos)?; + stmt2.prepare("insert into t0 values(?, ?)")?; + + let cols = vec![ + ColumnView::from_millis_timestamp(vec![ + 1726803356466, + 1726803357466, + 1726803358466, + 1726803359466, + ]), + ColumnView::from_ints(vec![99, 100, 101, 102]), + ]; + + let param = Stmt2BindParam::new(None, None, Some(cols)); + let affected = stmt2.bind(&[param])?.exec()?; + assert_eq!(affected, 4); + assert_eq!(stmt2.affected_rows(), 4); + + #[derive(Debug, Deserialize)] + struct Row { + ts: i64, + c1: i32, + } + + let rows: Vec = taos + .query("select * from t0")? + .deserialize() + .try_collect()?; + + assert_eq!(rows.len(), 4); + + assert_eq!(rows[0].ts, 1726803356466); + assert_eq!(rows[1].ts, 1726803357466); + assert_eq!(rows[2].ts, 1726803358466); + assert_eq!(rows[3].ts, 1726803359466); + + assert_eq!(rows[0].c1, 99); + assert_eq!(rows[1].c1, 100); + assert_eq!(rows[2].c1, 101); + assert_eq!(rows[3].c1, 102); + + taos.exec(format!("drop database {db}"))?; + + Ok(()) + } + + #[test] + fn test_stmt2_query_single_row() -> anyhow::Result<()> { + let db = "stmt2_202411281747"; + let dsn = "ws://localhost:6041"; + + let taos = TaosBuilder::from_dsn(dsn)?.build()?; + taos.exec_many(vec![ + &format!("drop database if exists {db}"), + &format!("create database {db}"), + &format!("use {db}"), + "create table t0 (ts timestamp, c1 bool, c2 tinyint, c3 smallint, c4 int, c5 bigint, + c6 tinyint unsigned, c7 smallint unsigned, c8 int unsigned, c9 bigint unsigned, + c10 float, c11 double, c12 varchar(100), c13 nchar(100))", + &format!( + "insert into t0 values(1726803356466, 1, NULL, 2, 3, 4, 5, 6, 7, 8, 1.1, 2.2, 'hello', '中文')" + ), + ])?; + + let mut stmt2 = Stmt2::init(&taos)?; + stmt2.prepare("select * from t0 where c8 > ? and c10 > ? and c12 = ?")?; + + let cols = vec![ + ColumnView::from_ints(vec![0]), + ColumnView::from_floats(vec![0f32]), + ColumnView::from_varchar(vec!["hello"]), + ]; + + let param = Stmt2BindParam::new(None, None, Some(cols)); + let affected = stmt2.bind(&[param])?.exec()?; + assert_eq!(affected, 0); + assert_eq!(stmt2.affected_rows(), 0); + + #[derive(Debug, Deserialize)] + struct Row { + ts: i64, + c1: bool, + c2: Option, + c3: i16, + c4: i32, + c5: i64, + c6: u8, + c7: u16, + c8: u32, + c9: u64, + c10: Option, + c11: f64, + c12: String, + c13: String, + } + + let rows: Vec = stmt2.result_set()?.deserialize().try_collect()?; + assert_eq!(rows.len(), 1); + + let row = &rows[0]; + assert_eq!(row.ts, 1726803356466); + assert_eq!(row.c1, true); + assert_eq!(row.c2, None); + assert_eq!(row.c3, 2); + assert_eq!(row.c4, 3); + assert_eq!(row.c5, 4); + assert_eq!(row.c6, 5); + assert_eq!(row.c7, 6); + assert_eq!(row.c8, 7); + assert_eq!(row.c9, 8); + assert_eq!(row.c10, Some(1.1)); + assert_eq!(row.c11, 2.2); + assert_eq!(row.c12, "hello"); + assert_eq!(row.c13, "中文"); + + taos.exec(format!("drop database {db}"))?; + + Ok(()) + } + + #[test] + fn test_stmt2_query_multi_row() -> anyhow::Result<()> { + let db = "stmt2_202411222202"; + let dsn = "ws://localhost:6041"; + + let taos = TaosBuilder::from_dsn(dsn)?.build()?; + taos.exec_many(vec![ + &format!("drop database if exists {db}"), + &format!("create database {db}"), + &format!("use {db}"), + "create table t0 (ts timestamp, c1 int)", + "insert into t0 values(1726803356466, 99)", + "insert into t0 values(1726803357466, 100)", + "insert into t0 values(1726803358466, 101)", + "insert into t0 values(1726803359466, 102)", + ])?; + + let mut stmt2 = Stmt2::init(&taos)?; + stmt2.prepare("select * from t0 where c1 > ?")?; + + let cols = vec![ColumnView::from_ints(vec![100])]; + let param = Stmt2BindParam::new(None, None, Some(cols)); + let affected = stmt2.bind(&[param])?.exec()?; + assert_eq!(affected, 0); + assert_eq!(stmt2.affected_rows(), 0); + + #[derive(Debug, Deserialize)] + struct Row { + ts: i64, + c1: i32, + } + + let rows: Vec = stmt2.result_set()?.deserialize().try_collect()?; + assert_eq!(rows.len(), 2); + + assert_eq!(rows[0].ts, 1726803358466); + assert_eq!(rows[1].ts, 1726803359466); + + assert_eq!(rows[0].c1, 101); + assert_eq!(rows[1].c1, 102); + + taos.exec(format!("drop database {db}"))?; + + Ok(()) + } +} + +#[cfg(test)] +mod async_tests { + use serde::Deserialize; + use taos_query::common::ColumnView; + use taos_query::stmt2::{Stmt2AsyncBindable, Stmt2BindParam}; + use taos_query::{AsyncQueryable, AsyncTBuilder}; + + use super::Stmt2; + use crate::*; + + #[tokio::test(flavor = "multi_thread")] + async fn test_stmt2_insert_single_row() -> anyhow::Result<()> { + let db = "stmt2_202411231450"; + let dsn = "ws://localhost:6041"; + + let taos = TaosBuilder::from_dsn(dsn)?.build().await?; + taos.exec_many([ + &format!("drop database if exists {db}"), + &format!("create database {db} keep 36500"), + &format!("use {db}"), + "create table t0 (ts timestamp, c1 bool, c2 tinyint, c3 smallint, c4 int, c5 bigint, + c6 tinyint unsigned, c7 smallint unsigned, c8 int unsigned, c9 bigint unsigned, + c10 float, c11 double, c12 varchar(100), c13 nchar(100))", + ]) + .await?; + + let mut stmt2 = Stmt2::init(&taos).await?; + stmt2 + .prepare("insert into t0 values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") + .await?; + + let cols = vec![ + ColumnView::from_millis_timestamp(vec![1726803356466]), + ColumnView::from_bools(vec![true]), + ColumnView::from_tiny_ints(vec![None]), + ColumnView::from_small_ints(vec![i16::MAX]), + ColumnView::from_ints(vec![i32::MAX]), + ColumnView::from_big_ints(vec![i64::MAX]), + ColumnView::from_unsigned_tiny_ints(vec![u8::MAX]), + ColumnView::from_unsigned_small_ints(vec![u16::MAX]), + ColumnView::from_unsigned_ints(vec![u32::MAX]), + ColumnView::from_unsigned_big_ints(vec![u64::MAX]), + ColumnView::from_floats(vec![f32::MAX]), + ColumnView::from_doubles(vec![f64::MAX]), + ColumnView::from_varchar(vec!["hello"]), + ColumnView::from_nchar(vec!["中文"]), + ]; + + let param = Stmt2BindParam::new(None, None, Some(cols)); + let affected = stmt2.bind(&[param]).await?.exec().await?; + assert_eq!(affected, 1); + assert_eq!(stmt2.affected_rows().await, 1); + + #[derive(Debug, Deserialize)] + struct Row { + ts: u64, + c1: bool, + c2: Option, + c3: i16, + c4: i32, + c5: i64, + c6: u8, + c7: u16, + c8: u32, + c9: u64, + c10: Option, + c11: f64, + c12: String, + c13: String, + } + + let rows: Vec = taos + .query("select * from t0") + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(rows.len(), 1); + + let row = &rows[0]; + + assert_eq!(row.ts, 1726803356466); + assert_eq!(row.c1, true); + assert_eq!(row.c2, None); + assert_eq!(row.c3, i16::MAX); + assert_eq!(row.c4, i32::MAX); + assert_eq!(row.c5, i64::MAX); + assert_eq!(row.c6, u8::MAX); + assert_eq!(row.c7, u16::MAX); + assert_eq!(row.c8, u32::MAX); + assert_eq!(row.c9, u64::MAX); + assert_eq!(row.c10, Some(f32::MAX)); + assert_eq!(row.c11, f64::MAX); + assert_eq!(row.c12, "hello"); + assert_eq!(row.c13, "中文"); + + taos.exec(format!("drop database {db}")).await?; + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_stmt2_insert_multi_row() -> anyhow::Result<()> { + let db = "stmt2_202411222208"; + let dsn = "ws://localhost:6041"; + + let taos = TaosBuilder::from_dsn(dsn)?.build().await?; + taos.exec_many(vec![ + &format!("drop database if exists {db}"), + &format!("create database {db}"), + &format!("use {db}"), + "create table t0 (ts timestamp, c1 int)", + ]) + .await?; + + let mut stmt2 = Stmt2::init(&taos).await?; + stmt2.prepare("insert into t0 values(?, ?)").await?; + + let cols = vec![ + ColumnView::from_millis_timestamp(vec![ + 1726803356466, + 1726803357466, + 1726803358466, + 1726803359466, + ]), + ColumnView::from_ints(vec![99, 100, 101, 102]), + ]; + + let param = Stmt2BindParam::new(None, None, Some(cols)); + let affected = stmt2.bind(&[param]).await?.exec().await?; + assert_eq!(affected, 4); + assert_eq!(stmt2.affected_rows().await, 4); + + #[derive(Debug, Deserialize)] + struct Row { + ts: i64, + c1: i32, + } + + let rows: Vec = taos + .query("select * from t0") + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(rows.len(), 4); + + assert_eq!(rows[0].ts, 1726803356466); + assert_eq!(rows[1].ts, 1726803357466); + assert_eq!(rows[2].ts, 1726803358466); + assert_eq!(rows[3].ts, 1726803359466); + + assert_eq!(rows[0].c1, 99); + assert_eq!(rows[1].c1, 100); + assert_eq!(rows[2].c1, 101); + assert_eq!(rows[3].c1, 102); + + taos.exec(format!("drop database {db}")).await?; + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_stmt2_query_single_row() -> anyhow::Result<()> { + let db = "stmt2_202411281740"; + let dsn = "ws://localhost:6041"; + + let taos = TaosBuilder::from_dsn(dsn)?.build().await?; + taos.exec_many(vec![ + &format!("drop database if exists {db}"), + &format!("create database {db}"), + &format!("use {db}"), + "create table t0 (ts timestamp, c1 bool, c2 tinyint, c3 smallint, c4 int, c5 bigint, + c6 tinyint unsigned, c7 smallint unsigned, c8 int unsigned, c9 bigint unsigned, + c10 float, c11 double, c12 varchar(100), c13 nchar(100))", + &format!( + "insert into t0 values(1726803356466, 1, NULL, 2, 3, 4, 5, 6, 7, 8, 1.1, 2.2, 'hello', '中文')" + ), + ]) + .await?; + + let mut stmt2 = Stmt2::init(&taos).await?; + stmt2 + .prepare("select * from t0 where c8 > ? and c10 > ? and c12 = ?") + .await?; + + let cols = vec![ + ColumnView::from_ints(vec![0]), + ColumnView::from_floats(vec![0f32]), + ColumnView::from_varchar(vec!["hello"]), + ]; + + let param = Stmt2BindParam::new(None, None, Some(cols)); + let affected = stmt2.bind(&[param]).await?.exec().await?; + assert_eq!(affected, 0); + assert_eq!(stmt2.affected_rows().await, 0); + + #[derive(Debug, Deserialize)] + struct Row { + ts: i64, + c1: bool, + c2: Option, + c3: i16, + c4: i32, + c5: i64, + c6: u8, + c7: u16, + c8: u32, + c9: u64, + c10: Option, + c11: f64, + c12: String, + c13: String, + } + + let rows: Vec = stmt2 + .result_set() + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(rows.len(), 1); + + let row = &rows[0]; + + assert_eq!(row.ts, 1726803356466); + assert_eq!(row.c1, true); + assert_eq!(row.c2, None); + assert_eq!(row.c3, 2); + assert_eq!(row.c4, 3); + assert_eq!(row.c5, 4); + assert_eq!(row.c6, 5); + assert_eq!(row.c7, 6); + assert_eq!(row.c8, 7); + assert_eq!(row.c9, 8); + assert_eq!(row.c10, Some(1.1)); + assert_eq!(row.c11, 2.2); + assert_eq!(row.c12, "hello"); + assert_eq!(row.c13, "中文"); + + taos.exec(format!("drop database {db}")).await?; + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_stmt2_query_multi_row() -> anyhow::Result<()> { + let db = "stmt2_202411222213"; + let dsn = "ws://localhost:6041"; + + let taos = TaosBuilder::from_dsn(dsn)?.build().await?; + taos.exec_many(vec![ + &format!("drop database if exists {db}"), + &format!("create database {db}"), + &format!("use {db}"), + "create table t0 (ts timestamp, c1 int)", + "insert into t0 values(1726803356466, 99)", + "insert into t0 values(1726803357466, 100)", + "insert into t0 values(1726803358466, 101)", + "insert into t0 values(1726803359466, 102)", + ]) + .await?; + + let mut stmt2 = Stmt2::init(&taos).await?; + stmt2.prepare("select * from t0 where c1 > ?").await?; + + let cols = vec![ColumnView::from_ints(vec![100])]; + let param = Stmt2BindParam::new(None, None, Some(cols)); + let affected = stmt2.bind(&[param]).await?.exec().await?; + assert_eq!(affected, 0); + assert_eq!(stmt2.affected_rows().await, 0); + + #[derive(Debug, Deserialize)] + struct Row { + ts: i64, + c1: i32, + } + + let rows: Vec = stmt2 + .result_set() + .await? + .deserialize() + .try_collect() + .await?; + + assert_eq!(rows.len(), 2); + + assert_eq!(rows[0].ts, 1726803358466); + assert_eq!(rows[1].ts, 1726803359466); + + assert_eq!(rows[0].c1, 101); + assert_eq!(rows[1].c1, 102); + + taos.exec(format!("drop database {db}")).await?; + + Ok(()) + } +}