@amritk/nish-x86_64-linux 0.30.0 → 0.32.0
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 +66 -8
- package/package.json +1 -1
- package/runtime/core/host.d.ts +2 -0
- package/runtime/core/host.mjs +9 -0
- package/runtime/nish.d.ts +48 -2
- package/runtime/nish.h +9 -1
- package/runtime/nish.mjs +36 -0
- package/runtime/runtime-net.c +32 -3
- package/runtime/runtime-os.c +33 -1
- package/runtime/runtime-region.c +15 -3
- package/runtime/runtime.c +9 -2
- package/std/README.md +9 -7
- package/std/crypto/bcrypt.ts +152 -101
- package/std/net/{http-client.ts → fetch.ts} +17 -17
- package/std/net/handler.ts +1 -1
- package/std/net/{http1-client.ts → http1-client-codec.ts} +6 -6
- package/std/net/{http1.ts → http1-codec.ts} +2 -2
- package/std/net/http1-server.ts +4 -4
- package/std/net/outbound.ts +2 -2
- package/std/net/postgres.ts +383 -49
- package/std/net/prefork.ts +149 -13
- package/std/net/router.ts +1 -1
- package/std/net/upstream.ts +3 -3
- package/std/net/websocket.ts +5 -5
- package/std/region.ts +95 -0
- package/std/sort.ts +178 -0
package/std/net/postgres.ts
CHANGED
|
@@ -59,8 +59,9 @@
|
|
|
59
59
|
* does not need: a program prepares a statement and executes it in the same
|
|
60
60
|
* flight.
|
|
61
61
|
*
|
|
62
|
-
* **Text results, no copies.** Every parameter is in text format
|
|
63
|
-
*
|
|
62
|
+
* **Text results, no copies.** Every parameter is in text format unless it
|
|
63
|
+
* is one of the binary integer arrays below, and every result column is text
|
|
64
|
+
* unless the bind asks otherwise. A row is read where it
|
|
64
65
|
* lies in the input buffer: `next()` frames each message once, validates its
|
|
65
66
|
* layout, and records each column's start and length in two reused arrays, so
|
|
66
67
|
* a row costs no allocation. `int`, `bool`, `isNull` and `PgText.timestamp`
|
|
@@ -72,6 +73,21 @@
|
|
|
72
73
|
* Bind, so a list needs no string built first. A program whose element was
|
|
73
74
|
* refused for room can drop the whole bind, unsent, with `bindCancel`.
|
|
74
75
|
*
|
|
76
|
+
* **Binary integer arrays.** `paramInt4Array` and `paramInt8Array` send an
|
|
77
|
+
* `i64[]` window as an `int4[]` or `int8[]` in PostgreSQL's binary layout:
|
|
78
|
+
* four or eight bytes an element and no digits to write or parse, which the
|
|
79
|
+
* benchmark (`bench/postgres/arrays.mjs`) measured at about a tenth of the
|
|
80
|
+
* text encoder's client CPU, and at 1,000 elements a 51% cut in the server's
|
|
81
|
+
* on `unnest` for `int8[]`, 30% for `int4[]`.
|
|
82
|
+
* `binaryArrayStart(oid)`, `binaryArrayInt` and `binaryArrayNull` build one
|
|
83
|
+
* element at a time, NULLs included, and `arrayEnd` closes it. The Bind's
|
|
84
|
+
* format codes stay a single zero word while every parameter is text; the
|
|
85
|
+
* first binary one makes `bindEnd` write a code for each, so an all-text bind
|
|
86
|
+
* costs nothing more. An `int4` element outside `int4` panics rather than
|
|
87
|
+
* wrapping. The element type must match the statement's parameter: the server
|
|
88
|
+
* answers a mismatch with `ErrorResponse` (42804) for that execution, and the
|
|
89
|
+
* connection goes on.
|
|
90
|
+
*
|
|
75
91
|
* **Binary results.** A bind opened with `bindStartBinary` instead of
|
|
76
92
|
* `bindStart` asks for every result column in binary format: an `int8` is
|
|
77
93
|
* eight bytes, a `timestamptz` eight bytes of microseconds since 2000, a
|
|
@@ -133,7 +149,8 @@
|
|
|
133
149
|
* **Batching.** `PgBatcher` turns many reads of one row shape by key — a user
|
|
134
150
|
* by id, an article by slug — into one execution of a statement over
|
|
135
151
|
* `unnest($1) with ordinality`. `addInt` or `addText` queues a request's key
|
|
136
|
-
* and tag; `take(conn)` binds the oldest waiting, each distinct key once
|
|
152
|
+
* and tag; `take(conn)` binds the oldest waiting, each distinct key once (past
|
|
153
|
+
* `PG_BATCH_PROBES` slots of its table probe, a key is bound again), up
|
|
137
154
|
* to `maxRequests` and `maxBytes`, and answers a run's handle; `row`, `end`
|
|
138
155
|
* and `fail` take that execution's events (a handle already answered makes
|
|
139
156
|
* them a no-op counted in `stray`, so a server's late event cannot panic),
|
|
@@ -159,7 +176,7 @@
|
|
|
159
176
|
* Written from PostgreSQL's "Frontend/Backend Protocol" chapter (version 18)
|
|
160
177
|
* and RFC 5802 and RFC 7677; not ported from another implementation.
|
|
161
178
|
*/
|
|
162
|
-
import { copyBytes, getF64BE, getI32BE, getI64BE, getU16BE, setI32BE, setU16BE } from "nish/bytes"
|
|
179
|
+
import { copyBytes, getF64BE, getI32BE, getI64BE, getU16BE, setI32BE, setI64BE, setU16BE } from "nish/bytes"
|
|
163
180
|
import { base64StdPadDecode, base64StdPadEncode } from "nish/crypto/base64url"
|
|
164
181
|
import { timingSafeEqual } from "nish/crypto/ct"
|
|
165
182
|
import { hmacSha256, pbkdf2HmacSha256 } from "nish/crypto/hmac"
|
|
@@ -256,7 +273,8 @@ const PG_TAIL: i32 = 17
|
|
|
256
273
|
const PG_BINARY_TAIL: i32 = 2
|
|
257
274
|
// The result format code for binary.
|
|
258
275
|
const PG_FORMAT_BINARY: i32 = 1
|
|
259
|
-
// The element types `PgBinaryArray` reads,
|
|
276
|
+
// The element types `PgBinaryArray` reads, and the binary array parameters
|
|
277
|
+
// write (int4 and int8), by OID: int8, int2 and int4.
|
|
260
278
|
const PG_OID_INT8: i32 = 20
|
|
261
279
|
const PG_OID_INT2: i32 = 21
|
|
262
280
|
const PG_OID_INT4: i32 = 23
|
|
@@ -436,6 +454,16 @@ const pgDecimalLength = (value: i64): i32 => {
|
|
|
436
454
|
return n
|
|
437
455
|
}
|
|
438
456
|
|
|
457
|
+
/** Whether `value` is an `int4`, -2^31..2^31-1. */
|
|
458
|
+
const pgFitsInt4 = (value: i64): boolean => value >= toI64(-2147483648) && value <= toI64(2147483647)
|
|
459
|
+
|
|
460
|
+
/** Panics unless `values[start, start + count)` is inside `values`. */
|
|
461
|
+
const pgIntWindow = (values: i64[], start: i32, count: i32): void => {
|
|
462
|
+
if (start < 0 || count < 0 || count > toI32(values.length) - start) {
|
|
463
|
+
panic("nish/net/postgres: an int array parameter outside its array")
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
439
467
|
/** Whether `text` holds a NUL byte, which a protocol string cannot. */
|
|
440
468
|
const pgHasNul = (text: string): boolean => {
|
|
441
469
|
const n: i32 = toI32(text.length)
|
|
@@ -1211,9 +1239,19 @@ export class PgConnection {
|
|
|
1211
1239
|
private bindAt: i32 = -1
|
|
1212
1240
|
private paramCountAt: i32 = 0
|
|
1213
1241
|
private paramCount: i32 = 0
|
|
1242
|
+
// What the open bind's end owes beyond `PG_TAIL`: its binary result format
|
|
1243
|
+
// code, and once it holds a binary parameter, a format code per parameter.
|
|
1244
|
+
private bindOwed: i32 = 0
|
|
1245
|
+
// The open bind's binary parameters, by index; `bindEnd` back-patches their format codes.
|
|
1246
|
+
private binaryParams: i32[]
|
|
1247
|
+
private binaryParamCount: i32 = 0
|
|
1214
1248
|
// The open array parameter's length word, -1 when none is open, and its elements so far.
|
|
1215
1249
|
private arrayAt: i32 = -1
|
|
1216
1250
|
private arrayCount: i32 = 0
|
|
1251
|
+
// The open array's element OID when it is binary (`binaryArrayStart`), 0 when it is a text literal.
|
|
1252
|
+
private arrayOid: i32 = 0
|
|
1253
|
+
// Whether the open binary array holds a NULL element.
|
|
1254
|
+
private arrayNulls: boolean = false
|
|
1217
1255
|
private state: i32 = 0
|
|
1218
1256
|
private clientNonce: string = ""
|
|
1219
1257
|
private serverSignature: u8[]
|
|
@@ -1239,6 +1277,7 @@ export class PgConnection {
|
|
|
1239
1277
|
this.colStarts = new Array<i32>(16)
|
|
1240
1278
|
this.colLengths = new Array<i32>(16)
|
|
1241
1279
|
this.colTypes = new Array<i32>(16)
|
|
1280
|
+
this.binaryParams = []
|
|
1242
1281
|
this.serverSignature = []
|
|
1243
1282
|
const head: u8[] = []
|
|
1244
1283
|
for (let i: i32 = 0; i < PG_NONCE_BYTES && i < toI32(nonce.length); i++) {
|
|
@@ -1915,14 +1954,13 @@ export class PgConnection {
|
|
|
1915
1954
|
|
|
1916
1955
|
/**
|
|
1917
1956
|
* Whether a builder call may queue `size` more bytes and still leave
|
|
1918
|
-
* `PG_TAIL
|
|
1957
|
+
* `PG_TAIL`, and what the open bind's end owes, under `maxOutput`. When they would not fit after the end of the
|
|
1919
1958
|
* buffer, the unsent bytes move to its start first: a builder call asks
|
|
1920
1959
|
* before it takes any offset into the buffer, so nothing it holds moves.
|
|
1921
1960
|
*/
|
|
1922
1961
|
private room(size: i32): boolean {
|
|
1923
1962
|
const queued: i32 = this.outEnd - this.outStart
|
|
1924
|
-
|
|
1925
|
-
if (size < 0 || size > this.config.maxOutput - tail - queued) {
|
|
1963
|
+
if (size < 0 || size > this.config.maxOutput - PG_TAIL - this.bindOwed - queued) {
|
|
1926
1964
|
return false
|
|
1927
1965
|
}
|
|
1928
1966
|
if (this.outStart > 0 && this.outEnd + size > toI32(this.out.length)) {
|
|
@@ -2116,6 +2154,8 @@ export class PgConnection {
|
|
|
2116
2154
|
}
|
|
2117
2155
|
this.bindOpened = opens
|
|
2118
2156
|
this.bindBinary = binary
|
|
2157
|
+
this.bindOwed = extra
|
|
2158
|
+
this.binaryParamCount = 0
|
|
2119
2159
|
this.bindAt = this.startMessage(PG_FE_BIND)
|
|
2120
2160
|
this.putByte(PG_ZERO)
|
|
2121
2161
|
this.putCString(name)
|
|
@@ -2133,9 +2173,37 @@ export class PgConnection {
|
|
|
2133
2173
|
*/
|
|
2134
2174
|
private startParam(size: i32): i32 {
|
|
2135
2175
|
this.noArrayOpen()
|
|
2136
|
-
|
|
2176
|
+
// Once the bind holds a binary parameter, every parameter owes its format code.
|
|
2177
|
+
const owed: i32 = this.binaryParamCount > 0 ? 2 : 0
|
|
2178
|
+
if (this.bindAt < 0 || this.paramCount === 65535 || size > PG_MAX_CAP || !this.room(4 + size + owed)) {
|
|
2137
2179
|
return -1
|
|
2138
2180
|
}
|
|
2181
|
+
this.bindOwed += owed
|
|
2182
|
+
this.paramCount += 1
|
|
2183
|
+
const at: i32 = this.outEnd
|
|
2184
|
+
this.putI32(PG_ZERO)
|
|
2185
|
+
return at
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
/**
|
|
2189
|
+
* `startParam` for a parameter in binary format: the first one in a bind
|
|
2190
|
+
* owes a format code for every parameter before it as well as its own,
|
|
2191
|
+
* which `bindEnd` writes once the count is known. A text-only bind owes none
|
|
2192
|
+
* and writes the zero count it always has.
|
|
2193
|
+
*/
|
|
2194
|
+
private startBinaryParam(size: i32): i32 {
|
|
2195
|
+
this.noArrayOpen()
|
|
2196
|
+
const owed: i32 = this.binaryParamCount > 0 ? toI32(2) : toI32(2) * (this.paramCount + toI32(1))
|
|
2197
|
+
if (this.bindAt < 0 || this.paramCount === 65535 || size > PG_MAX_CAP || !this.room(4 + size + owed)) {
|
|
2198
|
+
return -1
|
|
2199
|
+
}
|
|
2200
|
+
if (this.binaryParamCount < toI32(this.binaryParams.length)) {
|
|
2201
|
+
this.binaryParams[this.binaryParamCount] = this.paramCount
|
|
2202
|
+
} else {
|
|
2203
|
+
this.binaryParams.push(this.paramCount)
|
|
2204
|
+
}
|
|
2205
|
+
this.binaryParamCount += 1
|
|
2206
|
+
this.bindOwed += owed
|
|
2139
2207
|
this.paramCount += 1
|
|
2140
2208
|
const at: i32 = this.outEnd
|
|
2141
2209
|
this.putI32(PG_ZERO)
|
|
@@ -2248,7 +2316,7 @@ export class PgConnection {
|
|
|
2248
2316
|
const end: i32 = start + count
|
|
2249
2317
|
// Its exact size first, so that room is asked for once and a refusal writes nothing.
|
|
2250
2318
|
let size: i64 = count > 0 ? toI64(count + 1) : toI64(2)
|
|
2251
|
-
for (let i: i32 = start; i < end && i < toI32(values.length); i++) {
|
|
2319
|
+
for (let i: i32 = start; i >= 0 && i < end && i < toI32(values.length); i++) {
|
|
2252
2320
|
size += toI64(pgDecimalLength(values[i]))
|
|
2253
2321
|
}
|
|
2254
2322
|
const at: i32 = this.startParam(size > toI64(PG_MAX_CAP) ? PG_MAX_CAP + 1 : toI32(size))
|
|
@@ -2256,7 +2324,7 @@ export class PgConnection {
|
|
|
2256
2324
|
return false
|
|
2257
2325
|
}
|
|
2258
2326
|
this.putByte(PG_LBRACE)
|
|
2259
|
-
for (let i: i32 = start; i < end && i < toI32(values.length); i++) {
|
|
2327
|
+
for (let i: i32 = start; i >= 0 && i < end && i < toI32(values.length); i++) {
|
|
2260
2328
|
// Read before the comma's call, which could drop the length fact.
|
|
2261
2329
|
const value: i64 = values[i]
|
|
2262
2330
|
if (i > start) {
|
|
@@ -2310,6 +2378,80 @@ export class PgConnection {
|
|
|
2310
2378
|
return true
|
|
2311
2379
|
}
|
|
2312
2380
|
|
|
2381
|
+
// Binary `int4[]` and `int8[]` parameters, in PostgreSQL's binary array
|
|
2382
|
+
// layout (`array_send`'s): the Bind's only parameters not in text, and its
|
|
2383
|
+
// format codes say so. Their elements are fixed-size big-endian integers,
|
|
2384
|
+
// so there are no digits to write and none for the server to parse. The
|
|
2385
|
+
// element type is explicit and must be the statement's: an `int4[]` sent
|
|
2386
|
+
// where the server expects `int8[]`, or the reverse, is refused by the
|
|
2387
|
+
// server as that execution's `PG_ERROR`, and the connection goes on. An
|
|
2388
|
+
// `int4` element outside -2^31..2^31-1 panics as a program error: false
|
|
2389
|
+
// would read as "no room", and a batching loop would retry it forever.
|
|
2390
|
+
|
|
2391
|
+
/**
|
|
2392
|
+
* An `int4[]` parameter in binary from `values[start, start + count)`;
|
|
2393
|
+
* false as the `param...` calls are. Panics, writing nothing, when the
|
|
2394
|
+
* window is not inside `values` or an element is outside `int4`.
|
|
2395
|
+
*/
|
|
2396
|
+
paramInt4Array(values: i64[], start: i32, count: i32): boolean {
|
|
2397
|
+
pgIntWindow(values, start, count)
|
|
2398
|
+
const end: i32 = start + count
|
|
2399
|
+
for (let i: i32 = start; i >= 0 && i < end && i < toI32(values.length); i++) {
|
|
2400
|
+
if (!pgFitsInt4(values[i])) {
|
|
2401
|
+
panic("nish/net/postgres: paramInt4Array() with an element outside int4")
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
return this.paramBinaryInts(values, start, count, PG_OID_INT4)
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
/**
|
|
2408
|
+
* An `int8[]` parameter in binary from `values[start, start + count)`;
|
|
2409
|
+
* false as the `param...` calls are. Panics when the window is not inside
|
|
2410
|
+
* `values`.
|
|
2411
|
+
*/
|
|
2412
|
+
paramInt8Array(values: i64[], start: i32, count: i32): boolean {
|
|
2413
|
+
pgIntWindow(values, start, count)
|
|
2414
|
+
return this.paramBinaryInts(values, start, count, PG_OID_INT8)
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
private paramBinaryInts(values: i64[], start: i32, count: i32, oid: i32): boolean {
|
|
2418
|
+
const width: i32 = oid === PG_OID_INT4 ? 4 : 8
|
|
2419
|
+
// The header, its one dimension when there are elements, then each element's length word and bytes.
|
|
2420
|
+
const size: i64 =
|
|
2421
|
+
count > 0
|
|
2422
|
+
? toI64(PG_ARRAY_HEADER + PG_ARRAY_DIM) + toI64(count) * toI64(4 + width)
|
|
2423
|
+
: toI64(PG_ARRAY_HEADER)
|
|
2424
|
+
const at: i32 = this.startBinaryParam(size > toI64(PG_MAX_CAP) ? PG_MAX_CAP + 1 : toI32(size))
|
|
2425
|
+
if (at < 0) {
|
|
2426
|
+
return false
|
|
2427
|
+
}
|
|
2428
|
+
this.reserve(this.outEnd + toI32(size))
|
|
2429
|
+
this.putArrayHeader(oid, count)
|
|
2430
|
+
const end: i32 = start + count
|
|
2431
|
+
for (let i: i32 = start; i >= 0 && i < end && i < toI32(values.length); i++) {
|
|
2432
|
+
setI32BE(this.out, this.outEnd, width)
|
|
2433
|
+
if (width === 4) {
|
|
2434
|
+
setI32BE(this.out, this.outEnd + 4, toI32(values[i]))
|
|
2435
|
+
} else {
|
|
2436
|
+
setI64BE(this.out, this.outEnd + 4, values[i])
|
|
2437
|
+
}
|
|
2438
|
+
this.outEnd += 4 + width
|
|
2439
|
+
}
|
|
2440
|
+
this.endParam(at)
|
|
2441
|
+
return true
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
/** A binary array's header with no NULLs: no dimension when it is empty, as `array_send` writes one, else one of `count` elements from 1. */
|
|
2445
|
+
private putArrayHeader(oid: i32, count: i32): void {
|
|
2446
|
+
this.putI32(count > 0 ? toI32(1) : PG_ZERO)
|
|
2447
|
+
this.putI32(PG_ZERO)
|
|
2448
|
+
this.putI32(oid)
|
|
2449
|
+
if (count > 0) {
|
|
2450
|
+
this.putI32(count)
|
|
2451
|
+
this.putI32(toI32(1))
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2313
2455
|
// An array parameter built element by element: `arrayStart()`, then
|
|
2314
2456
|
// `arrayElement`, `arrayInt` or `arrayNull` for each element, then `arrayEnd()`. An
|
|
2315
2457
|
// element refused for room writes nothing and leaves the array open, so a
|
|
@@ -2339,6 +2481,9 @@ export class PgConnection {
|
|
|
2339
2481
|
if (this.arrayAt < 0) {
|
|
2340
2482
|
panic("nish/net/postgres: an array element with no arrayStart()")
|
|
2341
2483
|
}
|
|
2484
|
+
if (this.arrayOid !== 0) {
|
|
2485
|
+
panic("nish/net/postgres: a text array element in a binary array")
|
|
2486
|
+
}
|
|
2342
2487
|
}
|
|
2343
2488
|
|
|
2344
2489
|
/** Whether the open array has room for an element of `size` bytes, its comma, and the `}` it owes. */
|
|
@@ -2417,13 +2562,98 @@ export class PgConnection {
|
|
|
2417
2562
|
return true
|
|
2418
2563
|
}
|
|
2419
2564
|
|
|
2420
|
-
|
|
2565
|
+
// The binary twin: `binaryArrayStart(elementOid)` opens an `int4[]` (OID
|
|
2566
|
+
// 23) or `int8[]` (OID 20) parameter in binary, `binaryArrayInt` and
|
|
2567
|
+
// `binaryArrayNull` add its elements, and the same `arrayEnd()` closes it.
|
|
2568
|
+
// The text builder's rules hold, and a text element in a binary array, or a
|
|
2569
|
+
// binary element in a text one, panics.
|
|
2570
|
+
|
|
2571
|
+
/** Opens a binary array parameter of `elementOid`'s elements; false as the `param...` calls are. Panics on another OID, or while an array is open. */
|
|
2572
|
+
binaryArrayStart(elementOid: i32): boolean {
|
|
2573
|
+
if (this.arrayAt >= 0) {
|
|
2574
|
+
panic("nish/net/postgres: binaryArrayStart() while an array is open")
|
|
2575
|
+
}
|
|
2576
|
+
if (elementOid !== PG_OID_INT4 && elementOid !== PG_OID_INT8) {
|
|
2577
|
+
panic("nish/net/postgres: binaryArrayStart() with an element OID other than int4 (23) or int8 (20)")
|
|
2578
|
+
}
|
|
2579
|
+
// The header with its dimension; `arrayEnd()` fills in the length and the
|
|
2580
|
+
// NULL flag, or drops the dimension when there are no elements.
|
|
2581
|
+
const at: i32 = this.startBinaryParam(PG_ARRAY_HEADER + PG_ARRAY_DIM)
|
|
2582
|
+
if (at < 0) {
|
|
2583
|
+
return false
|
|
2584
|
+
}
|
|
2585
|
+
this.putArrayHeader(elementOid, toI32(1))
|
|
2586
|
+
this.arrayAt = at
|
|
2587
|
+
this.arrayCount = 0
|
|
2588
|
+
this.arrayOid = elementOid
|
|
2589
|
+
this.arrayNulls = false
|
|
2590
|
+
return true
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
private binaryArrayOpen(): void {
|
|
2594
|
+
if (this.arrayAt < 0 || this.arrayOid === 0) {
|
|
2595
|
+
panic("nish/net/postgres: a binary array element with no binaryArrayStart()")
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
/**
|
|
2600
|
+
* An integer element of the open binary array; false, and nothing written,
|
|
2601
|
+
* when there is no room. Panics when no binary array is open, or when it is
|
|
2602
|
+
* an `int4[]` and `value` is outside `int4`.
|
|
2603
|
+
*/
|
|
2604
|
+
binaryArrayInt(value: i64): boolean {
|
|
2605
|
+
this.binaryArrayOpen()
|
|
2606
|
+
const width: i32 = this.arrayOid === PG_OID_INT4 ? 4 : 8
|
|
2607
|
+
if (width === 4 && !pgFitsInt4(value)) {
|
|
2608
|
+
panic("nish/net/postgres: binaryArrayInt() with an element outside int4")
|
|
2609
|
+
}
|
|
2610
|
+
if (!this.room(4 + width)) {
|
|
2611
|
+
return false
|
|
2612
|
+
}
|
|
2613
|
+
this.putI32(width)
|
|
2614
|
+
if (width === 4) {
|
|
2615
|
+
this.putI32(toI32(value))
|
|
2616
|
+
} else {
|
|
2617
|
+
this.reserve(this.outEnd + 8)
|
|
2618
|
+
setI64BE(this.out, this.outEnd, value)
|
|
2619
|
+
this.outEnd += 8
|
|
2620
|
+
}
|
|
2621
|
+
this.arrayCount += 1
|
|
2622
|
+
return true
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
/** A NULL element of the open binary array, length -1; false, and nothing written, when there is no room. Panics when no binary array is open. */
|
|
2626
|
+
binaryArrayNull(): boolean {
|
|
2627
|
+
this.binaryArrayOpen()
|
|
2628
|
+
if (!this.room(toI32(4))) {
|
|
2629
|
+
return false
|
|
2630
|
+
}
|
|
2631
|
+
this.putI32(toI32(-1))
|
|
2632
|
+
this.arrayNulls = true
|
|
2633
|
+
this.arrayCount += 1
|
|
2634
|
+
return true
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
/** Ends the open array parameter, text or binary; never refused for room, which its elements kept. Panics when no array is open. */
|
|
2421
2638
|
arrayEnd(): void {
|
|
2422
2639
|
if (this.arrayAt < 0) {
|
|
2423
2640
|
panic("nish/net/postgres: arrayEnd() with no arrayStart()")
|
|
2424
2641
|
}
|
|
2425
|
-
this.
|
|
2426
|
-
|
|
2642
|
+
if (this.arrayOid !== 0) {
|
|
2643
|
+
// The header follows the parameter's length word: ndim, the NULL flag, the OID, then the dimension.
|
|
2644
|
+
const header: i32 = this.arrayAt + 4
|
|
2645
|
+
if (this.arrayCount === 0) {
|
|
2646
|
+
setI32BE(this.out, header, PG_ZERO)
|
|
2647
|
+
this.outEnd = header + PG_ARRAY_HEADER
|
|
2648
|
+
} else {
|
|
2649
|
+
setI32BE(this.out, header + 4, this.arrayNulls ? 1 : 0)
|
|
2650
|
+
setI32BE(this.out, header + PG_ARRAY_HEADER, this.arrayCount)
|
|
2651
|
+
}
|
|
2652
|
+
this.arrayOid = 0
|
|
2653
|
+
} else {
|
|
2654
|
+
this.tailRoom(toI32(1))
|
|
2655
|
+
this.putByte(PG_RBRACE)
|
|
2656
|
+
}
|
|
2427
2657
|
this.endParam(this.arrayAt)
|
|
2428
2658
|
this.arrayAt = -1
|
|
2429
2659
|
}
|
|
@@ -2442,8 +2672,11 @@ export class PgConnection {
|
|
|
2442
2672
|
panic("nish/net/postgres: bindEnd() while an array is open")
|
|
2443
2673
|
}
|
|
2444
2674
|
const binary: boolean = this.bindBinary
|
|
2445
|
-
this.tailRoom(
|
|
2675
|
+
this.tailRoom(PG_TAIL + this.bindOwed)
|
|
2446
2676
|
const at: i32 = this.bindAt
|
|
2677
|
+
if (this.binaryParamCount > 0) {
|
|
2678
|
+
this.putFormatCodes()
|
|
2679
|
+
}
|
|
2447
2680
|
setU16BE(this.out, this.paramCountAt, toU16(this.paramCount))
|
|
2448
2681
|
if (binary) {
|
|
2449
2682
|
// One format code, which the server applies to every column.
|
|
@@ -2454,6 +2687,7 @@ export class PgConnection {
|
|
|
2454
2687
|
}
|
|
2455
2688
|
this.endMessage(at)
|
|
2456
2689
|
this.bindAt = -1
|
|
2690
|
+
this.bindOwed = 0
|
|
2457
2691
|
const execute: i32 = this.startMessage(PG_FE_EXECUTE)
|
|
2458
2692
|
this.putByte(PG_ZERO)
|
|
2459
2693
|
this.putI32(PG_ZERO)
|
|
@@ -2482,7 +2716,30 @@ export class PgConnection {
|
|
|
2482
2716
|
private dropBind(): void {
|
|
2483
2717
|
this.outEnd = this.bindAt
|
|
2484
2718
|
this.bindAt = -1
|
|
2719
|
+
this.bindOwed = 0
|
|
2485
2720
|
this.arrayAt = -1
|
|
2721
|
+
this.arrayOid = 0
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
/**
|
|
2725
|
+
* Writes the format codes a bind holding a binary parameter owes, in front
|
|
2726
|
+
* of its parameter count: every parameter moves up by a code each, and
|
|
2727
|
+
* every code is text's but the binary parameters'.
|
|
2728
|
+
*/
|
|
2729
|
+
private putFormatCodes(): void {
|
|
2730
|
+
const from: i32 = this.paramCountAt
|
|
2731
|
+
const codes: i32 = 2 * this.paramCount
|
|
2732
|
+
this.reserve(this.outEnd + codes)
|
|
2733
|
+
copyBytes(this.out, from + codes, this.out, from, this.outEnd - from)
|
|
2734
|
+
this.outEnd += codes
|
|
2735
|
+
setU16BE(this.out, from - 2, toU16(this.paramCount))
|
|
2736
|
+
for (let i: i32 = 0; i < this.paramCount; i++) {
|
|
2737
|
+
setU16BE(this.out, from + 2 * i, toU16(0))
|
|
2738
|
+
}
|
|
2739
|
+
for (let k: i32 = 0; k < this.binaryParamCount && k < toI32(this.binaryParams.length); k++) {
|
|
2740
|
+
setU16BE(this.out, from + 2 * this.binaryParams[k], toU16(PG_FORMAT_BINARY))
|
|
2741
|
+
}
|
|
2742
|
+
this.paramCountAt = from + codes
|
|
2486
2743
|
}
|
|
2487
2744
|
|
|
2488
2745
|
/** Sync: ends the open group, which the server answers with `PG_GROUP_DONE`. False when no group is open or a bind is; never for room. */
|
|
@@ -3005,10 +3262,22 @@ export const PG_BATCH_MISSING: i32 = 3
|
|
|
3005
3262
|
/** `PgBatcher.next(run)`: the request `tag`'s execution failed, and its rows so far may be incomplete. */
|
|
3006
3263
|
export const PG_BATCH_FAILED: i32 = 4
|
|
3007
3264
|
|
|
3008
|
-
//
|
|
3009
|
-
//
|
|
3010
|
-
//
|
|
3011
|
-
|
|
3265
|
+
// The most requests one execution answers. The cost of a take does not grow
|
|
3266
|
+
// with it: each request probes at most `PG_BATCH_PROBES` slots, and the
|
|
3267
|
+
// arrays sized by it grow with the batches taken, not with the cap. It is
|
|
3268
|
+
// 65536 so that one full run's arrays stay at 512 KiB, and one execution's
|
|
3269
|
+
// int8 key array, which the server reads into memory whole, at about 1.4 MiB
|
|
3270
|
+
// (65536 keys of at most 20 digits and a comma); a text key array is bounded
|
|
3271
|
+
// by `maxBytes` instead.
|
|
3272
|
+
const PG_BATCH_MAX_REQUESTS: i32 = 65536
|
|
3273
|
+
// The most occupied slots one request's probe visits in `take` before its key
|
|
3274
|
+
// is bound again rather than found. A take of n requests holds at most n - 1
|
|
3275
|
+
// keys when it probes, so up to 1024 requests (the cap this replaced) no probe
|
|
3276
|
+
// stops; past that, keys chosen to collide cost each request this many probes
|
|
3277
|
+
// and key comparisons, not one per key before it.
|
|
3278
|
+
const PG_BATCH_PROBES: i32 = 1024
|
|
3279
|
+
// The fewest entries the arrays that grow with the batches taken start at.
|
|
3280
|
+
const PG_BATCH_MIN_GROWTH: i32 = 16
|
|
3012
3281
|
const PG_BATCH_MAX_QUEUE: i32 = 1048576
|
|
3013
3282
|
const PG_BATCH_MAX_RUNS: i32 = 1024
|
|
3014
3283
|
// The takes a run's handles count before they wrap: handles stay under 2^30.
|
|
@@ -3016,6 +3285,28 @@ const PG_BATCH_USES: i32 = 1048576
|
|
|
3016
3285
|
// The array's `{}` and the longest int8 element, `-9223372036854775808`.
|
|
3017
3286
|
const PG_BATCH_MIN_BYTES: i32 = 22
|
|
3018
3287
|
|
|
3288
|
+
// The low 32 bits of an int8 key.
|
|
3289
|
+
const PG_LOW32: i64 = 4294967295
|
|
3290
|
+
|
|
3291
|
+
/**
|
|
3292
|
+
* The batcher's table hash of an int8 key, 31 bits: the high half times an
|
|
3293
|
+
* odd constant xored into the low half, then MurmurHash3's 32-bit finalizer,
|
|
3294
|
+
* whose unsigned multiplies wrap by definition. Every step is a bijection on
|
|
3295
|
+
* 32 bits, so keys of one high half never collide, and the finalizer spreads
|
|
3296
|
+
* a dense run of ids over the whole table: runs that would land on one
|
|
3297
|
+
* another's slots under a near-identity hash do not merge into one cluster
|
|
3298
|
+
* longer than `PG_BATCH_PROBES`.
|
|
3299
|
+
*/
|
|
3300
|
+
const pgIntHash = (key: i64): i32 => {
|
|
3301
|
+
const lo: u32 = toU32(key & PG_LOW32)
|
|
3302
|
+
const hi: u32 = toU32((key >> toI64(32)) & PG_LOW32)
|
|
3303
|
+
let h: u32 = lo ^ (hi * 2654435769)
|
|
3304
|
+
h = (h ^ (h >> 16)) * 2246822507
|
|
3305
|
+
h = (h ^ (h >> 13)) * 3266489909
|
|
3306
|
+
h = h ^ (h >> 16)
|
|
3307
|
+
return toI32(h >> 1)
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3019
3310
|
/** What a `PgBatcher` sends: one statement shape, its key, and its caps. */
|
|
3020
3311
|
export class PgBatchConfig {
|
|
3021
3312
|
/** The prepared statement's name, which `PgBatcher.sql` is prepared under. */
|
|
@@ -3042,9 +3333,9 @@ export class PgBatchConfig {
|
|
|
3042
3333
|
|
|
3043
3334
|
/** One execution's requests, grouped by key, and how far its rows have been routed. */
|
|
3044
3335
|
class PgBatchRun {
|
|
3045
|
-
/** Key `k`'s requests' tags are `tags[first[k], first[k + 1])`, in the order they were added. */
|
|
3046
|
-
|
|
3047
|
-
|
|
3336
|
+
/** Key `k`'s requests' tags are `tags[first[k], first[k + 1])`, in the order they were added; grown by `take` to the batch. */
|
|
3337
|
+
tags: i32[]
|
|
3338
|
+
first: i32[]
|
|
3048
3339
|
keys: i32 = 0
|
|
3049
3340
|
/** The key rows are being read for, whether it has had one, and the next of its requests to answer. */
|
|
3050
3341
|
cur: i32 = 0
|
|
@@ -3059,9 +3350,9 @@ class PgBatchRun {
|
|
|
3059
3350
|
ended: boolean = false
|
|
3060
3351
|
failed: boolean = false
|
|
3061
3352
|
|
|
3062
|
-
constructor(
|
|
3063
|
-
this.tags =
|
|
3064
|
-
this.first =
|
|
3353
|
+
constructor() {
|
|
3354
|
+
this.tags = []
|
|
3355
|
+
this.first = []
|
|
3065
3356
|
}
|
|
3066
3357
|
|
|
3067
3358
|
/** Opens the run for `keys` keys, none of whose rows have been read. */
|
|
@@ -3082,7 +3373,9 @@ class PgBatchRun {
|
|
|
3082
3373
|
* it reads no clock and makes no socket. Each request is a key and the
|
|
3083
3374
|
* caller's `tag`, queued by `addInt` or `addText`. When a connection is free,
|
|
3084
3375
|
* `take(conn)` binds up to `maxRequests` of them, and up to `maxBytes` of
|
|
3085
|
-
* keys, as one execution of `sql`, each distinct key once
|
|
3376
|
+
* keys, as one execution of `sql`, each distinct key once (a key whose
|
|
3377
|
+
* probe of the batcher's table passes 1024 slots, which takes keys chosen to
|
|
3378
|
+
* collide, is bound again and answered the same), and answers the
|
|
3086
3379
|
* execution's run handle. Each `PG_ROW` of that execution goes to
|
|
3087
3380
|
* `row(run, conn)`, its `PG_COMPLETE` to `end(run)`, and an error or a lost
|
|
3088
3381
|
* connection to `fail(run)`; after each, `next(run)` answers events until
|
|
@@ -3122,12 +3415,13 @@ export class PgBatcher {
|
|
|
3122
3415
|
private readonly qLengths: i32[]
|
|
3123
3416
|
private readonly store: u8[]
|
|
3124
3417
|
// `take`'s open-addressed table of the keys it has bound, each a key number
|
|
3125
|
-
// plus one, 0 for an empty slot; and for each key, its slot
|
|
3126
|
-
|
|
3127
|
-
private
|
|
3128
|
-
private
|
|
3129
|
-
private
|
|
3130
|
-
private
|
|
3418
|
+
// plus one, 0 for an empty slot; and for each key, its slot (-1 when its
|
|
3419
|
+
// probe stopped) and its first request. Grown by `take` to the batch.
|
|
3420
|
+
private table: i32[]
|
|
3421
|
+
private keySlots: i32[]
|
|
3422
|
+
private keyEntries: i32[]
|
|
3423
|
+
private keyCounts: i32[]
|
|
3424
|
+
private takenKeys: i32[]
|
|
3131
3425
|
private readonly runs: PgBatchRun[]
|
|
3132
3426
|
/** The request the last `next(run)` answered about; -1 for none. */
|
|
3133
3427
|
tag: i32 = -1
|
|
@@ -3194,19 +3488,14 @@ export class PgBatcher {
|
|
|
3194
3488
|
this.qStarts = new Array<i32>(textSize)
|
|
3195
3489
|
this.qLengths = new Array<i32>(textSize)
|
|
3196
3490
|
this.store = new Array<u8>(storeSize)
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
this.table = new Array<i32>(tableSize)
|
|
3203
|
-
this.keySlots = new Array<i32>(maxRequests)
|
|
3204
|
-
this.keyEntries = new Array<i32>(maxRequests)
|
|
3205
|
-
this.keyCounts = new Array<i32>(maxRequests)
|
|
3206
|
-
this.takenKeys = new Array<i32>(maxRequests)
|
|
3491
|
+
this.table = []
|
|
3492
|
+
this.keySlots = []
|
|
3493
|
+
this.keyEntries = []
|
|
3494
|
+
this.keyCounts = []
|
|
3495
|
+
this.takenKeys = []
|
|
3207
3496
|
this.runs = []
|
|
3208
3497
|
for (let r: i32 = 0; r < config.runs; r++) {
|
|
3209
|
-
this.runs.push(new PgBatchRun(
|
|
3498
|
+
this.runs.push(new PgBatchRun())
|
|
3210
3499
|
}
|
|
3211
3500
|
}
|
|
3212
3501
|
|
|
@@ -3242,10 +3531,8 @@ export class PgBatcher {
|
|
|
3242
3531
|
if (q < 0) {
|
|
3243
3532
|
return false
|
|
3244
3533
|
}
|
|
3245
|
-
// Both halves folded to 31 bits, so that no step can overflow.
|
|
3246
|
-
const h: i32 = toI32(key & toI64(0x7fffffff)) ^ toI32((key >> toI64(31)) & toI64(0x7fffffff))
|
|
3247
3534
|
this.qInts[q] = key
|
|
3248
|
-
this.added(q, tag,
|
|
3535
|
+
this.added(q, tag, pgIntHash(key), pgDecimalLength(key))
|
|
3249
3536
|
return true
|
|
3250
3537
|
}
|
|
3251
3538
|
|
|
@@ -3394,15 +3681,19 @@ export class PgBatcher {
|
|
|
3394
3681
|
return -1
|
|
3395
3682
|
}
|
|
3396
3683
|
const ring: i32 = toI32(this.qTags.length)
|
|
3684
|
+
this.grow(this.count < this.maxRequests ? this.count : this.maxRequests)
|
|
3397
3685
|
const mask: i32 = toI32(this.table.length) - 1
|
|
3398
3686
|
let keys: i32 = 0
|
|
3399
3687
|
let taken: i32 = 0
|
|
3400
3688
|
let bytes: i32 = 2
|
|
3401
3689
|
while (taken < this.count && taken < this.maxRequests && taken < toI32(this.takenKeys.length)) {
|
|
3402
3690
|
const q: i32 = (this.head + taken) % ring
|
|
3403
|
-
// Probe for the key: a slot holding it, or the empty slot it would take
|
|
3691
|
+
// Probe for the key: a slot holding it, or the empty slot it would take;
|
|
3692
|
+
// -1 after `PG_BATCH_PROBES` occupied ones, when the key is bound again,
|
|
3693
|
+
// its own ordinal and rows, and left out of the table.
|
|
3404
3694
|
let slot: i32 = this.qHashes[q] & mask
|
|
3405
3695
|
let k: i32 = -1
|
|
3696
|
+
let probes: i32 = 0
|
|
3406
3697
|
while (this.table[slot] > 0) {
|
|
3407
3698
|
const seen: i32 = this.table[slot] - 1
|
|
3408
3699
|
const e: i32 = this.keyEntries[seen]
|
|
@@ -3410,6 +3701,11 @@ export class PgBatcher {
|
|
|
3410
3701
|
k = seen
|
|
3411
3702
|
break
|
|
3412
3703
|
}
|
|
3704
|
+
probes += 1
|
|
3705
|
+
if (probes === PG_BATCH_PROBES) {
|
|
3706
|
+
slot = -1
|
|
3707
|
+
break
|
|
3708
|
+
}
|
|
3413
3709
|
slot = (slot + 1) & mask
|
|
3414
3710
|
}
|
|
3415
3711
|
if (k < 0) {
|
|
@@ -3420,7 +3716,9 @@ export class PgBatcher {
|
|
|
3420
3716
|
bytes += cost
|
|
3421
3717
|
k = keys
|
|
3422
3718
|
keys += 1
|
|
3423
|
-
|
|
3719
|
+
if (slot >= 0) {
|
|
3720
|
+
this.table[slot] = keys
|
|
3721
|
+
}
|
|
3424
3722
|
this.keySlots[k] = slot
|
|
3425
3723
|
this.keyEntries[k] = q
|
|
3426
3724
|
this.keyCounts[k] = 0
|
|
@@ -3430,7 +3728,10 @@ export class PgBatcher {
|
|
|
3430
3728
|
taken += 1
|
|
3431
3729
|
}
|
|
3432
3730
|
for (let k: i32 = 0; k < keys && k < toI32(this.keySlots.length); k++) {
|
|
3433
|
-
this.
|
|
3731
|
+
const slot: i32 = this.keySlots[k]
|
|
3732
|
+
if (slot >= 0) {
|
|
3733
|
+
this.table[slot] = 0
|
|
3734
|
+
}
|
|
3434
3735
|
}
|
|
3435
3736
|
if (taken === 0) {
|
|
3436
3737
|
conn.bindCancel()
|
|
@@ -3440,6 +3741,11 @@ export class PgBatcher {
|
|
|
3440
3741
|
conn.bindEnd()
|
|
3441
3742
|
// The requests grouped by key, each key's in the order they were added.
|
|
3442
3743
|
const run: PgBatchRun = this.runs[r]
|
|
3744
|
+
if (toI32(run.tags.length) < taken) {
|
|
3745
|
+
// Grown together: `first` holds one more than `tags`, which bounds the keys too.
|
|
3746
|
+
run.tags = new Array<i32>(toI32(this.takenKeys.length))
|
|
3747
|
+
run.first = new Array<i32>(toI32(this.takenKeys.length) + 1)
|
|
3748
|
+
}
|
|
3443
3749
|
run.first[0] = 0
|
|
3444
3750
|
for (let k: i32 = 0; k < keys && k < toI32(this.keyCounts.length); k++) {
|
|
3445
3751
|
run.first[k + 1] = run.first[k] + this.keyCounts[k]
|
|
@@ -3461,6 +3767,34 @@ export class PgBatcher {
|
|
|
3461
3767
|
return run.handle
|
|
3462
3768
|
}
|
|
3463
3769
|
|
|
3770
|
+
/**
|
|
3771
|
+
* Grows `take`'s arrays, when they are smaller, to hold `need` requests:
|
|
3772
|
+
* to a power of two, at least `PG_BATCH_MIN_GROWTH` and at most
|
|
3773
|
+
* `maxRequests`, and the table to twice that, so it is at most half full.
|
|
3774
|
+
* A batcher whose batches stay small never holds arrays the size of its cap.
|
|
3775
|
+
*/
|
|
3776
|
+
private grow(need: i32): void {
|
|
3777
|
+
if (toI32(this.takenKeys.length) >= need) {
|
|
3778
|
+
return
|
|
3779
|
+
}
|
|
3780
|
+
let size: i32 = PG_BATCH_MIN_GROWTH
|
|
3781
|
+
while (size < need) {
|
|
3782
|
+
size *= 2
|
|
3783
|
+
}
|
|
3784
|
+
if (size > this.maxRequests) {
|
|
3785
|
+
size = this.maxRequests
|
|
3786
|
+
}
|
|
3787
|
+
let tableSize: i32 = 2
|
|
3788
|
+
while (tableSize < 2 * size) {
|
|
3789
|
+
tableSize *= 2
|
|
3790
|
+
}
|
|
3791
|
+
this.table = new Array<i32>(tableSize)
|
|
3792
|
+
this.keySlots = new Array<i32>(size)
|
|
3793
|
+
this.keyEntries = new Array<i32>(size)
|
|
3794
|
+
this.keyCounts = new Array<i32>(size)
|
|
3795
|
+
this.takenKeys = new Array<i32>(size)
|
|
3796
|
+
}
|
|
3797
|
+
|
|
3464
3798
|
/** Writes the queued request `q`'s key as the open array's next element; false when `conn` has no room. */
|
|
3465
3799
|
private bindKey(conn: PgConnection, q: i32): boolean {
|
|
3466
3800
|
if (this.keyType === PG_BATCH_INT) {
|