Skip to content

Commit 3e852e4

Browse files
Merge pull request #264 from github/robust-transactions
Use transaction mechanism instead of explicit BEGIN/COMMIT
2 parents 9de0462 + afb98e0 commit 3e852e4

File tree

1 file changed

+56
-64
lines changed

1 file changed

+56
-64
lines changed

stack-graphs/src/storage.rs

Lines changed: 56 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -139,56 +139,56 @@ pub struct SQLiteWriter {
139139
impl SQLiteWriter {
140140
/// Open an in-memory database.
141141
pub fn open_in_memory() -> Result<Self> {
142-
let conn = Connection::open_in_memory()?;
143-
Self::init(&conn)?;
142+
let mut conn = Connection::open_in_memory()?;
143+
Self::init(&mut conn)?;
144144
Ok(Self { conn })
145145
}
146146

147147
/// Open a file database. If the file does not exist, it is automatically created.
148148
/// An error is returned if the database version is not supported.
149149
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
150150
let is_new = !path.as_ref().exists();
151-
let conn = Connection::open(path)?;
151+
let mut conn = Connection::open(path)?;
152152
set_pragmas_and_functions(&conn)?;
153153
if is_new {
154-
Self::init(&conn)?;
154+
Self::init(&mut conn)?;
155155
} else {
156156
check_version(&conn)?;
157157
}
158158
Ok(Self { conn })
159159
}
160160

161161
/// Create database tables and write metadata.
162-
fn init(conn: &Connection) -> Result<()> {
163-
conn.execute("BEGIN", [])?;
164-
conn.execute_batch(SCHEMA)?;
165-
conn.execute("INSERT INTO metadata (version) VALUES (?)", [VERSION])?;
166-
conn.execute("COMMIT", [])?;
162+
fn init(conn: &mut Connection) -> Result<()> {
163+
let tx = conn.transaction()?;
164+
tx.execute_batch(SCHEMA)?;
165+
tx.execute("INSERT INTO metadata (version) VALUES (?)", [VERSION])?;
166+
tx.commit()?;
167167
Ok(())
168168
}
169169

170170
/// Clean all data from the database.
171171
pub fn clean_all(&mut self) -> Result<usize> {
172-
self.conn.execute("BEGIN", [])?;
173-
let count = self.clean_all_inner()?;
174-
self.conn.execute("COMMIT", [])?;
172+
let tx = self.conn.transaction()?;
173+
let count = Self::clean_all_inner(&tx)?;
174+
tx.commit()?;
175175
Ok(count)
176176
}
177177

178178
/// Clean all data from the database.
179179
///
180180
/// This is an inner method, which does not wrap individual SQL statements in a transaction.
181-
fn clean_all_inner(&mut self) -> Result<usize> {
181+
fn clean_all_inner(conn: &Connection) -> Result<usize> {
182182
{
183-
let mut stmt = self.conn.prepare_cached("DELETE FROM file_paths")?;
183+
let mut stmt = conn.prepare_cached("DELETE FROM file_paths")?;
184184
stmt.execute([])?;
185185
}
186186
{
187-
let mut stmt = self.conn.prepare_cached("DELETE FROM root_paths")?;
187+
let mut stmt = conn.prepare_cached("DELETE FROM root_paths")?;
188188
stmt.execute([])?;
189189
}
190190
let count = {
191-
let mut stmt = self.conn.prepare_cached("DELETE FROM graphs")?;
191+
let mut stmt = conn.prepare_cached("DELETE FROM graphs")?;
192192
stmt.execute([])?
193193
};
194194
Ok(count)
@@ -197,33 +197,27 @@ impl SQLiteWriter {
197197
/// Clean file data from the database. If recursive is true, data for all descendants of
198198
/// that file is cleaned.
199199
pub fn clean_file(&mut self, file: &Path) -> Result<usize> {
200-
self.conn.execute("BEGIN", [])?;
201-
let count = self.clean_file_inner(file)?;
202-
self.conn.execute("COMMIT", [])?;
200+
let tx = self.conn.transaction()?;
201+
let count = Self::clean_file_inner(&tx, file)?;
202+
tx.commit()?;
203203
Ok(count)
204204
}
205205

206206
/// Clean file data from the database.
207207
///
208208
/// This is an inner method, which does not wrap individual SQL statements in a transaction.
209-
fn clean_file_inner(&mut self, file: &Path) -> Result<usize> {
209+
fn clean_file_inner(conn: &Connection, file: &Path) -> Result<usize> {
210210
let file = file.to_string_lossy();
211211
{
212-
let mut stmt = self
213-
.conn
214-
.prepare_cached("DELETE FROM file_paths WHERE file=?")?;
212+
let mut stmt = conn.prepare_cached("DELETE FROM file_paths WHERE file=?")?;
215213
stmt.execute([&file])?;
216214
}
217215
{
218-
let mut stmt = self
219-
.conn
220-
.prepare_cached("DELETE FROM root_paths WHERE file=?")?;
216+
let mut stmt = conn.prepare_cached("DELETE FROM root_paths WHERE file=?")?;
221217
stmt.execute([&file])?;
222218
}
223219
let count = {
224-
let mut stmt = self
225-
.conn
226-
.prepare_cached("DELETE FROM graphs WHERE file=?")?;
220+
let mut stmt = conn.prepare_cached("DELETE FROM graphs WHERE file=?")?;
227221
stmt.execute([&file])?
228222
};
229223
Ok(count)
@@ -232,55 +226,56 @@ impl SQLiteWriter {
232226
/// Clean file or directory data from the database. Data for all decendants of the given path
233227
/// is cleaned.
234228
pub fn clean_file_or_directory(&mut self, file_or_directory: &Path) -> Result<usize> {
235-
self.conn.execute("BEGIN", [])?;
236-
let count = self.clean_file_or_directory_inner(file_or_directory)?;
237-
self.conn.execute("COMMIT", [])?;
229+
let tx = self.conn.transaction()?;
230+
let count = Self::clean_file_or_directory_inner(&tx, file_or_directory)?;
231+
tx.commit()?;
238232
Ok(count)
239233
}
240234

241235
/// Clean file or directory data from the database. Data for all decendants of the given path
242236
/// is cleaned.
243237
///
244238
/// This is an inner method, which does not wrap individual SQL statements in a transaction.
245-
fn clean_file_or_directory_inner(&mut self, file_or_directory: &Path) -> Result<usize> {
239+
fn clean_file_or_directory_inner(conn: &Connection, file_or_directory: &Path) -> Result<usize> {
246240
let file_or_directory = file_or_directory.to_string_lossy();
247241
{
248-
let mut stmt = self
249-
.conn
250-
.prepare_cached("DELETE FROM file_paths WHERE path_descendant_of(file, ?)")?;
242+
let mut stmt =
243+
conn.prepare_cached("DELETE FROM file_paths WHERE path_descendant_of(file, ?)")?;
251244
stmt.execute([&file_or_directory])?;
252245
}
253246
{
254-
let mut stmt = self
255-
.conn
256-
.prepare_cached("DELETE FROM root_paths WHERE path_descendant_of(file, ?)")?;
247+
let mut stmt =
248+
conn.prepare_cached("DELETE FROM root_paths WHERE path_descendant_of(file, ?)")?;
257249
stmt.execute([&file_or_directory])?;
258250
}
259251
let count = {
260-
let mut stmt = self
261-
.conn
262-
.prepare_cached("DELETE FROM graphs WHERE path_descendant_of(file, ?)")?;
252+
let mut stmt =
253+
conn.prepare_cached("DELETE FROM graphs WHERE path_descendant_of(file, ?)")?;
263254
stmt.execute([&file_or_directory])?
264255
};
265256
Ok(count)
266257
}
267258

268259
/// Store an error, indicating that indexing this file failed.
269260
pub fn store_error_for_file(&mut self, file: &Path, tag: &str, error: &str) -> Result<()> {
270-
self.conn.execute("BEGIN", [])?;
271-
self.store_error_for_file_inner(file, tag, error)?;
272-
self.conn.execute("COMMIT", [])?;
261+
let tx = self.conn.transaction()?;
262+
Self::store_error_for_file_inner(&tx, file, tag, error)?;
263+
tx.commit()?;
273264
Ok(())
274265
}
275266

276267
/// Store an error, indicating that indexing this file failed.
277268
///
278269
/// This is an inner method, which does not wrap individual SQL statements in a transaction.
279-
fn store_error_for_file_inner(&mut self, file: &Path, tag: &str, error: &str) -> Result<()> {
270+
fn store_error_for_file_inner(
271+
conn: &Connection,
272+
file: &Path,
273+
tag: &str,
274+
error: &str,
275+
) -> Result<()> {
280276
copious_debugging!("--> Store error for {}", file.display());
281-
let mut stmt = self
282-
.conn
283-
.prepare_cached("INSERT INTO graphs (file, tag, error, json) VALUES (?, ?, ?, ?)")?;
277+
let mut stmt =
278+
conn.prepare_cached("INSERT INTO graphs (file, tag, error, json) VALUES (?, ?, ?, ?)")?;
284279
let graph = crate::serde::StackGraph::default();
285280
stmt.execute((
286281
&file.to_string_lossy(),
@@ -304,28 +299,27 @@ impl SQLiteWriter {
304299
IP: IntoIterator<Item = &'a PartialPath>,
305300
{
306301
let path = Path::new(graph[file].name());
307-
self.conn.execute("BEGIN", [])?;
308-
self.clean_file_inner(path)?;
309-
self.store_graph_for_file_inner(graph, file, tag)?;
310-
self.store_partial_paths_for_file_inner(graph, file, partials, paths)?;
311-
self.conn.execute("COMMIT", [])?;
302+
let tx = self.conn.transaction()?;
303+
Self::clean_file_inner(&tx, path)?;
304+
Self::store_graph_for_file_inner(&tx, graph, file, tag)?;
305+
Self::store_partial_paths_for_file_inner(&tx, graph, file, partials, paths)?;
306+
tx.commit()?;
312307
Ok(())
313308
}
314309

315310
/// Store the file graph.
316311
///
317312
/// This is an inner method, which does not wrap individual SQL statements in a transaction.
318313
fn store_graph_for_file_inner(
319-
&mut self,
314+
conn: &Connection,
320315
graph: &StackGraph,
321316
file: Handle<File>,
322317
tag: &str,
323318
) -> Result<()> {
324319
let file_str = graph[file].name();
325320
copious_debugging!("--> Store graph for {}", file_str);
326-
let mut stmt = self
327-
.conn
328-
.prepare_cached("INSERT INTO graphs (file, tag, json) VALUES (?, ?, ?)")?;
321+
let mut stmt =
322+
conn.prepare_cached("INSERT INTO graphs (file, tag, json) VALUES (?, ?, ?)")?;
329323
let graph = serde::StackGraph::from_graph_filter(graph, &FileFilter(file));
330324
stmt.execute((file_str, tag, &serde_json::to_vec(&graph)?))?;
331325
Ok(())
@@ -335,7 +329,7 @@ impl SQLiteWriter {
335329
///
336330
/// This is an inner method, which does not wrap individual SQL statements in a transaction.
337331
fn store_partial_paths_for_file_inner<'a, IP>(
338-
&mut self,
332+
conn: &Connection,
339333
graph: &StackGraph,
340334
file: Handle<File>,
341335
partials: &mut PartialPaths,
@@ -345,11 +339,9 @@ impl SQLiteWriter {
345339
IP: IntoIterator<Item = &'a PartialPath>,
346340
{
347341
let file_str = graph[file].name();
348-
let mut node_stmt = self
349-
.conn
350-
.prepare_cached("INSERT INTO file_paths (file, local_id, json) VALUES (?, ?, ?)")?;
351-
let mut root_stmt = self
352-
.conn
342+
let mut node_stmt =
343+
conn.prepare_cached("INSERT INTO file_paths (file, local_id, json) VALUES (?, ?, ?)")?;
344+
let mut root_stmt = conn
353345
.prepare_cached("INSERT INTO root_paths (file, symbol_stack, json) VALUES (?, ?, ?)")?;
354346
#[cfg_attr(not(feature = "copious-debugging"), allow(unused))]
355347
let mut node_path_count = 0usize;

0 commit comments

Comments
 (0)