Factour out SQLite handling
This commit is contained in:
		
							parent
							
								
									2ae43ced9a
								
							
						
					
					
						commit
						d9c5e3bbf0
					
				
					 4 changed files with 224 additions and 204 deletions
				
			
		|  | @ -36,177 +36,6 @@ | ||||||
| namespace nix { | namespace nix { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| MakeError(SQLiteError, Error); |  | ||||||
| MakeError(SQLiteBusy, SQLiteError); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| [[noreturn]] static void throwSQLiteError(sqlite3 * db, const format & f) |  | ||||||
| { |  | ||||||
|     int err = sqlite3_errcode(db); |  | ||||||
|     if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) { |  | ||||||
|         if (err == SQLITE_PROTOCOL) |  | ||||||
|             printMsg(lvlError, "warning: SQLite database is busy (SQLITE_PROTOCOL)"); |  | ||||||
|         else { |  | ||||||
|             static bool warned = false; |  | ||||||
|             if (!warned) { |  | ||||||
|                 printMsg(lvlError, "warning: SQLite database is busy"); |  | ||||||
|                 warned = true; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         /* Sleep for a while since retrying the transaction right away
 |  | ||||||
|            is likely to fail again. */ |  | ||||||
| #if HAVE_NANOSLEEP |  | ||||||
|         struct timespec t; |  | ||||||
|         t.tv_sec = 0; |  | ||||||
|         t.tv_nsec = (random() % 100) * 1000 * 1000; /* <= 0.1s */ |  | ||||||
|         nanosleep(&t, 0); |  | ||||||
| #else |  | ||||||
|         sleep(1); |  | ||||||
| #endif |  | ||||||
|         throw SQLiteBusy(format("%1%: %2%") % f.str() % sqlite3_errmsg(db)); |  | ||||||
|     } |  | ||||||
|     else |  | ||||||
|         throw SQLiteError(format("%1%: %2%") % f.str() % sqlite3_errmsg(db)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| /* Convenience function for retrying a SQLite transaction when the
 |  | ||||||
|    database is busy. */ |  | ||||||
| template<typename T> |  | ||||||
| T retrySQLite(std::function<T()> fun) |  | ||||||
| { |  | ||||||
|     while (true) { |  | ||||||
|         try { |  | ||||||
|             return fun(); |  | ||||||
|         } catch (SQLiteBusy & e) { |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| SQLite::~SQLite() |  | ||||||
| { |  | ||||||
|     try { |  | ||||||
|         if (db && sqlite3_close(db) != SQLITE_OK) |  | ||||||
|             throwSQLiteError(db, "closing database"); |  | ||||||
|     } catch (...) { |  | ||||||
|         ignoreException(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| void SQLiteStmt::create(sqlite3 * db, const string & s) |  | ||||||
| { |  | ||||||
|     checkInterrupt(); |  | ||||||
|     assert(!stmt); |  | ||||||
|     if (sqlite3_prepare_v2(db, s.c_str(), -1, &stmt, 0) != SQLITE_OK) |  | ||||||
|         throwSQLiteError(db, "creating statement"); |  | ||||||
|     this->db = db; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| void SQLiteStmt::reset() |  | ||||||
| { |  | ||||||
|     assert(stmt); |  | ||||||
|     /* Note: sqlite3_reset() returns the error code for the most
 |  | ||||||
|        recent call to sqlite3_step().  So ignore it. */ |  | ||||||
|     sqlite3_reset(stmt); |  | ||||||
|     curArg = 1; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| SQLiteStmt::~SQLiteStmt() |  | ||||||
| { |  | ||||||
|     try { |  | ||||||
|         if (stmt && sqlite3_finalize(stmt) != SQLITE_OK) |  | ||||||
|             throwSQLiteError(db, "finalizing statement"); |  | ||||||
|     } catch (...) { |  | ||||||
|         ignoreException(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| void SQLiteStmt::bind(const string & value) |  | ||||||
| { |  | ||||||
|     if (sqlite3_bind_text(stmt, curArg++, value.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK) |  | ||||||
|         throwSQLiteError(db, "binding argument"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| void SQLiteStmt::bind(int value) |  | ||||||
| { |  | ||||||
|     if (sqlite3_bind_int(stmt, curArg++, value) != SQLITE_OK) |  | ||||||
|         throwSQLiteError(db, "binding argument"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| void SQLiteStmt::bind64(long long value) |  | ||||||
| { |  | ||||||
|     if (sqlite3_bind_int64(stmt, curArg++, value) != SQLITE_OK) |  | ||||||
|         throwSQLiteError(db, "binding argument"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| void SQLiteStmt::bind() |  | ||||||
| { |  | ||||||
|     if (sqlite3_bind_null(stmt, curArg++) != SQLITE_OK) |  | ||||||
|         throwSQLiteError(db, "binding argument"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| /* Helper class to ensure that prepared statements are reset when
 |  | ||||||
|    leaving the scope that uses them.  Unfinished prepared statements |  | ||||||
|    prevent transactions from being aborted, and can cause locks to be |  | ||||||
|    kept when they should be released. */ |  | ||||||
| struct SQLiteStmtUse |  | ||||||
| { |  | ||||||
|     SQLiteStmt & stmt; |  | ||||||
|     SQLiteStmtUse(SQLiteStmt & stmt) : stmt(stmt) |  | ||||||
|     { |  | ||||||
|         stmt.reset(); |  | ||||||
|     } |  | ||||||
|     ~SQLiteStmtUse() |  | ||||||
|     { |  | ||||||
|         try { |  | ||||||
|             stmt.reset(); |  | ||||||
|         } catch (...) { |  | ||||||
|             ignoreException(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| struct SQLiteTxn |  | ||||||
| { |  | ||||||
|     bool active; |  | ||||||
|     sqlite3 * db; |  | ||||||
| 
 |  | ||||||
|     SQLiteTxn(sqlite3 * db) : active(false) { |  | ||||||
|         this->db = db; |  | ||||||
|         if (sqlite3_exec(db, "begin;", 0, 0, 0) != SQLITE_OK) |  | ||||||
|             throwSQLiteError(db, "starting transaction"); |  | ||||||
|         active = true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     void commit() |  | ||||||
|     { |  | ||||||
|         if (sqlite3_exec(db, "commit;", 0, 0, 0) != SQLITE_OK) |  | ||||||
|             throwSQLiteError(db, "committing transaction"); |  | ||||||
|         active = false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     ~SQLiteTxn() |  | ||||||
|     { |  | ||||||
|         try { |  | ||||||
|             if (active && sqlite3_exec(db, "rollback;", 0, 0, 0) != SQLITE_OK) |  | ||||||
|                 throwSQLiteError(db, "aborting transaction"); |  | ||||||
|         } catch (...) { |  | ||||||
|             ignoreException(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| void checkStoreNotSymlink() | void checkStoreNotSymlink() | ||||||
| { | { | ||||||
|     if (getEnv("NIX_IGNORE_SYMLINK_STORE") == "1") return; |     if (getEnv("NIX_IGNORE_SYMLINK_STORE") == "1") return; | ||||||
|  |  | ||||||
|  | @ -1,15 +1,12 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include "sqlite.hh" | ||||||
| #include <string> | #include <string> | ||||||
| #include <unordered_set> | #include <unordered_set> | ||||||
| 
 | 
 | ||||||
|  | #include "pathlocks.hh" | ||||||
| #include "store-api.hh" | #include "store-api.hh" | ||||||
| #include "util.hh" | #include "util.hh" | ||||||
| #include "pathlocks.hh" |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class sqlite3; |  | ||||||
| class sqlite3_stmt; |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| namespace nix { | namespace nix { | ||||||
|  | @ -52,34 +49,6 @@ struct RunningSubstituter | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| /* Wrapper object to close the SQLite database automatically. */ |  | ||||||
| struct SQLite |  | ||||||
| { |  | ||||||
|     sqlite3 * db; |  | ||||||
|     SQLite() { db = 0; } |  | ||||||
|     ~SQLite(); |  | ||||||
|     operator sqlite3 * () { return db; } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| /* Wrapper object to create and destroy SQLite prepared statements. */ |  | ||||||
| struct SQLiteStmt |  | ||||||
| { |  | ||||||
|     sqlite3 * db; |  | ||||||
|     sqlite3_stmt * stmt; |  | ||||||
|     unsigned int curArg; |  | ||||||
|     SQLiteStmt() { stmt = 0; } |  | ||||||
|     void create(sqlite3 * db, const string & s); |  | ||||||
|     void reset(); |  | ||||||
|     ~SQLiteStmt(); |  | ||||||
|     operator sqlite3_stmt * () { return stmt; } |  | ||||||
|     void bind(const string & value); |  | ||||||
|     void bind(int value); |  | ||||||
|     void bind64(long long value); |  | ||||||
|     void bind(); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class LocalStore : public LocalFSStore | class LocalStore : public LocalFSStore | ||||||
| { | { | ||||||
| private: | private: | ||||||
|  |  | ||||||
							
								
								
									
										139
									
								
								src/libstore/sqlite.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								src/libstore/sqlite.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,139 @@ | ||||||
|  | #include "sqlite.hh" | ||||||
|  | #include "util.hh" | ||||||
|  | 
 | ||||||
|  | #include <sqlite3.h> | ||||||
|  | 
 | ||||||
|  | namespace nix { | ||||||
|  | 
 | ||||||
|  | [[noreturn]] void throwSQLiteError(sqlite3 * db, const format & f) | ||||||
|  | { | ||||||
|  |     int err = sqlite3_errcode(db); | ||||||
|  |     if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) { | ||||||
|  |         if (err == SQLITE_PROTOCOL) | ||||||
|  |             printMsg(lvlError, "warning: SQLite database is busy (SQLITE_PROTOCOL)"); | ||||||
|  |         else { | ||||||
|  |             static bool warned = false; | ||||||
|  |             if (!warned) { | ||||||
|  |                 printMsg(lvlError, "warning: SQLite database is busy"); | ||||||
|  |                 warned = true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         /* Sleep for a while since retrying the transaction right away
 | ||||||
|  |            is likely to fail again. */ | ||||||
|  | #if HAVE_NANOSLEEP | ||||||
|  |         struct timespec t; | ||||||
|  |         t.tv_sec = 0; | ||||||
|  |         t.tv_nsec = (random() % 100) * 1000 * 1000; /* <= 0.1s */ | ||||||
|  |         nanosleep(&t, 0); | ||||||
|  | #else | ||||||
|  |         sleep(1); | ||||||
|  | #endif | ||||||
|  |         throw SQLiteBusy(format("%1%: %2%") % f.str() % sqlite3_errmsg(db)); | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |         throw SQLiteError(format("%1%: %2%") % f.str() % sqlite3_errmsg(db)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SQLite::~SQLite() | ||||||
|  | { | ||||||
|  |     try { | ||||||
|  |         if (db && sqlite3_close(db) != SQLITE_OK) | ||||||
|  |             throwSQLiteError(db, "closing database"); | ||||||
|  |     } catch (...) { | ||||||
|  |         ignoreException(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SQLiteStmt::create(sqlite3 * db, const string & s) | ||||||
|  | { | ||||||
|  |     checkInterrupt(); | ||||||
|  |     assert(!stmt); | ||||||
|  |     if (sqlite3_prepare_v2(db, s.c_str(), -1, &stmt, 0) != SQLITE_OK) | ||||||
|  |         throwSQLiteError(db, "creating statement"); | ||||||
|  |     this->db = db; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SQLiteStmt::reset() | ||||||
|  | { | ||||||
|  |     assert(stmt); | ||||||
|  |     /* Note: sqlite3_reset() returns the error code for the most
 | ||||||
|  |        recent call to sqlite3_step().  So ignore it. */ | ||||||
|  |     sqlite3_reset(stmt); | ||||||
|  |     curArg = 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SQLiteStmt::~SQLiteStmt() | ||||||
|  | { | ||||||
|  |     try { | ||||||
|  |         if (stmt && sqlite3_finalize(stmt) != SQLITE_OK) | ||||||
|  |             throwSQLiteError(db, "finalizing statement"); | ||||||
|  |     } catch (...) { | ||||||
|  |         ignoreException(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SQLiteStmt::bind(const string & value) | ||||||
|  | { | ||||||
|  |     if (sqlite3_bind_text(stmt, curArg++, value.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK) | ||||||
|  |         throwSQLiteError(db, "binding argument"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SQLiteStmt::bind(int value) | ||||||
|  | { | ||||||
|  |     if (sqlite3_bind_int(stmt, curArg++, value) != SQLITE_OK) | ||||||
|  |         throwSQLiteError(db, "binding argument"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SQLiteStmt::bind64(long long value) | ||||||
|  | { | ||||||
|  |     if (sqlite3_bind_int64(stmt, curArg++, value) != SQLITE_OK) | ||||||
|  |         throwSQLiteError(db, "binding argument"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SQLiteStmt::bind() | ||||||
|  | { | ||||||
|  |     if (sqlite3_bind_null(stmt, curArg++) != SQLITE_OK) | ||||||
|  |         throwSQLiteError(db, "binding argument"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SQLiteStmtUse::SQLiteStmtUse(SQLiteStmt & stmt) | ||||||
|  |     : stmt(stmt) | ||||||
|  | { | ||||||
|  |     stmt.reset(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SQLiteStmtUse::~SQLiteStmtUse() | ||||||
|  | { | ||||||
|  |     try { | ||||||
|  |         stmt.reset(); | ||||||
|  |     } catch (...) { | ||||||
|  |         ignoreException(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SQLiteTxn::SQLiteTxn(sqlite3 * db) | ||||||
|  | { | ||||||
|  |     this->db = db; | ||||||
|  |     if (sqlite3_exec(db, "begin;", 0, 0, 0) != SQLITE_OK) | ||||||
|  |         throwSQLiteError(db, "starting transaction"); | ||||||
|  |     active = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SQLiteTxn::commit() | ||||||
|  | { | ||||||
|  |     if (sqlite3_exec(db, "commit;", 0, 0, 0) != SQLITE_OK) | ||||||
|  |         throwSQLiteError(db, "committing transaction"); | ||||||
|  |     active = false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SQLiteTxn::~SQLiteTxn() | ||||||
|  | { | ||||||
|  |     try { | ||||||
|  |         if (active && sqlite3_exec(db, "rollback;", 0, 0, 0) != SQLITE_OK) | ||||||
|  |             throwSQLiteError(db, "aborting transaction"); | ||||||
|  |     } catch (...) { | ||||||
|  |         ignoreException(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										83
									
								
								src/libstore/sqlite.hh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/libstore/sqlite.hh
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,83 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <functional> | ||||||
|  | #include <string> | ||||||
|  | 
 | ||||||
|  | #include "types.hh" | ||||||
|  | 
 | ||||||
|  | class sqlite3; | ||||||
|  | class sqlite3_stmt; | ||||||
|  | 
 | ||||||
|  | namespace nix { | ||||||
|  | 
 | ||||||
|  | /* RAII wrapper to close a SQLite database automatically. */ | ||||||
|  | struct SQLite | ||||||
|  | { | ||||||
|  |     sqlite3 * db; | ||||||
|  |     SQLite() { db = 0; } | ||||||
|  |     ~SQLite(); | ||||||
|  |     operator sqlite3 * () { return db; } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /* RAII wrapper to create and destroy SQLite prepared statements. */ | ||||||
|  | struct SQLiteStmt | ||||||
|  | { | ||||||
|  |     sqlite3 * db; | ||||||
|  |     sqlite3_stmt * stmt; | ||||||
|  |     unsigned int curArg; | ||||||
|  |     SQLiteStmt() { stmt = 0; } | ||||||
|  |     void create(sqlite3 * db, const std::string & s); | ||||||
|  |     void reset(); | ||||||
|  |     ~SQLiteStmt(); | ||||||
|  |     operator sqlite3_stmt * () { return stmt; } | ||||||
|  |     void bind(const std::string & value); | ||||||
|  |     void bind(int value); | ||||||
|  |     void bind64(long long value); | ||||||
|  |     void bind(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /* Helper class to ensure that prepared statements are reset when
 | ||||||
|  |    leaving the scope that uses them.  Unfinished prepared statements | ||||||
|  |    prevent transactions from being aborted, and can cause locks to be | ||||||
|  |    kept when they should be released. */ | ||||||
|  | struct SQLiteStmtUse | ||||||
|  | { | ||||||
|  |     SQLiteStmt & stmt; | ||||||
|  |     SQLiteStmtUse(SQLiteStmt & stmt); | ||||||
|  |     ~SQLiteStmtUse(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /* RAII helper that ensures transactions are aborted unless explicitly
 | ||||||
|  |    committed. */ | ||||||
|  | struct SQLiteTxn | ||||||
|  | { | ||||||
|  |     bool active = false; | ||||||
|  |     sqlite3 * db; | ||||||
|  | 
 | ||||||
|  |     SQLiteTxn(sqlite3 * db); | ||||||
|  | 
 | ||||||
|  |     void commit(); | ||||||
|  | 
 | ||||||
|  |     ~SQLiteTxn(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | MakeError(SQLiteError, Error); | ||||||
|  | MakeError(SQLiteBusy, SQLiteError); | ||||||
|  | 
 | ||||||
|  | [[noreturn]] void throwSQLiteError(sqlite3 * db, const format & f); | ||||||
|  | 
 | ||||||
|  | /* Convenience function for retrying a SQLite transaction when the
 | ||||||
|  |    database is busy. */ | ||||||
|  | template<typename T> | ||||||
|  | T retrySQLite(std::function<T()> fun) | ||||||
|  | { | ||||||
|  |     while (true) { | ||||||
|  |         try { | ||||||
|  |             return fun(); | ||||||
|  |         } catch (SQLiteBusy & e) { | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue