Upserta un slice de VaultFile en vault_index.db (tabla files + FTS5 files_fts) dentro de una sola transaccion. Cuenta Inserted/Updated/FTS. Con prune=true elimina filas no presentes en el slice.
vault
sqlite
index
write
upsert
fts
infra
vault_file_go_infra
false
error_go_core
database/sql
fmt
strings
time
name
desc
db
*sql.DB abierto sobre vault_index.db (tipicamente retornado por VaultIndexOpen)
name
desc
files
slice de VaultFile a insertar/actualizar; puede ser vacio
name
desc
prune
si true, elimina de 'files' todas las filas cuyo rel_path no este en el slice (sincronizacion destructiva)
WriteReport con conteos Inserted/Updated/Pruned/FTS; error si falla la transaccion
Se carga el conjunto de rel_paths existentes en un map antes del loop. Un upsert
se clasifica como Inserted si el rel_path no estaba en el map, Updated si estaba.
Esto evita N+1 SELECTs y es correcto porque la transaccion serializa los cambios.
FTS5
files_fts usa content='' (tabla de contenido externo vacio). Para cada archivo
se borra la fila FTS existente y se reinserta con content_text=''. Los profilers
posteriores (csv_profiles, knowledge_docs) son responsables de actualizar
content_text con texto indexable real.
Prune
Con prune=true se construye un IN clause con los rel_paths del slice. La FK con
ON DELETE CASCADE propaga el DELETE a csv_profiles, pdf_extracts y knowledge_docs
automaticamente. Con slice vacio + prune=true se borra todo (DELETE FROM files).
Escapado SQL
El IN clause se construye escapando las comillas simples en rel_path (duplicandolas).
Evita inyeccion en rutas con apostrofos. Para entornos con rutas controladas
(interior de vaults sin apostrofos) esto es suficiente; para entornos adversariales
usar parametros binding con VALUES multiples via prepared statement.