sqlx_build_trust_sqlite/options/
parse.rs1use crate::error::Error;
2use crate::SqliteConnectOptions;
3use percent_encoding::{percent_decode_str, utf8_percent_encode, NON_ALPHANUMERIC};
4use std::borrow::Cow;
5use std::path::{Path, PathBuf};
6use std::str::FromStr;
7use std::sync::atomic::{AtomicUsize, Ordering};
8use url::Url;
9
10static IN_MEMORY_DB_SEQ: AtomicUsize = AtomicUsize::new(0);
13
14impl SqliteConnectOptions {
15 pub(crate) fn from_db_and_params(database: &str, params: Option<&str>) -> Result<Self, Error> {
16 let mut options = Self::default();
17
18 if database == ":memory:" {
19 options.in_memory = true;
20 options.shared_cache = true;
21 let seqno = IN_MEMORY_DB_SEQ.fetch_add(1, Ordering::Relaxed);
22 options.filename = Cow::Owned(PathBuf::from(format!("file:sqlx-in-memory-{seqno}")));
23 } else {
24 options.filename = Cow::Owned(
26 Path::new(
27 &*percent_decode_str(database)
28 .decode_utf8()
29 .map_err(Error::config)?,
30 )
31 .to_path_buf(),
32 );
33 }
34
35 if let Some(params) = params {
36 for (key, value) in url::form_urlencoded::parse(params.as_bytes()) {
37 match &*key {
38 "mode" => {
43 match &*value {
44 "ro" => {
45 options.read_only = true;
46 }
47
48 "rw" => {}
50
51 "rwc" => {
52 options.create_if_missing = true;
53 }
54
55 "memory" => {
56 options.in_memory = true;
57 options.shared_cache = true;
58 }
59
60 _ => {
61 return Err(Error::Configuration(
62 format!("unknown value {value:?} for `mode`").into(),
63 ));
64 }
65 }
66 }
67
68 "cache" => match &*value {
72 "private" => {
73 options.shared_cache = false;
74 }
75
76 "shared" => {
77 options.shared_cache = true;
78 }
79
80 _ => {
81 return Err(Error::Configuration(
82 format!("unknown value {value:?} for `cache`").into(),
83 ));
84 }
85 },
86
87 "immutable" => match &*value {
88 "true" | "1" => {
89 options.immutable = true;
90 }
91 "false" | "0" => {
92 options.immutable = false;
93 }
94 _ => {
95 return Err(Error::Configuration(
96 format!("unknown value {value:?} for `immutable`").into(),
97 ));
98 }
99 },
100
101 "vfs" => options.vfs = Some(Cow::Owned(value.into_owned())),
102
103 _ => {
104 return Err(Error::Configuration(
105 format!("unknown query parameter `{key}` while parsing connection URL")
106 .into(),
107 ));
108 }
109 }
110 }
111 }
112
113 Ok(options)
114 }
115
116 pub(crate) fn build_url(&self) -> Url {
117 let filename =
118 utf8_percent_encode(&self.filename.to_string_lossy(), NON_ALPHANUMERIC).to_string();
119 let mut url =
120 Url::parse(&format!("sqlite://{}", filename)).expect("BUG: generated un-parseable URL");
121
122 let mode = match (self.in_memory, self.create_if_missing, self.read_only) {
123 (true, _, _) => "memory",
124 (false, true, _) => "rwc",
125 (false, false, true) => "ro",
126 (false, false, false) => "rw",
127 };
128 url.query_pairs_mut().append_pair("mode", mode);
129
130 let cache = match self.shared_cache {
131 true => "shared",
132 false => "private",
133 };
134 url.query_pairs_mut().append_pair("cache", cache);
135
136 url.query_pairs_mut()
137 .append_pair("immutable", &self.immutable.to_string());
138
139 if let Some(vfs) = &self.vfs {
140 url.query_pairs_mut().append_pair("vfs", &vfs);
141 }
142
143 url
144 }
145}
146
147impl FromStr for SqliteConnectOptions {
148 type Err = Error;
149
150 fn from_str(mut url: &str) -> Result<Self, Self::Err> {
151 url = url
153 .trim_start_matches("sqlite://")
154 .trim_start_matches("sqlite:");
155
156 let mut database_and_params = url.splitn(2, '?');
157
158 let database = database_and_params.next().unwrap_or_default();
159 let params = database_and_params.next();
160
161 Self::from_db_and_params(database, params)
162 }
163}
164
165#[test]
166fn test_parse_in_memory() -> Result<(), Error> {
167 let options: SqliteConnectOptions = "sqlite::memory:".parse()?;
168 assert!(options.in_memory);
169 assert!(options.shared_cache);
170
171 let options: SqliteConnectOptions = "sqlite://?mode=memory".parse()?;
172 assert!(options.in_memory);
173 assert!(options.shared_cache);
174
175 let options: SqliteConnectOptions = "sqlite://:memory:".parse()?;
176 assert!(options.in_memory);
177 assert!(options.shared_cache);
178
179 let options: SqliteConnectOptions = "sqlite://?mode=memory&cache=private".parse()?;
180 assert!(options.in_memory);
181 assert!(!options.shared_cache);
182
183 Ok(())
184}
185
186#[test]
187fn test_parse_read_only() -> Result<(), Error> {
188 let options: SqliteConnectOptions = "sqlite://a.db?mode=ro".parse()?;
189 assert!(options.read_only);
190 assert_eq!(&*options.filename.to_string_lossy(), "a.db");
191
192 Ok(())
193}
194
195#[test]
196fn test_parse_shared_in_memory() -> Result<(), Error> {
197 let options: SqliteConnectOptions = "sqlite://a.db?cache=shared".parse()?;
198 assert!(options.shared_cache);
199 assert_eq!(&*options.filename.to_string_lossy(), "a.db");
200
201 Ok(())
202}
203
204#[test]
205fn it_returns_the_parsed_url() -> Result<(), Error> {
206 let url = "sqlite://test.db?mode=rw&cache=shared";
207 let options: SqliteConnectOptions = url.parse()?;
208
209 let expected_url = Url::parse(url).unwrap();
210 assert_eq!(options.build_url(), expected_url);
211
212 Ok(())
213}