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