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