Skip to content

Commit b2f9fa5

Browse files
committed
fix(schema): rebuild pdp_prove_tasks after 0017 left it FK'd to a dropped table (#75)
0017_drop_legacy_v1_artifacts dropped pdp_proof_sets but missed that pdp_prove_tasks (created in 0010) still declared REFERENCES pdp_proof_sets(id). Two failures followed on any fresh install: 1. With foreign_keys=ON, DELETE FROM harmony_task (harmonytask's completion recorder) FK-scans pdp_prove_tasks and dies with 'no such table: main.pdp_proof_sets' -- so EVERY task completion failed to record and retried forever. Observed live on a clean calibration boot (task_type_handler.go:482 error spam). 2. The table was still in v1 shape (proofset column), while tasks/pdpv0/task_prove.go inserts (data_set, task_id) -- so ProveTask scheduling would have failed with 'no such column'. Migration 0022 rebuilds pdp_prove_tasks in the v0 vocabulary, FK'd to pdp_data_sets, copying any in-flight rows. Also: - migrations_test: pdp_proof_sets/pdp_proofset_* moved from wantTables to an explicit dropped-tables assertion (the test had been failing on main since 0017 landed) - new TestApplyMigrations_TaskCompletionDeleteWorks: PRAGMA foreign_key_check + the real harmony_task DELETE path + v0 column shape, so the dangling-FK class can't regress silently Lesson encoded in the migration header: when dropping a referenced table, grep schema files for 'REFERENCES <table>' first.
1 parent b675b5f commit b2f9fa5

2 files changed

Lines changed: 116 additions & 2 deletions

File tree

internal/harmonysqlite/migrations_test.go

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,10 @@ func TestApplyMigrations_PDPTablesPresent(t *testing.T) {
168168
"sector_location", "storage_path",
169169
// Eth chain queue
170170
"eth_keys", "message_sends_eth", "message_waits_eth", "message_send_eth_locks",
171-
// PDP v1
171+
// PDP v1 (legacy pdp_proof_sets / pdp_proofset_* vocabulary was
172+
// renamed to pdp_data_sets / pdp_data_set_* and the old tables are
173+
// DROPPED by 0017_drop_legacy_v1_artifacts.sql — asserted absent below)
172174
"pdp_services", "pdp_piecerefs", "pdp_piece_uploads", "pdp_piece_mh_to_commp",
173-
"pdp_proof_sets", "pdp_proofset_creates", "pdp_proofset_roots", "pdp_proofset_root_adds",
174175
"pdp_prove_tasks",
175176
// PDP v0 (renamed vocabulary)
176177
"pdp_data_sets", "pdp_data_set_creates", "pdp_data_set_pieces", "pdp_data_set_piece_adds",
@@ -191,6 +192,21 @@ func TestApplyMigrations_PDPTablesPresent(t *testing.T) {
191192
}
192193
})
193194
}
195+
196+
// Legacy v1 vocabulary must be GONE after 0017_drop_legacy_v1_artifacts.
197+
droppedTables := []string{
198+
"pdp_proof_sets", "pdp_proofset_creates", "pdp_proofset_roots", "pdp_proofset_root_adds",
199+
}
200+
for _, tbl := range droppedTables {
201+
t.Run("dropped/"+tbl, func(t *testing.T) {
202+
var got string
203+
err := db.QueryRow(ctx,
204+
`SELECT name FROM sqlite_master WHERE type='table' AND name=?`, tbl).Scan(&got)
205+
if err == nil {
206+
t.Errorf("legacy table %s still present; 0017 should have dropped it", tbl)
207+
}
208+
})
209+
}
194210
}
195211

196212
// TestApplyMigrations_PDPRefcountTriggerWorks: the inline-body trigger
@@ -290,3 +306,53 @@ func TestApplyMigrations_FKEnforced(t *testing.T) {
290306
t.Logf("FK violation (formatted): %v", err)
291307
}
292308
}
309+
310+
// TestApplyMigrations_TaskCompletionDeleteWorks reproduces the live
311+
// failure behind migration 0022: harmonytask's completion recorder does
312+
// DELETE FROM harmony_task WHERE id=$1, and with foreign_keys=ON SQLite
313+
// scans every table whose FK references harmony_task. When 0017 dropped
314+
// pdp_proof_sets but left pdp_prove_tasks declaring
315+
// REFERENCES pdp_proof_sets(id), that scan failed with
316+
// "no such table: main.pdp_proof_sets" and EVERY task completion
317+
// retried forever. This test fails if any FK in the schema dangles.
318+
func TestApplyMigrations_TaskCompletionDeleteWorks(t *testing.T) {
319+
db, err := Open(Config{Path: ":memory:", ForeignKeys: true})
320+
if err != nil {
321+
t.Fatalf("Open: %v", err)
322+
}
323+
defer db.Close()
324+
ctx := context.Background()
325+
if err := db.ApplyMigrations(ctx); err != nil {
326+
t.Fatalf("ApplyMigrations: %v", err)
327+
}
328+
329+
// foreign_key_check catches dangling FK targets schema-wide.
330+
rows, err := db.Query(ctx, `PRAGMA foreign_key_check`)
331+
if err != nil {
332+
t.Fatalf("PRAGMA foreign_key_check errored (dangling FK table?): %v", err)
333+
}
334+
rows.Close()
335+
336+
// And the actual harmonytask completion path: insert a task with no
337+
// owner, then DELETE it. The DELETE triggers FK-cascade scans across
338+
// all referencing tables (pdp_prove_tasks among them).
339+
var taskID int64
340+
err = db.QueryRow(ctx,
341+
`INSERT INTO harmony_task (posted_time, added_by, name)
342+
VALUES (CURRENT_TIMESTAMP, 1, 'completion-test') RETURNING id`).Scan(&taskID)
343+
if err != nil {
344+
t.Fatalf("insert harmony_task: %v", err)
345+
}
346+
if _, err := db.ExecCount(ctx, `DELETE FROM harmony_task WHERE id=?`, taskID); err != nil {
347+
t.Fatalf("DELETE FROM harmony_task failed (this is the 0022 bug class): %v", err)
348+
}
349+
350+
// pdp_prove_tasks must be in the v0 shape: (data_set, task_id),
351+
// FK'd to pdp_data_sets, so task_prove.go's INSERT works.
352+
var colName string
353+
err = db.QueryRow(ctx,
354+
`SELECT name FROM pragma_table_info('pdp_prove_tasks') WHERE name='data_set'`).Scan(&colName)
355+
if err != nil {
356+
t.Errorf("pdp_prove_tasks.data_set column missing (still v1 'proofset' shape?): %v", err)
357+
}
358+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
-- 0022_fix_prove_tasks_dangling_fk.sql
2+
--
3+
-- 0017_drop_legacy_v1_artifacts dropped pdp_proof_sets but left
4+
-- pdp_prove_tasks behind, still declaring:
5+
--
6+
-- proofset INTEGER NOT NULL REFERENCES pdp_proof_sets(id) ON DELETE CASCADE
7+
--
8+
-- Two bugs follow:
9+
--
10+
-- 1. DANGLING FK. With foreign_keys=ON, SQLite resolves FK targets at
11+
-- statement prepare. Any DML that touches pdp_prove_tasks -- including
12+
-- the FK-cascade scan triggered by DELETE FROM harmony_task in
13+
-- harmonytask's completion recorder -- fails with
14+
-- "no such table: main.pdp_proof_sets". On a fresh install this means
15+
-- EVERY task completion fails to record and retries forever
16+
-- (observed live: harmonytask task_type_handler.go:482 error spam on
17+
-- a clean calibration boot).
18+
--
19+
-- 2. STALE COLUMN NAME. Upstream's 20250730-pdp-v0-rename.sql renamed
20+
-- proofset -> data_set; tasks/pdpv0/task_prove.go inserts
21+
-- (data_set, task_id). Against the v1-shape table that INSERT fails
22+
-- with "no such column", so ProveTask scheduling breaks too.
23+
--
24+
-- Fix: rebuild pdp_prove_tasks in the v0 vocabulary, FK'd to
25+
-- pdp_data_sets. The table is transient scheduling state (rows live only
26+
-- while a prove task is in flight), so the copy is best-effort: on any
27+
-- broken deployment the table is empty (nothing could insert), and on a
28+
-- pre-0017 deployment rows are in the old column shape.
29+
--
30+
-- NOTE for future drops: when dropping a referenced table, always grep
31+
-- schema files for "REFERENCES <table>" first. 0017 missed this one.
32+
33+
CREATE TABLE IF NOT EXISTS pdp_prove_tasks_v0 (
34+
data_set INTEGER NOT NULL REFERENCES pdp_data_sets(id) ON DELETE CASCADE,
35+
task_id INTEGER NOT NULL REFERENCES harmony_task(id) ON DELETE CASCADE,
36+
PRIMARY KEY (data_set, task_id)
37+
);
38+
39+
-- Copy any in-flight rows (old column name "proofset" -> "data_set").
40+
-- On post-0017 fresh installs the source table exists but is empty;
41+
-- the SELECT lists the legacy column, which still exists in the legacy
42+
-- table shape this migration replaces.
43+
INSERT OR IGNORE INTO pdp_prove_tasks_v0 (data_set, task_id)
44+
SELECT proofset, task_id FROM pdp_prove_tasks;
45+
46+
DROP TABLE pdp_prove_tasks;
47+
48+
ALTER TABLE pdp_prove_tasks_v0 RENAME TO pdp_prove_tasks;

0 commit comments

Comments
 (0)