rsdbc_sqlite/options/
parse.rs

1// From SQLx - https://github.com/launchbadge/sqlx/blob/master/sqlx-core/src/sqlite/options/parse.rs
2// https://www.sqlite.org/uri.html
3
4use std::borrow::Cow;
5use std::path::{Path, PathBuf};
6use std::str::FromStr;
7use crate::options::SqliteConnectOptions;
8use percent_encoding::percent_decode_str;
9use rsdbc_core::error::RsdbcErrors;
10
11impl FromStr for SqliteConnectOptions {
12    type Err = RsdbcErrors;
13
14    fn from_str(mut uri: &str) -> Result<Self, Self::Err> {
15        let mut options = Self::new();
16
17        // remove scheme from the URI
18        uri = uri
19            .trim_start_matches("sqlite://")
20            .trim_start_matches("sqlite:");
21
22        let mut database_and_params = uri.splitn(2, '?');
23
24        let database = database_and_params.next().unwrap_or_default();
25
26        if database == ":memory:" {
27            options.in_memory = true;
28            // setting shared_cache to true. See https://www.sqlite.org/sharedcache.html
29            options.shared_cache = true;
30            options.filename = Cow::Owned(PathBuf::from(database));
31        } else {
32            // % decode to allow for `?` or `#` in the filename
33            options.filename = Cow::Owned(
34                Path::new(
35                    &*percent_decode_str(database)
36                        .decode_utf8()
37                        .map_err(|e| RsdbcErrors::config(e.to_string()))?,
38                ).to_path_buf(),
39            );
40        }
41
42        if let Some(params) = database_and_params.next() {
43            for (key, value) in url::form_urlencoded::parse(params.as_bytes()) {
44                match &*key {
45                    // The mode query parameter determines if the new database is opened read-only,
46                    // read-write, read-write and created if it does not exist, or that the
47                    // database is a pure in-memory database that never interacts with disk,
48                    // respectively.
49                    "mode" => {
50                        match &*value {
51                            "ro" => {
52                                options.read_only = true;
53                            }
54
55                            // default
56                            "rw" => {}
57
58                            "rwc" => {
59                                options.create_if_missing = true;
60                            }
61
62                            "memory" => {
63                                options.in_memory = true;
64                                options.shared_cache = true;
65                            }
66
67                            _ => {
68                                return Err(RsdbcErrors::Configuration(
69                                    format!("unknown value {:?} for `mode`", value).into(),
70                                ));
71                            }
72                        }
73                    }
74
75                    // The cache query parameter specifies the cache behaviour across multiple
76                    // connections to the same database within the process. A shared cache is
77                    // essential for persisting data across connections to an in-memory database.
78                    "cache" => match &*value {
79                        "private" => {
80                            options.shared_cache = false;
81                        }
82
83                        "shared" => {
84                            options.shared_cache = true;
85                        }
86
87                        _ => {
88                            return Err(RsdbcErrors::Configuration(
89                                format!("unknown value {:?} for `cache`", value).into(),
90                            ));
91                        }
92                    },
93
94                    _ => {
95                        return Err(RsdbcErrors::Configuration(
96                            format!(
97                                "unknown query parameter `{}` while parsing connection URI",
98                                key
99                            ).into(),
100                        ));
101                    }
102                }
103            }
104        }
105
106        Ok(options)
107    }
108}
109
110#[test]
111fn parse_in_memory() -> Result<(), RsdbcErrors> {
112    let options: SqliteConnectOptions = "sqlite::memory:".parse()?;
113    assert!(options.in_memory);
114    assert!(options.shared_cache);
115
116    let options: SqliteConnectOptions = "sqlite://?mode=memory".parse()?;
117    assert!(options.in_memory);
118    assert!(options.shared_cache);
119
120    let options: SqliteConnectOptions = "sqlite://:memory:".parse()?;
121    assert!(options.in_memory);
122    assert!(options.shared_cache);
123
124    let options: SqliteConnectOptions = "sqlite://?mode=memory&cache=private".parse()?;
125    assert!(options.in_memory);
126    assert!(!options.shared_cache);
127
128    Ok(())
129}
130
131#[test]
132fn parse_read_only() -> Result<(), RsdbcErrors> {
133    let options: SqliteConnectOptions = "sqlite://a.db?mode=ro".parse()?;
134    assert!(options.read_only);
135    assert_eq!(&*options.filename.to_string_lossy(), "a.db");
136
137    Ok(())
138}
139
140#[test]
141fn parse_shared_in_memory() -> Result<(), RsdbcErrors> {
142    let options: SqliteConnectOptions = "sqlite://a.db?cache=shared".parse()?;
143    assert!(options.shared_cache);
144    assert_eq!(&*options.filename.to_string_lossy(), "a.db");
145
146    Ok(())
147}
148
149#[test]
150fn from_str() -> Result<(), RsdbcErrors> {
151    let options = SqliteConnectOptions::from_str("sqlite://a.db")?;
152    assert_eq!(&*options.filename.to_string_lossy(), "a.db");
153    assert!(!options.shared_cache);
154    assert!(!options.in_memory);
155    assert!(!options.read_only);
156    assert!(!options.create_if_missing);
157
158    Ok(())
159}