@amritk/nish-x86_64-linux 0.29.0 → 0.31.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.
@@ -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, and so is
63
- * every result column unless the bind asks otherwise. A row is read where it
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
@@ -123,14 +139,18 @@
123
139
  * `ticket` naming the request. Statements given to `register` are prepared on
124
140
  * each connection ahead of its first request. A connection that fails reports
125
141
  * `PG_LOST` for each request it took, then `PG_FAILED`; the program closes the
126
- * socket and the slot is built again on its next use. `tests/postgres/carrier.ts`
127
- * is a whole carrier: one `pollWait` loop holding the pool's sockets beside an
128
- * HTTP listener.
142
+ * socket and the slot is built again on its next use.
143
+ *
144
+ * **Carrier.** `PgCarrier`, in `nish/net/postgres-carrier`, moves a pool's
145
+ * bytes on an `HttpHandler`'s loop, so a route handler queries without
146
+ * blocking its worker. It is a module of its own because it reads sockets,
147
+ * the clock and the entropy source, and this one reaches none of them.
129
148
  *
130
149
  * **Batching.** `PgBatcher` turns many reads of one row shape by key — a user
131
150
  * by id, an article by slug — into one execution of a statement over
132
151
  * `unnest($1) with ordinality`. `addInt` or `addText` queues a request's key
133
- * and tag; `take(conn)` binds the oldest waiting, each distinct key once, up
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
134
154
  * to `maxRequests` and `maxBytes`, and answers a run's handle; `row`, `end`
135
155
  * and `fail` take that execution's events (a handle already answered makes
136
156
  * them a no-op counted in `stray`, so a server's late event cannot panic),
@@ -156,12 +176,13 @@
156
176
  * Written from PostgreSQL's "Frontend/Backend Protocol" chapter (version 18)
157
177
  * and RFC 5802 and RFC 7677; not ported from another implementation.
158
178
  */
159
- import { copyBytes, getF64BE, getI32BE, getI64BE, getU16BE, setI32BE, setU16BE } from "nish/bytes"
179
+ import { copyBytes, getF64BE, getI32BE, getI64BE, getU16BE, setI32BE, setI64BE, setU16BE } from "nish/bytes"
160
180
  import { base64StdPadDecode, base64StdPadEncode } from "nish/crypto/base64url"
161
181
  import { timingSafeEqual } from "nish/crypto/ct"
162
182
  import { hmacSha256, pbkdf2HmacSha256 } from "nish/crypto/hmac"
163
183
  import { Sha256 } from "nish/crypto/sha256"
164
184
  import { Secret, expose, exposeWith, secret, wipe } from "nish:secret"
185
+ import { urlDecode } from "nish/net/url"
165
186
 
166
187
  /** `next()` needs more input before it can say anything. */
167
188
  export const PG_NEED_MORE: i32 = 0
@@ -252,7 +273,8 @@ const PG_TAIL: i32 = 17
252
273
  const PG_BINARY_TAIL: i32 = 2
253
274
  // The result format code for binary.
254
275
  const PG_FORMAT_BINARY: i32 = 1
255
- // The element types `PgBinaryArray` reads, by OID: int8, int2 and int4.
276
+ // The element types `PgBinaryArray` reads, and the binary array parameters
277
+ // write (int4 and int8), by OID: int8, int2 and int4.
256
278
  const PG_OID_INT8: i32 = 20
257
279
  const PG_OID_INT2: i32 = 21
258
280
  const PG_OID_INT4: i32 = 23
@@ -432,6 +454,16 @@ const pgDecimalLength = (value: i64): i32 => {
432
454
  return n
433
455
  }
434
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
+
435
467
  /** Whether `text` holds a NUL byte, which a protocol string cannot. */
436
468
  const pgHasNul = (text: string): boolean => {
437
469
  const n: i32 = toI32(text.length)
@@ -1207,9 +1239,19 @@ export class PgConnection {
1207
1239
  private bindAt: i32 = -1
1208
1240
  private paramCountAt: i32 = 0
1209
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
1210
1248
  // The open array parameter's length word, -1 when none is open, and its elements so far.
1211
1249
  private arrayAt: i32 = -1
1212
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
1213
1255
  private state: i32 = 0
1214
1256
  private clientNonce: string = ""
1215
1257
  private serverSignature: u8[]
@@ -1235,6 +1277,7 @@ export class PgConnection {
1235
1277
  this.colStarts = new Array<i32>(16)
1236
1278
  this.colLengths = new Array<i32>(16)
1237
1279
  this.colTypes = new Array<i32>(16)
1280
+ this.binaryParams = []
1238
1281
  this.serverSignature = []
1239
1282
  const head: u8[] = []
1240
1283
  for (let i: i32 = 0; i < PG_NONCE_BYTES && i < toI32(nonce.length); i++) {
@@ -1911,14 +1954,13 @@ export class PgConnection {
1911
1954
 
1912
1955
  /**
1913
1956
  * Whether a builder call may queue `size` more bytes and still leave
1914
- * `PG_TAIL` under `maxOutput`. When they would not fit after the end of the
1957
+ * `PG_TAIL`, and what the open bind's end owes, under `maxOutput`. When they would not fit after the end of the
1915
1958
  * buffer, the unsent bytes move to its start first: a builder call asks
1916
1959
  * before it takes any offset into the buffer, so nothing it holds moves.
1917
1960
  */
1918
1961
  private room(size: i32): boolean {
1919
1962
  const queued: i32 = this.outEnd - this.outStart
1920
- const tail: i32 = this.bindAt >= 0 && this.bindBinary ? PG_TAIL + PG_BINARY_TAIL : PG_TAIL
1921
- if (size < 0 || size > this.config.maxOutput - tail - queued) {
1963
+ if (size < 0 || size > this.config.maxOutput - PG_TAIL - this.bindOwed - queued) {
1922
1964
  return false
1923
1965
  }
1924
1966
  if (this.outStart > 0 && this.outEnd + size > toI32(this.out.length)) {
@@ -2112,6 +2154,8 @@ export class PgConnection {
2112
2154
  }
2113
2155
  this.bindOpened = opens
2114
2156
  this.bindBinary = binary
2157
+ this.bindOwed = extra
2158
+ this.binaryParamCount = 0
2115
2159
  this.bindAt = this.startMessage(PG_FE_BIND)
2116
2160
  this.putByte(PG_ZERO)
2117
2161
  this.putCString(name)
@@ -2129,9 +2173,37 @@ export class PgConnection {
2129
2173
  */
2130
2174
  private startParam(size: i32): i32 {
2131
2175
  this.noArrayOpen()
2132
- if (this.bindAt < 0 || this.paramCount === 65535 || size > PG_MAX_CAP || !this.room(4 + size)) {
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)) {
2179
+ return -1
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)) {
2133
2198
  return -1
2134
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
2135
2207
  this.paramCount += 1
2136
2208
  const at: i32 = this.outEnd
2137
2209
  this.putI32(PG_ZERO)
@@ -2244,7 +2316,7 @@ export class PgConnection {
2244
2316
  const end: i32 = start + count
2245
2317
  // Its exact size first, so that room is asked for once and a refusal writes nothing.
2246
2318
  let size: i64 = count > 0 ? toI64(count + 1) : toI64(2)
2247
- 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++) {
2248
2320
  size += toI64(pgDecimalLength(values[i]))
2249
2321
  }
2250
2322
  const at: i32 = this.startParam(size > toI64(PG_MAX_CAP) ? PG_MAX_CAP + 1 : toI32(size))
@@ -2252,7 +2324,7 @@ export class PgConnection {
2252
2324
  return false
2253
2325
  }
2254
2326
  this.putByte(PG_LBRACE)
2255
- 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++) {
2256
2328
  // Read before the comma's call, which could drop the length fact.
2257
2329
  const value: i64 = values[i]
2258
2330
  if (i > start) {
@@ -2306,6 +2378,80 @@ export class PgConnection {
2306
2378
  return true
2307
2379
  }
2308
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
+
2309
2455
  // An array parameter built element by element: `arrayStart()`, then
2310
2456
  // `arrayElement`, `arrayInt` or `arrayNull` for each element, then `arrayEnd()`. An
2311
2457
  // element refused for room writes nothing and leaves the array open, so a
@@ -2335,6 +2481,9 @@ export class PgConnection {
2335
2481
  if (this.arrayAt < 0) {
2336
2482
  panic("nish/net/postgres: an array element with no arrayStart()")
2337
2483
  }
2484
+ if (this.arrayOid !== 0) {
2485
+ panic("nish/net/postgres: a text array element in a binary array")
2486
+ }
2338
2487
  }
2339
2488
 
2340
2489
  /** Whether the open array has room for an element of `size` bytes, its comma, and the `}` it owes. */
@@ -2413,13 +2562,98 @@ export class PgConnection {
2413
2562
  return true
2414
2563
  }
2415
2564
 
2416
- /** Ends the open array parameter; never refused for room, which its elements kept. Panics when no array is open. */
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. */
2417
2638
  arrayEnd(): void {
2418
2639
  if (this.arrayAt < 0) {
2419
2640
  panic("nish/net/postgres: arrayEnd() with no arrayStart()")
2420
2641
  }
2421
- this.tailRoom(toI32(1))
2422
- this.putByte(PG_RBRACE)
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
+ }
2423
2657
  this.endParam(this.arrayAt)
2424
2658
  this.arrayAt = -1
2425
2659
  }
@@ -2438,8 +2672,11 @@ export class PgConnection {
2438
2672
  panic("nish/net/postgres: bindEnd() while an array is open")
2439
2673
  }
2440
2674
  const binary: boolean = this.bindBinary
2441
- this.tailRoom(binary ? PG_TAIL + PG_BINARY_TAIL : PG_TAIL)
2675
+ this.tailRoom(PG_TAIL + this.bindOwed)
2442
2676
  const at: i32 = this.bindAt
2677
+ if (this.binaryParamCount > 0) {
2678
+ this.putFormatCodes()
2679
+ }
2443
2680
  setU16BE(this.out, this.paramCountAt, toU16(this.paramCount))
2444
2681
  if (binary) {
2445
2682
  // One format code, which the server applies to every column.
@@ -2450,6 +2687,7 @@ export class PgConnection {
2450
2687
  }
2451
2688
  this.endMessage(at)
2452
2689
  this.bindAt = -1
2690
+ this.bindOwed = 0
2453
2691
  const execute: i32 = this.startMessage(PG_FE_EXECUTE)
2454
2692
  this.putByte(PG_ZERO)
2455
2693
  this.putI32(PG_ZERO)
@@ -2478,7 +2716,30 @@ export class PgConnection {
2478
2716
  private dropBind(): void {
2479
2717
  this.outEnd = this.bindAt
2480
2718
  this.bindAt = -1
2719
+ this.bindOwed = 0
2481
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
2482
2743
  }
2483
2744
 
2484
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. */
@@ -3001,10 +3262,22 @@ export const PG_BATCH_MISSING: i32 = 3
3001
3262
  /** `PgBatcher.next(run)`: the request `tag`'s execution failed, and its rows so far may be incomplete. */
3002
3263
  export const PG_BATCH_FAILED: i32 = 4
3003
3264
 
3004
- // `take` probes at most maxRequests^2 / 2 slots, so the cap keeps that near 5 * 10^5;
3005
- // a probe whose hash matches in full compares the key's bytes, so the bytes
3006
- // compared are at most that times the longest key, which `maxBytes` bounds.
3007
- const PG_BATCH_MAX_REQUESTS: i32 = 1024
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
3008
3281
  const PG_BATCH_MAX_QUEUE: i32 = 1048576
3009
3282
  const PG_BATCH_MAX_RUNS: i32 = 1024
3010
3283
  // The takes a run's handles count before they wrap: handles stay under 2^30.
@@ -3012,6 +3285,28 @@ const PG_BATCH_USES: i32 = 1048576
3012
3285
  // The array's `{}` and the longest int8 element, `-9223372036854775808`.
3013
3286
  const PG_BATCH_MIN_BYTES: i32 = 22
3014
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
+
3015
3310
  /** What a `PgBatcher` sends: one statement shape, its key, and its caps. */
3016
3311
  export class PgBatchConfig {
3017
3312
  /** The prepared statement's name, which `PgBatcher.sql` is prepared under. */
@@ -3038,9 +3333,9 @@ export class PgBatchConfig {
3038
3333
 
3039
3334
  /** One execution's requests, grouped by key, and how far its rows have been routed. */
3040
3335
  class PgBatchRun {
3041
- /** Key `k`'s requests' tags are `tags[first[k], first[k + 1])`, in the order they were added. */
3042
- readonly tags: i32[]
3043
- readonly first: i32[]
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[]
3044
3339
  keys: i32 = 0
3045
3340
  /** The key rows are being read for, whether it has had one, and the next of its requests to answer. */
3046
3341
  cur: i32 = 0
@@ -3055,9 +3350,9 @@ class PgBatchRun {
3055
3350
  ended: boolean = false
3056
3351
  failed: boolean = false
3057
3352
 
3058
- constructor(maxRequests: i32) {
3059
- this.tags = new Array<i32>(maxRequests)
3060
- this.first = new Array<i32>(maxRequests + 1)
3353
+ constructor() {
3354
+ this.tags = []
3355
+ this.first = []
3061
3356
  }
3062
3357
 
3063
3358
  /** Opens the run for `keys` keys, none of whose rows have been read. */
@@ -3078,7 +3373,9 @@ class PgBatchRun {
3078
3373
  * it reads no clock and makes no socket. Each request is a key and the
3079
3374
  * caller's `tag`, queued by `addInt` or `addText`. When a connection is free,
3080
3375
  * `take(conn)` binds up to `maxRequests` of them, and up to `maxBytes` of
3081
- * keys, as one execution of `sql`, each distinct key once, and answers the
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
3082
3379
  * execution's run handle. Each `PG_ROW` of that execution goes to
3083
3380
  * `row(run, conn)`, its `PG_COMPLETE` to `end(run)`, and an error or a lost
3084
3381
  * connection to `fail(run)`; after each, `next(run)` answers events until
@@ -3118,12 +3415,13 @@ export class PgBatcher {
3118
3415
  private readonly qLengths: i32[]
3119
3416
  private readonly store: u8[]
3120
3417
  // `take`'s open-addressed table of the keys it has bound, each a key number
3121
- // plus one, 0 for an empty slot; and for each key, its slot and its first request.
3122
- private readonly table: i32[]
3123
- private readonly keySlots: i32[]
3124
- private readonly keyEntries: i32[]
3125
- private readonly keyCounts: i32[]
3126
- private readonly takenKeys: i32[]
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[]
3127
3425
  private readonly runs: PgBatchRun[]
3128
3426
  /** The request the last `next(run)` answered about; -1 for none. */
3129
3427
  tag: i32 = -1
@@ -3131,8 +3429,9 @@ export class PgBatcher {
3131
3429
  refused: i32 = 0
3132
3430
  /** The number of `row`, `end` and `fail` calls on a spent handle, each ignored, stopping at 2^31 - 1. */
3133
3431
  stray: i32 = 0
3432
+ /** The most requests one execution takes, from `PgBatchConfig.maxRequests`. */
3433
+ readonly maxRequests: i32
3134
3434
  private readonly keyType: i32
3135
- private readonly maxRequests: i32
3136
3435
  private readonly maxBytes: i32
3137
3436
  private readonly binary: boolean
3138
3437
  private storeEnd: i32 = 0
@@ -3189,19 +3488,14 @@ export class PgBatcher {
3189
3488
  this.qStarts = new Array<i32>(textSize)
3190
3489
  this.qLengths = new Array<i32>(textSize)
3191
3490
  this.store = new Array<u8>(storeSize)
3192
- // At least twice the keys a take can hold, a power of two for the mask.
3193
- let tableSize: i32 = 2
3194
- while (tableSize < 2 * maxRequests) {
3195
- tableSize *= 2
3196
- }
3197
- this.table = new Array<i32>(tableSize)
3198
- this.keySlots = new Array<i32>(maxRequests)
3199
- this.keyEntries = new Array<i32>(maxRequests)
3200
- this.keyCounts = new Array<i32>(maxRequests)
3201
- this.takenKeys = new Array<i32>(maxRequests)
3491
+ this.table = []
3492
+ this.keySlots = []
3493
+ this.keyEntries = []
3494
+ this.keyCounts = []
3495
+ this.takenKeys = []
3202
3496
  this.runs = []
3203
3497
  for (let r: i32 = 0; r < config.runs; r++) {
3204
- this.runs.push(new PgBatchRun(maxRequests))
3498
+ this.runs.push(new PgBatchRun())
3205
3499
  }
3206
3500
  }
3207
3501
 
@@ -3210,6 +3504,24 @@ export class PgBatcher {
3210
3504
  return this.count
3211
3505
  }
3212
3506
 
3507
+ /**
3508
+ * Drops the oldest waiting request and answers its tag, or -1 when none
3509
+ * waits: for a program that cannot reach the server to fail requests
3510
+ * rather than hold them.
3511
+ */
3512
+ shed(): i32 {
3513
+ if (this.count === 0) {
3514
+ return -1
3515
+ }
3516
+ const tag: i32 = this.qTags[this.head]
3517
+ this.head = (this.head + 1) % toI32(this.qTags.length)
3518
+ this.count -= 1
3519
+ if (this.count === 0) {
3520
+ this.storeEnd = 0
3521
+ }
3522
+ return tag
3523
+ }
3524
+
3213
3525
  /** Queues a request for the int8 `key`; false when `queueSize` are waiting. Panics on a text batcher. */
3214
3526
  addInt(key: i64, tag: i32): boolean {
3215
3527
  if (this.keyType !== PG_BATCH_INT) {
@@ -3219,10 +3531,8 @@ export class PgBatcher {
3219
3531
  if (q < 0) {
3220
3532
  return false
3221
3533
  }
3222
- // Both halves folded to 31 bits, so that no step can overflow.
3223
- const h: i32 = toI32(key & toI64(0x7fffffff)) ^ toI32((key >> toI64(31)) & toI64(0x7fffffff))
3224
3534
  this.qInts[q] = key
3225
- this.added(q, tag, h ^ (h >> 15), pgDecimalLength(key))
3535
+ this.added(q, tag, pgIntHash(key), pgDecimalLength(key))
3226
3536
  return true
3227
3537
  }
3228
3538
 
@@ -3371,15 +3681,19 @@ export class PgBatcher {
3371
3681
  return -1
3372
3682
  }
3373
3683
  const ring: i32 = toI32(this.qTags.length)
3684
+ this.grow(this.count < this.maxRequests ? this.count : this.maxRequests)
3374
3685
  const mask: i32 = toI32(this.table.length) - 1
3375
3686
  let keys: i32 = 0
3376
3687
  let taken: i32 = 0
3377
3688
  let bytes: i32 = 2
3378
3689
  while (taken < this.count && taken < this.maxRequests && taken < toI32(this.takenKeys.length)) {
3379
3690
  const q: i32 = (this.head + taken) % ring
3380
- // 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.
3381
3694
  let slot: i32 = this.qHashes[q] & mask
3382
3695
  let k: i32 = -1
3696
+ let probes: i32 = 0
3383
3697
  while (this.table[slot] > 0) {
3384
3698
  const seen: i32 = this.table[slot] - 1
3385
3699
  const e: i32 = this.keyEntries[seen]
@@ -3387,6 +3701,11 @@ export class PgBatcher {
3387
3701
  k = seen
3388
3702
  break
3389
3703
  }
3704
+ probes += 1
3705
+ if (probes === PG_BATCH_PROBES) {
3706
+ slot = -1
3707
+ break
3708
+ }
3390
3709
  slot = (slot + 1) & mask
3391
3710
  }
3392
3711
  if (k < 0) {
@@ -3397,7 +3716,9 @@ export class PgBatcher {
3397
3716
  bytes += cost
3398
3717
  k = keys
3399
3718
  keys += 1
3400
- this.table[slot] = keys
3719
+ if (slot >= 0) {
3720
+ this.table[slot] = keys
3721
+ }
3401
3722
  this.keySlots[k] = slot
3402
3723
  this.keyEntries[k] = q
3403
3724
  this.keyCounts[k] = 0
@@ -3407,7 +3728,10 @@ export class PgBatcher {
3407
3728
  taken += 1
3408
3729
  }
3409
3730
  for (let k: i32 = 0; k < keys && k < toI32(this.keySlots.length); k++) {
3410
- this.table[this.keySlots[k]] = 0
3731
+ const slot: i32 = this.keySlots[k]
3732
+ if (slot >= 0) {
3733
+ this.table[slot] = 0
3734
+ }
3411
3735
  }
3412
3736
  if (taken === 0) {
3413
3737
  conn.bindCancel()
@@ -3417,6 +3741,11 @@ export class PgBatcher {
3417
3741
  conn.bindEnd()
3418
3742
  // The requests grouped by key, each key's in the order they were added.
3419
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
+ }
3420
3749
  run.first[0] = 0
3421
3750
  for (let k: i32 = 0; k < keys && k < toI32(this.keyCounts.length); k++) {
3422
3751
  run.first[k + 1] = run.first[k] + this.keyCounts[k]
@@ -3438,6 +3767,34 @@ export class PgBatcher {
3438
3767
  return run.handle
3439
3768
  }
3440
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
+
3441
3798
  /** Writes the queued request `q`'s key as the open array's next element; false when `conn` has no room. */
3442
3799
  private bindKey(conn: PgConnection, q: i32): boolean {
3443
3800
  if (this.keyType === PG_BATCH_INT) {
@@ -3958,3 +4315,273 @@ export class PgBatchTurns {
3958
4315
  }
3959
4316
  }
3960
4317
  }
4318
+
4319
+ /** `PgTarget.failure`: none; connect as `unix` says. */
4320
+ export const PG_TARGET_OK: i32 = 0
4321
+ /** `PgTarget.failure`: not a `postgres://` or `postgresql://` URL, or a query parameter with no `=`. */
4322
+ export const PG_TARGET_BAD_URL: i32 = 1
4323
+ /** `PgTarget.failure`: a `%` not followed by two hex digits, `%00`, or escapes that decode to bytes that are not UTF-8. */
4324
+ export const PG_TARGET_BAD_ESCAPE: i32 = 2
4325
+ /** `PgTarget.failure`: no host, more than one (a `,`), an unclosed `[` or text after its `]`, a raw `@` past the user information, or a NUL byte. */
4326
+ export const PG_TARGET_BAD_HOST: i32 = 3
4327
+ /** `PgTarget.failure`: a port that is not 1 to 65535 in decimal digits. */
4328
+ export const PG_TARGET_BAD_PORT: i32 = 4
4329
+ /** `PgTarget.failure`: a socket path longer than Linux's `sun_path` holds. */
4330
+ export const PG_TARGET_TOO_LONG: i32 = 5
4331
+
4332
+ /** The port a target names when nothing else does. */
4333
+ const PG_DEFAULT_PORT: i32 = 5432
4334
+ /** The longest socket path, NUL excluded: Linux's 108-byte `sun_path`, less its NUL; an `@` name has no NUL, so its `@` takes that byte. */
4335
+ const PG_SOCKET_PATH_MAX: i32 = 107
4336
+
4337
+ /**
4338
+ * Where a connection goes, from libpq's `host` and `port` or a
4339
+ * `postgres://` URL: `pgTarget` and `pgTargetUrl` make one, reading no
4340
+ * socket and no file. When `failure` is `PG_TARGET_OK`, a `unix` target is
4341
+ * `unixConnect(path)`, and any other is `host` and `port`, for
4342
+ * `netAddress` and `tcpConnect`.
4343
+ *
4344
+ * A host starting with `/` is a socket directory, and one starting with `@`
4345
+ * a Linux abstract name, as in libpq: the socket is
4346
+ * `<host>/.s.PGSQL.<port>`. The host is always the directory, never the
4347
+ * socket file itself. Any other host is TCP.
4348
+ */
4349
+ export class PgTarget {
4350
+ /** `PG_TARGET_OK`, or the `PG_TARGET_*` reason the input was refused. */
4351
+ failure: i32 = 0
4352
+ /** Whether the target is a Unix socket, at `path`. */
4353
+ unix: boolean = false
4354
+ /** The host as given (a URL's decoded, without an IPv6 address's brackets); for a socket, its directory or `@` name. */
4355
+ host: string = ""
4356
+ /** The port: the TCP port, or the number in the socket's name. */
4357
+ port: i32 = 5432
4358
+ /** The socket's path, `<host>/.s.PGSQL.<port>`, when `unix`; otherwise "". */
4359
+ path: string = ""
4360
+ }
4361
+
4362
+ /** A target refused for `failure`. */
4363
+ const pgTargetFailed = (failure: i32): PgTarget => {
4364
+ const t = new PgTarget()
4365
+ t.failure = failure
4366
+ return t
4367
+ }
4368
+
4369
+ /**
4370
+ * The target `host` and `port` name, as libpq's `host` and `port` do: a host
4371
+ * starting with `/` or `@` is the socket `<host>/.s.PGSQL.<port>`, any other
4372
+ * a TCP host. The host is not percent-decoded. A port outside 1 to 65535, an
4373
+ * empty host, a `,` (more than one host) or a NUL byte is refused, and so is
4374
+ * a socket path over 107 bytes (an `@` name over 107 after its `@`), never
4375
+ * cut short.
4376
+ */
4377
+ export const pgTarget = (host: string, port: i32): PgTarget => {
4378
+ if (port < 1 || port > 65535) {
4379
+ return pgTargetFailed(PG_TARGET_BAD_PORT)
4380
+ }
4381
+ const n: i32 = toI32(host.length)
4382
+ if (n === 0) {
4383
+ return pgTargetFailed(PG_TARGET_BAD_HOST)
4384
+ }
4385
+ for (let i: i32 = 0; i < n; i++) {
4386
+ const c: i32 = toI32(host.charCodeAt(i))
4387
+ if (c === 0 || c === 44) {
4388
+ return pgTargetFailed(PG_TARGET_BAD_HOST)
4389
+ }
4390
+ }
4391
+ const t = new PgTarget()
4392
+ t.host = host
4393
+ t.port = port
4394
+ const first: i32 = toI32(host.charCodeAt(0))
4395
+ if (first === 47 || first === 64) {
4396
+ const path: string = `${host}/.s.PGSQL.${port}`
4397
+ // An `@` name's NUL-free bytes after the `@` fill what a path's NUL would.
4398
+ const limit: i32 = first === 64 ? PG_SOCKET_PATH_MAX + 1 : PG_SOCKET_PATH_MAX
4399
+ if (toI32(path.length) > limit) {
4400
+ return pgTargetFailed(PG_TARGET_TOO_LONG)
4401
+ }
4402
+ t.unix = true
4403
+ t.path = path
4404
+ }
4405
+ return t
4406
+ }
4407
+
4408
+ /** `text[start, end)` percent-decoded, or null for a `%` without two hex digits after it, `%00`, or bytes that are not UTF-8. */
4409
+ const pgDecoded = (text: string, start: i32, end: i32): string | null =>
4410
+ urlDecode(text.substring(start, end), 0)
4411
+
4412
+ /** The port `text` spells, 1 to 5 decimal digits naming 1 to 65535, or -1. */
4413
+ const pgPortValue = (text: string): i32 => {
4414
+ const n: i32 = toI32(text.length)
4415
+ if (n === 0 || n > 5) {
4416
+ return -1
4417
+ }
4418
+ let v: i32 = 0
4419
+ for (let i: i32 = 0; i < n; i++) {
4420
+ const c: i32 = toI32(text.charCodeAt(i))
4421
+ if (c < 48 || c > 57) {
4422
+ return -1
4423
+ }
4424
+ v = v * 10 + (c - 48)
4425
+ }
4426
+ if (v < 1 || v > 65535) {
4427
+ return -1
4428
+ }
4429
+ return v
4430
+ }
4431
+
4432
+ /**
4433
+ * The target a `postgres://` or `postgresql://` URL names, as libpq reads
4434
+ * one: `postgres://[user[:password]@][host][:port][/dbname][?key=value&...]`.
4435
+ * The host, the port and every query key and value are percent-decoded, so a
4436
+ * socket directory is written `%2Ftmp` or passed as `?host=/tmp`; a `host` or
4437
+ * `port` in the query overrides the authority's, and other keys are ignored.
4438
+ * The user, password and database are not read here, but a bad escape in them
4439
+ * still refuses the URL. A `%` without two hex digits after it, `%00`, or a
4440
+ * part that decodes to bytes that are not UTF-8 is `PG_TARGET_BAD_ESCAPE`.
4441
+ * The user information ends at the first `@`, as in libpq; a raw `@` after a
4442
+ * `?` or after that first `@` in the authority, or a raw NUL byte anywhere,
4443
+ * is `PG_TARGET_BAD_HOST`, as is a URL with no host anywhere, where libpq
4444
+ * would use its default socket directory. An empty port or query value is
4445
+ * refused, where libpq would take it as unset.
4446
+ * The decoded host is then resolved as `pgTarget` resolves one.
4447
+ */
4448
+ export const pgTargetUrl = (url: string): PgTarget => {
4449
+ let at: i32 = 0
4450
+ if (url.startsWith("postgresql://")) {
4451
+ at = 13
4452
+ } else if (url.startsWith("postgres://")) {
4453
+ at = 11
4454
+ } else {
4455
+ return pgTargetFailed(PG_TARGET_BAD_URL)
4456
+ }
4457
+ const n: i32 = toI32(url.length)
4458
+ // The authority runs to the first `/` or `?`; the path to the `?`.
4459
+ let authorityEnd: i32 = at
4460
+ while (authorityEnd < n) {
4461
+ const c: i32 = toI32(url.charCodeAt(authorityEnd))
4462
+ if (c === 47 || c === 63) {
4463
+ break
4464
+ }
4465
+ authorityEnd += 1
4466
+ }
4467
+ let queryStart: i32 = authorityEnd
4468
+ while (queryStart < n && toI32(url.charCodeAt(queryStart)) !== 63) {
4469
+ queryStart += 1
4470
+ }
4471
+ // A raw NUL byte anywhere is refused: no part may carry one past here.
4472
+ for (let i: i32 = at; i < n; i++) {
4473
+ if (toI32(url.charCodeAt(i)) === 0) {
4474
+ return pgTargetFailed(PG_TARGET_BAD_HOST)
4475
+ }
4476
+ }
4477
+ // libpq ends the user information at the first `@` before a `/`, a `?`
4478
+ // included. One after a `?`, or a second in the host and port, reads
4479
+ // differently by other parsers, so either is refused. The loops below also
4480
+ // test `>= 0` and `< n`, always true as `authorityEnd <= n`, so the prover
4481
+ // drops each access's bounds check.
4482
+ let firstAt: i32 = at
4483
+ while (firstAt >= 0 && firstAt < n) {
4484
+ const c: i32 = toI32(url.charCodeAt(firstAt))
4485
+ if (c === 64 || c === 47) {
4486
+ break
4487
+ }
4488
+ firstAt += 1
4489
+ }
4490
+ let hostStart: i32 = at
4491
+ if (firstAt < n && toI32(url.charCodeAt(firstAt)) === 64) {
4492
+ if (firstAt > authorityEnd) {
4493
+ return pgTargetFailed(PG_TARGET_BAD_HOST)
4494
+ }
4495
+ hostStart = firstAt + 1
4496
+ }
4497
+ for (let i: i32 = hostStart; i >= 0 && i < authorityEnd && i < n; i++) {
4498
+ if (toI32(url.charCodeAt(i)) === 64) {
4499
+ return pgTargetFailed(PG_TARGET_BAD_HOST)
4500
+ }
4501
+ }
4502
+ if (hostStart > at && pgDecoded(url, at, hostStart - 1) === null) {
4503
+ return pgTargetFailed(PG_TARGET_BAD_ESCAPE)
4504
+ }
4505
+ if (authorityEnd < queryStart && pgDecoded(url, authorityEnd, queryStart) === null) {
4506
+ return pgTargetFailed(PG_TARGET_BAD_ESCAPE)
4507
+ }
4508
+ // The host ends at a `:` before the port, after an IPv6 address's `]`.
4509
+ let hostEnd: i32 = authorityEnd
4510
+ let portStart: i32 = -1
4511
+ let bracketed: boolean = false
4512
+ if (hostStart < authorityEnd && toI32(url.charCodeAt(hostStart)) === 91) {
4513
+ let close: i32 = hostStart + 1
4514
+ while (close >= 0 && close < authorityEnd && close < n && toI32(url.charCodeAt(close)) !== 93) {
4515
+ close += 1
4516
+ }
4517
+ if (close === authorityEnd) {
4518
+ return pgTargetFailed(PG_TARGET_BAD_HOST)
4519
+ }
4520
+ bracketed = true
4521
+ hostEnd = close + 1
4522
+ if (hostEnd < authorityEnd) {
4523
+ if (toI32(url.charCodeAt(hostEnd)) !== 58) {
4524
+ return pgTargetFailed(PG_TARGET_BAD_HOST)
4525
+ }
4526
+ portStart = hostEnd + 1
4527
+ }
4528
+ } else {
4529
+ for (let i: i32 = hostStart; i >= 0 && i < authorityEnd && i < n; i++) {
4530
+ if (toI32(url.charCodeAt(i)) === 58) {
4531
+ hostEnd = i
4532
+ portStart = i + 1
4533
+ break
4534
+ }
4535
+ }
4536
+ }
4537
+ const decodedHost: string | null = bracketed
4538
+ ? pgDecoded(url, hostStart + 1, hostEnd - 1)
4539
+ : pgDecoded(url, hostStart, hostEnd)
4540
+ if (decodedHost === null) {
4541
+ return pgTargetFailed(PG_TARGET_BAD_ESCAPE)
4542
+ }
4543
+ let host: string = decodedHost
4544
+ let port: i32 = PG_DEFAULT_PORT
4545
+ if (portStart >= 0) {
4546
+ const decodedPort: string | null = pgDecoded(url, portStart, authorityEnd)
4547
+ if (decodedPort === null) {
4548
+ return pgTargetFailed(PG_TARGET_BAD_ESCAPE)
4549
+ }
4550
+ port = pgPortValue(decodedPort)
4551
+ if (port < 0) {
4552
+ return pgTargetFailed(PG_TARGET_BAD_PORT)
4553
+ }
4554
+ }
4555
+ // The query: `key=value` pairs split at `&`.
4556
+ let i: i32 = queryStart + 1
4557
+ while (i >= 0 && i < n) {
4558
+ let end: i32 = i
4559
+ let eq: i32 = -1
4560
+ while (end < n && toI32(url.charCodeAt(end)) !== 38) {
4561
+ if (eq < 0 && toI32(url.charCodeAt(end)) === 61) {
4562
+ eq = end
4563
+ }
4564
+ end += 1
4565
+ }
4566
+ if (end > i) {
4567
+ if (eq < 0) {
4568
+ return pgTargetFailed(PG_TARGET_BAD_URL)
4569
+ }
4570
+ const key: string | null = pgDecoded(url, i, eq)
4571
+ const value: string | null = pgDecoded(url, eq + 1, end)
4572
+ if (key === null || value === null) {
4573
+ return pgTargetFailed(PG_TARGET_BAD_ESCAPE)
4574
+ }
4575
+ if (key === "host") {
4576
+ host = value
4577
+ } else if (key === "port") {
4578
+ port = pgPortValue(value)
4579
+ if (port < 0) {
4580
+ return pgTargetFailed(PG_TARGET_BAD_PORT)
4581
+ }
4582
+ }
4583
+ }
4584
+ i = end + 1
4585
+ }
4586
+ return pgTarget(host, port)
4587
+ }