Skip to content

Commit 72385de

Browse files
authored
Merge pull request #15288 from roberth/flake-eval-cache-fixes
Flake eval cache fixes
2 parents 6b071af + 4d9b962 commit 72385de

4 files changed

Lines changed: 74 additions & 12 deletions

File tree

src/libexpr/eval-cache.cc

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ struct AttrDb
5858
SQLiteStmt insertAttributeWithContext;
5959
SQLiteStmt queryAttribute;
6060
SQLiteStmt queryAttributes;
61+
SQLiteStmt upsertAttribute;
62+
SQLiteStmt insertAttributeIfNotExists;
63+
SQLiteStmt deleteMissingChildren;
6164
std::unique_ptr<SQLiteTxn> txn;
6265
};
6366

@@ -92,6 +95,17 @@ struct AttrDb
9295

9396
state->queryAttributes.create(state->db, "select name from Attributes where parent = ?");
9497

98+
state->upsertAttribute.create(
99+
state->db,
100+
"insert into Attributes(parent, name, type, value) values (?, ?, ?, ?) "
101+
"on conflict(parent, name) do update set type = excluded.type, value = excluded.value "
102+
"returning rowid");
103+
104+
state->insertAttributeIfNotExists.create(
105+
state->db, "insert or ignore into Attributes(parent, name, type, value) values (?, ?, ?, ?)");
106+
107+
state->deleteMissingChildren.create(state->db, "delete from Attributes where parent = ? and type = 3");
108+
95109
state->txn = std::make_unique<SQLiteTxn>(state->db);
96110
}
97111

@@ -126,18 +140,20 @@ struct AttrDb
126140
return doSQLite([&]() {
127141
auto state(_state->lock());
128142

129-
state->insertAttribute.use()
130-
.apply(key.first)
131-
.apply(symbols[key.second])
132-
.apply(AttrType::FullAttrs)
133-
.apply(0, false)
134-
.exec();
135-
136-
AttrId rowId = state->db.getLastInsertedRowId();
143+
// Seal the attribute names: we now know the complete set.
144+
auto upsertAttribute(state->upsertAttribute.use()
145+
.apply(key.first)
146+
.apply(symbols[key.second])
147+
.apply(AttrType::FullAttrs)
148+
.apply(0, false));
149+
upsertAttribute.next();
150+
auto rowId = (AttrId) upsertAttribute.getInt(0);
137151
assert(rowId);
152+
state->deleteMissingChildren.use().apply(rowId).exec();
138153

154+
// Insert children as placeholders, but don't replace existing entries
139155
for (auto & attr : attrs)
140-
state->insertAttribute.use()
156+
state->insertAttributeIfNotExists.use()
141157
.apply(rowId)
142158
.apply(symbols[attr])
143159
.apply(AttrType::Placeholder)
@@ -461,9 +477,13 @@ Value & AttrCursor::forceValue()
461477
}
462478

463479
if (root->db && (!cachedValue || std::get_if<placeholder_t>(&cachedValue->second))) {
464-
if (v.type() == nString)
465-
cachedValue = {root->db->setString(getKey(), v.string_view(), v.context()), string_t{v.string_view(), {}}};
466-
else if (v.type() == nPath) {
480+
if (v.type() == nString) {
481+
NixStringContext context;
482+
copyContext(v, context);
483+
cachedValue = {
484+
root->db->setString(getKey(), v.string_view(), v.context()),
485+
string_t{v.string_view(), std::move(context)}};
486+
} else if (v.type() == nPath) {
467487
auto path = v.path().path;
468488
cachedValue = {root->db->setString(getKey(), path.abs()), string_t{path.abs(), {}}};
469489
} else if (v.type() == nBool)

src/libexpr/meson.build

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ deps_other += boost
5252
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
5353
deps_public += nlohmann_json
5454

55+
# eval-cache uses `INSERT ... RETURNING` which requires >= 3.35
56+
sqlite = dependency('sqlite3', 'sqlite', version : '>= 3.35')
57+
deps_private += sqlite
58+
5559
bdw_gc_required = get_option('gc').disable_if(
5660
'address' in get_option('b_sanitize'),
5761
error_message : 'Building with Boehm GC and ASAN is not supported',

src/libexpr/package.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
boost,
1414
boehmgc,
1515
nlohmann_json,
16+
sqlite,
1617
toml11,
1718
libcpuid,
1819

@@ -64,6 +65,7 @@ mkMesonLibrary (finalAttrs: {
6465
];
6566

6667
buildInputs = [
68+
sqlite
6769
toml11
6870
]
6971
++ lib.optional stdenv.hostPlatform.isx86_64 libcpuid;

tests/functional/flakes/eval-cache.sh

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,42 @@ source ./common.sh
44

55
requireGit
66

7+
# Test basic caching: trace should only appear on first evaluation
8+
basicCacheDir="$TEST_ROOT/basic-cache-flake"
9+
createGitRepo "$basicCacheDir" ""
10+
cp "${config_nix}" "$basicCacheDir/"
11+
git -C "$basicCacheDir" add config.nix
12+
git -C "$basicCacheDir" commit -m "config.nix"
13+
14+
cat >"$basicCacheDir/flake.nix" <<EOF
15+
{
16+
description = "Basic cache test";
17+
outputs = { self }: let inherit (import ./config.nix) mkDerivation; in {
18+
# Assert pure-eval is enabled (builtins.currentSystem is unavailable in pure mode)
19+
packages.$system.default = assert builtins ? currentSystem -> throw "pure-eval not enabled";
20+
builtins.trace "basic-test-evaluating" (mkDerivation {
21+
name = "cached-build";
22+
buildCommand = "echo hello > \\\$out";
23+
});
24+
};
25+
}
26+
EOF
27+
28+
git -C "$basicCacheDir" add flake.nix
29+
git -C "$basicCacheDir" commit -m "Init"
30+
31+
clearBinaryCache
32+
33+
# First build should show trace
34+
nix build --no-link "$basicCacheDir" 2>&1 | grepQuiet 'trace: basic-test-evaluating'
35+
36+
# Second build should use cache, no trace output
37+
nix build --no-link "$basicCacheDir" 2>&1 | grepQuietInverse 'trace: basic-test-evaluating'
38+
39+
# Third build should also use cache, no trace output
40+
nix build --no-link "$basicCacheDir" 2>&1 | grepQuietInverse 'trace: basic-test-evaluating'
41+
42+
# Test edge cases with separate flake
743
flake1Dir="$TEST_ROOT/eval-cache-flake"
844

945
createGitRepo "$flake1Dir" ""

0 commit comments

Comments
 (0)