lix/src/libstore/sqlite.cc

247 lines
6.4 KiB
C++
Raw Normal View History

2016-03-30 11:27:25 +00:00
#include "sqlite.hh"
#include "globals.hh"
2016-03-30 11:27:25 +00:00
#include "util.hh"
#include <sqlite3.h>
2017-02-28 12:59:11 +00:00
#include <atomic>
2016-03-30 11:27:25 +00:00
namespace nix {
SQLiteError::SQLiteError(const char *path, const char *errMsg, int errNo, int extendedErrNo, int offset, hintformat && hf)
: Error(""), path(path), errMsg(errMsg), errNo(errNo), extendedErrNo(extendedErrNo), offset(offset)
{
auto offsetStr = (offset == -1) ? "" : "at offset " + std::to_string(offset) + ": ";
err.msg = hintfmt("%s: %s%s, %s (in '%s')",
normaltxt(hf.str()),
offsetStr,
sqlite3_errstr(extendedErrNo),
errMsg,
path ? path : "(in-memory)");
}
[[noreturn]] void SQLiteError::throw_(sqlite3 * db, hintformat && hf)
{
int err = sqlite3_errcode(db);
int exterr = sqlite3_extended_errcode(db);
int offset = sqlite3_error_offset(db);
auto path = sqlite3_db_filename(db, nullptr);
auto errMsg = sqlite3_errmsg(db);
if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) {
auto exp = SQLiteBusy(path, errMsg, err, exterr, offset, std::move(hf));
exp.err.msg = hintfmt(
err == SQLITE_PROTOCOL
? "SQLite database '%s' is busy (SQLITE_PROTOCOL)"
: "SQLite database '%s' is busy",
path ? path : "(in-memory)");
throw exp;
} else
throw SQLiteError(path, errMsg, err, exterr, offset, std::move(hf));
}
SQLite::SQLite(const Path & path, bool create)
2016-08-09 12:27:30 +00:00
{
// useSQLiteWAL also indicates what virtual file system we need. Using
// `unix-dotfile` is needed on NFS file systems and on Windows' Subsystem
// for Linux (WSL) where useSQLiteWAL should be false by default.
const char *vfs = settings.useSQLiteWAL ? 0 : "unix-dotfile";
2016-08-09 12:27:30 +00:00
if (sqlite3_open_v2(path.c_str(), &db,
SQLITE_OPEN_READWRITE | (create ? SQLITE_OPEN_CREATE : 0), vfs) != SQLITE_OK)
throw Error("cannot open SQLite database '%s'", path);
if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK)
SQLiteError::throw_(db, "setting timeout");
exec("pragma foreign_keys = 1");
2016-08-09 12:27:30 +00:00
}
2016-03-30 11:27:25 +00:00
SQLite::~SQLite()
{
try {
if (db && sqlite3_close(db) != SQLITE_OK)
SQLiteError::throw_(db, "closing database");
2016-03-30 11:27:25 +00:00
} catch (...) {
ignoreException();
}
}
void SQLite::isCache()
{
exec("pragma synchronous = off");
exec("pragma main.journal_mode = truncate");
}
2016-08-09 12:27:30 +00:00
void SQLite::exec(const std::string & stmt)
{
2017-02-28 12:59:11 +00:00
retrySQLite<void>([&]() {
if (sqlite3_exec(db, stmt.c_str(), 0, 0, 0) != SQLITE_OK)
SQLiteError::throw_(db, "executing SQLite statement '%s'", stmt);
2017-02-28 12:59:11 +00:00
});
2016-08-09 12:27:30 +00:00
}
uint64_t SQLite::getLastInsertedRowId()
{
return sqlite3_last_insert_rowid(db);
}
void SQLiteStmt::create(sqlite3 * db, const std::string & sql)
2016-03-30 11:27:25 +00:00
{
checkInterrupt();
assert(!stmt);
2017-02-28 12:59:11 +00:00
if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0) != SQLITE_OK)
SQLiteError::throw_(db, "creating statement '%s'", sql);
2016-03-30 11:27:25 +00:00
this->db = db;
2017-02-28 12:59:11 +00:00
this->sql = sql;
2016-03-30 11:27:25 +00:00
}
SQLiteStmt::~SQLiteStmt()
{
try {
if (stmt && sqlite3_finalize(stmt) != SQLITE_OK)
SQLiteError::throw_(db, "finalizing statement '%s'", sql);
2016-03-30 11:27:25 +00:00
} catch (...) {
ignoreException();
}
}
SQLiteStmt::Use::Use(SQLiteStmt & stmt)
: stmt(stmt)
2016-03-30 11:27:25 +00:00
{
assert(stmt.stmt);
/* Note: sqlite3_reset() returns the error code for the most
recent call to sqlite3_step(). So ignore it. */
sqlite3_reset(stmt);
2016-04-05 13:29:56 +00:00
}
SQLiteStmt::Use::~Use()
{
sqlite3_reset(stmt);
2016-03-30 11:27:25 +00:00
}
2020-04-16 23:00:56 +00:00
SQLiteStmt::Use & SQLiteStmt::Use::operator () (std::string_view value, bool notNull)
2016-03-30 11:27:25 +00:00
{
if (notNull) {
2020-04-16 23:00:56 +00:00
if (sqlite3_bind_text(stmt, curArg++, value.data(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
SQLiteError::throw_(stmt.db, "binding argument");
} else
bind();
return *this;
2016-03-30 11:27:25 +00:00
}
SQLiteStmt::Use & SQLiteStmt::Use::operator () (const unsigned char * data, size_t len, bool notNull)
{
if (notNull) {
if (sqlite3_bind_blob(stmt, curArg++, data, len, SQLITE_TRANSIENT) != SQLITE_OK)
SQLiteError::throw_(stmt.db, "binding argument");
} else
bind();
return *this;
}
SQLiteStmt::Use & SQLiteStmt::Use::operator () (int64_t value, bool notNull)
2016-03-30 11:27:25 +00:00
{
if (notNull) {
if (sqlite3_bind_int64(stmt, curArg++, value) != SQLITE_OK)
SQLiteError::throw_(stmt.db, "binding argument");
} else
bind();
return *this;
2016-03-30 11:27:25 +00:00
}
SQLiteStmt::Use & SQLiteStmt::Use::bind()
2016-03-30 11:27:25 +00:00
{
if (sqlite3_bind_null(stmt, curArg++) != SQLITE_OK)
SQLiteError::throw_(stmt.db, "binding argument");
return *this;
2016-03-30 11:27:25 +00:00
}
int SQLiteStmt::Use::step()
2016-03-30 11:27:25 +00:00
{
return sqlite3_step(stmt);
2016-03-30 11:27:25 +00:00
}
void SQLiteStmt::Use::exec()
2016-03-30 11:27:25 +00:00
{
int r = step();
assert(r != SQLITE_ROW);
if (r != SQLITE_DONE)
SQLiteError::throw_(stmt.db, fmt("executing SQLite statement '%s'", sqlite3_expanded_sql(stmt.stmt)));
}
bool SQLiteStmt::Use::next()
{
int r = step();
if (r != SQLITE_DONE && r != SQLITE_ROW)
SQLiteError::throw_(stmt.db, fmt("executing SQLite query '%s'", sqlite3_expanded_sql(stmt.stmt)));
return r == SQLITE_ROW;
}
std::string SQLiteStmt::Use::getStr(int col)
{
auto s = (const char *) sqlite3_column_text(stmt, col);
assert(s);
return s;
}
int64_t SQLiteStmt::Use::getInt(int col)
{
// FIXME: detect nulls?
return sqlite3_column_int64(stmt, col);
2016-03-30 11:27:25 +00:00
}
bool SQLiteStmt::Use::isNull(int col)
{
return sqlite3_column_type(stmt, col) == SQLITE_NULL;
}
2016-03-30 11:27:25 +00:00
SQLiteTxn::SQLiteTxn(sqlite3 * db)
{
this->db = db;
if (sqlite3_exec(db, "begin;", 0, 0, 0) != SQLITE_OK)
SQLiteError::throw_(db, "starting transaction");
2016-03-30 11:27:25 +00:00
active = true;
}
void SQLiteTxn::commit()
{
if (sqlite3_exec(db, "commit;", 0, 0, 0) != SQLITE_OK)
SQLiteError::throw_(db, "committing transaction");
2016-03-30 11:27:25 +00:00
active = false;
}
SQLiteTxn::~SQLiteTxn()
{
try {
if (active && sqlite3_exec(db, "rollback;", 0, 0, 0) != SQLITE_OK)
SQLiteError::throw_(db, "aborting transaction");
2016-03-30 11:27:25 +00:00
} catch (...) {
ignoreException();
}
}
2017-02-28 12:59:11 +00:00
void handleSQLiteBusy(const SQLiteBusy & e)
{
static std::atomic<time_t> lastWarned{0};
time_t now = time(0);
if (now > lastWarned + 10) {
lastWarned = now;
logWarning({
Improve error formatting Changes: * The divider lines are gone. These were in practice a bit confusing, in particular with --show-trace or --keep-going, since then there were multiple lines, suggesting a start/end which wasn't the case. * Instead, multi-line error messages are now indented to align with the prefix (e.g. "error: "). * The 'description' field is gone since we weren't really using it. * 'hint' is renamed to 'msg' since it really wasn't a hint. * The error is now printed *before* the location info. * The 'name' field is no longer printed since most of the time it wasn't very useful since it was just the name of the exception (like EvalError). Ideally in the future this would be a unique, easily googleable error ID (like rustc). * "trace:" is now just "…". This assumes error contexts start with something like "while doing X". Example before: error: --- AssertionError ---------------------------------------------------------------------------------------- nix at: (7:7) in file: /home/eelco/Dev/nixpkgs/pkgs/applications/misc/hello/default.nix 6| 7| x = assert false; 1; | ^ 8| assertion 'false' failed ----------------------------------------------------- show-trace ----------------------------------------------------- trace: while evaluating the attribute 'x' of the derivation 'hello-2.10' at: (192:11) in file: /home/eelco/Dev/nixpkgs/pkgs/stdenv/generic/make-derivation.nix 191| // (lib.optionalAttrs (!(attrs ? name) && attrs ? pname && attrs ? version)) { 192| name = "${attrs.pname}-${attrs.version}"; | ^ 193| } // (lib.optionalAttrs (stdenv.hostPlatform != stdenv.buildPlatform && !dontAddHostSuffix && (attrs ? name || (attrs ? pname && attrs ? version)))) { Example after: error: assertion 'false' failed at: (7:7) in file: /home/eelco/Dev/nixpkgs/pkgs/applications/misc/hello/default.nix 6| 7| x = assert false; 1; | ^ 8| … while evaluating the attribute 'x' of the derivation 'hello-2.10' at: (192:11) in file: /home/eelco/Dev/nixpkgs/pkgs/stdenv/generic/make-derivation.nix 191| // (lib.optionalAttrs (!(attrs ? name) && attrs ? pname && attrs ? version)) { 192| name = "${attrs.pname}-${attrs.version}"; | ^ 193| } // (lib.optionalAttrs (stdenv.hostPlatform != stdenv.buildPlatform && !dontAddHostSuffix && (attrs ? name || (attrs ? pname && attrs ? version)))) {
2021-01-20 23:27:36 +00:00
.msg = hintfmt(e.what())
});
2017-02-28 12:59:11 +00:00
}
/* Sleep for a while since retrying the transaction right away
is likely to fail again. */
checkInterrupt();
struct timespec t;
t.tv_sec = 0;
t.tv_nsec = (random() % 100) * 1000 * 1000; /* <= 0.1s */
nanosleep(&t, 0);
}
2016-03-30 11:27:25 +00:00
}