GizStack
View .mdOpen in ChatGPTOpen in Claude

gizdb:store

the database a web app keeps its records in, one call shape over three engines. GizDB is the default: a folder, whose engine this process serves (other processes, a worker or the platform, reach it at the folder's unix socket with its password). DATABASE_URL selects another: PostgreSQL (a server; each worker process holds a pool) or a SQLite file (embedded; every worker process opens it). Apps, the feed and the model write one SQL for all (ON CONFLICT, RETURNING, $N, TRUE and FALSE); what differs, DDL types and locks, is chosen here or by the SQL Dialect (db/sql/types/dialect.giz). Typed records (db/data.giz) use the same GizDB engine through db/access.giz.

gizlang
import * as Store from "gizdb:store";

Types

Engine

gizlang
type Engine = { endpoint: Access.Endpoint; identity: string; secret: bytes; }

The GizDB engine this process serves for a folder: the endpoint typed connections reach it at from any thread (Data.of).

  • endpoint: M47.Endpoint
  • identity: string
  • secret: bytes

Served

gizlang
type Served = { engine: Engine; server: Task.Task<Result<i64, string>>; }

A GizDB engine of this process: how it is reached, and its server task.

  • engine: M51.Engine
  • server: gizlang_task.Task<_gizlang_sdk_core.Result<i64, string>>

Folder

gizlang
type Folder = { pool: Pg.Pool; served: Option<Served>; }

A GizDB folder open in this process: its pool, and its engine when this process serves it.

  • pool: gizlang_postgres.Pool
  • served: _gizlang_sdk_core.Option<M51.Served>

Database

gizlang
type Database = | { kind: "gizdb"; folder: Ref<Folder>; } | { kind: "sqlite"; db: Sqlite.Database; } | { kind: "postgres"; pool: Pg.Pool; } | { kind: "session"; connection: Pg.Connection; }

A database: a GizDB folder (its pool, and its engine when this process serves it), a SQLite file, a PostgreSQL pool, or inside a transaction the one PostgreSQL connection it runs on.

  • gizdb
  • sqlite
  • postgres
  • session

Rows

gizlang
type Rows = { rows: Vec<Vec<Option<string>>>; affected: i64; }

A statement's rows, each column as text and none for NULL, and the rows an INSERT, UPDATE or DELETE without RETURNING changed.

  • rows: Vec<Vec<_gizlang_sdk_core.Option<string>>>
  • affected: i64

StoreError

gizlang
type StoreError = { sqlstate: Option<string>; message: string; detail: string; position: Option<i64>; code: i64; }

A store failure. PostgreSQL fills sqlstate, detail and position from the server's error; SQLite has none of them. code is the engine's own number (the SQLite extended result code, a transport errno), else 0.

  • sqlstate: _gizlang_sdk_core.Option<string>
  • message: string
  • detail: string
  • position: _gizlang_sdk_core.Option<i64>
  • code: i64

Selected

gizlang
type Selected = { columns: Vec<string>; rows: Vec<Vec<Option<string>>>; }

A query's result with its column names: for a reader of any result (the Giz Data console).

  • columns: Vec<string>
  • rows: Vec<Vec<_gizlang_sdk_core.Option<string>>>

Moved

gizlang
type Moved = { same: bool; lines: Vec<string>; }

What a move found: same when every table is equal, and a line for each table.

  • same: bool
  • lines: Vec<string>

Functions

flag

gizlang
function flag(text: string): bool

A boolean column of Rows: SQLite spells true "1", PostgreSQL "t".

storeError

gizlang
function storeError(message: string): StoreError

A storage error with MESSAGE only.

integer

gizlang
function integer(text: Option<string>): Result<i64, StoreError>

An integer column of Rows (decimal in both engines). NULL, or text that is not an i64, is an error, never a silent 0.

Effects: fail.

open

gizlang
function open(sys: System, location: string): Result<Database, StoreError>

location is postgres://USER:PASSWORD@HOST:PORT/DATABASE (no percent escapes; as libpq, a trailing ?host=/FOLDER names the folder of the server's unix-domain socket instead of HOST, which may then be empty), a SQLite file (a path that ends in .db, or :memory:), else a GizDB folder (made when missing; see gizdb). STORE_QUOTA, when set, is the most bytes a GizDB or SQLite store holds: past it a write fails and reads go on. A SQLite file is opened as each worker process (GIZLANG_WORKERS) needs it: WAL, so readers never wait for the writer; a writer that finds another worker's write in progress waits up to 5 s for it instead of failing; commits sync at checkpoints (synchronous NORMAL: a power loss may drop the last commits, never corrupt).

Effects: fail, read, write, console, io.

gizdbLocation

gizlang
function gizdbLocation(sys: System, folder: string, password: string): string

The postgres:// location of the GizDB server of folder (absolute) with password.

Effects: fail, io.

gizdbSocket

gizlang
function gizdbSocket(sys: System, folder: string): string

The path to listen and connect at for the socket folder GIZDB_RUN of folder (absolute).

Effects: fail, io.

socketFolder

gizlang
function socketFolder(sys: System, folder: string): string

The path of folder (absolute) for its unix-domain sockets (GIZDB_RUN, a hosted app's platform socket). The socket files are always in the folder (a hosted app reaches them there), but a socket path may be at most 107 bytes. For a deep folder this is /tmp/giz-HASH (the folder's SHA-256), a link to it that this makes.

Effects: fail, io.

serving

gizlang
function serving(sys: System, folder: string): bool

Whether a process serves the GizDB folder folder now.

Effects: fail, io.

identityColumn

gizlang
function identityColumn(dialect: Dialect, name: string): string

The DDL of column name: an i64 primary key that the database numbers upward on each INSERT and never hands out again.

Effects: fail.

move

gizlang
function move(src: Database, dst: Database, verify: bool): Result<Moved, StoreError>

Move the SQLite database src into dst, which has none of its tables, and prove the copy; with verify, only compare an earlier copy.

Effects: fail, read, write, console, io.

conflictError

gizlang
function conflictError(message: string): StoreError

A failed guard (a write that had to change an exact number of rows): a serialization conflict, as the transaction's caller reads it.

stored

gizlang
function stored<T>(r: Result<T, StoreError>): Result<T, string>

A store result whose failure is the text of its error.

migrate

gizlang
function migrate(db: Database, shape: Model.Shape): Result<Rows, StoreError>

Bring the table up to the shape, additively: create it, add each missing column (a required one with an empty, zero or false default for the rows already there) and the indexes. A column the shape no longer has keeps its data: dropping or converting is an explicit, separate step. One transaction under the table's lock, so worker processes starting together migrate one after another (PostgreSQL's CREATE TABLE IF NOT EXISTS is not safe to race). The result's affected counts the statements that changed the schema.

Effects: fail, read, write, console, io.

migrateLocked

gizlang
function migrateLocked(tx: Database, shape: Model.Shape): Result<Rows, StoreError>

Within the caller's locked transaction: @was, additive columns, then indexes.

Effects: fail, read, write, console, io.

records

gizlang
function records<T>(db: Database, tail: string, params: Vec<Option<string>>): Result<Vec<T>, StoreError>

The records of the stored type T that tail (the SQL after "FROM TABLE", such as "WHERE org = $1 ORDER BY created") finds with params. A Gate-checked read is Data.find; one answers a 404 when there is none.

recordsOf

gizlang
function recordsOf<T>(found: Rows): Vec<T>

The records T of rows whose columns are columns(shape<T>()), such as an UPDATE ... RETURNING answer.

record

gizlang
function record<T>(db: Database, tail: string, params: Vec<Option<string>>): Result<Option<T>, StoreError>

The first record that records finds, if any.

one

gizlang
function one<T>(db: Database, tail: string, params: Vec<Option<string>>, message: string): Result<T, Model.Rejection>

The record that record finds, or a 404 with message (as Data.one).

insert

gizlang
function insert<T>(db: Database, shape: Model.Shape, record: T): Result<Rows, StoreError>

Insert one record of a stored Shape into a SQL store (or inside its transaction). A Gate-checked write is Data.insert.

Methods

Database.dialect

gizlang
function Database.dialect(self: Database): Dialect

The SQL a Database speaks.

Effects: fail.

Database.query

gizlang
function Database.query(self: Database, sql: string, params: Vec<Option<string>>): Result<Rows, StoreError>

One statement with parameters $1.. (none for NULL).

Effects: fail, read, write, console, io.

Database.select

gizlang
function Database.select(self: Database, sql: string, params: Vec<Option<string>>): Result<Selected, StoreError>

One statement with parameters $1.., its rows with their column names.

Effects: fail, read, write, console, io.

Database.exec

gizlang
function Database.exec(self: Database, sql: string): Result<bool, StoreError>

A script of statements without parameters or results.

Effects: fail, read, write, console, io.

Database.transaction

gizlang
function Database.transaction<F>(self: Database, work: F): ReturnType<F>

Runs an application callback once. Commit conflicts are returned with their SQLSTATE; an application effect is never implicitly replayed.

Database.lock

gizlang
function Database.lock(self: Database, name: string): Result<bool, StoreError>

Inside a transaction: the lock named name until it ends, so transactions taking the same name run one after another while readers go on reading (the name need not be a table yet). SQLite's writers already run one after another; PostgreSQL takes a transaction advisory lock on the name (GizDB checks it at commit: see Database.transaction).

Effects: fail, read, write, io.

Database.columns

gizlang
function Database.columns(self: Database, table: string): Result<Vec<string>, StoreError>

The names of a table's columns; none when it does not exist.

Effects: fail, read, write, console, io.

Database.tables

gizlang
function Database.tables(self: Database): Result<Vec<string>, StoreError>

The names of the tables in the current schema, in order.

Effects: fail, read, write, console, io.

Database.engine

gizlang
function Database.engine(self: Database): Option<Engine>

The GizDB engine this process serves for the database, which typed records reach from any thread (db/data.giz of); none for another engine or a folder that another process serves.

Effects: fail, read.

Database.close

gizlang
function Database.close(self: Database): void

Close the database (a pool waits for its lent connections to come back; the GizDB engine this process serves stops after it).

Effects: fail, read, write, io.

StoreError.conflicting

gizlang
function StoreError.conflicting(self: StoreError): bool

True when the write lost to another or broke a key or constraint: PostgreSQL class 23 (integrity) or 40 (rollback), SQLite's constraint code.

Effects: fail.

StoreError.text

gizlang
function StoreError.text(self: StoreError): string

The failure as one line: its message, then its detail when it has one.

Effects: fail.

StoreError.rejection

gizlang
function StoreError.rejection(self: StoreError): Model.Rejection

The error as a rejection that a request answers: status 500 with the message.

Effects: fail.

Constants

GIZDB_FILES

gizlang
const GIZDB_FILES = ".gizdb"

The engine's files.

GIZDB_RUN

gizlang
const GIZDB_RUN = ".gizdb.run"

The folder of its one way in, the unix socket .s.PGSQL.5432 (libpq's name); no TCP port is open.

GIZDB_PASSWORD

gizlang
const GIZDB_PASSWORD = ".gizdb.pw"

Its password: gizdb without one would accept every client.

GIZDB_MOVED

gizlang
const GIZDB_MOVED = ".gizdb.moved"

The record that its first start moved the folder's SQLite file in, or found none.