sqlite_rs/io/
mod.rs

1use crate::result::{SqliteError, SqliteResult};
2use crate::traits::SqliteRawIo;
3use crate::{error, trace};
4use std::fmt::{Debug, Display};
5use std::fs::File;
6use std::io::Seek;
7use std::io::SeekFrom;
8use std::io::{Cursor, Read};
9use std::path::PathBuf;
10use std::str::FromStr;
11
12// #[cfg(test)]
13// mod tests;
14
15pub struct SqliteIo {
16  mode: SqliteIoMode,
17  raw_io: Box<dyn SqliteRawIo>,
18}
19
20impl Debug for SqliteIo {
21  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22    f.debug_struct("SqliteIo")
23      .field("mode", &self.mode)
24      .finish()
25  }
26}
27
28#[derive(Debug, PartialEq, Eq)]
29pub enum SqliteIoMode {
30  InMemory,
31  File,
32}
33impl Display for SqliteIoMode {
34  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35    write!(f, "{self:?}")
36  }
37}
38impl FromStr for SqliteIoMode {
39  type Err = SqliteError;
40
41  fn from_str(uri_str: &str) -> Result<Self, Self::Err> {
42    let mode = match uri_str.trim() {
43      ":memory:" => SqliteIoMode::InMemory,
44      _ => SqliteIoMode::File,
45    };
46    Ok(mode)
47  }
48}
49impl SqliteIo {
50  pub fn open(input: impl AsRef<str>) -> SqliteResult<Self> {
51    let conn_str = input.as_ref();
52    let mode = conn_str.parse::<SqliteIoMode>()?;
53    match mode {
54      SqliteIoMode::InMemory => {
55        let cursor: Box<Cursor<Vec<u8>>> = Box::new(Cursor::new(vec![]));
56        let raw_io = cursor as Box<dyn SqliteRawIo>;
57        Ok(Self { mode, raw_io })
58      }
59
60      SqliteIoMode::File => {
61        let uri = conn_str.parse::<SqliteUri>()?;
62        let file = Box::new(File::open(uri.path())?);
63        let raw_io: Box<dyn SqliteRawIo> = file as Box<dyn SqliteRawIo>;
64        Ok(Self { mode, raw_io })
65      }
66    }
67  }
68
69  pub fn is_empty(&mut self) -> SqliteResult<bool> {
70    if self.raw_io.read(&mut [0u8; 1])? == 0 {
71      Ok(true)
72    } else {
73      Ok(false)
74    }
75  }
76
77  pub fn read(&mut self, buf: &mut [u8]) -> SqliteResult<usize> {
78    Ok(self.raw_io.read(buf)?)
79  }
80
81  pub fn seek(&mut self, pos: u64) -> SqliteResult<u64> {
82    Ok(self.raw_io.seek(SeekFrom::Start(pos))?)
83  }
84
85  pub fn rewind(&mut self) -> SqliteResult<()> {
86    Ok(self.raw_io.rewind()?)
87  }
88  pub fn stream_position(&mut self) -> SqliteResult<u64> {
89    Ok(self.raw_io.stream_position()?)
90  }
91
92  pub fn close() -> SqliteResult<()> {
93    todo!("Close not yet implemented");
94  }
95
96  pub fn mode(&self) -> &SqliteIoMode {
97    &self.mode
98  }
99}
100
101#[derive(Debug)]
102pub struct SqliteUri {
103  uri: String,
104  path: PathBuf,
105  mode: SqliteUriFileMode,
106}
107
108impl SqliteUri {
109  pub fn path(&self) -> &PathBuf {
110    &self.path
111  }
112}
113impl FromStr for SqliteUri {
114  type Err = SqliteError;
115
116  fn from_str(uri_str: &str) -> Result<Self, Self::Err> {
117    let mut iter_uri = uri_str.split("://");
118    let maybe_schema = iter_uri.next();
119    let maybe_path = iter_uri.next();
120    match (maybe_schema, maybe_path) {
121      (Some(_), Some(path_str)) => {
122        let mut iter_path = path_str.split('?');
123        let file_path = iter_path
124          .next()
125          .ok_or(SqliteError::Custom("Filepath not defined".into()))
126          .map_err(|err| {
127            error!("{err}");
128            err
129          })?;
130        let mode = iter_path
131          .next()
132          .and_then(|s| {
133            trace!("Trying to parse mode [{s}]");
134            s.parse::<SqliteUriFileMode>().ok()
135          })
136          .unwrap_or_default();
137        trace!("{mode:?}");
138        let file_path = PathBuf::from_str(file_path).unwrap();
139        trace!("{file_path:?}");
140        let path = if mode == SqliteUriFileMode::ReadWriteCreate {
141          create_file(&file_path)?;
142          file_path
143        } else {
144          file_path.canonicalize().map_err(|err| {
145            error!("Error on open file [{uri_str}]: [{err}].");
146            error!("Hint: You can change mode to `?mode=rwc` or check you file path.");
147            SqliteError::Custom("Error on parsing file path".into())
148          })?
149        };
150        // TODO: Implement modes
151        // if file_path.exists()  .not() {
152        //   return Err(Sqlite);
153        // }
154
155        Ok(Self {
156          uri: uri_str.into(),
157          path,
158          mode,
159        })
160      }
161      _ => {
162        error!("Error on parsing sqlite connection uri[{}]", uri_str);
163        Err(SqliteError::Custom(
164          "Error on parsing sqlite connection uri".into(),
165        ))
166      }
167    }
168  }
169}
170
171///  The mode query parameter determines if the new database is opened
172/// read-only, read-write, read-write and created if it does not exist, or that
173/// the database is a pure in-memory database that never interacts with disk,
174/// respectively.
175///
176/// *Reference:* https://www.sqlite.org/uri.html#urimode
177#[derive(Debug, Default, PartialEq, Eq)]
178pub enum SqliteUriFileMode {
179  ReadOnly,
180  #[default]
181  ReadWrite,
182  ReadWriteCreate,
183}
184
185impl FromStr for SqliteUriFileMode {
186  type Err = SqliteError;
187
188  fn from_str(s: &str) -> Result<Self, Self::Err> {
189    trace!("impl FromStr for SqliteUriFileMode {s}");
190    match s {
191      "mode=ro" => Ok(Self::ReadOnly),
192      "mode=rw" => Ok(Self::ReadWrite),
193      "mode=rwc" => Ok(Self::ReadWriteCreate),
194      _ => Err(SqliteError::InvalidFileUriMode),
195    }
196  }
197}
198
199fn create_file(path: &PathBuf) -> SqliteResult<()> {
200  let maybe_parent_dir = path.parent();
201  maybe_parent_dir
202    .map(std::fs::create_dir_all)
203    .transpose()?;
204  File::create(path)?;
205  Ok(())
206}