@amritk/nish-x86_64-linux 0.24.0 → 0.26.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 +45 -4
- package/package.json +1 -1
- package/runtime/browser.mjs +4 -3
- package/runtime/core/host.d.ts +12 -0
- package/runtime/core/host.mjs +74 -2
- package/runtime/nish.d.ts +148 -0
- package/runtime/nish.h +59 -0
- package/runtime/nish.mjs +25 -0
- package/runtime/node.mjs +136 -6
- package/runtime/runtime-net.c +44 -3
- package/runtime/runtime-os.c +351 -18
- package/scripts/build.sh +13 -3
- package/std/README.md +27 -10
- package/std/cli-args.ts +1216 -0
- package/std/cli-flags.ts +585 -0
- package/std/cli.ts +308 -19
- package/std/compress/checksum.ts +132 -20
- package/std/compress/deflate.ts +638 -248
- package/std/compress/gzip.ts +20 -6
- package/std/crypto/LICENSE-fiat-crypto-p384 +21 -0
- package/std/crypto/p256.ts +63 -1
- package/std/crypto/p384.ts +6044 -0
- package/std/crypto/pki-roots.ts +322 -0
- package/std/crypto/pki.ts +1207 -0
- package/std/crypto/rsa.ts +663 -0
- package/std/crypto/x509.ts +1054 -58
- package/std/net/dns-config.ts +734 -0
- package/std/net/dns-message.ts +517 -0
- package/std/net/dns-resolver.ts +1015 -0
- package/std/net/dns.ts +478 -0
- package/std/net/http1-client.ts +1227 -0
- package/std/net/http1-server.ts +7 -2
- package/std/net/http1.ts +9 -9
- package/std/net/postgres.ts +259 -26
- package/std/net/tls/client.ts +1094 -0
- package/std/net/tls/codec.ts +585 -3
- package/std/net/tls/record-client.ts +1192 -0
- package/std/net/tls/verify.ts +221 -0
- package/std/net/tls-tcp.ts +642 -11
- package/std/net/tls.ts +2 -2
- package/std/net/url.ts +559 -4
- package/std/yaml-write.ts +415 -0
package/std/net/http1-server.ts
CHANGED
|
@@ -1272,7 +1272,10 @@ export class Http1Connection {
|
|
|
1272
1272
|
enc.identityFields(names, values)
|
|
1273
1273
|
}
|
|
1274
1274
|
// `respond` and the body together, or neither: what `respond` sets is
|
|
1275
|
-
// put back when the body does not fit behind the head.
|
|
1275
|
+
// put back when the body does not fit behind the head. `room` first, so
|
|
1276
|
+
// that what was sent is already moved out of the way and the `outputEnd`
|
|
1277
|
+
// put back is one `respond` did not move.
|
|
1278
|
+
this.room()
|
|
1276
1279
|
const outputEnd: i32 = this.outputEnd
|
|
1277
1280
|
const closeAfter: boolean = this.closeAfter
|
|
1278
1281
|
const r: i32 = this.respond(status, enc.names, enc.values, n)
|
|
@@ -1954,7 +1957,9 @@ export class Http1TlsServer {
|
|
|
1954
1957
|
readable(slot: i32): i32 {
|
|
1955
1958
|
const live: boolean = this.live(slot)
|
|
1956
1959
|
const now: i64 = monotonicNanos()
|
|
1957
|
-
|
|
1960
|
+
// What a lingering slot drains is not activity, so `idleTimeout` bounds
|
|
1961
|
+
// how long a client that trickles bytes after the close can hold it.
|
|
1962
|
+
if (live && !this.tls.lingering(slot)) {
|
|
1958
1963
|
this.connections[slot].lastActive = now
|
|
1959
1964
|
}
|
|
1960
1965
|
this.tls.readable(slot)
|
package/std/net/http1.ts
CHANGED
|
@@ -524,7 +524,7 @@ const http1TakeLine = (p: Http1Parser, limit: i32, overStatus: i32): i32 => {
|
|
|
524
524
|
}
|
|
525
525
|
|
|
526
526
|
/** Whether `c` is a tchar of RFC 9110 §5.6.2, the bytes a token is made of. */
|
|
527
|
-
const http1IsTokenByte = (c: i32): boolean =>
|
|
527
|
+
export const http1IsTokenByte = (c: i32): boolean =>
|
|
528
528
|
(c >= 97 && c <= 122) ||
|
|
529
529
|
(c >= 65 && c <= 90) ||
|
|
530
530
|
(c >= 48 && c <= 57) ||
|
|
@@ -541,10 +541,10 @@ const http1IsTokenByte = (c: i32): boolean =>
|
|
|
541
541
|
c === 126
|
|
542
542
|
|
|
543
543
|
/** Whether `c` may appear in a field value (RFC 9110 §5.5): HTAB, SP, VCHAR or obs-text. */
|
|
544
|
-
const http1IsValueByte = (c: i32): boolean => c === 9 || (c >= 32 && c !== 127)
|
|
544
|
+
export const http1IsValueByte = (c: i32): boolean => c === 9 || (c >= 32 && c !== 127)
|
|
545
545
|
|
|
546
546
|
/** `buf[from .. to)` as a string, each upper-case ASCII letter lowered when `lower`. */
|
|
547
|
-
const http1Text = (buf: u8[], from: i32, to: i32, lower: boolean): string => {
|
|
547
|
+
export const http1Text = (buf: u8[], from: i32, to: i32, lower: boolean): string => {
|
|
548
548
|
const parts: string[] = []
|
|
549
549
|
for (let k: i32 = from; k >= 0 && k < to && k < toI32(buf.length); k += 1) {
|
|
550
550
|
let c: i32 = toI32(buf[k])
|
|
@@ -557,10 +557,10 @@ const http1Text = (buf: u8[], from: i32, to: i32, lower: boolean): string => {
|
|
|
557
557
|
}
|
|
558
558
|
|
|
559
559
|
/** Whether `c` is optional whitespace, a space or a tab (OWS, RFC 9110 §5.6.3). */
|
|
560
|
-
const http1IsOws = (c: i32): boolean => c === 32 || c === 9
|
|
560
|
+
export const http1IsOws = (c: i32): boolean => c === 32 || c === 9
|
|
561
561
|
|
|
562
562
|
/** Whether `text[a .. b)` is `word`, which is lowercase, in any ASCII case. */
|
|
563
|
-
const http1SameWord = (text: string, a: i32, b: i32, word: string): boolean => {
|
|
563
|
+
export const http1SameWord = (text: string, a: i32, b: i32, word: string): boolean => {
|
|
564
564
|
const n: i32 = toI32(word.length)
|
|
565
565
|
if (a < 0 || b - a !== n || b > toI32(text.length)) {
|
|
566
566
|
return false
|
|
@@ -989,7 +989,7 @@ const http1ContentLength = (bytes: u8[], from: i32, to: i32, limit: i32): i32 =>
|
|
|
989
989
|
}
|
|
990
990
|
|
|
991
991
|
/** The value of the hex digit `c`, or -1. */
|
|
992
|
-
const http1HexValue = (c: i32): i32 => {
|
|
992
|
+
export const http1HexValue = (c: i32): i32 => {
|
|
993
993
|
if (c >= 48 && c <= 57) {
|
|
994
994
|
return c - 48
|
|
995
995
|
}
|
|
@@ -1190,7 +1190,7 @@ const http1BytesOf = (text: string): u8[] => {
|
|
|
1190
1190
|
}
|
|
1191
1191
|
|
|
1192
1192
|
/** Whether every byte of `text` is a tchar, and there is at least one. */
|
|
1193
|
-
const http1IsToken = (text: string): boolean => {
|
|
1193
|
+
export const http1IsToken = (text: string): boolean => {
|
|
1194
1194
|
const n: i32 = toI32(text.length)
|
|
1195
1195
|
if (n === 0) {
|
|
1196
1196
|
return false
|
|
@@ -1204,7 +1204,7 @@ const http1IsToken = (text: string): boolean => {
|
|
|
1204
1204
|
}
|
|
1205
1205
|
|
|
1206
1206
|
/** Whether every byte of `text` may stand in a field value or a reason phrase. */
|
|
1207
|
-
const http1IsFieldText = (text: string): boolean => {
|
|
1207
|
+
export const http1IsFieldText = (text: string): boolean => {
|
|
1208
1208
|
const n: i32 = toI32(text.length)
|
|
1209
1209
|
for (let k: i32 = 0; k < n; k += 1) {
|
|
1210
1210
|
if (!http1IsValueByte(toI32(text.charCodeAt(k)))) {
|
|
@@ -1395,7 +1395,7 @@ const http1DecimalDigits = (n: i32): i32 => {
|
|
|
1395
1395
|
}
|
|
1396
1396
|
|
|
1397
1397
|
/** Writes `text` at `out[at ..)`, which the caller has made sure holds it, and answers where it ends. */
|
|
1398
|
-
const http1PutText = (out: u8[], at: i32, text: string): i32 => {
|
|
1398
|
+
export const http1PutText = (out: u8[], at: i32, text: string): i32 => {
|
|
1399
1399
|
const n: i32 = toI32(text.length)
|
|
1400
1400
|
for (let k: i32 = 0; k < n && at + k < toI32(out.length); k += 1) {
|
|
1401
1401
|
if (at + k >= 0) {
|
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,29 @@
|
|
|
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.
|
|
72
85
|
*
|
|
73
86
|
* **Authentication.** SCRAM-SHA-256 (RFC 5802, RFC 7677) or none (`trust`).
|
|
74
87
|
* The password arrives as a `Secret<u8[]>`; the connection keeps a copy in a
|
|
@@ -100,8 +113,9 @@
|
|
|
100
113
|
* that needs a socket, `connect(slot, nonce)` starts its connection, and the
|
|
101
114
|
* program moves its bytes as above through `connection(slot)`. A request is a
|
|
102
115
|
* ticket from `request()`; `dispatch()` gives the oldest to the ready
|
|
103
|
-
* connection with the fewest groups in flight,
|
|
104
|
-
*
|
|
116
|
+
* connection with the fewest groups in flight (or, with `PgConfig.pack`, to the
|
|
117
|
+
* first ready one with room, so a burst fills few connections in fuller
|
|
118
|
+
* writes), the program builds its group and ends it with `send()`, and `next(slot)` reads that connection's events with
|
|
105
119
|
* `ticket` naming the request. Statements given to `register` are prepared on
|
|
106
120
|
* each connection ahead of its first request. A connection that fails reports
|
|
107
121
|
* `PG_LOST` for each request it took, then `PG_FAILED`; the program closes the
|
|
@@ -127,7 +141,7 @@
|
|
|
127
141
|
* Written from PostgreSQL's "Frontend/Backend Protocol" chapter (version 18)
|
|
128
142
|
* and RFC 5802 and RFC 7677; not ported from another implementation.
|
|
129
143
|
*/
|
|
130
|
-
import { copyBytes, getI32BE, getU16BE, setI32BE, setU16BE } from "nish/bytes"
|
|
144
|
+
import { copyBytes, getF64BE, getI32BE, getI64BE, getU16BE, setI32BE, setU16BE } from "nish/bytes"
|
|
131
145
|
import { base64StdPadDecode, base64StdPadEncode } from "nish/crypto/base64url"
|
|
132
146
|
import { timingSafeEqual } from "nish/crypto/ct"
|
|
133
147
|
import { hmacSha256, pbkdf2HmacSha256 } from "nish/crypto/hmac"
|
|
@@ -218,6 +232,11 @@ const PG_NONCE_BYTES: i32 = 18
|
|
|
218
232
|
// take: kept free under `maxOutput` by every other builder call, so that
|
|
219
233
|
// `bindEnd()` and `sync()` are never refused for room.
|
|
220
234
|
const PG_TAIL: i32 = 17
|
|
235
|
+
// What a binary bind's end takes beyond `PG_TAIL`: its one result format code,
|
|
236
|
+
// kept free from `bindStartBinary` on.
|
|
237
|
+
const PG_BINARY_TAIL: i32 = 2
|
|
238
|
+
// The result format code for binary.
|
|
239
|
+
const PG_FORMAT_BINARY: i32 = 1
|
|
221
240
|
// The largest `maxMessage` and `maxOutput`. The input buffer doubles up to one
|
|
222
241
|
// past them; the output buffer doubles only while twice its size fits in an i32.
|
|
223
242
|
const PG_MAX_CAP: i32 = 1073741823
|
|
@@ -238,6 +257,15 @@ export class PgConfig {
|
|
|
238
257
|
maxMessage: i32 = 1048576
|
|
239
258
|
/** The most groups waiting for their answers at once. */
|
|
240
259
|
maxInFlight: i32 = 16
|
|
260
|
+
/**
|
|
261
|
+
* Read by `PgPool` only: whether `dispatch()` packs requests onto the
|
|
262
|
+
* first ready connection with room, filling it to `maxInFlight` before the
|
|
263
|
+
* next, rather than spreading them over every connection, fewest in flight
|
|
264
|
+
* first. Fewer connections in use send fuller writes, fewer packets, which
|
|
265
|
+
* pays on a lossy link to the server; a connection is still wanted only once
|
|
266
|
+
* every ready one is full.
|
|
267
|
+
*/
|
|
268
|
+
pack: boolean = false
|
|
241
269
|
/** The input buffer's first size, in bytes; it grows to a message's length, up to `maxMessage` + 1. */
|
|
242
270
|
inputSize: i32 = 8192
|
|
243
271
|
/** The widest row; past it, `PG_ERR_TOO_BIG`. PostgreSQL's own limit is 1664. */
|
|
@@ -677,6 +705,159 @@ export class PgText {
|
|
|
677
705
|
}
|
|
678
706
|
}
|
|
679
707
|
|
|
708
|
+
// The days from 0000-03-01, where the civil calendar's year starts for the
|
|
709
|
+
// conversion below, to 2000-01-01, where PostgreSQL's binary times count from.
|
|
710
|
+
const PG_EPOCH_DAYS: i64 = 730425
|
|
711
|
+
// The first and last days `PgBinary.iso` writes, counted from 2000-01-01:
|
|
712
|
+
// 0001-01-01 and 9999-12-31, the years four digits spell.
|
|
713
|
+
const PG_FIRST_DAY: i64 = -730119
|
|
714
|
+
const PG_LAST_DAY: i64 = 2921939
|
|
715
|
+
|
|
716
|
+
/** `value`'s last `width` decimal digits into `out[at, at + width)`. */
|
|
717
|
+
const pgDigits = (out: u8[], at: i32, value: i32, width: i32): void => {
|
|
718
|
+
let v: i32 = value
|
|
719
|
+
for (let i: i32 = width - 1; i >= 0; i--) {
|
|
720
|
+
out[at + i] = toU8((v % 10) + 48)
|
|
721
|
+
v = v / 10
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Decoders for PostgreSQL's binary format, in place, for the rows of a bind
|
|
727
|
+
* that `bindStartBinary` opened; a bad input sets `failed`, which stays set.
|
|
728
|
+
* A `text` or `varchar` column's bytes are the same in either format, so
|
|
729
|
+
* `columnText`, `colStart` and `colLength` read it as they read a text one.
|
|
730
|
+
*/
|
|
731
|
+
export class PgBinary {
|
|
732
|
+
/** Whether a decode since the last `reset()` was given something it could not read. */
|
|
733
|
+
failed: boolean = false
|
|
734
|
+
|
|
735
|
+
/** Clears `failed`. */
|
|
736
|
+
reset(): void {
|
|
737
|
+
this.failed = false
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Whether `[start, start + length)` is inside `b`; sets `failed` when not.
|
|
741
|
+
private inside(b: u8[], start: i32, length: i32): boolean {
|
|
742
|
+
if (start < 0 || length < 0 || start > toI32(b.length) - length) {
|
|
743
|
+
this.failed = true
|
|
744
|
+
return false
|
|
745
|
+
}
|
|
746
|
+
return true
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* An `int2`, `int4` or `int8` column, told apart by its length (2, 4 or 8
|
|
751
|
+
* bytes, big-endian two's complement); 0 and `failed` for any other length,
|
|
752
|
+
* NULL's -1 among them. A `timestamptz` or `timestamp` reads as an `int8`:
|
|
753
|
+
* microseconds since 2000-01-01 00:00:00 UTC, which `iso` writes out.
|
|
754
|
+
*/
|
|
755
|
+
int(b: u8[], start: i32, length: i32): i64 {
|
|
756
|
+
if ((length !== 2 && length !== 4 && length !== 8) || !this.inside(b, start, length)) {
|
|
757
|
+
this.failed = true
|
|
758
|
+
return 0
|
|
759
|
+
}
|
|
760
|
+
if (length === 8) {
|
|
761
|
+
return getI64BE(b, start)
|
|
762
|
+
}
|
|
763
|
+
if (length === 4) {
|
|
764
|
+
return toI64(getI32BE(b, start))
|
|
765
|
+
}
|
|
766
|
+
const v: i32 = toI32(getU16BE(b, start))
|
|
767
|
+
return toI64(v >= 32768 ? v - 65536 : v)
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/** A `bool` column: one byte, 1 or 0; false and `failed` otherwise. */
|
|
771
|
+
bool(b: u8[], start: i32, length: i32): boolean {
|
|
772
|
+
if (length === 1 && this.inside(b, start, length)) {
|
|
773
|
+
const c: i32 = toI32(b[start])
|
|
774
|
+
if (c === 1) {
|
|
775
|
+
return true
|
|
776
|
+
}
|
|
777
|
+
if (c === 0) {
|
|
778
|
+
return false
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
this.failed = true
|
|
782
|
+
return false
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/** A `float8` column: eight bytes, IEEE 754 big-endian; 0 and `failed` otherwise. */
|
|
786
|
+
float(b: u8[], start: i32, length: i32): f64 {
|
|
787
|
+
if (length !== 8 || !this.inside(b, start, length)) {
|
|
788
|
+
this.failed = true
|
|
789
|
+
return 0
|
|
790
|
+
}
|
|
791
|
+
return getF64BE(b, start)
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* A `timestamptz` or `timestamp` column, written to `out[at, at + 24)` as
|
|
796
|
+
* `iso` writes it. Answers 24, or -1 and `failed` for a column that is not
|
|
797
|
+
* eight bytes or a time `iso` refuses.
|
|
798
|
+
*/
|
|
799
|
+
timestamp(b: u8[], start: i32, length: i32, out: u8[], at: i32): i32 {
|
|
800
|
+
if (length !== 8 || !this.inside(b, start, length)) {
|
|
801
|
+
this.failed = true
|
|
802
|
+
return -1
|
|
803
|
+
}
|
|
804
|
+
return this.iso(getI64BE(b, start), out, at)
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* `micros`, microseconds since 2000-01-01 00:00:00 UTC, written to
|
|
809
|
+
* `out[at, at + 24)` as `2024-01-02T03:04:05.123Z`: the microseconds cut,
|
|
810
|
+
* not rounded, to milliseconds, as `PgText.timestamp` cuts the same time.
|
|
811
|
+
* Answers 24, or -1 and `failed` with `out` untouched for a year outside
|
|
812
|
+
* 0001–9999 (`infinity` and `-infinity` among them) or no room in `out`.
|
|
813
|
+
*/
|
|
814
|
+
iso(micros: i64, out: u8[], at: i32): i32 {
|
|
815
|
+
if (at < 0 || at > toI32(out.length) - 24) {
|
|
816
|
+
this.failed = true
|
|
817
|
+
return -1
|
|
818
|
+
}
|
|
819
|
+
// Floored, so that a time before 2000 keeps its day and is cut toward the past.
|
|
820
|
+
const dayMicros: i64 = toI64(86400000) * 1000
|
|
821
|
+
let days: i64 = micros / dayMicros
|
|
822
|
+
let rest: i64 = micros % dayMicros
|
|
823
|
+
if (rest < 0) {
|
|
824
|
+
days -= 1
|
|
825
|
+
rest += dayMicros
|
|
826
|
+
}
|
|
827
|
+
if (days < PG_FIRST_DAY || days > PG_LAST_DAY) {
|
|
828
|
+
this.failed = true
|
|
829
|
+
return -1
|
|
830
|
+
}
|
|
831
|
+
// The civil date of a day count: eras of 400 years (146097 days), each
|
|
832
|
+
// year starting on 1 March so that a leap day is its last.
|
|
833
|
+
const z: i64 = days + PG_EPOCH_DAYS
|
|
834
|
+
const era: i64 = z / 146097
|
|
835
|
+
const doe: i64 = z - era * 146097
|
|
836
|
+
const yoe: i64 = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365
|
|
837
|
+
const doy: i64 = doe - (365 * yoe + yoe / 4 - yoe / 100)
|
|
838
|
+
const mp: i64 = (5 * doy + 2) / 153
|
|
839
|
+
const day: i64 = doy - (153 * mp + 2) / 5 + 1
|
|
840
|
+
const month: i64 = mp < 10 ? mp + 3 : mp - 9
|
|
841
|
+
const year: i64 = yoe + era * 400 + (month <= 2 ? 1 : 0)
|
|
842
|
+
const millis: i32 = toI32(rest / 1000)
|
|
843
|
+
pgDigits(out, at, toI32(year), 4)
|
|
844
|
+
out[at + 4] = toU8(45)
|
|
845
|
+
pgDigits(out, at + 5, toI32(month), 2)
|
|
846
|
+
out[at + 7] = toU8(45)
|
|
847
|
+
pgDigits(out, at + 8, toI32(day), 2)
|
|
848
|
+
out[at + 10] = toU8(84)
|
|
849
|
+
pgDigits(out, at + 11, millis / 3600000, 2)
|
|
850
|
+
out[at + 13] = toU8(58)
|
|
851
|
+
pgDigits(out, at + 14, (millis / 60000) % 60, 2)
|
|
852
|
+
out[at + 16] = toU8(58)
|
|
853
|
+
pgDigits(out, at + 17, (millis / 1000) % 60, 2)
|
|
854
|
+
out[at + 19] = toU8(46)
|
|
855
|
+
pgDigits(out, at + 20, millis % 1000, 3)
|
|
856
|
+
out[at + 23] = toU8(90)
|
|
857
|
+
return 24
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
680
861
|
/**
|
|
681
862
|
* A walk over a one-dimensional `text[]` column in place: `start` the
|
|
682
863
|
* column, then `next()` until `PG_ARRAY_END` or `PG_ARRAY_BAD`. Each element
|
|
@@ -842,10 +1023,11 @@ export class PgConnection {
|
|
|
842
1023
|
lastNotice: PgError | null = null
|
|
843
1024
|
/** The decoders `int`, `bool` and `isNull` use; `text.failed` says whether one was given a column it could not read. */
|
|
844
1025
|
readonly text: PgText
|
|
1026
|
+
/** The decoders `binaryInt`, `binaryBool` and `binaryFloat` use, for a bind `bindStartBinary` opened; `binary.failed` as `text.failed`. */
|
|
1027
|
+
readonly binary: PgBinary
|
|
845
1028
|
|
|
846
1029
|
private readonly config: PgConfig
|
|
847
1030
|
private readonly password: PgPassword
|
|
848
|
-
private state: i32 = 0
|
|
849
1031
|
private input: u8[]
|
|
850
1032
|
private inStart: i32 = 0
|
|
851
1033
|
private inEnd: i32 = 0
|
|
@@ -863,12 +1045,15 @@ export class PgConnection {
|
|
|
863
1045
|
groupFailed: boolean = false
|
|
864
1046
|
// Whether the open bind opened its group, which `bindCancel` closes again.
|
|
865
1047
|
private bindOpened: boolean = false
|
|
1048
|
+
// Whether the open bind asks for its result columns in binary.
|
|
1049
|
+
private bindBinary: boolean = false
|
|
866
1050
|
private bindAt: i32 = -1
|
|
867
1051
|
private paramCountAt: i32 = 0
|
|
868
1052
|
private paramCount: i32 = 0
|
|
869
1053
|
// The open array parameter's length word, -1 when none is open, and its elements so far.
|
|
870
1054
|
private arrayAt: i32 = -1
|
|
871
1055
|
private arrayCount: i32 = 0
|
|
1056
|
+
private state: i32 = 0
|
|
872
1057
|
private clientNonce: string = ""
|
|
873
1058
|
private serverSignature: u8[]
|
|
874
1059
|
|
|
@@ -885,6 +1070,7 @@ export class PgConnection {
|
|
|
885
1070
|
pgCheckConfig(config)
|
|
886
1071
|
this.config = config
|
|
887
1072
|
this.text = new PgText()
|
|
1073
|
+
this.binary = new PgBinary()
|
|
888
1074
|
this.password = new PgPassword(password)
|
|
889
1075
|
const inputSize: i32 = config.inputSize < 64 ? 64 : config.inputSize
|
|
890
1076
|
this.input = new Array<u8>(inputSize)
|
|
@@ -1574,7 +1760,8 @@ export class PgConnection {
|
|
|
1574
1760
|
*/
|
|
1575
1761
|
private room(size: i32): boolean {
|
|
1576
1762
|
const queued: i32 = this.outEnd - this.outStart
|
|
1577
|
-
|
|
1763
|
+
const tail: i32 = this.bindAt >= 0 && this.bindBinary ? PG_TAIL + PG_BINARY_TAIL : PG_TAIL
|
|
1764
|
+
if (size < 0 || size > this.config.maxOutput - tail - queued) {
|
|
1578
1765
|
return false
|
|
1579
1766
|
}
|
|
1580
1767
|
if (this.outStart > 0 && this.outEnd + size > toI32(this.out.length)) {
|
|
@@ -1740,11 +1927,31 @@ export class PgConnection {
|
|
|
1740
1927
|
* order, then `bindEnd()`. False as `prepare` is.
|
|
1741
1928
|
*/
|
|
1742
1929
|
bindStart(name: string): boolean {
|
|
1930
|
+
return this.openBind(name, false)
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
/**
|
|
1934
|
+
* `bindStart`, asking for every result column of the execution in binary
|
|
1935
|
+
* format rather than text: fixed-size integers, booleans, floats and
|
|
1936
|
+
* times, read in place by `binaryInt`, `binaryBool`, `binaryFloat` and
|
|
1937
|
+
* `binary.timestamp`. A `text` column's bytes are the same in either
|
|
1938
|
+
* format, so `columnText` and the column windows read it as before; a
|
|
1939
|
+
* `text[]` or `numeric` column comes in PostgreSQL's binary layout, which
|
|
1940
|
+
* no decoder here reads. Its `bindEnd()` writes two bytes more, kept free
|
|
1941
|
+
* under `maxOutput` from here on. False as `prepare` is.
|
|
1942
|
+
*/
|
|
1943
|
+
bindStartBinary(name: string): boolean {
|
|
1944
|
+
return this.openBind(name, true)
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
private openBind(name: string, binary: boolean): boolean {
|
|
1743
1948
|
const opens: boolean = !this.groupOpen
|
|
1744
|
-
|
|
1949
|
+
const extra: i32 = binary ? PG_BINARY_TAIL : 0
|
|
1950
|
+
if (pgHasNul(name) || toI32(name.length) > PG_MAX_CAP || !this.canOpen(11 + extra + toI32(name.length))) {
|
|
1745
1951
|
return false
|
|
1746
1952
|
}
|
|
1747
1953
|
this.bindOpened = opens
|
|
1954
|
+
this.bindBinary = binary
|
|
1748
1955
|
this.bindAt = this.startMessage(PG_FE_BIND)
|
|
1749
1956
|
this.putByte(PG_ZERO)
|
|
1750
1957
|
this.putCString(name)
|
|
@@ -2058,9 +2265,10 @@ export class PgConnection {
|
|
|
2058
2265
|
}
|
|
2059
2266
|
|
|
2060
2267
|
/**
|
|
2061
|
-
* Ends the Bind (every result column in text
|
|
2062
|
-
*
|
|
2063
|
-
* room, which the calls
|
|
2268
|
+
* Ends the Bind (every result column in text, or in binary after
|
|
2269
|
+
* `bindStartBinary`) and queues its Execute, for every row. False, and
|
|
2270
|
+
* nothing written, when no bind is open; never for room, which the calls
|
|
2271
|
+
* before it kept.
|
|
2064
2272
|
*/
|
|
2065
2273
|
bindEnd(): boolean {
|
|
2066
2274
|
if (this.bindAt < 0) {
|
|
@@ -2069,10 +2277,17 @@ export class PgConnection {
|
|
|
2069
2277
|
if (this.arrayAt >= 0) {
|
|
2070
2278
|
panic("nish/net/postgres: bindEnd() while an array is open")
|
|
2071
2279
|
}
|
|
2072
|
-
this.
|
|
2280
|
+
const binary: boolean = this.bindBinary
|
|
2281
|
+
this.tailRoom(binary ? PG_TAIL + PG_BINARY_TAIL : PG_TAIL)
|
|
2073
2282
|
const at: i32 = this.bindAt
|
|
2074
2283
|
setU16BE(this.out, this.paramCountAt, toU16(this.paramCount))
|
|
2075
|
-
|
|
2284
|
+
if (binary) {
|
|
2285
|
+
// One format code, which the server applies to every column.
|
|
2286
|
+
this.putI16(toI32(1))
|
|
2287
|
+
this.putI16(PG_FORMAT_BINARY)
|
|
2288
|
+
} else {
|
|
2289
|
+
this.putI16(PG_ZERO)
|
|
2290
|
+
}
|
|
2076
2291
|
this.endMessage(at)
|
|
2077
2292
|
this.bindAt = -1
|
|
2078
2293
|
const execute: i32 = this.startMessage(PG_FE_EXECUTE)
|
|
@@ -2170,6 +2385,21 @@ export class PgConnection {
|
|
|
2170
2385
|
return this.text.bool(this.input, this.colStarts[c], this.colLengths[c])
|
|
2171
2386
|
}
|
|
2172
2387
|
|
|
2388
|
+
/** Binary column `c`, an `int2`, `int4`, `int8` or a time's microseconds, as an integer; NULL or another length sets `binary.failed`. */
|
|
2389
|
+
binaryInt(c: i32): i64 {
|
|
2390
|
+
return this.binary.int(this.input, this.colStarts[c], this.colLengths[c])
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
/** Binary column `c` as a boolean; NULL or a bad value sets `binary.failed`. */
|
|
2394
|
+
binaryBool(c: i32): boolean {
|
|
2395
|
+
return this.binary.bool(this.input, this.colStarts[c], this.colLengths[c])
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
/** Binary column `c`, a `float8`, as an f64; NULL or another length sets `binary.failed`. */
|
|
2399
|
+
binaryFloat(c: i32): f64 {
|
|
2400
|
+
return this.binary.float(this.input, this.colStarts[c], this.colLengths[c])
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2173
2403
|
/** Whether the tag of the execution just completed starts with the word `word` (`INSERT`, `SELECT`, ...). */
|
|
2174
2404
|
tagIs(word: string): boolean {
|
|
2175
2405
|
const n: i32 = toI32(word.length)
|
|
@@ -2266,7 +2496,8 @@ class PgPoolSlot {
|
|
|
2266
2496
|
*
|
|
2267
2497
|
* A request is a ticket from `request()`, queued in FIFO order. `dispatch()`
|
|
2268
2498
|
* hands the queue's head to the ready connection with the fewest groups in
|
|
2269
|
-
* flight,
|
|
2499
|
+
* flight, or under `PgConfig.pack` the lowest-numbered ready one with room,
|
|
2500
|
+
* never past `maxInFlight` on one; the program builds the request's
|
|
2270
2501
|
* group on `connection(slot)` and ends it with `send()`, not `sync()`.
|
|
2271
2502
|
* `next(slot)` then reads that connection's events, `ticket` naming the
|
|
2272
2503
|
* request each is about. A connection that fails reports `PG_LOST` for each
|
|
@@ -2367,7 +2598,8 @@ export class PgPool {
|
|
|
2367
2598
|
}
|
|
2368
2599
|
|
|
2369
2600
|
/**
|
|
2370
|
-
* Hands the oldest waiting request to a ready connection with room
|
|
2601
|
+
* Hands the oldest waiting request to a ready connection with room (the
|
|
2602
|
+
* one with the fewest in flight, or under `PgConfig.pack` the first), and
|
|
2371
2603
|
* answers its slot, `ticket` set to the request: build its group on
|
|
2372
2604
|
* `connection(slot)`, then `send()`. `PG_POOL_NONE` when nothing waits, a
|
|
2373
2605
|
* request is still being built, or no ready connection has room; then a
|
|
@@ -2381,6 +2613,7 @@ export class PgPool {
|
|
|
2381
2613
|
return PG_POOL_NONE
|
|
2382
2614
|
}
|
|
2383
2615
|
const cap: i32 = this.config.maxInFlight
|
|
2616
|
+
const pack: boolean = this.config.pack
|
|
2384
2617
|
let best: i32 = -1
|
|
2385
2618
|
let load: i32 = 0
|
|
2386
2619
|
let starting: i32 = 0
|
|
@@ -2389,7 +2622,7 @@ export class PgPool {
|
|
|
2389
2622
|
const slot: PgPoolSlot = this.slots[s]
|
|
2390
2623
|
const conn: PgConnection | null = slot.conn
|
|
2391
2624
|
if (slot.state === PG_SLOT_READY && conn !== null) {
|
|
2392
|
-
if (conn.inFlight < cap && (best < 0 || conn.inFlight < load)) {
|
|
2625
|
+
if (conn.inFlight < cap && (best < 0 || (!pack && conn.inFlight < load))) {
|
|
2393
2626
|
best = s
|
|
2394
2627
|
load = conn.inFlight
|
|
2395
2628
|
}
|