cdbc_sqlite/options/
parse.rs

1use cdbc::error::Error;
2use crate::SqliteConnectOptions;
3use percent_encoding::percent_decode_str;
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:sqlx-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(Error::config)?,
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::Configuration(
72                                    format!("unknown value {:?} for `mode`", value).into(),
73                                ));
74                            }
75                        }
76                    }
77
78                    // The cache query parameter specifies the cache behaviour across multiple
79                    // connections to the same database within the process. A shared cache is
80                    // essential for persisting data across connections to an in-memory database.
81                    "cache" => match &*value {
82                        "private" => {
83                            options.shared_cache = false;
84                        }
85
86                        "shared" => {
87                            options.shared_cache = true;
88                        }
89
90                        _ => {
91                            return Err(Error::Configuration(
92                                format!("unknown value {:?} for `cache`", value).into(),
93                            ));
94                        }
95                    },
96
97                    "immutable" => match &*value {
98                        "true" | "1" => {
99                            options.immutable = true;
100                        }
101                        "false" | "0" => {
102                            options.immutable = false;
103                        }
104                        _ => {
105                            return Err(Error::Configuration(
106                                format!("unknown value {:?} for `immutable`", value).into(),
107                            ));
108                        }
109                    },
110
111                    _ => {
112                        return Err(Error::Configuration(
113                            format!(
114                                "unknown query parameter `{}` while parsing connection URI",
115                                key
116                            )
117                            .into(),
118                        ));
119                    }
120                }
121            }
122        }
123
124        Ok(options)
125    }
126}
127
128#[test]
129fn test_parse_in_memory() -> Result<(), Error> {
130    let options: SqliteConnectOptions = "sqlite::memory:".parse()?;
131    assert!(options.in_memory);
132    assert!(options.shared_cache);
133
134    let options: SqliteConnectOptions = "sqlite://?mode=memory".parse()?;
135    assert!(options.in_memory);
136    assert!(options.shared_cache);
137
138    let options: SqliteConnectOptions = "sqlite://:memory:".parse()?;
139    assert!(options.in_memory);
140    assert!(options.shared_cache);
141
142    let options: SqliteConnectOptions = "sqlite://?mode=memory&cache=private".parse()?;
143    assert!(options.in_memory);
144    assert!(!options.shared_cache);
145
146    Ok(())
147}
148
149#[test]
150fn test_parse_read_only() -> Result<(), Error> {
151    let options: SqliteConnectOptions = "sqlite://a.db?mode=ro".parse()?;
152    assert!(options.read_only);
153    assert_eq!(&*options.filename.to_string_lossy(), "a.db");
154
155    Ok(())
156}
157
158#[test]
159fn test_parse_shared_in_memory() -> Result<(), Error> {
160    let options: SqliteConnectOptions = "sqlite://a.db?cache=shared".parse()?;
161    assert!(options.shared_cache);
162    assert_eq!(&*options.filename.to_string_lossy(), "a.db");
163
164    Ok(())
165}