The fields of the error, never null: Pg.error_fields(error).constraint.
since 0.27.0
#import "Extensions/Postgres";
A PostgreSQL client over Io fibers. A jaic extension, not official Jai: other Jai compilers have no such module, and it may change between jaic releases. See docs/extensions/postgres.md.
Io :: #import "Extensions/Io";
Pg :: #import "Extensions/Postgres";
main :: () {
Io.io_run(() {
config, error := Pg.parse_url("postgres://app@localhost/app_dev");
db:, error = Pg.connect(config);
if error.kind != .NONE { print("%\n", Pg.error_text(error)); return; }
defer Pg.close(db);
result:, error = Pg.query_dynamic(db, "SELECT id, name FROM users WHERE id > $1", 10);
defer Pg.free_result(*result);
for row: 0..result.row_count - 1 {
id := Pg.get(*result, row, 0, s64);
name := Pg.get(*result, row, 1, string);
}
});
}This one is only checked to compile, not run.
Show moreShow less
Every call that talks to the server runs on a fiber inside Io.io_run and blocks only that
fiber. Io.with_deadline bounds any of them.
TLS needs Extensions/Tls (mbedTLS). Programs that only connect over Unix sockets or cleartext
TCP can leave it out with #import "Extensions/Postgres"(TLS = false).
The other parameters say where the compile-time side finds the project's files, relative to the project root (docs/postgres/typed-queries.md, "Module parameters"):
Pg :: #import "Extensions/Postgres"(MIGRATIONS = "sql/migrations", SCHEMA = "sql/schema.jai", QUERY_CACHE = "sql/cache", MIN_SERVER_VERSION = 15);Doesn't run in the browser (skipped on wasm).
MIN_SERVER_VERSION is a major version; a server older than that fails every describe.
Procedures#
Error#
One line describing the error, in temporary storage.
Frees what the error owns.
The five characters of a code, in temporary storage.
The code with only its class kept: sqlstate_class(.UNIQUE_VIOLATION) == .INTEGRITY_CONSTRAINT_VIOLATION.
The code written as text.
Config#
postgres://user:password@host:port/database?sslmode=verify-full&application_name=api.
postgresql:// works too. A Unix socket directory goes in host, percent-encoded or as a
parameter: postgres:///app?host=/var/run/postgresql. It is parse_conninfo for a URI.
libpq's connection strings: a URI (see parse_url) or host=localhost port=5432 dbname=app,
where a value with spaces is quoted, 'a value', and \ escapes the next character
(\' and \\ inside quotes). Parameters (keywords): host, hostaddr, port, user, password, passfile, dbname,
sslmode, sslnegotiation (direct), sslrootcert (a PEM file, or system), sslcert, sslkey,
sslpassword, sslcertmode, sslcrl, sslcrldir, sslsni, sslkeylogfile, ssl_min_protocol_version,
ssl_max_protocol_version, channel_binding,
application_name, fallback_application_name, options, connect_timeout (seconds), client_encoding,
replication and require_auth. libpq's other keywords are accepted and ignored. Errors read like
libpq's.
The Config for the options of a connection string, as parse_conninfo makes it. Programs
that combine several sources (a command line and a connection string) set the options
themselves with conninfo_set first.
A config that owns copies of its strings, for keeping beyond the original (free_config).
Frees the strings of a config from parse_conninfo, parse_url or copy_config; does nothing
for one the program built.
Conninfo#
Parses a URI (postgresql:// or postgres://) or a key=value list. The strings in the
result belong to it: free_conninfo.
Frees the strings of the options.
Whether text is something a program should hand to parse_conninfo: a URI or a list with
an = in it. psql and libpq use this to tell a connection string from a database name.
The value of keyword and whether it was set.
Sets keyword to a copy of value, replacing what was there. False for a keyword libpq does
not know.
Saslprep#
The prepared password and true, or password and false when it cannot be prepared. ASCII
passwords come back unchanged. The result is in temporary storage unless it is password.
Auth#
Reads a require_auth value (libpq's syntax: see Config.require_auth). The errors are
libpq's.
Fails the login when the server's way of authenticating is not one require_auth allows.
code is the AuthenticationRequest the server sent.
The password for a login from the password file, or an empty string. host is the name
connected to; a socket directory or no name searches for localhost.
Connection#
Opens a connection and authenticates. On failure the connection is null and nothing needs
closing. Must run on a fiber (Io.io_run); config.connect_timeout bounds each attempt.
Ends the session (Terminate) and frees the connection. now skips the goodbye.
A ParameterStatus value, or "" if the server did not report it.
Writes a server notice to the log.
Query#
Runs one statement with parameters $1, $2, ... taken from args, and returns its rows in
text format. Arguments may be integers, floats, bools, strings, [] u8 (bytea), Uuid,
Timestamp, Local_Timestamp, Date, Time, Interval, Numeric, Nullable of any of
those, and one-dimensional arrays of them.
query_dynamic with binary results: smaller and faster to decode, but every column's type must
be one get can decode in binary (see Oid).
Parses sql as the unnamed prepared statement and describes it (Parse, Describe, Sync). Free
the result with free_statement_info.
Frees what describe_statement returned.
Runs SQL that may hold several statements, without parameters (the simple query protocol), and discards any rows. For migrations and scripts. Without an explicit BEGIN the whole string runs as one implicit transaction, so the first error stops it and undoes the statements before it.
Runs SQL that may hold several statements (the simple query protocol) and reports each
statement's outcome to hooks. The returned error is the one that stopped the script, after the
results of the statements before it were reported. Without an explicit BEGIN the whole string
runs as one implicit transaction.
The value at row and column as a T. NULL is an error unless T is a Nullable.
Whether the value is NULL. False when the row or column does not exist.
The first column with this name, or -1.
The undecoded bytes of a value, in the column's format; is_null for NULL.
Drops the rows of a result and what was decoded from them, keeping its columns.
Frees a result and everything decoded from it. It is zeroed.
Statements#
Runs one statement like query_dynamic, prepared under a name and kept in the connection's cache
(unless plan is .CUSTOM or the cache is off): the first run parses it, later runs with the
same text and parameter types only bind. Typed queries do this on their own.
How many statements the connection has prepared on the server and remembers.
The server name of the statement a query with this text and these parameter types is prepared
as (j and 16 hex digits), for looking it up in pg_prepared_statements.
Copy#
Sends rows through sql, a copy <table> (<columns>) from stdin (format binary) statement,
and returns how many rows the server stored. Each row sends the members named in columns, in
that order, or every member when columns is empty. The columns' types must match the members'
binary forms exactly (an s32 for int4, an s64 for int8): COPY casts nothing. A Json(T)
member goes as jsonb unless it has the @pg_json note, which sends it as json.
stored, error := Pg.copy_in(db, "copy users (email, score) from stdin (format binary)", users, .["email", "score"]);Not run: the example is marked ignore.
Runs a COPY <table or (query)> TO STDOUT statement and calls proc with each piece of its
data, as it arrives, so a table of any size streams through. Returns how many rows the server
sent. Any format works: text, CSV or binary.
count, error := Pg.copy_out(db, "copy (select email from users) to stdout (format csv)", write_chunk, file);Not run: the example is marked ignore.
Frees the rows of a binary COPY and everything they point to.
Types#
The value, or default when it is NULL.
A non-null Nullable holding value.
value as a json/jsonb parameter.
Decodes a document into a T.
The document as a Json_Value tree, which the caller releases with free_json (from
Extensions/Json); any JSON can be read this way, whatever T a column would map it to.
Microseconds since 1970-01-01 00:00:00 UTC.
The current time from the system clock.
The date of a proleptic Gregorian calendar day. Year 0 is 1 BC, as in ISO 8601.
The year, month and day of a date.
The instant at that wall-clock reading, taken as UTC.
Spans of time as an Interval, for arithmetic on timestamps: Pg.now() - Pg.days(7). A day is
a calendar day, not 24 hours, as in Postgres; the rest are exact.
An interval of exact hours.
An interval of exact minutes.
An interval of exact seconds.
Every field negated.
Months, days and microseconds added field by field.
Uuid from "0f8fad5b-d9cb-469f-a165-70867728950e" (dashes optional, any case).
"0f8fad5b-d9cb-469f-a165-70867728950e", in temporary storage.
The integer part of a numeric; false for NaN, the infinities, or one out of range. A fraction is truncated toward zero.
The numeric as a float, and false if its text does not read as a number.
Pool#
The config is copied. Nothing connects until the first acquire, or min_idle asks for it.
Closes the idle connections and frees the pool once every borrowed one is back.
A connection for this fiber alone, until release. Waits for one when max_size are in use,
up to acquire_timeout (and any Io.with_deadline around the call).
Gives a connection back. One left inside a transaction is rolled back first; a broken one, or
one past max_lifetime, is closed instead.
A round trip that runs nothing: an empty Sync. Fails if the connection is dead.
Runs body in a transaction on a connection from the pool. The transaction commits when body
returns no error, and rolls back when it returns one (which comes out of transaction) or a
statement in it failed. A serialization failure or deadlock (40001, 40P01) runs body again, up
to retries more times, so body must be safe to repeat. data is passed through, as Jai
procedures capture nothing.
Transfer :: struct { from: s64; to: s64; amount: s64; }
error := Pg.transaction(pool, Transfer.{ 1, 2, 10 }, (tx: *Pg.Connection, t: Transfer) -> Pg.Pg_Error {
_, error := Pg.query_dynamic(tx, "update accounts set balance = balance - $1 where id = $2", t.amount, t.from);
if error.kind != .NONE return error;
_, error = Pg.query_dynamic(tx, "update accounts set balance = balance + $1 where id = $2", t.amount, t.to);
return error;
}, .SERIALIZABLE, retries = 3);Not run: the example is marked ignore.
Pipeline#
The answer to a statement queued with queue or queue_execute: its rows, or the count of rows
it changed. The statements queued so far get a sync point first if they have none, so the batch
ends. Pendings may be taken in any order; results read on the way are kept for their own take.
Starts pipeline mode on a connection. Entering again returns the same pipeline.
Ends pipeline mode. It fails, and the pipeline stays, while results are unread.
Statements waiting for their results, sync points included.
Queues a statement with parameters $1, $2, ... from args (see query_dynamic for the types),
results in text format.
pipeline_query with results in binary format.
Queues a statement whose parameters are already text. types are the parameter OIDs to declare
(0, or fewer than values, leaves the rest to the server).
Prepares a named statement. types are the parameter OIDs to declare.
Runs a prepared statement with args (see pipeline_query), results in text format.
Runs a prepared statement with parameters that are already text.
The result of these four is .RESULT; a describe also fills result.columns, and a statement
describe parameter_types.
Queues a describe of a portal.
Closing a statement or portal that does not exist is not an error.
Queues a close of a portal.
Queues a sync point and sends everything queued.
Queues a sync point without sending.
Asks the server to send the answers so far without ending the batch, and sends everything queued. Without a sync point, a batch that fails is not rolled back until the next one.
Sends everything queued.
Makes the next statement to be read deliver its rows count at a time (.ROWS; the last piece
may have fewer), then end with a .RESULT that has no rows but the command tag, so a large
result never has to fit in memory. It only works before the statement's first result is read.
Reverts at the statement's end.
pipeline_chunked_rows with one row per result.
Reads the result of the oldest queued statement or sync point, sending what is still unsent
first. Blocks the fiber until the answer is in, within any Io.with_deadline: after a deadline
the pipeline keeps what it had read, and the call can be repeated. The result's kind is NONE
when nothing is queued. The returned error is for failures of the connection; the server's
errors come as .ERROR results. Free the result with free_pipeline_result.
Frees a result from pipeline_next_result.
Gives up on everything queued: asks the server to cancel what runs, reads and drops all answers
up to the last sync point (queuing one if needed), and leaves pipeline mode. The connection
breaks if the server does not finish within cancel_timeout.
Describes a prepared statement by name, without a pipeline (Describe, Sync).
Describes a portal (a cursor, say) by name: the columns it returns.
Drops a prepared statement by name. One that does not exist is not an error.
Drops a portal by name. One that does not exist is not an error.
Notify#
Frees a notification's strings. It is zeroed.
The oldest queued notification, without waiting. found is false when none is queued. Free it
with free_notification.
Frees every queued notification.
The oldest queued notification, or the next one to arrive: blocks the fiber until then, within
any Io.with_deadline or Io.cancel (the error is .TIMED_OUT or .CANCELED, and the
connection stays usable). Free the notification with free_notification. Nothing else may use
the connection while a fiber waits on it. With on_notification set, notifications never queue,
so there is nothing to wait for.
LISTEN on a channel. The name is quoted, so any text is a channel.
Stops listening to one channel.
Stops listening to every channel.
NOTIFY a channel, with a payload of up to 7999 bytes. Delivered to the listeners when the transaction commits; inside none, at once. A connection also hears its own notifications.
The text as an SQL identifier: in double quotes, with embedded quotes doubled.
Listener#
The next notification, in order. Blocks the fiber until one arrives, within any Io.with_deadline
or Io.cancel (.TIMED_OUT or .CANCELED); .CLOSED once the listener is closed and what it
held is taken. Free the notification with free_notification.
Stops listening: the connection goes back to the pool without its subscriptions, and the listener is freed. Notifications not taken yet are dropped.
Trace#
Starts tracing: proc gets a line per message from now on (null stops). With regress, the
output is libpq's PQTRACE_SUPPRESS_TIMESTAMPS | PQTRACE_REGRESS_MODE: OIDs, process ids and the
server's source locations are replaced by fixed text, and an error's length by NN, so a trace
is the same from one server run to the next. Without it they show as they are.
Migrate#
The migrations in folder, sorted by version, for embedding: #run load_migrations(...).
A relative folder starts at the calling file's folder. A badly named file or a repeated version
stops the compile.
load_migrations for tools: the migrations, or a message saying what is wrong with the folder.
relative_to is a file whose folder relative paths start at; empty for the working directory.
The schema hash: SHA-256 chained over each migration's name and digest, in order, so it changes when any file is renamed, edited, added or removed. Hex.
The schema hash of each prefix of migrations: entry i covers migrations 0 to i.
Applies the pending migrations in order, each in its own transaction, and returns how many it
applied. An advisory lock keeps two processes from migrating at once. It refuses (applying
nothing) when an applied migration changed or disappeared, or a pending one is numbered below
the newest applied one, unless options allows it.
migrate, and when a migration fails, failure says which one and where in its file.
Each migration's state, in the list's order, then the applied ones missing from the list. Creates the migrations table if it does not exist.
Splits a script into its statements, skipping quoted text, dollar quotes and comments, so a ;
inside them does not end a statement. Empty statements are dropped. The pieces point into sql.
The line and column of a 1-based character position in text, or 0 and 0 for no position.
Characters between two byte offsets of text.
Where error stopped in the file whole of migration name. statement is the text the
server was sent: the whole file, or one statement inside it (a slice of whole) for a migration
without a transaction.
For an error that came without a position: the first statement of the migration whole that
fails the same way, as the failure's line. Runs inside the caller's open transaction, in the
state the migration started from, and leaves it in that state (a savepoint is rolled back to).
Gives failure back unchanged when no single statement reproduces the error, which happens when
the file's ; splitting is not how the server saw it.
How many newlines text has.
"migration 0007_add_teams.sql failed at line 3: relation ... does not exist".
JSON options#
A Json_Columns entry's three options as the second parameter of Json(T, OPTIONS): two bits
each, in the order of the enums.
The options pack_json_options packed.
Wire#
Starts a message in the output buffer, with its length left to fill in.
Fills in the length of the message begin_message started.
Adds a Parse message to the output.
Parameters go as text unless one needs another format; the format codes are left out when every parameter is text.
Adds a Describe message to the output.
Adds a Close message to the output.
Adds an Execute message to the output.
Adds a Sync message to the output.
Adds a Flush message to the output.
Writes the output buffer to the server.
Room for total items, growing by doubling so a long run of small appends copies little.
Appends bytes.
A C string: the text and a zero. Text holding a zero would end early, so callers check first.
Reads a big-endian number.
Reads a big-endian number.
Reads a big-endian number.
Makes at least count unread bytes available.
The bytes left in the cursor's data.
Startup#
Connects c to the server: transport, TLS, the startup message and authentication.
Opens the TCP or Unix socket and sets up the reader and writer.
Asks for TLS and starts it, as ssl_mode requires. Does nothing for a Unix socket.
Sets up the TLS configuration from the settings and runs the handshake.
Appends a line to the connection's key log file, which only its owner may read. A file that cannot be opened or written is reported, as libpq does, and the connection goes on.
Prints a line to standard error.
The handshake over the connection's reader and writer, the stream becoming theirs.
What libpq prints for a failed handshake, or a TLS failure afterwards.
The alerts OpenSSL words as "ssl/tls alert ..." (those of SSL 3.0); the others it calls TLS 1.0's.
Whether host is written as an address (only digits and dots, or with a colon), which libpq
never sends as SNI.
Asks the server to stop what c is running: a CancelRequest on a connection of its own, over
TLS when c uses it. The server answers nothing and closes that connection once it has acted.
Whether a statement was stopped shows on c (QUERY_CANCELED), not here.
Reads the server's replies to the startup message until ReadyForQuery, answering authentication requests.
Answers one AuthenticationRequest: sends the password or the next SCRAM step.
The transaction status ReadyForQuery reports.
A .PROTOCOL error for what.
An .AUTH error for what.
Whether text has a zero byte, which a startup parameter cannot hold.
Asks the server to cancel the statement c is running, as libpq's PQcancel does. Meant for
another fiber than the one waiting for the statement (a signal handler's, say): the waiting
call then returns the server's "canceling statement due to user request". Does nothing useful
when no statement is running.
TLS name#
"" when the certificate is for host, else why not.
The address host stands for and true, or false when host is a name.
Whether c is a to f in either case.
Whether the certificate entry is address, byte for byte.
The text of a raw address, as libpq shows it in its messages.
A name from the certificate against the host: equal without regard to case, or a wildcard
*.rest for a host with exactly one more label in front of .rest.
SCRAM#
Frees what a SCRAM exchange holds.
Picks a mechanism from the server's offer and sends the client-first message.
Answers the server-first message with the client-final message.
PBKDF2 of the password, taken from the cache when it was computed before.
Checks the server-final message: its signature proves the server knows the password.
The password as SCRAM uses it: SASLprep (saslprep.jai), or its own bytes when that cannot be done, like libpq.
The hash of the server's certificate for tls-server-end-point: the certificate's own signature hash, with MD5 and SHA-1 raised to SHA-256. Empty when the signature uses no hash (Ed25519) or one this client cannot compute.
The SCRAM-SHA-256 secret the server stores for a password, to put in ALTER ROLE ... PASSWORD
without the server seeing the password (libpq's PQencryptPasswordConn does the same). The
password is prepared with SASLprep. iterations is what the server reports as
scram_iterations, 4096 by default.
The client-final-message for a server-first-message, and the signature the server must prove it
knows in its final message. binding is the GS2 header (plus the certificate hash for -PLUS),
salt the decoded salt. Exported so the RFC 7677 example can be checked.
How many times a SCRAM exchange ran PBKDF2 in this process: cache hits do not count.
The MD5 secret for a password and a role name: md5 and the hash of the two joined.
SQL scan#
Rewrites % to $1, $2, ... in order and %% to %. A $n of Postgres' own in the source
is an error: one style only. Text inside quotes and comments is copied as it is.
The offset in the source of a byte of the rewritten statement. Inside a replaced % it is
that %.
The byte offset in text of a 1-based character position (what an ErrorResponse's P field
counts, in characters, not bytes). Past the end, the end.
A 64-bit statement id: the first eight bytes of SHA-256 of the rewritten statement.
The full SHA-256 of a text, as lower-case hex.
The 16 lower-case hex digits of a statement id.
Lower-case hex of count bytes at data, twice as long.
Entry#
The JSON document of a cache entry.
Reads an entry back from its JSON. False when the document is not shaped like one.
Describe#
The complaint for a server older than the request allows, or an empty string.
The request as JSON text.
Describes request.sql on db and returns the answer as JSON text:
{"kind":"ok","entry":{...}} the cache entry (see entry_to_json)
{"kind":"sql_error","error":{...}} the server refused the statement
{"kind":"error","message":"..."} anything else (the connection died, say)
The answer to a fingerprints request: {"kind":"ok","server_major":17,"fingerprints":{key: hash}}.
The answer for migrations that do not build and leave no earlier schema to describe against.
Schema databases#
The name of the database for a schema hash.
Picks (and builds when it is time) the schema database for the migrations in folder. db is
connected to the server's own database and config is how it was reached.
Drops schema databases beyond the newest SCHEMA_DATABASES_KEPT. One still in use (by another
editor, say) is left for next time.
Resolve#
The project's migrations folder, schema file and query cache, from the module parameters MIGRATIONS, SCHEMA and QUERY_CACHE (paths relative to the project root).
The path of the schema file.
The path of the query cache folder.
MIN_SERVER_VERSION: the oldest server major version the project supports; 0 accepts any.
The folder that holds the project's files: from the file with the query, the nearest folder up
the tree with a jacket.toml or the first folder of MIGRATIONS (db, by default); if there is
none, that file's own folder. JACKET_PG_ROOT overrides it.
The compile-time database's URL: JACKET_DATABASE_URL, else DATABASE_URL, from the environment;
when it has neither, the same two names in the project's .env file. Empty when there is none.
The project's migrations folder and their schema hash (see schema_hash), or empty strings when
there are none.
id_hex read back.
The path of the cache file for a statement.
The entry in a cache file and true, or false when there is none, it cannot be read, or it is for another statement.
Writes a statement's cache entry, leaving the file alone if it would not change.
Dotenv#
The value key gets in the text of a .env file, and whether it is assigned at all. The last
assignment wins. The result lives in temporary storage.
The database URL the project's .env file gives: JACKET_DATABASE_URL, else DATABASE_URL. Empty
when there is no file or neither is assigned.
ORM#
The rows of T's table that pass where, in order_by order (desc for descending), at most
limit of them (-1: all) after skipping offset. from_sql replaces the table with a query of
your own that returns its columns, with %name / %(expression) parameters:
from_sql = "select * from users_visible_to(%viewer)". fields = "id, email" selects only
those columns, and the rows are then Partial(T, "id, email").
Sets fields of the rows that pass where: set = #code it.score = score + 1 (or a block of such
assignments). Returns the number of rows changed.
Deletes the rows that pass where and returns how many there were. where is required: pass
#code true to delete every row.
Inserts value and returns the row the table now holds (with its defaults and generated
columns). Fields with a database default (@pg_default) or a generated value
(@pg_generated) are left to the database.
Inserts each value in turn (one statement each), stopping at the first error. Returns the rows inserted so far, from context.allocator.
A select whose columns come from the loop that reads it: only the fields of T the body reads,
plus the primary key, are fetched; the other fields of it stay zero. A json document read only
for some top-level fields comes back with just those keys, and it.author.email (a to-one
relation) is fetched in the same statement, making it an Orm_Item that usings a T. When it is used whole
(passed to a procedure, stored, *it), every column is fetched. The query runs when the loop
starts, and a failure lands in error (which is cleared first). Strings in it read from the
result, which is freed when the loop ends: copy what you keep.
error: Pg.Pg_Error;
for Pg.each(db, User, *error, where = #code it.score > min) print("%\n", it.email); // select "id", "email" ...Not run: the example is marked ignore.
Makes Pg.each loopable.
Stores values with one binary COPY and returns how many rows the server stored. Much faster
than insert_many for many rows, but it reads nothing back: fields marked @pg_default or
@pg_generated are left to the database, as in insert, and their values are not returned.
Loads a has-many relation for several rows; see Loaded_Many.
Loads a to-one relation for several rows; see Loaded_One.
load_many with the related struct given, for Pg.each, whose loop knows it already.
Frees what load_many returned.
like: Pg.like(it.email, "%@example.com").
ilike: case-insensitive like.
= any(values): Pg.in(it.id, ids).
lower(value).
upper(value).
(value is null or condition): an optional filter, Pg.if_some(team, #code it.team_id == team)
with team: Nullable(s64), where team stands for its value inside the condition.
?: the jsonb document has the top-level key: Pg.has_key(it.settings, "beta").
@?: the jsonpath matches: Pg.json_path(it.settings, "$.tags[*] ? (@ == \"x\")").
Typed#
Every row decoded and copied, with the strings, arrays and documents it points to, into one
block from allocator. The rows stay valid after free_rows; array_free(items) frees all of
it (with temp, nothing needs freeing). Check rows.error afterwards.
Not run: the example is marked ignore.
Frees the result data of rows.
Runs the statement and returns its rows. Rows(Row(...)) has one field per column; when the
columns are exactly one table's, it is Rows(Table) with that table's struct from db/schema.jai
(row type identity, see row_type).
The only row, or a .NOT_FOUND error when there is none. More rows are a .USAGE error. The row
lives in temporary storage (until reset_temporary_storage).
The first row and whether there was one.
Runs a statement and returns the number of rows it affected.
query on a pipeline: the statement is queued, checked like query, and its rows come later
from take. Queue several, then take them: they share one round trip.
p := Pg.pipeline_enter(c);
users := Pg.queue(p, "select id, name from users where id = $1", id);
count := Pg.queue_execute(p, "update users set seen = now() where id = $1", id);
rows, error := Pg.take(p, users);
changed, error = Pg.take(p, count);
Pg.pipeline_exit(p);Not run: the example is marked ignore.
execute on a pipeline: take gives the count of rows the statement changed.
query for results too big to hold: the rows arrive batch at a time and for decodes them
one by one, so at most one batch is in memory. The statement is sent here, and the connection is
busy until the loop ends; leaving the loop early cancels the rest. A failure, before or during the
loop, lands in error (cleared first). Strings in it live until the next batch: copy what you
keep.
error: Pg.Pg_Error;
for Pg.stream(c, *error, "select id, body from events where day = %", day, batch = 5000) {
total += it.body.count;
}
if error.kind != .NONE ...Not run: the example is marked ignore.
The first column of the only row.
query into your own struct. The columns are matched to fields by name, and checked: a column
that can be NULL needs a Nullable field, the types must be assignable, and a Json(T) field
says which type a json column holds. project_file is as for execute.
Schema#
The Jai type a json or jsonb column holds, from the Json_Columns visible where this is
called (a macro, so the generated file sees the user's list), else Json_Raw. A mapping can
change without generating the schema again.
The options of the Json_Columns entry for column, packed as the second parameter of
Json(T, OPTIONS); 0 when it has none.
Does a file called name sit in the folder of file? For the generated file to load
json_columns.jai when there is one.
Stops the compile when file (the generated schema) was made from other migrations than the
ones in the project now. Needs no server: it compares hashes of db/migrations/*.sql.
Why file no longer matches the migrations, or an empty string when it does.
The hash a generated file records in its header, or an empty string.
Stops the compile when a Json_Columns entry names something that is not a json or jsonb
column. known is every json column of the schema, as the generated file lists them.
How a Postgres enum label is spelled as a Jai enum member, and back. A label that is a plain
name is used as it is, so 'pending' is .pending. Any other (spaces, dashes, a leading
digit, a Jai keyword, a double underscore) becomes __ followed by its letters and digits,
with every other byte as _ and two hex digits: 'in progress' is __in_20progress.
The label an enum member name stands for: the inverse of enum_member_name.
Generate#
Reads the catalog of the migrations' schema database (see schema_databases.jai) on the server
config names, building it first when there is none, and returns the text of db/schema.jai;
or what went wrong. Runs on a fiber, like every call that talks to the server.
The catalog of the migrations in migrations_folder and their schema hash, read from the
schema database on the server config names (built first when there is none); or what went
wrong. Runs on a fiber, like every call that talks to the server.
The text of db/schema.jai for a catalog made from migrations with this schema hash, or a
problem such as a column that cannot be a field name. Laid out as the formatter would.
Everything the generator needs from a migrated database.
The Jai names of the catalog's types. One whose preferred name is taken falls back to a longer one, so two tables never collapse into one struct.
The type of a column's field, or a problem: an enum the catalog does not list.
Diff#
Reads the catalog of the migrations' schema database (see schema_databases.jai), compares it
with the structs in schema_file, and returns the proposed SQL; or what went wrong. An empty
sql means nothing differs. Runs on a fiber, like every call that talks to the server.
The structs and enums of db/schema.jai, which must have the shape generate writes: one
declaration per line of the form Name :: struct @note(...) {, then name: Type; @note per
field line, then a } in the first column. Structs without @pg_table are not tables and are
skipped. Anything else in a table struct or an enum is a problem naming the line.
Compares the structs with the catalog and writes the SQL that would bring the migrated schema to what the structs say. See docs/postgres/diff.md for what it covers.
The Jai type text of a field as the Postgres type that would hold it, or an empty string for a
type that has no mapping. refs resolves the names of enums.
Describe service#
The service's answer to one request.
The URL of the managed server, starting it if needed, or why it cannot.
The slot holding the connection for this pair, added if there is none.
Closes the held connection for this pair, if any.
The held connection for this pair, connecting when there is none, or why it cannot. Failures are remembered for a while.
Types#
The five-character SQLSTATE, packed into a number so that it compares and switches like an
enum: if error.code == .UNIQUE_VIOLATION. Codes not listed here still compare and print
(sqlstate_text). Its class is the first two characters: sqlstate_class(code).
What a call returns. It is small on purpose (the hot calls hand it back by value): the kind, the
SQLSTATE and the primary message. The rest of an ErrorResponse is in error_fields(error).
The other fields of an ErrorResponse or NoticeResponse. Empty strings and zeros for the ones the server did not send, and for errors that did not come from the server.
Which of several servers connect accepts, as libpq's target_session_attrs.
The order connect tries the hosts in, as libpq's load_balance_hosts.
The TLS versions libpq names in ssl_min_protocol_version and ssl_max_protocol_version. The
TLS library behind this client speaks 1.2 and 1.3 only, so a range that ends below 1.2 cannot
be used, and a minimum below it is raised to 1.2.
The options a connection string set. A keyword that was not given has no value; one given empty has an empty value, which is not the same.
The methods require_auth allows. restricted is false when it is empty: anything goes.
A minor version of protocol 3, for Config.min_protocol_version and max_protocol_version.
Version 3.2 (PostgreSQL 18 and later) allows cancel keys of up to 256 bytes, where 3.0 has four.
Called for each NoticeResponse (warnings, RAISE NOTICE). The notice is freed after the call.
Called for each NotificationResponse (a NOTIFY on a channel the connection LISTENs to), in the
middle of whatever request is reading. The notification is freed after the call. Without one,
notifications queue on the connection (next_notification, wait_for_notification).
The rows of one statement. Strings and byte slices that get returns point into the result,
and arrays and numerics it builds belong to it: all stay valid until free_result.
What the server makes of a statement without running it: the types it infers for $1, $2,
... and the columns it would return (none for a statement that returns no rows).
What run_script does with each statement's outcome. Every hook is optional.
The shape of a COPY the server has started.
Whether a connection keeps prepared statements (Config.statement_cache).
How the server plans a typed query (plan = on query, execute, ...).
Gets the data of a COPY ... TO STDOUT in the pieces the server sends it in. A piece is one row
in the text and CSV formats, and any slice of the stream in binary; it is valid during the call.
The rows of a binary COPY. Strings, byte slices and what arrays and numerics hold belong to
result: free them all with free_copy_rows.
The type OIDs this client knows by number. Other types (enums, domains, composites) get their OIDs when they are created, so they are only known by name.
A value that may be SQL NULL. null is true for NULL, and value is then zero.
A json or jsonb document holding a T. get decodes it with the Json module's decoder for
T, under the result's json_options and the query's json (json_options.jai); as a parameter
it is encoded with write_json. OPTIONS is a Json_Columns entry's options (packed by
pack_json_options), which the generated schema puts in the type; 0 says nothing.
write lets a parameter passed as Any find its encoder, so leave it as it is. Json(Json_Raw)
keeps the document undecoded: it is what a generated schema gives a json column nobody mapped.
A json or jsonb document, undecoded: its text, which points into the result like any string.
decode_json turns it into a T later. Also a parameter, sent as it is.
uuid: the 16 bytes in network order.
timestamptz: an instant, in microseconds since 2000-01-01 00:00:00 UTC (Postgres' own epoch).
TIMESTAMP_INFINITY and TIMESTAMP_MINUS_INFINITY are infinity and -infinity.
timestamp (without time zone): a wall-clock reading, in microseconds since 2000-01-01 00:00:00.
date: days since 2000-01-01. DATE_INFINITY and DATE_MINUS_INFINITY are infinity and
-infinity.
time (without time zone): microseconds since midnight.
interval: months, days and microseconds are kept apart, as Postgres keeps them, because a
month has no fixed number of days and a day has no fixed number of microseconds across a
daylight-saving change. From PostgreSQL 17 infinity is every field at S32_MAX / S64_MAX and
-infinity every field at the minimum.
numeric: an exact decimal, as its text: "-12.3400", "0", "NaN", "Infinity", "-Infinity".
numeric_to_s64 and numeric_to_float64 convert it.
Belongs to one scheduler: create it, and use it, on the fibers of one io_run (with
io_run_threads, one pool per thread). No locks are involved.
What one queued statement or sync point turned into.
The pipeline of a connection (pipeline_enter). While it is active, the connection answers no
other request.
A queued typed statement whose answer is still to come: take it after queuing the rest. R is
what take returns.
A dedicated, self-healing LISTEN on a pool's database. listen(pool, "jobs") takes a connection
out of the pool for as long as the listener lives, LISTENs on it, and reads notifications on a
fiber of its own into a queue. If the connection fails (the server restarts, the backend is
terminated, the network drops) the listener returns it to the pool, takes another, LISTENs
again and carries on. Postgres keeps no notifications for a session that is not connected, so
those sent while the listener is reconnecting are lost; code that must not miss any should read
its table again after reconnects changes.
listener, error := Pg.listen(pool, "jobs", "mail");
while true {
n, error := Pg.receive(listener);
if error.kind != .NONE break;
defer Pg.free_notification(*n);
print("% on %\n", n.payload, n.channel);
}
Pg.close_listener(listener);Not run: the example is marked ignore.
Called with each traced line, without its newline. The line is in temporary storage.
Where a migration stopped, for tools that point at the file.
Where one migration stands against a database.
Reading inside a message body. A read past the end sets bad and returns zeros, so a parser
checks bad once at the end.
One place where the rewritten text differs from the source: a % that became $n (hole is
n), or a %% that became % (hole is 0).
Where to describe: database on the same server (empty: the URL's own database) after running
setup inside the transaction. built names a database this call created, so the caller can
let go of connections to older ones and then prune_schema_databases.
Where one migration's SQL lies in setup, so a failure in the transaction can be traced to its
file.
Some fields of a table struct, with their types and notes: the rows of select(..., fields = ...).
fields is a comma-separated list of T's field names.
Contact :: Pg.Partial(User, "id, email");Doesn't run in the browser (skipped on wasm).
How Pg.each loads a has-many relation its loop body reads (for post: it.posts).
The rows parents have through the has-many relation relation (a @pg_has_many note of
T), in one query: items[i] belongs to parents[i]. Free it with free_loaded; the items
read from its result.
posts, error := Pg.load_many(db, users, "posts");
for users print("% wrote % posts\n", it.email, posts.items[it_index].count);Not run: the example is marked ignore.
The row each of parents points to through the to-one relation relation (a
@pg_belongs_to note of T), in one query: items[i] is null when parents[i] points nowhere.
A typed SQL NULL for any parameter: Pg.query(db, "update t set x = %", Pg.NULL).
The row of a described statement: one field per column, named by the column. shape is the
struct body the describe step wrote (doc comments included), so equal columns give one type.
The result of query: it owns the received data, and for rows hands out one row at a time.
Strings and byte slices in a row point into that data and stay valid until free_rows.
A one-field struct whose field has the type of a column: for query_scalar's return type.
One line of the hand-written db/json_columns.jai:
Json_Columns :: Pg.Json_Column.[ .{ "users.settings", Settings, .{ unknown_fields = .DENY } } ];Doesn't run in the browser (skipped on wasm).
A Postgres enum.
A table, view, materialized view or foreign table.
One field of a struct in the schema file.
A struct with an @pg_table note.
An enum of the schema file: a Postgres enum.
What parse_schema_text reads from the file: the table structs and the enums.
The migration diff proposes.
How an enum of the file is called in Postgres.
A connection kept between requests, and the backoff for reaching it again after a failure.
database empty is the URL's own database; otherwise a schema database on the same server.
The managed server this service process owns (managed.jai). It is started by the first request
that needs it and then reused; start_managed takes a lock, so two services (two compiles, or
a compile and the editor) never start the same data folder at once. The server keeps running
when the service exits, so the next build finds it ready.
Constants#
Every keyword libpq accepts, in the order it lists them.
infinity and -infinity of timestamps and dates, as the server stores them.
Microseconds from 1970-01-01 (the Unix epoch) to 2000-01-01.
notifications held before the reader waits for the program
ms before the first reconnect attempt, doubling
Messages the server may send are never larger than this; a bigger length means the stream is out of step or hostile.
A message starts with its type byte and its four-byte length.
3 << 16
PostgreSQL 18 and later; older servers answer with version 3.0
AuthenticationRequest codes.
SaltedPassword (the PBKDF2 of the password) for the last few (password, salt, iterations)
triples of the process, so a pool that reconnects, or opens several connections at once, runs
the iterations once. The key is a hash of the triple: the cache holds no password text. The
salted value is as good as the password for logging in with SCRAM, and lives as long as the
process, as the password does in the connection's Config.
The version of what an entry holds and how its statement is rewritten. Raise it when the description changes shape, or when the placeholder rewrite, the alias syntax or anything else that decides what a statement means does: every entry written before is then a miss, described again when a database is there (or by the pg tool's prepare), and an error offline.
How long a set of migrations must stay unchanged before its database is built;
JACKET_PG_BUILD_DELAY_MS overrides it.
Schema databases kept on the server, newest first.
Defaults for select: no filter, no order.