mirror of
https://github.com/NixOS/nix.git
synced 2025-11-13 22:12:43 +01:00
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)))) {
227 lines
5.5 KiB
C++
227 lines
5.5 KiB
C++
#include "sqlite.hh"
|
|
#include "util.hh"
|
|
|
|
#include <sqlite3.h>
|
|
|
|
#include <atomic>
|
|
|
|
namespace nix {
|
|
|
|
[[noreturn]] void throwSQLiteError(sqlite3 * db, const FormatOrString & fs)
|
|
{
|
|
int err = sqlite3_errcode(db);
|
|
int exterr = sqlite3_extended_errcode(db);
|
|
|
|
auto path = sqlite3_db_filename(db, nullptr);
|
|
if (!path) path = "(in-memory)";
|
|
|
|
if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) {
|
|
throw SQLiteBusy(
|
|
err == SQLITE_PROTOCOL
|
|
? fmt("SQLite database '%s' is busy (SQLITE_PROTOCOL)", path)
|
|
: fmt("SQLite database '%s' is busy", path));
|
|
}
|
|
else
|
|
throw SQLiteError("%s: %s (in '%s')", fs.s, sqlite3_errstr(exterr), path);
|
|
}
|
|
|
|
SQLite::SQLite(const Path & path, bool create)
|
|
{
|
|
if (sqlite3_open_v2(path.c_str(), &db,
|
|
SQLITE_OPEN_READWRITE | (create ? SQLITE_OPEN_CREATE : 0), 0) != SQLITE_OK)
|
|
throw Error("cannot open SQLite database '%s'", path);
|
|
|
|
if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK)
|
|
throwSQLiteError(db, "setting timeout");
|
|
|
|
exec("pragma foreign_keys = 1");
|
|
}
|
|
|
|
SQLite::~SQLite()
|
|
{
|
|
try {
|
|
if (db && sqlite3_close(db) != SQLITE_OK)
|
|
throwSQLiteError(db, "closing database");
|
|
} catch (...) {
|
|
ignoreException();
|
|
}
|
|
}
|
|
|
|
void SQLite::isCache()
|
|
{
|
|
exec("pragma synchronous = off");
|
|
exec("pragma main.journal_mode = truncate");
|
|
}
|
|
|
|
void SQLite::exec(const std::string & stmt)
|
|
{
|
|
retrySQLite<void>([&]() {
|
|
if (sqlite3_exec(db, stmt.c_str(), 0, 0, 0) != SQLITE_OK)
|
|
throwSQLiteError(db, format("executing SQLite statement '%s'") % stmt);
|
|
});
|
|
}
|
|
|
|
uint64_t SQLite::getLastInsertedRowId()
|
|
{
|
|
return sqlite3_last_insert_rowid(db);
|
|
}
|
|
|
|
void SQLiteStmt::create(sqlite3 * db, const string & sql)
|
|
{
|
|
checkInterrupt();
|
|
assert(!stmt);
|
|
if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0) != SQLITE_OK)
|
|
throwSQLiteError(db, fmt("creating statement '%s'", sql));
|
|
this->db = db;
|
|
this->sql = sql;
|
|
}
|
|
|
|
SQLiteStmt::~SQLiteStmt()
|
|
{
|
|
try {
|
|
if (stmt && sqlite3_finalize(stmt) != SQLITE_OK)
|
|
throwSQLiteError(db, fmt("finalizing statement '%s'", sql));
|
|
} catch (...) {
|
|
ignoreException();
|
|
}
|
|
}
|
|
|
|
SQLiteStmt::Use::Use(SQLiteStmt & stmt)
|
|
: stmt(stmt)
|
|
{
|
|
assert(stmt.stmt);
|
|
/* Note: sqlite3_reset() returns the error code for the most
|
|
recent call to sqlite3_step(). So ignore it. */
|
|
sqlite3_reset(stmt);
|
|
}
|
|
|
|
SQLiteStmt::Use::~Use()
|
|
{
|
|
sqlite3_reset(stmt);
|
|
}
|
|
|
|
SQLiteStmt::Use & SQLiteStmt::Use::operator () (std::string_view value, bool notNull)
|
|
{
|
|
if (notNull) {
|
|
if (sqlite3_bind_text(stmt, curArg++, value.data(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
|
|
throwSQLiteError(stmt.db, "binding argument");
|
|
} else
|
|
bind();
|
|
return *this;
|
|
}
|
|
|
|
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)
|
|
throwSQLiteError(stmt.db, "binding argument");
|
|
} else
|
|
bind();
|
|
return *this;
|
|
}
|
|
|
|
SQLiteStmt::Use & SQLiteStmt::Use::operator () (int64_t value, bool notNull)
|
|
{
|
|
if (notNull) {
|
|
if (sqlite3_bind_int64(stmt, curArg++, value) != SQLITE_OK)
|
|
throwSQLiteError(stmt.db, "binding argument");
|
|
} else
|
|
bind();
|
|
return *this;
|
|
}
|
|
|
|
SQLiteStmt::Use & SQLiteStmt::Use::bind()
|
|
{
|
|
if (sqlite3_bind_null(stmt, curArg++) != SQLITE_OK)
|
|
throwSQLiteError(stmt.db, "binding argument");
|
|
return *this;
|
|
}
|
|
|
|
int SQLiteStmt::Use::step()
|
|
{
|
|
return sqlite3_step(stmt);
|
|
}
|
|
|
|
void SQLiteStmt::Use::exec()
|
|
{
|
|
int r = step();
|
|
assert(r != SQLITE_ROW);
|
|
if (r != SQLITE_DONE)
|
|
throwSQLiteError(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)
|
|
throwSQLiteError(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);
|
|
}
|
|
|
|
bool SQLiteStmt::Use::isNull(int col)
|
|
{
|
|
return sqlite3_column_type(stmt, col) == SQLITE_NULL;
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
void handleSQLiteBusy(const SQLiteBusy & e)
|
|
{
|
|
static std::atomic<time_t> lastWarned{0};
|
|
|
|
time_t now = time(0);
|
|
|
|
if (now > lastWarned + 10) {
|
|
lastWarned = now;
|
|
logWarning({
|
|
.name = "Sqlite busy",
|
|
.msg = hintfmt(e.what())
|
|
});
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
}
|