Documentation
¶
Overview ¶
Package db provides a multi-layer database abstraction supporting: - User-level SQLite with sqlite-vec for personal data and vector search - Organization-level SQLite for shared tenant data - PostgreSQL with pgvector for scalable deployments - MongoDB/FerretDB for document storage - Hanzo Datastore for deep analytics
ZAP protocol driver for the ORM.
ZAP (Zero-Copy App Proto) uses binary encoding over RPC, communicating directly with ZAP-native backends (hanzo/sql, hanzo/kv, hanzo/datastore, hanzo/documentdb). Each backend speaks ZAP natively — no sidecar needed.
Transport is github.com/zap-proto/http: a fasthttp-style request/response exchange carried over ZAP length-prefixed frames (encoded by the pure-Go zap-proto/go runtime). This is the same ZAP-HTTP transport the gateway, ingress, and luxd use — one and only one internal transport. The driver speaks it as a client: each backend op is a POST to a path (/query, /get, /set, /find, …) with a JSON body; the response carries a status and a JSON body. Routing is by address (each backend on its own port; see DefaultPorts) and by path — there is no peer-discovery layer, so the ORM takes no mDNS dependency.
Index ¶
- Variables
- func GenerateID() string
- func LowercaseFirst(s string) string
- func NormalizeOp(op string) string
- func ParseFilterString(s string) (field, op string)
- func ToJSONFieldName(field string) string
- type AfterCreateHook
- type AfterDeleteHook
- type AfterUpdateHook
- type AnalyticsBatch
- type AnalyticsRows
- type AnalyticsStore
- type BeforeCreateHook
- type BeforeDeleteHook
- type BeforeUpdateHook
- type Config
- type Cursor
- type DB
- type DatastoreConfig
- type Entity
- type IsolationLevel
- type Iterator
- type Key
- type Kind
- type Layer
- type Manager
- func (m *Manager) Analytics() AnalyticsStore
- func (m *Manager) Close() error
- func (m *Manager) Org(orgID string) (DB, error)
- func (m *Manager) RegisterOrgDB(orgID string, db DB)
- func (m *Manager) RegisterUserDB(userID string, db DB)
- func (m *Manager) SetAnalyticsStore(store AnalyticsStore)
- func (m *Manager) User(userID string) (DB, error)
- type Model
- func (m *Model) Create(ctx context.Context) error
- func (m *Model) DB() DB
- func (m *Model) Delete(ctx context.Context) error
- func (m *Model) Entity() Kind
- func (m *Model) Exists(ctx context.Context) (bool, error)
- func (m *Model) Get(ctx context.Context) error
- func (m *Model) GetByID(ctx context.Context, id string) error
- func (m *Model) GetID() string
- func (m *Model) GetKind() string
- func (m *Model) GetNamespace() string
- func (m *Model) Init(database DB, entity Kind)
- func (m *Model) IsCreated() bool
- func (m *Model) IsLoaded() bool
- func (m *Model) JSON() ([]byte, error)
- func (m *Model) JSONString() string
- func (m *Model) Key() Key
- func (m *Model) MarkLoaded()
- func (m *Model) ModelQuery() Query
- func (m *Model) MustGet(ctx context.Context)
- func (m *Model) MustGetByID(ctx context.Context, id string)
- func (m *Model) MustPut(ctx context.Context)
- func (m *Model) Put(ctx context.Context) error
- func (m *Model) RunInTransaction(ctx context.Context, fn func(tx Transaction) error) error
- func (m *Model) SetDB(database DB)
- func (m *Model) SetEntity(entity Kind)
- func (m *Model) SetID(id string)
- func (m *Model) SetKey(key Key) error
- func (m *Model) SetKeyFromString(id string) error
- func (m *Model) SetNamespace(ns string)
- func (m *Model) SoftDelete(ctx context.Context) error
- func (m *Model) Update(ctx context.Context) error
- type Query
- type QueryFilter
- type QueryOrder
- type Registry
- type RegistryConfig
- type SQLiteConfig
- type SQLiteDB
- func (db *SQLiteDB) AllocateIDs(kind string, parent Key, n int) ([]Key, error)
- func (db *SQLiteDB) Close() error
- func (db *SQLiteDB) CreateIfAbsent(ctx context.Context, key Key, src interface{}) (bool, error)
- func (db *SQLiteDB) Delete(ctx context.Context, key Key) error
- func (db *SQLiteDB) DeleteMulti(ctx context.Context, keys []Key) error
- func (db *SQLiteDB) Get(ctx context.Context, key Key, dst interface{}) error
- func (db *SQLiteDB) GetMulti(ctx context.Context, keys []Key, dst interface{}) error
- func (db *SQLiteDB) NewIncompleteKey(kind string, parent Key) Key
- func (db *SQLiteDB) NewKey(kind string, stringID string, intID int64, parent Key) Key
- func (db *SQLiteDB) Put(ctx context.Context, key Key, src interface{}) (Key, error)
- func (db *SQLiteDB) PutMulti(ctx context.Context, keys []Key, src interface{}) ([]Key, error)
- func (db *SQLiteDB) PutVector(ctx context.Context, kind string, id string, vector []float32, ...) error
- func (db *SQLiteDB) Query(kind string) Query
- func (db *SQLiteDB) RunInTransaction(ctx context.Context, fn func(tx Transaction) error, opts *TransactionOptions) error
- func (db *SQLiteDB) TenantID() string
- func (db *SQLiteDB) TenantType() string
- func (db *SQLiteDB) VectorSearch(ctx context.Context, opts *VectorSearchOptions) ([]VectorResult, error)
- type SQLiteDBConfig
- type SimpleCursor
- type Syncable
- type Tenant
- type Transaction
- type TransactionOptions
- type Validator
- type VectorResult
- type VectorSearchOptions
- type ZapBackend
- type ZapConfig
- type ZapDB
- func (z *ZapDB) AllocateIDs(kind string, parent Key, n int) ([]Key, error)
- func (z *ZapDB) Close() error
- func (z *ZapDB) CreateIfAbsent(ctx context.Context, key Key, src interface{}) (bool, error)
- func (z *ZapDB) Delete(ctx context.Context, key Key) error
- func (z *ZapDB) DeleteMulti(ctx context.Context, keys []Key) error
- func (z *ZapDB) Get(ctx context.Context, key Key, dst interface{}) error
- func (z *ZapDB) GetMulti(ctx context.Context, keys []Key, dst interface{}) error
- func (z *ZapDB) NewIncompleteKey(kind string, parent Key) Key
- func (z *ZapDB) NewKey(kind string, stringID string, intID int64, parent Key) Key
- func (z *ZapDB) Put(ctx context.Context, key Key, src interface{}) (Key, error)
- func (z *ZapDB) PutMulti(ctx context.Context, keys []Key, src interface{}) ([]Key, error)
- func (z *ZapDB) PutVector(ctx context.Context, kind string, id string, vector []float32, ...) error
- func (z *ZapDB) Query(kind string) Query
- func (z *ZapDB) RunInTransaction(ctx context.Context, fn func(tx Transaction) error, opts *TransactionOptions) error
- func (z *ZapDB) TenantID() string
- func (z *ZapDB) TenantType() string
- func (z *ZapDB) VectorSearch(ctx context.Context, opts *VectorSearchOptions) ([]VectorResult, error)
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNoSuchEntity is returned when an entity is not found. ErrNoSuchEntity = errors.New("db: no such entity") // ErrInvalidKey is returned when a key is invalid. ErrInvalidKey = errors.New("db: invalid key") // ErrInvalidEntityType is returned when an entity type is invalid. ErrInvalidEntityType = errors.New("db: invalid entity type") // ErrConcurrentModification is returned when optimistic locking fails. ErrConcurrentModification = errors.New("db: concurrent modification") // ErrDatabaseClosed is returned when operating on a closed database. ErrDatabaseClosed = errors.New("db: database closed") // ErrValidationFailed is returned when entity validation fails. ErrValidationFailed = errors.New("db: validation failed") // ErrEntityNotFound aliases ErrNoSuchEntity. ErrEntityNotFound = ErrNoSuchEntity // ErrKindMismatch is returned by CreateIfAbsent when the id is already held // by a row of a DIFFERENT kind. Entity identity is (kind, id); on the SQL // backends the id column is a bare primary key, so two kinds cannot share an // id. That squatting row is invisible to Get (which filters by kind), so // reporting created=false would strand the caller — CreateIfAbsent surfaces // the collision loudly instead. Keep each kind in its own stringID keyspace. ErrKindMismatch = errors.New("db: id held by a different kind") )
var DefaultPorts = map[ZapBackend]int{ ZapSQL: 9651, ZapKV: 9653, ZapDocumentDB: 9654, ZapDatastore: 9655, }
DefaultPorts for each ZAP-native backend.
var ErrRegistryClosed = errors.New("db: registry closed")
ErrRegistryClosed is returned once the registry is closed.
Functions ¶
func LowercaseFirst ¶
LowercaseFirst lowercases the first character of a string.
func ParseFilterString ¶
ParseFilterString parses "Field=" into field and operator.
func ToJSONFieldName ¶
ToJSONFieldName converts a Go struct field name (PascalCase) to its JSON equivalent (camelCase) by lowercasing the first letter of each path segment. Handles nested paths like "Account.TransactionHash" → "account.transactionHash".
Types ¶
type AfterCreateHook ¶
type AfterCreateHook interface {
AfterCreate() error
}
AfterCreateHook is called after entity creation.
type AfterDeleteHook ¶
type AfterDeleteHook interface {
AfterDelete() error
}
AfterDeleteHook is called after entity deletion.
type AfterUpdateHook ¶
type AfterUpdateHook interface {
AfterUpdate(prev interface{}) error
}
AfterUpdateHook is called after entity update.
type AnalyticsBatch ¶
type AnalyticsBatch interface {
Append(v ...interface{}) error
AppendStruct(v interface{}) error
Flush() error
Send() error
Abort() error
Rows() int
Close() error
}
AnalyticsBatch for bulk inserts into analytics store.
type AnalyticsRows ¶
type AnalyticsRows interface {
Next() bool
Scan(dest ...interface{}) error
ScanStruct(dest interface{}) error
Columns() []string
Close() error
Err() error
}
AnalyticsRows represents analytics query results.
type AnalyticsStore ¶
type AnalyticsStore interface {
Query(ctx context.Context, query string, args ...interface{}) (AnalyticsRows, error)
Select(ctx context.Context, dest interface{}, query string, args ...interface{}) error
Exec(ctx context.Context, query string, args ...interface{}) error
PrepareBatch(ctx context.Context, query string) (AnalyticsBatch, error)
AsyncInsert(ctx context.Context, query string, wait bool, args ...interface{}) error
Close() error
}
AnalyticsStore is the interface for analytics queries (e.g. Hanzo Datastore).
type BeforeCreateHook ¶
type BeforeCreateHook interface {
BeforeCreate() error
}
BeforeCreateHook is called before entity creation.
type BeforeDeleteHook ¶
type BeforeDeleteHook interface {
BeforeDelete() error
}
BeforeDeleteHook is called before entity deletion.
type BeforeUpdateHook ¶
type BeforeUpdateHook interface {
BeforeUpdate(prev interface{}) error
}
BeforeUpdateHook is called before entity update.
type Config ¶
type Config struct {
DataDir string
UserDataDir string
OrgDataDir string
DatastoreDSN string
EnableDatastore bool
EnableVectorSearch bool
VectorDimensions int
SQLite SQLiteConfig
Datastore DatastoreConfig
IsDev bool
}
Config holds database configuration options.
type Cursor ¶
type Cursor interface {
String() string
}
Cursor represents a position in a result set.
func DecodeCursor ¶
DecodeCursor parses a cursor string.
type DB ¶
type DB interface {
// Core operations
Get(ctx context.Context, key Key, dst interface{}) error
Put(ctx context.Context, key Key, src interface{}) (Key, error)
// CreateIfAbsent conditionally inserts src under key with first-writer-wins
// semantics. It returns created=true iff this call inserted the row (key was
// absent); created=false iff a live row already existed under key, which is
// left untouched. Unlike Put — an unconditional upsert — CreateIfAbsent never
// overwrites a live row, so the winner's content is immutable: a caller that
// sees created=false can Get the existing row with no lost update and no
// TOCTOU window.
//
// "Absent" means no live row of the SAME kind. A soft-deleted row (see
// Delete) of the same kind is resurrected as the new content and reported
// created=true, so CreateIfAbsent and Get share one definition of existence.
// Resurrection never changes an existing row's kind.
//
// Existence is scoped to (kind, id). Because the id column is a bare primary
// key on the SQL backends, an id already held by a DIFFERENT kind is a
// keyspace collision: CreateIfAbsent returns ErrKindMismatch rather than a
// silent created=false that Get could not see. Callers must therefore keep
// each kind in its own stringID keyspace. CreateIfAbsent is also exact-match
// on the stringID: "Acme", "acme" and "acme " are distinct ids, so callers
// must normalize (case, trim, Unicode) BEFORE constructing the key.
//
// The write is atomic at the storage layer — SQLite serializes writers and
// the SQL backend applies INSERT ... ON CONFLICT at the row — so for N
// concurrent callers on the same absent key exactly one observes created=true.
// key must be complete with a non-empty id; otherwise ErrInvalidKey.
CreateIfAbsent(ctx context.Context, key Key, src interface{}) (created bool, err error)
Delete(ctx context.Context, key Key) error
// Batch operations
GetMulti(ctx context.Context, keys []Key, dst interface{}) error
PutMulti(ctx context.Context, keys []Key, src interface{}) ([]Key, error)
DeleteMulti(ctx context.Context, keys []Key) error
// Query
Query(kind string) Query
// Vector search
VectorSearch(ctx context.Context, opts *VectorSearchOptions) ([]VectorResult, error)
PutVector(ctx context.Context, kind string, id string, vector []float32, metadata map[string]interface{}) error
// Key management
NewKey(kind string, stringID string, intID int64, parent Key) Key
NewIncompleteKey(kind string, parent Key) Key
AllocateIDs(kind string, parent Key, n int) ([]Key, error)
// Transactions
RunInTransaction(ctx context.Context, fn func(tx Transaction) error, opts *TransactionOptions) error
// Lifecycle
Close() error
// Tenant info
TenantID() string
TenantType() string
}
DB is the main database interface for entity storage.
type DatastoreConfig ¶
type DatastoreConfig struct {
MaxOpenConns int
MaxIdleConns int
ConnMaxLifetime time.Duration
Compression string
QueryTimeout time.Duration
}
DatastoreConfig holds Hanzo Datastore configuration.
type Entity ¶
type Entity interface {
Kind() string
}
Entity is the interface that all model entities should implement.
type IsolationLevel ¶
type IsolationLevel int
IsolationLevel represents transaction isolation levels.
const ( IsolationDefault IsolationLevel = iota IsolationReadUncommitted IsolationReadCommitted IsolationRepeatableRead IsolationSerializable )
type Key ¶
type Key interface {
Kind() string
StringID() string
IntID() int64
Parent() Key
Namespace() string
Incomplete() bool
Encode() string
Equal(other Key) bool
}
Key represents a unique identifier for an entity.
type Kind ¶
type Kind interface {
Kind() string
}
Kind interface for entities with a kind/table name.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager is the main entry point for database operations. It manages multiple database layers and provides unified access.
func NewManager ¶
NewManager creates a new database manager.
func (*Manager) Analytics ¶
func (m *Manager) Analytics() AnalyticsStore
Analytics returns the analytics store.
func (*Manager) RegisterOrgDB ¶
RegisterOrgDB registers an organization database.
func (*Manager) RegisterUserDB ¶
RegisterUserDB registers a user database.
func (*Manager) SetAnalyticsStore ¶
func (m *Manager) SetAnalyticsStore(store AnalyticsStore)
SetAnalyticsStore sets the analytics store for the manager.
type Model ¶
type Model struct {
Parent Key `json:"-"`
ID string `json:"id,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
UpdatedAt time.Time `json:"updatedAt,omitempty"`
Deleted bool `json:"deleted,omitempty"`
Version int64 `json:"version,omitempty"`
Namespace_ string `json:"-"`
Mock bool `json:"-"`
UseStringKey bool `json:"-"`
// contains filtered or unexported fields
}
Model is a base type that provides common functionality for entities. Embed this in your entity structs for non-generic model usage.
func (*Model) GetNamespace ¶
GetNamespace returns the namespace for this entity.
func (*Model) JSONString ¶
JSONString returns the JSON string representation.
func (*Model) ModelQuery ¶
ModelQuery returns a new query for this entity's kind.
func (*Model) MustGetByID ¶
MustGetByID retrieves by ID or panics.
func (*Model) RunInTransaction ¶
RunInTransaction executes a function within a transaction.
func (*Model) SetKeyFromString ¶
SetKeyFromString sets the key from a string ID.
func (*Model) SetNamespace ¶
SetNamespace sets the namespace.
func (*Model) SoftDelete ¶
SoftDelete marks the entity as deleted without removing it.
type Query ¶
type Query interface {
Filter(filterStr string, value interface{}) Query
FilterField(fieldPath string, op string, value interface{}) Query
Order(fieldPath string) Query
OrderDesc(fieldPath string) Query
Limit(limit int) Query
Offset(offset int) Query
Project(fieldNames ...string) Query
Distinct() Query
Ancestor(ancestor Key) Query
GetAll(ctx context.Context, dst interface{}) ([]Key, error)
First(ctx context.Context, dst interface{}) (Key, error)
Count(ctx context.Context) (int, error)
Keys(ctx context.Context) ([]Key, error)
Run(ctx context.Context) Iterator
Start(cursor Cursor) Query
End(cursor Cursor) Query
}
Query provides a fluent interface for querying entities.
type QueryFilter ¶
QueryFilter holds a filter condition.
type QueryOrder ¶
QueryOrder holds an order directive.
type Registry ¶ added in v0.6.8
Registry resolves a tenant to its database.
The model is one SQLite file per tenant, S3 as the source of truth, and local disk as a cache. A node holds a bounded number of tenant databases open, materialising a file from remote storage when it is not on disk and closing the coldest handles when the bound is reached. That is what lets any node serve any tenant, and lets a node stay small while the tenant count grows.
Before this existed the capability was split and neither half was complete: this package declared the tenant contract (TenantID/TenantType) with no lifecycle, while hanzoai/commerce carried the lifecycle in unbounded userDBs/orgDBs maps whose handles were only closed at shutdown — so file descriptors and memory grew with the number of tenants ever touched, and nothing replicated. Open-per-tenant without a bound leaks by construction.
T is the handle type. The registry calls exactly one method on it — Close — because releasing a handle is the whole of its job; what a handle *is* stays the caller's business. That separation is load-bearing: pinning T to this package's DB would force every owner of per-tenant files to adopt this package's entity API as well, which is exactly the toll that made commerce write its own lifecycle instead of reusing this one. Use Registry[DB] here, Registry[yourDB] there, one implementation either way.
Layering, one job each:
transport carries WHICH tenant (request context) Registry resolves tenant -> handle: open, cache, evict <- here Replicate makes each tenant file durable (WAL -> S3) caller asks for a tenant's database and thinks about none of it
func NewRegistry ¶ added in v0.6.8
func NewRegistry[T io.Closer](cfg RegistryConfig[T]) (*Registry[T], error)
NewRegistry builds a Registry. Dir, a positive MaxOpen and Open are required — an unbounded registry is the leak this type exists to prevent.
func (*Registry[T]) Close ¶ added in v0.6.8
Close shuts every open database. Further calls to Do fail with ErrRegistryClosed.
func (*Registry[T]) Do ¶ added in v0.6.8
Do runs fn with the tenant's database held open.
This is the whole API on purpose. Handing callers a raw handle means handing them the job of returning it, and a forgotten return pins a database open forever — reintroducing exactly the unbounded growth this type prevents. Within fn the handle cannot be evicted; after fn returns it becomes evictable. Do not retain the handle beyond fn.
type RegistryConfig ¶ added in v0.6.8
type RegistryConfig[T io.Closer] struct { // Dir is the local cache directory holding tenant files. It is a CACHE: // anything here must be reconstructible from remote storage, because // eviction deletes handles and a node may be replaced at any time. Dir string // MaxOpen bounds how many databases stay open at once. Reaching it evicts // the least recently used handle that nobody is currently using. // // Zero means unbounded, which is the shape that leaks; NewRegistry rejects // it rather than letting it be the accidental default. MaxOpen int // IdleTTL closes handles unused for this long, even when below MaxOpen, so // a node that goes quiet gives its file descriptors back. Zero disables it. IdleTTL time.Duration // Open opens the database for a tenant at path. Required — there is no // default, because a default could only ever be right for one T, and a // silently-wrong handle type is worse than a missing one. OpenSQLiteTenant // is the ready-made opener for Registry[DB]. Open func(t Tenant, path string) (T, error) // Materialize is called when path does not exist locally, to restore it // from remote storage before Open. Returning nil without creating the file // is valid and means "new tenant, start empty". // // Nil skips the step entirely — local-only, which is correct for tests and // single-node development but is NOT the production shape. Materialize func(ctx context.Context, t Tenant, path string) error // OnOpen runs after a database is opened, for per-tenant setup that must // track the handle's lifetime — starting WAL replication for this file is // the reason it exists. Its error fails the open. OnOpen func(t Tenant, path string, db T) error // OnClose runs before a database is closed, to undo OnOpen. Its error is // returned by Close but does not prevent the handle being released. OnClose func(t Tenant, path string, db T) error // PathFor maps a tenant to its file path under Dir. Defaults to // <Dir>/<type>/<id>.db. PathFor func(dir string, t Tenant) string }
RegistryConfig configures how tenant databases are located, opened and bounded.
type SQLiteConfig ¶
type SQLiteConfig struct {
MaxOpenConns int
MaxIdleConns int
BusyTimeout int
JournalMode string
Synchronous string
CacheSize int
QueryTimeout time.Duration
}
SQLiteConfig holds SQLite-specific configuration.
type SQLiteDB ¶
type SQLiteDB struct {
// contains filtered or unexported fields
}
SQLiteDB implements the DB interface using SQLite.
func AdaptSQLDB ¶ added in v0.6.7
AdaptSQLDB layers the ORM's typed-record model (the `_entities` table) over an already-open *sql.DB the CALLER owns. Use it when a store's file is opened elsewhere — cloud's per-org SQLite, for instance, is opened through one seam (cek-encrypted at rest, single-writer, WAL pragmas) and handed to subsystems as a *sql.DB. AdaptSQLDB lets the ORM manage records IN that file without owning the file: the caller keeps the connection's pragmas, encryption, durability, and Close; the ORM only ensures its schema and marshals records.
The one connection serves both reads and writes (the caller's pool, typically MaxOpenConns(1) for a serialized single writer), serialized by writeMu exactly as NewSQLiteDB serializes against its dedicated write connection. initSchema runs so `_entities` exists; any table the caller created is untouched. Close is a no-op — the connection belongs to the caller, who closes it.
func NewSQLiteDB ¶
func NewSQLiteDB(cfg *SQLiteDBConfig) (*SQLiteDB, error)
NewSQLiteDB creates a new SQLite database connection.
func (*SQLiteDB) AllocateIDs ¶
func (*SQLiteDB) CreateIfAbsent ¶ added in v0.6.6
func (*SQLiteDB) DeleteMulti ¶
func (*SQLiteDB) NewIncompleteKey ¶
func (*SQLiteDB) RunInTransaction ¶
func (db *SQLiteDB) RunInTransaction(ctx context.Context, fn func(tx Transaction) error, opts *TransactionOptions) error
func (*SQLiteDB) TenantType ¶
func (*SQLiteDB) VectorSearch ¶
func (db *SQLiteDB) VectorSearch(ctx context.Context, opts *VectorSearchOptions) ([]VectorResult, error)
type SQLiteDBConfig ¶
type SQLiteDBConfig struct {
Path string
Config SQLiteConfig
EnableVectorSearch bool
VectorDimensions int
TenantID string
TenantType string
}
SQLiteDBConfig holds configuration for a SQLite database.
type SimpleCursor ¶
SimpleCursor is a basic cursor implementation.
func (*SimpleCursor) String ¶
func (c *SimpleCursor) String() string
type Tenant ¶ added in v0.6.8
Tenant identifies one database. Type separates keyspaces that may share an id — a user and an org called "acme" are different tenants.
type Transaction ¶
type Transaction interface {
Get(key Key, dst interface{}) error
Put(key Key, src interface{}) (Key, error)
// CreateIfAbsent is the transaction-scoped conditional insert: the same
// first-writer-wins semantics as DB.CreateIfAbsent, participating in the
// enclosing transaction.
CreateIfAbsent(key Key, src interface{}) (created bool, err error)
Delete(key Key) error
Query(kind string) Query
// GetForUpdate reads the row into dst AND acquires a row-level exclusive
// lock for the duration of the transaction. Concurrent txs that also call
// GetForUpdate on the same key block until this tx commits or rolls back.
// Required for compare-and-swap patterns against a shared row, where SSI
// alone is insufficient because ON CONFLICT DO UPDATE can miss the
// rw-dependency cycle. SQLite honors this via the write mutex it already
// holds; drivers without row-locking treat it as a regular Get.
GetForUpdate(key Key, dst interface{}) error
}
Transaction represents a database transaction.
type TransactionOptions ¶
type TransactionOptions struct {
ReadOnly bool
MaxAttempts int
Isolation IsolationLevel
}
TransactionOptions configures transaction behavior.
type Validator ¶
type Validator interface {
Validate() error
}
Validator interface for entities that support validation.
type VectorResult ¶
VectorResult represents a vector search result.
type VectorSearchOptions ¶
type VectorSearchOptions struct {
Kind string
Vector []float32
Limit int
MinScore float32
Filters map[string]interface{}
}
VectorSearchOptions configures vector similarity search.
type ZapBackend ¶ added in v0.2.0
type ZapBackend int
ZapBackend selects which ZAP-native backend to connect to.
const ( // ZapSQL connects to hanzo/sql (PostgreSQL fork) on port 9651. ZapSQL ZapBackend = iota // ZapDocumentDB connects to hanzo/documentdb (FerretDB fork) on port 9654. // Provides MongoDB-style document semantics over PostgreSQL storage. // Clients who "think mongo" use this; data lives in hanzo/sql. ZapDocumentDB // ZapKV connects to hanzo/kv (Valkey fork) on port 9653. ZapKV // ZapDatastore connects to hanzo/datastore (ClickHouse fork) on port 9655. ZapDatastore )
type ZapConfig ¶ added in v0.2.0
type ZapConfig struct {
// Addr is the backend address (e.g., "localhost:9651").
// If empty, uses DefaultPorts[Backend] on localhost.
Addr string
// Backend selects which ZAP-native backend to connect to.
Backend ZapBackend
// Database is the target database name (for SQL/DocumentDB backends).
Database string
// Collection is the default collection/table for entity storage.
// Defaults to "_entities" for SQL, "entities" for DocumentDB.
Collection string
// QueryTimeout is the per-query timeout (default 30s).
QueryTimeout time.Duration
}
ZapConfig configures a ZAP database connection.
type ZapDB ¶ added in v0.2.0
type ZapDB struct {
// contains filtered or unexported fields
}
ZapDB implements db.DB over the ZAP-HTTP binary protocol.
func NewZapDB ¶ added in v0.2.0
NewZapDB dials a ZAP-native backend and returns a DB implementation. The transport connects lazily on the first operation, so this does not fail when the backend is momentarily unreachable — the first Get/Put surfaces a clear dial error instead.
func (*ZapDB) AllocateIDs ¶ added in v0.2.0
func (*ZapDB) CreateIfAbsent ¶ added in v0.6.6
CreateIfAbsent conditionally inserts src under key, first-writer-wins. See db.DB.CreateIfAbsent for the contract. Dispatch mirrors Put: each ZAP-native backend uses its own conditional-insert primitive (SQL ON CONFLICT, Valkey SET NX, document unique _id). The reply decides created; an unrecognized or error reply returns an error rather than a guessed created value, so a caller never mistakes an upsert or a transport failure for a first-writer win.
Status: the hanzo ZAP backends do not yet expose a zap-proto/http listener (see LLM.md), so these paths are wire-complete but exercised only by the env-gated live integration test, not unit CI. The SQLite backend is the fully-tested reference implementation of the identical contract.
func (*ZapDB) DeleteMulti ¶ added in v0.2.0
func (*ZapDB) NewIncompleteKey ¶ added in v0.2.0
func (*ZapDB) RunInTransaction ¶ added in v0.2.0
func (z *ZapDB) RunInTransaction(ctx context.Context, fn func(tx Transaction) error, opts *TransactionOptions) error
func (*ZapDB) TenantType ¶ added in v0.2.0
func (*ZapDB) VectorSearch ¶ added in v0.2.0
func (z *ZapDB) VectorSearch(ctx context.Context, opts *VectorSearchOptions) ([]VectorResult, error)