use asupersync::Cx;
use oracledb_protocol::thin::{LobTextDecoder, LobValue};
use crate::{Connection, Error, Result};
#[derive(Clone, Debug)]
pub struct LobReader {
locator: Vec<u8>,
total: u64,
pos: u64,
chunk: u64,
}
impl LobReader {
pub fn new(lob: &LobValue, chunk: u64) -> Self {
Self::from_parts(lob.locator.clone(), lob.size, chunk)
}
pub fn from_parts(locator: Vec<u8>, size: u64, chunk: u64) -> Self {
LobReader {
locator,
total: size,
pos: 1,
chunk: chunk.max(1),
}
}
pub fn is_eof(&self) -> bool {
self.pos > self.total
}
pub fn locator(&self) -> &[u8] {
&self.locator
}
pub async fn read_chunk(&mut self, conn: &mut Connection, cx: &Cx) -> Result<Option<Vec<u8>>> {
if self.pos > self.total {
return Ok(None);
}
let want = (self.total - self.pos + 1).min(self.chunk);
let result = conn.read_lob(cx, &self.locator, self.pos, want).await?;
self.pos += want;
let data = result.data.unwrap_or_default();
let _span = obs_span!(
"oracledb.lob_stream",
db.lob_chunk_units = want,
db.lob_chunk_bytes = data.len() as u64,
);
Ok(Some(data))
}
pub async fn read_to_end(&mut self, conn: &mut Connection, cx: &Cx) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(usize::try_from(self.total).unwrap_or(0));
while let Some(chunk) = self.read_chunk(conn, cx).await? {
out.extend_from_slice(&chunk);
}
Ok(out)
}
}
#[derive(Clone, Debug)]
pub struct ClobReader {
inner: LobReader,
decoder: LobTextDecoder,
}
impl ClobReader {
pub fn new(lob: &LobValue, chunk: u64) -> Self {
ClobReader {
inner: LobReader::new(lob, chunk),
decoder: LobTextDecoder::from_lob(lob.csfrm, Some(&lob.locator)),
}
}
pub async fn read_text_chunk(
&mut self,
conn: &mut Connection,
cx: &Cx,
) -> Result<Option<String>> {
match self.inner.read_chunk(conn, cx).await? {
Some(bytes) => {
let text = self.decoder.push(&bytes).map_err(Error::Protocol)?;
let _span = obs_span!(
"oracledb.lob_stream_text",
db.lob_utf16_boundary_split = self.decoder.clone().finish().is_err(),
db.lob_chunk_chars = text.chars().count() as u64,
);
Ok(Some(text))
}
None => Ok(None),
}
}
pub async fn read_to_string(mut self, conn: &mut Connection, cx: &Cx) -> Result<String> {
let mut out = String::new();
while let Some(piece) = self.read_text_chunk(conn, cx).await? {
out.push_str(&piece);
}
self.decoder.finish().map_err(Error::Protocol)?;
Ok(out)
}
}
#[derive(Clone, Debug)]
pub struct LobWriter {
locator: Vec<u8>,
pos: u64,
}
impl LobWriter {
pub fn new(locator: Vec<u8>) -> Self {
LobWriter { locator, pos: 1 }
}
pub fn locator(&self) -> &[u8] {
&self.locator
}
pub fn into_locator(self) -> Vec<u8> {
self.locator
}
pub async fn write_chunk(&mut self, conn: &mut Connection, cx: &Cx, data: &[u8]) -> Result<()> {
if data.is_empty() {
return Ok(());
}
let result = conn.write_lob(cx, &self.locator, self.pos, data).await?;
if !result.locator.is_empty() {
self.locator = result.locator;
}
self.pos += data.len() as u64;
Ok(())
}
}