taos-query 0.12.4

Driver for TDengine - a timeseries database and analysis platform
Documentation
mod inner;

use std::io::Write;

use super::{BorrowedValue, Column, Field, Precision};

use crate::{util::*, BlockExt};

pub use inner::*;

pub mod raw;
pub use raw::Raw;

use bitflags::bitflags;
use once_cell::unsync::OnceCell;

bitflags! {
    #[derive(Default)]
    #[repr(transparent)]
    struct Fx: u8 {
        /// Has database name in the block.
        const HAS_DB = 0b00000001;
        /// Has table name in the block
        const HAS_TB = 0b00000010;
        /// Has fields data in the block
        const HAS_FL = 0b00000100;
        /// Has precision set.
        const HAS_PC = 0b00001000;
        /// Precision set as 'ms'(milliseconds).
        const IS_MS  = 0b00001000;
        /// Precision set as 'us'(microseconds).
        const IS_US  = 0b00011000;
        /// Precision set as 'ns'(nanoseconds).
        const IS_NS  = 0b00101000;
    }
}

#[derive(Debug)]
pub struct Block {
    database: Option<String>,
    table: Option<String>,
    fields: Option<Vec<Field>>,
    raw: OnceCell<RawBlock>,
    precision: Precision,
    columns: OnceCell<Vec<Column>>,
}

impl Inlinable for Block {
    #[inline]
    fn read_inlined<R: std::io::Read>(mut reader: R) -> std::io::Result<Self> {
        let flags = Fx::from_bits(reader.read_u8()?).unwrap();
        let database = if flags.contains(Fx::HAS_DB) {
            Some(reader.read_inlined_str::<2>()?)
        } else {
            None
        };
        let table = if flags.contains(Fx::HAS_DB) {
            Some(reader.read_inlined_str::<2>()?)
        } else {
            None
        };
        use itertools::Itertools;
        let fields = if flags.contains(Fx::HAS_FL) {
            Some(
                (0..reader.read_u16()?)
                    .map(|_| reader.read_inlinable::<Field>())
                    .try_collect()?,
            )
        } else {
            None
        };

        let precision = if flags.contains(Fx::IS_MS) {
            Precision::Millisecond
        } else if flags.contains(Fx::IS_US) {
            Precision::Microsecond
        } else if flags.contains(Fx::IS_NS) {
            Precision::Nanosecond
        } else {
            Precision::Millisecond
        };

        let rows = reader.read_u32()?;
        let cols = reader.read_u32()?;

        let bytes = reader.read_inlined_bytes::<4>()?;
        let raw = OnceCell::new();
        raw.try_insert(RawBlock::from_bytes(
            bytes,
            rows as usize,
            cols as usize,
            precision,
        ))
        .unwrap();

        Ok(Self {
            database,
            table,
            fields,
            raw,
            precision,
            columns: OnceCell::new(),
        })
    }

    #[inline]
    fn write_inlined<W: Write>(&self, mut wtr: W) -> std::io::Result<usize> {
        let mut l = wtr.write_u8(self.flags().bits)?;

        if let Some(name) = self.database.as_ref() {
            l += wtr.write_inlined_bytes::<2>(name.as_bytes())?;
        }
        if let Some(name) = self.table.as_ref() {
            l += wtr.write_inlined_bytes::<2>(name.as_bytes())?;
        }
        if let Some(fields) = self.fields.as_ref() {
            l += wtr.write_len_with_width::<2>(fields.len())?;
            for field in fields {
                l += wtr.write_inlinable(field)?;
            }
        }
        let raw = self.as_raw_block();
        l += wtr.write_len_with_width::<4>(raw.nrows())?;
        l += wtr.write_len_with_width::<4>(raw.ncols())?;
        l += wtr.write_inlined_bytes::<4>(raw.as_bytes())?;
        Ok(l)
    }
}

impl Block {
    #[inline]
    pub fn fields(&self) -> &[Field] {
        const FIELDS: [Field; 0] = [];
        if let Some(f) = self.fields.as_ref() {
            &f
        } else {
            &FIELDS
        }
    }

    #[inline]
    pub const fn has_database_name(&self) -> bool {
        self.database.is_some()
    }
    #[inline]
    pub const fn has_table_name(&self) -> bool {
        self.table.is_some()
    }

    #[inline]
    pub fn with_database_name(&mut self, database: &str) -> &mut Self {
        self.database = Some(database.to_string());
        self
    }

    #[inline]
    pub fn with_table_name(&mut self, table: &str) -> &mut Self {
        self.table = Some(table.to_string());
        self
    }

    #[inline]
    pub fn with_fields(&mut self, fields: Vec<Field>) -> &mut Self {
        assert!(fields.len() > 0);
        self.fields = Some(fields);
        self
    }

    #[inline]
    pub fn from_raw_block(data: RawBlock) -> Self {
        let raw = OnceCell::new();
        raw.try_insert(data).unwrap();
        Self {
            raw,
            database: None,
            table: None,
            fields: None,
            precision: Precision::Millisecond,
            columns: OnceCell::new(),
        }
    }

    #[inline]
    pub fn from_raw_bytes(bytes: Vec<u8>, rows: usize, cols: usize, precision: Precision) -> Self {
        let data = RawBlock::from_bytes(bytes, rows, cols, precision);
        Self::from_raw_block(data)
    }

    #[inline]
    pub fn push_column(&mut self, column: impl Into<Column>) -> &mut Self {
        if let Some(mut v) = self.columns.take() {
            v.push(column.into());
            self.columns.try_insert(v).unwrap();
        } else {
            let v = vec![column.into()];
            self.columns.try_insert(v).unwrap();
        }
        self
    }

    #[inline]
    pub fn as_raw_block(&self) -> &RawBlock {
        self.raw.get_or_init(|| todo!())
    }

    #[inline]
    pub fn to_raw_vec(&self) -> std::io::Result<Vec<u8>> {
        let mut bytes = Vec::new();
        bytes.write_inlinable(self)?;
        Ok(bytes)
    }

    #[inline]
    pub fn nrows(&self) -> usize {
        self.as_raw_block().nrows()
    }

    #[inline]
    pub fn ncols(&self) -> usize {
        self.as_raw_block().ncols()
    }

    #[inline]
    pub const fn precision(&self) -> Precision {
        self.precision
    }

    #[inline]
    pub unsafe fn get_ref_unchecked(&self, row: usize, col: usize) -> BorrowedValue {
        self.as_raw_block().get_ref_unchecked(row, col)
    }

    fn flags(&self) -> Fx {
        let mut flags = Fx::default();
        if self.has_database_name() {
            flags |= Fx::HAS_DB;
        }
        if self.has_table_name() {
            flags |= Fx::HAS_TB;
        }
        if self.fields.is_some() {
            flags |= Fx::HAS_FL;
        }
        flags
    }
}

impl BlockExt for Block {
    fn num_of_rows(&self) -> usize {
        self.nrows()
    }

    fn fields(&self) -> &[Field] {
        self.fields()
    }

    fn precision(&self) -> Precision {
        self.precision()
    }

    fn is_null(&self, row: usize, col: usize) -> bool {
        todo!()
    }

    unsafe fn cell_unchecked(&self, row: usize, col: usize) -> (&Field, BorrowedValue) {
        (
            self.get_field_unchecked(col),
            self.get_ref_unchecked(row, col),
        )
    }

    unsafe fn get_col_unchecked(&self, col: usize) -> super::BorrowedColumn {
        todo!()
    }
}

#[test]
fn inner_block() -> anyhow::Result<()> {
    use crate::common::Precision::Millisecond;
    let bytes = b"5\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x08\x00\x00\x00\x01\x00\x01\x00\x00\x00\x02\x00\x01\x00\x00\x00\x03\x00\x02\x00\x00\x00\x04\x00\x04\x00\x00\x00\x05\x00\x08\x00\x00\x00\x0b\x00\x01\x00\x00\x00\x0c\x00\x02\x00\x00\x00\r\x00\x04\x00\x00\x00\x0e\x00\x08\x00\x00\x00\x06\x00\x04\x00\x00\x00\x07\x00\x08\x00\x00\x00\x08\x00f\x00\x00\x00\n\x00\x92\x01\x00\x00\x10\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x08\x00\x00\x00\x10\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x08\x00\x00\x00\x10\x00\x00\x00\x08\x00\x00\x00\x10\x00\x00\x00\x05\x00\x00\x00\x16\x00\x00\x00\x00\xf2=\xc3u\x81\x01\x00\x00\xdaA\xc3u\x81\x01\x00\x00@\x01\x00@\xff\x00@\xff\xff\x00\x00@\xff\xff\xff\xff\x00\x00\x00\x00@\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00@\x01\x00@\x01\x00\x00\x00@\x01\x00\x00\x00\x00\x00\x00\x00@\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x03\x00abc\x00\x00\x00\x00\xff\xff\xff\xff\x14\x00\x9bm\x00\x00\x1d`\x00\x00\x1e\xd1\x01\x00pe\x00\x00nc\x00\x00\x00\x00\x00\x00";
    let rows = 2;
    let cols = 14;
    let precision = Millisecond;

    let raw = unsafe { RawBlock::from_ptr(bytes.as_ptr(), rows, cols, precision) };

    let mut block = Block::from_raw_block(raw);
    block.with_database_name("abc").with_table_name("n1");

    let a = block.printable_inlined();

    let inlined = block.inlined();

    anyhow::ensure!(
        &inlined[0..18] == b"\x03\x03\x00abc\x02\x00n1\x02\x00\x00\x00\x0e\x00\x00\x00"
    );
    assert!(&inlined[18..] == bytes);

    let block = Block::read_inlined(inlined.as_slice())?;
    let b = block.printable_inlined();

    assert_eq!(a, b);
    Ok(())
}