Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions deps/db/sql-sync.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package gcore:db@0.1.0;

/// FastEdge SQL interface (synchronous variant).
///
/// The contract is PostgreSQL: dialect, `$1`-style positional parameters and SQLSTATE error codes.
/// Applications name a binding (e.g. "main"); the host maps it to a backend. Backend, location and
/// credentials are invisible to the application, so a binding can move between backends without
/// code changes.
///
/// Every call is a network round trip to a regional database. There is no session state between
/// calls: each `query` auto-commits and each `batch` is its own transaction.
///
/// Calls are subject to host limits (timeout, response size, statements per batch). Exceeding
/// them returns `error::timeout` or `error::limit`.
interface sql-sync {
use sql-types.{value, query-result, statement, error};

/// FastEdge SQL database resource.
resource database {
/// Open the database bound to `name`.
///
/// `error::no-such-database` will be raised if the `name` is not recognized.
open: static func(name: string) -> result<database, error>;

/// Run one statement with positional parameters (`$1`, `$2`, ...). Auto-commits.
///
/// `sql` must contain exactly one statement.
query: func(sql: string, params: list<value>) -> result<query-result, error>;

/// Run several statements in one transaction. Either all commit or none do.
///
/// Returns one `query-result` per statement, in order. On failure nothing is committed
/// and the error of the failing statement is returned.
batch: func(statements: list<statement>) -> result<list<query-result>, error>;
}
}
88 changes: 88 additions & 0 deletions deps/db/sql-types.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package gcore:db@0.1.0;

/// Shared types for the SQL interfaces.
interface sql-types {
/// A parameter or result value.
///
/// Results use the narrowest variant that is lossless:
/// - `int2`, `int4` → `int32`
/// - `int8` → `int64`
/// - `float4`, `float8` → `float64`
/// - `numeric` → `numeric` (decimal text, e.g. "12345678901234567890.123")
/// - `bool` → `boolean`
/// - `json`, `jsonb` → `json` (JSON text)
/// - `bytea` → `bytes`
/// - everything else (text, varchar, uuid, date/time types, arrays, ...) → `text`, in PostgreSQL
/// text format. `timestamptz` is rendered in UTC. Arrays are PostgreSQL array literals
/// (e.g. "{1,2,3}"). Use `column.type-oid` to interpret them.
/// - SQL `NULL` → `null`
///
/// Parameters are sent to the database as text; PostgreSQL casts them to the target type.
variant value {
null,
boolean(bool),
int32(s32),
int64(s64),
float64(f64),
numeric(string),
text(string),
bytes(list<u8>),
json(string),
}

/// A result column.
record column {
/// Column name or alias.
name: string,
/// PostgreSQL type OID (e.g. 23 = int4, 1184 = timestamptz, 1007 = int4[]).
type-oid: u32,
}

/// The result of one statement.
record query-result {
columns: list<column>,
/// Rows in column order. Empty for statements that return no rows.
rows: list<list<value>>,
/// Rows returned (SELECT) or affected (INSERT/UPDATE/DELETE).
row-count: u64,
}

/// One statement in a batch.
record statement {
sql: string,
params: list<value>,
}

/// Error details reported by the database.
record db-error {
/// SQLSTATE code (e.g. "23505" unique_violation), when the database provides one.
code: option<string>,
message: string,
detail: option<string>,
hint: option<string>,
/// Name of the violated constraint, for constraint errors.
constraint: option<string>,
}

/// The set of errors which may be raised by the SQL interfaces.
variant error {
/// The host does not recognize the database binding requested.
no-such-database,
/// The requesting component does not have access to the specified database
/// (which may or may not exist).
access-denied,
/// The binding is misconfigured: bad credentials, unknown database name, invalid connection
/// settings. Not retryable without a configuration change.
config(db-error),
/// The database could not be reached (network, DNS, TLS, backend unavailable).
connection(string),
/// The call exceeded the host's time limit. The statement may or may not have been applied.
timeout,
/// A host or backend limit was exceeded (e.g. response size, statements per batch).
limit(string),
/// The database rejected the statement (syntax, constraint, permission, ...).
query(db-error),
/// Some unexpected internal error has occurred.
internal-error,
}
}
1 change: 1 addition & 0 deletions world.wit
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ world reactor {
import key-value;
import utils;
import cache-sync;
import gcore:db/sql-sync@0.1.0;

export http-handler;
}