use crate::DB_TYPE_BFILE;
use crate::client::ClientRef;
use crate::constants;
use crate::db_type::DbType;
use crate::error::Error;
use crate::messages::{LobOp, LobOpMessage};
use crate::response::Response;
use crate::utils;
use std::borrow::Cow;
use std::fmt;
use std::io;
#[derive(Clone)]
pub struct Lob {
client_ref: ClientRef,
locator: Vec<u8>,
db_type: &'static DbType,
size: Option<usize>,
chunk_size: Option<usize>,
offset: usize,
}
impl fmt::Debug for Lob {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Lob")
.field("locator_len", &self.locator.len())
.field("db_type", &self.db_type.name())
.field("size", &self.size)
.field("chunk_size", &self.chunk_size)
.field("offset", &self.offset)
.finish()
}
}
impl Lob {
fn decode_string(
bytes: &[u8],
encoding: LobStringEncoding,
) -> io::Result<String> {
match encoding {
LobStringEncoding::Utf8 => String::from_utf8(bytes.to_vec())
.map_err(|error| {
io::Error::new(
io::ErrorKind::InvalidData,
error.to_string(),
)
}),
LobStringEncoding::Utf16Be => {
Ok(utils::utf16be_bytes_to_string(bytes))
}
LobStringEncoding::Utf16Le => {
Ok(utils::utf16le_bytes_to_string(bytes))
}
}
}
fn io_error(error: Error) -> io::Error {
io::Error::other(error.to_string())
}
fn is_character_lob(&self) -> bool {
self.db_type.ora_type_num == constants::ORA_TYPE_NUM_CLOB
}
fn length_to_amount(&self, length: usize) -> usize {
if self.is_character_lob() {
length / 2
} else {
length
}
}
fn open_mode(&self) -> u64 {
if self.db_type == &DB_TYPE_BFILE {
constants::TTC_LOB_OPEN_READ_ONLY
} else {
constants::TTC_LOB_OPEN_READ_WRITE
}
}
fn process_lob_op<'a>(
&mut self,
op: LobOp<'a>,
) -> Result<LobOpMessage<'a>, Error> {
let mut message = LobOpMessage::new(&self.locator, op);
self.client_ref
.lock()
.unwrap()
.process_message(&mut message)?;
if let Some(locator) = message.take_returned_locator() {
self.locator = locator;
}
Ok(message)
}
fn read_at(
&mut self,
offset: usize,
amount: usize,
) -> Result<Vec<u8>, Error> {
let mut message = self.process_lob_op(LobOp::Read(
offset.try_into().unwrap(),
amount.try_into().unwrap(),
))?;
Ok(message.take_data())
}
fn string_encoding(&self) -> LobStringEncoding {
if self.db_type.csfrm == constants::CS_FORM_NCHAR {
return LobStringEncoding::Utf16Be;
}
let flags3 = self.locator[constants::TTC_LOB_LOC_OFFSET_FLAG_3];
if flags3 & constants::TTC_LOB_LOC_FLAGS_VAR_LENGTH_CHARSET != 0 {
let flags4 = self.locator[constants::TTC_LOB_LOC_OFFSET_FLAG_4];
if flags4 & constants::TTC_LOB_LOC_FLAGS_LITTLE_ENDIAN != 0 {
return LobStringEncoding::Utf16Le;
}
return LobStringEncoding::Utf16Be;
}
LobStringEncoding::Utf8
}
fn write_at(
&mut self,
data: Cow<'_, [u8]>,
offset: usize,
) -> Result<(), Error> {
let amount = self.length_to_amount(data.len());
self.process_lob_op(LobOp::Write(offset.try_into().unwrap(), data))?;
self.size = None;
self.offset += amount;
Ok(())
}
pub(crate) fn from_resp(
resp: &mut Response,
db_type: &'static DbType,
) -> Result<Option<Lob>, Error> {
let non_null_indicator = resp.read_ub4()?;
if non_null_indicator == 0 {
Ok(None)
} else {
let size: usize = resp.read_ub8()?.try_into().unwrap();
let chunk_size: usize = resp.read_ub4()?.try_into().unwrap();
let locator = resp.read_bytes_with_length()?.into_owned();
Ok(Some(Lob {
client_ref: resp.get_client_ref(),
locator,
db_type,
size: Some(size),
chunk_size: Some(chunk_size),
offset: 1,
}))
}
}
pub fn close(&mut self) -> Result<(), Error> {
self.process_lob_op(LobOp::Close)?;
Ok(())
}
pub fn db_type(&self) -> &'static DbType {
self.db_type
}
pub fn get_chunk_size(&mut self) -> Result<usize, Error> {
if self.chunk_size.is_none() {
let message = self.process_lob_op(LobOp::GetChunkSize)?;
let chunk_size: usize =
message.returned_amount().try_into().unwrap();
self.chunk_size = Some(chunk_size);
}
Ok(self.chunk_size.unwrap())
}
pub fn get_size(&mut self) -> Result<usize, Error> {
if self.size.is_none() {
let message = self.process_lob_op(LobOp::GetLength)?;
let size: usize = message.returned_amount().try_into().unwrap();
self.size = Some(size);
}
Ok(self.size.unwrap())
}
pub fn is_open(&mut self) -> Result<bool, Error> {
let message = self.process_lob_op(LobOp::IsOpen)?;
Ok(message.returned_bool())
}
pub fn open(&mut self) -> Result<(), Error> {
self.process_lob_op(LobOp::Open(self.open_mode()))?;
Ok(())
}
pub fn trim(&mut self, new_size: usize) -> Result<(), Error> {
self.process_lob_op(LobOp::Trim(new_size.try_into().unwrap()))?;
self.size = Some(new_size);
if self.offset > new_size + 1 {
self.offset = new_size + 1;
}
Ok(())
}
}
impl io::Read for Lob {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let size = self.get_size().map_err(Self::io_error)?;
if self.offset > size {
return Ok(0);
}
let is_character_lob = self.is_character_lob();
let remaining = size - self.offset + 1;
let amount = if is_character_lob {
remaining.min((buf.len() / 3).max(1))
} else {
remaining.min(buf.len())
};
let bytes =
self.read_at(self.offset, amount).map_err(Self::io_error)?;
let offset_increment = self.length_to_amount(bytes.len());
let data = if is_character_lob {
let encoding = self.string_encoding();
let value = Self::decode_string(&bytes, encoding)?;
value.into_bytes()
} else {
bytes
};
if data.len() > buf.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"buffer too small for LOB read",
));
}
buf[..data.len()].copy_from_slice(&data);
self.offset += offset_increment;
Ok(data.len())
}
}
impl io::Write for Lob {
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
if self.db_type == &DB_TYPE_BFILE {
return Err(io::Error::new(
io::ErrorKind::Unsupported,
"writing to BFILE is not supported",
));
}
let data = if self.is_character_lob() {
let value = std::str::from_utf8(buf).map_err(|error| {
io::Error::new(io::ErrorKind::InvalidData, error.to_string())
})?;
match self.string_encoding() {
LobStringEncoding::Utf16Be => {
Cow::Owned(utils::string_to_utf16be_bytes(value))
}
LobStringEncoding::Utf16Le => {
Cow::Owned(utils::string_to_utf16le_bytes(value))
}
_ => todo!(),
}
} else {
Cow::Borrowed(buf)
};
self.write_at(data, self.offset).map_err(Self::io_error)?;
Ok(buf.len())
}
}
#[derive(Clone, Copy, Debug)]
enum LobStringEncoding {
Utf8,
Utf16Be,
Utf16Le,
}