Documentation
¶
Overview ¶
Package hazedb is a memory-resident SQL store for embedded Go applications with single-process deployment and latency-sensitive reads.
Index ¶
- Constants
- Variables
- func ArgsFromJSON(raw []byte) ([]any, error)
- func DeregisterDBIf(name string, db *DB)
- func ErrorJSON(msg string) []byte
- func ExecResultJSON(affected int) []byte
- func LookupDBSlot(name string) *atomic.Pointer[DB]
- func QueryArgs(s string) ([]any, error)
- func RegisterDB(name string, db *DB)
- func RowsToJSON(cols []string, rows []Row) ([]byte, error)
- func RowsToJSONObjects(cols []string, rows []Row) ([]byte, error)
- type ColumnDef
- type ColumnType
- type DB
- func (db *DB) Close() error
- func (db *DB) Exec(sql string, args ...any) (int, error)
- func (db *DB) ExecValues(sql string, args ...Value) (int, error)
- func (db *DB) FlushWAL() error
- func (db *DB) Prepare(sql string) (*Stmt, error)
- func (db *DB) Query(sql string, args ...any) ([]string, []Row, error)
- func (db *DB) QueryRow(sql string, args ...any) ([]string, Row, error)
- func (db *DB) QueryRowValues(sql string, args ...Value) ([]string, Row, error)
- func (db *DB) QueryValues(sql string, args ...Value) ([]string, []Row, error)
- func (db *DB) Transaction(fn func(*Tx) error) error
- type IndexDef
- type Options
- type Row
- type Schema
- type Stmt
- type TableDef
- type Tx
- type UUID
- type Value
- func (v Value) AsInt() (int64, error)
- func (v Value) AsString() string
- func (v Value) Bool() bool
- func (v Value) Bytes() []byte
- func (v Value) Compare(o Value) (int, bool)
- func (v Value) Equal(o Value) bool
- func (v Value) Int() int64
- func (v Value) IsNull() bool
- func (v Value) Str() string
- func (v Value) UUID() UUID
- type ValueKind
Constants ¶
const ( WALOff = iota // 0: memory only; nothing survives a crash. WALPeriodic // 1: writes fsynced by a background ticker (~1s); the caller never waits. WALPerWrite // 2: every write fsynced before the caller gets a reply — slowest, safest. )
WALLevel selects on-disk durability — the single switch for the write-ahead log. WALPath turns nothing on by itself; the level does.
Variables ¶
var ( ErrDuplicatePK = errors.New("fastsql: duplicate primary key") ErrUnknownTable = errors.New("fastsql: unknown table") ErrUnknownColumn = errors.New("fastsql: unknown column") ErrTypeMismatch = errors.New("fastsql: type mismatch") ErrParamMismatch = errors.New("fastsql: parameter count mismatch") ErrPKUpdate = errors.New("fastsql: UPDATE on PK column not supported") ErrParse = errors.New("fastsql: parse error") ErrWALCorrupt = errors.New("fastsql: WAL corrupted") ErrTableExists = errors.New("hazedb: table already exists") ErrTxUnsupported = errors.New("hazedb: operation not supported in a transaction") )
Functions ¶
func ArgsFromJSON ¶ added in v0.1.4
ArgsFromJSON parses a JSON array of positional SQL args into the []any db.Query / db.Exec accept. Empty/nil input yields no args. See the file header for the type mapping (notably the canonical-UUID-string rule).
func DeregisterDBIf ¶ added in v0.1.4
DeregisterDBIf clears the slot only if it still holds db (CAS). Safe for caddymodule Cleanup during reload: preserves a newer Provision's registration.
func ExecResultJSON ¶ added in v0.1.4
ExecResultJSON renders a write result as {"affected":n}.
func LookupDBSlot ¶ added in v0.1.4
LookupDBSlot returns the stable atomic slot for name (lazily created). Cache it once and Load() per use:
var defaultSlot = hazedb.LookupDBSlot("default")
db := defaultSlot.Load() // nil until a caddymodule Provision registers one
func QueryArgs ¶ added in v0.1.4
QueryArgs parses an adapter's args parameter in either form:
- "" → no args
- starts with '[' → a JSON array (ArgsFromJSON; multi-arg / typed / writes)
- anything else → ONE positional arg passed directly: a canonical UUID string → UUID, otherwise STRING
The direct form lets a caller pass a key it already has — e.g. the UUID from a clicked link — straight into `WHERE id = ?` with no json_encode wrapping, the common single-key read. For integer args or several args, use the JSON array form so types are unambiguous.
func RegisterDB ¶ added in v0.1.4
RegisterDB publishes db under name. Pass nil to clear unconditionally; from Cleanup-style paths use DeregisterDBIf to avoid clobbering a newer registration.
func RowsToJSON ¶ added in v0.1.4
RowsToJSON renders a result set as {"columns":[...],"rows":[[...],...]}.
Hand-rolled: it appends each Value straight into one capacity-hinted buffer, with no [][]any intermediate, no interface boxing, and no reflection. On a hot read path (one PHP call per request) that is ~1.5–3× faster than encoding/json and turns ~4 allocations per row into a single buffer allocation. The returned []byte is the caller's to keep or copy (the PHP adapter copies it into a zend_string immediately). Never errors — the signature keeps the error for API symmetry with the rest of wire.go.
func RowsToJSONObjects ¶ added in v0.1.4
RowsToJSONObjects renders a result set as a JSON array of objects keyed by column name — [{"col":val,...},...] — the shape a fetchall-style caller forwards straight to an HTTP/JSON response. Same hand-rolled, single-buffer approach as RowsToJSON; never errors (error kept for API symmetry).
Types ¶
type ColumnDef ¶
type ColumnDef struct {
Name string
Type ColumnType
// PK marks the single primary-key column. It must be TypeUUID and is
// implicitly immutable.
PK bool
// Immutable rejects UPDATE SET on this column at plan time. Used for an
// ordering column (e.g. seq) so a tail index can cache its order safely.
Immutable bool
// PartitionKey marks the column the table is sharded/ordered by (e.g.
// thread_id on a messages table). At most one per table; must be UUID in
// v1; implicitly immutable (a partition move is DELETE + INSERT). Storage
// routing + the ordered tail index build on it.
PartitionKey bool
Nullable bool
}
ColumnDef describes one column at table-create time.
type ColumnType ¶
type ColumnType uint8
ColumnType is the declared type of a column. Generic v1 supports a small set; codegen (M3) expands to richer types.
const ( TypeInt ColumnType = iota + 1 TypeString TypeBytes TypeBool TypeUUID )
func (ColumnType) String ¶
func (t ColumnType) String() string
type DB ¶
type DB struct {
// contains filtered or unexported fields
}
--- Gateway contract -------------------------------------------------------
*DB is the single official entry point — the "gateway". Every consumer enters through it: Caddy calls these methods as native Go, and the FrankenPHP/PHP extension reaches them via cgo (C → exported Go → these same methods). There is no second transport — the PHP path is cgo calling the very same verbs, not a parallel API. The gateway verbs are Open/Close/FlushWAL/Exec/Query/QueryRow here, plus Transaction (txn.go).
Every verb upholds these guarantees, so all consumers inherit them for free:
- Validation. SQL is parsed, planned, and bound to the live catalog in prepare(); args are type-coerced in toValue. Bad SQL or args fail here.
- Boundary clone. []byte/Value args are deep-copied on the way in (storage never aliases caller memory), and returned rows are deep-cloned on the way out (callers may retain them past later writes).
- No bypass. table/shard/catalog/wal are unexported, so no consumer can reach storage around the validated verbs.
Boundary rule: db semantics live behind the gateway (this package); cross-cutting concerns — auth, tenancy, logging, and the PHP↔Go marshalling the extension needs — live in the consumer/adapter, which then calls these same verbs. Never push consumer-specific concerns into the core.
DB is the embedded database handle. One DB per process per WAL path. Open is goroutine-safe; Exec and Query are goroutine-safe.
func LookupDB ¶ added in v0.1.4
LookupDB returns the *DB registered under name, or nil. Convenience wrapper; hot-path consumers should cache the slot via LookupDBSlot and Load() per use.
func Open ¶
Open prepares the database. If WALPath is non-empty, the file is opened and any existing records are replayed into memory before Open returns. Open is blocking until replay completes.
func (*DB) Close ¶
Close stops the SQLite drain loop (which seals the active segment and runs a final drain so the mirror is current), then the index merger, then flushes and closes the WAL, then closes the mirror. Memory-only DBs still stop the merger. The drain loop is stopped first, while the WAL is still open.
func (*DB) Exec ¶
Exec runs an INSERT, UPDATE, DELETE, CREATE TABLE, or DROP TABLE. Returns the affected row count (0 for DDL).
func (*DB) ExecValues ¶ added in v0.1.4
ExecValues runs a write with pre-typed Value arguments, skipping the []any arg-conversion layer Exec uses (no JSON decode, no interface boxing, no per-arg type switch). It is the in-process fast path for callers that already hold typed Values — notably the PHP extension reading a native zend_array via cgo, which avoids the json_encode/json.Decode round-trip the string args form pays. Returns the affected row count (0 for DDL).
func (*DB) FlushWAL ¶
FlushWAL forces bufio to fsync. Use before reading a record back from disk in tests, or for an explicit durability boundary in callers. Memory-only DBs are a no-op.
func (*DB) Prepare ¶ added in v0.1.4
Prepare compiles sql into a reusable statement bound to the current catalog.
func (*DB) Query ¶
Query runs a SELECT. Returns the column names (in projection order) and the rows. Rows are deep-cloned; callers may retain them past future Exec calls without worrying about aliasing into storage.
func (*DB) QueryRow ¶ added in v0.1.2
QueryRow runs a SELECT expected to yield a single row and returns the first matching row, or a nil Row if there is none — without allocating the []Row result slice that Query needs. For a PK-pinned query (WHERE id = ?) it goes straight through the point-read path (the common case); for an unpinned query it returns the first row of the scan, so constrain such queries with LIMIT 1 to avoid scanning more rows than needed. The returned row is deep-cloned, as with Query.
func (*DB) QueryRowValues ¶ added in v0.1.4
QueryRowValues is QueryRow with pre-typed args (see QueryValues).
func (*DB) QueryValues ¶ added in v0.1.4
QueryValues is Query with pre-typed args — the read counterpart of ExecValues, for in-process callers (the PHP extension) that already hold typed Values and want to skip the []any/JSON conversion. Reads never store the args, so no per-arg clone is needed.
type IndexDef ¶ added in v0.1.5
type IndexDef struct {
Name string // optional; auto-derived as "idx_<col>" when empty
Column string
Unique bool
// Ordered makes this a sorted index (serves equality + ranges + ORDER BY)
// instead of the default hash index (equality only). A column has one or the
// other, not both.
Ordered bool
}
IndexDef declares a secondary index on one non-PK column. Maintained asynchronously and read through a hybrid path; see docs/secondary-indexes.md. Single-column only in v1 (composite is v1.1+).
type Options ¶
type Options struct {
// Schema declares the tables present at startup. May be empty — tables can
// also be created at runtime with CREATE TABLE.
Schema Schema
// WALLevel is the durability switch: WALOff (memory only), WALPeriodic
// (background fsync), or WALPerWrite (fsync per write). Above WALOff, WALPath
// is required; setting WALPath or SQLitePath with WALOff is rejected.
WALLevel int
// WALPath is the directory holding the write-ahead log segments, created if
// absent. Required when WALLevel > 0.
WALPath string
// WALRotateInterval is how often the active segment is sealed and the next
// opened, so a drain (or any external consumer) reads settled segments and
// the log never grows as one unbounded file. Zero = 5s.
WALRotateInterval time.Duration
// SQLitePath enables the on-disk SQLite mirror: the drain feeds sealed
// segments into it (compacted current state) and it becomes the recovery
// source. Empty = no mirror (the WAL itself replays into memory on boot).
// Requires WALLevel > 0.
SQLitePath string
// contains filtered or unexported fields
}
Options configure Open. The EXPORTED fields are the entire operator surface; the unexported fields are internal tuning, settable only from package tests (the defaults are right for every deployment). Genuine implementation constants — buffer sizes, SQLite PRAGMAs, segment naming, shard counts — are not settings and live next to the code that uses them.
type Schema ¶
type Schema struct {
Tables []TableDef
}
Schema is the database schema. Tables are addressed by name.
type Stmt ¶ added in v0.1.4
type Stmt struct {
// contains filtered or unexported fields
}
Stmt is a SQL string compiled to a plan once and reused. Versus the bare DB.Query/Exec path it skips the per-call statement-cache lookup (no SQL-string hash), and it adds a typed, zero-allocation point-read fast path, QueryRowByPK. Safe for concurrent use: the held plan is rebound automatically if a CREATE/DROP changes the catalog after Prepare.
func (*Stmt) Columns ¶ added in v0.1.4
Columns returns the SELECT output column names (nil for a non-SELECT). The slice is shared and read-only — callers must not mutate it.
func (*Stmt) QueryRow ¶ added in v0.1.4
QueryRow runs a prepared single-row SELECT. See DB.QueryRow.
func (*Stmt) QueryRowByPK ¶ added in v0.1.4
QueryRowByPK is the zero-allocation point-read fast path. The statement must be a PK-pinned SELECT (WHERE <pk> = ?); the key is taken as a typed UUID (no interface boxing) and the projected cells are written into dst, which is grown only if too small — reuse it across calls. found reports whether a row matched. Non-byte cells are copied directly; BYTES cells are cloned to honour storage's no-alias guarantee, so a projection without BYTES columns allocates nothing.
type TableDef ¶
TableDef defines a table. Exactly one column must have PK=true. Multi-column PK is v1.1+.
type Tx ¶
type Tx struct {
// contains filtered or unexported fields
}
Tx is the transaction handle passed to the db.Transaction closure.
type UUID ¶
type UUID [16]byte
UUID is a 128-bit identifier (RFC 9562). It is a fixed array so it is comparable and usable directly as a map key with no allocation — the primary key index is map[UUID]uint64.
func NewUUIDv7 ¶
func NewUUIDv7() UUID
NewUUIDv7 returns a fresh UUIDv7 with within-millisecond monotonicity: IDs from this process sort by creation time, including inside one millisecond, via a 12-bit counter in rand_a (RFC 9562 "fixed-length dedicated counter"). Beyond 4096 IDs in a single ms the timestamp is nudged forward to keep the ordering total. Client-supplied UUIDs get no such guarantee.
func ParseUUID ¶
ParseUUID parses the canonical 36-character hyphenated hex form. It accepts any well-formed UUID (version is not enforced here — callers that require v7 check IsV7). Used at the API boundary to turn a string PK into the internal [16]byte; storage never sees the string form.
func (UUID) IsV7 ¶
IsV7 reports whether u has the v7 version nibble and the RFC 4122/9562 variant bits (10xx). Used to validate client-supplied IDs.
type Value ¶
type Value struct {
Kind ValueKind
// contains filtered or unexported fields
}
Value is the runtime cell type: a packed 32-byte tagged union (versus the 72 bytes a struct with separate int/string/bytes/uuid fields would take). Kind is the public tag; the payload is overlapped across two words and a pointer, since a cell is only ever one kind at a time:
- KindInt / KindBool : the value lives in w0
- KindUUID : the 16 bytes live inline in (w0, w1), big-endian so comparing the words equals byte-lexicographic order
- KindString/KindBytes: ptr is the backing-data pointer, w0 the length
- KindNull : all zero
ptr is ALWAYS nil or a real Go pointer (a string/[]byte backing) — never a reinterpreted non-pointer — so the garbage collector scans it correctly and keeps the backing alive. Read payloads through the typed accessors below; never touch the private fields.
func (Value) AsInt ¶
AsInt returns the value as int64, coercing strings via strconv. Used for ORDER BY / range comparisons.
func (Value) AsString ¶
AsString returns the value formatted as text. Used for PK keys in the map index and for display.
func (Value) Compare ¶
Compare returns -1/0/1 for v < o, v == o, v > o. Coerces along the most-precise route: int-int → numeric, otherwise string-string. Null comparisons return 0 with comparable=false to let callers decide.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
addons
|
|
|
frankenphp-ext
module
|
|
|
caddymodule
module
|
|
|
Package GO-SQLDB is a deliberately minimal spike to validate the central thesis of the GO-SQLDB design: that an in-memory Go store with a canonical WAL can beat SQLite-in-memory on the hot OLTP path.
|
Package GO-SQLDB is a deliberately minimal spike to validate the central thesis of the GO-SQLDB design: that an in-memory Go store with a canonical WAL can beat SQLite-in-memory on the hot OLTP path. |