minato_codec (minato v0.18.6)
View SourceErlang terms to PostgreSQL wire bytes and back, for both the binary and the text wire format.
This module is pure. It does no I/O, starts no process, reads no ETS table and keeps no state, so a codec can be tested exhaustively without a database and reused wherever the bytes come from.
The two formats
PostgreSQL will send and accept every value in either a binary or a text representation, chosen per parameter and per result column. minato implements both. Binary is what the protocol layer asks for on the types it knows; text is what makes an unknown type still usable, because any value at all can be carried as its text representation.
preferred_format/1 says which format to request for a given OID.
NULL
null is the NULL sentinel in both directions, and it is the only one.
NULL is not a value in the wire format: it is a length of -1 where the bytes
would be. encode/4 therefore returns the atom null rather than iodata()
when the value is null, and the caller writing the message body turns that
into a length of -1. decode/4 accepts null in the same place and returns
null.
Errors
Every function in this module either returns a value or raises. A codec failure
is error({minato_codec, Report}), where Report is a map with type,
reason and value keys.
Raising rather than returning {error, _} is deliberate: decoding a result set
calls decode/4 once per cell, and a failure there means the bytes disagree
with the OID the server itself announced, which is a bug and not a condition to
branch on. One try around a whole row costs nothing, a tagged tuple per cell
does.
Options
#{uuid_format => string | binary,
datetime_format => datetime | microseconds,
numeric_format => binary | float}All three are decode-time options; encoding accepts every form regardless.
numeric_formatdefaults tobinary, giving the exact decimal text PostgreSQL sent.floatconverts it, which loses exactness and is the right answer for an average or a ratio and the wrong one for money. A client that did this by default would be quietly lossy about the one type chosen for not being lossy, which is why it has to be asked for.uuid_formatdefaults tostring, giving the 36 character hyphenated form.binarygives the 16 raw bytes.datetime_formatdefaults todatetime, giving{Hour, Minute, Second}and{{Year, Month, Day}, {Hour, Minute, Second}}tuples with sub-second precision discarded.microsecondsis lossless and gives integers. Seeminato_codec_datetimefor exactly how the two differ.
Types covered
bool, bytea, int2, int4, int8, float4, float8, numeric, text,
varchar, json, jsonb, uuid, date, time, timestamp, timestamptz,
and the array type of each of them.
Examples
1> minato_codec:encode(23, 42, binary).
<<0,0,0,42>>
2> minato_codec:decode(23, <<0,0,0,42>>, binary).
42
3> minato_codec:decode(1009, ~"{a,NULL,\"b,c\"}", text).
[<<"a">>,null,<<"b,c">>]
4> minato_codec:encode(2950, ~"0196f0a9-1c4f-7e21-9b3a-6d2c5e8f1a44", binary).
<<1,150,240,169,28,79,126,33,155,58,109,44,94,143,26,68>>
Summary
Types
A calendar date, the shape of calendar:date/0 without the range refinements.
A date and a time, the shape of calendar:datetime/0.
Which of the two PostgreSQL wire representations a call is about.
Decode-time options. See the module documentation.
The map carried by an error({minato_codec, Report}) exception.
A wall clock time, the shape of calendar:time/0 without the range refinements.
Every Erlang shape a codec in minato produces or accepts.
Functions
Raise a codec failure.
decode/4 with the default options.
Decode wire bytes of the type named by Oid to an Erlang term.
A decoder is the type resolution for one column, done once.
encode/4 with the default options.
Encode an Erlang term to the wire bytes of the type named by Oid.
The wire format to request for a column or parameter of this type.
Whether minato has a codec for this OID.
Flatten a value the caller offered as iodata() into a binary.
Types
A calendar date, the shape of calendar:date/0 without the range refinements.
A date and a time, the shape of calendar:datetime/0.
-opaque decoder()
One column's type resolution, from decoder/3.
-type format() :: binary | text.
Which of the two PostgreSQL wire representations a call is about.
-type opts() ::
#{uuid_format => string | binary,
numeric_format => binary | float,
datetime_format => datetime | microseconds}.
Decode-time options. See the module documentation.
The map carried by an error({minato_codec, Report}) exception.
A wall clock time, the shape of calendar:time/0 without the range refinements.
-type value() :: null | boolean() | integer() | float() | binary() | nan | infinity | neg_infinity | date() | datetime() | [value()].
Every Erlang shape a codec in minato produces or accepts.
Functions
Raise a codec failure.
Exported so that the codec modules share one exception shape. Not part of the API a caller of minato uses.
Decode one value with a decoder from decoder/3.
-spec decode(minato_oid:oid(), binary() | null, format()) -> value().
decode/4 with the default options.
-spec decode(minato_oid:oid(), binary() | null, format(), opts()) -> value().
Decode wire bytes of the type named by Oid to an Erlang term.
null in, null out. Raises error({minato_codec, t:report/0}) when the bytes
do not fit the type.
For an OID minato has no codec for, the text format returns the bytes unchanged so the caller can parse them, and the binary format raises.
-spec decoder(minato_oid:oid(), format(), opts()) -> decoder().
A decoder is the type resolution for one column, done once.
decode/4 asks minato_oid:name/1 what a type is on every value, which for a
result set is the same answer per column repeated once per row: a thousand rows
of two columns resolve the same two types two thousand times. The column types
are fixed by the RowDescription before the first row arrives, so they can be
resolved there instead, and decode/2 applies the answer.
-spec encode(minato_oid:oid(), term(), format()) -> iodata() | null.
encode/4 with the default options.
-spec encode(minato_oid:oid(), term(), format(), opts()) -> iodata() | null.
Encode an Erlang term to the wire bytes of the type named by Oid.
Returns the atom null for a null value, because NULL is carried by a length
of -1 and has no bytes of its own. Raises error({minato_codec, t:report/0})
when the term does not fit the type.
For an OID minato has no codec for, the text format passes iodata() through
unchanged and the binary format raises: minato cannot invent a binary
representation for a type it does not know, but it can always let the server
parse the text one.
-spec preferred_format(minato_oid:oid()) -> format().
The wire format to request for a column or parameter of this type.
binary for every type minato has a codec for, text for everything else, so
that an unrecognised type still arrives in a form the caller can read.
-spec supports(minato_oid:oid()) -> boolean().
Whether minato has a codec for this OID.
Flatten a value the caller offered as iodata() into a binary.
Written out rather than handed to iolist_to_binary/1 so that a caller who
passes something that is not iodata() gets a codec failure naming the type
instead of a bare badarg. Binaries, which is what every hot path passes,
never reach it.
Exported so that the codec modules share one implementation. Not part of the API a caller of minato uses.