The keyset cursor of a bulk pull: where a page starts and where the next one continues.
A pull answers at most max_records_per_request rows in primary-key
order. When rows are left, the sender names the last row's key values
("next_after"), and the receiver asks for the page after them with
"after". Both are a map of every primary-key column to the value's
text, e.g. %{"uuid" => "0190…"} or %{"order_id" => "7", "line" => "3"}.
The page after a key is a row-value comparison in the columns' own types,
("a", "b") > ($1::text::bigint, $2::text::uuid), so a composite key and
a non-integer key continue exactly where ORDER BY "a", "b" left off.
Values are bound, never interpolated; the only text that reaches the SQL
is a quoted column name and a type name from the list below.
A key type has a cursor when a value's text reads back as exactly that
value and ORDER BY and the row comparison order it alike. That holds
for the types below. A float goes as its shortest text that reads back
to the same float (NaN sorts last and equals itself in both), bytea
as hex, character compares without its padding on both sides.
Keys of other types have no cursor: such a table answers its first page
and "truncated", as a sender without cursors does, and
unsupported_types/1 names the type for the admin. Left out, and why:
time with time zone (Postgrex returns the time without its zone, so
two keys can share one text), interval (no canonical text, and equal
intervals are spelled differently), money (its text depends on the
locale), enum, citext and other extension types (the schema names them
all USER-DEFINED, with no type name to cast to, and an enum orders by
declaration), network types, arrays and json.
Summary
Functions
Reads the "after" field of a request: {:ok, nil} when there is none,
{:ok, map} for a map of strings and integers (integers are read as their
text), {:error, :invalid_cursor} for anything else.
The most bytes the text of one key value may be in a cursor.
The cursor that continues after row, a map of column name to the value
as Postgrex returned it: {:ok, map}, :error when the key has no
cursor or the row lacks a key value, or {:too_long, type} when a key
value's text is past max_value_bytes/0. Every position answered here is
one from_params/1 and where/4 accept.
Whether every column of key has a type a cursor can continue from.
What a cut answer names as the reason it has no position to continue
from, for {:too_long, type}: read where a key type without a cursor is.
The types in key that no cursor continues from, each once, in key
order; [] for a key that has a cursor. A column whose type is not
known is named "unknown".
The condition for the rows strictly after values in the order of key,
with its bind values numbered from $(offset + 1). quote_ident quotes a
column name.
Types
Functions
@spec from_params(map()) :: {:ok, %{required(String.t()) => String.t()} | nil} | {:error, :invalid_cursor}
Reads the "after" field of a request: {:ok, nil} when there is none,
{:ok, map} for a map of strings and integers (integers are read as their
text), {:error, :invalid_cursor} for anything else.
@spec max_value_bytes() :: pos_integer()
The most bytes the text of one key value may be in a cursor.
@spec next_after(key(), map()) :: {:ok, %{required(String.t()) => String.t()}} | :error | {:too_long, String.t()}
The cursor that continues after row, a map of column name to the value
as Postgrex returned it: {:ok, map}, :error when the key has no
cursor or the row lacks a key value, or {:too_long, type} when a key
value's text is past max_value_bytes/0. Every position answered here is
one from_params/1 and where/4 accept.
Whether every column of key has a type a cursor can continue from.
What a cut answer names as the reason it has no position to continue
from, for {:too_long, type}: read where a key type without a cursor is.
The types in key that no cursor continues from, each once, in key
order; [] for a key that has a cursor. A column whose type is not
known is named "unknown".
@spec where( key(), %{required(String.t()) => String.t()}, non_neg_integer(), (String.t() -> String.t()) ) :: {:ok, String.t(), [String.t()]} | {:error, :invalid_cursor | :unsupported_key_type}
The condition for the rows strictly after values in the order of key,
with its bind values numbered from $(offset + 1). quote_ident quotes a
column name.
{:error, :invalid_cursor} when values does not name exactly the key's
columns or a value does not read as its column's type;
{:error, :unsupported_key_type} for a key no cursor continues from.