Skip to content

Commit 95621cd

Browse files
fix(core): use upsert to prevent FK constraint violations in task DB (#34977)
## Current Behavior `INSERT OR REPLACE` is used in `task_details` and `cache_outputs` tables. The bundled SQLite (`libsqlite3-sys`) is compiled with `SQLITE_DEFAULT_FOREIGN_KEYS=1`, so FK constraints are enforced by default. `INSERT OR REPLACE` does a DELETE + INSERT on PK conflict, and the implicit DELETE on `task_details` fails when child rows exist in `task_history` or `cache_outputs`: ``` NX DB transaction error: SqliteFailure(Error { code: ConstraintViolation, extended_code: 787 }, Some("FOREIGN KEY constraint failed")) ``` This happens because `record_task_details` is called multiple times with the same hash across different code paths (`hashTask`, `hashTasks`, `hashBatchTasks`), and by the second call, child rows already reference that hash. ## Expected Behavior Use `INSERT ... ON CONFLICT DO UPDATE` (upsert) which updates the existing row in-place without deleting it. No DELETE means no FK violation, while preserving the same idempotent behavior the code relies on. --------- Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
1 parent cbd3951 commit 95621cd

2 files changed

Lines changed: 6 additions & 2 deletions

File tree

packages/nx/src/native/cache/cache.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,8 @@ impl NxCache {
237237
fn record_to_cache(&self, hash: String, code: i16, size: i64) -> anyhow::Result<()> {
238238
trace!("Recording to cache: {}, {}, {}", &hash, code, size);
239239
self.db.lock().unwrap().execute(
240-
"INSERT OR REPLACE INTO cache_outputs (hash, code, size) VALUES (?1, ?2, ?3)",
240+
"INSERT INTO cache_outputs (hash, code, size) VALUES (?1, ?2, ?3)
241+
ON CONFLICT(hash) DO UPDATE SET code = excluded.code, size = excluded.size, created_at = CURRENT_TIMESTAMP, accessed_at = CURRENT_TIMESTAMP",
241242
params![hash, code, size],
242243
)?;
243244
if self.max_cache_size != 0 {

packages/nx/src/native/tasks/details.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ impl TaskDetails {
4040
pub fn record_task_details(&mut self, tasks: Vec<HashedTask>) -> anyhow::Result<()> {
4141
trace!("Recording task details");
4242
self.db.lock().unwrap().transaction(|conn| {
43-
let mut stmt = conn.prepare("INSERT OR REPLACE INTO task_details (hash, project, target, configuration) VALUES (?1, ?2, ?3, ?4)")?;
43+
let mut stmt = conn.prepare(
44+
"INSERT INTO task_details (hash, project, target, configuration) VALUES (?1, ?2, ?3, ?4)
45+
ON CONFLICT(hash) DO UPDATE SET project = excluded.project, target = excluded.target, configuration = excluded.configuration"
46+
)?;
4447
for task in tasks.iter() {
4548
stmt.execute(
4649
params![task.hash, task.project, task.target, task.configuration],

0 commit comments

Comments
 (0)