postgres · API

postgres.result

postgres.result — the rows a query returned, and the typed accessors.

Connection.query hands back a Result: an owned PGresult plus the column metadata read out of it once. Cells are read on demand -- libpq has already materialised the whole result set in its own memory, so there is nothing to be gained by copying it eagerly, and a Result costs one PQclear no matter how many rows it holds.

Row is the other half, and it is a snapshot: Result.row copies one row's cells into owned Strings, so the Row stays valid after the Result that produced it is destroyed. That is what makes

def newest(mut conn: Connection) raises -> Row:
    var res = conn.query("SELECT id, name FROM t ORDER BY id DESC LIMIT 1")
    return res.row(0)          # `res` is cleared here; the Row survives

work, and it is why for row in res: yields values rather than references. Column names would be expensive to copy per row, so both types share one ArcPointer[_Columns] -- the names and OIDs, read once from the PGresult.

Typed accessors do not check the column's OID. row.int64("n") parses the cell's text as an int8 whatever the server called it, because the contract is "you asked for int64, we parse the text as int64"; a mismatch surfaces as the codec's own error, which names both the type and the offending text (postgres.text: invalid int8 text: 'abc'). Call column_oid first if you would rather dispatch on what the server actually sent.

NULL is not the empty string. libpq renders both as ""; is_null is the only thing that separates them. Every typed accessor raises on NULL, so a nullable column is read with Row.is_null first, or with Row.opt_text.

Structs

Row

struct Row

One result row, copied out of the PGresult and independent of it.

Obtained from Result.row or by iterating a Result. Every cell is an owned String plus a NULL flag, so a Row may outlive the Result -- and may be stored, returned, or copied freely.

Each accessor comes in two overloads: one taking a 0-based column index, one taking the column name. The name form resolves through the shared column metadata, so it works on a Row whose Result is long gone.

Example:

var res = conn.query("SELECT id, name, note FROM t")
for row in res:
    print(row.int64("id"), row.text("name"), row.opt_text("note"))

Implements Copyable, Movable

__init__

def __init__(out self, var values: List[String], var nulls: List[Bool], cols: ArcPointer[_Columns])

Build a snapshot from already-copied cells.

Args
values: One text-format value per column.
nulls: True where the cell is SQL NULL, parallel to values.
cols: The result's shared column metadata.

num_cols

def num_cols(self) -> Int

The number of columns in this row.

column_name

def column_name(self, col: Int) -> String

The name of column col.

Args
col: The 0-based column index.

Raises Error: If col is out of range.

column_index

def column_index(self, name: String) -> Int

The index of the column called name.

Args
name: The column name, matched exactly (see _Columns.index_of).

Raises Error: If there is no such column; the message lists the ones there are.

column_oid

def column_oid(self, col: Int) -> UInt32

The pg_type.oid of column col.

Args
col: The 0-based column index.

Raises Error: If col is out of range.

is_null

def is_null(self, col: Int) -> Bool

Whether column col is SQL NULL.

Args
col: The 0-based column index.

Raises Error: If col is out of range.

def is_null(self, col: String) -> Bool

Whether the column named col is SQL NULL.

Args
col: The column name.

Raises Error: If there is no such column.

opt_text

def opt_text(self, col: Int) -> Optional[String]

Column col as text, or None when it is SQL NULL.

The NULL-tolerant counterpart to Row.text.

Args
col: The 0-based column index.

Raises Error: If col is out of range.

def opt_text(self, col: String) -> Optional[String]

The column named col as text, or None when it is SQL NULL.

Args
col: The column name.

Raises Error: If there is no such column.

text

def text(self, col: Int) -> String

Column col exactly as the server rendered it.

The escape hatch for every type this module has no accessor for, and the only way to read the values the typed decoders refuse -- an infinity date, a BC timestamp, an array, a composite.

Args
col: The 0-based column index.

Raises Error: If col is out of range, or the cell is SQL NULL.

def text(self, col: String) -> String

The column named col, exactly as the server rendered it.

Args
col: The column name.

Raises Error: If there is no such column, or the cell is SQL NULL.

bool

def bool(self, col: Int) -> Bool

Column col decoded as bool (t/f).

Args
col: The 0-based column index.

Raises Error: If the cell is NULL or is not valid bool text.

def bool(self, col: String) -> Bool

The column named col, decoded as bool.

Args
col: The column name.

Raises Error: If there is no such column, the cell is NULL, or the text is not valid bool.

int16

def int16(self, col: Int) -> Int16

Column col decoded as int2.

Args
col: The 0-based column index.

Raises Error: If the cell is NULL, or the text is not an in-range int2.

def int16(self, col: String) -> Int16

The column named col, decoded as int2.

Args
col: The column name.

Raises Error: If there is no such column, the cell is NULL, or the text is not an in-range int2.

int32

def int32(self, col: Int) -> Int32

Column col decoded as int4.

Args
col: The 0-based column index.

Raises Error: If the cell is NULL, or the text is not an in-range int4.

def int32(self, col: String) -> Int32

The column named col, decoded as int4.

Args
col: The column name.

Raises Error: If there is no such column, the cell is NULL, or the text is not an in-range int4.

int64

def int64(self, col: Int) -> Int64

Column col decoded as int8.

Args
col: The 0-based column index.

Raises Error: If the cell is NULL, or the text is not an in-range int8.

def int64(self, col: String) -> Int64

The column named col, decoded as int8.

Args
col: The column name.

Raises Error: If there is no such column, the cell is NULL, or the text is not an in-range int8.

float32

def float32(self, col: Int) -> Float32

Column col decoded as float4.

Args
col: The 0-based column index.

Raises Error: If the cell is NULL, or the text is not a valid float.

def float32(self, col: String) -> Float32

The column named col, decoded as float4.

Args
col: The column name.

Raises Error: If there is no such column, the cell is NULL, or the text is not a valid float.

float64

def float64(self, col: Int) -> Float64

Column col decoded as float8.

Args
col: The 0-based column index.

Raises Error: If the cell is NULL, or the text is not a valid float.

def float64(self, col: String) -> Float64

The column named col, decoded as float8.

Args
col: The column name.

Raises Error: If there is no such column, the cell is NULL, or the text is not a valid float.

numeric

def numeric(self, col: Int) -> String

Column col as a numeric literal, kept as text.

numeric is arbitrary precision; turning it into a Float64 would silently lose digits, so this hands back exactly the digits the server sent -- trailing zeros of the column's scale included.

Args
col: The 0-based column index.

Raises Error: If col is out of range, or the cell is SQL NULL.

def numeric(self, col: String) -> String

The column named col as a numeric literal, kept as text.

Args
col: The column name.

Raises Error: If there is no such column, or the cell is SQL NULL.

bytea

def bytea(self, col: Int) -> List[UInt8]

Column col decoded as bytea.

Both text sub-formats are accepted: the \x-prefixed hex the server emits by default, and the legacy octal-escape format.

Args
col: The 0-based column index.

Raises Error: If the cell is NULL, or the text is not valid bytea.

def bytea(self, col: String) -> List[UInt8]

The column named col, decoded as bytea.

Args
col: The column name.

Raises Error: If there is no such column, the cell is NULL, or the text is not valid bytea.

date_days

def date_days(self, col: Int) -> Int32

Column col decoded as date, in days since 1970-01-01.

Args
col: The 0-based column index.

Raises Error: If the cell is NULL, the text is not a valid date, or the date is infinity/-infinity or a BC year -- neither of which the epoch encoding can carry. Read Row.text for those.

def date_days(self, col: String) -> Int32

The column named col, decoded as date in days since the epoch.

Args
col: The column name.

Raises Error: If there is no such column, the cell is NULL, or the date cannot be expressed as an epoch day count.

time_micros

def time_micros(self, col: Int) -> Int64

Column col decoded as time, in microseconds since midnight.

Args
col: The 0-based column index.

Raises Error: If the cell is NULL, or the text is not a valid time.

def time_micros(self, col: String) -> Int64

The column named col, decoded as time in microseconds.

Args
col: The column name.

Raises Error: If there is no such column, the cell is NULL, or the text is not a valid time.

timestamp_micros

def timestamp_micros(self, col: Int) -> Int64

Column col decoded as naive timestamp, in epoch microseconds.

Args
col: The 0-based column index.

Raises Error: If the cell is NULL, the text is not a valid timestamp, or it is infinite or BC.

def timestamp_micros(self, col: String) -> Int64

The column named col, decoded as naive timestamp.

Args
col: The column name.

Raises Error: If there is no such column, the cell is NULL, or the timestamp cannot be expressed in epoch microseconds.

timestamptz_micros

def timestamptz_micros(self, col: Int) -> Int64

Column col decoded as timestamptz, in UTC epoch microseconds.

The server renders timestamptz in the session's TimeZone, with the offset appended; the offset is applied here, so the result is UTC regardless of what the session is set to.

Args
col: The 0-based column index.

Raises Error: If the cell is NULL, the text is not a valid zoned timestamp, or it is infinite or BC.

def timestamptz_micros(self, col: String) -> Int64

The column named col, decoded as timestamptz in UTC micros.

Args
col: The column name.

Raises Error: If there is no such column, the cell is NULL, or the timestamp cannot be expressed in epoch microseconds.

uuid

def uuid(self, col: Int) -> String

Column col as uuid text, e.g. "a0ee-...-4f1b".

The server always renders a uuid in the canonical lower-case 8-4-4-4-12 form, so this is the raw text.

Args
col: The 0-based column index.

Raises Error: If col is out of range, or the cell is SQL NULL.

def uuid(self, col: String) -> String

The column named col as uuid text.

Args
col: The column name.

Raises Error: If there is no such column, or the cell is SQL NULL.

json

def json(self, col: Int) -> String

Column col as json/jsonb text.

Not parsed -- this tin has no JSON model. json comes back exactly as it was stored; jsonb comes back in the server's normalised form (keys reordered, whitespace canonical).

Args
col: The 0-based column index.

Raises Error: If col is out of range, or the cell is SQL NULL.

def json(self, col: String) -> String

The column named col as json/jsonb text.

Args
col: The column name.

Raises Error: If there is no such column, or the cell is SQL NULL.

Result

struct Result

The rows one command returned, owning the underlying PGresult.

Built by Connection.query from an already status-checked result handle, and cleared exactly once when it is destroyed. Not copyable: two owners would mean two PQclears.

A command that returns no rows -- an INSERT, a CREATE TABLE -- still yields a Result, with num_rows and num_cols both 0 and affected_rows carrying the count.

Example:

var res = conn.query("SELECT id, name FROM t WHERE id > $1",
                     Params().int64(10))
print(res.num_rows(), "rows")
for row in res:
    print(row.int64("id"), row.text("name"))

Implements Iterable, Movable, Sized

comptime IteratorType: Iterator = _RowIter[iterable_origin]

The iterator for row in res: uses; it yields Row snapshots.

__init__

def __init__(out self, res: Int)

Take ownership of an already status-checked result handle.

The status check belongs to the caller: by the time a Result exists the command has succeeded, so none of its accessors have to answer for PGRES_FATAL_ERROR. Connection.query is the only intended caller.

Args
res: A PGresultPtr whose PQresultStatus is PGRES_TUPLES_OK, PGRES_COMMAND_OK or PGRES_EMPTY_QUERY. This Result now owns it and will PQclear it exactly once.

Raises Error: Only if libpq itself could not be reached, which cannot happen once a Connection exists.

__deinit__

def __deinit__(deinit self)

Clear the PGresult, releasing libpq's copy of every row.

num_rows

def num_rows(self) -> Int

The number of rows.

num_cols

def num_cols(self) -> Int

The number of columns.

__len__

def __len__(self) -> Int

The number of rows, so len(res) reads naturally.

column_name

def column_name(self, col: Int) -> String

The name of column col.

Args
col: The 0-based column index.

Raises Error: If col is out of range.

column_index

def column_index(self, name: String) -> Int

The index of the column called name.

Matching is exact, as in PQfnumber: an unquoted SQL identifier arrives folded to lower case, so ask for "userid", not "userId".

Args
name: The column name.

Raises Error: If there is no such column; the message lists the ones there are.

column_oid

def column_oid(self, col: Int) -> UInt32

The pg_type.oid of column col.

The hook for callers who want to dispatch on the server's actual type rather than assert one -- Row's typed accessors deliberately do not consult it.

Args
col: The 0-based column index.

Raises Error: If col is out of range.

affected_rows

def affected_rows(self) -> Int

Rows inserted, updated, deleted or retrieved by the command.

Raises Error: If libpq reported a count that is not an integer, which would mean a libpq bug rather than a user error.

is_null

def is_null(self, row: Int, col: Int) -> Bool

Whether the cell at (row, col) is SQL NULL.

Args
row: The 0-based row index.
col: The 0-based column index.

Raises Error: If either index is out of range.

text

def text(self, row: Int, col: Int) -> String

The cell at (row, col) as the server rendered it.

Args
row: The 0-based row index.
col: The 0-based column index.

Raises Error: If either index is out of range, or the cell is SQL NULL -- check Result.is_null first, or read the whole row with Result.row and use Row.opt_text.

row

def row(self, row: Int) -> Row

Copy row row into a standalone Row snapshot.

The snapshot owns its cells and shares only the column metadata, so it stays usable after this Result is destroyed.

Args
row: The 0-based row index.

Raises Error: If row is out of range.

__iter__

def __iter__(ref self) -> _RowIter[origin_of(self)]

Iterate the rows, yielding one Row snapshot at a time.

Rows are copied lazily -- only the row the loop is on exists as a Row -- so a large result costs one row's worth of Strings at a time on top of what libpq already holds.