* Implement registerValidPath().

This commit is contained in:
Eelco Dolstra 2010-02-19 16:04:51 +00:00
parent 268f9aaf28
commit 762cee72cc
3 changed files with 96 additions and 112 deletions

View file

@ -74,6 +74,24 @@ SQLiteStmt::~SQLiteStmt()
} }
/* 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()
{
stmt.reset();
}
};
struct SQLiteTxn struct SQLiteTxn
{ {
bool active; bool active;
@ -223,10 +241,10 @@ int LocalStore::getSchema()
} }
#include "schema.sql.hh"
void LocalStore::initSchema() void LocalStore::initSchema()
{ {
#include "schema.sql.hh"
if (sqlite3_exec(db, (const char *) schema, 0, 0, 0) != SQLITE_OK) if (sqlite3_exec(db, (const char *) schema, 0, 0, 0) != SQLITE_OK)
throw SQLiteError(db, "initialising database schema"); throw SQLiteError(db, "initialising database schema");
@ -237,9 +255,9 @@ void LocalStore::initSchema()
void LocalStore::prepareStatements() void LocalStore::prepareStatements()
{ {
stmtRegisterValidPath.create(db, stmtRegisterValidPath.create(db,
"insert into ValidPaths (path, hash, registrationTime, deriver) values (?, ?, ?, ?);"); "insert or replace into ValidPaths (path, hash, registrationTime, deriver) values (?, ?, ?, ?);");
stmtAddReference.create(db, stmtAddReference.create(db,
"insert into Refs (referrer, reference) values (?, ?);"); "insert or replace into Refs (referrer, reference) values (?, ?);");
stmtQueryPathInfo.create(db, stmtQueryPathInfo.create(db,
"select id, hash, registrationTime, deriver from ValidPaths where path = ?;"); "select id, hash, registrationTime, deriver from ValidPaths where path = ?;");
stmtQueryReferences.create(db, stmtQueryReferences.create(db,
@ -336,75 +354,69 @@ void LocalStore::registerValidPath(const Path & path,
} }
void LocalStore::registerValidPath(const ValidPathInfo & info, bool ignoreValidity) unsigned long long LocalStore::addValidPath(const ValidPathInfo & info)
{ {
abort(); SQLiteStmtUse use(stmtRegisterValidPath);
#if 0 if (sqlite3_bind_text(stmtRegisterValidPath, 1, info.path.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
Path infoFile = infoFileFor(info.path); throw SQLiteError(db, "binding argument");
string h = "sha256:" + printHash(info.hash);
ValidPathInfo oldInfo; if (sqlite3_bind_text(stmtRegisterValidPath, 2, h.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
if (pathExists(infoFile)) oldInfo = queryPathInfo(info.path); throw SQLiteError(db, "binding argument");
if (sqlite3_bind_int(stmtRegisterValidPath, 3, info.registrationTime) != SQLITE_OK)
/* Note that it's possible for infoFile to already exist. */ throw SQLiteError(db, "binding argument");
if (info.deriver != "") {
/* Acquire a lock on each referrer file. This prevents those if (sqlite3_bind_text(stmtRegisterValidPath, 4, info.deriver.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
paths from being invalidated. (It would be a violation of the throw SQLiteError(db, "binding argument");
store invariants if we registered info.path as valid while some } else {
of its references are invalid.) NB: there can be no deadlock if (sqlite3_bind_null(stmtRegisterValidPath, 4) != SQLITE_OK)
here since we're acquiring the locks in sorted order. */ throw SQLiteError(db, "binding argument");
PathSet lockNames; }
foreach (PathSet::const_iterator, i, info.references) if (sqlite3_step(stmtRegisterValidPath) != SQLITE_DONE)
if (*i != info.path) lockNames.insert(referrersFileFor(*i)); throw SQLiteError(db, format("registering valid path `%1%' in database") % info.path);
PathLocks referrerLocks(lockNames); return sqlite3_last_insert_rowid(db);
referrerLocks.setDeletion(true);
string refs;
foreach (PathSet::const_iterator, i, info.references) {
if (!refs.empty()) refs += " ";
refs += *i;
if (!ignoreValidity && *i != info.path && !isValidPath(*i))
throw Error(format("cannot register `%1%' as valid, because its reference `%2%' isn't valid")
% info.path % *i);
/* Update the referrer mapping for *i. This must be done
before the info file is written to maintain the invariant
that if `path' is a valid path, then all its references
have referrer mappings back to `path'. A " " is prefixed
to separate it from the previous entry. It's not suffixed
to deal with interrupted partial writes to this file. */
if (oldInfo.references.find(*i) == oldInfo.references.end())
appendReferrer(*i, info.path, false);
} }
void LocalStore::addReference(unsigned long long referrer, unsigned long long reference)
{
SQLiteStmtUse use(stmtAddReference);
if (sqlite3_bind_int(stmtAddReference, 1, referrer) != SQLITE_OK)
throw SQLiteError(db, "binding argument");
if (sqlite3_bind_int(stmtAddReference, 2, reference) != SQLITE_OK)
throw SQLiteError(db, "binding argument");
if (sqlite3_step(stmtAddReference) != SQLITE_DONE)
throw SQLiteError(db, "adding reference to database");
}
void LocalStore::registerValidPath(const ValidPathInfo & info)
{
assert(info.hash.type == htSHA256); assert(info.hash.type == htSHA256);
ValidPathInfo info2(info);
if (info2.registrationTime == 0) info2.registrationTime = time(0);
string s = (format( SQLiteTxn txn(db);
"Hash: sha256:%1%\n"
"References: %2%\n"
"Deriver: %3%\n"
"Registered-At: %4%\n")
% printHash(info.hash) % refs % info.deriver %
(oldInfo.registrationTime ? oldInfo.registrationTime : time(0))).str();
/* Atomically rewrite the info file. */ unsigned long long id = addValidPath(info2);
Path tmpFile = tmpFileForAtomicUpdate(infoFile);
writeFile(tmpFile, s, doFsync); foreach (PathSet::const_iterator, i, info2.references) {
if (rename(tmpFile.c_str(), infoFile.c_str()) == -1) ValidPathInfo ref = queryPathInfo(*i);
throw SysError(format("cannot rename `%1%' to `%2%'") % tmpFile % infoFile); addReference(id, ref.id);
#endif }
txn.commit();
} }
void LocalStore::registerFailedPath(const Path & path) void LocalStore::registerFailedPath(const Path & path)
{ {
abort(); throw Error("not implemented");
} }
bool LocalStore::hasPathFailed(const Path & path) bool LocalStore::hasPathFailed(const Path & path)
{ {
abort(); throw Error("not implemented");
} }
@ -424,13 +436,13 @@ Hash parseHashField(const Path & path, const string & s)
ValidPathInfo LocalStore::queryPathInfo(const Path & path) ValidPathInfo LocalStore::queryPathInfo(const Path & path)
{ {
ValidPathInfo res; ValidPathInfo info;
res.path = path; info.path = path;
assertStorePath(path); assertStorePath(path);
/* Get the path info. */ /* Get the path info. */
stmtQueryPathInfo.reset(); SQLiteStmtUse use1(stmtQueryPathInfo);
if (sqlite3_bind_text(stmtQueryPathInfo, 1, path.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK) if (sqlite3_bind_text(stmtQueryPathInfo, 1, path.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
throw SQLiteError(db, "binding argument"); throw SQLiteError(db, "binding argument");
@ -439,39 +451,39 @@ ValidPathInfo LocalStore::queryPathInfo(const Path & path)
if (r == SQLITE_DONE) throw Error(format("path `%1%' is not valid") % path); if (r == SQLITE_DONE) throw Error(format("path `%1%' is not valid") % path);
if (r != SQLITE_ROW) throw SQLiteError(db, "querying path in database"); if (r != SQLITE_ROW) throw SQLiteError(db, "querying path in database");
unsigned int id = sqlite3_column_int(stmtQueryPathInfo, 0); info.id = sqlite3_column_int(stmtQueryPathInfo, 0);
const char * s = (const char *) sqlite3_column_text(stmtQueryPathInfo, 1); const char * s = (const char *) sqlite3_column_text(stmtQueryPathInfo, 1);
assert(s); assert(s);
res.hash = parseHashField(path, s); info.hash = parseHashField(path, s);
res.registrationTime = sqlite3_column_int(stmtQueryPathInfo, 2); info.registrationTime = sqlite3_column_int(stmtQueryPathInfo, 2);
s = (const char *) sqlite3_column_text(stmtQueryPathInfo, 3); s = (const char *) sqlite3_column_text(stmtQueryPathInfo, 3);
if (s) res.deriver = s; if (s) info.deriver = s;
/* Get the references. */ /* Get the references. */
stmtQueryReferences.reset(); SQLiteStmtUse use2(stmtQueryReferences);
if (sqlite3_bind_int(stmtQueryReferences, 1, id) != SQLITE_OK) if (sqlite3_bind_int(stmtQueryReferences, 1, info.id) != SQLITE_OK)
throw SQLiteError(db, "binding argument"); throw SQLiteError(db, "binding argument");
while ((r = sqlite3_step(stmtQueryReferences)) == SQLITE_ROW) { while ((r = sqlite3_step(stmtQueryReferences)) == SQLITE_ROW) {
s = (const char *) sqlite3_column_text(stmtQueryReferences, 0); s = (const char *) sqlite3_column_text(stmtQueryReferences, 0);
assert(s); assert(s);
res.references.insert(s); info.references.insert(s);
} }
if (r != SQLITE_DONE) if (r != SQLITE_DONE)
throw SQLiteError(db, format("error getting references of `%1%'") % path); throw SQLiteError(db, format("error getting references of `%1%'") % path);
return res; return info;
} }
bool LocalStore::isValidPath(const Path & path) bool LocalStore::isValidPath(const Path & path)
{ {
stmtQueryPathInfo.reset(); SQLiteStmtUse use(stmtQueryPathInfo);
if (sqlite3_bind_text(stmtQueryPathInfo, 1, path.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK) if (sqlite3_bind_text(stmtQueryPathInfo, 1, path.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
throw SQLiteError(db, "binding argument"); throw SQLiteError(db, "binding argument");
int res = sqlite3_step(stmtQueryPathInfo); int res = sqlite3_step(stmtQueryPathInfo);
@ -514,7 +526,7 @@ void LocalStore::queryReferrers(const Path & path, PathSet & referrers)
{ {
assertStorePath(path); assertStorePath(path);
stmtQueryReferrers.reset(); SQLiteStmtUse use(stmtQueryReferrers);
if (sqlite3_bind_text(stmtQueryReferrers, 1, path.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK) if (sqlite3_bind_text(stmtQueryReferrers, 1, path.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
throw SQLiteError(db, "binding argument"); throw SQLiteError(db, "binding argument");
@ -687,7 +699,7 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
there are no referrers. */ there are no referrers. */
void LocalStore::invalidatePath(const Path & path) void LocalStore::invalidatePath(const Path & path)
{ {
abort(); throw Error("not implemented");
#if 0 #if 0
debug(format("invalidating path `%1%'") % path); debug(format("invalidating path `%1%'") % path);
@ -1010,7 +1022,8 @@ Path LocalStore::importPath(bool requireSignature, Source & source)
void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFreed, void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFreed,
unsigned long long & blocksFreed) unsigned long long & blocksFreed)
{ {
abort(); throw Error("not implemented");
#if 0 #if 0
bytesFreed = 0; bytesFreed = 0;
@ -1077,13 +1090,6 @@ void LocalStore::verifyStore(bool checkContents)
/* Functions for upgrading from the pre-SQLite database. */ /* Functions for upgrading from the pre-SQLite database. */
static Path infoFileFor(const Path & path)
{
string baseName = baseNameOf(path);
return (format("%1%/info/%2%") % nixDBPath % baseName).str();
}
PathSet LocalStore::queryValidPathsOld() PathSet LocalStore::queryValidPathsOld()
{ {
PathSet paths; PathSet paths;
@ -1100,7 +1106,8 @@ ValidPathInfo LocalStore::queryPathInfoOld(const Path & path)
res.path = path; res.path = path;
/* Read the info file. */ /* Read the info file. */
Path infoFile = infoFileFor(path); string baseName = baseNameOf(path);
Path infoFile = (format("%1%/info/%2%") % nixDBPath % baseName).str();
if (!pathExists(infoFile)) if (!pathExists(infoFile))
throw Error(format("path `%1%' is not valid") % path); throw Error(format("path `%1%' is not valid") % path);
string info = readFile(infoFile); string info = readFile(infoFile);
@ -1152,27 +1159,7 @@ void LocalStore::upgradeStore6()
foreach (PathSet::iterator, i, validPaths) { foreach (PathSet::iterator, i, validPaths) {
ValidPathInfo info = queryPathInfoOld(*i); ValidPathInfo info = queryPathInfoOld(*i);
pathToId[*i] = addValidPath(info);
stmtRegisterValidPath.reset();
if (sqlite3_bind_text(stmtRegisterValidPath, 1, i->c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
throw SQLiteError(db, "binding argument");
string h = "sha256:" + printHash(info.hash);
if (sqlite3_bind_text(stmtRegisterValidPath, 2, h.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
throw SQLiteError(db, "binding argument");
if (sqlite3_bind_int(stmtRegisterValidPath, 3, info.registrationTime) != SQLITE_OK)
throw SQLiteError(db, "binding argument");
if (info.deriver != "") {
if (sqlite3_bind_text(stmtRegisterValidPath, 4, info.deriver.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
throw SQLiteError(db, "binding argument");
} else {
if (sqlite3_bind_null(stmtRegisterValidPath, 4) != SQLITE_OK)
throw SQLiteError(db, "binding argument");
}
if (sqlite3_step(stmtRegisterValidPath) != SQLITE_DONE)
throw SQLiteError(db, "registering valid path in database");
pathToId[*i] = sqlite3_last_insert_rowid(db);
std::cerr << "."; std::cerr << ".";
} }
@ -1180,19 +1167,11 @@ void LocalStore::upgradeStore6()
foreach (PathSet::iterator, i, validPaths) { foreach (PathSet::iterator, i, validPaths) {
ValidPathInfo info = queryPathInfoOld(*i); ValidPathInfo info = queryPathInfoOld(*i);
foreach (PathSet::iterator, j, info.references) { foreach (PathSet::iterator, j, info.references) {
stmtAddReference.reset();
if (sqlite3_bind_int(stmtAddReference, 1, pathToId[*i]) != SQLITE_OK)
throw SQLiteError(db, "binding argument");
if (pathToId.find(*j) == pathToId.end()) if (pathToId.find(*j) == pathToId.end())
throw Error(format("path `%1%' referenced by `%2%' is invalid") % *j % *i); throw Error(format("path `%1%' referenced by `%2%' is invalid") % *j % *i);
if (sqlite3_bind_int(stmtAddReference, 2, pathToId[*j]) != SQLITE_OK) addReference(pathToId[*i], pathToId[*j]);
throw SQLiteError(db, "binding argument");
if (sqlite3_step(stmtAddReference) != SQLITE_DONE)
throw SQLiteError(db, "adding reference to database");
} }
std::cerr << "."; std::cerr << ".";
} }

View file

@ -198,7 +198,11 @@ private:
void prepareStatements(); void prepareStatements();
void registerValidPath(const ValidPathInfo & info, bool ignoreValidity = false); unsigned long long addValidPath(const ValidPathInfo & info);
void addReference(unsigned long long referrer, unsigned long long reference);
void registerValidPath(const ValidPathInfo & info);
ValidPathInfo queryPathInfo(const Path & path); ValidPathInfo queryPathInfo(const Path & path);

View file

@ -333,6 +333,7 @@ struct ValidPathInfo
Hash hash; Hash hash;
PathSet references; PathSet references;
time_t registrationTime; time_t registrationTime;
unsigned long long id; // internal use only
ValidPathInfo() : registrationTime(0) { } ValidPathInfo() : registrationTime(0) { }
}; };