Skip to content

Commit a01f3a2

Browse files
committed
Use trait instead of Deref to implement ReadTransaction on Transaction
1 parent 401224f commit a01f3a2

File tree

3 files changed

+77
-85
lines changed

3 files changed

+77
-85
lines changed

src/handle.rs

+3-7
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,7 @@ impl Handle {
251251
pub fn start_deferred_transaction_for_read(&self) -> rusqlite::Result<OwnedReadTx> {
252252
self.start_transaction(|conn, _handle| {
253253
let rtx = conn.transaction_with_behavior(TransactionBehavior::Deferred)?;
254-
Ok(ReadTransaction {
255-
tx: ReadOnlyRusqliteTransaction { conn: rtx },
256-
})
254+
Ok(ReadTransactionOwned(rtx))
257255
})
258256
}
259257

@@ -380,9 +378,7 @@ impl Handle {
380378
}
381379
}
382380
let tx = conn.transaction_with_behavior(TransactionBehavior::Deferred)?;
383-
let tx = ReadTransaction {
384-
tx: ReadOnlyRusqliteTransaction { conn: tx },
385-
};
381+
let tx = ReadTransactionOwned(tx);
386382
pending_values = Self::punch_values(&dir, pending_values, &tx)?;
387383
}
388384
Ok(())
@@ -473,7 +469,7 @@ use item::Item;
473469

474470
use crate::dir::Dir;
475471
use crate::ownedtx::{OwnedReadTx, OwnedTxInner};
476-
use crate::tx::{ReadOnlyRusqliteTransaction, ReadTransaction};
472+
use crate::tx::ReadTransaction;
477473
use crate::walk::EntryType;
478474

479475
impl Drop for Handle {

src/main.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use itertools::Itertools;
88
use log::info;
99
use possum::sys::seekhole::{file_regions, Region, RegionType};
1010
use possum::sys::{punchfile, FileLocking, SparseFile};
11-
use possum::{ceil_multiple, check_hole, walk, Handle, NonzeroValueLocation, ReadTransactionRef};
11+
use possum::*;
1212

1313
#[derive(clap::Subcommand)]
1414
enum Commands {
@@ -124,7 +124,7 @@ fn main() -> anyhow::Result<()> {
124124
.unwrap_or(true)
125125
{
126126
print_missing_holes(
127-
tx.as_ref(),
127+
&tx,
128128
values_file_entry,
129129
handle.block_size(),
130130
fragments,
@@ -158,7 +158,7 @@ fn main() -> anyhow::Result<()> {
158158
for FileRegion {
159159
mut start,
160160
mut length,
161-
} in missing_holes(tx.as_ref(), values_file_entry)?
161+
} in missing_holes(&tx, values_file_entry)?
162162
{
163163
let delay = ceil_multiple(start, handle.block_size()) - start;
164164
// dbg!(start, length, delay);

src/tx.rs

+71-75
Original file line numberDiff line numberDiff line change
@@ -9,73 +9,68 @@ pub(crate) struct PostCommitWork<'h, T> {
99
reward: T,
1010
}
1111

12-
/// Checks outgoing stmts for readonly status
13-
#[repr(transparent)]
14-
pub(crate) struct ReadOnlyRusqliteTransaction<T> {
15-
pub(crate) conn: T,
12+
/// Exposes a rusqlite Transaction to implement ReadTransaction.
13+
pub trait ReadOnlyTransactionAccessor {
14+
fn readonly_transaction(&self) -> &rusqlite::Transaction;
1615
}
1716

18-
impl<'t, T> ReadOnlyRusqliteTransaction<T>
19-
where
20-
T: Borrow<rusqlite::Transaction<'t>>,
21-
{
22-
pub fn prepare_cached<'a>(&'a self, sql: &str) -> rusqlite::Result<CachedStatement<'a>>
23-
where
24-
't: 'a,
25-
{
26-
let stmt = self.conn.borrow().prepare_cached(sql)?;
27-
assert!(stmt.readonly());
28-
Ok(stmt)
29-
}
17+
/// Extends rusqlite objects with stuff needed for ReadTransaction.
18+
trait ReadOnlyRusqliteTransaction {
19+
fn prepare_cached_readonly(&self, sql: &str) -> rusqlite::Result<CachedStatement>;
3020
}
3121

32-
impl ReadTransactionOwned<'_> {
33-
pub fn as_ref(&self) -> ReadTransactionRef {
34-
ReadTransactionRef {
35-
tx: ReadOnlyRusqliteTransaction {
36-
conn: &self.tx.conn,
37-
},
38-
}
22+
// This could just as easily be implemented for rusqlite::Connection too.
23+
impl ReadOnlyRusqliteTransaction for rusqlite::Transaction<'_> {
24+
fn prepare_cached_readonly(&self, sql: &str) -> rusqlite::Result<CachedStatement> {
25+
prepare_cached_readonly(self.borrow(), sql)
3926
}
4027
}
4128

42-
pub type ReadTransactionRef<'a> = ReadTransaction<&'a rusqlite::Transaction<'a>>;
29+
fn prepare_cached_readonly<'a>(
30+
conn: &'a Connection,
31+
sql: &str,
32+
) -> rusqlite::Result<CachedStatement<'a>> {
33+
let stmt = conn.prepare_cached(sql)?;
34+
assert!(stmt.readonly());
35+
Ok(stmt)
36+
}
37+
38+
pub type ReadTransactionRef<'a> = &'a ReadTransactionOwned<'a>;
4339

44-
pub type ReadTransactionOwned<'a> = ReadTransaction<rusqlite::Transaction<'a>>;
40+
/// Helper type for wrapping rusqlite::Transaction to only provide ReadTransaction capabilities.
41+
pub struct ReadTransactionOwned<'a>(pub(crate) rusqlite::Transaction<'a>);
4542

46-
/// Only provides methods that are known to be read only, and has a ReadOnly connection internally.
47-
#[repr(transparent)]
48-
pub struct ReadTransaction<T> {
49-
pub(crate) tx: ReadOnlyRusqliteTransaction<T>,
43+
impl ReadOnlyTransactionAccessor for ReadTransactionOwned<'_> {
44+
fn readonly_transaction(&self) -> &rusqlite::Transaction {
45+
&self.0
46+
}
5047
}
5148

52-
impl<'a, T> ReadTransaction<T>
53-
where
54-
T: Borrow<rusqlite::Transaction<'a>>,
55-
{
56-
pub fn file_values(
57-
&'a self,
58-
file_id: FileId,
59-
) -> rusqlite::Result<FileValues<CachedStatement<'a>>> {
60-
let stmt = self.tx.prepare_cached(&format!(
61-
"select {} from keys where file_id=? order by file_offset",
62-
value_columns_sql()
63-
))?;
49+
/// Extra methods for types exposing a rusqlite Transaction that's allowed to do read transaction
50+
/// stuff.
51+
pub trait ReadTransaction: ReadOnlyTransactionAccessor {
52+
fn file_values(&self, file_id: FileId) -> rusqlite::Result<FileValues<CachedStatement>> {
53+
let stmt = self
54+
.readonly_transaction()
55+
.prepare_cached_readonly(&format!(
56+
"select {} from keys where file_id=? order by file_offset",
57+
value_columns_sql()
58+
))?;
6459
let iter = FileValues { stmt, file_id };
6560
Ok(iter)
6661
}
6762

68-
pub fn sum_value_length(&self) -> rusqlite::Result<u64> {
69-
self.tx
70-
.prepare_cached("select value from sums where key='value_length'")?
63+
fn sum_value_length(&self) -> rusqlite::Result<u64> {
64+
self.readonly_transaction()
65+
.prepare_cached_readonly("select value from sums where key='value_length'")?
7166
.query_row([], |row| row.get(0))
7267
.map_err(Into::into)
7368
}
7469

7570
/// Returns the end offset of the last active value before offset in the same file.
76-
pub fn query_last_end_offset(&self, file_id: &FileId, offset: u64) -> rusqlite::Result<u64> {
77-
self.tx
78-
.prepare_cached(
71+
fn query_last_end_offset(&self, file_id: &FileId, offset: u64) -> rusqlite::Result<u64> {
72+
self.readonly_transaction()
73+
.prepare_cached_readonly(
7974
"select max(file_offset+value_length) as last_offset \
8075
from keys \
8176
where file_id=? and file_offset+value_length <= ?",
@@ -89,13 +84,13 @@ where
8984
}
9085

9186
/// Returns the next value offset with at least min_offset.
92-
pub fn next_value_offset(
87+
fn next_value_offset(
9388
&self,
9489
file_id: &FileId,
9590
min_offset: u64,
9691
) -> rusqlite::Result<Option<u64>> {
97-
self.tx
98-
.prepare_cached(
92+
self.readonly_transaction()
93+
.prepare_cached_readonly(
9994
"select min(file_offset) \
10095
from keys \
10196
where file_id=? and file_offset >= ?",
@@ -104,7 +99,7 @@ where
10499
}
105100

106101
// TODO: Make this iterate.
107-
pub fn list_items(&self, prefix: &[u8]) -> PubResult<Vec<Item>> {
102+
fn list_items(&self, prefix: &[u8]) -> PubResult<Vec<Item>> {
108103
let range_end = {
109104
let mut prefix = prefix.to_owned();
110105
if inc_big_endian_array(&mut prefix) {
@@ -114,14 +109,16 @@ where
114109
}
115110
};
116111
match range_end {
117-
None => self.list_items_inner(
112+
None => list_items_inner(
113+
self.readonly_transaction(),
118114
&format!(
119115
"select {}, key from keys where key >= ?",
120116
value_columns_sql()
121117
),
122118
[prefix],
123119
),
124-
Some(range_end) => self.list_items_inner(
120+
Some(range_end) => list_items_inner(
121+
self.readonly_transaction(),
125122
&format!(
126123
"select {}, key from keys where key >= ? and key < ?",
127124
value_columns_sql()
@@ -130,20 +127,23 @@ where
130127
),
131128
}
132129
}
130+
}
133131

134-
fn list_items_inner(&self, sql: &str, params: impl rusqlite::Params) -> PubResult<Vec<Item>> {
135-
self.tx
136-
.prepare_cached(sql)
137-
.unwrap()
138-
.query_map(params, |row| {
139-
Ok(Item {
140-
value: Value::from_row(row)?,
141-
key: row.get(VALUE_COLUMN_NAMES.len())?,
142-
})
143-
})?
144-
.collect::<rusqlite::Result<Vec<_>>>()
145-
.map_err(Into::into)
146-
}
132+
fn list_items_inner(
133+
tx: &rusqlite::Transaction,
134+
sql: &str,
135+
params: impl rusqlite::Params,
136+
) -> PubResult<Vec<Item>> {
137+
tx.prepare_cached_readonly(sql)
138+
.unwrap()
139+
.query_map(params, |row| {
140+
Ok(Item {
141+
value: Value::from_row(row)?,
142+
key: row.get(VALUE_COLUMN_NAMES.len())?,
143+
})
144+
})?
145+
.collect::<rusqlite::Result<Vec<_>>>()
146+
.map_err(Into::into)
147147
}
148148

149149
impl<'h, T> PostCommitWork<'h, T> {
@@ -172,18 +172,14 @@ pub(crate) struct Transaction<'h> {
172172

173173
// TODO: Try doing this with a read trait that just requires a rusqlite::Transaction be available.
174174

175-
impl<'h> Deref for Transaction<'h> {
176-
type Target = ReadTransaction<rusqlite::Transaction<'h>>;
177-
178-
fn deref(&self) -> &Self::Target {
179-
unsafe {
180-
std::mem::transmute::<&rusqlite::Transaction, &ReadTransaction<rusqlite::Transaction>>(
181-
&self.tx,
182-
)
183-
}
175+
impl<'t> ReadOnlyTransactionAccessor for Transaction<'t> {
176+
fn readonly_transaction(&self) -> &rusqlite::Transaction {
177+
&self.tx
184178
}
185179
}
186180

181+
impl<T> ReadTransaction for T where T: ReadOnlyTransactionAccessor {}
182+
187183
impl<'h> Transaction<'h> {
188184
pub fn new(tx: rusqlite::Transaction<'h>, handle: &'h Handle) -> Self {
189185
Self {

0 commit comments

Comments
 (0)