1use 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 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 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 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 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 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 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
349pub trait Lob {
351 fn size(&self) -> Result<u64>;
356
357 fn truncate(&mut self, new_size: u64) -> Result<()>;
363
364 fn chunk_size(&self) -> Result<usize>;
367
368 fn open_resource(&mut self) -> Result<()>;
378
379 fn close_resource(&mut self) -> Result<()>;
386
387 fn is_resource_open(&self) -> Result<bool>;
392}
393
394#[derive(Clone, Debug)]
420pub struct Bfile {
421 pub(crate) lob: LobLocator,
422}
423
424#[allow(dead_code)] impl 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 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 pub fn close(&mut self) -> Result<()> {
445 self.lob.close()
446 }
447
448 pub fn directory_and_file_name(&self) -> Result<(String, String)> {
450 self.lob.directory_and_file_name()
451 }
452
453 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 pub fn file_exists(&self) -> Result<bool> {
470 self.lob.file_exists()
471 }
472}
473
474#[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 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 pub fn close(&mut self) -> Result<()> {
533 self.lob.close()
534 }
535}
536
537#[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 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 pub fn close(&mut self) -> Result<()> {
579 self.lob.close()
580 }
581}
582
583#[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 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 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 const CRAB_CHARS: [&str; 4] = [
764 "crab",
766 "краб",
770 "蟹",
774 "🦀",
778 ];
779
780 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 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 let mut stmt = conn.statement(sql).build()?;
851 assert_eq!(stmt.query_row_as::<Vec<u8>>(&[])?, b"BLOB DATA");
852
853 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 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(); lob.read(&mut buf[0..2]).unwrap_err(); lob.read(&mut buf[0..3]).unwrap_err(); 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(); 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}