Documentation
¶
Overview ¶
Package docstore is a small document store on SQLite: collections of JSON documents addressed by string ids — "CRUD by file names", with the directory/file mental model (path-like ids such as "uid/upload" are fine) but real multi-process safety.
Design rules:
- Only stdlib + the pure-Go SQLite driver (modernc.org/sqlite, no cgo).
- No app-level cache: SQLite's page cache serves hot reads in microseconds and is never stale across processes.
- The concurrency primitive is Update (transactional read-modify-write, BEGIN IMMEDIATE) — it replaces file locks and per-entity mutexes and is correct across processes.
- Indexed lookups use SQLite generated columns over JSON paths, so documents stay schemaless while lookups stay O(log n).
- Every document carries created_at/updated_at metadata; soft deletion (deleted_at) is opt-in per collection via WithSoftDelete.
Index ¶
- Variables
- type Collection
- func (c *Collection) Count() (n int, err error)
- func (c *Collection) CountDeleted() (n int, err error)
- func (c *Collection) Delete(id string) (err error)
- func (c *Collection) Each(fn func(id string, raw []byte) error) (err error)
- func (c *Collection) EachDeleted(fn func(id string, raw []byte) error) (err error)
- func (c *Collection) ExistsBy(col string, val any, exceptID string) (found bool, err error)
- func (c *Collection) ExportDir(dir string) (n int, err error)
- func (c *Collection) Find(jsonPath, op string, value any, limit int) (out []Doc, err error)
- func (c *Collection) FindAll(conds []Condition, limit int) (out []Doc, err error)
- func (c *Collection) FindAllIndexed(conds []Condition, limit int) (out []Doc, err error)
- func (c *Collection) FindAny(conds []Condition, limit int) (out []Doc, err error)
- func (c *Collection) FindIndexed(jsonPath, op string, value any, limit int) (out []Doc, err error)
- func (c *Collection) FindPath(path, op string, value any, limit int) (out []Doc, err error)
- func (c *Collection) Get(id string, out any) (err error)
- func (c *Collection) GetBy(col string, val any, out any) (err error)
- func (c *Collection) GetRaw(id string) (raw []byte, err error)
- func (c *Collection) IDs() (out []string, err error)
- func (c *Collection) ImportDir(dir string) (n int, err error)
- func (c *Collection) ImportFile(path string) (err error)
- func (c *Collection) Indexes() ([]IndexInfo, error)
- func (c *Collection) Insert(id string, doc any) (err error)
- func (c *Collection) Meta(id string) (m Meta, err error)
- func (c *Collection) Purge(id string) (err error)
- func (c *Collection) Put(id string, doc any) (err error)
- func (c *Collection) Restore(id string) (err error)
- func (c *Collection) Update(id string, fn func(raw []byte) ([]byte, error)) (err error)
- type Condition
- type Doc
- type IndexInfo
- type Meta
- type Option
- type Store
Constants ¶
This section is empty.
Variables ¶
var ( ErrNotFound = errors.New("docstore: not found") ErrExists = errors.New("docstore: already exists") )
Functions ¶
This section is empty.
Types ¶
type Collection ¶
type Collection struct {
// contains filtered or unexported fields
}
Collection is a named set of documents — think "directory".
func (*Collection) Count ¶
func (c *Collection) Count() (n int, err error)
Count returns the number of (live) documents.
func (*Collection) CountDeleted ¶ added in v0.3.0
func (c *Collection) CountDeleted() (n int, err error)
CountDeleted returns the number of soft-deleted documents (deleted_at set). Works regardless of WithSoftDelete — it counts the column.
func (*Collection) Delete ¶
func (c *Collection) Delete(id string) (err error)
Delete removes the document — hard by default, or marks deleted_at on WithSoftDelete collections. ErrNotFound when there is nothing (live) to delete.
func (*Collection) Each ¶
func (c *Collection) Each(fn func(id string, raw []byte) error) (err error)
Each streams every (live) (id, raw document) pair, sorted by id. Returning an error from fn stops the iteration and propagates it.
func (*Collection) EachDeleted ¶
func (c *Collection) EachDeleted(fn func(id string, raw []byte) error) (err error)
EachDeleted streams soft-deleted documents only.
func (*Collection) ExistsBy ¶
ExistsBy reports whether any document other than exceptID has this value in the indexed column (pass "" to check all documents).
func (*Collection) ExportDir ¶
func (c *Collection) ExportDir(dir string) (n int, err error)
ExportDir writes every (live) document to dir as <id>.json (pretty printed); path-like ids become subdirectories. Returns the number of exported documents. Ids that would escape dir are rejected.
func (*Collection) Find ¶
Find returns documents whose json_extract(doc, jsonPath) matches value under op (one of = != < > like). value is used as-is: pass a number for numeric JSON fields, a string for text. limit <= 0 means no limit.
func (*Collection) FindAll ¶ added in v0.6.0
func (c *Collection) FindAll(conds []Condition, limit int) (out []Doc, err error)
FindAll returns documents matching every condition (AND), via json_extract per row like Find — a full scan even if some paths are indexed. Use FindAllIndexed when every condition's path is indexed and an index seek is wanted instead.
func (*Collection) FindAllIndexed ¶ added in v0.6.0
func (c *Collection) FindAllIndexed(conds []Condition, limit int) (out []Doc, err error)
FindAllIndexed is FindAll restricted to conditions whose paths all have a WithIndex/WithUniqueIndex on them — same real-index-seek technique as FindIndexed, applied per condition, so SQLite can seek each generated column instead of scanning with json_extract. Errors up front if any condition's path isn't indexed.
func (*Collection) FindAny ¶ added in v0.6.0
func (c *Collection) FindAny(conds []Condition, limit int) (out []Doc, err error)
FindAny returns documents matching at least one condition (OR), via json_extract per row like Find.
func (*Collection) FindIndexed ¶ added in v0.4.0
FindIndexed is Find restricted to a jsonPath that has a WithIndex/ WithUniqueIndex on it. Find's json_extract(doc, ?) is a per-row function call over a bound parameter — SQLite never matches that back to a generated column's index, even when jsonPath is the exact one an index was built on, so every Find is a full table scan regardless of what indexes the collection has. FindIndexed instead queries the underlying generated column by name (same technique GetBy/ExistsBy already use), which is a real index seek, and returns an error up front if jsonPath isn't indexed, so an accidentally-unindexed lookup fails immediately instead of quietly degrading into a scan. Use Find (or FindPath) when a full scan is genuinely intended.
func (*Collection) FindPath ¶ added in v0.2.0
FindPath is a friendlier variant of Find for simple, index-free dotted paths (e.g. "login", "address.city", "loginHistory.at" — no leading "$." and no array indices; a leading "$." is tolerated and stripped for muscle-memory compatibility).
The key difference from Find: if the FIRST path segment turns out to be an array, every element is searched — not just index 0. So "loginHistory.at" matches a document if ANY entry in its loginHistory array has that field equal to value, and a bare "tags" matches if value is a member of the tags array (array of scalars). A path whose first segment is not an array falls back to a plain field match, so "login" behaves exactly as expected.
Only the first segment is treated as a potential array boundary — deeper/multiple array hops need Find with a literal SQLite json_extract path (e.g. "$.a[2].b[0].c") instead.
func (*Collection) Get ¶
func (c *Collection) Get(id string, out any) (err error)
Get unmarshals the document with this id into out.
func (*Collection) GetBy ¶
func (c *Collection) GetBy(col string, val any, out any) (err error)
GetBy looks a document up through an indexed column (see WithIndex / WithUniqueIndex). With multiple matches the smallest id wins.
func (*Collection) GetRaw ¶
func (c *Collection) GetRaw(id string) (raw []byte, err error)
GetRaw returns the raw JSON of the document with this id.
func (*Collection) IDs ¶
func (c *Collection) IDs() (out []string, err error)
IDs lists every (live) document id, sorted.
func (*Collection) ImportDir ¶
func (c *Collection) ImportDir(dir string) (n int, err error)
ImportDir walks dir recursively and stores every *.json file; ids are the slash-separated relative paths without the .json extension (so "users/id1.json" becomes id "users/id1"). Returns the number of imported documents; the first invalid file aborts with an error.
func (*Collection) ImportFile ¶
func (c *Collection) ImportFile(path string) (err error)
ImportFile stores one JSON file as a document; the id is the file name without its .json extension. Invalid JSON is rejected.
func (*Collection) Indexes ¶ added in v0.5.0
func (c *Collection) Indexes() ([]IndexInfo, error)
Indexes lists the indexes present on the collection's table, read directly from the SQLite schema (sqlite_master) rather than from the Options the collection happens to have been opened with — so it reports the truth even when opened generically (e.g. by a browsing tool that doesn't know which WithIndex/WithUniqueIndex created them).
func (*Collection) Insert ¶
func (c *Collection) Insert(id string, doc any) (err error)
Insert stores a NEW document; any conflict (id or unique index) returns ErrExists — including a conflict with a soft-deleted document, which still occupies its id.
func (*Collection) Meta ¶
func (c *Collection) Meta(id string) (m Meta, err error)
Meta returns the document's metadata timestamps. Unlike Get, it also answers for soft-deleted documents (that's how you inspect them).
func (*Collection) Purge ¶
func (c *Collection) Purge(id string) (err error)
Purge removes a document for real, regardless of soft-delete state.
func (*Collection) Put ¶
func (c *Collection) Put(id string, doc any) (err error)
Put creates or replaces the document with this id, keeping its original created_at. Putting over a soft-deleted document revives it (deleted_at cleared). A unique-index conflict with a DIFFERENT document still returns ErrExists.
func (*Collection) Restore ¶
func (c *Collection) Restore(id string) (err error)
Restore clears a soft-deleted document's deleted_at mark. Available on any collection (it acts on the column, not the option).
func (*Collection) Update ¶
Update runs a transactional read-modify-write on one document: fn receives the current raw JSON and returns the replacement. The whole sequence holds the database write lock (BEGIN IMMEDIATE via the txlock DSN), so concurrent Updates — same process or another one — serialize instead of losing writes. fn returning an error aborts.
type Condition ¶ added in v0.6.0
Condition is one clause of a compound query: json_extract(doc, Path) compared to Value under Op (same triple Find takes: = != < > like).
type IndexInfo ¶ added in v0.5.0
type IndexInfo struct {
Name string `json:"name"` // the index's SQL name (ix_<table>_<col>)
SQL string `json:"sql"` // the exact CREATE INDEX statement, as SQLite stored it
Column string `json:"column"` // the generated column the index is on
JSONPath string `json:"jsonPath"` // the json_extract path the column computes, e.g. "$.login"
Unique bool `json:"unique"`
NoCase bool `json:"nocase"`
}
IndexInfo describes one index found on a collection's table.
type Option ¶
type Option func(*Collection)
Option configures a Collection at open time.
func WithIndex ¶
WithIndex adds a non-unique index; nocase makes lookups case-insensitive (à la strings.EqualFold).
func WithSoftDelete ¶
func WithSoftDelete() Option
WithSoftDelete makes Delete mark documents (deleted_at) instead of removing them; reads filter marked documents out, Restore/Purge manage them. CAVEAT with unique indexes: a soft-deleted document still holds its unique values, so re-creating "the same" document conflicts until purged — prefer hard delete (the default) or no unique indexes on soft-delete collections.
func WithUniqueIndex ¶
WithUniqueIndex adds a UNIQUE index on json_extract(doc, jsonPath), exposed as a queryable column (GetBy/ExistsBy). Put/Insert of a conflicting document returns ErrExists.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is one SQLite database file holding any number of collections.
func Open ¶
Open opens (or creates) the database file. WAL journaling for concurrent readers + one writer, busy_timeout so competing writers queue instead of erroring, txlock=immediate so Update transactions take their write lock up front (no deferred-upgrade SQLITE_BUSY).
func (*Store) Collection ¶
func (s *Store) Collection(name string, opts ...Option) (*Collection, error)
Collection opens (creating if needed) a collection and applies its indexes. Idempotent — and it upgrades tables created by older versions of this package in place (adds missing metadata columns).
func (*Store) Collections ¶
Collections lists the collection names present in the database (tables matching the c_* naming scheme). Table names preserve the collection name (including '-'); older databases that stored '-' as '_' still list the underscore form.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
godocstore
command
cmd/godocstore/backup_cmd.go
|
cmd/godocstore/backup_cmd.go |