@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.
- package/bin/nish +0 -0
- package/docs/AI.md +30 -4
- package/package.json +1 -1
- package/runtime/core/host.d.ts +4 -0
- package/runtime/core/host.mjs +24 -0
- package/runtime/nish.d.ts +53 -5
- package/runtime/nish.h +43 -4
- package/runtime/nish.mjs +4 -0
- package/runtime/node.mjs +79 -0
- package/runtime/runtime-os.c +257 -22
- package/runtime/runtime-simd.c +63 -2
- package/runtime/runtime-wasm.c +18 -0
- package/scripts/build.sh +16 -1
- package/std/README.md +3 -2
- package/std/crypto/sha256.ts +335 -188
- package/std/net/http-client.ts +1645 -0
- package/std/net/postgres.ts +425 -29
package/std/net/postgres.ts
CHANGED
|
@@ -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
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* length in two reused arrays, so
|
|
64
|
-
* `isNull` and `PgText.timestamp`
|
|
65
|
-
*
|
|
66
|
-
* allocates, a string. An array
|
|
67
|
-
* `paramIntArray` writes an `int8[]`
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
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,
|
|
104
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2062
|
-
*
|
|
2063
|
-
* room, which the calls
|
|
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.
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|