~/bend-docscommunity

postgres.bend relies on unsafe/foreign

raw source on the hub · import bend-kit-postgres@0.1.0.2/postgres.bend as Postgres

Postgres client: protocol 3.0 over TCP or TLS, SCRAM-SHA-256, prepared queries, and a pool. Source: https://github.com/paymog/bend-kit/tree/main/postgres

6 imports
import Base
import bend-kit-bytes@0.3.1.0/bytes.bend as Bytes
import bend-kit-wire@0.4.6.1/wire.bend as Wire
import bend-kit-dns@0.6.0.2/dns.bend as Dns
import ./codec.bend as Codec
import ./scram.bend as Scram

Types

type Config source · line 12 · raw

Data

host is a name or a numeric address; TLS checks the certificate against it. With tls set, a server that refuses SSLRequest fails with NoTls rather than falling back to TCP. ms bounds each connect and read (0: none). user, pass, and db are byte strings (one Char per octet).

type Err source · line 20 · raw

Data

A server error: the SQLSTATE code (for example "42P01") and the message.

type Failure source · line 25 · raw

Type

A failed call closes its connection. Rejected carries the server's error during startup; Auth is an authentication method this client does not speak, or a SCRAM check that failed.

type Reply source · line 36 · raw

Type

A query's result. Rows holds the columns, the rows in order (None is NULL, values in text format), and the command tag ("SELECT 2", "INSERT 0 1"). Failed is an error the server reported for this query; the connection stays in step and can run the next one.

type Conn source · line 42 · raw

Type

key names the pool bucket: TLS, user, host, port, and db (never the password). status is the last ReadyForQuery byte: 73 'I' idle, 84 'T' in a transaction, 69 'E' failed one.

type Rd source · line 132 · raw

Type

RNext holds the reader after its last decode attempt; RFail a socket to close.

type Acc source · line 462 · raw

Type

type Step source · line 465 · raw

Type

type Pool source · line 582 · raw

Type

Idle connections per key, up to cap per key.

Definitions

def config source · line 16 · raw

@+host:String -> @+port:U32 -> @+user:String -> @+pass:String -> @+db:String -> Config

TLS on, 10 s deadlines.

def text source · line 46 · raw

@+s:String -> Maybe<&1, 0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes>

A parameter in text format.

def bytes source · line 49 · raw

@+s:String -> 0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes

def err.put source · line 54 · raw

@+k:U32 -> @+s:String -> @e:Err -> Err

def err.go source · line 58 · raw

@fs:List<&1, 0xf63ce82892c02964729ebb2a79be513f/codec.Notice> -> @e:Err -> Err

def err.of source · line 66 · raw

@fs:List<&1, 0xf63ce82892c02964729ebb2a79be513f/codec.Notice> -> Err

The C (SQLSTATE) and M (message) fields of an ErrorResponse.

def io.close source · line 71 · raw

@tls:Bool -> @s:Socket -> IO(Unit)

def io.send source · line 78 · raw

@tls:Bool -> @s:Socket -> @+len:U32 -> @buf:Array<U32> -> IO(Pair(Socket, Result<&1, &1, Pair(U32, String), Unit>))

def io.write source · line 85 · raw

@tls:Bool -> @s:Socket -> @data:0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes -> IO(Pair(Socket, Result<&1, &1, Pair(U32, String), Unit>))

def io.recv source · line 89 · raw

@tls:Bool -> @s:Socket -> @+ms:U32 -> IO(Pair(Socket, Result<&1, &1, Pair(U32, String), Pair(U32, Array<U32>)>))

def io.close.after source · line 96 · raw

@tls:Bool -> @m:Pair(Socket, Result<&1, &1, Pair(U32, String), Unit>) -> IO(Unit)

def close source · line 101 · raw

@c:Conn -> IO(Unit)

Sends Terminate, then closes the socket.

def abort source · line 107 · raw

@-A:Type -> @c:Conn -> @why:Failure -> IO(Result<&1, &1, Failure, A>)

def reject source · line 112 · raw

@-A:Type -> @c:Conn -> @fs:List<&1, 0xf63ce82892c02964729ebb2a79be513f/codec.Notice> -> IO(Result<&1, &1, Failure, A>)

def send.after source · line 115 · raw

@+tls:Bool -> @rd:0xf63ce82892c02964729ebb2a79be513f/codec.Reader -> @+key:String -> @+ms:U32 -> @+status:U32 -> @m:Pair(Socket, Result<&1, &1, Pair(U32, String), Unit>) -> IO(Result<&1, &1, Failure, Conn>)

def send source · line 125 · raw

@c:Conn -> @data:0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes -> IO(Result<&1, &1, Failure, Conn>)

def got.len source · line 136 · raw

@zero:Bool -> @+tls:Bool -> @+key:String -> @+ms:U32 -> @+status:U32 -> @rd:0xf63ce82892c02964729ebb2a79be513f/codec.Reader -> @s:Socket -> @+len:U32 -> @words:Array<U32> -> Rd

def got source · line 143 · raw

@+tls:Bool -> @+key:String -> @+ms:U32 -> @+status:U32 -> @rd:0xf63ce82892c02964729ebb2a79be513f/codec.Reader -> @m:Pair(Socket, Result<&1, &1, Pair(U32, String), Pair(U32, Array<U32>)>) -> Rd

def read.fail source · line 151 · raw

@tls:Bool -> @sock:Socket -> @why:Failure -> IO(Result<&1, &1, Failure, Pair(Conn, 0xf63ce82892c02964729ebb2a79be513f/codec.Back)>)

def read.loop source · line 157 · raw

@fuel:Nat -> @st:Rd -> IO(Result<&1, &1, Failure, Pair(Conn, 0xf63ce82892c02964729ebb2a79be513f/codec.Back)>)

One recv per step. NoticeResponse and ParameterStatus may arrive at any time; they are skipped.

def read source · line 187 · raw

@c:Conn -> IO(Result<&1, &1, Failure, Pair(Conn, 0xf63ce82892c02964729ebb2a79be513f/codec.Back)>)

The next message. Bytes already read are decoded before the socket is asked for more. 2^24 reads of up to 64 KiB pass Postgres's 1 GiB field limit.

def reply source · line 192 · raw

@r:Result<&1, &1, Failure, Conn> -> IO(Result<&1, &1, Failure, Pair(Conn, 0xf63ce82892c02964729ebb2a79be513f/codec.Back)>)

The next message after a send.

def exchange source · line 199 · raw

@c:Conn -> @m:0xf63ce82892c02964729ebb2a79be513f/codec.Front -> IO(Result<&1, &1, Failure, Pair(Conn, 0xf63ce82892c02964729ebb2a79be513f/codec.Back)>)

def set.status source · line 204 · raw

@c:Conn -> @+status:U32 -> Conn

def ready.loop source · line 211 · raw

@fuel:Nat -> @r:Result<&1, &1, Failure, Pair(Conn, 0xf63ce82892c02964729ebb2a79be513f/codec.Back)> -> IO(Result<&1, &1, Failure, Conn>)

BackendKeyData, then ReadyForQuery.

def ready source · line 236 · raw

@c:Conn -> IO(Result<&1, &1, Failure, Conn>)

def auth.done source · line 242 · raw

@r:Result<&1, &1, Failure, Pair(Conn, 0xf63ce82892c02964729ebb2a79be513f/codec.Back)> -> IO(Result<&1, &1, Failure, Conn>)

AuthenticationOk, after a password or the SCRAM exchange.

def sasl.verified source · line 255 · raw

@c:Conn -> @v:Result<&1, &1, Pair(U32, String), Unit> -> IO(Result<&1, &1, Failure, Conn>)

def sasl.final source · line 265 · raw

@fin:0xf63ce82892c02964729ebb2a79be513f/scram.Final -> @r:Result<&1, &1, Failure, Pair(Conn, 0xf63ce82892c02964729ebb2a79be513f/codec.Back)> -> IO(Result<&1, &1, Failure, Conn>)

AuthenticationSASLFinal carries the server signature, which proves the server knew the password.

def sasl.respond source · line 280 · raw

@c:Conn -> @f:Result<&1, &1, Pair(U32, String), Pair(0xf63ce82892c02964729ebb2a79be513f/scram.Final, String)> -> IO(Result<&1, &1, Failure, Conn>)

def sasl.cont source · line 289 · raw

@st:0xf63ce82892c02964729ebb2a79be513f/scram.First -> @+pass:String -> @r:Result<&1, &1, Failure, Pair(Conn, 0xf63ce82892c02964729ebb2a79be513f/codec.Back)> -> IO(Result<&1, &1, Failure, Conn>)

def sasl.first source · line 304 · raw

@c:Conn -> @+pass:String -> @f:Result<&1, &1, Pair(U32, String), Pair(0xf63ce82892c02964729ebb2a79be513f/scram.First, String)> -> IO(Result<&1, &1, Failure, Conn>)

def sasl source · line 313 · raw

@ok:Bool -> @c:Conn -> @+user:String -> @+pass:String -> IO(Result<&1, &1, Failure, Conn>)

def has.mech source · line 322 · raw

@xs:List<&1, 0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes> -> Bool

def auth source · line 330 · raw

@+user:String -> @+pass:String -> @r:Result<&1, &1, Failure, Pair(Conn, 0xf63ce82892c02964729ebb2a79be513f/codec.Back)> -> IO(Result<&1, &1, Failure, Conn>)

The first message after StartupMessage picks the authentication method.

def startup source · line 351 · raw

@c:Conn -> @+user:String -> @+pass:String -> @+db:String -> IO(Result<&1, &1, Failure, Conn>)

def tls.done source · line 356 · raw

@+key:String -> @+ms:U32 -> @+user:String -> @+pass:String -> @+db:String -> @m:Pair(Socket, Result<&1, &1, Pair(U32, String), Unit>) -> IO(Result<&1, &1, Failure, Conn>)

def plain.fail source · line 366 · raw

@s:Socket -> @why:Failure -> IO(Result<&1, &1, Failure, Conn>)

def ssl.byte source · line 372 · raw

@s:Socket -> @+key:String -> @+host:String -> @+user:String -> @+pass:String -> @+db:String -> @+ms:U32 -> @r:Pair(0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes, Maybe<&2, U32>) -> IO(Result<&1, &1, Failure, Conn>)

'S' starts TLS; 'N' refuses it. Anything else (an old server's error) is not an answer.

def ssl.answer source · line 389 · raw

@+key:String -> @+host:String -> @+user:String -> @+pass:String -> @+db:String -> @+ms:U32 -> @a:Pair(Socket, Result<&1, &1, Pair(U32, String), Pair(U32, Array<U32>)>) -> IO(Result<&1, &1, Failure, Conn>)

The answer is one byte, read alone so no byte sent before the TLS handshake is taken as data.

def ssl.sent source · line 397 · raw

@+key:String -> @+host:String -> @+user:String -> @+pass:String -> @+db:String -> @+ms:U32 -> @m:Pair(Socket, Result<&1, &1, Pair(U32, String), Unit>) -> IO(Result<&1, &1, Failure, Conn>)

def ssl.ask source · line 407 · raw

@s:Socket -> @+key:String -> @+host:String -> @+user:String -> @+pass:String -> @+db:String -> @+ms:U32 -> IO(Result<&1, &1, Failure, Conn>)

def dialed source · line 412 · raw

@r:Result<&1, &1, Pair(U32, String), Socket> -> @tls:Bool -> @+key:String -> @+host:String -> @+user:String -> @+pass:String -> @+db:String -> @+ms:U32 -> IO(Result<&1, &1, Failure, Conn>)

def dial.after source · line 423 · raw

@r:Result<&1, &1, Pair(U32, String), Socket> -> @+ip:String -> @+port:U32 -> @+ms:U32 -> IO(Result<&1, &1, Pair(U32, String), Socket>)

def dial.more source · line 430 · raw

@prev:IO(Result<&1, &1, Pair(U32, String), Socket>) -> @+ip:String -> @+port:U32 -> @+ms:U32 -> IO(Result<&1, &1, Pair(U32, String), Socket>)

def dial source · line 436 · raw

@ips:List<&2, String> -> @+port:U32 -> @+ms:U32 -> @acc:IO(Result<&1, &1, Pair(U32, String), Socket>) -> IO(Result<&1, &1, Pair(U32, String), Socket>)

Each address in turn, until one connects.

def key source · line 443 · raw

@cfg:Config -> String

def connect.to source · line 448 · raw

@+k:String -> @cfg:Config -> IO(Result<&1, &1, Failure, Conn>)

def connect source · line 457 · raw

@+cfg:Config -> IO(Result<&1, &1, Failure, Conn>)

TCP, SSLRequest and TLS when cfg.tls (checking the certificate and host name), StartupMessage, cleartext or SCRAM-SHA-256 authentication, then ReadyForQuery. MD5 is refused as Auth.

def acc.fields source · line 470 · raw

@a:Acc -> @fs:List<&1, 0xf63ce82892c02964729ebb2a79be513f/codec.Field> -> Acc

def acc.row source · line 475 · raw

@a:Acc -> @cols:List<&1, Maybe<&1, 0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes>> -> Acc

Rows are kept newest first until ReadyForQuery.

def acc.tag source · line 479 · raw

@a:Acc -> @t:0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes -> Acc

def err.first source · line 483 · raw

@old:Maybe<&1, Err> -> @e:Err -> Maybe<&1, Err>

def acc.err source · line 490 · raw

@a:Acc -> @e:Err -> Acc

def absorb source · line 494 · raw

@a:Acc -> @m:0xf63ce82892c02964729ebb2a79be513f/codec.Back -> Step

def absorbed source · line 521 · raw

@a:Acc -> @r:Result<&1, &1, Failure, Pair(Conn, 0xf63ce82892c02964729ebb2a79be513f/codec.Back)> -> Result<&1, &1, Failure, Pair(Conn, Step)>

def finish source · line 528 · raw

@c:Conn -> @+status:U32 -> @a:Acc -> IO(Result<&1, &1, Failure, Pair(Conn, Reply)>)

def collect source · line 538 · raw

@fuel:Nat -> @r:Result<&1, &1, Failure, Pair(Conn, Step)> -> IO(Result<&1, &1, Failure, Pair(Conn, Reply)>)

After an ErrorResponse the server skips to Sync, so reading on to ReadyForQuery keeps the connection in step. A message that has no place in the reply closes it.

def query.bytes source · line 562 · raw

@+sql:String -> @params:List<&1, Maybe<&1, 0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes>> -> 0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes

The whole extended-query exchange for one statement, in one write.

def query source · line 573 · raw

@c:Conn -> @+sql:String -> @params:List<&1, Maybe<&1, 0xb7603dcfa1d7f60d01e06c928738cc73/bytes.Bytes>> -> IO(Result<&1, &1, Failure, Pair(Conn, Reply)>)

Runs sql (a byte string, parameters as $1, $2, ...) as an unnamed prepared statement: Parse, Bind, Describe, Execute, Sync. Parameters and results are in text format; None is NULL. A server error is a Failed reply on a live connection; a Failure has closed it.

def pool.new.with source · line 585 · raw

@+cap:U32 -> Pool

def pool.new source · line 588 · raw

Pool

def pool.take source · line 591 · raw

@+cap:U32 -> @+k:String -> @r:Pair(Map<&1, List<&1, Conn>>, Maybe<&1, List<&1, Conn>>) -> Pair(Pool, Maybe<&1, Conn>)

def pool.opened source · line 603 · raw

@p:Pool -> @r:Result<&1, &1, Failure, Conn> -> IO(Pair(Pool, Result<&1, &1, Failure, Conn>))

def pool.got source · line 606 · raw

@cfg:Config -> @r:Pair(Pool, Maybe<&1, Conn>) -> IO(Pair(Pool, Result<&1, &1, Failure, Conn>))

def pool.get source · line 618 · raw

@p:Pool -> @+cfg:Config -> IO(Pair(Pool, Result<&1, &1, Failure, Conn>))

The idle connection given back last, or a new one. ponytail: an idle connection the server has since closed fails on its first query; ping before reuse if that bites.

def conns.cut source · line 624 · raw

@xs:List<&1, Conn> -> @n:Nat -> IO(List<&1, Conn>)

The first n connections; the rest are closed.

def pool.keep source · line 639 · raw

@+cap:U32 -> @+k:String -> @c:Conn -> @r:Pair(Map<&1, List<&1, Conn>>, Maybe<&1, List<&1, Conn>>) -> IO(Pool)

def pool.clean source · line 645 · raw

@clean:Bool -> @p:Pool -> @c:Conn -> IO(Pool)

def pool.left source · line 656 · raw

@p:Pool -> @+tls:Bool -> @sock:Socket -> @+key:String -> @+ms:U32 -> @+status:U32 -> @r:Pair(0xf63ce82892c02964729ebb2a79be513f/codec.Reader, U32) -> IO(Pool)

def pool.put source · line 662 · raw

@p:Pool -> @c:Conn -> IO(Pool)

Gives c back for reuse. A connection with unread bytes is out of step, and one inside a transaction would leak it to the next user, so either is closed instead.

def pool.close.go source · line 666 · raw

@xs:List<&1, List<&1, Conn>> -> IO(Unit)

def pool.close source · line 675 · raw

@p:Pool -> IO(Unit)