webext_storage/db.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::error::*;
use crate::schema;
use interrupt_support::{SqlInterruptHandle, SqlInterruptScope};
use parking_lot::Mutex;
use rusqlite::types::{FromSql, ToSql};
use rusqlite::Connection;
use rusqlite::OpenFlags;
use sql_support::open_database::open_database_with_flags;
use sql_support::ConnExt;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use url::Url;
/// A `StorageDb` wraps a read-write SQLite connection, and handles schema
/// migrations and recovering from database file corruption. It can be used
/// anywhere a `rusqlite::Connection` is expected, thanks to its `Deref{Mut}`
/// implementations.
///
/// We only support a single writer connection - so that's the only thing we
/// store. It's still a bit overkill, but there's only so many yaks in a day.
pub enum WebExtStorageDb {
Open(Connection),
Closed,
}
pub struct StorageDb {
pub writer: WebExtStorageDb,
interrupt_handle: Arc<SqlInterruptHandle>,
}
impl StorageDb {
/// Create a new, or fetch an already open, StorageDb backed by a file on disk.
pub fn new(db_path: impl AsRef<Path>) -> Result<Self> {
let db_path = normalize_path(db_path)?;
Self::new_named(db_path)
}
/// Create a new, or fetch an already open, memory-based StorageDb. You must
/// provide a name, but you are still able to have a single writer and many
/// reader connections to the same memory DB open.
#[cfg(test)]
pub fn new_memory(db_path: &str) -> Result<Self> {
let name = PathBuf::from(format!("file:{}?mode=memory&cache=shared", db_path));
Self::new_named(name)
}
fn new_named(db_path: PathBuf) -> Result<Self> {
// We always create the read-write connection for an initial open so
// we can create the schema and/or do version upgrades.
let flags = OpenFlags::SQLITE_OPEN_NO_MUTEX
| OpenFlags::SQLITE_OPEN_URI
| OpenFlags::SQLITE_OPEN_CREATE
| OpenFlags::SQLITE_OPEN_READ_WRITE;
let conn = open_database_with_flags(db_path, flags, &schema::WebExtMigrationLogin)?;
Ok(Self {
interrupt_handle: Arc::new(SqlInterruptHandle::new(&conn)),
writer: WebExtStorageDb::Open(conn),
})
}
pub fn interrupt_handle(&self) -> Arc<SqlInterruptHandle> {
Arc::clone(&self.interrupt_handle)
}
#[allow(dead_code)]
pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope> {
Ok(self.interrupt_handle.begin_interrupt_scope()?)
}
/// Closes the database connection. If there are any unfinalized prepared
/// statements on the connection, `close` will fail and the `StorageDb` will
/// remain open and the connection will be leaked - we used to return the
/// underlying connection so the caller can retry but (a) that's very tricky
/// in an Arc<Mutex<>> world and (b) we never actually took advantage of
/// that retry capability.
pub fn close(&mut self) -> Result<()> {
let conn = match std::mem::replace(&mut self.writer, WebExtStorageDb::Closed) {
WebExtStorageDb::Open(conn) => conn,
WebExtStorageDb::Closed => return Ok(()),
};
conn.close().map_err(|(_, y)| Error::SqlError(y))
}
pub(crate) fn get_connection(&self) -> Result<&Connection> {
let db = &self.writer;
match db {
WebExtStorageDb::Open(y) => Ok(y),
WebExtStorageDb::Closed => Err(Error::DatabaseConnectionClosed),
}
}
}
// We almost exclusively use this ThreadSafeStorageDb
pub struct ThreadSafeStorageDb {
db: Mutex<StorageDb>,
// This "outer" interrupt_handle not protected by the mutex means
// consumers can interrupt us when the mutex is held - which it always will
// be if we are doing anything interruptible!
interrupt_handle: Arc<SqlInterruptHandle>,
}
impl ThreadSafeStorageDb {
pub fn new(db: StorageDb) -> Self {
Self {
interrupt_handle: db.interrupt_handle(),
db: Mutex::new(db),
}
}
pub fn interrupt_handle(&self) -> Arc<SqlInterruptHandle> {
Arc::clone(&self.interrupt_handle)
}
#[allow(dead_code)]
pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope> {
Ok(self.interrupt_handle.begin_interrupt_scope()?)
}
}
// Deref to a Mutex<StorageDb>, which is how we will use ThreadSafeStorageDb most of the time
impl Deref for ThreadSafeStorageDb {
type Target = Mutex<StorageDb>;
#[inline]
fn deref(&self) -> &Mutex<StorageDb> {
&self.db
}
}
// Also implement AsRef<SqlInterruptHandle> so that we can interrupt this at shutdown
impl AsRef<SqlInterruptHandle> for ThreadSafeStorageDb {
fn as_ref(&self) -> &SqlInterruptHandle {
&self.interrupt_handle
}
}
pub(crate) mod sql_fns {
use rusqlite::{functions::Context, Result};
use sync_guid::Guid as SyncGuid;
#[inline(never)]
pub fn generate_guid(_ctx: &Context<'_>) -> Result<SyncGuid> {
Ok(SyncGuid::random())
}
}
// These should be somewhere else...
pub fn put_meta(db: &Connection, key: &str, value: &dyn ToSql) -> Result<()> {
db.conn().execute_cached(
"REPLACE INTO meta (key, value) VALUES (:key, :value)",
rusqlite::named_params! { ":key": key, ":value": value },
)?;
Ok(())
}
pub fn get_meta<T: FromSql>(db: &Connection, key: &str) -> Result<Option<T>> {
let res = db.conn().try_query_one(
"SELECT value FROM meta WHERE key = :key",
&[(":key", &key)],
true,
)?;
Ok(res)
}
pub fn delete_meta(db: &Connection, key: &str) -> Result<()> {
db.conn()
.execute_cached("DELETE FROM meta WHERE key = :key", &[(":key", &key)])?;
Ok(())
}
// Utilities for working with paths.
// (From places_utils - ideally these would be shared, but the use of
// ErrorKind values makes that non-trivial.
/// `Path` is basically just a `str` with no validation, and so in practice it
/// could contain a file URL. Rusqlite takes advantage of this a bit, and says
/// `AsRef<Path>` but really means "anything sqlite can take as an argument".
///
/// Swift loves using file urls (the only support it has for file manipulation
/// is through file urls), so it's handy to support them if possible.
fn unurl_path(p: impl AsRef<Path>) -> PathBuf {
p.as_ref()
.to_str()
.and_then(|s| Url::parse(s).ok())
.and_then(|u| {
if u.scheme() == "file" {
u.to_file_path().ok()
} else {
None
}
})
.unwrap_or_else(|| p.as_ref().to_owned())
}
/// If `p` is a file URL, return it, otherwise try and make it one.
///
/// Errors if `p` is a relative non-url path, or if it's a URL path
/// that's isn't a `file:` URL.
#[allow(dead_code)]
pub fn ensure_url_path(p: impl AsRef<Path>) -> Result<Url> {
if let Some(u) = p.as_ref().to_str().and_then(|s| Url::parse(s).ok()) {
if u.scheme() == "file" {
Ok(u)
} else {
Err(Error::IllegalDatabasePath(p.as_ref().to_owned()))
}
} else {
let p = p.as_ref();
let u = Url::from_file_path(p).map_err(|_| Error::IllegalDatabasePath(p.to_owned()))?;
Ok(u)
}
}
/// As best as possible, convert `p` into an absolute path, resolving
/// all symlinks along the way.
///
/// If `p` is a file url, it's converted to a path before this.
fn normalize_path(p: impl AsRef<Path>) -> Result<PathBuf> {
let path = unurl_path(p);
if let Ok(canonical) = path.canonicalize() {
return Ok(canonical);
}
// It probably doesn't exist yet. This is an error, although it seems to
// work on some systems.
//
// We resolve this by trying to canonicalize the parent directory, and
// appending the requested file name onto that. If we can't canonicalize
// the parent, we return an error.
//
// Also, we return errors if the path ends in "..", if there is no
// parent directory, etc.
let file_name = path
.file_name()
.ok_or_else(|| Error::IllegalDatabasePath(path.clone()))?;
let parent = path
.parent()
.ok_or_else(|| Error::IllegalDatabasePath(path.clone()))?;
let mut canonical = parent.canonicalize()?;
canonical.push(file_name);
Ok(canonical)
}
// Helpers for tests
#[cfg(test)]
pub mod test {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
// A helper for our tests to get their own memory Api.
static ATOMIC_COUNTER: AtomicUsize = AtomicUsize::new(0);
pub fn new_mem_db() -> StorageDb {
let _ = env_logger::try_init();
let counter = ATOMIC_COUNTER.fetch_add(1, Ordering::Relaxed);
StorageDb::new_memory(&format!("test-api-{}", counter)).expect("should get an API")
}
pub fn new_mem_thread_safe_storage_db() -> Arc<ThreadSafeStorageDb> {
Arc::new(ThreadSafeStorageDb::new(new_mem_db()))
}
}
#[cfg(test)]
mod tests {
use super::test::*;
use super::*;
// Sanity check that we can create a database.
#[test]
fn test_open() {
new_mem_db();
// XXX - should we check anything else? Seems a bit pointless, but if
// we move the meta functions away from here then it's better than
// nothing.
}
#[test]
fn test_meta() -> Result<()> {
let db = new_mem_db();
let conn = &db.get_connection()?;
assert_eq!(get_meta::<String>(conn, "foo")?, None);
put_meta(conn, "foo", &"bar".to_string())?;
assert_eq!(get_meta(conn, "foo")?, Some("bar".to_string()));
delete_meta(conn, "foo")?;
assert_eq!(get_meta::<String>(conn, "foo")?, None);
Ok(())
}
}