db

package
v0.6.21 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2026 License: MIT Imports: 19 Imported by: 1

Documentation

Overview

Package db is the relational plane: entities in a database, one database per namespace.

DB is the entity contract, implemented here by SQLite (with sqlite-vec for vector search) and by ZAP for the backends that speak it — hanzo/sql over PostgreSQL, document storage, KV. Namespaces owns the other half: which database a namespace resolves to, how many stay open, and when a cold one is closed.

Datastore is declared here and implemented in orm/datastore. It is the analytics plane — measurements in one shared columnar store, where a tenant is a column rather than a database — and it deliberately shares no connection with this one.

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

Constants

This section is empty.

Variables

View Source
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")
)
View Source
var DefaultPorts = map[ZapBackend]int{
	ZapSQL:        9651,
	ZapKV:         9653,
	ZapDocumentDB: 9654,
	ZapDatastore:  9655,
}

DefaultPorts for each ZAP-native backend.

View Source
var ErrClosed = errors.New("db: namespaces closed")

ErrClosed is returned once the namespace set is closed.

Functions

func LowercaseFirst

func LowercaseFirst(s string) string

LowercaseFirst lowercases the first character of a string.

func NormalizeOp

func NormalizeOp(op string) string

NormalizeOp converts operators to SQL.

func ParseFilterString

func ParseFilterString(s string) (field, op string)

ParseFilterString parses "Field=" into field and operator.

func ToJSONFieldName

func ToJSONFieldName(field string) string

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".

It also drops every character a JSON field path cannot contain, because the result is interpolated into a SQL string literal — json_extract(data, '$.X') — where a quote ENDS the literal and the rest is executed. Callers pass this straight from a query string: commerce's generic REST list helper does .Order(c.Query("sort")) for every entity it serves. Without the filter, ?sort=x') UNION SELECT name FROM sqlite_master -- closed the literal and appended a working UNION.

Filtering rather than rejecting keeps the signature and fails CLOSED: a name that had illegal characters becomes a field that does not exist, so a filter on it matches no rows and an ORDER BY on it sorts every row equally. A field path is identifier segments joined by dots and never needed anything else.

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 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 Cursor

type Cursor interface {
	String() string
}

Cursor represents a position in a result set.

func DecodeCursor

func DecodeCursor(s string) (Cursor, error)

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
}

DB is the main database interface for entity storage.

func OpenNamespace added in v0.6.14

func OpenNamespace(ns Namespace, path string) (DB, error)

OpenNamespace opens a namespace's SQLite store with this package's defaults. It is the NamespacesConfig[DB].Open for the common case.

type Datastore added in v0.6.12

type Datastore interface {
	Ready() bool
	Exec(ctx context.Context, stmt string, args ...any) error
	Query(ctx context.Context, query string, args ...any) ([]map[string]any, error)
	Close() error
}

Datastore is the analytics plane: one shared columnar warehouse holding measurements, where a tenant is a column rather than a database. It is the counterpart of DB, which holds entities and where a tenant is the whole database. Analytics SQL is written by hand, so this carries statements and rows and maps no records.

Ready reports whether the warehouse is reachable; Exec and Query return an error rather than fabricating a result when it is not. Implemented by orm/datastore.Conn — declared here so the relational plane can name the analytics plane without importing its driver.

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 Iterator

type Iterator interface {
	Next(dst interface{}) (Key, error)
	Cursor() (Cursor, error)
}

Iterator allows iterating over query results.

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 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) Create

func (m *Model) Create(ctx context.Context) error

Create creates a new entity.

func (*Model) DB

func (m *Model) DB() DB

DB returns the database interface.

func (*Model) Delete

func (m *Model) Delete(ctx context.Context) error

Delete removes the entity from the database.

func (*Model) Entity

func (m *Model) Entity() Kind

Entity returns the entity reference.

func (*Model) Exists

func (m *Model) Exists(ctx context.Context) (bool, error)

Exists checks if the entity exists in the database.

func (*Model) Get

func (m *Model) Get(ctx context.Context) error

Get retrieves the entity from the database.

func (*Model) GetByID

func (m *Model) GetByID(ctx context.Context, id string) error

GetByID retrieves an entity by its ID.

func (*Model) GetID

func (m *Model) GetID() string

GetID returns the entity ID.

func (*Model) GetKind

func (m *Model) GetKind() string

GetKind returns the entity kind.

func (*Model) GetNamespace

func (m *Model) GetNamespace() string

GetNamespace returns the namespace for this entity.

func (*Model) Init

func (m *Model) Init(database DB, entity Kind)

Init initializes the model with a database and entity reference.

func (*Model) IsCreated

func (m *Model) IsCreated() bool

IsCreated returns true if the entity has been persisted.

func (*Model) IsLoaded

func (m *Model) IsLoaded() bool

IsLoaded returns true if the entity has been loaded from the database.

func (*Model) JSON

func (m *Model) JSON() ([]byte, error)

JSON returns the JSON representation of the entity.

func (*Model) JSONString

func (m *Model) JSONString() string

JSONString returns the JSON string representation.

func (*Model) Key

func (m *Model) Key() Key

Key returns the database key for this entity.

func (*Model) MarkLoaded

func (m *Model) MarkLoaded()

MarkLoaded marks the entity as loaded.

func (*Model) ModelQuery

func (m *Model) ModelQuery() Query

ModelQuery returns a new query for this entity's kind.

func (*Model) MustGet

func (m *Model) MustGet(ctx context.Context)

MustGet retrieves the entity or panics.

func (*Model) MustGetByID

func (m *Model) MustGetByID(ctx context.Context, id string)

MustGetByID retrieves by ID or panics.

func (*Model) MustPut

func (m *Model) MustPut(ctx context.Context)

MustPut saves the entity or panics.

func (*Model) Put

func (m *Model) Put(ctx context.Context) error

Put saves the entity to the database.

func (*Model) RunInTransaction

func (m *Model) RunInTransaction(ctx context.Context, fn func(tx Transaction) error) error

RunInTransaction executes a function within a transaction.

func (*Model) SetDB

func (m *Model) SetDB(database DB)

SetDB sets the database interface.

func (*Model) SetEntity

func (m *Model) SetEntity(entity Kind)

SetEntity sets the entity reference.

func (*Model) SetID

func (m *Model) SetID(id string)

SetID sets the entity ID.

func (*Model) SetKey

func (m *Model) SetKey(key Key) error

SetKey sets the database key.

func (*Model) SetKeyFromString

func (m *Model) SetKeyFromString(id string) error

SetKeyFromString sets the key from a string ID.

func (*Model) SetNamespace

func (m *Model) SetNamespace(ns string)

SetNamespace sets the namespace.

func (*Model) SoftDelete

func (m *Model) SoftDelete(ctx context.Context) error

SoftDelete marks the entity as deleted without removing it.

func (*Model) Update

func (m *Model) Update(ctx context.Context) error

Update updates an existing entity.

type Namespace added in v0.6.14

type Namespace string

Namespace names one database, e.g. "org/acme" or "user/123/notes". It is opaque here: a path component and an eviction key, nothing more. What qualifies a namespace is hanzoai/iam's business, and this package never branches on what one means. One tenant owns many namespaces; a namespace is what maps to a single file.

func (Namespace) String added in v0.6.14

func (ns Namespace) String() string

type Namespaces added in v0.6.14

type Namespaces[T io.Closer] struct {
	// contains filtered or unexported fields
}

Namespaces resolves a namespace to its database.

The model is one SQLite file per namespace, remote storage as the source of truth, and local disk as a cache. A node holds a bounded number of 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 namespace, and lets a node stay small while their number grows.

Before this existed the capability was split and neither half was complete: this package declared a per-tenant database contract 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-name without a bound leaks by construction, which is why MaxOpen has no default.

T is the handle type. This type 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-namespace 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 Namespaces[DB] here, Namespaces[yourDB] there, one implementation either way.

Layering, one job each:

transport   carries WHICH namespace (request context)
Namespaces  resolves namespace -> handle: open, cache, evict   <- here
replicate   makes each file durable (WAL -> object storage)
caller      asks for a namespace's database and thinks about none of it

func NewNamespaces added in v0.6.14

func NewNamespaces[T io.Closer](cfg NamespacesConfig[T]) (*Namespaces[T], error)

NewNamespaces builds a Namespaces. Dir, a positive MaxOpen and Open are required — an unbounded registry is the leak this type exists to prevent.

func (*Namespaces[T]) Close added in v0.6.14

func (n *Namespaces[T]) Close() error

Close shuts every open database. Further calls to With fail with ErrClosed.

It DRAINS: a database still inside a With is not closed until that With returns. evict has always refused to touch an entry with in-flight users — "a slow request is never yanked out from under its caller" — and shutdown is the one moment every in-flight request meets that promise at once, so it is the last place to break it. Closing under a live query does not merely fail that query; the handle it is reading through goes away beneath it.

The wait is unbounded on purpose. With releases its reference with defer, so this drains unless a caller's fn never returns — and a deadline here would be this type inventing a shutdown policy it cannot know. A caller that wants a bounded drain owns that decision and can impose it from outside.

func (*Namespaces[T]) Held added in v0.6.17

func (n *Namespaces[T]) Held() int

Held reports how many databases are currently held open. Named for the state it reports, not the verb that produced it: Open on this type is the opener in NamespacesConfig, and one word cannot be both a count and an action.

func (*Namespaces[T]) With added in v0.6.14

func (n *Namespaces[T]) With(ctx context.Context, ns Namespace, fn func(T) error) error

With runs fn while the namespace's database is 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.

func (*Namespaces[T]) WithAll added in v0.6.19

func (n *Namespaces[T]) WithAll(ctx context.Context, nss []Namespace, fn func(map[Namespace]T) error) error

WithAll runs fn with several namespaces held open at once, acquired in parallel.

It exists because entities relate across namespaces even though each one lives in exactly one, and because the files are meant to be small: a request that renders an issue list touches the repository's issues, its labels, and a handful of users, and every one of those is a different database. Serving that by nesting With per namespace builds a pyramid as deep as the number of namespaces involved, and makes the wall clock their SUM -- which is the wrong cost model entirely when each open may be a restore from object storage.

So they are acquired concurrently and the wall clock is the slowest one, not the total. There is no lock-ordering hazard to design around: acquire holds no lock while it waits, it takes a reference and waits on that entry alone, so two callers asking for the same namespaces in opposite orders cannot hold each other's next handle. Sorting would buy nothing here and cost the parallelism.

Namespaces are canonicalised and de-duplicated first, so asking for the same one twice is one handle rather than a second reference that must also be returned.

Held handles cannot be evicted, so fn holds len(ns) of them at once. That may exceed MaxOpen -- the bound governs how many stay open while IDLE, not how many one caller may use -- but a request naming more namespaces than the entire bound describes a query that does not fit on this node, and is refused here rather than left to evict every other tenant to make room for itself.

The handles follow the same rule as With: valid for the life of fn, and not beyond it.

type NamespacesConfig added in v0.6.14

type NamespacesConfig[T io.Closer] struct {
	// Dir is the local cache directory holding the 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; NewNamespaces 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.
	//
	// Finding idle handles means looking at every open one, so it runs as a
	// sweep paced at IdleTTL/2 on registry activity, not on every request: a
	// handle closes between IdleTTL and 1.5*IdleTTL after its last use. Per
	// request the sweep made serving a hit O(open), which is backwards for the
	// type whose job is holding many handles.
	IdleTTL time.Duration

	// Open opens the database for a namespace 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. OpenNamespace
	// is the ready-made opener for Namespaces[DB].
	Open func(ns Namespace, 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 namespace, 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, ns Namespace, path string) error

	// OnOpen runs after a database is opened, for 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(ns Namespace, 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(ns Namespace, path string, db T) error

	// OnEvictError reports a handle that failed to shut down during eviction.
	//
	// Close returns its shutdown error to the caller; eviction has nobody to
	// return to, so without this the error is discarded. That matters more here
	// than anywhere else in the type: eviction IS the durability checkpoint. When
	// OnClose is a final WAL flush to object storage, a failure means that
	// namespace's writes are gone — and until now it happened with no error, no log
	// and no signal of any kind, on the one path the whole "disk is a cache, S3
	// is the truth" model depends on.
	//
	// Nil means those failures stay silent. That is a deliberate choice a caller
	// has to make, not a default they back into: set it to log, alert, or refuse
	// to evict further.
	OnEvictError func(ns Namespace, path string, err error)

	// PathFor maps a namespace to its file path under Dir. It returns an error
	// when the result would escape Dir. Defaults to
	// <Dir>/<type>/<id>.db.
	PathFor func(dir string, ns Namespace) (string, error)
}

NamespacesConfig configures how namespace databases are located, opened and bounded.

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

type QueryFilter struct {
	Field string
	Op    string
	Value interface{}
}

QueryFilter holds a filter condition.

type QueryOrder

type QueryOrder struct {
	Field string
	Desc  bool
}

QueryOrder holds an order directive.

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

func AdaptSQLDB(conn *sql.DB) (*SQLiteDB, error)

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 (db *SQLiteDB) AllocateIDs(kind string, parent Key, n int) ([]Key, error)

func (*SQLiteDB) Close

func (db *SQLiteDB) Close() error

func (*SQLiteDB) CreateIfAbsent added in v0.6.6

func (db *SQLiteDB) CreateIfAbsent(ctx context.Context, key Key, src interface{}) (bool, error)

func (*SQLiteDB) Delete

func (db *SQLiteDB) Delete(ctx context.Context, key Key) error

func (*SQLiteDB) DeleteMulti

func (db *SQLiteDB) DeleteMulti(ctx context.Context, keys []Key) error

func (*SQLiteDB) Get

func (db *SQLiteDB) Get(ctx context.Context, key Key, dst interface{}) error

func (*SQLiteDB) GetMulti

func (db *SQLiteDB) GetMulti(ctx context.Context, keys []Key, dst interface{}) error

func (*SQLiteDB) NewIncompleteKey

func (db *SQLiteDB) NewIncompleteKey(kind string, parent Key) Key

func (*SQLiteDB) NewKey

func (db *SQLiteDB) NewKey(kind string, stringID string, intID int64, parent Key) Key

func (*SQLiteDB) Put

func (db *SQLiteDB) Put(ctx context.Context, key Key, src interface{}) (Key, error)

func (*SQLiteDB) PutMulti

func (db *SQLiteDB) PutMulti(ctx context.Context, keys []Key, src interface{}) ([]Key, error)

func (*SQLiteDB) PutVector

func (db *SQLiteDB) PutVector(ctx context.Context, kind string, id string, vector []float32, metadata map[string]interface{}) error

func (*SQLiteDB) Query

func (db *SQLiteDB) Query(kind string) Query

func (*SQLiteDB) RunInTransaction

func (db *SQLiteDB) RunInTransaction(ctx context.Context, fn func(tx Transaction) error, opts *TransactionOptions) error

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
	Namespace          string
}

SQLiteDBConfig holds configuration for a SQLite database.

type SimpleCursor

type SimpleCursor struct {
	ID     string
	Offset int
}

SimpleCursor is a basic cursor implementation.

func (*SimpleCursor) String

func (c *SimpleCursor) String() string

type Syncable

type Syncable interface {
	Entity
	SyncToDatastore() bool
}

Syncable entities can be synced to analytics store.

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

type VectorResult struct {
	ID       string
	Score    float32
	Metadata map[string]interface{}
}

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, the relational transactional backend, on
	// port 9651.
	ZapSQL ZapBackend = iota
	// ZapDocumentDB connects to hanzo/documentdb on port 9654. It serves
	// document semantics over relational storage, for clients that model data
	// as documents; the rows live in hanzo/sql.
	ZapDocumentDB
	// ZapKV connects to hanzo/kv, the key-value cache and session backend, on
	// port 9653.
	ZapKV
	// ZapDatastore connects to hanzo/datastore, the columnar analytics
	// backend, 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

func NewZapDB(cfg *ZapConfig) (*ZapDB, error)

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 (z *ZapDB) AllocateIDs(kind string, parent Key, n int) ([]Key, error)

func (*ZapDB) Close added in v0.2.0

func (z *ZapDB) Close() error

func (*ZapDB) CreateIfAbsent added in v0.6.6

func (z *ZapDB) CreateIfAbsent(ctx context.Context, key Key, src interface{}) (bool, error)

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) Delete added in v0.2.0

func (z *ZapDB) Delete(ctx context.Context, key Key) error

func (*ZapDB) DeleteMulti added in v0.2.0

func (z *ZapDB) DeleteMulti(ctx context.Context, keys []Key) error

func (*ZapDB) Get added in v0.2.0

func (z *ZapDB) Get(ctx context.Context, key Key, dst interface{}) error

func (*ZapDB) GetMulti added in v0.2.0

func (z *ZapDB) GetMulti(ctx context.Context, keys []Key, dst interface{}) error

func (*ZapDB) NewIncompleteKey added in v0.2.0

func (z *ZapDB) NewIncompleteKey(kind string, parent Key) Key

func (*ZapDB) NewKey added in v0.2.0

func (z *ZapDB) NewKey(kind string, stringID string, intID int64, parent Key) Key

func (*ZapDB) Put added in v0.2.0

func (z *ZapDB) Put(ctx context.Context, key Key, src interface{}) (Key, error)

func (*ZapDB) PutMulti added in v0.2.0

func (z *ZapDB) PutMulti(ctx context.Context, keys []Key, src interface{}) ([]Key, error)

func (*ZapDB) PutVector added in v0.2.0

func (z *ZapDB) PutVector(ctx context.Context, kind string, id string, vector []float32, metadata map[string]interface{}) error

func (*ZapDB) Query added in v0.2.0

func (z *ZapDB) Query(kind string) Query

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) VectorSearch added in v0.2.0

func (z *ZapDB) VectorSearch(ctx context.Context, opts *VectorSearchOptions) ([]VectorResult, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL