feat(search): add transactional rebuild and index optimization
This commit is contained in:
1 parent
39dfe8b285
commit
ee95b9a181
3 files changed
+169
-19
No files matched your search
@@ -0,0 +1,99 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"rawmark/internal/vault"
|
||||
)
|
||||
|
||||
func insert(tx *sql.Tx, p, rev string, content []byte) (int64, error) {
|
||||
metadata, err := json.Marshal(vault.Analyze(p, content))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
result, err := tx.Exec(`INSERT INTO docs(path,rev,metadata) VALUES (?,?,?)`, p, rev, string(metadata))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_, err = tx.Exec(`INSERT INTO docs_fts(rowid,path,body) VALUES (?,?,?)`, id, p, string(content))
|
||||
return id, err
|
||||
}
|
||||
|
||||
// Rebuild 与后台同步互斥,失败时事务和已知快照均保持原状。
|
||||
func (x *Index) Rebuild(ctx context.Context) error {
|
||||
x.work.Lock()
|
||||
defer x.work.Unlock()
|
||||
x.mu.Lock()
|
||||
version := x.pendingVersion
|
||||
x.mu.Unlock()
|
||||
entries, err := x.v.Tree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tx, err := x.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
if _, err = tx.ExecContext(ctx, `DELETE FROM docs_fts; DELETE FROM docs`); err != nil {
|
||||
return err
|
||||
}
|
||||
next := map[string]doc{}
|
||||
for _, entry := range entries {
|
||||
if entry.Dir || !vault.IsNote(entry.Path) {
|
||||
continue
|
||||
}
|
||||
if err = ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
content, rev, err := x.v.Read(entry.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id, err := insert(tx, entry.Path, rev, content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
next[entry.Path] = doc{id: id, rev: rev}
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
x.known = next
|
||||
x.mu.Lock()
|
||||
if version == x.pendingVersion {
|
||||
x.pending = nil
|
||||
}
|
||||
x.ready = true
|
||||
x.lastError = nil
|
||||
x.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
func (x *Index) Optimize(ctx context.Context) error {
|
||||
x.work.Lock()
|
||||
defer x.work.Unlock()
|
||||
for _, stmt := range []string{`INSERT INTO docs_fts(docs_fts) VALUES ('optimize')`, `PRAGMA wal_checkpoint(TRUNCATE)`, `VACUUM`} {
|
||||
if _, err := x.db.ExecContext(ctx, stmt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Index) syncPending() error {
|
||||
x.work.Lock()
|
||||
defer x.work.Unlock()
|
||||
x.mu.Lock()
|
||||
files := x.pending
|
||||
x.pending = nil
|
||||
x.mu.Unlock()
|
||||
if files == nil {
|
||||
return nil
|
||||
}
|
||||
return x.syncLocked(files)
|
||||
}
|
||||
Reference in new issue
Block a user