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
/* 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};

/// 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 connections: 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 {
            error_support::report_error!(
                "places-nonchunked-maybe-commit",
                "maybe_commit called on a non-chunked transaction"
            );
            if cfg!(debug_assertions) {
                panic!("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
    }
}