1
- //! The repository persists all requests and responses. It consists of a SQLite
2
- //! database to persist all request history and an in-memory layer to provide
3
- //! caching and other ephemeral data (e.g. prettified content).
1
+ //! The database is responsible for persisting data, including requests and
2
+ //! responses.
4
3
5
4
use crate :: {
6
5
collection:: { CollectionId , RequestRecipeId } ,
@@ -18,78 +17,74 @@ use tokio::sync::Mutex;
18
17
use tracing:: debug;
19
18
use uuid:: Uuid ;
20
19
21
- /// A record of all HTTP history, which is persisted on disk. This is also used
22
- /// to populate chained values. This uses a sqlite DB underneath, which means
23
- /// all operations are internally fallible. Generally speaking, any error that
20
+ /// A SQLite database for persisting data. Generally speaking, any error that
24
21
/// occurs *after* opening the DB connection should be an internal bug, but
25
- /// should be shown to the user whenever possible. All operations are also
26
- /// async because of the database and the locking nature of the interior cache.
27
- /// They generally will be fast though, so it's safe to block on them in the
28
- /// main loop. Do not call this from the draw phase though; instead, cache the
29
- /// results in UI state for as long as they're needed.
22
+ /// should be shown to the user whenever possible. All operations are async
23
+ /// to enable concurrent accesses to yield. Do not call block on thisfrom the
24
+ /// draw phase; instead, cache the results in UI state for as long as they're
25
+ /// needed.
30
26
///
31
- /// Only requests that received a valid HTTP response should be stored.
32
- /// In-flight requests, invalid requests, and requests that failed to complete
33
- /// (e.g. because of a network error) should not (and cannot) be stored.
27
+ /// This uses an `Arc` internally, so it's safe and cheap to clone.
34
28
///
35
29
/// Note: Despite all the operations being async, the actual database isn't
36
30
/// async. Each operation will asynchronously wait for the connection mutex,
37
31
/// then block while performing the operation. This is just a shortcut, if it
38
32
/// becomes a bottleneck we can change that.
39
33
#[ derive( Clone , Debug ) ]
40
- pub struct Repository {
41
- /// History is stored in a sqlite DB. Mutex is needed for multi-threaded
34
+ pub struct Database {
35
+ /// Data is stored in a sqlite DB. Mutex is needed for multi-threaded
42
36
/// access. This is a bottleneck but the access rate should be so low that
43
37
/// it doesn't matter.
44
38
connection : Arc < Mutex < Connection > > ,
45
39
}
46
40
47
- impl Repository {
48
- /// Load the repository database. This will perform first-time setup, so
49
- /// this should only be called at the main session entrypoint.
41
+ impl Database {
42
+ /// Load the database. This will perform first-time setup, so this should
43
+ /// only be called at the main session entrypoint.
50
44
pub fn load ( collection_id : & CollectionId ) -> anyhow:: Result < Self > {
51
45
let mut connection = Connection :: open ( Self :: path ( collection_id) ?) ?;
52
46
// Use WAL for concurrency
53
47
connection. pragma_update ( None , "journal_mode" , "WAL" ) ?;
54
- Self :: setup ( & mut connection) ?;
48
+ Self :: migrate ( & mut connection) ?;
55
49
Ok ( Self {
56
50
connection : Arc :: new ( Mutex :: new ( connection) ) ,
57
51
} )
58
52
}
59
53
60
- /// Path to the repository database file. This will create the directory if
61
- /// it doesn't exist
54
+ /// Path to the database file. This will create the directory if it doesn't
55
+ /// exist
62
56
fn path ( collection_id : & CollectionId ) -> anyhow:: Result < PathBuf > {
63
57
Ok ( Directory :: data ( collection_id)
64
58
. create ( ) ?
65
59
. join ( "state.sqlite" ) )
66
60
}
67
61
68
- /// Apply first-time setup
69
- fn setup ( connection : & mut Connection ) -> anyhow:: Result < ( ) > {
62
+ /// Apply database migrations
63
+ fn migrate ( connection : & mut Connection ) -> anyhow:: Result < ( ) > {
70
64
let migrations = Migrations :: new ( vec ! [ M :: up(
71
65
// The request state kind is a bit hard to map to tabular data.
72
- // Everything that we need to query on (success/error kind, HTTP
73
- // status code, end_time, etc.) is in its own column. The
74
- // request/repsonse and response will be serialized
75
- // into messagepack bytes
66
+ // Everything that we need to query on (HTTP status code,
67
+ // end_time, etc.) is in its own column. The
68
+ // request/repsonse and response will be serialized into
69
+ // msgpack bytes
76
70
"CREATE TABLE requests (
77
- id UUID PRIMARY KEY,
78
- recipe_id TEXT,
79
- start_time TEXT,
80
- end_time TEXT,
81
- request BLOB,
82
- response BLOB,
83
- status_code INTEGER
84
- )" ,
85
- ) ] ) ;
71
+ id UUID PRIMARY KEY,
72
+ recipe_id TEXT,
73
+ start_time TEXT,
74
+ end_time TEXT,
75
+ request BLOB,
76
+ response BLOB,
77
+ status_code INTEGER
78
+ )" ,
79
+ )
80
+ . down( "DROP TABLE requests" ) ] ) ;
86
81
migrations. to_latest ( connection) ?;
87
82
Ok ( ( ) )
88
83
}
89
84
90
85
/// Get the most recent request+response for a recipe, or `None` if there
91
86
/// has never been one received.
92
- pub async fn get_last (
87
+ pub async fn get_last_request (
93
88
& self ,
94
89
recipe_id : & RequestRecipeId ,
95
90
) -> anyhow:: Result < Option < RequestRecord > > {
@@ -107,13 +102,12 @@ impl Repository {
107
102
. traced ( )
108
103
}
109
104
110
- /// Add a new request to history. This should be called immediately before
111
- /// or after the request is sent, so the generated start_time timestamp
112
- /// is accurate.
113
- ///
114
- /// The HTTP engine is responsible for inserting its requests, so this isn't
115
- /// exposed outside the `http` module.
116
- pub ( super ) async fn insert (
105
+ /// Add a new request to history. The HTTP engine is responsible for
106
+ /// inserting its own requests. Only requests that received a valid HTTP
107
+ /// response should be stored. In-flight requests, invalid requests, and
108
+ /// requests that failed to complete (e.g. because of a network error)
109
+ /// should not (and cannot) be stored.
110
+ pub async fn insert_request (
117
111
& self ,
118
112
record : & RequestRecord ,
119
113
) -> anyhow:: Result < ( ) > {
@@ -155,23 +149,15 @@ impl Repository {
155
149
156
150
/// Test-only helpers
157
151
#[ cfg( test) ]
158
- impl Repository {
152
+ impl Database {
159
153
/// Create an in-memory DB, only for testing
160
154
pub fn testing ( ) -> Self {
161
155
let mut connection = Connection :: open_in_memory ( ) . unwrap ( ) ;
162
- Self :: setup ( & mut connection) . unwrap ( ) ;
156
+ Self :: migrate ( & mut connection) . unwrap ( ) ;
163
157
Self {
164
158
connection : Arc :: new ( Mutex :: new ( connection) ) ,
165
159
}
166
160
}
167
-
168
- /// Public insert function, only for tests
169
- pub async fn insert_test (
170
- & self ,
171
- record : & RequestRecord ,
172
- ) -> anyhow:: Result < ( ) > {
173
- self . insert ( record) . await
174
- }
175
161
}
176
162
177
163
impl ToSql for RequestId {
0 commit comments