Documentation
¶
Overview ¶
Package datastore is the analytics plane: a client for Hanzo Datastore, the columnar warehouse behind the usage and observability ledgers.
One store, not one per tenant. Rows carry the tenant as a column and as the leading sort key, so a single-tenant read is a bound predicate over a shared table and a fleet-wide aggregate is the same table with that predicate left off. This is the opposite of the relational plane in orm/db, where a tenant IS a database file resolved through db.Registry. The two planes share no connection and are configured independently; keeping them apart is what lets an aggregate read span every tenant while an entity read cannot leave one.
The surface is Exec for statements and Query for reads. Analytics SQL is written by hand — aggregates, windows and engine-specific DDL have no useful typed builder — so this plane maps no records: orm/db models entities, this carries measurements.
Index ¶
Constants ¶
This section is empty.
Variables ¶
ErrUnavailable is returned by Exec and Query when there is no live connection: the plane is unconfigured, still connecting, or closed. Callers gate on Ready to report an honest gap instead of reaching this.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// Addr is host:port of the warehouse's native protocol port. Empty
// disables the plane — see Open.
Addr string
// Database holds the ledger tables. Defaults to "hanzo". It must be a bare
// identifier: CREATE DATABASE takes no bound parameters, so this value
// reaches the wire by concatenation.
Database string
// User and Password authenticate the connection. User defaults to
// "default".
User string
Password string
// DialTimeout bounds one connection attempt. Defaults to 5s. It does not
// bound queries — Exec and Query are governed by the caller's context, so
// a deadline lives in exactly one place.
DialTimeout time.Duration
// Log receives connection lifecycle events. Defaults to slog.Default().
Log *slog.Logger
}
Config addresses one warehouse.
func Env ¶
func Env() Config
Env reads a Config from the process environment:
DATASTORE_ADDR host:port of the warehouse's native port; unset disables the plane DATASTORE_DB database (default "hanzo") DATASTORE_USER user (default "default") DATASTORE_PASSWORD password
Open never reads the environment itself, so a caller that configures the plane another way — a test, a job, a second warehouse — builds a Config and this function stays out of it.
type Conn ¶
type Conn struct {
// contains filtered or unexported fields
}
Conn is a client for one warehouse. It is safe for concurrent use, and its zero value behaves like a disabled client, so a nil *Conn is a usable "no datastore here" rather than a panic.
func Open ¶
Open returns a client for the configured warehouse. It never blocks and never fails: the connection is established in the background and Ready reports when it is usable. A warehouse that is down at boot therefore leaves the ledgers unavailable instead of taking down the process that owns them.
An empty Addr yields a client that is never ready. That is the disabled state, and it is deliberately the same state as "not connected yet": callers already gate on Ready and report an honest gap, so being unconfigured needs no second code path. A Database that is not a bare identifier disables the client the same way, since it cannot be sent safely.
func (*Conn) Close ¶
Close releases the connection and ends connecting. It is idempotent, and Exec and Query return ErrUnavailable afterwards.
func (*Conn) Exec ¶
Exec runs a statement — DDL, or an insert. Placeholders bind positionally from args.
func (*Conn) Query ¶
Query runs a read and materialises the result, decoding each column into its native scan type (uint64, string, time.Time, float64, ...) keyed by column name. Placeholders bind positionally from args and are never interpolated into the statement, so a tenant predicate holds whatever the tenant value contains.
The result is fully read before returning: analytics reads are aggregates, small in rows and wide in the work behind them, so there is no cursor to manage and no open rows to leak.