Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

75 lines
2.9 KiB
Go

package store_sqlite
import (
"database/sql"
"fmt"
"os"
"github.com/zzet/gortex/internal/graph"
)
// ReadRepoIndexStates opens the SQLite store at path read-only and returns
// every repo_index_state freshness row keyed by repo_prefix.
//
// It is a deliberately lightweight side door for read-only callers (notably
// `gortex repos`) that must inspect index freshness WITHOUT going through
// Open — which runs schema migrations, alters columns, starts a checkpoint
// goroutine, and (on a version mismatch) can refuse to open or rebuild the
// file. None of that is appropriate for a status command that may run while
// a daemon holds the same store open.
//
// The connection is query-only and inherits the database's existing journal
// mode, so it reads safely alongside a running daemon (WAL permits concurrent
// readers). A missing store file, or a database that predates the
// repo_index_state table, both yield an empty map and a nil error — that is
// "nothing recorded yet", not a failure, so the caller can fall back to other
// freshness sources rather than surfacing an error to the user.
func ReadRepoIndexStates(path string) (map[string]graph.RepoIndexState, error) {
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return map[string]graph.RepoIndexState{}, nil
}
return nil, fmt.Errorf("stat sqlite store %q: %w", path, err)
}
// query_only blocks accidental writes; busy_timeout keeps a brief read
// from erroring out if the daemon happens to hold the write lock for a
// moment. We deliberately do NOT set journal_mode — forcing it could try
// to switch the live database's mode; inheriting the on-disk WAL mode is
// exactly what a concurrent reader wants.
dsn := path + "?_pragma=busy_timeout(2000)&_pragma=query_only(1)"
db, err := sql.Open("sqlite", dsn)
if err != nil {
return nil, fmt.Errorf("open sqlite store %q: %w", path, err)
}
defer db.Close()
db.SetMaxOpenConns(1)
rows, err := db.Query(`
SELECT repo_prefix, indexed_sha, dirty, indexed_at, workspace_fp, node_count, edge_count, extractor_versions
FROM repo_index_state`)
if err != nil {
// A store written before the repo_index_state table existed (or any
// other read error) is treated as "no freshness recorded yet" — a
// status command must never hard-fail on a degraded cache.
return map[string]graph.RepoIndexState{}, nil
}
defer rows.Close()
out := map[string]graph.RepoIndexState{}
for rows.Next() {
var st graph.RepoIndexState
var dirty int
if err := rows.Scan(&st.RepoPrefix, &st.IndexedSHA, &dirty, &st.IndexedAt,
&st.WorkspaceFP, &st.NodeCount, &st.EdgeCount, &st.ExtractorVersions); err != nil {
return nil, fmt.Errorf("scan repo_index_state: %w", err)
}
st.Dirty = dirty != 0
out[st.RepoPrefix] = st
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate repo_index_state: %w", err)
}
return out, nil
}