SQL and psql
UltraFabric speaks the PostgreSQL protocol through a gateway, ufgw. You log in with a token, and every statement becomes a plan that the data node verifies before it runs.
This is a first, deliberately small SQL subset. There is no public build or hosted endpoint yet. This page describes what runs in the repository's end-to-end tests today. Early access teams will get the first hosted databases.
How a connection works
- The client connects to
ufgwand sends a token as its password. - The gateway verifies the token: RS256 signature against a configured key set, issuer, audience
ultrafabric, and expiry. A token signed with any other key is refused with28P01. - The gateway opens a connection to the data node,
ufd, over its local socket. The node establishes the requester's identity itself; it does not take the gateway's word for it. - Each statement is parsed by PostgreSQL's own parser, bound against the catalog and turned into a plan. The node's verifier refuses any plan that could read outside your tenant or your access policy.
Tokens travel in clear text inside the PostgreSQL password message, so the gateway accepts them only over TLS or from the same machine. Without a certificate it refuses to listen on anything but a loopback address. ufd itself never listens on the network.
A session
$ PGPASSWORD=$TOKEN psql -h 127.0.0.1 -p 45432 -U app -d uf
uf=> CREATE TABLE orders (id bigint PRIMARY KEY, cust int NOT NULL,
status text, amount numeric(12,2), placed date);
uf=> INSERT INTO orders VALUES (1, 7, 'paid', 120.50, '2024-03-02');
uf=> SELECT status, count(*), sum(amount) FROM orders GROUP BY status ORDER BY status;
status | count | sum
--------+-------+-------------
open | 3310 | 16454322.57
paid | 3324 | 16582184.52
void | 3366 | 16970803.94
(3 rows)Tenants
Every query reads only your tenant's rows; there is no tenant column to select, filter on or write. Primary keys are unique per tenant, so two tenants can both have an order 1. An UPDATE or DELETE changes only your tenant's rows.
The schema belongs to the application, not to a tenant: all tenants on a server share the same tables, and any logged-in tenant can see their names and columns, never their rows. Creating tables needs an operator credential, a separate kind of token for the application's operator. It can manage the schema but can never read or write tenant data, and an ordinary tenant token can never manage the schema.
What works today
| Area | Supported |
|---|---|
| Tables | CREATE TABLE [IF NOT EXISTS] with a primary key, NOT NULL, and WITH (storage = columnar) for column-oriented tables. |
| Types | smallint, int, bigint, real, double precision, numeric(p,s), text, boolean, uuid, date, timestamp, bytea. |
| Writes | INSERT … VALUES with one or many rows; an existing primary key fails the whole statement with 23505. UPDATE … SET … WHERE and DELETE … WHERE, each applied as one atomic step. A primary key cannot be changed, and an UPDATE of a table with columns above your clearance is refused rather than risk overwriting them. |
| Queries | SELECT with WHERE, inner and left joins, comma joins, GROUP BY, count, sum, avg, min, max, ORDER BY, LIMIT and OFFSET. Lookups and ranges on the primary key read only the matching rows. |
| Expressions | Comparisons, AND/OR/NOT, IN, BETWEEN, IS [NOT] NULL, + - *, casts. Numbers follow PostgreSQL's typing: numeric(12,2) * 2 keeps two decimal places, and text compared with a number is an error. |
| Protocol | The simple query protocol that psql uses, and prepared statements with $1 parameters, the extended protocol most drivers use by default, in text and binary formats. Parameter types are inferred as PostgreSQL infers them. The gateway allows 10 seconds to log in, small messages until the token is verified, and 200 connections by default. |
The end-to-end test loads 10,000 rows for one tenant and 100 for another and checks that neither sees the other's rows. It compares five aggregate and filter queries with DuckDB on the same data, runs updates and deletes on one tenant and compares the result with DuckDB after the same statements, and repeats queries and inserts as prepared statements through the Go driver pgx. The answers are identical.
Not yet
Everything else fails at once with SQLSTATE 0A000 and names the missing feature. It is never silently approximated. The main gaps, in the order they are planned:
BEGIN/COMMIT,RETURNINGandON CONFLICT.- Subqueries,
WITH,UNION,DISTINCT,HAVING, window functions, and SQL functions such asupper(). - The
pg_catalogtables that database GUIs and ORMs read when they connect. - Row-level policies. Today tenant isolation is the access boundary.
- Division of
realanddouble precisionvalues,varchar(n)length limits andtimestamp(p)precision. - Several statements in one query string when one of them writes, because there are no transactions yet to undo a half-applied string. Several reads in one string are fine.
Errors
| SQLSTATE | When |
|---|---|
28P01 | The token was refused: bad signature, wrong issuer or audience, or expired. |
0A000 | The statement uses something not supported yet. |
23505 | The primary key already exists for your tenant. |
42P01 / 42703 | No such table / no such column. |
42883 | No operator for these types, for example text compared with a number. |
42501 | Not allowed: creating a table without an operator credential, or an UPDATE of a table with columns above your clearance. |
23502 | NULL into a NOT NULL column. |
42P18 | Nothing in the statement says what type a $n parameter is (give it a cast, as in PostgreSQL). |
22012 / 22003 | Division by zero / numeric overflow. |
53200 | The query went over its memory budget. |
