use odbc_sys::{CDataType, DATA_AT_EXEC, len_data_at_exec};
use crate::{
DataType, Error, ParameterCollection, ParameterTupleElement,
handles::{DelayedInput, HasDataType, Statement},
};
use std::{
ffi::c_void,
fs::File,
io::{self, BufRead, BufReader},
num::NonZeroUsize,
path::Path,
};
pub unsafe trait Blob: HasDataType + Send {
fn c_data_type(&self) -> CDataType;
fn size_hint(&self) -> Option<usize>;
fn next_batch(&mut self) -> io::Result<Option<&[u8]>>;
fn as_blob_param(&mut self) -> BlobParam<'_>
where
Self: Sized,
{
BlobParam::new(self)
}
}
pub struct BlobParam<'a> {
indicator: isize,
blob: &'a mut dyn Blob,
}
impl<'a> BlobParam<'a> {
pub fn new(blob: &'a mut impl Blob) -> Self {
let indicator = if let Some(size) = blob.size_hint() {
len_data_at_exec(size.try_into().unwrap())
} else {
DATA_AT_EXEC
};
Self { indicator, blob }
}
}
unsafe impl DelayedInput for BlobParam<'_> {
fn cdata_type(&self) -> CDataType {
self.blob.c_data_type()
}
fn indicator_ptr(&self) -> *const isize {
&self.indicator as *const isize
}
fn stream_ptr(&mut self) -> *mut c_void {
debug_assert_eq!(
std::mem::size_of::<*mut &mut dyn Blob>(),
std::mem::size_of::<*mut c_void>()
);
&mut self.blob as *mut &mut dyn Blob as *mut c_void
}
}
impl HasDataType for BlobParam<'_> {
fn data_type(&self) -> DataType {
self.blob.data_type()
}
}
unsafe impl ParameterCollection for BlobParam<'_> {
fn parameter_set_size(&self) -> usize {
1
}
unsafe fn bind_parameters_to(&mut self, stmt: &mut impl Statement) -> Result<(), Error> {
unsafe { stmt.bind_delayed_input_parameter(1, self) }.into_result(stmt)
}
}
unsafe impl ParameterTupleElement for &mut BlobParam<'_> {
unsafe fn bind_to(
&mut self,
parameter_number: u16,
stmt: &mut impl Statement,
) -> Result<(), Error> {
unsafe { stmt.bind_delayed_input_parameter(parameter_number, *self) }.into_result(stmt)
}
}
pub struct BlobSlice<'a> {
pub is_binary: bool,
pub batch_size: usize,
pub blob: &'a [u8],
}
impl<'a> BlobSlice<'a> {
pub fn from_byte_slice(blob: &'a [u8]) -> Self {
Self {
is_binary: true,
batch_size: blob.len(),
blob,
}
}
pub fn from_text(text: &'a str) -> Self {
Self {
is_binary: false,
batch_size: text.len(),
blob: text.as_bytes(),
}
}
}
impl HasDataType for BlobSlice<'_> {
fn data_type(&self) -> DataType {
if self.is_binary {
DataType::LongVarbinary {
length: NonZeroUsize::new(self.blob.len()),
}
} else {
DataType::LongVarchar {
length: NonZeroUsize::new(self.blob.len()),
}
}
}
}
unsafe impl Blob for BlobSlice<'_> {
fn c_data_type(&self) -> CDataType {
if self.is_binary {
CDataType::Binary
} else {
CDataType::Char
}
}
fn size_hint(&self) -> Option<usize> {
Some(self.blob.len())
}
fn next_batch(&mut self) -> io::Result<Option<&[u8]>> {
if self.blob.is_empty() {
return Ok(None);
}
if self.blob.len() >= self.batch_size {
let (head, tail) = self.blob.split_at(self.batch_size);
self.blob = tail;
Ok(Some(head))
} else {
let last_batch = self.blob;
self.blob = &[];
Ok(Some(last_batch))
}
}
}
pub struct BlobRead<R> {
exact: bool,
size: usize,
consume: usize,
buf_read: R,
}
impl<R> BlobRead<R> {
pub fn with_upper_bound(buf_read: R, upper_bound: usize) -> Self {
Self {
exact: false,
consume: 0,
size: upper_bound,
buf_read,
}
}
pub unsafe fn with_exact_size(buf_read: R, exact_size: usize) -> Self {
Self {
exact: true,
consume: 0,
size: exact_size,
buf_read,
}
}
}
impl BlobRead<BufReader<File>> {
pub fn from_path(path: &Path) -> io::Result<Self> {
let file = File::open(path)?;
let size = file.metadata()?.len().try_into().unwrap();
let buf_read = BufReader::new(file);
Ok(Self {
consume: 0,
exact: true,
size,
buf_read,
})
}
}
impl<R> HasDataType for BlobRead<R>
where
R: BufRead,
{
fn data_type(&self) -> DataType {
DataType::LongVarbinary {
length: NonZeroUsize::new(self.size),
}
}
}
unsafe impl<R> Blob for BlobRead<R>
where
R: BufRead + Send,
{
fn c_data_type(&self) -> CDataType {
CDataType::Binary
}
fn size_hint(&self) -> Option<usize> {
if self.exact { Some(self.size) } else { None }
}
fn next_batch(&mut self) -> io::Result<Option<&[u8]>> {
if self.consume != 0 {
self.buf_read.consume(self.consume);
}
let batch = self.buf_read.fill_buf()?;
self.consume = batch.len();
if batch.is_empty() {
Ok(None)
} else {
Ok(Some(batch))
}
}
}