feat: add CountEntries to skip decryption for list --detail (#562)

* feat: add CountEntries to skip decryption for list --detail (#549)
* test: add CountEntries and countCategory tests at browser level
* fix: address review feedback on CountRows and countLocalStorage
* test: add CountRows unit tests
This commit is contained in:
Roger
2026-04-07 22:28:39 +08:00
committed by GitHub
parent 5f42d4fe5f
commit b3bbc0dadf
40 changed files with 1009 additions and 101 deletions
+31 -1
View File
@@ -1,6 +1,36 @@
package sqliteutil
import "database/sql"
import (
"database/sql"
"fmt"
"os"
)
// CountRows runs a scalar count query (e.g. SELECT COUNT(*) FROM ...) and
// returns the integer result. Unlike QuerySQLite (which swallows per-row scan
// errors), CountRows uses QueryRow for fail-fast behavior on scan failures.
func CountRows(dbPath string, journalOff bool, query string) (int, error) {
if _, err := os.Stat(dbPath); err != nil {
return 0, fmt.Errorf("database file: %w", err)
}
db, err := sql.Open("sqlite", dbPath)
if err != nil {
return 0, err
}
defer db.Close()
if journalOff {
if _, err := db.Exec("PRAGMA journal_mode=off"); err != nil {
return 0, err
}
}
var count int
if err := db.QueryRow(query).Scan(&count); err != nil {
return 0, fmt.Errorf("count rows: %w", err)
}
return count, nil
}
// QueryRows is a generic helper (Go 1.18+) that wraps QuerySQLite and collects
// results into a typed slice. Each extract method only needs to provide the
+73
View File
@@ -86,6 +86,79 @@ func TestQuerySQLite_BadQuery(t *testing.T) {
require.Error(t, err)
}
func TestCountRows(t *testing.T) {
tests := []struct {
name string
schema string
inserts string
journalOff bool
query string
wantCount int
wantErr bool
}{
{
name: "count rows",
schema: "CREATE TABLE items (id INTEGER, name TEXT)",
inserts: "INSERT INTO items VALUES (1, 'alpha'), (2, 'beta'), (3, 'gamma')",
query: "SELECT COUNT(*) FROM items",
wantCount: 3,
},
{
name: "empty table",
schema: "CREATE TABLE t (v TEXT)",
query: "SELECT COUNT(*) FROM t",
wantCount: 0,
},
{
name: "journal off",
schema: "CREATE TABLE t (v TEXT)",
inserts: "INSERT INTO t VALUES ('a'), ('b')",
journalOff: true,
query: "SELECT COUNT(*) FROM t",
wantCount: 2,
},
{
name: "file not found",
query: "SELECT COUNT(*) FROM t",
wantErr: true,
},
{
name: "bad query",
schema: "CREATE TABLE t (v TEXT)",
query: "SELECT COUNT(*) FROM nonexistent",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var dbPath string
if tt.schema != "" {
dbPath = filepath.Join(t.TempDir(), "test.db")
db, err := sql.Open("sqlite", dbPath)
require.NoError(t, err)
_, err = db.Exec(tt.schema)
require.NoError(t, err)
if tt.inserts != "" {
_, err = db.Exec(tt.inserts)
require.NoError(t, err)
}
require.NoError(t, db.Close())
} else {
dbPath = "/nonexistent/path.db"
}
count, err := CountRows(dbPath, tt.journalOff, tt.query)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.wantCount, count)
})
}
}
func TestQueryRows(t *testing.T) {
dir := t.TempDir()
dbPath := filepath.Join(dir, "test.db")