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
/* 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/. */

mod coop_transaction;

use crate::api::places_api::ConnectionType;
use crate::error::*;
use coop_transaction::ChunkedCoopTransaction;
use rusqlite::Connection;
use sql_support::{ConnExt, UncheckedTransaction};

macro_rules! debug_complaint {
    ($($fmt_args:tt)*) => {
        log::error!($($fmt_args)*);
        if cfg!(debug_assertions) {
            panic!($($fmt_args)*);
        }
    };
}
/// High level transaction type which "does the right thing" for you.
/// Construct one with `PlacesDb::begin_transaction()`.
pub struct PlacesTransaction<'conn>(PlacesTransactionRepr<'conn>);

/// Only separated from PlacesTransaction so that the internals of the former
/// are private (so that it can't be `matched` on, for example)
enum PlacesTransactionRepr<'conn> {
    ChunkedWrite(ChunkedCoopTransaction<'conn>),
    UnchunkedWrite(UncheckedTransaction<'conn>),
    // Note: these might seem pointless, but can allow us to ensure consistency
    // between separate reads.
    ReadOnly(UncheckedTransaction<'conn>),
}

impl<'conn> PlacesTransaction<'conn> {
    /// Returns `true` if the current transaction should be committed at the
    /// earliest opportunity.
    #[inline]
    pub fn should_commit(&self) -> bool {
        match &self.0 {
            PlacesTransactionRepr::ChunkedWrite(tx) => tx.should_commit(),
            _ => true,
        }
    }

    /// - For transactions on sync connnections: Checks to see if we have held a
    ///   transaction for longer than the requested time, and if so, commits the
    ///   current transaction and opens another.
    /// - For transactions on other connections: `debug_assert!`s, or logs a
    ///   warning and does nothing.
    #[inline]
    pub fn maybe_commit(&mut self) -> Result<()> {
        if let PlacesTransactionRepr::ChunkedWrite(tx) = &mut self.0 {
            tx.maybe_commit()?;
        } else {
            debug_complaint!("maybe_commit called on a non-chunked transaction");
        }
        Ok(())
    }

    /// Consumes and commits a PlacesTransaction transaction.
    pub fn commit(self) -> Result<()> {
        match self.0 {
            PlacesTransactionRepr::ChunkedWrite(t) => t.commit()?,
            PlacesTransactionRepr::UnchunkedWrite(t) => t.commit()?,
            PlacesTransactionRepr::ReadOnly(t) => t.commit()?,
        };
        Ok(())
    }

    /// Consumes and attempst to roll back a PlacesTransaction. Note that if
    /// maybe_commit has been called, this may only roll back as far as that
    /// call.
    pub fn rollback(self) -> Result<()> {
        match self.0 {
            PlacesTransactionRepr::ChunkedWrite(t) => t.rollback()?,
            PlacesTransactionRepr::UnchunkedWrite(t) => t.rollback()?,
            PlacesTransactionRepr::ReadOnly(t) => t.rollback()?,
        };
        Ok(())
    }
}

impl super::PlacesDb {
    /// Begin the "correct" transaction type for this connection.
    ///
    /// - For Sync connections, begins a chunked coop transaction.
    /// - for ReadWrite connections, begins a normal coop transaction
    /// - for ReadOnly connections, begins an unchecked transaction.
    pub fn begin_transaction(&self) -> Result<PlacesTransaction<'_>> {
        Ok(PlacesTransaction(match self.conn_type() {
            ConnectionType::Sync => {
                PlacesTransactionRepr::ChunkedWrite(self.chunked_coop_trransaction()?)
            }
            ConnectionType::ReadWrite => {
                PlacesTransactionRepr::UnchunkedWrite(self.coop_transaction()?)
            }
            ConnectionType::ReadOnly => {
                // Use an unchecked transaction with no locking.
                PlacesTransactionRepr::ReadOnly(self.unchecked_transaction()?)
            }
        }))
    }
}

impl<'conn> std::ops::Deref for PlacesTransaction<'conn> {
    type Target = Connection;

    fn deref(&self) -> &Connection {
        match &self.0 {
            PlacesTransactionRepr::ChunkedWrite(t) => &t,
            PlacesTransactionRepr::UnchunkedWrite(t) => &t,
            PlacesTransactionRepr::ReadOnly(t) => &t,
        }
    }
}

impl<'conn> ConnExt for PlacesTransaction<'conn> {
    #[inline]
    fn conn(&self) -> &Connection {
        &*self
    }
}