Files
Roger b3bbc0dadf 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
2026-04-07 22:28:39 +08:00

52 lines
1.4 KiB
Go

package sqliteutil
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
// scan function that converts one database row into a typed value.
//
// Rows that fail to scan are skipped (logged at debug level by QuerySQLite).
func QueryRows[T any](dbPath string, journalOff bool, query string, scanRow func(*sql.Rows) (T, error)) ([]T, error) {
var items []T
err := QuerySQLite(dbPath, journalOff, query, func(rows *sql.Rows) error {
item, err := scanRow(rows)
if err != nil {
return err
}
items = append(items, item)
return nil
})
return items, err
}