# 儲存寫入

查看解析後的 chunk 如何寫入 SQLite，以及刪除的檔案如何 soft-delete。

## Transactional upsert

`databaseHandler.Upsert` 是 filesystem layer 使用的唯一內容寫入入口。它會在同一 SQLite transaction 中：

1. 將此 source 目前所有 active row 標記為 `dismiss = TRUE`。
2. 插入每個目前 chunk，或更新 `(source, chunk)` conflict，包含 chunk 的 `total`。
3. 將目前 chunk 恢復為 `dismiss = FALSE`。
4. 只有在 chunk content 未變時保留 embedding 與 `is_embed`。
5. 清除已變更的 embedding，並重設 `is_embed = FALSE`。

此順序可處理縮短的檔案，不會回傳過期的尾端 chunk，也不會重新 embedding 未變更的 chunk。解析結果為零個 chunk 時（例如只有 header 列的工作表），該 source 的所有 row 都會維持 dismissed。

`file_data` table 以 `UNIQUE (source, chunk)` 約束，並在 `is_embed = FALSE AND dismiss = FALSE` 的 row 上建立 partial index，這正是 embedding scheduler 的選取條件。

## 已移除檔案

掃描目前 entry 後，walker 會比較先前 snapshot。缺少的 file node 會遞迴傳給 `databaseHandler.Dismiss`，透過設定 `dismiss = TRUE` 將 active row soft-delete。資料仍保留在 SQLite，但 indexing 與 query read 都會排除。

被移除的目錄會依 snapshot 記錄的 children 走訪，因此其中每個檔案都會個別被 dismiss。
