rbdc_sqlite/options/
parse.rs

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