/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! A Rust wrapper for mozStorage. //! //! mozStorage wraps the SQLite C API with support for XPCOM data structures, //! asynchronous statement execution, cleanup on shutdown, and connection //! cloning that propagates attached databases, pragmas, functions, and //! temporary entities. It also collects timing and memory usage stats for //! telemetry, and supports detailed statement logging. Additionally, mozStorage //! makes it possible to use the same connection handle from JS and native //! (C++ and Rust) code. //! //! Most mozStorage objects, like connections, statements, result rows, //! and variants, are thread-safe. Each connection manages a background //! thread that can be used to execute statements asynchronously, without //! blocking the main thread. //! //! This crate provides a thin wrapper to make mozStorage easier to use //! from Rust. It only wraps the synchronous API, so you can either manage //! the entire connection from a background thread, or use the `moz_task` //! crate to dispatch tasks to the connection's async thread. Executing //! synchronous statements on the main thread is not supported, and will //! assert in debug builds.
#![allow(non_snake_case)]
use std::{borrow::Cow, convert::TryFrom, error, fmt, ops::Deref, result};
use nserror::{nsresult, NS_ERROR_NO_INTERFACE, NS_ERROR_UNEXPECTED}; use nsstring::nsCString; use storage_variant::VariantType; use xpcom::{
getter_addrefs,
interfaces::{
mozIStorageAsyncConnection, mozIStorageConnection, mozIStorageStatement, nsIEventTarget,
nsIThread,
},
RefPtr, XpCom,
};
// This is safe as long as our `mozIStorageConnection` is an instance of // `mozilla::storage::Connection`, which is atomically reference counted. unsafeimpl Send for Conn {} unsafeimpl Sync for Conn {}
impl Conn { /// Wraps a `mozIStorageConnection` in a `Conn`. #[inline] pubfn wrap(connection: RefPtr<mozIStorageConnection>) -> Conn {
Conn { handle: connection }
}
/// Returns the maximum number of bound parameters for statements executed /// on this connection. pubfn variable_limit(&self) -> Result<usize> { letmut limit = 0i32; let rv = unsafe { self.handle.GetVariableLimit(&mut limit) }; if rv.failed() { return Err(Error::Limit);
}
usize::try_from(limit).map_err(|_| Error::Limit)
}
/// Returns the async thread for this connection. This can be used /// with `moz_task` to run synchronous statements on the storage /// thread, without blocking the main thread. pubfn thread(&self) -> Result<RefPtr<nsIThread>> { let target = self.handle.get_interface::<nsIEventTarget>();
target
.and_then(|t| t.query_interface::<nsIThread>())
.ok_or(Error::NoThread)
}
/// Prepares a SQL statement. `query` should only contain one SQL statement. /// If `query` contains multiple statements, only the first will be prepared, /// and the rest will be ignored. pubfn prepare<Q: AsRef<str>>(&self, query: Q) -> Result<Statement> { let statement = self.call_and_wrap_error(DatabaseOp::Prepare, || {
getter_addrefs(|p| unsafe { self.handle
.CreateStatement(&*nsCString::from(query.as_ref()), p)
})
})?;
Ok(Statement {
conn: self,
handle: statement,
})
}
/// Executes a SQL statement. `query` may contain one or more /// semicolon-separated SQL statements. pubfn exec<Q: AsRef<str>>(&self, query: Q) -> Result<()> { self.call_and_wrap_error(DatabaseOp::Exec, || { unsafe { self.handle
.ExecuteSimpleSQL(&*nsCString::from(query.as_ref()))
}
.to_result()
})
}
/// Opens a transaction with the default transaction behavior for this /// connection. The transaction should be committed when done. Uncommitted /// `Transaction`s will automatically roll back when they go out of scope. pubfn transaction(&mutself) -> Result<Transaction> { let behavior = self.get_default_transaction_behavior();
Transaction::new(self, behavior)
}
/// Indicates if a transaction is currently open on this connection. /// Attempting to open a new transaction when one is already in progress /// will fail with a "cannot start a transaction within a transaction" /// error. /// /// Note that this is `true` even if the transaction was started by another /// caller, like `Sqlite.sys.mjs` or `mozStorageTransaction` from C++. See the /// explanation above `mozIStorageConnection.transactionInProgress` for why /// this matters. pubfn transaction_in_progress(&self) -> Result<bool> { letmut in_progress = false; unsafe { self.handle.GetTransactionInProgress(&mut in_progress) }.to_result()?;
Ok(in_progress)
}
/// Opens a transaction with the requested behavior. pubfn transaction_with_behavior(
&mutself,
behavior: TransactionBehavior,
) -> Result<Transaction> {
Transaction::new(self, behavior)
}
fn get_default_transaction_behavior(&self) -> TransactionBehavior { letmut typ = 0i32; let rv = unsafe { self.handle.GetDefaultTransactionType(&mut typ) }; if rv.failed() { return TransactionBehavior::Deferred;
} match typ {
mozIStorageAsyncConnection::TRANSACTION_IMMEDIATE => TransactionBehavior::Immediate,
mozIStorageAsyncConnection::TRANSACTION_EXCLUSIVE => TransactionBehavior::Exclusive,
_ => TransactionBehavior::Deferred,
}
}
/// Invokes a storage operation and returns the last SQLite error if the /// operation fails. This lets `Conn::{prepare, exec}` and /// `Statement::{step, execute}` return more detailed errors, as the /// `nsresult` codes that mozStorage uses are often too generic. For /// example, `NS_ERROR_FAILURE` might be anything from a SQL syntax error /// to an invalid column name in a trigger. /// /// Note that the last error may not be accurate if the underlying /// `mozIStorageConnection` is used concurrently from multiple threads. /// Multithreaded callers that share a connection should serialize their /// uses. fn call_and_wrap_error<T>(
&self,
op: DatabaseOp,
func: impl FnOnce() -> result::Result<T, nsresult>,
) -> Result<T> {
func().or_else(|rv| -> Result<T> { letmut code = 0i32; unsafe { self.handle.GetLastError(&mut code) }.to_result()?;
Err(if code != SQLITE_OK { letmut message = nsCString::new(); unsafe { self.handle.GetLastErrorString(&mut *message) }.to_result()?;
Error::Database {
rv,
op,
code,
message,
}
} else {
rv.into()
})
})
}
}
impl<'c> Statement<'c> { /// Binds a parameter at the given `index` to the prepared statement. /// `value` is any type that can be converted into a `Variant`. pubfn bind_by_index<V: VariantType>(&mutself, index: u32, value: V) -> Result<()> { let variant = value.into_variant(); unsafe { self.handle.BindByIndex(index as u32, variant.coerce()) }
.to_result()
.map_err(|rv| Error::BindByIndex {
rv,
data_type: V::type_name(),
index,
})
}
/// Binds a parameter with the given `name` to the prepared statement. pubfn bind_by_name<N: AsRef<str>, V: VariantType>(&mutself, name: N, value: V) -> Result<()> { let name = name.as_ref(); let variant = value.into_variant(); unsafe { self.handle
.BindByName(&*nsCString::from(name), variant.coerce())
}
.to_result()
.map_err(|rv| Error::BindByName {
rv,
data_type: V::type_name(),
name: name.into(),
})
}
/// Executes the statement and returns the next row of data. pubfn step<'s>(&'s mutself) -> Result<Option<Step<'c, 's>>> { let has_more = self.conn.call_and_wrap_error(DatabaseOp::Step, || { letmut has_more = false; unsafe { self.handle.ExecuteStep(&mut has_more) }.to_result()?;
Ok(has_more)
})?;
Ok(if has_more { Some(Step(self)) } else { None })
}
/// Executes the statement once, discards any data, and resets the /// statement. pubfn execute(&mutself) -> Result<()> { self.conn.call_and_wrap_error(DatabaseOp::Execute, || { unsafe { self.handle.Execute() }.to_result()
})
}
/// Resets the prepared statement so that it's ready to be executed /// again, and clears any bound parameters. pubfn reset(&mutself) -> Result<()> { unsafe { self.handle.Reset() }.to_result()?;
Ok(())
}
fn get_column_value<T: VariantType>(&self, index: u32) -> result::Result<T, nsresult> { let variant = getter_addrefs(|p| unsafe { self.handle.GetVariant(index, p) })?; let value = T::from_variant(variant.coerce())?;
Ok(value)
}
}
impl<'c> Drop for Statement<'c> { fn drop(&mutself) { unsafe { self.handle.Finalize() };
}
}
/// A step is the next row in the result set for a statement. pubstruct Step<'c, 's>(&'s mut Statement<'c>);
impl<'c, 's> Step<'c, 's> { /// Returns the value of the column at `index` for the current row. pubfn get_by_index<T: VariantType>(&self, index: u32) -> Result<T> { self.0
.get_column_value(index)
.map_err(|rv| Error::GetByIndex {
rv,
data_type: T::type_name(),
index,
})
}
/// A convenience wrapper that returns the default value for the column /// at `index` if `NULL`. pubfn get_by_index_or_default<T: VariantType + Default>(&self, index: u32) -> T { self.get_by_index(index).unwrap_or_default()
}
/// Returns the value of the column specified by `name` for the current row. pubfn get_by_name<N: AsRef<str>, T: VariantType>(&self, name: N) -> Result<T> { let name = name.as_ref(); let index = self.0.get_column_index(name)?; self.0
.get_column_value(index)
.map_err(|rv| Error::GetByName {
rv,
data_type: T::type_name(),
name: name.into(),
})
}
/// Returns the default value for the column with the given `name`, or the /// default if the column is `NULL`. pubfn get_by_name_or_default<N: AsRef<str>, T: VariantType + Default>(&self, name: N) -> T { self.get_by_name(name).unwrap_or_default()
}
}
/// A database operation, included for better context in error messages. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pubenum DatabaseOp {
Exec,
Prepare,
Step,
Execute,
}
impl DatabaseOp { /// Returns a description of the operation to include in an error message. pubfn what(&self) -> &'static str { matchself {
DatabaseOp::Exec => "execute SQL string",
DatabaseOp::Prepare => "prepare statement",
DatabaseOp::Step => "step statement",
DatabaseOp::Execute => "execute statement",
}
}
}
/// Storage errors. #[derive(Debug)] pubenum Error { /// A connection doesn't have a usable async thread. The connection might be /// closed, or the thread manager may have shut down.
NoThread,
/// Failed to get a limit for a database connection.
Limit,
/// A database operation failed. The error includes a SQLite result code, /// and an explanation string.
Database {
rv: nsresult,
op: DatabaseOp,
code: i32,
message: nsCString,
},
/// A parameter with the given data type couldn't be bound at this index, /// likely because the index is out of range.
BindByIndex {
rv: nsresult,
data_type: Cow<'static, str>,
index: u32,
},
/// A parameter with the given type couldn't be bound to this name, likely /// because the statement doesn't have a matching `:`-prefixed parameter /// with the name.
BindByName {
rv: nsresult,
data_type: Cow<'static, str>,
name: String,
},
/// A column with this name doesn't exist.
InvalidColumn { rv: nsresult, name: String },
/// A value of the given type couldn't be accessed at this index. This is /// the error returned when a type conversion fails; for example, requesting /// an `nsString` instead of an `Option<nsString>` when the column is `NULL`.
GetByIndex {
rv: nsresult,
data_type: Cow<'static, str>,
index: u32,
},
/// A value of the given type couldn't be accessed for the column with /// this name.
GetByName {
rv: nsresult,
data_type: Cow<'static, str>,
name: String,
},
/// A storage operation failed for other reasons.
Other(nsresult),
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.