use crate::{
DataType, Error,
buffers::Resize,
columnar_bulk_inserter::BoundInputSlice,
error::TooLargeBufferSize,
handles::{
ASSUMED_MAX_LENGTH_OF_VARCHAR, ASSUMED_MAX_LENGTH_OF_W_VARCHAR, CData, CDataMut,
HasDataType, Statement, StatementRef,
},
};
use super::{ColumnBuffer, Indicator};
use log::trace;
use odbc_sys::{CDataType, NULL_DATA};
use std::{cmp::min, ffi::c_void, mem::size_of, num::NonZeroUsize, panic};
use widestring::U16Str;
pub type CharColumn = TextColumn<u8>;
pub type WCharColumn = TextColumn<u16>;
#[derive(Debug)]
pub struct TextColumn<C> {
max_str_len: usize,
values: Vec<C>,
indicators: Vec<isize>,
}
impl<C> TextColumn<C> {
pub fn try_new(batch_size: usize, max_str_len: usize) -> Result<Self, TooLargeBufferSize>
where
C: Default + Copy,
{
let element_size = max_str_len + 1;
let len = element_size * batch_size;
let mut values = Vec::new();
values
.try_reserve_exact(len)
.map_err(|_| TooLargeBufferSize {
num_elements: batch_size,
element_size: element_size * size_of::<C>(),
})?;
values.resize(len, C::default());
Ok(TextColumn {
max_str_len,
values,
indicators: vec![0; batch_size],
})
}
pub fn new(batch_size: usize, max_str_len: usize) -> Self
where
C: Default + Copy,
{
let element_size = max_str_len + 1;
let len = element_size * batch_size;
let mut values = Vec::new();
values.reserve_exact(len);
values.resize(len, C::default());
TextColumn {
max_str_len,
values,
indicators: vec![NULL_DATA; batch_size],
}
}
pub fn value_at(&self, row_index: usize) -> Option<&[C]> {
self.content_length_at(row_index).map(|length| {
let offset = row_index * (self.max_str_len + 1);
&self.values[offset..offset + length]
})
}
pub fn max_len(&self) -> usize {
self.max_str_len
}
pub fn indicator_at(&self, row_index: usize) -> Indicator {
Indicator::from_isize(self.indicators[row_index])
}
pub fn content_length_at(&self, row_index: usize) -> Option<usize> {
match self.indicator_at(row_index) {
Indicator::Null => None,
Indicator::NoTotal => Some(self.max_str_len),
Indicator::Length(length_in_bytes) => {
let length_in_chars = length_in_bytes / size_of::<C>();
let length = min(self.max_str_len, length_in_chars);
Some(length)
}
}
}
pub fn has_truncated_values(&self, num_rows: usize) -> Option<Indicator> {
let max_bin_length = self.max_str_len * size_of::<C>();
self.indicators
.iter()
.copied()
.take(num_rows)
.find_map(|indicator| {
let indicator = Indicator::from_isize(indicator);
indicator.is_truncated(max_bin_length).then_some(indicator)
})
}
pub fn resize_max_str(&mut self, new_max_str_len: usize, num_rows: usize)
where
C: Default + Copy,
{
#[cfg(not(feature = "structured_logging"))]
trace!(
"Rebinding text column buffer with {} elements. Maximum string length {} => {}",
num_rows, self.max_str_len, new_max_str_len
);
#[cfg(feature = "structured_logging")]
trace!(
target: "odbc_api",
num_rows = num_rows,
old_max_str_len = self.max_str_len,
new_max_str_len = new_max_str_len;
"Text column buffer resized"
);
let batch_size = self.indicators.len();
let mut new_values = vec![C::default(); (new_max_str_len + 1) * batch_size];
let max_copy_length = min(self.max_str_len, new_max_str_len);
for ((&indicator, old_value), new_value) in self
.indicators
.iter()
.zip(self.values.chunks_exact_mut(self.max_str_len + 1))
.zip(new_values.chunks_exact_mut(new_max_str_len + 1))
.take(num_rows)
{
match Indicator::from_isize(indicator) {
Indicator::Null => (),
Indicator::NoTotal => {
new_value[..max_copy_length].clone_from_slice(&old_value[..max_copy_length]);
}
Indicator::Length(num_bytes_len) => {
let num_bytes_to_copy = min(num_bytes_len / size_of::<C>(), max_copy_length);
new_value[..num_bytes_to_copy].copy_from_slice(&old_value[..num_bytes_to_copy]);
}
}
}
self.values = new_values;
self.max_str_len = new_max_str_len;
}
pub fn set_value(&mut self, index: usize, input: Option<&[C]>)
where
C: Default + Copy,
{
if let Some(input) = input {
self.set_mut(index, input.len()).copy_from_slice(input);
} else {
self.indicators[index] = NULL_DATA;
}
}
pub fn set_mut(&mut self, index: usize, length: usize) -> &mut [C]
where
C: Default,
{
if length > self.max_str_len {
panic!(
"Tried to insert a value into a text buffer which is larger than the maximum \
allowed string length for the buffer."
);
}
self.indicators[index] = (length * size_of::<C>()).try_into().unwrap();
let start = (self.max_str_len + 1) * index;
let end = start + length;
self.values[end] = C::default();
&mut self.values[start..end]
}
pub fn fill_null(&mut self, from: usize, to: usize) {
for index in from..to {
self.indicators[index] = NULL_DATA;
}
}
pub fn raw_value_buffer(&self, num_valid_rows: usize) -> &[C] {
&self.values[..(self.max_str_len + 1) * num_valid_rows]
}
pub fn row_capacity(&self) -> usize {
self.values.len()
}
}
impl WCharColumn {
pub unsafe fn ustr_at(&self, row_index: usize) -> Option<&U16Str> {
self.value_at(row_index).map(U16Str::from_slice)
}
}
unsafe impl<C: 'static> ColumnBuffer for TextColumn<C>
where
TextColumn<C>: CDataMut + HasDataType,
{
type View<'a> = TextColumnView<'a, C>;
fn view(&self, valid_rows: usize) -> TextColumnView<'_, C> {
TextColumnView {
num_rows: valid_rows,
col: self,
}
}
fn capacity(&self) -> usize {
self.indicators.len()
}
fn has_truncated_values(&self, num_rows: usize) -> Option<Indicator> {
let max_bin_length = self.max_str_len * size_of::<C>();
self.indicators
.iter()
.copied()
.take(num_rows)
.find_map(|indicator| {
let indicator = Indicator::from_isize(indicator);
indicator.is_truncated(max_bin_length).then_some(indicator)
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct TextColumnView<'c, C> {
num_rows: usize,
col: &'c TextColumn<C>,
}
impl<'c, C> TextColumnView<'c, C> {
pub fn len(&self) -> usize {
self.num_rows
}
pub fn is_empty(&self) -> bool {
self.num_rows == 0
}
pub fn get(&self, index: usize) -> Option<&'c [C]> {
self.col.value_at(index)
}
pub fn iter(&self) -> TextColumnIt<'c, C> {
TextColumnIt {
pos: 0,
num_rows: self.num_rows,
col: self.col,
}
}
pub fn content_length_at(&self, row_index: usize) -> Option<usize> {
if row_index >= self.num_rows {
panic!("Row index points beyond the range of valid values.")
}
self.col.content_length_at(row_index)
}
pub fn raw_value_buffer(&self) -> &'c [C] {
self.col.raw_value_buffer(self.num_rows)
}
pub fn max_len(&self) -> usize {
self.col.max_len()
}
pub fn has_truncated_values(&self) -> Option<Indicator> {
self.col.has_truncated_values(self.num_rows)
}
}
unsafe impl<'a, C: 'static> BoundInputSlice<'a> for TextColumn<C> {
type SliceMut = TextColumnSliceMut<'a, C>;
unsafe fn as_view_mut(
&'a mut self,
parameter_index: u16,
stmt: StatementRef<'a>,
) -> Self::SliceMut {
TextColumnSliceMut {
column: self,
stmt,
parameter_index,
}
}
}
pub struct TextColumnSliceMut<'a, C> {
column: &'a mut TextColumn<C>,
stmt: StatementRef<'a>,
parameter_index: u16,
}
impl<C> TextColumnSliceMut<'_, C>
where
C: Default + Copy + Send,
{
pub fn set_cell(&mut self, row_index: usize, element: Option<&[C]>) {
self.column.set_value(row_index, element)
}
pub fn ensure_max_element_length(
&mut self,
element_length: usize,
num_rows_to_copy: usize,
) -> Result<(), Error>
where
TextColumn<C>: HasDataType + CData,
{
if element_length > self.column.max_len() {
let new_max_str_len = element_length;
self.column
.resize_max_str(new_max_str_len, num_rows_to_copy);
unsafe {
self.stmt
.bind_input_parameter(self.parameter_index, self.column)
.into_result(&self.stmt)?
}
}
Ok(())
}
pub fn set_mut(&mut self, index: usize, length: usize) -> &mut [C] {
self.column.set_mut(index, length)
}
}
#[derive(Debug)]
pub struct TextColumnIt<'c, C> {
pos: usize,
num_rows: usize,
col: &'c TextColumn<C>,
}
impl<'c, C> TextColumnIt<'c, C> {
fn next_impl(&mut self) -> Option<Option<&'c [C]>> {
if self.pos == self.num_rows {
None
} else {
let ret = Some(self.col.value_at(self.pos));
self.pos += 1;
ret
}
}
}
impl<'c> Iterator for TextColumnIt<'c, u8> {
type Item = Option<&'c [u8]>;
fn next(&mut self) -> Option<Self::Item> {
self.next_impl()
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.num_rows - self.pos;
(len, Some(len))
}
}
impl ExactSizeIterator for TextColumnIt<'_, u8> {}
impl<'c> Iterator for TextColumnIt<'c, u16> {
type Item = Option<&'c U16Str>;
fn next(&mut self) -> Option<Self::Item> {
self.next_impl().map(|opt| opt.map(U16Str::from_slice))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.num_rows - self.pos;
(len, Some(len))
}
}
impl ExactSizeIterator for TextColumnIt<'_, u16> {}
unsafe impl CData for CharColumn {
fn cdata_type(&self) -> CDataType {
CDataType::Char
}
fn indicator_ptr(&self) -> *const isize {
self.indicators.as_ptr()
}
fn value_ptr(&self) -> *const c_void {
self.values.as_ptr() as *const c_void
}
fn buffer_length(&self) -> isize {
(self.max_str_len + 1).try_into().unwrap()
}
}
unsafe impl CDataMut for CharColumn {
fn mut_indicator_ptr(&mut self) -> *mut isize {
self.indicators.as_mut_ptr()
}
fn mut_value_ptr(&mut self) -> *mut c_void {
self.values.as_mut_ptr() as *mut c_void
}
}
impl HasDataType for CharColumn {
fn data_type(&self) -> DataType {
if self.max_str_len <= ASSUMED_MAX_LENGTH_OF_VARCHAR {
DataType::Varchar {
length: NonZeroUsize::new(self.max_str_len),
}
} else {
DataType::LongVarchar {
length: NonZeroUsize::new(self.max_str_len),
}
}
}
}
unsafe impl CData for WCharColumn {
fn cdata_type(&self) -> CDataType {
CDataType::WChar
}
fn indicator_ptr(&self) -> *const isize {
self.indicators.as_ptr()
}
fn value_ptr(&self) -> *const c_void {
self.values.as_ptr() as *const c_void
}
fn buffer_length(&self) -> isize {
((self.max_str_len + 1) * 2).try_into().unwrap()
}
}
unsafe impl CDataMut for WCharColumn {
fn mut_indicator_ptr(&mut self) -> *mut isize {
self.indicators.as_mut_ptr()
}
fn mut_value_ptr(&mut self) -> *mut c_void {
self.values.as_mut_ptr() as *mut c_void
}
}
impl HasDataType for WCharColumn {
fn data_type(&self) -> DataType {
if self.max_str_len <= ASSUMED_MAX_LENGTH_OF_W_VARCHAR {
DataType::WVarchar {
length: NonZeroUsize::new(self.max_str_len),
}
} else {
DataType::WLongVarchar {
length: NonZeroUsize::new(self.max_str_len),
}
}
}
}
impl<C> Resize for TextColumn<C>
where
C: Clone + Default,
{
fn resize(&mut self, new_capacity: usize) {
self.values
.resize((self.max_str_len + 1) * new_capacity, C::default());
self.indicators.resize(new_capacity, NULL_DATA);
}
}
#[cfg(test)]
mod tests {
use crate::buffers::{Resize, TextColumn};
#[test]
fn resize_text_column_buffer() {
let mut col = TextColumn::<u8>::new(2, 10);
col.set_value(0, Some(b"Hello"));
col.set_value(1, Some(b"World"));
col.resize(3);
assert_eq!(col.value_at(0), Some(b"Hello".as_ref()));
assert_eq!(col.value_at(1), Some(b"World".as_ref()));
assert_eq!(col.value_at(2), None);
}
}