hazedb

package module
v0.1.78 Latest Latest
Warning

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

Go to latest
Published: Jun 15, 2026 License: Apache-2.0 Imports: 28 Imported by: 0

README

hazedb

sql db written in go

Documentation

Overview

Package hazedb is a memory-resident SQL store for embedded Go applications with single-process deployment and latency-sensitive reads.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrDuplicatePK       = errors.New("hazedb: duplicate primary key")
	ErrUnknownTable      = errors.New("hazedb: unknown table")
	ErrUnknownColumn     = errors.New("hazedb: unknown column")
	ErrTypeMismatch      = errors.New("hazedb: type mismatch")
	ErrParamMismatch     = errors.New("hazedb: parameter count mismatch")
	ErrPKUpdate          = errors.New("hazedb: UPDATE on PK column not supported")
	ErrParse             = errors.New("hazedb: parse error")
	ErrUnindexedJoin     = errors.New("hazedb: JOIN requires an index on the join column")
	ErrWALCorrupt        = errors.New("hazedb: WAL corrupted")
	ErrWALVersion        = errors.New("hazedb: WAL version mismatch")
	ErrTableExists       = errors.New("hazedb: table already exists")
	ErrTxUnsupported     = errors.New("hazedb: operation not supported in a transaction")
	ErrBatchTooLarge     = errors.New("hazedb: atomic batch too large")
	ErrCapacity          = errors.New("hazedb: store byte capacity exceeded")
	ErrReservedName      = errors.New("hazedb: table name uses the reserved _hz_ prefix")
	ErrCompanionInMemory = errors.New("hazedb: companion must be an on-disk file, not in-memory")
	ErrClosed            = errors.New("hazedb: database is closed")
	ErrMirrorSchema      = errors.New("hazedb: mirror schema mismatch; migration required")
	ErrMirrorCorrupt     = errors.New("hazedb: SQLite mirror corrupted")
)

Functions

func ArgsFromJSON added in v0.1.4

func ArgsFromJSON(raw []byte) ([]any, error)

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

func DeregisterDBIf(name string, db *DB)

DeregisterDBIf clears the slot only if it still holds db (CAS). Safe for caddymodule Cleanup during reload: preserves a newer Provision's registration.

func ErrorJSON added in v0.1.4

func ErrorJSON(msg string) []byte

ErrorJSON renders an error as {"error":"msg"}.

func ExecResultJSON added in v0.1.4

func ExecResultJSON(affected int) []byte

ExecResultJSON renders a write result as {"affected":n}. Hand-appended — the shape is fixed, so the reflection-based json.Marshal it used was pure overhead on every HTTP write response.

func LookupDBSlot added in v0.1.4

func LookupDBSlot(name string) *atomic.Pointer[DB]

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

func QueryArgs(s string) ([]any, error)

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

func RegisterDB(name string, db *DB)

RegisterDB publishes db under name. Pass nil to clear unconditionally; from Cleanup-style paths use DeregisterDBIf to avoid clobbering a newer registration.

func RowToJSONObject added in v0.1.43

func RowToJSONObject(cols []string, row Row) ([]byte, error)

RowToJSONObject renders ONE row as a single JSON object {"col":val,...} — the shape a PK / single-row read returns (vs RowsToJSONObjects' array). Never errors (kept for API symmetry).

func RowsToJSON added in v0.1.4

func RowsToJSON(cols []string, rows []Row) ([]byte, error)

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

func RowsToJSONObjects(cols []string, rows []Row) ([]byte, error)

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). The *Values (pre-typed args) and *JSON* (encode-in-place) methods are in-process fast-path variants of the same verbs, upholding the same guarantees. The verbs live in db_exec.go (writes) and db_query.go (reads); WAL/recovery replay is in db_replay.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.

Caller obligation — parameterize. Pass values as ? placeholders, never spliced into the SQL text. prepare caches one compiled plan per unique SQL string and keeps it for the process lifetime; a parameterized statement set is finite so the cache stays bounded, but interpolating values mints a new key per call and the cache grows without bound in a long-lived process.

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

func LookupDB(name string) *DB

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

func Open(opts Options) (*DB, error)

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

func (db *DB) Close() error

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 and the compaction sweeper, then flushes and closes the WAL, then closes the mirror. Memory-only DBs still stop the background loops. The drain loop is stopped first, while the WAL is still open. Close marks the DB closed before tearing anything down, so every public verb then fails with ErrClosed; a second Close is a no-op.

func (*DB) Exec

func (db *DB) Exec(sql string, args ...any) (int, error)

Exec runs an INSERT, UPDATE, DELETE, CREATE TABLE, or DROP TABLE. Returns the affected row count (0 for DDL).

func (*DB) ExecScript added in v0.1.68

func (db *DB) ExecScript(script string) (int, error)

ExecScript runs a TRUSTED, operator-supplied multi-statement script — a boot / migration / seed .sql file. It splits on top-level ';' with the lexer, so a ';' inside a string literal does NOT split the script, and runs each statement in order. Unlike Exec it permits inline value literals: a seed file writes constant data and has no ? args to bind. For that reason it must NEVER be fed request or otherwise untrusted input — that reopens the SQL-injection path Exec closes. Statements are not cached (one-shot boot work), so an unchecked literal plan can never reach a later Exec through a cache hit. Returns the summed affected-row count; a failing statement stops the script and is named in the error.

func (*DB) ExecValues added in v0.1.4

func (db *DB) ExecValues(sql string, args ...Value) (int, error)

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

func (db *DB) FlushWAL() error

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) MetaJSON added in v0.1.53

func (db *DB) MetaJSON() []byte

MetaJSON renders MetaSnapshot as a JSON object for the out-of-core adapters (Caddy GET /meta, PHP hazedb_meta) — one wire-shape definition both share, so the HTTP and cgo surfaces always emit identical bytes. Cold operator path, so it uses stdlib json.Marshal (like ExecResultJSON/ErrorJSON) rather than the hand-rolled row encoder. Never errors — the struct is plain data.

func (*DB) MetaSnapshot added in v0.1.34

func (db *DB) MetaSnapshot() StoreMeta

MetaSnapshot returns the current StoreMeta. It loads the catalog atomically (lock-free) and reads each table with one brief per-shard RLock sweep. Stats are sorted by table name for a stable overview.

func (*DB) Prepare added in v0.1.4

func (db *DB) Prepare(sql string) (*Stmt, error)

Prepare compiles sql into a reusable statement bound to the current catalog.

func (*DB) Query

func (db *DB) Query(sql string, args ...any) ([]string, []Row, error)

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) QueryEach added in v0.1.14

func (db *DB) QueryEach(sql string, args []Value, visit func(cols []string, row Row) bool) error

QueryEach runs a SELECT and calls visit(cols, row) once per result row, in order. It is the low-allocation counterpart to QueryValues for consumers that serialize each row immediately (e.g. building a PHP array). visit returns false to stop early. cols is the same slice on every call.

The row obeys the streaming memory-safety contract above: valid only during the call; copy out anything kept. Use QueryValues when you need owned rows.

func (*DB) QueryJSON added in v0.1.14

func (db *DB) QueryJSON(sql string, args ...Value) ([]string, []byte, error)

QueryJSON runs a SELECT and returns the result as a JSON array of objects ([{"col":val,...},...]) — the same shape as RowsToJSONObjects, but encoded straight from the live rows into one buffer, with no intermediate []Row and no per-row clone. The returned bytes are the caller's to keep or copy. For a read-and-forward path (an HTTP/JSON response) this avoids materializing rows only to re-encode and discard them.

func (*DB) QueryJSONInto added in v0.1.16

func (db *DB) QueryJSONInto(dst []byte, sql string, args ...Value) ([]string, []byte, error)

QueryJSONInto is QueryJSON that appends into the caller-supplied buffer dst, returning the columns and the grown slice. Pass dst[:0] to reuse a pooled scratch buffer: a hot read-and-forward caller (the PHP/HTTP fetchall_json path) that keeps one buffer per worker and copies the result out immediately pays no per-call allocation for the JSON envelope — only the amortised growth to the high-water mark. The returned slice may share dst's backing array.

func (*DB) QueryRow added in v0.1.2

func (db *DB) QueryRow(sql string, args ...any) ([]string, Row, error)

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) QueryRowJSONByIndex added in v0.1.44

func (db *DB) QueryRowJSONByIndex(dst []byte, sql string, key Value) (out []byte, found bool, err error)

QueryRowJSONByIndex is QueryRowJSONByPK for a single-indexed-equality point lookup: WHERE <indexed> = ? LIMIT 1 (no ORDER BY / OFFSET). It resolves the candidates through the secondary index, re-checks the full WHERE on each live candidate, and appends the first match as a flat JSON object into dst UNDER the shard read lock (no Row clone). key is the typed lookup value (no arg boxing); dst is caller-owned and reused, so a steady-state hit allocates only the index-bucket copy. Returns the grown buffer and whether a row matched.

func (*DB) QueryRowJSONByPK added in v0.1.43

func (db *DB) QueryRowJSONByPK(dst []byte, sql string, id UUID) (out []byte, found bool, err error)

QueryRowJSONByPK looks up a PK-pinned SELECT and appends the single matching row as a flat JSON object {"col":val,...} into dst, encoding the cells UNDER the shard read lock straight from the live row (no Row clone) with a typed id (no string→any boxing). dst is caller-owned and reused across calls, so a steady-state call allocates nothing. Returns the grown buffer and whether a row matched. The allocation-free read lane for an in-process JSON consumer (the Caddy GET handler); requires WHERE id = ? — a non-PK-pinned SELECT is rejected, like QueryRowByPK.

func (*DB) QueryRowValues added in v0.1.4

func (db *DB) QueryRowValues(sql string, args ...Value) ([]string, Row, error)

QueryRowValues is QueryRow with pre-typed args (see QueryValues).

func (*DB) QueryValues added in v0.1.4

func (db *DB) QueryValues(sql string, args ...Value) ([]string, []Row, error)

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.

func (*DB) Transaction

func (db *DB) Transaction(fn func(*Tx) error) error

Transaction runs fn as a transaction. Returning nil from fn commits all staged statements atomically; returning an error (or any tx.Exec having failed) discards everything — nothing is written to the store or the WAL.

type IndexDef added in v0.1.5

type IndexDef struct {
	Name    string // optional; auto-derived as "idx_<col>[_<col>...]" when empty
	Columns []string
	// 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. Composite indexes are always Ordered.
	Ordered bool
}

IndexDef declares a secondary index on one or more non-PK columns. Maintained asynchronously and read through a hybrid path; see docs/secondary-indexes.md. A composite (multi-column) index must be Ordered (see parseIndexClause).

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

	// WALPath is the directory holding the write-ahead log segments, created if
	// absent. Setting it turns the WAL ON; leaving it empty is memory-only
	// (nothing survives a restart). The WAL has one durability story: a write is
	// sealed to disk within ~0.5s (or sooner once 1 MiB has accumulated), so a
	// crash loses at most that window. Acknowledge-after-fsync is intentionally
	// not offered — use a disk-first database if you need it.
	WALPath string

	// CompanionPath is where the SQLite companion file lives — always a real file on
	// disk, present in EVERY mode (WAL on or off). It is hazedb's always-on store:
	// the _hz_events operational log (logging, health, later perf samples) and,
	// when WAL is on, the data mirror + recovery base. Set it to pin hazedb.db at
	// one fixed path regardless of WAL. Empty defaults to "hazedb.db" inside WALPath
	// when WAL is on, else "hazedb.db" in the working directory. Never in-memory.
	CompanionPath string

	// MaxBytes caps the store's approximate in-RAM size (the sum of every table's
	// byte tally, the same figure MetaSnapshot reports). An INSERT that would push
	// the total past it is rejected with ErrCapacity; the store never auto-evicts,
	// so the caller frees space with DELETE / DROP TABLE. 0 (the default) is
	// unlimited and adds no write-path cost.
	MaxBytes int64
	// 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, flush triggers, SQLite PRAGMAs, segment naming, shard counts — are not settings and live next to the code that uses them.

type Row

type Row []Value

Row is a single tuple. Ordinals match the table's column order.

func (Row) Clone

func (r Row) Clone() Row

Clone returns a deep copy of the row. Needed when the executor returns rows that the caller may mutate while the underlying storage lock is no longer held.

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

func (s *Stmt) Columns() []string

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) Exec added in v0.1.4

func (s *Stmt) Exec(args ...any) (int, error)

Exec runs a prepared INSERT/UPDATE/DELETE/DDL. See DB.Exec.

func (*Stmt) Query added in v0.1.4

func (s *Stmt) Query(args ...any) ([]string, []Row, error)

Query runs a prepared SELECT. See DB.Query.

func (*Stmt) QueryRow added in v0.1.4

func (s *Stmt) QueryRow(args ...any) ([]string, Row, error)

QueryRow runs a prepared single-row SELECT. See DB.QueryRow.

func (*Stmt) QueryRowByIndex added in v0.1.32

func (s *Stmt) QueryRowByIndex(key Value, dst []Value) (out []Value, found bool, err error)

QueryRowByIndex is the point-lookup fast path for a single secondary-index equality: the statement must pin exactly one indexed column with an explicit single-row cap — WHERE <indexed> = ? LIMIT 1 — and no ORDER BY or OFFSET. It returns the first live row that passes the full WHERE and stops. The match is ordering-undefined by design: this serves unique-key lookups (fetch the account row for an email at login) and existence checks (does this email already exist?), where "any one match" is the answer.

It cannot return the newest/highest row — there is no ordering on this path. For that, write ORDER BY <ordered-indexed col> DESC LIMIT 1, which compiles to the ordered-index walk and is rejected here (orderOrdinal >= 0). The mandatory LIMIT 1 keeps that distinction explicit at the call site.

key is the typed value for the one parameter (no interface boxing); the matching row's projection is written into dst, grown only if too small — reuse it across calls. found reports whether a row matched. In steady state it allocates only the index-bucket copy (~one small slice); the projection scan and the empty dirty overlay add none. The full WHERE is re-checked on each live candidate (staleness + any residual), so a statement needing a second parameter returns an error rather than a wrong row.

func (*Stmt) QueryRowByPK added in v0.1.4

func (s *Stmt) QueryRowByPK(pk UUID, dst []Value) (out []Value, found bool, err error)

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 StoreMeta added in v0.1.34

type StoreMeta struct {
	Tables int `json:"tables"`
	// MaxBytes is the configured byte cap (Options.MaxBytes); 0 means unlimited.
	// TotalApproxBytes is what it is measured against — an insert is rejected once
	// the total would exceed it.
	MaxBytes int64 `json:"max_bytes"`
	// TotalRows / TotalApproxBytes / TotalTombstones roll up every table's line, so
	// a caller gets the store-wide figures without summing TableStats itself.
	// TotalApproxBytes is the sum of the per-table estimates and inherits their
	// slight over-bias.
	TotalRows        int         `json:"total_rows"`
	TotalApproxBytes int64       `json:"total_approx_bytes"`
	TotalTombstones  int         `json:"total_tombstones"`
	TableStats       []TableStat `json:"table_stats"`
}

MetaSnapshot / StoreMeta give a lock-light overview of what the store holds: the table count plus, per table, row count, column count, secondary-index count, and an in-RAM size. For operator dashboards and health checks — a read path, never the write path.

The size walks every live row under a brief per-shard RLock and sums each cell's exact footprint, so the payload term — the part that varies — is exact, not sampled. It adds the fixed per-row overhead and a flat per-row charge for each secondary index, both modeled constants biased slightly high. Cost is O(live rows): right for an occasional dashboard hit, not for a per-write budget check — a future byte cap maintains its own O(1) counter on the write path rather than calling this.

type TableDef

type TableDef struct {
	Name    string
	Columns []ColumnDef
	Indexes []IndexDef
}

TableDef defines a table. Exactly one column must have PK=true. Multi-column PK is v1.1+.

type TableStat added in v0.1.34

type TableStat struct {
	Name        string `json:"name"`
	Rows        int    `json:"rows"`
	Columns     int    `json:"columns"`
	Indexes     int    `json:"indexes"`
	ApproxBytes int64  `json:"approx_bytes"`
	Tombstones  int    `json:"tombstones"`
}

TableStat is one table's line in a StoreMeta. ApproxBytes sums exact cell payloads plus modeled overhead (see StoreMeta); a display layer formats it to MiB.

Tombstones is the count of dead arena slots — rows deleted but whose arena slot is not yet reclaimed. It is a gauge, not a leak: the background compaction sweeper (compact.go) reclaims dense shards, so it rises with deletes and falls as the sweeper runs. (Partitioned scan cost is independent — the delete path prunes a partition's scan list, so scanPartition stays O(live) regardless.) A momentarily high Tombstones/(Rows+Tombstones) fraction is normal between sweeps; a persistently high one means deletes outrun the sweeper's interval.

type Tx

type Tx struct {
	// contains filtered or unexported fields
}

Tx is the transaction handle passed to the db.Transaction closure.

func (*Tx) Exec

func (tx *Tx) Exec(sql string, args ...any) (int, error)

Exec stages one INSERT/UPDATE/DELETE. It does not touch the live store. The returned count is the optimistic affected-row count (1); the real effect is decided at commit. A poisoned transaction no-ops and returns the sticky error.

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 key with no allocation — the primary key index is the open-addressed pkMap (pkmap.go) keyed by UUID.

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"). The counter caps at 4096 IDs per ms (≈4.1M auto-PKs/sec); beyond that the timestamp is nudged forward to keep the ordering total, so under sustained bulk-load above that rate the stamp drifts ahead of the wall clock (it self-corrects once the rate drops; uniqueness and ordering always hold).

Auto-PK inserts (id omitted) are the only callers. The stamp advances by a single lock-free CAS, so parallel inserts never queue behind a generator mutex; rand_b is copied from one of uuidRandShards independently locked buffers. Client-supplied UUIDs skip this path entirely — no monotonicity guarantee, but also no shared state touched.

func ParseUUID

func ParseUUID(s string) (UUID, error)

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.

Hand-decoded via a nibble lookup table in one pass — no per-segment hex.Decode call and no string→[]byte conversion (a hot read-path cost since every string PK arg lands here).

func ParseUUIDOk added in v0.1.59

func ParseUUIDOk(s string) (UUID, bool)

ParseUUIDOk is ParseUUID without the error: it returns ok=false instead of formatting an error. The text adapters guess UUID-ness by trying to parse every string arg (the WHERE email=? case parses-and-fails on the hot path), so the discarded error string in ParseUUID was an allocation per non-UUID arg. Callers that only branch on success use this; callers that surface the error (DDL/coercion) use ParseUUID.

func (UUID) AppendString added in v0.1.44

func (u UUID) AppendString(b []byte) []byte

AppendString appends u's canonical 8-4-4-4-12 lowercase hex form to b and returns the extended slice — the allocation-free counterpart of String for hot encoders that pack into a reused buffer (the PHP row builder, JSON output), where String's per-call result string is pure waste.

func (UUID) IsV7

func (u UUID) IsV7() bool

IsV7 reports whether u has the v7 version nibble and the RFC 4122/9562 variant bits (10xx). Used to validate client-supplied IDs.

func (UUID) IsZero

func (u UUID) IsZero() bool

IsZero reports whether u is the zero UUID.

func (UUID) String

func (u UUID) String() string

String renders the canonical 8-4-4-4-12 lowercase hex form.

func (UUID) Version

func (u UUID) Version() byte

Version returns the 4-bit version field (7 for a UUIDv7).

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 Bool

func Bool(v bool) Value

func Bytes

func Bytes(v []byte) Value

func Int

func Int(v int64) Value

func Null

func Null() Value

func Str

func Str(v string) Value

func UUIDVal

func UUIDVal(u UUID) Value

func (Value) AsInt

func (v Value) AsInt() (int64, error)

AsInt returns the value as int64, coercing strings via strconv. Used for ORDER BY / range comparisons.

func (Value) AsString

func (v Value) AsString() string

AsString returns the value formatted as text. Used for PK keys in the map index and for display.

func (Value) Bool added in v0.1.2

func (v Value) Bool() bool

func (Value) Bytes added in v0.1.2

func (v Value) Bytes() []byte

func (Value) Compare

func (v Value) Compare(o Value) (int, bool)

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.

func (Value) Equal

func (v Value) Equal(o Value) bool

Equal returns true when v and o represent the same value, including null-equals-null (SQL semantics differ — null != null in WHERE — but the executor checks IsNull explicitly).

func (Value) Int added in v0.1.2

func (v Value) Int() int64

func (Value) IsNull

func (v Value) IsNull() bool

func (Value) Str added in v0.1.2

func (v Value) Str() string

func (Value) UUID added in v0.1.2

func (v Value) UUID() UUID

type ValueKind

type ValueKind uint8

ValueKind discriminates the union below.

const (
	KindNull ValueKind = iota
	KindInt
	KindString
	KindBytes
	KindBool
	KindUUID
)

Directories

Path Synopsis
addons
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.

Jump to

Keyboard shortcuts

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