use crate::error::Error;
use crate::odbc::{Odbc, OdbcBatch, OdbcTypeInfo};
use crate::type_info::TypeInfo;
use crate::value::{Value, ValueRef};
use odbc_api::buffers::{AnyColumnBufferSlice, BufferDesc, NullableSlice};
use odbc_api::handles::CDataMut;
use odbc_api::parameter::CElement;
use odbc_api::{DataType, Nullable};
use std::borrow::Cow;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub enum OdbcValueVec {
TinyInt(Vec<i8>),
SmallInt(Vec<i16>),
Integer(Vec<i32>),
BigInt(Vec<i64>),
Real(Vec<f32>),
Double(Vec<f64>),
Bit(Vec<bool>),
Text(Vec<String>),
Binary(Vec<Vec<u8>>),
Date(Vec<odbc_api::sys::Date>),
Time(Vec<odbc_api::sys::Time>),
Timestamp(Vec<odbc_api::sys::Timestamp>),
}
impl OdbcValueVec {
pub(crate) fn with_capacity_for_type(data_type: DataType, capacity: usize) -> Self {
match data_type {
DataType::TinyInt => OdbcValueVec::TinyInt(Vec::with_capacity(capacity)),
DataType::SmallInt => OdbcValueVec::SmallInt(Vec::with_capacity(capacity)),
DataType::Integer | DataType::BigInt => {
OdbcValueVec::BigInt(Vec::with_capacity(capacity))
}
DataType::Real => OdbcValueVec::Real(Vec::with_capacity(capacity)),
DataType::Float { .. } | DataType::Double => {
OdbcValueVec::Double(Vec::with_capacity(capacity))
}
DataType::Bit => OdbcValueVec::Bit(Vec::with_capacity(capacity)),
DataType::Date => OdbcValueVec::Date(Vec::with_capacity(capacity)),
DataType::Time { .. } => OdbcValueVec::Time(Vec::with_capacity(capacity)),
DataType::Timestamp { .. } => OdbcValueVec::Timestamp(Vec::with_capacity(capacity)),
DataType::Binary { .. }
| DataType::Varbinary { .. }
| DataType::LongVarbinary { .. } => OdbcValueVec::Binary(Vec::with_capacity(capacity)),
_ => OdbcValueVec::Text(Vec::with_capacity(capacity)),
}
}
pub(crate) fn push_from_cursor_row(
&mut self,
cursor_row: &mut odbc_api::CursorRow<'_>,
col_index: u16,
nulls: &mut Vec<bool>,
) -> Result<(), odbc_api::Error> {
match self {
OdbcValueVec::TinyInt(v) => push_get_data(cursor_row, col_index, v, nulls),
OdbcValueVec::SmallInt(v) => push_get_data(cursor_row, col_index, v, nulls),
OdbcValueVec::Integer(v) => push_get_data(cursor_row, col_index, v, nulls),
OdbcValueVec::BigInt(v) => push_get_data(cursor_row, col_index, v, nulls),
OdbcValueVec::Real(v) => push_get_data(cursor_row, col_index, v, nulls),
OdbcValueVec::Double(v) => push_get_data(cursor_row, col_index, v, nulls),
OdbcValueVec::Bit(v) => push_bit(cursor_row, col_index, v, nulls),
OdbcValueVec::Date(v) => push_get_data(cursor_row, col_index, v, nulls),
OdbcValueVec::Time(v) => push_get_data(cursor_row, col_index, v, nulls),
OdbcValueVec::Timestamp(v) => push_get_data(cursor_row, col_index, v, nulls),
OdbcValueVec::Binary(v) => push_binary(cursor_row, col_index, v, nulls),
OdbcValueVec::Text(v) => push_text(cursor_row, col_index, v, nulls),
}
}
}
fn push_get_data<T: Default + Copy + CElement + CDataMut>(
cursor_row: &mut odbc_api::CursorRow<'_>,
col_index: u16,
vec: &mut Vec<T>,
nulls: &mut Vec<bool>,
) -> Result<(), odbc_api::Error>
where
Nullable<T>: CElement + CDataMut,
{
let mut tmp = Nullable::null();
cursor_row.get_data(col_index, &mut tmp)?;
let option = tmp.into_opt();
nulls.push(option.is_none());
vec.push(option.unwrap_or_default());
Ok(())
}
fn push_binary(
cursor_row: &mut odbc_api::CursorRow<'_>,
col_index: u16,
vec: &mut Vec<Vec<u8>>,
nulls: &mut Vec<bool>,
) -> Result<(), odbc_api::Error> {
let mut buf = Vec::new();
let is_not_null = cursor_row.get_binary(col_index, &mut buf)?;
nulls.push(!is_not_null);
vec.push(buf);
Ok(())
}
fn push_text(
cursor_row: &mut odbc_api::CursorRow<'_>,
col_index: u16,
vec: &mut Vec<String>,
nulls: &mut Vec<bool>,
) -> Result<(), odbc_api::Error> {
let mut buf = Vec::<u16>::new();
let is_not_null = cursor_row.get_wide_text(col_index, &mut buf)?;
vec.push(String::from_utf16_lossy(&buf).to_string());
nulls.push(!is_not_null);
Ok(())
}
fn push_bit(
cursor_row: &mut odbc_api::CursorRow<'_>,
col_index: u16,
vec: &mut Vec<bool>,
nulls: &mut Vec<bool>,
) -> Result<(), odbc_api::Error> {
let mut bit_val = Nullable::<odbc_api::Bit>::null();
cursor_row.get_data(col_index, &mut bit_val)?;
match bit_val.into_opt() {
Some(bit) => {
nulls.push(false);
vec.push(bit.as_bool());
}
None => {
nulls.push(true);
vec.push(false);
}
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct ColumnData {
pub values: OdbcValueVec,
pub type_info: OdbcTypeInfo,
pub nulls: Vec<bool>,
}
#[derive(Debug)]
pub struct OdbcValueRef<'r> {
pub(crate) batch: &'r OdbcBatch,
pub(crate) row_index: usize,
pub(crate) column_index: usize,
}
#[derive(Debug, Clone)]
pub struct OdbcValue {
pub(crate) batch: Arc<OdbcBatch>,
pub(crate) row_index: usize,
pub(crate) column_index: usize,
}
impl<'r> ValueRef<'r> for OdbcValueRef<'r> {
type Database = Odbc;
fn to_owned(&self) -> OdbcValue {
OdbcValue {
batch: Arc::new(self.batch.clone()),
row_index: self.row_index,
column_index: self.column_index,
}
}
fn type_info(&self) -> Cow<'_, OdbcTypeInfo> {
Cow::Borrowed(&self.batch.column_data[self.column_index].type_info)
}
fn is_null(&self) -> bool {
value_vec_is_null(&self.batch.column_data[self.column_index], self.row_index)
}
}
impl Value for OdbcValue {
type Database = Odbc;
fn as_ref(&self) -> OdbcValueRef<'_> {
OdbcValueRef {
batch: &self.batch,
row_index: self.row_index,
column_index: self.column_index,
}
}
fn type_info(&self) -> Cow<'_, OdbcTypeInfo> {
Cow::Borrowed(&self.batch.column_data[self.column_index].type_info)
}
fn is_null(&self) -> bool {
value_vec_is_null(&self.batch.column_data[self.column_index], self.row_index)
}
}
impl OdbcValue {
pub fn new(batch: Arc<OdbcBatch>, row_index: usize, column_index: usize) -> Self {
Self {
batch,
row_index,
column_index,
}
}
pub fn get_raw(&self) -> Option<OdbcValueType> {
value_vec_get_raw(&self.batch.column_data[self.column_index], self.row_index)
}
pub fn as_int<T: TryFromInt>(&self) -> Option<T> {
value_vec_int(&self.batch.column_data[self.column_index], self.row_index)
}
pub fn as_f64(&self) -> Option<f64> {
value_vec_float(&self.batch.column_data[self.column_index], self.row_index)
}
pub fn as_str(&self) -> Option<Cow<'_, str>> {
value_vec_text(&self.batch.column_data[self.column_index], self.row_index)
.map(Cow::Borrowed)
}
pub fn as_bytes(&self) -> Option<Cow<'_, [u8]>> {
value_vec_blob(&self.batch.column_data[self.column_index], self.row_index)
.map(Cow::Borrowed)
}
}
impl<'r> OdbcValueRef<'r> {
pub fn new(batch: &'r OdbcBatch, row_index: usize, column_index: usize) -> Self {
Self {
batch,
row_index,
column_index,
}
}
pub fn get_raw(&self) -> Option<OdbcValueType> {
value_vec_get_raw(&self.batch.column_data[self.column_index], self.row_index)
}
pub fn int<T: TryFromInt>(&self) -> Option<T> {
value_vec_int(&self.batch.column_data[self.column_index], self.row_index)
}
pub fn try_int<T: TryFromInt + crate::types::Type<Odbc>>(&self) -> crate::error::Result<T> {
self.int::<T>().ok_or_else(|| {
crate::error::Error::Decode(Box::new(crate::error::MismatchedTypeError {
rust_type: std::any::type_name::<T>().to_string(),
rust_sql_type: T::type_info().name().to_string(),
sql_type: self.batch.column_data[self.column_index]
.type_info
.name()
.to_string(),
source: Some(format!("ODBC: cannot decode {:?}", self).into()),
}))
})
}
pub fn try_float<T: TryFromFloat + crate::types::Type<Odbc>>(&self) -> crate::error::Result<T> {
self.float::<T>().ok_or_else(|| {
crate::error::Error::Decode(Box::new(crate::error::MismatchedTypeError {
rust_type: std::any::type_name::<T>().to_string(),
rust_sql_type: T::type_info().name().to_string(),
sql_type: self.batch.column_data[self.column_index]
.type_info
.name()
.to_string(),
source: Some(format!("ODBC: cannot decode {:?}", self).into()),
}))
})
}
pub fn float<T: TryFromFloat>(&self) -> Option<T> {
value_vec_float(&self.batch.column_data[self.column_index], self.row_index)
}
pub fn text(&self) -> Option<&'r str> {
value_vec_text(&self.batch.column_data[self.column_index], self.row_index)
}
pub fn blob(&self) -> Option<&'r [u8]> {
value_vec_blob(&self.batch.column_data[self.column_index], self.row_index)
}
pub fn date(&self) -> Option<odbc_api::sys::Date> {
if self.is_null() {
None
} else {
match &self.batch.column_data[self.column_index].values {
OdbcValueVec::Date(raw_values) => raw_values.get(self.row_index).copied(),
_ => None,
}
}
}
pub fn time(&self) -> Option<odbc_api::sys::Time> {
if self.is_null() {
None
} else {
match &self.batch.column_data[self.column_index].values {
OdbcValueVec::Time(raw_values) => raw_values.get(self.row_index).copied(),
_ => None,
}
}
}
pub fn timestamp(&self) -> Option<odbc_api::sys::Timestamp> {
if self.is_null() {
None
} else {
match &self.batch.column_data[self.column_index].values {
OdbcValueVec::Timestamp(raw_values) => raw_values.get(self.row_index).copied(),
_ => None,
}
}
}
}
#[derive(Debug, Clone)]
pub enum OdbcValueType {
TinyInt(i8),
SmallInt(i16),
Integer(i32),
BigInt(i64),
Real(f32),
Double(f64),
Bit(bool),
Text(String),
Binary(Vec<u8>),
Date(odbc_api::sys::Date),
Time(odbc_api::sys::Time),
Timestamp(odbc_api::sys::Timestamp),
}
fn handle_non_nullable_slice_with<T: Copy, U>(
slice: &[T],
constructor: fn(Vec<U>) -> OdbcValueVec,
convert: impl FnMut(T) -> U,
) -> (OdbcValueVec, Vec<bool>) {
let values = slice.iter().copied().map(convert).collect();
(constructor(values), vec![false; slice.len()])
}
fn handle_nullable_slice_with<T: Copy, U: Default>(
slice: NullableSlice<'_, T>,
constructor: fn(Vec<U>) -> OdbcValueVec,
mut convert: impl FnMut(T) -> U,
) -> (OdbcValueVec, Vec<bool>) {
let size = slice.size_hint().1.unwrap_or(0);
let mut values = Vec::with_capacity(size);
let mut nulls = Vec::with_capacity(size);
for opt in slice {
let is_null = opt.is_none();
values.push(opt.copied().map(&mut convert).unwrap_or_default());
nulls.push(is_null);
}
(constructor(values), nulls)
}
fn handle_buffer_slice<T: Copy + odbc_api::Pod, U: Default>(
slice: &AnyColumnBufferSlice<'_>,
desc: BufferDesc,
nullable: bool,
constructor: fn(Vec<U>) -> OdbcValueVec,
convert: impl FnMut(T) -> U,
) -> Result<(OdbcValueVec, Vec<bool>), Error> {
if nullable {
Ok(handle_nullable_slice_with(
expect_slice(slice.as_nullable_slice::<T>(), desc)?,
constructor,
convert,
))
} else {
Ok(handle_non_nullable_slice_with(
expect_slice(slice.as_slice::<T>(), desc)?,
constructor,
convert,
))
}
}
fn buffer_slice_mismatch(desc: BufferDesc) -> Error {
Error::Protocol(format!(
"ODBC column buffer {desc:?} did not match fetched slice"
))
}
fn expect_slice<T>(slice: Option<T>, desc: BufferDesc) -> Result<T, Error> {
slice.ok_or_else(|| buffer_slice_mismatch(desc))
}
fn handle_optional_values<T, U: Default>(
len: usize,
values: impl IntoIterator<Item = Option<T>>,
constructor: fn(Vec<U>) -> OdbcValueVec,
mut convert: impl FnMut(T) -> U,
) -> (OdbcValueVec, Vec<bool>) {
let mut converted = Vec::with_capacity(len);
let mut nulls = Vec::with_capacity(len);
for value in values {
let is_null = value.is_none();
converted.push(value.map(&mut convert).unwrap_or_default());
nulls.push(is_null);
}
(constructor(converted), nulls)
}
pub(crate) fn convert_dyn_slice_to_value_vec(
slice: AnyColumnBufferSlice<'_>,
desc: BufferDesc,
) -> Result<(OdbcValueVec, Vec<bool>), Error> {
Ok(match desc {
BufferDesc::I8 { nullable } => {
handle_buffer_slice(&slice, desc, nullable, OdbcValueVec::TinyInt, |value| value)?
}
BufferDesc::I16 { nullable } => {
handle_buffer_slice(&slice, desc, nullable, OdbcValueVec::SmallInt, |value| {
value
})?
}
BufferDesc::I32 { nullable } => {
handle_buffer_slice(&slice, desc, nullable, OdbcValueVec::Integer, |value| value)?
}
BufferDesc::I64 { nullable } => {
handle_buffer_slice(&slice, desc, nullable, OdbcValueVec::BigInt, |value| value)?
}
BufferDesc::U8 { nullable } => {
handle_buffer_slice::<u8, i64>(&slice, desc, nullable, OdbcValueVec::BigInt, i64::from)?
}
BufferDesc::F32 { nullable } => {
handle_buffer_slice(&slice, desc, nullable, OdbcValueVec::Real, |value| value)?
}
BufferDesc::F64 { nullable } => {
handle_buffer_slice(&slice, desc, nullable, OdbcValueVec::Double, |value| value)?
}
BufferDesc::Bit { nullable } => handle_buffer_slice(
&slice,
desc,
nullable,
OdbcValueVec::Bit,
|value: odbc_api::Bit| value.as_bool(),
)?,
BufferDesc::Date { nullable } => {
handle_buffer_slice(&slice, desc, nullable, OdbcValueVec::Date, |value| value)?
}
BufferDesc::Time { nullable } => {
handle_buffer_slice(&slice, desc, nullable, OdbcValueVec::Time, |value| value)?
}
BufferDesc::Timestamp { nullable } => {
handle_buffer_slice(&slice, desc, nullable, OdbcValueVec::Timestamp, |value| {
value
})?
}
BufferDesc::Text { .. } => {
let s = expect_slice(slice.as_text(), desc)?;
handle_optional_values(s.len(), s.iter(), OdbcValueVec::Text, |bytes| {
String::from_utf8_lossy(bytes).into_owned()
})
}
BufferDesc::WText { .. } => {
let s = expect_slice(slice.as_wide_text(), desc)?;
handle_optional_values(s.len(), s.iter(), OdbcValueVec::Text, |chars| {
String::from_utf16_lossy(chars.into())
})
}
BufferDesc::Binary { .. } => {
let s = expect_slice(slice.as_binary(), desc)?;
handle_optional_values(s.len(), s.iter(), OdbcValueVec::Binary, |bytes| {
bytes.to_vec()
})
}
BufferDesc::Numeric => {
return Err(Error::Protocol(format!(
"unsupported ODBC buffer descriptor: {desc:?}"
)));
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn converts_unsigned_tinyint_slices() {
let (values, nulls) =
handle_non_nullable_slice_with(&[0, 255], OdbcValueVec::BigInt, i64::from);
assert_eq!(nulls, vec![false, false]);
assert!(matches!(values, OdbcValueVec::BigInt(values) if values == vec![0, 255]));
}
}
fn value_vec_is_null(column_data: &ColumnData, row_index: usize) -> bool {
column_data.nulls.get(row_index).copied().unwrap_or(false)
}
macro_rules! impl_get_raw_arm_copy {
($vec:expr, $row_index:expr, $variant:ident, $type:ty) => {
$vec.get($row_index).copied().map(OdbcValueType::$variant)
};
}
fn value_vec_get_raw(column_data: &ColumnData, row_index: usize) -> Option<OdbcValueType> {
if value_vec_is_null(column_data, row_index) {
return None;
}
match &column_data.values {
OdbcValueVec::TinyInt(v) => v.get(row_index).map(|&val| OdbcValueType::TinyInt(val)),
OdbcValueVec::SmallInt(v) => v.get(row_index).map(|&val| OdbcValueType::SmallInt(val)),
OdbcValueVec::Integer(v) => v.get(row_index).map(|&val| OdbcValueType::Integer(val)),
OdbcValueVec::BigInt(v) => v.get(row_index).map(|&val| OdbcValueType::BigInt(val)),
OdbcValueVec::Real(v) => v.get(row_index).map(|&val| OdbcValueType::Real(val)),
OdbcValueVec::Double(v) => v.get(row_index).map(|&val| OdbcValueType::Double(val)),
OdbcValueVec::Bit(v) => v.get(row_index).map(|&val| OdbcValueType::Bit(val)),
OdbcValueVec::Text(v) => v.get(row_index).cloned().map(OdbcValueType::Text),
OdbcValueVec::Binary(v) => v.get(row_index).cloned().map(OdbcValueType::Binary),
OdbcValueVec::Date(v) => impl_get_raw_arm_copy!(v, row_index, Date, odbc_api::sys::Date),
OdbcValueVec::Time(v) => impl_get_raw_arm_copy!(v, row_index, Time, odbc_api::sys::Time),
OdbcValueVec::Timestamp(v) => {
impl_get_raw_arm_copy!(v, row_index, Timestamp, odbc_api::sys::Timestamp)
}
}
}
pub trait TryFromInt:
TryFrom<u8>
+ TryFrom<i16>
+ TryFrom<i32>
+ TryFrom<i64>
+ TryFrom<i8>
+ TryFrom<u16>
+ TryFrom<u32>
+ TryFrom<u64>
+ std::str::FromStr
{
}
impl<
T: TryFrom<u8>
+ TryFrom<i16>
+ TryFrom<i32>
+ TryFrom<i64>
+ TryFrom<i8>
+ TryFrom<u16>
+ TryFrom<u32>
+ TryFrom<u64>
+ std::str::FromStr,
> TryFromInt for T
{
}
macro_rules! impl_int_conversion {
($vec:expr, $row_index:expr, $type:ty) => {
<$type>::try_from(*$vec.get($row_index)?).ok()
};
($vec:expr, $row_index:expr, $type:ty, text) => {
if let Some(Some(text)) = $vec.get($row_index) {
text.trim().parse().ok()
} else {
None
}
};
}
fn value_vec_int<T: TryFromInt>(column_data: &ColumnData, row_index: usize) -> Option<T> {
if value_vec_is_null(column_data, row_index) {
return None;
}
match &column_data.values {
OdbcValueVec::TinyInt(v) => impl_int_conversion!(v, row_index, T),
OdbcValueVec::SmallInt(v) => impl_int_conversion!(v, row_index, T),
OdbcValueVec::Integer(v) => impl_int_conversion!(v, row_index, T),
OdbcValueVec::BigInt(v) => impl_int_conversion!(v, row_index, T),
OdbcValueVec::Bit(v) => T::try_from(*v.get(row_index)? as u8).ok(),
OdbcValueVec::Text(v) => v.get(row_index).and_then(|text| text.trim().parse().ok()),
_ => None,
}
}
pub trait TryFromFloat: TryFrom<f32> + TryFrom<f64> {}
impl<T: TryFrom<f32> + TryFrom<f64>> TryFromFloat for T {}
macro_rules! impl_float_conversion {
($vec:expr, $row_index:expr, $type:ty) => {
<$type>::try_from(*$vec.get($row_index)?).ok()
};
}
fn value_vec_float<T: TryFromFloat>(column_data: &ColumnData, row_index: usize) -> Option<T> {
if value_vec_is_null(column_data, row_index) {
return None;
}
match &column_data.values {
OdbcValueVec::Real(v) => impl_float_conversion!(v, row_index, T),
OdbcValueVec::Double(v) => impl_float_conversion!(v, row_index, T),
_ => None,
}
}
fn value_vec_text(column_data: &ColumnData, row_index: usize) -> Option<&str> {
if value_vec_is_null(column_data, row_index) {
return None;
}
match &column_data.values {
OdbcValueVec::Text(v) => v.get(row_index).map(|s| s.as_str()),
_ => None,
}
}
fn value_vec_blob(column_data: &ColumnData, row_index: usize) -> Option<&[u8]> {
if value_vec_is_null(column_data, row_index) {
return None;
}
match &column_data.values {
OdbcValueVec::Binary(v) => v.get(row_index).map(|b| b.as_slice()),
_ => None,
}
}
#[cfg(feature = "any")]
impl<'r> From<OdbcValueRef<'r>> for crate::any::AnyValueRef<'r> {
fn from(value: OdbcValueRef<'r>) -> Self {
crate::any::AnyValueRef {
type_info: crate::any::AnyTypeInfo::from(
value.batch.column_data[value.column_index]
.type_info
.clone(),
),
kind: crate::any::value::AnyValueRefKind::Odbc(value),
}
}
}
#[cfg(feature = "any")]
impl From<OdbcValue> for crate::any::AnyValue {
fn from(value: OdbcValue) -> Self {
crate::any::AnyValue {
type_info: crate::any::AnyTypeInfo::from(
value.batch.column_data[value.column_index]
.type_info
.clone(),
),
kind: crate::any::value::AnyValueKind::Odbc(value),
}
}
}