sqlx_core_oldapi/sqlite/options/
parse.rs

1use crate::error::Error;
2use crate::sqlite::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 url: &str) -> Result<Self, Self::Err> {
17        let mut options = Self::new();
18
19        // remove scheme from the URL
20        url = url
21            .trim_start_matches("sqlite://")
22            .trim_start_matches("sqlite:");
23
24        let mut database_and_params = url.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                    "vfs" => options.vfs = Some(Cow::Owned(value.into_owned())),
112
113                    _ => {
114                        return Err(Error::Configuration(
115                            format!(
116                                "unknown query parameter `{}` while parsing connection URL",
117                                key
118                            )
119                            .into(),
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}