1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 //! A Rust wrapper for mozStorage.
6 //!
7 //! mozStorage wraps the SQLite C API with support for XPCOM data structures,
8 //! asynchronous statement execution, cleanup on shutdown, and connection
9 //! cloning that propagates attached databases, pragmas, functions, and
10 //! temporary entities. It also collects timing and memory usage stats for
11 //! telemetry, and supports detailed statement logging. Additionally, mozStorage
12 //! makes it possible to use the same connection handle from JS and native
13 //! (C++ and Rust) code.
14 //!
15 //! Most mozStorage objects, like connections, statements, result rows,
16 //! and variants, are thread-safe. Each connection manages a background
17 //! thread that can be used to execute statements asynchronously, without
18 //! blocking the main thread.
19 //!
20 //! This crate provides a thin wrapper to make mozStorage easier to use
21 //! from Rust. It only wraps the synchronous API, so you can either manage
22 //! the entire connection from a background thread, or use the `moz_task`
23 //! crate to dispatch tasks to the connection's async thread. Executing
24 //! synchronous statements on the main thread is not supported, and will
25 //! assert in debug builds.
26 
27 #![allow(non_snake_case)]
28 
29 use std::{borrow::Cow, convert::TryFrom, error, fmt, ops::Deref, result};
30 
31 use nserror::{nsresult, NS_ERROR_NO_INTERFACE, NS_ERROR_UNEXPECTED};
32 use nsstring::nsCString;
33 use storage_variant::VariantType;
34 use xpcom::{
35     getter_addrefs,
36     interfaces::{
37         mozIStorageAsyncConnection, mozIStorageConnection, mozIStorageStatement, nsIEventTarget,
38         nsIThread,
39     },
40     RefPtr, XpCom,
41 };
42 
43 const SQLITE_OK: i32 = 0;
44 
45 pub type Result<T> = result::Result<T, Error>;
46 
47 /// `Conn` wraps a `mozIStorageConnection`.
48 #[derive(Clone)]
49 pub struct Conn {
50     handle: RefPtr<mozIStorageConnection>,
51 }
52 
53 // This is safe as long as our `mozIStorageConnection` is an instance of
54 // `mozilla::storage::Connection`, which is atomically reference counted.
55 unsafe impl Send for Conn {}
56 unsafe impl Sync for Conn {}
57 
58 impl Conn {
59     /// Wraps a `mozIStorageConnection` in a `Conn`.
60     #[inline]
wrap(connection: RefPtr<mozIStorageConnection>) -> Conn61     pub fn wrap(connection: RefPtr<mozIStorageConnection>) -> Conn {
62         Conn { handle: connection }
63     }
64 
65     /// Returns the wrapped `mozIStorageConnection`.
66     #[inline]
connection(&self) -> &mozIStorageConnection67     pub fn connection(&self) -> &mozIStorageConnection {
68         &self.handle
69     }
70 
71     /// Returns the maximum number of bound parameters for statements executed
72     /// on this connection.
variable_limit(&self) -> Result<usize>73     pub fn variable_limit(&self) -> Result<usize> {
74         let mut limit = 0i32;
75         let rv = unsafe { self.handle.GetVariableLimit(&mut limit) };
76         if rv.failed() {
77             return Err(Error::Limit);
78         }
79         usize::try_from(limit).map_err(|_| Error::Limit)
80     }
81 
82     /// Returns the async thread for this connection. This can be used
83     /// with `moz_task` to run synchronous statements on the storage
84     /// thread, without blocking the main thread.
thread(&self) -> Result<RefPtr<nsIThread>>85     pub fn thread(&self) -> Result<RefPtr<nsIThread>> {
86         let target = self.handle.get_interface::<nsIEventTarget>();
87         target
88             .and_then(|t| t.query_interface::<nsIThread>())
89             .ok_or(Error::NoThread)
90     }
91 
92     /// Prepares a SQL statement. `query` should only contain one SQL statement.
93     /// If `query` contains multiple statements, only the first will be prepared,
94     /// and the rest will be ignored.
prepare<Q: AsRef<str>>(&self, query: Q) -> Result<Statement>95     pub fn prepare<Q: AsRef<str>>(&self, query: Q) -> Result<Statement> {
96         let statement = self.call_and_wrap_error(DatabaseOp::Prepare, || {
97             getter_addrefs(|p| unsafe {
98                 self.handle
99                     .CreateStatement(&*nsCString::from(query.as_ref()), p)
100             })
101         })?;
102         Ok(Statement {
103             conn: self,
104             handle: statement,
105         })
106     }
107 
108     /// Executes a SQL statement. `query` may contain one or more
109     /// semicolon-separated SQL statements.
exec<Q: AsRef<str>>(&self, query: Q) -> Result<()>110     pub fn exec<Q: AsRef<str>>(&self, query: Q) -> Result<()> {
111         self.call_and_wrap_error(DatabaseOp::Exec, || {
112             unsafe {
113                 self.handle
114                     .ExecuteSimpleSQL(&*nsCString::from(query.as_ref()))
115             }
116             .to_result()
117         })
118     }
119 
120     /// Opens a transaction with the default transaction behavior for this
121     /// connection. The transaction should be committed when done. Uncommitted
122     /// `Transaction`s will automatically roll back when they go out of scope.
transaction(&mut self) -> Result<Transaction>123     pub fn transaction(&mut self) -> Result<Transaction> {
124         let behavior = self.get_default_transaction_behavior();
125         Transaction::new(self, behavior)
126     }
127 
128     /// Indicates if a transaction is currently open on this connection.
129     /// Attempting to open a new transaction when one is already in progress
130     /// will fail with a "cannot start a transaction within a transaction"
131     /// error.
132     ///
133     /// Note that this is `true` even if the transaction was started by another
134     /// caller, like `Sqlite.jsm` or `mozStorageTransaction` from C++. See the
135     /// explanation above `mozIStorageConnection.transactionInProgress` for why
136     /// this matters.
transaction_in_progress(&self) -> Result<bool>137     pub fn transaction_in_progress(&self) -> Result<bool> {
138         let mut in_progress = false;
139         unsafe { self.handle.GetTransactionInProgress(&mut in_progress) }.to_result()?;
140         Ok(in_progress)
141     }
142 
143     /// Opens a transaction with the requested behavior.
transaction_with_behavior( &mut self, behavior: TransactionBehavior, ) -> Result<Transaction>144     pub fn transaction_with_behavior(
145         &mut self,
146         behavior: TransactionBehavior,
147     ) -> Result<Transaction> {
148         Transaction::new(self, behavior)
149     }
150 
get_default_transaction_behavior(&self) -> TransactionBehavior151     fn get_default_transaction_behavior(&self) -> TransactionBehavior {
152         let mut typ = 0i32;
153         let rv = unsafe { self.handle.GetDefaultTransactionType(&mut typ) };
154         if rv.failed() {
155             return TransactionBehavior::Deferred;
156         }
157         match typ as i64 {
158             mozIStorageAsyncConnection::TRANSACTION_IMMEDIATE => TransactionBehavior::Immediate,
159             mozIStorageAsyncConnection::TRANSACTION_EXCLUSIVE => TransactionBehavior::Exclusive,
160             _ => TransactionBehavior::Deferred,
161         }
162     }
163 
164     /// Invokes a storage operation and returns the last SQLite error if the
165     /// operation fails. This lets `Conn::{prepare, exec}` and
166     /// `Statement::{step, execute}` return more detailed errors, as the
167     /// `nsresult` codes that mozStorage uses are often too generic. For
168     /// example, `NS_ERROR_FAILURE` might be anything from a SQL syntax error
169     /// to an invalid column name in a trigger.
170     ///
171     /// Note that the last error may not be accurate if the underlying
172     /// `mozIStorageConnection` is used concurrently from multiple threads.
173     /// Multithreaded callers that share a connection should serialize their
174     /// uses.
call_and_wrap_error<T>( &self, op: DatabaseOp, func: impl FnOnce() -> result::Result<T, nsresult>, ) -> Result<T>175     fn call_and_wrap_error<T>(
176         &self,
177         op: DatabaseOp,
178         func: impl FnOnce() -> result::Result<T, nsresult>,
179     ) -> Result<T> {
180         func().or_else(|rv| -> Result<T> {
181             let mut code = 0i32;
182             unsafe { self.handle.GetLastError(&mut code) }.to_result()?;
183             Err(if code != SQLITE_OK {
184                 let mut message = nsCString::new();
185                 unsafe { self.handle.GetLastErrorString(&mut *message) }.to_result()?;
186                 Error::Database {
187                     rv,
188                     op,
189                     code,
190                     message,
191                 }
192             } else {
193                 rv.into()
194             })
195         })
196     }
197 }
198 
199 pub enum TransactionBehavior {
200     Deferred,
201     Immediate,
202     Exclusive,
203 }
204 
205 pub struct Transaction<'c> {
206     conn: &'c mut Conn,
207     active: bool,
208 }
209 
210 impl<'c> Transaction<'c> {
211     /// Opens a transaction on `conn` with the given `behavior`.
new(conn: &'c mut Conn, behavior: TransactionBehavior) -> Result<Transaction<'c>>212     fn new(conn: &'c mut Conn, behavior: TransactionBehavior) -> Result<Transaction<'c>> {
213         conn.exec(match behavior {
214             TransactionBehavior::Deferred => "BEGIN DEFERRED",
215             TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
216             TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
217         })?;
218         Ok(Transaction { conn, active: true })
219     }
220 
221     /// Commits the transaction.
commit(mut self) -> Result<()>222     pub fn commit(mut self) -> Result<()> {
223         if self.active {
224             self.conn.exec("COMMIT")?;
225             self.active = false;
226         }
227         Ok(())
228     }
229 
230     /// Rolls the transaction back.
rollback(mut self) -> Result<()>231     pub fn rollback(mut self) -> Result<()> {
232         self.abort()
233     }
234 
abort(&mut self) -> Result<()>235     fn abort(&mut self) -> Result<()> {
236         if self.active {
237             self.conn.exec("ROLLBACK")?;
238             self.active = false;
239         }
240         Ok(())
241     }
242 }
243 
244 impl<'c> Deref for Transaction<'c> {
245     type Target = Conn;
246 
deref(&self) -> &Conn247     fn deref(&self) -> &Conn {
248         self.conn
249     }
250 }
251 
252 impl<'c> Drop for Transaction<'c> {
drop(&mut self)253     fn drop(&mut self) {
254         let _ = self.abort();
255     }
256 }
257 
258 pub struct Statement<'c> {
259     conn: &'c Conn,
260     handle: RefPtr<mozIStorageStatement>,
261 }
262 
263 impl<'c> Statement<'c> {
264     /// Binds a parameter at the given `index` to the prepared statement.
265     /// `value` is any type that can be converted into a `Variant`.
bind_by_index<V: VariantType>(&mut self, index: u32, value: V) -> Result<()>266     pub fn bind_by_index<V: VariantType>(&mut self, index: u32, value: V) -> Result<()> {
267         let variant = value.into_variant();
268         unsafe { self.handle.BindByIndex(index as u32, variant.coerce()) }
269             .to_result()
270             .map_err(|rv| Error::BindByIndex {
271                 rv,
272                 data_type: V::type_name(),
273                 index,
274             })
275     }
276 
277     /// Binds a parameter with the given `name` to the prepared statement.
bind_by_name<N: AsRef<str>, V: VariantType>(&mut self, name: N, value: V) -> Result<()>278     pub fn bind_by_name<N: AsRef<str>, V: VariantType>(&mut self, name: N, value: V) -> Result<()> {
279         let name = name.as_ref();
280         let variant = value.into_variant();
281         unsafe {
282             self.handle
283                 .BindByName(&*nsCString::from(name), variant.coerce())
284         }
285         .to_result()
286         .map_err(|rv| Error::BindByName {
287             rv,
288             data_type: V::type_name(),
289             name: name.into(),
290         })
291     }
292 
293     /// Executes the statement and returns the next row of data.
step<'s>(&'s mut self) -> Result<Option<Step<'c, 's>>>294     pub fn step<'s>(&'s mut self) -> Result<Option<Step<'c, 's>>> {
295         let has_more = self.conn.call_and_wrap_error(DatabaseOp::Step, || {
296             let mut has_more = false;
297             unsafe { self.handle.ExecuteStep(&mut has_more) }.to_result()?;
298             Ok(has_more)
299         })?;
300         Ok(if has_more { Some(Step(self)) } else { None })
301     }
302 
303     /// Executes the statement once, discards any data, and resets the
304     /// statement.
execute(&mut self) -> Result<()>305     pub fn execute(&mut self) -> Result<()> {
306         self.conn.call_and_wrap_error(DatabaseOp::Execute, || {
307             unsafe { self.handle.Execute() }.to_result()
308         })
309     }
310 
311     /// Resets the prepared statement so that it's ready to be executed
312     /// again, and clears any bound parameters.
reset(&mut self) -> Result<()>313     pub fn reset(&mut self) -> Result<()> {
314         unsafe { self.handle.Reset() }.to_result()?;
315         Ok(())
316     }
317 
get_column_index(&self, name: &str) -> Result<u32>318     fn get_column_index(&self, name: &str) -> Result<u32> {
319         let mut index = 0u32;
320         let rv = unsafe {
321             self.handle
322                 .GetColumnIndex(&*nsCString::from(name), &mut index)
323         };
324         if rv.succeeded() {
325             Ok(index)
326         } else {
327             Err(Error::InvalidColumn {
328                 rv,
329                 name: name.into(),
330             })
331         }
332     }
333 
get_column_value<T: VariantType>(&self, index: u32) -> result::Result<T, nsresult>334     fn get_column_value<T: VariantType>(&self, index: u32) -> result::Result<T, nsresult> {
335         let variant = getter_addrefs(|p| unsafe { self.handle.GetVariant(index, p) })?;
336         let value = T::from_variant(variant.coerce())?;
337         Ok(value)
338     }
339 }
340 
341 impl<'c> Drop for Statement<'c> {
drop(&mut self)342     fn drop(&mut self) {
343         unsafe { self.handle.Finalize() };
344     }
345 }
346 
347 /// A step is the next row in the result set for a statement.
348 pub struct Step<'c, 's>(&'s mut Statement<'c>);
349 
350 impl<'c, 's> Step<'c, 's> {
351     /// Returns the value of the column at `index` for the current row.
get_by_index<T: VariantType>(&self, index: u32) -> Result<T>352     pub fn get_by_index<T: VariantType>(&self, index: u32) -> Result<T> {
353         self.0
354             .get_column_value(index)
355             .map_err(|rv| Error::GetByIndex {
356                 rv,
357                 data_type: T::type_name(),
358                 index,
359             })
360     }
361 
362     /// A convenience wrapper that returns the default value for the column
363     /// at `index` if `NULL`.
get_by_index_or_default<T: VariantType + Default>(&self, index: u32) -> T364     pub fn get_by_index_or_default<T: VariantType + Default>(&self, index: u32) -> T {
365         self.get_by_index(index).unwrap_or_default()
366     }
367 
368     /// Returns the value of the column specified by `name` for the current row.
get_by_name<N: AsRef<str>, T: VariantType>(&self, name: N) -> Result<T>369     pub fn get_by_name<N: AsRef<str>, T: VariantType>(&self, name: N) -> Result<T> {
370         let name = name.as_ref();
371         let index = self.0.get_column_index(name)?;
372         self.0
373             .get_column_value(index)
374             .map_err(|rv| Error::GetByName {
375                 rv,
376                 data_type: T::type_name(),
377                 name: name.into(),
378             })
379     }
380 
381     /// Returns the default value for the column with the given `name`, or the
382     /// default if the column is `NULL`.
get_by_name_or_default<N: AsRef<str>, T: VariantType + Default>(&self, name: N) -> T383     pub fn get_by_name_or_default<N: AsRef<str>, T: VariantType + Default>(&self, name: N) -> T {
384         self.get_by_name(name).unwrap_or_default()
385     }
386 }
387 
388 /// A database operation, included for better context in error messages.
389 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
390 pub enum DatabaseOp {
391     Exec,
392     Prepare,
393     Step,
394     Execute,
395 }
396 
397 impl DatabaseOp {
398     /// Returns a description of the operation to include in an error message.
what(&self) -> &'static str399     pub fn what(&self) -> &'static str {
400         match self {
401             DatabaseOp::Exec => "execute SQL string",
402             DatabaseOp::Prepare => "prepare statement",
403             DatabaseOp::Step => "step statement",
404             DatabaseOp::Execute => "execute statement",
405         }
406     }
407 }
408 
409 /// Storage errors.
410 #[derive(Debug)]
411 pub enum Error {
412     /// A connection doesn't have a usable async thread. The connection might be
413     /// closed, or the thread manager may have shut down.
414     NoThread,
415 
416     /// Failed to get a limit for a database connection.
417     Limit,
418 
419     /// A database operation failed. The error includes a SQLite result code,
420     /// and an explanation string.
421     Database {
422         rv: nsresult,
423         op: DatabaseOp,
424         code: i32,
425         message: nsCString,
426     },
427 
428     /// A parameter with the given data type couldn't be bound at this index,
429     /// likely because the index is out of range.
430     BindByIndex {
431         rv: nsresult,
432         data_type: Cow<'static, str>,
433         index: u32,
434     },
435 
436     /// A parameter with the given type couldn't be bound to this name, likely
437     /// because the statement doesn't have a matching `:`-prefixed parameter
438     /// with the name.
439     BindByName {
440         rv: nsresult,
441         data_type: Cow<'static, str>,
442         name: String,
443     },
444 
445     /// A column with this name doesn't exist.
446     InvalidColumn { rv: nsresult, name: String },
447 
448     /// A value of the given type couldn't be accessed at this index. This is
449     /// the error returned when a type conversion fails; for example, requesting
450     /// an `nsString` instead of an `Option<nsString>` when the column is `NULL`.
451     GetByIndex {
452         rv: nsresult,
453         data_type: Cow<'static, str>,
454         index: u32,
455     },
456 
457     /// A value of the given type couldn't be accessed for the column with
458     /// this name.
459     GetByName {
460         rv: nsresult,
461         data_type: Cow<'static, str>,
462         name: String,
463     },
464 
465     /// A storage operation failed for other reasons.
466     Other(nsresult),
467 }
468 
469 impl error::Error for Error {
source(&self) -> Option<&(dyn error::Error + 'static)>470     fn source(&self) -> Option<&(dyn error::Error + 'static)> {
471         None
472     }
473 }
474 
475 impl From<nsresult> for Error {
from(rv: nsresult) -> Error476     fn from(rv: nsresult) -> Error {
477         Error::Other(rv)
478     }
479 }
480 
481 impl From<Error> for nsresult {
from(err: Error) -> nsresult482     fn from(err: Error) -> nsresult {
483         match err {
484             Error::NoThread => NS_ERROR_NO_INTERFACE,
485             Error::Limit => NS_ERROR_UNEXPECTED,
486             Error::Database { rv, .. }
487             | Error::BindByIndex { rv, .. }
488             | Error::BindByName { rv, .. }
489             | Error::InvalidColumn { rv, .. }
490             | Error::GetByIndex { rv, .. }
491             | Error::GetByName { rv, .. }
492             | Error::Other(rv) => rv,
493         }
494     }
495 }
496 
497 impl fmt::Display for Error {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result498     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
499         match self {
500             Error::NoThread => f.write_str("Async thread unavailable for storage connection"),
501             Error::Limit => f.write_str("Failed to get limit for storage connection"),
502             Error::Database {
503                 op, code, message, ..
504             } => {
505                 if message.is_empty() {
506                     write!(f, "Failed to {} with code {}", op.what(), code)
507                 } else {
508                     write!(
509                         f,
510                         "Failed to {} with code {} ({})",
511                         op.what(),
512                         code,
513                         message
514                     )
515                 }
516             }
517             Error::BindByIndex {
518                 data_type, index, ..
519             } => write!(f, "Can't bind {} at {}", data_type, index),
520             Error::BindByName {
521                 data_type, name, ..
522             } => write!(f, "Can't bind {} to named parameter {}", data_type, name),
523             Error::InvalidColumn { name, .. } => write!(f, "Column {} doesn't exist", name),
524             Error::GetByIndex {
525                 data_type, index, ..
526             } => write!(f, "Can't get {} at {}", data_type, index),
527             Error::GetByName {
528                 data_type, name, ..
529             } => write!(f, "Can't get {} for column {}", data_type, name),
530             Error::Other(rv) => write!(f, "Storage operation failed with {}", rv.error_name()),
531         }
532     }
533 }
534