autofill/db/
mod.rs

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
5pub mod addresses;
6pub mod credit_cards;
7pub mod models;
8pub mod passports;
9pub mod schema;
10pub mod store;
11
12use crate::error::*;
13
14use error_support::error;
15use interrupt_support::{SqlInterruptHandle, SqlInterruptScope};
16use rusqlite::{Connection, OpenFlags};
17use sql_support::open_database;
18use sql_support::path::normalize_database_path;
19use std::sync::Arc;
20use std::{
21    ops::{Deref, DerefMut},
22    path::{Path, PathBuf},
23};
24
25pub struct AutofillDb {
26    pub writer: Connection,
27    interrupt_handle: Arc<SqlInterruptHandle>,
28}
29
30impl AutofillDb {
31    pub fn new(db_path: impl AsRef<Path>) -> Result<Self> {
32        let db_path = normalize_database_path(db_path)?;
33        Self::new_named(db_path)
34    }
35
36    pub fn new_memory(db_path: &str) -> Result<Self> {
37        let name = PathBuf::from(format!("file:{}?mode=memory&cache=shared", db_path));
38        Self::new_named(name)
39    }
40
41    fn new_named(db_path: PathBuf) -> Result<Self> {
42        // We always create the read-write connection for an initial open so
43        // we can create the schema and/or do version upgrades.
44        let flags = OpenFlags::SQLITE_OPEN_NO_MUTEX
45            | OpenFlags::SQLITE_OPEN_URI
46            | OpenFlags::SQLITE_OPEN_CREATE
47            | OpenFlags::SQLITE_OPEN_READ_WRITE;
48
49        let conn = open_database::open_database_with_flags(
50            db_path,
51            flags,
52            &schema::AutofillConnectionInitializer,
53        )?;
54
55        Ok(Self {
56            interrupt_handle: Arc::new(SqlInterruptHandle::new(&conn)),
57            writer: conn,
58        })
59    }
60
61    #[inline]
62    pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope> {
63        Ok(self.interrupt_handle.begin_interrupt_scope()?)
64    }
65
66    pub fn close(self) {
67        if let Err((_, err)) = self.writer.close() {
68            // Log the error, but continue with shutdown.
69            error!("Failed to close the connection: {:?}", err);
70        }
71    }
72}
73
74impl Deref for AutofillDb {
75    type Target = Connection;
76
77    fn deref(&self) -> &Self::Target {
78        &self.writer
79    }
80}
81
82impl DerefMut for AutofillDb {
83    fn deref_mut(&mut self) -> &mut Self::Target {
84        &mut self.writer
85    }
86}
87
88/// Runs `op` in a savepoint, rolling back to it if `op` fails, so that a record
89/// reported as an error by a bulk function leaves nothing behind. The shared
90/// triggers reject a guid that exists in the counterpart table with
91/// `RAISE(FAIL)`, which aborts the statement but keeps the row it already
92/// inserted - so without this the offending row would be committed along with
93/// the rest of the batch, putting the guid in both the data and tombstone
94/// tables.
95///
96/// The outer `Result` is a savepoint failure and aborts the batch; the inner one
97/// is the record's own failure.
98pub(crate) fn with_savepoint<T>(
99    tx: &rusqlite::Transaction<'_>,
100    op: impl FnOnce() -> Result<T>,
101) -> Result<std::result::Result<T, Error>> {
102    tx.execute_batch("SAVEPOINT bulk_record")?;
103    match op() {
104        Ok(value) => {
105            tx.execute_batch("RELEASE bulk_record")?;
106            Ok(Ok(value))
107        }
108        Err(e) => {
109            tx.execute_batch("ROLLBACK TO bulk_record; RELEASE bulk_record")?;
110            Ok(Err(e))
111        }
112    }
113}
114
115/// Builds a `Timestamp` from millis an application supplied.
116///
117/// Anything that is not a representable date becomes 0, which already means
118/// "unset" for these fields - see `sanitize_timestamp`. A bare `.max(0)` would
119/// not be enough: the corrupt values actually seen in the wild arrive *already*
120/// huge, because the negative-to-`u64` reinterpretation happened before the
121/// value reached us, and one of those would win every "latest wins" comparison
122/// in `Metadata::merge`. The tuple constructor is used rather than
123/// `Timestamp::from`, which asserts non-zero.
124pub(crate) fn timestamp_from_millis(millis: i64) -> types::Timestamp {
125    types::Timestamp(types::sanitize_timestamp(millis) as u64)
126}
127
128/// How an `update_internal_*` should treat the record's change counter.
129pub(crate) enum CounterUpdate {
130    /// Record a local change awaiting upload.
131    Increment,
132    /// Leave the counter alone, for a change that must not be uploaded - eg one
133    /// applied by Sync, which is already what the server has.
134    Leave,
135    /// Replace the counter, for a record whose counter is owned by the caller.
136    Set(i64),
137}
138
139impl CounterUpdate {
140    /// The SQL assigned to `sync_change_counter`, and the value bound to
141    /// `:counter` within it. `Leave` adds 0 rather than dropping `:counter` from
142    /// the SQL, because rusqlite rejects a named parameter the statement doesn't
143    /// use.
144    pub(crate) fn as_sql(&self) -> (&'static str, i64) {
145        match self {
146            Self::Increment => ("sync_change_counter + :counter", 1),
147            Self::Leave => ("sync_change_counter + :counter", 0),
148            Self::Set(counter) => (":counter", *counter),
149        }
150    }
151}
152
153pub(crate) mod sql_fns {
154    use rusqlite::{functions::Context, Result};
155    use sync_guid::Guid as SyncGuid;
156    use types::Timestamp;
157
158    #[inline(never)]
159    #[allow(dead_code)]
160    pub fn generate_guid(_ctx: &Context<'_>) -> Result<SyncGuid> {
161        Ok(SyncGuid::random())
162    }
163
164    #[inline(never)]
165    pub fn now(_ctx: &Context<'_>) -> Result<Timestamp> {
166        Ok(Timestamp::now())
167    }
168}
169
170// Helpers for tests
171#[cfg(test)]
172pub mod test {
173    use super::*;
174    use std::sync::atomic::{AtomicUsize, Ordering};
175
176    // A helper for our tests to get their own memory Api.
177    static ATOMIC_COUNTER: AtomicUsize = AtomicUsize::new(0);
178
179    pub fn new_mem_db() -> AutofillDb {
180        error_support::init_for_tests();
181        let counter = ATOMIC_COUNTER.fetch_add(1, Ordering::Relaxed);
182        AutofillDb::new_memory(&format!("test_autofill-api-{}", counter))
183            .expect("should get an API")
184    }
185}