oracle/sql_type/
lob.rs

1// Rust-oracle - Rust binding for Oracle database
2//
3// URL: https://github.com/kubo/rust-oracle
4//
5//-----------------------------------------------------------------------------
6// Copyright (c) 2017-2021 Kubo Takehiro <kubo@jiubao.org>. All rights reserved.
7// This program is free software: you can modify it and/or redistribute it
8// under the terms of:
9//
10// (i)  the Universal Permissive License v 1.0 or at your option, any
11//      later version (http://oss.oracle.com/licenses/upl); and/or
12//
13// (ii) the Apache License v 2.0. (http://www.apache.org/licenses/LICENSE-2.0)
14//-----------------------------------------------------------------------------
15use crate::chkerr;
16use crate::io::SeekInChars;
17use crate::sql_type::FromSql;
18use crate::sql_type::OracleType;
19use crate::sql_type::ToSql;
20use crate::sql_type::ToSqlNull;
21use crate::Connection;
22use crate::Context;
23use crate::OdpiStr;
24use crate::Result;
25use crate::SqlValue;
26use odpic_sys::*;
27use std::cmp;
28use std::convert::TryInto;
29use std::fmt;
30use std::io::{self, Read, Seek, Write};
31use std::os::raw::c_char;
32use std::ptr;
33use std::str;
34
35#[cfg(not(test))]
36const MIN_READ_SIZE: usize = 400;
37
38#[cfg(test)]
39const MIN_READ_SIZE: usize = 20;
40
41fn utf16_len(s: &[u8]) -> io::Result<usize> {
42    let s = map_to_io_error(str::from_utf8(s))?;
43    Ok(s.chars().fold(0, |acc, c| acc + c.len_utf16()))
44}
45
46fn map_to_io_error<T, E>(res: std::result::Result<T, E>) -> io::Result<T>
47where
48    E: Into<Box<dyn std::error::Error + Send + Sync>>,
49{
50    res.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
51}
52
53pub struct LobLocator {
54    ctxt: Context,
55    pub(crate) handle: *mut dpiLob,
56    pos: u64,
57}
58
59impl LobLocator {
60    fn from_raw(ctxt: &Context, handle: *mut dpiLob) -> Result<LobLocator> {
61        chkerr!(ctxt, dpiLob_addRef(handle));
62        Ok(LobLocator {
63            ctxt: ctxt.clone(),
64            handle,
65            pos: 0,
66        })
67    }
68
69    fn ctxt(&self) -> &Context {
70        &self.ctxt
71    }
72
73    fn close(&mut self) -> Result<()> {
74        chkerr!(self.ctxt(), dpiLob_close(self.handle));
75        Ok(())
76    }
77
78    fn read_bytes(&mut self, amount: usize, buf: &mut [u8]) -> Result<usize> {
79        unsafe { self.read_bytes_unsafe(amount, buf.as_mut_ptr(), buf.len()) }
80    }
81
82    unsafe fn read_bytes_unsafe(
83        &mut self,
84        amount: usize,
85        buf: *mut u8,
86        len: usize,
87    ) -> Result<usize> {
88        let mut len = len as u64;
89        chkerr!(
90            self.ctxt(),
91            dpiLob_readBytes(
92                self.handle,
93                self.pos + 1,
94                amount as u64,
95                buf as *mut c_char,
96                &mut len
97            )
98        );
99        Ok(len as usize)
100    }
101
102    /// read for `Blob` and `Bfile`
103    fn read_binary(&mut self, buf: &mut [u8]) -> io::Result<usize> {
104        let len = map_to_io_error(self.read_bytes(buf.len(), buf))?;
105        self.pos += len as u64;
106        Ok(len)
107    }
108
109    /// read for `Clob` and `Nclob`
110    fn read_chars(&mut self, buf: &mut [u8]) -> io::Result<usize> {
111        if buf.len() > MIN_READ_SIZE {
112            let len = map_to_io_error(self.read_bytes(buf.len(), buf))?;
113            self.pos += utf16_len(&buf[0..len])? as u64;
114            Ok(len)
115        } else {
116            let mut tmp = [0u8; MIN_READ_SIZE];
117            let buf_len = if buf.len() == 1 { 2 } else { buf.len() };
118            let len = map_to_io_error(self.read_bytes(buf_len, &mut tmp))?;
119            let len = cmp::min(len, buf.len());
120            let s = match str::from_utf8(&tmp[0..len]) {
121                Ok(s) => s,
122                Err(err) if err.error_len().is_some() => return map_to_io_error(Err(err)),
123                Err(err) if err.valid_up_to() == 0 => {
124                    return Err(io::Error::new(
125                        io::ErrorKind::Other,
126                        "too small buffer to read characters",
127                    ));
128                }
129                Err(err) => unsafe { str::from_utf8_unchecked(&tmp[0..err.valid_up_to()]) },
130            };
131            buf[0..s.len()].copy_from_slice(s.as_bytes());
132            self.pos += s.chars().fold(0, |acc, c| acc + c.len_utf16()) as u64;
133            Ok(s.len())
134        }
135    }
136
137    fn read_to_end(&mut self, buf: &mut Vec<u8>, nls_ratio: usize) -> io::Result<usize> {
138        let too_long_data_err = || {
139            io::Error::new(
140                io::ErrorKind::Other,
141                "The length of LOB data is too long to store a buffer",
142            )
143        };
144        let lob_size = map_to_io_error(self.size())?;
145        if self.pos >= lob_size {
146            return Ok(0);
147        }
148        let rest_size: usize = (lob_size - self.pos)
149            .try_into()
150            .map_err(|_| too_long_data_err())?;
151        let rest_byte_size = rest_size
152            .checked_mul(nls_ratio)
153            .filter(|n| {
154                if let Some(len) = buf.len().checked_add(*n) {
155                    len <= isize::MAX as usize
156                } else {
157                    false
158                }
159            })
160            .ok_or_else(too_long_data_err)?;
161        buf.reserve(rest_byte_size);
162        match unsafe {
163            self.read_bytes_unsafe(rest_size, buf.as_mut_ptr().add(buf.len()), rest_byte_size)
164        } {
165            Ok(size) => {
166                unsafe { buf.set_len(buf.len() + size) };
167                Ok(size)
168            }
169            Err(err) => map_to_io_error(Err(err)),
170        }
171    }
172
173    /// read_to_end for `BLOB` and `BFILE`
174    fn read_binary_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
175        let len = self.read_to_end(buf, 1)?;
176        self.pos += len as u64;
177        Ok(len)
178    }
179
180    /// read_to_end for `CLOB` and `NCLOB`
181    fn read_chars_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
182        let start_pos = buf.len();
183        let len = self.read_to_end(buf, 4)?;
184        self.pos += utf16_len(&buf[start_pos..])? as u64;
185        Ok(len)
186    }
187
188    fn write_bytes(&mut self, buf: &[u8]) -> Result<usize> {
189        let len = buf.len() as u64;
190        chkerr!(
191            self.ctxt(),
192            dpiLob_writeBytes(
193                self.handle,
194                self.pos + 1,
195                buf.as_ptr() as *const c_char,
196                len,
197            )
198        );
199        Ok(len as usize)
200    }
201
202    /// write for `BLOB` and `BFILE`
203    fn write_binary(&mut self, buf: &[u8]) -> io::Result<usize> {
204        let len = map_to_io_error(self.write_bytes(buf))?;
205        self.pos += len as u64;
206        Ok(len)
207    }
208
209    /// write for `CLOB` and `NCLOB`
210    fn write_chars(&mut self, buf: &[u8]) -> io::Result<usize> {
211        map_to_io_error(str::from_utf8(buf))?;
212        let len = map_to_io_error(self.write_bytes(buf))?;
213        self.pos += utf16_len(&buf[0..len])? as u64;
214        Ok(len)
215    }
216
217    fn size(&self) -> Result<u64> {
218        let mut size = 0;
219        chkerr!(self.ctxt(), dpiLob_getSize(self.handle, &mut size));
220        Ok(size)
221    }
222
223    fn truncate(&mut self, new_size: u64) -> Result<()> {
224        chkerr!(self.ctxt(), dpiLob_trim(self.handle, new_size));
225        if self.pos > new_size {
226            self.pos = new_size;
227        }
228        Ok(())
229    }
230
231    fn chunk_size(&self) -> Result<usize> {
232        let mut size = 0;
233        chkerr!(self.ctxt(), dpiLob_getChunkSize(self.handle, &mut size));
234        Ok(size.try_into()?)
235    }
236
237    fn open_resource(&mut self) -> Result<()> {
238        chkerr!(self.ctxt(), dpiLob_openResource(self.handle));
239        Ok(())
240    }
241
242    fn close_resource(&mut self) -> Result<()> {
243        chkerr!(self.ctxt(), dpiLob_closeResource(self.handle));
244        Ok(())
245    }
246
247    fn is_resource_open(&self) -> Result<bool> {
248        let mut is_open = 0;
249        chkerr!(
250            self.ctxt(),
251            dpiLob_getIsResourceOpen(self.handle, &mut is_open)
252        );
253        Ok(is_open != 0)
254    }
255
256    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
257        self.pos = match pos {
258            io::SeekFrom::Start(offset) => Some(offset),
259            io::SeekFrom::End(offset) => {
260                let size = map_to_io_error(self.size())?;
261                if offset < 0 {
262                    size.checked_sub((-offset) as u64)
263                } else {
264                    size.checked_add(offset as u64)
265                }
266            }
267            io::SeekFrom::Current(offset) => {
268                if offset < 0 {
269                    self.pos.checked_sub((-offset) as u64)
270                } else {
271                    self.pos.checked_add(offset as u64)
272                }
273            }
274        }
275        .ok_or_else(|| {
276            io::Error::new(
277                io::ErrorKind::Other,
278                format!("Cannot seek {:?} from offset {}", pos, self.pos),
279            )
280        })?;
281        Ok(self.pos)
282    }
283
284    fn directory_and_file_name(&self) -> Result<(String, String)> {
285        let mut dir_alias = OdpiStr::new("");
286        let mut file_name = OdpiStr::new("");
287        chkerr!(
288            self.ctxt(),
289            dpiLob_getDirectoryAndFileName(
290                self.handle,
291                &mut dir_alias.ptr,
292                &mut dir_alias.len,
293                &mut file_name.ptr,
294                &mut file_name.len
295            )
296        );
297        Ok((dir_alias.to_string(), file_name.to_string()))
298    }
299
300    fn set_directory_and_file_name(&self, directory_alias: &str, file_name: &str) -> Result<()> {
301        let dir_alias = OdpiStr::new(directory_alias);
302        let file_name = OdpiStr::new(file_name);
303        chkerr!(
304            self.ctxt(),
305            dpiLob_setDirectoryAndFileName(
306                self.handle,
307                dir_alias.ptr,
308                dir_alias.len,
309                file_name.ptr,
310                file_name.len
311            )
312        );
313        Ok(())
314    }
315
316    fn file_exists(&self) -> Result<bool> {
317        let mut exists = 0;
318        chkerr!(self.ctxt(), dpiLob_getFileExists(self.handle, &mut exists));
319        Ok(exists != 0)
320    }
321}
322
323impl Clone for LobLocator {
324    fn clone(&self) -> Self {
325        unsafe { dpiLob_addRef(self.handle) };
326        LobLocator {
327            ctxt: self.ctxt.clone(),
328            ..*self
329        }
330    }
331}
332
333impl fmt::Debug for LobLocator {
334    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335        write!(
336            f,
337            "LobLocator {{ handle: {:?}, pos: {} }} ",
338            self.handle, self.pos
339        )
340    }
341}
342
343impl Drop for LobLocator {
344    fn drop(&mut self) {
345        unsafe { dpiLob_release(self.handle) };
346    }
347}
348
349/// A trait for LOB types
350pub trait Lob {
351    /// Returns the size of the data stored in the LOB.
352    ///
353    /// **Note:** For [`Clob`] the size is in number of UCS-2 codepoints;
354    /// for [`Blob`] the size is in bytes.
355    fn size(&self) -> Result<u64>;
356
357    /// Shortens the data in the LOB so that it only contains the specified amount of
358    /// data.
359    ///
360    /// **Note:** For [`Clob`] the size is in number of UCS-2 codepoints;
361    /// for [`Blob`] the size is in bytes.
362    fn truncate(&mut self, new_size: u64) -> Result<()>;
363
364    /// Returns the chunk size, in bytes, of the internal LOB. Reading and writing
365    /// to the LOB in multiples of this size will improve performance.
366    fn chunk_size(&self) -> Result<usize>;
367
368    /// Opens the LOB resource for writing. This will improve performance when
369    /// writing to the LOB in chunks and there are functional or extensible indexes
370    /// associated with the LOB. If this function is not called, the LOB resource
371    /// will be opened and closed for each write that is performed. A call to the
372    /// [`close_resource`] should be done before performing a
373    /// call to the function [`Connection.commit`].
374    ///
375    /// [`close_resource`]: #method.close_resource
376    /// [`Connection.commit`]: Connection#method.commit
377    fn open_resource(&mut self) -> Result<()>;
378
379    /// Closes the LOB resource. This should be done when a batch of writes has
380    /// been completed so that the indexes associated with the LOB can be updated.
381    /// It should only be performed if a call to function
382    /// [`open_resource`] has been performed.
383    ///
384    /// [`open_resource`]: #method.open_resource
385    fn close_resource(&mut self) -> Result<()>;
386
387    /// Returns a boolean value indicating if the LOB resource has been opened by
388    /// making a call to the function [`open_resource`].
389    ///
390    /// [`open_resource`]: #method.open_resource
391    fn is_resource_open(&self) -> Result<bool>;
392}
393
394/// A reference to Oracle data type `BFILE`
395///
396/// This struct implements [`Read`], and [`Seek`] to
397/// read and write bytes; and seek to a position in a LOB.
398///
399/// # Examples
400///
401/// ```ignore
402/// # use oracle::test_util;
403/// use oracle::sql_type::Bfile;
404/// # let conn = test_util::connect()?;
405/// conn.execute(
406///     "insert into TestBFILEs values (1, BFILENAME('odpic_dir', 'non-existing-file'))",
407///     &[],
408/// )?;
409///
410/// let sql = "select BFILECol from TestBFILES where IntCol = 1";
411/// let mut stmt = conn.statement(sql).lob_locator().build()?;
412/// let bfile = stmt.query_row_as::<Bfile>(&[])?;
413/// let bfilename = bfile.directory_and_file_name()?;
414/// assert_eq!(bfilename.0, "odpic_dir");
415/// assert_eq!(bfilename.1, "non-existing-file");
416/// assert_eq!(bfile.file_exists()?, false);
417/// # Ok::<(), Box<dyn std::error::Error>>(())
418/// ```
419#[derive(Clone, Debug)]
420pub struct Bfile {
421    pub(crate) lob: LobLocator,
422}
423
424#[allow(dead_code)] // TODO: remove this
425impl Bfile {
426    pub(crate) fn from_raw(ctxt: &Context, handle: *mut dpiLob) -> Result<Bfile> {
427        Ok(Bfile {
428            lob: LobLocator::from_raw(ctxt, handle)?,
429        })
430    }
431
432    /// Returns a reference to a new temporary LOB which may subsequently be
433    /// written and bound to a statement.
434    pub fn new(conn: &Connection) -> Result<Bfile> {
435        let mut handle = ptr::null_mut();
436        chkerr!(
437            conn.ctxt(),
438            dpiConn_newTempLob(conn.handle(), DPI_ORACLE_TYPE_BLOB, &mut handle)
439        );
440        Bfile::from_raw(conn.ctxt(), handle)
441    }
442
443    /// Closes the LOB.
444    pub fn close(&mut self) -> Result<()> {
445        self.lob.close()
446    }
447
448    /// Returns the directory alias name and file name.
449    pub fn directory_and_file_name(&self) -> Result<(String, String)> {
450        self.lob.directory_and_file_name()
451    }
452
453    /// Sets the directory alias name and file name.
454    pub fn set_directory_and_file_name<D, F>(
455        &mut self,
456        directory_alias: D,
457        file_name: F,
458    ) -> Result<()>
459    where
460        D: AsRef<str>,
461        F: AsRef<str>,
462    {
463        self.lob
464            .set_directory_and_file_name(directory_alias.as_ref(), file_name.as_ref())
465    }
466
467    /// Returns a boolean value indicating if the file referenced by the `BFILE` type
468    /// LOB exists or not.
469    pub fn file_exists(&self) -> Result<bool> {
470        self.lob.file_exists()
471    }
472}
473
474/// A reference to Oracle data type `BLOB`
475///
476/// This struct implements [`Read`], [`Write`] and [`Seek`] to
477/// read and write bytes; and seek to a position in a LOB.
478///
479/// # Examples
480///
481/// ```
482/// # use oracle::Error;
483/// # use oracle::test_util;
484/// use oracle::sql_type::Blob;
485/// use oracle::sql_type::Lob;
486/// use std::io::BufReader;
487/// use std::io::Read;
488/// # let conn = test_util::connect()?;
489/// # conn.execute(
490/// #     "insert into TestBLOBs values (1, UTL_RAW.CAST_TO_RAW('BLOB DATA'))",
491/// #     &[],
492/// # )?;
493///
494/// let sql = "select BLOBCol from TestBLOBS where IntCol = 1";
495/// let mut stmt = conn.statement(sql).lob_locator().build()?;
496/// let blob = stmt.query_row_as::<Blob>(&[])?;
497/// let mut buf_reader = BufReader::with_capacity(blob.chunk_size()? * 16, blob);
498/// let mut buf = [0u8; 4];
499/// assert_eq!(buf_reader.read(&mut buf)?, 4); // read the first four bytes
500/// assert_eq!(&buf, b"BLOB");
501/// assert_eq!(buf_reader.read(&mut buf)?, 4); // read the next four bytes
502/// assert_eq!(&buf, b" DAT");
503/// assert_eq!(buf_reader.read(&mut buf)?, 1); // read the last one byte
504/// assert_eq!(&buf[0..1], b"A");
505/// assert_eq!(buf_reader.read(&mut buf)?, 0); // end of blob
506/// # Ok::<(), Box<dyn std::error::Error>>(())
507/// ```
508#[derive(Clone, Debug)]
509pub struct Blob {
510    pub(crate) lob: LobLocator,
511}
512
513impl Blob {
514    pub(crate) fn from_raw(ctxt: &Context, handle: *mut dpiLob) -> Result<Blob> {
515        Ok(Blob {
516            lob: LobLocator::from_raw(ctxt, handle)?,
517        })
518    }
519
520    /// Returns a reference to a new temporary LOB which may subsequently be
521    /// written and bound to a statement.
522    pub fn new(conn: &Connection) -> Result<Blob> {
523        let mut handle = ptr::null_mut();
524        chkerr!(
525            conn.ctxt(),
526            dpiConn_newTempLob(conn.handle(), DPI_ORACLE_TYPE_BLOB, &mut handle)
527        );
528        Blob::from_raw(conn.ctxt(), handle)
529    }
530
531    /// Closes the LOB.
532    pub fn close(&mut self) -> Result<()> {
533        self.lob.close()
534    }
535}
536
537/// A reference to Oracle data type `CLOB`
538///
539/// This struct implements [`Read`] and [`Write`] to read and write
540/// characters. [`Read::read`] fails when `buf` is too small
541/// to store one character. [`Write::write`] fails when `buf` contains
542/// invalid UTF-8 byte sequence.
543///
544/// This also implements [`SeekInChars`] to seek to a position in characters.
545/// Note that there is no way to seek in bytes.
546///
547/// # Notes
548///
549/// The size of LOBs returned by [`Lob::size`] and positions in
550/// [`SeekInChars`] are inaccurate if a character in the LOB requires
551/// more than one UCS-2 codepoint. That's becuase Oracle stores CLOBs
552/// and NCLOBs using the UTF-16 encoding and the number of characters
553/// is defined by the number of UCS-2 codepoints.
554#[derive(Clone, Debug)]
555pub struct Clob {
556    pub(crate) lob: LobLocator,
557}
558
559impl Clob {
560    pub(crate) fn from_raw(ctxt: &Context, handle: *mut dpiLob) -> Result<Clob> {
561        Ok(Clob {
562            lob: LobLocator::from_raw(ctxt, handle)?,
563        })
564    }
565
566    /// Returns a reference to a new temporary CLOB which may subsequently be
567    /// written and bound to a statement.
568    pub fn new(conn: &Connection) -> Result<Clob> {
569        let mut handle = ptr::null_mut();
570        chkerr!(
571            conn.ctxt(),
572            dpiConn_newTempLob(conn.handle(), DPI_ORACLE_TYPE_CLOB, &mut handle)
573        );
574        Clob::from_raw(conn.ctxt(), handle)
575    }
576
577    /// Closes the LOB.
578    pub fn close(&mut self) -> Result<()> {
579        self.lob.close()
580    }
581}
582
583/// A reference to Oracle data type `NCLOB`
584///
585/// This struct implements [`Read`] and [`Write`] to read and write
586/// characters. [`Read::read`] fails when `buf` is too small
587/// to store one character. [`Write::write`] fails when `buf` contains
588/// invalid UTF-8 byte sequence.
589///
590/// This also implements [`SeekInChars`] to seek to a position in characters.
591/// Note that there is no way to seek in bytes.
592///
593/// # Notes
594///
595/// The size of LOBs returned by [`Lob::size`] and positions in
596/// [`SeekInChars`] are inaccurate if a character in the LOB requires
597/// more than one UCS-2 codepoint. That's becuase Oracle stores CLOBs
598/// and NCLOBs using the UTF-16 encoding and the number of characters
599/// is defined by the number of UCS-2 codepoints.
600#[derive(Clone, Debug)]
601pub struct Nclob {
602    pub(crate) lob: LobLocator,
603}
604
605impl Nclob {
606    pub(crate) fn from_raw(ctxt: &Context, handle: *mut dpiLob) -> Result<Nclob> {
607        Ok(Nclob {
608            lob: LobLocator::from_raw(ctxt, handle)?,
609        })
610    }
611
612    /// Returns a reference to a new temporary NCLOB which may subsequently be
613    /// written and bound to a statement.
614    pub fn new(conn: &Connection) -> Result<Nclob> {
615        let mut handle = ptr::null_mut();
616        chkerr!(
617            conn.ctxt(),
618            dpiConn_newTempLob(conn.handle(), DPI_ORACLE_TYPE_NCLOB, &mut handle)
619        );
620        Nclob::from_raw(conn.ctxt(), handle)
621    }
622
623    /// Closes the LOB.
624    pub fn close(&mut self) -> Result<()> {
625        self.lob.close()
626    }
627}
628
629macro_rules! impl_traits {
630    (FromSql $(,$trait:ident)* for $name:ty : $type:ident) => {
631        paste::item! {
632            impl FromSql for $name {
633                fn from_sql(val: &SqlValue) -> Result<Self> {
634                    val.[<to_ $name:lower>]()
635                }
636            }
637        }
638        impl_traits!($($trait),* for $name : $type);
639    };
640
641    (ToSqlNull $(,$trait:ident)* for $name:ty : $type:ident) => {
642        paste::item! {
643            impl ToSqlNull for $name {
644                fn oratype_for_null(_conn: &Connection) -> Result<OracleType> {
645                    Ok(OracleType::[<$name:upper>])
646                }
647            }
648        }
649        impl_traits!($($trait),* for $name : $type);
650    };
651
652    (ToSql $(,$trait:ident)* for $name:ty : $type:ident) => {
653        paste::item! {
654            impl ToSql for $name {
655                fn oratype(&self, _conn: &Connection) -> Result<OracleType> {
656                    Ok(OracleType::[<$name:upper>])
657                }
658
659                fn to_sql(&self, val: &mut SqlValue) -> Result<()> {
660                    val.[<set_ $name:lower>](self)
661                }
662            }
663        }
664        impl_traits!($($trait),* for $name : $type);
665    };
666
667    (Read $(,$trait:ident)* for $name:ty : $type:ident) => {
668        paste::item! {
669            impl Read for $name {
670                fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
671                    self.lob.[<read_ $type>](buf)
672                }
673
674                fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
675                    self.lob.[<read_ $type _to_end>](buf)
676                }
677            }
678        }
679        impl_traits!($($trait),* for $name : $type);
680    };
681
682    (Write $(,$trait:ident)* for $name:ty : $type:ident) => {
683        paste::item! {
684            impl Write for $name {
685                fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
686                    self.lob.[<write_ $type>](buf)
687                }
688
689                fn flush(&mut self) -> io::Result<()> {
690                    Ok(())
691                }
692            }
693        }
694        impl_traits!($($trait),* for $name : $type);
695    };
696
697    (Seek $(,$trait:ident)* for $name:ty : $type:ident) => {
698        impl Seek for $name {
699            fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
700                self.lob.seek(pos)
701            }
702        }
703        impl_traits!($($trait),* for $name : $type);
704    };
705
706    (SeekInChars $(,$trait:ident)* for $name:ty : $type:ident) => {
707        impl SeekInChars for $name {
708            fn seek_in_chars(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
709                self.lob.seek(pos)
710            }
711        }
712        impl_traits!($($trait),* for $name : $type);
713    };
714
715    (Lob $(,$trait:ident)* for $name:ty : $type:ident) => {
716        impl Lob for $name {
717            fn size(&self) -> Result<u64> {
718                self.lob.size()
719            }
720
721            fn truncate(&mut self, new_size: u64) -> Result<()> {
722                self.lob.truncate(new_size)
723            }
724
725            fn chunk_size(&self) -> Result<usize> {
726                self.lob.chunk_size()
727            }
728
729            fn open_resource(&mut self) -> Result<()> {
730                self.lob.open_resource()
731            }
732
733            fn close_resource(&mut self) -> Result<()> {
734                self.lob.close_resource()
735            }
736
737            fn is_resource_open(&self) -> Result<bool> {
738                self.lob.is_resource_open()
739            }
740        }
741        impl_traits!($($trait),* for $name : $type);
742    };
743
744    (for $name:ty : $type:ident) => {
745    };
746}
747
748impl_traits!(FromSql, ToSqlNull, ToSql, Read, Seek, Lob for Bfile : binary);
749impl_traits!(FromSql, ToSqlNull, ToSql, Read, Write, Seek, Lob for Blob : binary);
750impl_traits!(FromSql, ToSqlNull, ToSql, Read, Write, SeekInChars, Lob for Clob : chars);
751impl_traits!(FromSql, ToSqlNull, ToSql, Read, Write, SeekInChars, Lob for Nclob : chars);
752
753#[cfg(test)]
754mod tests {
755    use super::*;
756    use crate::test_util;
757    use once_cell::sync::Lazy;
758    use std::io::Read;
759    use std::io::Seek;
760    use std::io::Write;
761
762    // one-byte characters in UTF-8
763    const CRAB_CHARS: [&str; 4] = [
764        // one-byte characters in UTF-8
765        "crab",
766        // two-byte characters in UTF-8
767        //   D0  BA  D1  80  D0  B0  D0  B1
768        //  208 186 209 128 208 176 208 177
769        "краб",
770        // three-byte character in UTF-8
771        //   E8  9F  B9
772        //  232 159 185
773        "蟹",
774        // four-byte character in UTF-8
775        //   F0  9F  A6  80
776        //  240 159 169 128
777        "🦀",
778    ];
779
780    // simple pseudo-random number generator which returns same sequence
781    struct Rand {
782        next: u64,
783    }
784
785    impl Rand {
786        fn new() -> Rand {
787            Rand { next: 1 }
788        }
789    }
790
791    impl Iterator for Rand {
792        type Item = u16;
793        fn next(&mut self) -> Option<Self::Item> {
794            // https://pubs.opengroup.org/onlinepubs/9699919799/functions/rand.html#tag_16_473_06_02
795            self.next = self.next.overflowing_mul(1103515245).0;
796            self.next = self.next.overflowing_add(12345).0;
797            Some(((self.next / 65536) % 32768) as u16)
798        }
799    }
800
801    static TEST_DATA: Lazy<String> = Lazy::new(|| {
802        Rand::new()
803            .take(100)
804            .map(|n| CRAB_CHARS[(n as usize) % CRAB_CHARS.len()])
805            .collect::<Vec<_>>()
806            .join("")
807    });
808
809    #[test]
810    fn read_write_blob() -> std::result::Result<(), std::boxed::Box<dyn std::error::Error>> {
811        let conn = test_util::connect()?;
812        let mut lob = Blob::new(&conn)?;
813        assert_eq!(lob.seek(io::SeekFrom::Current(0))?, 0);
814        assert_eq!(lob.write(TEST_DATA.as_bytes())?, TEST_DATA.len());
815        assert_eq!(lob.seek(io::SeekFrom::Current(0))?, TEST_DATA.len() as u64);
816
817        lob.open_resource()?;
818        assert!(lob.is_resource_open()?);
819        assert_eq!(lob.write(TEST_DATA.as_bytes())?, TEST_DATA.len());
820        lob.close_resource()?;
821        assert!(!lob.is_resource_open()?);
822
823        lob.seek(io::SeekFrom::Start(0))?;
824        let mut buf = vec![0; TEST_DATA.len()];
825        let len = lob.read(&mut buf)?;
826        assert_eq!(len, TEST_DATA.len());
827        assert_eq!(TEST_DATA.as_bytes(), buf);
828
829        let len = lob.read(&mut buf)?;
830        assert_eq!(len, TEST_DATA.len());
831        assert_eq!(TEST_DATA.as_bytes(), buf);
832        assert_eq!(
833            lob.seek(io::SeekFrom::Current(0))?,
834            TEST_DATA.len() as u64 * 2,
835        );
836
837        lob.truncate(TEST_DATA.len() as u64)?;
838        Ok(())
839    }
840
841    #[test]
842    fn query_blob() -> std::result::Result<(), std::boxed::Box<dyn std::error::Error>> {
843        let conn = test_util::connect()?;
844        let mut lob = Blob::new(&conn)?;
845        assert_eq!(lob.write(b"BLOB DATA")?, 9);
846        conn.execute("insert into TestBLOBs values (1, :1)", &[&lob])?;
847        let sql = "select BLOBCol from TestBLOBs where IntCol = 1";
848
849        // query blob as binary
850        let mut stmt = conn.statement(sql).build()?;
851        assert_eq!(stmt.query_row_as::<Vec<u8>>(&[])?, b"BLOB DATA");
852
853        // query blob as Blob
854        let mut stmt = conn.statement(sql).lob_locator().build()?;
855        let mut buf = Vec::new();
856        stmt.query_row_as::<Blob>(&[])?.read_to_end(&mut buf)?;
857        assert_eq!(buf, b"BLOB DATA");
858
859        // error when querying blob as Blob without `StatementBuilder.lob_locator()`.
860        let mut stmt = conn.statement(sql).build()?;
861        assert_eq!(
862            stmt.query_row_as::<Blob>(&[]).unwrap_err().to_string(),
863            "use StatementBuilder.lob_locator() instead to fetch LOB data as Blob"
864        );
865        Ok(())
866    }
867
868    #[test]
869    fn read_write_clob() -> std::result::Result<(), std::boxed::Box<dyn std::error::Error>> {
870        let conn = test_util::connect()?;
871        let mut lob = Clob::new(&conn)?;
872        let test_data_len = utf16_len(TEST_DATA.as_bytes())? as u64;
873        assert_eq!(lob.seek_in_chars(io::SeekFrom::Current(0))?, 0);
874        assert_eq!(lob.write(TEST_DATA.as_bytes())?, TEST_DATA.len());
875        assert_eq!(lob.stream_position_in_chars()?, test_data_len);
876        assert_eq!(lob.size()?, test_data_len);
877
878        lob.seek_in_chars(io::SeekFrom::Start(0))?;
879        let mut buf = vec![0; TEST_DATA.len()];
880        let mut offset = 0;
881        while offset < buf.len() {
882            let mut len = lob.read(&mut buf[offset..])?;
883            if len == 0 {
884                len = lob.read_to_end(&mut buf)?;
885                if len == 0 {
886                    panic!(
887                        "lob.read returns zero. (lob: {:?}, buf.len(): {}, offset: {}, buf: {:?}, data: {:?})",
888                        lob.lob,
889                        buf.len(),
890                        offset,
891                        &buf[0..offset],
892                        *TEST_DATA
893                    );
894                }
895            }
896            offset += len as usize;
897        }
898        assert_eq!(offset, TEST_DATA.len());
899        assert_eq!(TEST_DATA.as_bytes(), buf);
900
901        lob.write(&"🦀".as_bytes()[0..1]).unwrap_err();
902        lob.write(&"🦀".as_bytes()[0..2]).unwrap_err();
903        lob.write(&"🦀".as_bytes()[0..3]).unwrap_err();
904        assert_eq!(lob.write(&"🦀".as_bytes()[0..4])?, 4);
905
906        lob.seek_in_chars(io::SeekFrom::Current(-2))?;
907        lob.read(&mut buf[0..1]).unwrap_err(); // one byte buffer for four byte UTF-8
908        lob.read(&mut buf[0..2]).unwrap_err(); // two bytes buffer for four byte UTF-8
909        lob.read(&mut buf[0..3]).unwrap_err(); // three bytes buffer for four byte UTF-8
910        buf.fill(0);
911        assert_eq!(lob.read(&mut buf[0..4])?, 4);
912        assert_eq!(&buf[0..4], "🦀".as_bytes());
913        lob.seek_in_chars(io::SeekFrom::Current(-2))?;
914        buf.fill(0);
915        assert_eq!(lob.read(&mut buf[0..5])?, 4);
916        assert_eq!(&buf[0..4], "🦀".as_bytes());
917
918        lob.seek_in_chars(io::SeekFrom::Current(-3))?;
919        lob.read(&mut buf[0..1]).unwrap_err(); // one byte buffer for two byte UTF-8
920        buf.fill(0);
921        assert_eq!(lob.read(&mut buf[0..2])?, 2);
922        assert_eq!(&buf[0..2], "б".as_bytes());
923        lob.seek_in_chars(io::SeekFrom::Current(-1))?;
924        buf.fill(0);
925        assert_eq!(lob.read(&mut buf[0..3])?, 2);
926        assert_eq!(&buf[0..2], "б".as_bytes());
927        lob.seek_in_chars(io::SeekFrom::Current(-1))?;
928        buf.fill(0);
929        assert_eq!(lob.read(&mut buf[0..4])?, 2);
930        assert_eq!(&buf[0..2], "б".as_bytes());
931        lob.seek_in_chars(io::SeekFrom::Current(-1))?;
932        buf.fill(0);
933        assert_eq!(lob.read(&mut buf[0..5])?, 2);
934        assert_eq!(&buf[0..2], "б".as_bytes());
935        lob.seek_in_chars(io::SeekFrom::Current(-1))?;
936        buf.fill(0);
937        assert_eq!(lob.read(&mut buf[0..6])?, 6);
938        assert_eq!(&buf[0..6], "б🦀".as_bytes());
939
940        Ok(())
941    }
942}