@amritk/nish-aarch64-darwin 0.25.0 → 0.27.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,7 +17,9 @@
17
17
  * // PG_READY: authenticated; prepare statements and send queries
18
18
  * // conn.prepare("by_id", "select id, name from users where id = $1", [23]); conn.sync()
19
19
  * // conn.bindStart("by_id"); conn.paramInt(7); conn.bindEnd(); conn.sync()
20
+ * // or conn.bindStartBinary("by_id") for binary result columns
20
21
  * // PG_ROW: conn.int(0), conn.colStart(1) .. + conn.colLength(1) in conn.inputBuffer()
22
+ * // after bindStartBinary: conn.binaryInt(0), the text column as before
21
23
  * // PG_COMPLETE: conn.tagRows(); PG_ERROR: conn.lastError.code
22
24
  * // PG_GROUP_DONE: one sync()'s group has been answered
23
25
  * event = conn.next();
@@ -57,18 +59,33 @@
57
59
  * does not need: a program prepares a statement and executes it in the same
58
60
  * flight.
59
61
  *
60
- * **Text results, no copies.** Every parameter and every result column is in
61
- * text format. A row is read where it lies in the input buffer: `next()` frames
62
- * each message once, validates its layout, and records each column's start and
63
- * length in two reused arrays, so a row costs no allocation. `int`, `bool`,
64
- * `isNull` and `PgText.timestamp` decode a column in place; `PgArrayWalker`
65
- * walks a one-dimensional `text[]` in place. `columnText(c)` is the one that
66
- * allocates, a string. An array parameter is a text array literal too:
67
- * `paramIntArray` writes an `int8[]` from an `i64[]` window, and `arrayStart`,
68
- * `arrayElement`, `arrayInt`, `arrayNull` and `arrayEnd` build an array one
69
- * element at a time into the Bind, so a list needs no string built first. A
70
- * program whose element was refused for room can drop the whole bind,
71
- * unsent, with `bindCancel`.
62
+ * **Text results, no copies.** Every parameter is in text format, and so is
63
+ * every result column unless the bind asks otherwise. A row is read where it
64
+ * lies in the input buffer: `next()` frames each message once, validates its
65
+ * layout, and records each column's start and length in two reused arrays, so
66
+ * a row costs no allocation. `int`, `bool`, `isNull` and `PgText.timestamp`
67
+ * decode a column in place; `PgArrayWalker` walks a one-dimensional `text[]`
68
+ * in place. `columnText(c)` is the one that allocates, a string. An array
69
+ * parameter is a text array literal too: `paramIntArray` writes an `int8[]`
70
+ * from an `i64[]` window, and `arrayStart`, `arrayElement`, `arrayInt`,
71
+ * `arrayNull` and `arrayEnd` build an array one element at a time into the
72
+ * Bind, so a list needs no string built first. A program whose element was
73
+ * refused for room can drop the whole bind, unsent, with `bindCancel`.
74
+ *
75
+ * **Binary results.** A bind opened with `bindStartBinary` instead of
76
+ * `bindStart` asks for every result column in binary format: an `int8` is
77
+ * eight bytes, a `timestamptz` eight bytes of microseconds since 2000, a
78
+ * `bool` one byte, so there is nothing to parse and less on the wire.
79
+ * `binaryInt`, `binaryBool` and `binaryFloat` read them in place, and
80
+ * `PgBinary`'s `timestamp` and `iso` write a time as `PgText.timestamp` does.
81
+ * A `text` column is the same bytes in either format, so `columnText` and the
82
+ * column windows read it unchanged. The program knows which of its binds
83
+ * asked for which, and reads each execution's rows with the matching
84
+ * decoders. A `PgBatcher` with `binary` set opens each batch this way and
85
+ * reads its routing ordinal with `binaryInt`, so its rows are binary too.
86
+ * `PgBinaryArray` walks a one-dimensional `int2[]`, `int4[]` or `int8[]`
87
+ * column in its binary layout, and refuses any other array (a `text[]` or a
88
+ * multi-dimensional one among them).
72
89
  *
73
90
  * **Authentication.** SCRAM-SHA-256 (RFC 5802, RFC 7677) or none (`trust`).
74
91
  * The password arrives as a `Secret<u8[]>`; the connection keeps a copy in a
@@ -100,8 +117,9 @@
100
117
  * that needs a socket, `connect(slot, nonce)` starts its connection, and the
101
118
  * program moves its bytes as above through `connection(slot)`. A request is a
102
119
  * ticket from `request()`; `dispatch()` gives the oldest to the ready
103
- * connection with the fewest groups in flight, the program builds its group and
104
- * ends it with `send()`, and `next(slot)` reads that connection's events with
120
+ * connection with the fewest groups in flight (or, with `PgConfig.pack`, to the
121
+ * first ready one with room, so a burst fills few connections in fuller
122
+ * writes), the program builds its group and ends it with `send()`, and `next(slot)` reads that connection's events with
105
123
  * `ticket` naming the request. Statements given to `register` are prepared on
106
124
  * each connection ahead of its first request. A connection that fails reports
107
125
  * `PG_LOST` for each request it took, then `PG_FAILED`; the program closes the
@@ -127,7 +145,7 @@
127
145
  * Written from PostgreSQL's "Frontend/Backend Protocol" chapter (version 18)
128
146
  * and RFC 5802 and RFC 7677; not ported from another implementation.
129
147
  */
130
- import { copyBytes, getI32BE, getU16BE, setI32BE, setU16BE } from "nish/bytes"
148
+ import { copyBytes, getF64BE, getI32BE, getI64BE, getU16BE, setI32BE, setU16BE } from "nish/bytes"
131
149
  import { base64StdPadDecode, base64StdPadEncode } from "nish/crypto/base64url"
132
150
  import { timingSafeEqual } from "nish/crypto/ct"
133
151
  import { hmacSha256, pbkdf2HmacSha256 } from "nish/crypto/hmac"
@@ -218,6 +236,20 @@ const PG_NONCE_BYTES: i32 = 18
218
236
  // take: kept free under `maxOutput` by every other builder call, so that
219
237
  // `bindEnd()` and `sync()` are never refused for room.
220
238
  const PG_TAIL: i32 = 17
239
+ // What a binary bind's end takes beyond `PG_TAIL`: its one result format code,
240
+ // kept free from `bindStartBinary` on.
241
+ const PG_BINARY_TAIL: i32 = 2
242
+ // The result format code for binary.
243
+ const PG_FORMAT_BINARY: i32 = 1
244
+ // The element types `PgBinaryArray` reads, by OID: int8, int2 and int4.
245
+ const PG_OID_INT8: i32 = 20
246
+ const PG_OID_INT2: i32 = 21
247
+ const PG_OID_INT4: i32 = 23
248
+ // The bytes a binary array's header takes before its dimensions: ndim,
249
+ // hasnull and the element OID, one i32 each; one dimension adds its length
250
+ // and lower bound, another two i32s.
251
+ const PG_ARRAY_HEADER: i32 = 12
252
+ const PG_ARRAY_DIM: i32 = 8
221
253
  // The largest `maxMessage` and `maxOutput`. The input buffer doubles up to one
222
254
  // past them; the output buffer doubles only while twice its size fits in an i32.
223
255
  const PG_MAX_CAP: i32 = 1073741823
@@ -238,6 +270,15 @@ export class PgConfig {
238
270
  maxMessage: i32 = 1048576
239
271
  /** The most groups waiting for their answers at once. */
240
272
  maxInFlight: i32 = 16
273
+ /**
274
+ * Read by `PgPool` only: whether `dispatch()` packs requests onto the
275
+ * first ready connection with room, filling it to `maxInFlight` before the
276
+ * next, rather than spreading them over every connection, fewest in flight
277
+ * first. Fewer connections in use send fuller writes, fewer packets, which
278
+ * pays on a lossy link to the server; a connection is still wanted only once
279
+ * every ready one is full.
280
+ */
281
+ pack: boolean = false
241
282
  /** The input buffer's first size, in bytes; it grows to a message's length, up to `maxMessage` + 1. */
242
283
  inputSize: i32 = 8192
243
284
  /** The widest row; past it, `PG_ERR_TOO_BIG`. PostgreSQL's own limit is 1664. */
@@ -677,6 +718,292 @@ export class PgText {
677
718
  }
678
719
  }
679
720
 
721
+ // The days from 0000-03-01, where the civil calendar's year starts for the
722
+ // conversion below, to 2000-01-01, where PostgreSQL's binary times count from.
723
+ const PG_EPOCH_DAYS: i64 = 730425
724
+ // The first and last days `PgBinary.iso` writes, counted from 2000-01-01:
725
+ // 0001-01-01 and 9999-12-31, the years four digits spell.
726
+ const PG_FIRST_DAY: i64 = -730119
727
+ const PG_LAST_DAY: i64 = 2921939
728
+
729
+ /** `value`'s last `width` decimal digits into `out[at, at + width)`. */
730
+ const pgDigits = (out: u8[], at: i32, value: i32, width: i32): void => {
731
+ let v: i32 = value
732
+ for (let i: i32 = width - 1; i >= 0; i--) {
733
+ out[at + i] = toU8((v % 10) + 48)
734
+ v = v / 10
735
+ }
736
+ }
737
+
738
+ /**
739
+ * Decoders for PostgreSQL's binary format, in place, for the rows of a bind
740
+ * that `bindStartBinary` opened; a bad input sets `failed`, which stays set.
741
+ * A `text` or `varchar` column's bytes are the same in either format, so
742
+ * `columnText`, `colStart` and `colLength` read it as they read a text one.
743
+ */
744
+ export class PgBinary {
745
+ /** Whether a decode since the last `reset()` was given something it could not read. */
746
+ failed: boolean = false
747
+
748
+ /** Clears `failed`. */
749
+ reset(): void {
750
+ this.failed = false
751
+ }
752
+
753
+ // Whether `[start, start + length)` is inside `b`; sets `failed` when not.
754
+ private inside(b: u8[], start: i32, length: i32): boolean {
755
+ if (start < 0 || length < 0 || start > toI32(b.length) - length) {
756
+ this.failed = true
757
+ return false
758
+ }
759
+ return true
760
+ }
761
+
762
+ /**
763
+ * An `int2`, `int4` or `int8` column, told apart by its length (2, 4 or 8
764
+ * bytes, big-endian two's complement); 0 and `failed` for any other length,
765
+ * NULL's -1 among them. A `timestamptz` or `timestamp` reads as an `int8`:
766
+ * microseconds since 2000-01-01 00:00:00 UTC, which `iso` writes out.
767
+ */
768
+ int(b: u8[], start: i32, length: i32): i64 {
769
+ if ((length !== 2 && length !== 4 && length !== 8) || !this.inside(b, start, length)) {
770
+ this.failed = true
771
+ return 0
772
+ }
773
+ if (length === 8) {
774
+ return getI64BE(b, start)
775
+ }
776
+ if (length === 4) {
777
+ return toI64(getI32BE(b, start))
778
+ }
779
+ const v: i32 = toI32(getU16BE(b, start))
780
+ return toI64(v >= 32768 ? v - 65536 : v)
781
+ }
782
+
783
+ /** A `bool` column: one byte, 1 or 0; false and `failed` otherwise. */
784
+ bool(b: u8[], start: i32, length: i32): boolean {
785
+ if (length === 1 && this.inside(b, start, length)) {
786
+ const c: i32 = toI32(b[start])
787
+ if (c === 1) {
788
+ return true
789
+ }
790
+ if (c === 0) {
791
+ return false
792
+ }
793
+ }
794
+ this.failed = true
795
+ return false
796
+ }
797
+
798
+ /** A `float8` column: eight bytes, IEEE 754 big-endian; 0 and `failed` otherwise. */
799
+ float(b: u8[], start: i32, length: i32): f64 {
800
+ if (length !== 8 || !this.inside(b, start, length)) {
801
+ this.failed = true
802
+ return 0
803
+ }
804
+ return getF64BE(b, start)
805
+ }
806
+
807
+ /**
808
+ * A `timestamptz` or `timestamp` column, written to `out[at, at + 24)` as
809
+ * `iso` writes it. Answers 24, or -1 and `failed` for a column that is not
810
+ * eight bytes or a time `iso` refuses.
811
+ */
812
+ timestamp(b: u8[], start: i32, length: i32, out: u8[], at: i32): i32 {
813
+ if (length !== 8 || !this.inside(b, start, length)) {
814
+ this.failed = true
815
+ return -1
816
+ }
817
+ return this.iso(getI64BE(b, start), out, at)
818
+ }
819
+
820
+ /**
821
+ * `micros`, microseconds since 2000-01-01 00:00:00 UTC, written to
822
+ * `out[at, at + 24)` as `2024-01-02T03:04:05.123Z`: the microseconds cut,
823
+ * not rounded, to milliseconds, as `PgText.timestamp` cuts the same time.
824
+ * Answers 24, or -1 and `failed` with `out` untouched for a year outside
825
+ * 0001–9999 (`infinity` and `-infinity` among them) or no room in `out`.
826
+ */
827
+ iso(micros: i64, out: u8[], at: i32): i32 {
828
+ if (at < 0 || at > toI32(out.length) - 24) {
829
+ this.failed = true
830
+ return -1
831
+ }
832
+ // Floored, so that a time before 2000 keeps its day and is cut toward the past.
833
+ const dayMicros: i64 = toI64(86400000) * 1000
834
+ let days: i64 = micros / dayMicros
835
+ let rest: i64 = micros % dayMicros
836
+ if (rest < 0) {
837
+ days -= 1
838
+ rest += dayMicros
839
+ }
840
+ if (days < PG_FIRST_DAY || days > PG_LAST_DAY) {
841
+ this.failed = true
842
+ return -1
843
+ }
844
+ // The civil date of a day count: eras of 400 years (146097 days), each
845
+ // year starting on 1 March so that a leap day is its last.
846
+ const z: i64 = days + PG_EPOCH_DAYS
847
+ const era: i64 = z / 146097
848
+ const doe: i64 = z - era * 146097
849
+ const yoe: i64 = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365
850
+ const doy: i64 = doe - (365 * yoe + yoe / 4 - yoe / 100)
851
+ const mp: i64 = (5 * doy + 2) / 153
852
+ const day: i64 = doy - (153 * mp + 2) / 5 + 1
853
+ const month: i64 = mp < 10 ? mp + 3 : mp - 9
854
+ const year: i64 = yoe + era * 400 + (month <= 2 ? 1 : 0)
855
+ const millis: i32 = toI32(rest / 1000)
856
+ pgDigits(out, at, toI32(year), 4)
857
+ out[at + 4] = toU8(45)
858
+ pgDigits(out, at + 5, toI32(month), 2)
859
+ out[at + 7] = toU8(45)
860
+ pgDigits(out, at + 8, toI32(day), 2)
861
+ out[at + 10] = toU8(84)
862
+ pgDigits(out, at + 11, millis / 3600000, 2)
863
+ out[at + 13] = toU8(58)
864
+ pgDigits(out, at + 14, (millis / 60000) % 60, 2)
865
+ out[at + 16] = toU8(58)
866
+ pgDigits(out, at + 17, (millis / 1000) % 60, 2)
867
+ out[at + 19] = toU8(46)
868
+ pgDigits(out, at + 20, millis % 1000, 3)
869
+ out[at + 23] = toU8(90)
870
+ return 24
871
+ }
872
+ }
873
+
874
+ /**
875
+ * A walk over a one-dimensional `int2[]`, `int4[]` or `int8[]` column in
876
+ * PostgreSQL's binary array layout, in place: `begin` the column, then
877
+ * `next()` until `PG_ARRAY_END` or `PG_ARRAY_BAD`, and `int()` decodes the
878
+ * current element through `binary`. The header is `ndim`, `hasnull` and the
879
+ * element OID, then for `ndim` 1 the dimension's length and lower bound (the
880
+ * bound is read and not used); each element is a length word and its bytes,
881
+ * and a NULL is a length of -1 with no bytes. `ndim` 0 is the empty array.
882
+ * Every refusal sets `binary.failed`: an `ndim` above 1, another element
883
+ * type, an element whose length is not its type's size, a NULL when
884
+ * `hasnull` is 0, a column cut short, or bytes after the last element. The
885
+ * walk allocates nothing, and it reads nothing outside the column.
886
+ */
887
+ export class PgBinaryArray {
888
+ /** The number of elements the header gives: 0 for an empty array. */
889
+ count: i32 = 0
890
+ /** The element type's OID, 20, 21 or 23, once `begin` has read a header it accepts. */
891
+ elementOid: i32 = 0
892
+ /** The current element's first byte in the buffer. */
893
+ start: i32 = 0
894
+ /** The current element's length: its type's size, or 0 for a NULL. */
895
+ length: i32 = 0
896
+ private binary: PgBinary
897
+ private b: u8[]
898
+ private pos: i32 = 0
899
+ private end: i32 = 0
900
+ private size: i32 = 0
901
+ private seen: i32 = 0
902
+ private hasNull: boolean = false
903
+ private bad: boolean = true
904
+
905
+ constructor(binary: PgBinary) {
906
+ this.binary = binary
907
+ this.b = []
908
+ }
909
+
910
+ /** Starts a walk over `b[start, start + length)`; a header this walker does not read is refused. */
911
+ begin(b: u8[], start: i32, length: i32): void {
912
+ this.b = b
913
+ this.count = 0
914
+ this.elementOid = 0
915
+ this.seen = 0
916
+ this.start = 0
917
+ this.length = 0
918
+ this.bad = !(start >= 0 && length >= PG_ARRAY_HEADER && start <= toI32(b.length) - length)
919
+ if (this.bad) {
920
+ this.refuse()
921
+ return
922
+ }
923
+ const ndim: i32 = getI32BE(b, start)
924
+ const hasNull: i32 = getI32BE(b, start + 4)
925
+ const oid: i32 = getI32BE(b, start + 8)
926
+ this.size = 0
927
+ if (oid === PG_OID_INT8) {
928
+ this.size = 8
929
+ } else if (oid === PG_OID_INT2) {
930
+ this.size = 2
931
+ } else if (oid === PG_OID_INT4) {
932
+ this.size = 4
933
+ }
934
+ this.bad = (ndim !== 0 && ndim !== 1) || (hasNull !== 0 && hasNull !== 1) || this.size === 0
935
+ if (ndim === 1 && !this.bad) {
936
+ this.bad = length < PG_ARRAY_HEADER + PG_ARRAY_DIM
937
+ }
938
+ if (this.bad) {
939
+ this.refuse()
940
+ return
941
+ }
942
+ this.elementOid = oid
943
+ this.hasNull = hasNull === 1
944
+ this.pos = start + PG_ARRAY_HEADER
945
+ this.end = start + length
946
+ if (ndim === 1) {
947
+ const dim: i32 = getI32BE(b, start + PG_ARRAY_HEADER)
948
+ if (dim < 0) {
949
+ this.refuse()
950
+ return
951
+ }
952
+ this.count = dim
953
+ this.pos += PG_ARRAY_DIM
954
+ }
955
+ }
956
+
957
+ private refuse(): i32 {
958
+ this.bad = true
959
+ this.binary.failed = true
960
+ return PG_ARRAY_BAD
961
+ }
962
+
963
+ /** The next element: `PG_ARRAY_ELEMENT`, `PG_ARRAY_NULL`, `PG_ARRAY_END` or `PG_ARRAY_BAD`. */
964
+ next(): i32 {
965
+ if (this.bad) {
966
+ return PG_ARRAY_BAD
967
+ }
968
+ if (this.seen === this.count) {
969
+ if (this.pos === this.end) {
970
+ return PG_ARRAY_END
971
+ }
972
+ return this.refuse()
973
+ }
974
+ if (this.end - this.pos < 4) {
975
+ return this.refuse()
976
+ }
977
+ const n: i32 = getI32BE(this.b, this.pos)
978
+ this.pos += 4
979
+ this.seen += 1
980
+ if (n === -1) {
981
+ if (!this.hasNull) {
982
+ return this.refuse()
983
+ }
984
+ this.start = this.pos
985
+ this.length = 0
986
+ return PG_ARRAY_NULL
987
+ }
988
+ if (n !== this.size || this.end - this.pos < n) {
989
+ return this.refuse()
990
+ }
991
+ this.start = this.pos
992
+ this.length = n
993
+ this.pos += n
994
+ return PG_ARRAY_ELEMENT
995
+ }
996
+
997
+ /** The current element as an integer, through `PgBinary.int`; a NULL, or a walk that was refused, sets `binary.failed`. */
998
+ int(): i64 {
999
+ if (this.bad) {
1000
+ this.binary.failed = true
1001
+ return 0
1002
+ }
1003
+ return this.binary.int(this.b, this.start, this.length)
1004
+ }
1005
+ }
1006
+
680
1007
  /**
681
1008
  * A walk over a one-dimensional `text[]` column in place: `start` the
682
1009
  * column, then `next()` until `PG_ARRAY_END` or `PG_ARRAY_BAD`. Each element
@@ -842,10 +1169,11 @@ export class PgConnection {
842
1169
  lastNotice: PgError | null = null
843
1170
  /** The decoders `int`, `bool` and `isNull` use; `text.failed` says whether one was given a column it could not read. */
844
1171
  readonly text: PgText
1172
+ /** The decoders `binaryInt`, `binaryBool` and `binaryFloat` use, for a bind `bindStartBinary` opened; `binary.failed` as `text.failed`. */
1173
+ readonly binary: PgBinary
845
1174
 
846
1175
  private readonly config: PgConfig
847
1176
  private readonly password: PgPassword
848
- private state: i32 = 0
849
1177
  private input: u8[]
850
1178
  private inStart: i32 = 0
851
1179
  private inEnd: i32 = 0
@@ -863,12 +1191,15 @@ export class PgConnection {
863
1191
  groupFailed: boolean = false
864
1192
  // Whether the open bind opened its group, which `bindCancel` closes again.
865
1193
  private bindOpened: boolean = false
1194
+ // Whether the open bind asks for its result columns in binary.
1195
+ private bindBinary: boolean = false
866
1196
  private bindAt: i32 = -1
867
1197
  private paramCountAt: i32 = 0
868
1198
  private paramCount: i32 = 0
869
1199
  // The open array parameter's length word, -1 when none is open, and its elements so far.
870
1200
  private arrayAt: i32 = -1
871
1201
  private arrayCount: i32 = 0
1202
+ private state: i32 = 0
872
1203
  private clientNonce: string = ""
873
1204
  private serverSignature: u8[]
874
1205
 
@@ -885,6 +1216,7 @@ export class PgConnection {
885
1216
  pgCheckConfig(config)
886
1217
  this.config = config
887
1218
  this.text = new PgText()
1219
+ this.binary = new PgBinary()
888
1220
  this.password = new PgPassword(password)
889
1221
  const inputSize: i32 = config.inputSize < 64 ? 64 : config.inputSize
890
1222
  this.input = new Array<u8>(inputSize)
@@ -1574,7 +1906,8 @@ export class PgConnection {
1574
1906
  */
1575
1907
  private room(size: i32): boolean {
1576
1908
  const queued: i32 = this.outEnd - this.outStart
1577
- if (size < 0 || size > this.config.maxOutput - PG_TAIL - queued) {
1909
+ const tail: i32 = this.bindAt >= 0 && this.bindBinary ? PG_TAIL + PG_BINARY_TAIL : PG_TAIL
1910
+ if (size < 0 || size > this.config.maxOutput - tail - queued) {
1578
1911
  return false
1579
1912
  }
1580
1913
  if (this.outStart > 0 && this.outEnd + size > toI32(this.out.length)) {
@@ -1740,11 +2073,34 @@ export class PgConnection {
1740
2073
  * order, then `bindEnd()`. False as `prepare` is.
1741
2074
  */
1742
2075
  bindStart(name: string): boolean {
2076
+ return this.openBind(name, false)
2077
+ }
2078
+
2079
+ /**
2080
+ * `bindStart`, asking for every result column of the execution in binary
2081
+ * format rather than text: fixed-size integers, booleans, floats and
2082
+ * times, read in place by `binaryInt`, `binaryBool`, `binaryFloat` and
2083
+ * `binary.timestamp`. A `text` column's bytes are the same in either
2084
+ * format, so `columnText` and the column windows read it as before. An
2085
+ * `int2[]`, `int4[]` or `int8[]` column comes in PostgreSQL's binary layout,
2086
+ * which `PgBinaryArray` reads; a `text[]` or `numeric` column comes in that
2087
+ * layout too, and no decoder here reads it. `PgBatcher` opens its batches
2088
+ * with this when its `binary` is set, and reads their routing ordinals with
2089
+ * `binaryInt`. Its `bindEnd()` writes two bytes more, kept free under
2090
+ * `maxOutput` from here on. False as `prepare` is.
2091
+ */
2092
+ bindStartBinary(name: string): boolean {
2093
+ return this.openBind(name, true)
2094
+ }
2095
+
2096
+ private openBind(name: string, binary: boolean): boolean {
1743
2097
  const opens: boolean = !this.groupOpen
1744
- if (pgHasNul(name) || toI32(name.length) > PG_MAX_CAP || !this.canOpen(11 + toI32(name.length))) {
2098
+ const extra: i32 = binary ? PG_BINARY_TAIL : 0
2099
+ if (pgHasNul(name) || toI32(name.length) > PG_MAX_CAP || !this.canOpen(11 + extra + toI32(name.length))) {
1745
2100
  return false
1746
2101
  }
1747
2102
  this.bindOpened = opens
2103
+ this.bindBinary = binary
1748
2104
  this.bindAt = this.startMessage(PG_FE_BIND)
1749
2105
  this.putByte(PG_ZERO)
1750
2106
  this.putCString(name)
@@ -2058,9 +2414,10 @@ export class PgConnection {
2058
2414
  }
2059
2415
 
2060
2416
  /**
2061
- * Ends the Bind (every result column in text) and queues its Execute, for
2062
- * every row. False, and nothing written, when no bind is open; never for
2063
- * room, which the calls before it kept.
2417
+ * Ends the Bind (every result column in text, or in binary after
2418
+ * `bindStartBinary`) and queues its Execute, for every row. False, and
2419
+ * nothing written, when no bind is open; never for room, which the calls
2420
+ * before it kept.
2064
2421
  */
2065
2422
  bindEnd(): boolean {
2066
2423
  if (this.bindAt < 0) {
@@ -2069,10 +2426,17 @@ export class PgConnection {
2069
2426
  if (this.arrayAt >= 0) {
2070
2427
  panic("nish/net/postgres: bindEnd() while an array is open")
2071
2428
  }
2072
- this.tailRoom(PG_TAIL)
2429
+ const binary: boolean = this.bindBinary
2430
+ this.tailRoom(binary ? PG_TAIL + PG_BINARY_TAIL : PG_TAIL)
2073
2431
  const at: i32 = this.bindAt
2074
2432
  setU16BE(this.out, this.paramCountAt, toU16(this.paramCount))
2075
- this.putI16(PG_ZERO)
2433
+ if (binary) {
2434
+ // One format code, which the server applies to every column.
2435
+ this.putI16(toI32(1))
2436
+ this.putI16(PG_FORMAT_BINARY)
2437
+ } else {
2438
+ this.putI16(PG_ZERO)
2439
+ }
2076
2440
  this.endMessage(at)
2077
2441
  this.bindAt = -1
2078
2442
  const execute: i32 = this.startMessage(PG_FE_EXECUTE)
@@ -2170,6 +2534,21 @@ export class PgConnection {
2170
2534
  return this.text.bool(this.input, this.colStarts[c], this.colLengths[c])
2171
2535
  }
2172
2536
 
2537
+ /** Binary column `c`, an `int2`, `int4`, `int8` or a time's microseconds, as an integer; NULL or another length sets `binary.failed`. */
2538
+ binaryInt(c: i32): i64 {
2539
+ return this.binary.int(this.input, this.colStarts[c], this.colLengths[c])
2540
+ }
2541
+
2542
+ /** Binary column `c` as a boolean; NULL or a bad value sets `binary.failed`. */
2543
+ binaryBool(c: i32): boolean {
2544
+ return this.binary.bool(this.input, this.colStarts[c], this.colLengths[c])
2545
+ }
2546
+
2547
+ /** Binary column `c`, a `float8`, as an f64; NULL or another length sets `binary.failed`. */
2548
+ binaryFloat(c: i32): f64 {
2549
+ return this.binary.float(this.input, this.colStarts[c], this.colLengths[c])
2550
+ }
2551
+
2173
2552
  /** Whether the tag of the execution just completed starts with the word `word` (`INSERT`, `SELECT`, ...). */
2174
2553
  tagIs(word: string): boolean {
2175
2554
  const n: i32 = toI32(word.length)
@@ -2266,7 +2645,8 @@ class PgPoolSlot {
2266
2645
  *
2267
2646
  * A request is a ticket from `request()`, queued in FIFO order. `dispatch()`
2268
2647
  * hands the queue's head to the ready connection with the fewest groups in
2269
- * flight, never past `maxInFlight` on one; the program builds the request's
2648
+ * flight, or under `PgConfig.pack` the lowest-numbered ready one with room,
2649
+ * never past `maxInFlight` on one; the program builds the request's
2270
2650
  * group on `connection(slot)` and ends it with `send()`, not `sync()`.
2271
2651
  * `next(slot)` then reads that connection's events, `ticket` naming the
2272
2652
  * request each is about. A connection that fails reports `PG_LOST` for each
@@ -2367,7 +2747,8 @@ export class PgPool {
2367
2747
  }
2368
2748
 
2369
2749
  /**
2370
- * Hands the oldest waiting request to a ready connection with room, and
2750
+ * Hands the oldest waiting request to a ready connection with room (the
2751
+ * one with the fewest in flight, or under `PgConfig.pack` the first), and
2371
2752
  * answers its slot, `ticket` set to the request: build its group on
2372
2753
  * `connection(slot)`, then `send()`. `PG_POOL_NONE` when nothing waits, a
2373
2754
  * request is still being built, or no ready connection has room; then a
@@ -2381,6 +2762,7 @@ export class PgPool {
2381
2762
  return PG_POOL_NONE
2382
2763
  }
2383
2764
  const cap: i32 = this.config.maxInFlight
2765
+ const pack: boolean = this.config.pack
2384
2766
  let best: i32 = -1
2385
2767
  let load: i32 = 0
2386
2768
  let starting: i32 = 0
@@ -2389,7 +2771,7 @@ export class PgPool {
2389
2771
  const slot: PgPoolSlot = this.slots[s]
2390
2772
  const conn: PgConnection | null = slot.conn
2391
2773
  if (slot.state === PG_SLOT_READY && conn !== null) {
2392
- if (conn.inFlight < cap && (best < 0 || conn.inFlight < load)) {
2774
+ if (conn.inFlight < cap && (best < 0 || (!pack && conn.inFlight < load))) {
2393
2775
  best = s
2394
2776
  load = conn.inFlight
2395
2777
  }
@@ -2630,6 +3012,8 @@ export class PgBatchConfig {
2630
3012
  queueBytes: i32 = 65536
2631
3013
  /** The most executions in flight at once. */
2632
3014
  runs: i32 = 16
3015
+ /** Whether every row of a batch comes in binary format, read with the binary decoders and its ordinal by `binaryInt`; false for text. */
3016
+ binary: boolean = false
2633
3017
  }
2634
3018
 
2635
3019
  /** One execution's requests, grouped by key, and how far its rows have been routed. */
@@ -2680,7 +3064,10 @@ class PgBatchRun {
2680
3064
  * connection to `fail(run)`; after each, `next(run)` answers events until
2681
3065
  * `PG_BATCH_NONE`, `tag` naming the request each is about. Every row goes to
2682
3066
  * every request for its key, and every request gets one last event:
2683
- * `PG_BATCH_DONE`, `PG_BATCH_MISSING` or `PG_BATCH_FAILED`.
3067
+ * `PG_BATCH_DONE`, `PG_BATCH_MISSING` or `PG_BATCH_FAILED`. A batch with
3068
+ * `binary` set comes in binary format: its rows are read with the binary
3069
+ * decoders, `binaryInt` for each ordinal, and the caller's `binary.failed`
3070
+ * is left as it was.
2684
3071
  *
2685
3072
  * Nothing outlives its execution: a run is free again once the events
2686
3073
  * `end` or `fail` owe are answered, and its handle is then spent. What the
@@ -2727,6 +3114,7 @@ export class PgBatcher {
2727
3114
  private readonly keyType: i32
2728
3115
  private readonly maxRequests: i32
2729
3116
  private readonly maxBytes: i32
3117
+ private readonly binary: boolean
2730
3118
  private storeEnd: i32 = 0
2731
3119
  private head: i32 = 0
2732
3120
  private count: i32 = 0
@@ -2764,6 +3152,7 @@ export class PgBatcher {
2764
3152
  this.keyType = keyType
2765
3153
  this.maxRequests = maxRequests
2766
3154
  this.maxBytes = config.maxBytes
3155
+ this.binary = config.binary
2767
3156
  this.name = config.name
2768
3157
  const type: string = keyType === PG_BATCH_INT ? "int8" : "text"
2769
3158
  const order: string = config.orderBy.length > 0 ? `, ${config.orderBy}` : ""
@@ -2931,7 +3320,7 @@ export class PgBatcher {
2931
3320
  break
2932
3321
  }
2933
3322
  }
2934
- if (r < 0 || !conn.bindStart(this.name)) {
3323
+ if (r < 0 || !(this.binary ? conn.bindStartBinary(this.name) : conn.bindStart(this.name))) {
2935
3324
  return -1
2936
3325
  }
2937
3326
  if (!conn.arrayStart()) {
@@ -3063,7 +3452,14 @@ export class PgBatcher {
3063
3452
  const last: i32 = conn.columns - 1
3064
3453
  let ordinal: i64 = 0
3065
3454
  let bad: boolean = last < 0 || conn.isNull(last)
3066
- if (!bad) {
3455
+ if (!bad && this.binary) {
3456
+ // Read without touching `binary.failed`, which belongs to the program's own decoding.
3457
+ const failed: boolean = conn.binary.failed
3458
+ conn.binary.failed = false
3459
+ ordinal = conn.binaryInt(last)
3460
+ bad = conn.binary.failed
3461
+ conn.binary.failed = failed
3462
+ } else if (!bad) {
3067
3463
  // Read without touching `text.failed`, which belongs to the program's own decoding.
3068
3464
  const failed: boolean = conn.text.failed
3069
3465
  conn.text.failed = false