### ----------------------------------------------------
### -- Protocol::Postgres
### -- Licenses: Artistic-2.0
### -- Authors: Leon Timmermans
### -- File: lib/Protocol/Postgres.rakumod
### ----------------------------------------------------
### -- Chunk 1 of 3
unit module Protocol::Postgres:ver<0.0.13>:auth<zef:leont>;

enum ErrorField (:SeverityLocalized(83), :Severity(86), :ErrorCode(67), :Message(77), :Detail(68), :Hint(72), :Position(80), :InternalPosition(112), :InternalQuery(113), :Where(87), :SchemaName(115), :Table(116), :Column(99), :Datatype(100), :Constraint(110), :File(70), :Line(76), :Routine(82));
constant ErrorMap = Hash[Str, ErrorField];

our package X {
	our class Client is Exception {
		has Str:D $.message is required;
		method new(Str:D $message) {
			self.bless(:$message);
		}
	}

	our class Server is Exception {
		has Str:D $.prefix is required;
		has Str:D %.values{ErrorField} is required;
		method new(Str:D $prefix, Hash[Str,ErrorField] $values) {
			self.bless(:$prefix, :$values);
		}
		method message(--> Str) {
			"$!prefix: %!values{Message}";
		}
	}
}

my class EncodeBuffer {
	has Buf:D $!buffer = Buf.new;
	method buffer() { $!buffer }

	method write-int32(Int:D $value --> Nil) {
		$!buffer.write-int32($!buffer.elems, $value, BigEndian);
	}
	method write-int16(Int:D $value --> Nil) {
		$!buffer.write-int16($!buffer.elems, $value, BigEndian);
	}
	method write-int8(Int:D $value --> Nil) {
		$!buffer.write-int8($!buffer.elems, $value);
	}
	method write-buffer(Blob:D $value --> Nil) {
		$!buffer ~= $value;
	}
	method write-string(Str:D $value --> Nil) {
		$!buffer ~= $value.encode ~ Blob(0);
	}
}

my class DecodeBuffer {
	has Blob:D $!buffer is built is required;
	has Int $!elems = $!buffer.elems;
	has Int $!pos is built is required;

	method new(Blob:D $buffer, Int $pos) {
		self.bless(:$buffer, :$pos);
	}

	method read-int32() {
		die X::Client.new("Incomplete packet, couldn't read int32") if $!pos + 4 > $!elems;
		my $result = $!buffer.read-int32($!pos, BigEndian);
		$!pos += 4;
		$result;
	}
	method read-int16() {
		die X::Client.new("Incomplete packet, couldn't read int16") if $!pos + 2 > $!elems;
		my $result = $!buffer.read-int16($!pos, BigEndian);
		$!pos += 2;
		$result;
	}
	method read-int8() {
		die X::Client.new("Incomplete packet, couldn't read int8") if $!pos + 1 > $!elems;
		$!buffer.read-int8($!pos++);
	}
	method peek-int8() {
		die X::Client.new("Incomplete packet, couldn't read int8") if $!pos + 1 > $!elems;
		$!buffer.read-uint8($!pos);
	}
	method read-string() {
		my $current = $!pos;
		my $end = $!buffer.elems;
		$current++ while $current < $end and $!buffer[$current] != 0;
		die X::Client.new("Incomplete packet, couldn't read string") if $current + 1 > $!elems;
		my $result = $!buffer.subbuf($!pos, $current - $!pos);
		$!pos = $current + 1;
		$result.decode;
	}
	method read-buffer(Int $length) {
		die X::Client.new("Incomplete packet, couldn't read buffer") if $!pos + $length > $!elems;
		my $result = $!buffer.subbuf($!pos, $length);
		$!pos += $length;
		$result;
	}

	method remaining-bytes() {
		$!buffer.elems - $!pos;
	}
}

my role Serializable {
	method encode-to(EncodeBuffer $buffer, $value) { ... }
	method decode-from(DecodeBuffer $buffer) { ... }
}

my proto map-type(|) { * }
multi map-type(Serializable $type) { $type }

my role Serializable::Integer does Serializable {
	has Int:D $.value is required;
	method COERCE(Int:D $value) {
		self.new(:$value);
	}
}

my class Int32 does Serializable::Integer {
	method encode-to(EncodeBuffer $buffer, Int $value) {
		$buffer.write-int32($value);
	}
	method decode-from(DecodeBuffer $buffer) {
		$buffer.read-int32;
	}
}

multi map-type(Int:U) { Int32 }
multi map-type(Int:D $value) { Int32($value) }

my class Int16 does Serializable::Integer {
	method encode-to(EncodeBuffer $buffer, Int $value) {
		$buffer.write-int16($value);
	}
	method decode-from(DecodeBuffer $buffer) {
		$buffer.read-int16;
	}
}

my class Int8 does Serializable::Integer {
	method encode-to(EncodeBuffer $buffer, Int $value) {
		$buffer.write-int8($value);
	}
	method decode-from(DecodeBuffer $buffer) {
		$buffer.read-int8;
	}
}

my class String does Serializable {
	method encode-to(EncodeBuffer $buffer, Str $value) {
		$buffer.write-string($value);
	}
	method decode-from(DecodeBuffer $buffer) {
		$buffer.read-string;
	}
}
multi map-type(Str:U) { String }

my class Tail does Serializable {
	method encode-to(EncodeBuffer $buffer, Blob $value) {
		$buffer.write-buffer($value);
	}
	method decode-from(DecodeBuffer $buffer) {
		$buffer.read-buffer($buffer.remaining-bytes);
	}
}

my role Enum[Any:U $enum-type, Serializable:U $encoding-type] does Serializable {
	method encode-to(EncodeBuffer $buffer, Enumeration $value) {
		$encoding-type.encode-to($buffer, $value.value);
	}
	method decode-from(DecodeBuffer $buffer) {
		$enum-type($encoding-type.decode-from($buffer));
	}
}

my role Sequence[Any:U $raw-element-type, Serializable::Integer:U $count-type = Int16] does Serializable {
	my Serializable:U $element-type = map-type($raw-element-type);

	method encode-to(EncodeBuffer $buffer, @values) {
		$count-type.encode-to($buffer, @values.elems);
		for @values -> $value {
			$element-type.encode-to($buffer, $value);
		}
	}
	method decode-from(DecodeBuffer $buffer) {
		my $count = $count-type.decode-from($buffer);
		my @result = (^$count).map: { $element-type.decode-from($buffer) };
		@result;
	}
}
multi map-type(Array:U $array-type) { Sequence[$array-type.of] }

my role VarByte[Serializable::Integer:U $count-type] does Serializable {
	method encode-to(EncodeBuffer $buffer, Blob $value) {
		$count-type.encode-to($buffer, $value.elems);
		$buffer.write-buffer($value);
	}
	method decode-from(DecodeBuffer $buffer) {
		my $count = $count-type.decode-from($buffer);
		$count >= 0 ?? $buffer.read-buffer($count) !! Blob;
	}
}
multi map-type(Blob:U) { VarByte[Int32] }

my role Series[Any:U $raw-element-type] does Serializable {
	my Serializable:U $element-type = map-type($raw-element-type);

	method encode-to(EncodeBuffer $buffer, @values) {
		for @values -> $value {
			$element-type.encode-to($buffer, $value);
		}
		$buffer.write-int8(0);
	}
	method decode-from(DecodeBuffer $buffer) {
		my @result;
		while $buffer.peek-int8 != 0 {
			@result.push($element-type.decode-from($buffer));
		}
		$buffer.read-int8;
		@result;
	}
}

my role Mapping[Any:U $raw-key-type, Any:U $raw-value-type] does Serializable {
	my Serializable:U $key-type = map-type($raw-key-type);
	my Serializable:U $value-type = map-type($raw-value-type);

	method encode-to(EncodeBuffer $buffer, $values) {
		for %($values).sort -> $foo (:$key, :$value) {
			$key-type.encode-to($buffer, $key);
			$value-type.encode-to($buffer, $value);
		}
		$buffer.write-int8(0);
	}
	method decode-from(DecodeBuffer $buffer) {
		my %result{Any};
		while $buffer.peek-int8 != 0 {
			my $key = $key-type.decode-from($buffer);
			my $value = $value-type.decode-from($buffer);
			%result{$key} = $value;
		}
		$buffer.read-int8;
		%result;
	}
}
multi map-type(Hash:U $hash-type) { Mapping[$hash-type.keyof, $hash-type.of] }

my class Schema does Serializable {
	has Pair @.elements is required;

	method new(*@raw-elements) {
		my @elements = @raw-elements.map(-> (:$key, :$value) { $key => map-type($value) });
		self.bless(:@elements);
	}
	method encode-to(EncodeBuffer $encoder, %attributes) {
		for @!elements -> (:$key, :$value) {
			my $result = $value ?? $value.value !! %attributes{$key};
			$value.encode-to($encoder, $result);
		}
	}
	method decode-from(DecodeBuffer $decoder --> Map) {
		my %result;
		for @!elements -> (:$key, :$value) {
			with $value {
				my $decoded = $value.decode-from($decoder);
				die X::Client("Incorrect value for $key") if $decoded !== $value.value
			} else {
				%result{$key} := $value.decode-from($decoder);
			}
		}
		%result;
	}
}

my role Object[Any:U $outer] does Serializable {
	method encode-to(EncodeBuffer $encoder, $value) {
		$outer.schema.encode-to($encoder, $value.Capture.hash);
	}
	method decode-from(DecodeBuffer $decoder) {
		$outer.new(|$outer.schema.decode-from($decoder));
	}
}

multi map-type(ErrorField) { Enum[ErrorField, Int8] }

my sub postfix:<b>(Str $input) {
	$input.ord;
}

enum Format <Text Binary>;
multi map-type(Format) { Enum[Format, Int16] }

enum RequestType (:Prepared(83), :Portal(80));
multi map-type(RequestType) { Enum[RequestType, Int8] }

enum QueryStatus (:Idle(73), :Transaction(84), :Error(69));
multi map-type(QueryStatus) { Enum[QueryStatus, Int8] }

class FieldDescription {
	my $schema = Schema.new((:name(Str), :table(Int32), :column(Int16), :type(Int32), :size(Int16), :modifier(Int32), :format(Format)));
	method schema() { $schema }
	has Str:D $.name is required;
	has Int:D $.table = 0;
	has Int:D $.column = 0;
	has Int:D $.type = 0;
	has Int:D $.size = 0;
	has Int:D $.modifier = 0;
	has Format $.format = Text;
}
multi map-type(FieldDescription) { Object[FieldDescription] }

package Packet {
	role Base {
		method header() { ... }
		method !schema() { state $ = Schema.new }
		method encode(--> Blob) {
			my $buffer = EncodeBuffer.new;
			$buffer.write-int8(self.header);
			$buffer.write-int32(0);
			self!schema.encode-to($buffer, self.Capture.hash);
			my $result = $buffer.buffer;
			$result.write-int32(1, $result.elems - 1, BigEndian);
			$result;
		}
		method decode(Blob $buffer --> Base) {
			my $decoder = DecodeBuffer.new($buffer, 5);
			self.bless(|self!schema.decode-from($decoder));
		}
	}

	role Authentication does Base {
		method header() { 'R'b }
		method type() { ... }
	}

	class AuthenticationOk does Authentication {
		method type(--> 0) {}
		method !schema() { state $ = Schema.new((:0type)) }
	}
	class AuthenticationKerberosV5 does Authentication {
		method type(--> 2) {}
		method !schema() { state $ = Schema.new((:2type)) }
	}
	class AuthenticationCleartextPassword does Authentication {
		method type( --> 3) {}
		method !schema() { state $ = Schema.new((:3type)) }
	}
	class AuthenticationMD5Password does Authentication {
		method type(--> 5) {}
		method !schema() { state $ = Schema.new((:5type, :salt(Tail))) }
		has Blob:D $.salt is required;
	}
	class AuthenticationSCMCredential does Authentication {
		method type(--> 7) {}
		method !schema() { state $ = Schema.new((:7type)) }
	}
	class AuthenticationGSSContinue does Authentication {
		method type(--> 8) {}
		method !schema() { state $ = Schema.new((:8type)) }
	}
	class AuthenticationSSPI does Authentication {
		method type(--> 9) {}
		method !schema() { state $ = Schema.new((:9type)) }
	}
	class AuthenticationSASL does Authentication {
		method type(--> 10) {}
		method !schema() { state $ = Schema.new((:10type, :mechanisms(Series[Str]))) }
		has Str:D @.mechanisms is required;
	}
	class AuthenticationSASLContinue does Authentication {
		method type(--> 11) {}
		method !schema() { state $ = Schema.new((:11type, :server-payload(Tail))) }
		has Blob:D $.server-payload is required;
	}
	class AuthenticationSASLFinal does Authentication {
		method type(--> 12) {}
		method !schema() { state $ = Schema.new((:12type, :server-payload(Tail))) }
		has Blob:D $.server-payload is required;
	}

	class BackendKeyData does Base {
		method header() { 'K'b }
		method !schema() { state $ = Schema.new((:process-id(Int), :secret-key(Int))) }
		has Int:D $.process-id is required;
		has Int:D $.secret-key is required;
	}

	class Bind does Base {
		method header() { 'B'b }
		method !schema() { Schema.new((:portal(Str), :name(Str), :formats(Array[Format]), :fields(Array[Blob]), :result-formats(Array[Format]))) }
		has Str:D $.portal = '';
		has Str:D $.name = '';
		has Format @.formats = ();
		has Blob @.fields = ();
		has Format @.result-formats = ();
	}

	class BindComplete does Base {
		method header() { '2'b }
	}

	class Close does Base {
		method header() { 'C'b }
		method !schema() { state $ = Schema.new((:type(RequestType), :name(Str))) }
		has RequestType:D $.type = Prepared;
		has Str:D $.name = '';
	}

	class CloseComplete does Base {
		method header() { '3'b }
	}

	class CommandComplete does Base {
		method header() { 'C'b }
		method !schema() { state $ = Schema.new((:tag(Str))) }
		has Str:D $.tag is required;
	}

	class CopyData does Base {
		method header() { 'd'b }
		method !schema() { state $ = Schema.new((:row(Tail))) }
		has Blob:D $.row is required;
	}

	class CopyDone does Base {
		method header() { 'c'b }
	}

	class CopyFail does Base {
		method header() { 'f'b }
		method !schema() { state $ = Schema.new((:reason(Str))) }
		has Str:D $.reason is required;
	}

	role CopyResponse does Base {
		method !schema() { state $ = Schema.new((:format(Enum[Format, Int8]), :row-formats(Array[Format]))) }
		has Format:D $.format = Text;
		has Int @.row-formats = ();
	}

	class CopyInResponse does CopyResponse {
		method header() { 'G'b }
	}

	class CopyOutResponse does CopyResponse {
		method header() { 'H'b }
	}

	class CopyBothResponse does CopyResponse {
		method header() { 'W'b }
	}

	class DataRow does Base {
		method header() { 'D'b }
		method !schema() { state $ = Schema.new((:values(Array[Blob]))) }
		has Blob @.values is required;
		method decode(Blob $buffer --> Base) {
			my $decoder = DecodeBuffer.new($buffer, 5);
			my $count = $decoder.read-int16;
			my Blob @values;
			for ^$count {
				my $length = $decoder.read-int32;
				@values.push: $length >= 0 ?? $decoder.read-buffer($length) !! Blob;
			}
			self.bless(:@values);
		}
	}

	class Describe does Base {
		method header() { 'D'b }
		method !schema() { state $ = Schema.new((:type(RequestType), :name(Str))) }
		has RequestType:D $.type = Portal;
		has Str:D $.name = '';
	}

	class EmptyQueryResponse does Base {
		method header() { 'I'b }
	}

	class ErrorResponse does Base {
		method header() { 'E'b }
		method !schema() { state $ = Schema.new((:values(Hash[Str, ErrorField]))) }
		has Str %.values{ErrorField} is required;
	}

	class Execute does Base {
		method header() { 'E'b }
		method !schema() { state $ = Schema.new((:name(Str), :maximum-rows(Int))) }
		has Str:D $.name = '';
		has Int:D $.maximum-rows = 0;
	}

	class Flush does Base {
		method header() { 'F'b }
	}

	class FunctionCall does Base {
		method header() { 'F'b }
		method !schema() { state $ = Schema.new((:object-id(Int), :formats(Array[Format]), :values(Array[Blob]), :result-format(Format))) }
		has Int:D $.object-id is required;
		has Format @.formats = ();
		has Blob @.values = ();
		has Format $.result-format = Text;
	}

	class FunctionCallResponse does Base {
		method header() { 'V'b }
		method !schema() { state $ = Schema.new((:value(Blob))) }
		has Blob:D $.value is required;
	}

	class GSSResponse does Base {
		method header() { 'p'b }
		method !schema() { state $ = Schema.new((:payload(Blob))) }
		has Blob:D $.payload is required;
	}

	class NegotiateProtocolVersion does Base {
		method header() { 'v'b }
		method !schema() { state $ = Schema.new((:newest-minor-version(Int), :unknown-options(Sequence[Str, Int32]))) }
		has Int:D $.newest-minor-version is required;
		has Str @unknown-options;
	}

	class NoData does Base {
		method header() { 'n'b }
	}

	class NoticeResponse does Base {
		method header() { 'N'b }
		method !schema() { state $ = Schema.new((:values(Hash[Str, ErrorField]))) }
		has Str %.values{ErrorField} is required;
	}

	class NotificationResponse does Base {
		method header() { 'A'b }
		method !schema() { state $ = Schema.new((:sender(Int), :channel(Str), :message(Str))) }
		has Int:D $.sender is required;
		has Str:D $.channel is required;
		has Str:D $.message is required;
	}

	class ParameterDescription does Base {
		method header() { 't'b }
		method !schema() { state $ = Schema.new((:types(Array[Int]))) }
		has Int @.types is required;
	}

	class ParameterStatus does Base {
		method header() { 'S'b }
		method !schema() { state $ = Schema.new((:name(Str), :value(Str))) }
		has Str:D $.name is required;
		has Str:D $.value is required;
	}

	class Parse does Base {
		method header() { 'P'b }
		method !schema() { state $ = Schema.new((:name(Str), :query(Str), :oids(Array[Int]))) }
		has Str:D $.name = '';
		has Str:D $.query is required;
		has Int @.oids = ();
	}

	class ParseComplete does Base {
		method header() { '1'b }
	}

	class PasswordMessage does Base {
		method header() { 'p'b }
		method !schema() { state $ = Schema.new((:password(Str))) }
		has Str:D $.password is required;
	}

	class PortalSuspended does Base {
		method header() { 's'b }
	}

	class Query does Base {
		method header() { 'Q'b }
		method !schema() { state $ = Schema.new((:query(Str))) }
		has Str:D $.query is required;
	}

	class ReadyForQuery does Base {
		method header() { 'Z'b }
		method !schema() { state $ = Schema.new((:status(QueryStatus))) }
		has QueryStatus:D $.status is required;
	}

	class RowDescription does Base {
		method header() { 'T'b }
		method !schema() { state $ = Schema.new((:fields(Array[FieldDescription]))) }
		has FieldDescription @.fields is required;
	}

	class SASLInitialResponse does Base {
		method header() { 'p'b }
		method !schema() { state $ = Schema.new((:mechanism(Str), :initial-response(Blob))) }
		has Str:D $.mechanism is required;
		has Blob:D $.initial-response is required;
	}

	class SASLResponse does Base {
		method header() { 'p'b }
		method !schema() { state $ = Schema.new((:client-payload(Tail))) }
		has Blob:D $.client-payload is required;
	}

	class Sync does Base {
		method header() { 'S'b }
	}

	class Terminate does Base {
		method header() { 'X'b }
	}

	my sub get-decoder(Packet::Base $class) {
		$class.header => { $class.decode($^payload) }
	}

	my Callable %front-decoder = map &get-decoder, Packet::BackendKeyData, Packet::BindComplete, Packet::CloseComplete, Packet::CommandComplete, Packet::CopyData, Packet::CopyDone, Packet::CopyInResponse, Packet::CopyOutResponse, Packet::CopyBothResponse, Packet::DataRow, Packet::EmptyQueryResponse, Packet::ErrorResponse, Packet::FunctionCallResponse, Packet::NegotiateProtocolVersion, Packet::NoData, Packet::NoticeResponse, Packet::NotificationResponse, Packet::ParameterDescription, Packet::ParameterStatus, Packet::ParseComplete, Packet::PortalSuspended, Packet::ReadyForQuery, Packet::RowDescription;

	%front-decoder{82} = -> $payload {
		state %authentication-map = map { .type => $_ }, Packet::AuthenticationOk, Packet::AuthenticationKerberosV5, Packet::AuthenticationCleartextPassword, Packet::AuthenticationMD5Password, Packet::AuthenticationSCMCredential, Packet::AuthenticationGSSContinue, Packet::AuthenticationSSPI, Packet::AuthenticationSASL, Packet::AuthenticationSASLContinue, Packet::AuthenticationSASLFinal;
		my $type = $payload.read-int32(5, BigEndian);
		%authentication-map{$type}.decode($payload);
	}

	my Callable %back-decoder = map &get-decoder, Packet::Bind, Packet::Close, Packet::CopyData, Packet::CopyDone, Packet::CopyFail, Packet::Execute, Packet::Flush, Packet::FunctionCall, Packet::GSSResponse, Packet::Parse, Packet::PasswordMessage, Packet::Query, Packet::SASLInitialResponse, Packet::SASLResponse, Packet::Sync, Packet::Terminate;

	class Decoder {
		has Buf:D $!buffer is required;
		has Callable %!decoder-for;

		submethod BUILD(Blob :$buffer, Bool :$backend) {
			$!buffer = Buf.new($buffer // ());
			%!decoder-for = $backend ?? %back-decoder !! %front-decoder;
		}
		method add-data(Blob:D $data --> Nil) {
			$!buffer ~= $data;
		}
		method read-packet(--> Packet::Base) {
			return Packet::Base if $!buffer.elems < 5;
			my $length = $!buffer.read-int32(1, BigEndian);
			return Packet::Base if $!buffer.elems < 1 + $length;
			my $payload = $!buffer.subbuf(0, 1 + $length);
			$!buffer.subbuf-rw(0, 1 + $length) = Blob.new;
			my &decoder = %!decoder-for{$payload[0]} or die X::Client.new('Invalid message type ' ~ $payload.subbuf(0, 1).decode);
			decoder(Blob.new($payload));
		}
	}
}

package OpenPacket {
	role Base {
		method encode(--> Blob) {
			my $buffer = EncodeBuffer.new;
			$buffer.write-int32(0);
			self!schema.encode-to($buffer, self.Capture.hash);
			my $result = $buffer.buffer;
			$result.write-int32(0, $result.elems, BigEndian);
			$result;
		}
		method decode(Blob $buffer --> Base) {
			my $decoder = DecodeBuffer.new($buffer, 4);
			self.bless(|self!schema.decode-from($decoder));
		}
	}

	class CancelRequest does Base {
		method !schema() { state $ = Schema.new((:80877102id, :process-id(Int), :secret-key(Int))) }
		has Int:D $.process-id is required;
		has Int:D $.secret-key is required;
	}

	class GSSENCRequest does Base {
		method !schema() { state $ = Schema.new((:80877104id)) }
	}

	class SSLRequest does Base {
		method !schema() { state $ = Schema.new((:80877103id)) }
	}

	class StartupMessage does Base {
		method !schema() { state $ = Schema.new((:196608id, :parameters(Hash[Str, Str]))) }
		has Str %.parameters is required;
	}
}

role Type {
	method oid(--> Int) { ... }
	method type-object() { ... }
	method format() { Text }

	method encode-to-text(Any:D $value) { ... }
	multi method encode(Text, Any:D $value) {
		self.encode-to-text($value).encode;
	}
	multi method encode(Format, Any:U $value) { Blob }
	method decode-from-text(Str $string) { ... }
	multi method decode(Text, Blob:D $blob) {
		self.decode-from-text($blob.decode);
	}
	multi method decode(Format, Blob:U $blob) {
		self.type-object;
	}
}
sub type-encode(Type $type, Any $value) {
	$type.encode($type.format, $value);
}

role Type::Simple[Int:D $oid, Any:U $type] does Type {
	method oid(--> Int) { $oid }
	method type-object() { $type }
}

class Type::Bool does Type::Simple[16, Bool] {
	method encode-to-text(Bool(Any:D) $input) {
		$input ?? 't' !! 'f';
	}
	method decode-from-text(Str:D $string --> Bool) {
		$string eq 't';
	}
}

class Type::Blob does Type::Simple[17, Blob] {
	method format() { Binary }
	multi method encode(Binary, Blob $input) { $input }
	method encode-to-text(Blob $value) {
		Q{\x} ~ $value.decode('latin1').subst(/./, { .ord.fmt('%02x') }, :g);
	}
	multi method decode(Binary, Blob $input) { $input }
	multi method decode-from-text(Str $string where $string.starts-with(Q{\x})) {
		$string.substr(2).subst(/<xdigit>**2/, { :16(~$_).chr }, :g).encode('latin1');
	}
	multi method decode-from-text(Str $string) {
		$string.subst(q{''}, q{'}, :g).subst(Q{\\}, Q{\}, :g).subst(/ \\ (<[0..7]> ** 3) /, -> $/ { :8(~$1).chr }, :g).encode('latin1');
	}
}

class Type::Int does Type::Simple[20, Int] {
	method encode-to-text(Int(Cool:D) $int) {
		~$int;
	}
	method decode-from-text(Str:D $string --> Int) {
		$string.Int;
	}
}

class Type::Num does Type::Simple[701, Num] {
	method encode-to-text(Num(Cool:D) $num) {
		~$num;
	}
	method decode-from-text(Str:D $string --> Num) {
		$string.Num;
	}
}

class Type::Rat does Type::Simple[1700, Rat] {
	method encode-to-text(Rat(Cool:D) $rat) {
		~$rat;
	}
	method decode-from-text(Str:D $string --> Rat) {
		$string.Rat;
	}
}

class Type::Date does Type::Simple[1182, Date] {
	method encode-to-text(Date(Any:D) $date) {
		~$date;
	}
	method decode-from-text(Str:D $string --> Date) {
		$string.Date;
	}
}

class Type::DateTime does Type::Simple[1184, DateTime] {
	my sub to-datetime(Str $string --> DateTime) {
		$string.subst(' ', 'T').DateTime;
	}
	multi method encode-to-text(DateTime:D $datetime) {
		~$datetime;
	}
	multi method encode-to-text(Date:D $datetime) {
		~$datetime.DateTime;
	}
	multi method encode-to-text(Str:D $input) {
		~to-datetime($input);
	}
	method decode-from-text(Str:D $string --> DateTime) {
		to-datetime($string);
	}
}

class Type::JSON does Type::Simple[114, Any] {
	use JSON::Fast;

	multi method encode-to-text(Any $data) {
		to-json($data, :!pretty);
	}
	method decode-from-text(Str:D $string) {
		from-json($string);
	}
}

role Type::Array { ... }

my sub quote-string(Str:D $string) {
	'"' ~ $string.subst(Q{\}, Q{\\}, :g).subst(/\"/, '\\"', :g) ~ '"';
}
my multi encode-array($element where Type::Array|Type::Int|Type::Num|Type::Rat, @values) {
	'{' ~ @values.map({ $element.encode-to-text($^value) }).join(', ') ~ '}';
}
my multi encode-array($element, @values) {
	'{' ~ @values.map({ quote-string($element.encode-to-text($^value)) }).join(', ') ~ '}';
}

class Type::Str does Type::Simple[0, Str] {
	method encode-to-text(Str:D(Any) $input) {
		$input;
	}
	method decode-from-text(Str:D $input) {
		$input;
	}
}

role Type::Array[::Element, Int $oid] does Type {
	method oid(--> Int) { $oid }
	method type-object() { Array[Element.type-object] }
	my grammar ArrayParser {
		rule TOP {
			^ <array> $
			{ make $<array>.made }
		}
		rule array {
			'{' ~ '}' <element>* % ','
			{ make eager $<element>».made }
		}
		rule element {
			[ <value=array> | <value=string> | <value=quoted> | <value=null> ]
			{ make $<value>.made }
		}
		token string {
			<-[",{}\ ]>+
			{ make Element.decode-from-text(~$/) }
		}
		token quoted {
			'"' ~ '"' [ <str> | \\ <str=.escaped> ]*
			{ make Element.decode-from-text($<str>».made.join) }
		}
		token str {
			 <-["\\]>+
			{ make ~$/ }
		}
		token escaped {
			<["\\]>
			{ make ~$/ }
		}
		token null {
			'NULL'
			{ make Element.type-object }
		}
	}
	method encode-to-text(@values) {
		encode-array(Element, @values);
	}
	method decode-from-text(Str $string) {
		ArrayParser.parse($string).made;
	}
}

role Type::Enum[::Enum, Int $oid] does Type {
	method oid(--> Int) { $oid }
	method type-object() { Enum }

	method encode-to-text(Enum(Any) $value) {
		~$value;
	}
	method decode-from-text(Str $string) {
		Enum.WHO{$string};
	}
}

my grammar ObjectParser {
	rule TOP {
		^ <composite> $
		{ make $<composite>.made }
	}
	rule composite {
		'(' ~ ')' <element>* % ','
		{ make eager $<element>».made }
	}
	rule array {
		'{' ~ '}' <element>* % ','
		{ make eager $<element>».made }
	}
	rule element {
		[ <value=array> | <value=string> | <value=quoted> | <value=null> ]
		{ make $<value>.made }
	}
	token string {
		<-[",{}()\ ]>+
		{ make ~$/ }
	}
	token quoted {
		'"' ~ '"' [ <str> | \\ <str=.escaped> ]*
		{ make $<str>».made.join }
	}
	token str {
		 <-["\\]>+
		{ make ~$/ }
	}
	token escaped {
		<["\\]>
		{ make ~$/ }
	}
	token null {
		'NULL'
		{ make Nil }
	}
}

role Type::Composite[::Composite, Int $oid, Pair @elements, Bool $positional] does Type {
	my @names = @elements.keys;
	method oid(--> Int) { $oid }
	method type-object() { Composite }

	multi encode-attribute($element where Type::Array|Type::Int|Type::Num|Type::Rat, $value) {
		$element.encode-to-text($value) // '';
	}
	multi encode-attribute($element, $value) {
		my $raw = $element.encode-to-text($value);
		$raw.defined ?? quote-string($raw) !! '';
	}

	method encode-to-text(Type $value) {
		my @values = @names.map: { encode-attribute($value."$^name"()) };
		'(' ~ @values.join(',') ~ ')';
	}

	method decode-from-text(Str $string) {
		my $parsed = ObjectParser.parse($string) // die "Could not parse '$string'";
		if $positional {
			my @arguments = zip(@elements, $parsed.made, :with(-> $element, $raw {
				$element.value.decode-from-text($raw);
			}));
			Composite.new(|@arguments);
		} else {
			my %arguments = zip(@elements, $parsed.made, :with(-> $element, $raw {
				$element.key => $element.value.decode-from-text($raw);
			}));
			Composite.new(|%arguments);
		}
	}
}

role Type::Custom[::Custom, Int $oid, &from-string, &to-string] does Type {
	method oid(--> Int) { $oid }
	method type-object() { Custom }

	method encode-to-text(Custom $value) {
		to-string($value);
	}
	method decode-from-text(Str $string) {
		from-string($string);
	}
}

role TypeMap { ... }

my role As {
	method postgres-type-for(TypeMap $typemap) { ... }
}

my role As::Direct[::Captured] does As {
