postgrejs 3.12.2 → 3.14.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.
@@ -6,9 +6,29 @@ import * as os from 'os';
6
6
  * length-prefixed string where a `null`/`undefined` value is written as a
7
7
  * length of -1 with no bytes following.
8
8
  */
9
+ /** The ceiling on `maxSizeFor`, and its answer when there is no machine to measure. */
10
+ const HARD_MAX = 1024 * 1024 * 1024 * 2; // 2 GB
11
+ /**
12
+ * The most a buffer may ever hold, given how much memory the machine has.
13
+ *
14
+ * @param totalMemory Bytes of memory, or 0 when that cannot be known.
15
+ * @returns Half of it, capped - or the cap alone when it is unknown.
16
+ */
17
+ /* `os.totalmem()` answers 0 where there is no machine to ask about:
18
+ measured under Cloudflare's workerd, where `node:os` resolves and
19
+ returns zeros rather than throwing. Taking the minimum of that was a
20
+ maximum size of zero, which worked only because flexy-buffer reads a
21
+ falsy `maxLength` as "use the default" - an accident, and one that
22
+ would have become a buffer refusing every write the day that default
23
+ changed. */
24
+ export function maxSizeFor(totalMemory) {
25
+ return totalMemory > 0
26
+ ? Math.min(Math.floor(totalMemory / 2), HARD_MAX)
27
+ : HARD_MAX;
28
+ }
9
29
  export class SmartBuffer extends FlexyBuffer {
10
30
  static DEFAULT_PAGE_SIZE = 512;
11
- static DEFAULT_MAX_SIZE = Math.min(Math.floor(os.totalmem() / 2), 1024 * 1024 * 1024 * 2);
31
+ static DEFAULT_MAX_SIZE = maxSizeFor(typeof os.totalmem === 'function' ? os.totalmem() : 0);
12
32
  constructor(cfg) {
13
33
  super({
14
34
  pageSize: cfg?.pageSize || SmartBuffer.DEFAULT_PAGE_SIZE,
@@ -0,0 +1,87 @@
1
+ import { SafeEventEmitter } from '../safe-event-emitter.js';
2
+ /** What `cloudflare:sockets` hands back, as much of it as is used here. */
3
+ interface CfSocket {
4
+ readable: ReadableStream<Uint8Array>;
5
+ writable: WritableStream<Uint8Array>;
6
+ closed: Promise<void>;
7
+ close(): Promise<void>;
8
+ startTls(options: {
9
+ expectedServerHostname?: string;
10
+ }): CfSocket;
11
+ }
12
+ /** The one function `cloudflare:sockets` is reached for. */
13
+ export type CfConnect = (address: string, options?: {
14
+ secureTransport?: 'off' | 'on' | 'starttls';
15
+ }) => CfSocket;
16
+ /**
17
+ * A `net.Socket`-shaped view of a Cloudflare socket, for the part of it
18
+ * this client uses: `connect`, `write`, `destroy`, and `data`/`error`/
19
+ * `close` events, plus `startTls()`, which is the one thing Node's
20
+ * `tls.connect({ socket })` cannot do on that runtime.
21
+ *
22
+ * Reads are a loop over the stream rather than a `'data'` event the
23
+ * runtime emits, so the loop is what turns one into the other.
24
+ */
25
+ export declare class WorkerdSocket extends SafeEventEmitter {
26
+ private readonly _wantsTls;
27
+ private readonly _connect;
28
+ private _cf?;
29
+ private _writer?;
30
+ private _reader?;
31
+ private _upgrading;
32
+ private _destroyed;
33
+ private _paused?;
34
+ private _resume?;
35
+ constructor(_wantsTls: boolean, _connect?: () => Promise<CfConnect>);
36
+ /** Whether a TLS upgrade has been asked for and completed. */
37
+ get secure(): boolean;
38
+ private _secure;
39
+ /** Whether `destroy()` has been called. */
40
+ get destroyed(): boolean;
41
+ /** Whether writes are still accepted. */
42
+ get writable(): boolean;
43
+ cork(): void;
44
+ uncork(): void;
45
+ setNoDelay(): this;
46
+ setKeepAlive(): this;
47
+ setTimeout(): this;
48
+ /** Stops reading, so the sender is made to wait. */
49
+ pause(): this;
50
+ /** Starts reading again. */
51
+ resume(): this;
52
+ /** Closes the connection once anything already written has gone. */
53
+ end(): this;
54
+ /**
55
+ * Opens the connection and emits `connect` once it is writable.
56
+ *
57
+ * @param port The port to reach.
58
+ * @param host The host to reach.
59
+ */
60
+ connect(port: number, host: string): void;
61
+ /**
62
+ * Reads one chunk and hands it over, without starting the loop.
63
+ *
64
+ * @param cb Called with the bytes, or with nothing if the connection
65
+ * ended first.
66
+ */
67
+ readOnce(cb: (data?: Buffer) => void): void;
68
+ /**
69
+ * Upgrades the connection in place.
70
+ *
71
+ * @param servername The name to check the certificate against.
72
+ */
73
+ startTls(servername?: string): void;
74
+ /**
75
+ * Writes to the connection.
76
+ *
77
+ * @param data The bytes to send.
78
+ * @param cb Called once they are written, or with the error.
79
+ * @returns True - the runtime offers no "buffer is full" answer.
80
+ */
81
+ write(data: Uint8Array, cb?: (err?: Error | null) => void): boolean;
82
+ /** Closes the connection; `close` follows once the runtime agrees. */
83
+ destroy(): this;
84
+ protected _attach(cf: CfSocket, readLoop: boolean): void;
85
+ protected _read(reader: ReadableStreamDefaultReader<Uint8Array>): Promise<void>;
86
+ }
87
+ export {};
@@ -0,0 +1,245 @@
1
+ import { SafeEventEmitter } from '../safe-event-emitter.js';
2
+ /* Imported where it is called rather than at the top of the file: the
3
+ specifier does not resolve off workerd, and this module is reachable
4
+ from a Node build through `pg-socket.ts`. Nothing awaits it unless a
5
+ connection is actually being made on that runtime. */
6
+ const importCfConnect = async () => (await import(/* webpackIgnore: true */ 'cloudflare:sockets'))
7
+ .connect;
8
+ /**
9
+ * A `net.Socket`-shaped view of a Cloudflare socket, for the part of it
10
+ * this client uses: `connect`, `write`, `destroy`, and `data`/`error`/
11
+ * `close` events, plus `startTls()`, which is the one thing Node's
12
+ * `tls.connect({ socket })` cannot do on that runtime.
13
+ *
14
+ * Reads are a loop over the stream rather than a `'data'` event the
15
+ * runtime emits, so the loop is what turns one into the other.
16
+ */
17
+ /* Written here rather than taken from `pg-cloudflare`, which does the
18
+ same job for `pg`: the adapter is a hundred lines whose lifecycle this
19
+ client would otherwise not control, and only the upgrade is actually
20
+ needed - the plain path on workerd already works through `node:net`.
21
+
22
+ The setNoDelay/setKeepAlive/setTimeout trio answers nothing because
23
+ the runtime exposes no equivalent; they are here so the connect path
24
+ above can treat both socket kinds the same way. */
25
+ export class WorkerdSocket extends SafeEventEmitter {
26
+ _wantsTls;
27
+ _connect;
28
+ _cf;
29
+ _writer;
30
+ _reader;
31
+ /* True between startTls() and the old socket's `closed` settling: that
32
+ promise resolves because the socket was replaced, not because the
33
+ connection went away, and reporting it as a close would end the
34
+ session in the middle of its handshake. */
35
+ _upgrading = false;
36
+ _destroyed = false;
37
+ _paused;
38
+ _resume;
39
+ constructor(_wantsTls, _connect = importCfConnect) {
40
+ super();
41
+ this._wantsTls = _wantsTls;
42
+ this._connect = _connect;
43
+ }
44
+ /** Whether a TLS upgrade has been asked for and completed. */
45
+ get secure() {
46
+ return this._secure;
47
+ }
48
+ _secure = false;
49
+ /** Whether `destroy()` has been called. */
50
+ get destroyed() {
51
+ return this._destroyed;
52
+ }
53
+ /** Whether writes are still accepted. */
54
+ get writable() {
55
+ return !this._destroyed && !!this._writer;
56
+ }
57
+ /* `cork`/`uncork` gather several small writes into one syscall on
58
+ Node. There is no syscall to gather here - a write is a promise
59
+ against a stream - so the pair is accepted and does nothing, which
60
+ is what it means on this runtime. */
61
+ cork() {
62
+ /* nothing to gather */
63
+ }
64
+ uncork() {
65
+ /* nothing to gather */
66
+ }
67
+ setNoDelay() {
68
+ return this;
69
+ }
70
+ setKeepAlive() {
71
+ return this;
72
+ }
73
+ setTimeout() {
74
+ return this;
75
+ }
76
+ /** Stops reading, so the sender is made to wait. */
77
+ /* Real backpressure rather than a no-op: the read loop is what pulls
78
+ from the stream, so not pulling is the whole of it. `pause()` during
79
+ a COPY TO is the only caller, and a stream left unread is how the
80
+ runtime learns to stop. */
81
+ pause() {
82
+ if (!this._paused)
83
+ this._paused = new Promise(r => (this._resume = r));
84
+ return this;
85
+ }
86
+ /** Starts reading again. */
87
+ resume() {
88
+ const go = this._resume;
89
+ this._paused = undefined;
90
+ this._resume = undefined;
91
+ go?.();
92
+ return this;
93
+ }
94
+ /** Closes the connection once anything already written has gone. */
95
+ end() {
96
+ return this.destroy();
97
+ }
98
+ /**
99
+ * Opens the connection and emits `connect` once it is writable.
100
+ *
101
+ * @param port The port to reach.
102
+ * @param host The host to reach.
103
+ */
104
+ connect(port, host) {
105
+ void (async () => {
106
+ try {
107
+ const connect = await this._connect();
108
+ this._cf = connect(`${host}:${port}`, {
109
+ // `starttls` leaves the connection in the clear and allows a
110
+ // later upgrade, which is what the SSLRequest exchange needs.
111
+ secureTransport: this._wantsTls ? 'starttls' : 'off',
112
+ });
113
+ /* No read loop yet when a TLS upgrade is coming: `releaseLock()`
114
+ throws on a reader with an outstanding read, and a loop parked
115
+ in `reader.read()` is exactly that. The negotiation asks for
116
+ the one byte it expects through `readOnce()`, and the loop
117
+ starts on the upgraded socket. */
118
+ this._attach(this._cf, !this._wantsTls);
119
+ await this._writer.ready;
120
+ this.emit('connect');
121
+ }
122
+ catch (e) {
123
+ this.emit('error', e);
124
+ }
125
+ })();
126
+ }
127
+ /**
128
+ * Reads one chunk and hands it over, without starting the loop.
129
+ *
130
+ * @param cb Called with the bytes, or with nothing if the connection
131
+ * ended first.
132
+ */
133
+ /* What the SSLRequest exchange needs: a single answer, read while no
134
+ loop holds the reader, so the lock can be released for the upgrade
135
+ that follows. */
136
+ readOnce(cb) {
137
+ const reader = this._reader;
138
+ if (!reader)
139
+ return cb();
140
+ reader.read().then(({ done, value }) => cb(done || !value ? undefined : Buffer.from(value)), (e) => {
141
+ this.emit('error', e);
142
+ cb();
143
+ });
144
+ }
145
+ /**
146
+ * Upgrades the connection in place.
147
+ *
148
+ * @param servername The name to check the certificate against.
149
+ */
150
+ /* The locks have to go back before the socket is replaced, and the new
151
+ one needs its own reader and writer and its own read loop. */
152
+ startTls(servername) {
153
+ const cf = this._cf;
154
+ if (!cf) {
155
+ this.emit('error', new Error('Cannot upgrade a socket that is not connected'));
156
+ return;
157
+ }
158
+ try {
159
+ this._writer.releaseLock();
160
+ this._reader.releaseLock();
161
+ this._upgrading = true;
162
+ this._cf = cf.startTls(servername ? { expectedServerHostname: servername } : {});
163
+ this._attach(this._cf, true);
164
+ void this._writer.ready.then(() => {
165
+ this._secure = true;
166
+ this.emit('secureConnect');
167
+ }, (e) => this.emit('error', e));
168
+ }
169
+ catch (e) {
170
+ this.emit('error', e);
171
+ }
172
+ }
173
+ /**
174
+ * Writes to the connection.
175
+ *
176
+ * @param data The bytes to send.
177
+ * @param cb Called once they are written, or with the error.
178
+ * @returns True - the runtime offers no "buffer is full" answer.
179
+ */
180
+ write(data, cb) {
181
+ const writer = this._writer;
182
+ if (!writer) {
183
+ cb?.(new Error('Socket is not connected'));
184
+ return true;
185
+ }
186
+ writer.write(data).then(() => cb?.(), (e) => {
187
+ cb?.(e);
188
+ this.emit('error', e);
189
+ });
190
+ return true;
191
+ }
192
+ /** Closes the connection; `close` follows once the runtime agrees. */
193
+ destroy() {
194
+ if (this._destroyed)
195
+ return this;
196
+ this._destroyed = true;
197
+ const cf = this._cf;
198
+ this._cf = undefined;
199
+ cf?.close().catch(() => undefined);
200
+ return this;
201
+ }
202
+ _attach(cf, readLoop) {
203
+ this._writer = cf.writable.getWriter();
204
+ this._reader = cf.readable.getReader();
205
+ if (readLoop)
206
+ void this._read(this._reader);
207
+ cf.closed.then(() => {
208
+ if (this._upgrading) {
209
+ this._upgrading = false;
210
+ return;
211
+ }
212
+ this.emit('close');
213
+ }, (e) => {
214
+ if (this._upgrading) {
215
+ this._upgrading = false;
216
+ return;
217
+ }
218
+ this.emit('error', e);
219
+ });
220
+ }
221
+ async _read(reader) {
222
+ try {
223
+ for (;;) {
224
+ if (this._paused)
225
+ await this._paused;
226
+ const { done, value } = await reader.read();
227
+ if (done)
228
+ return;
229
+ /* Asked again on the way out: a pause() during a read already in
230
+ flight cannot un-await it, and delivering that chunk anyway
231
+ would be data arriving after the consumer said stop. */
232
+ if (this._paused)
233
+ await this._paused;
234
+ if (value?.length)
235
+ this.emit('data', Buffer.from(value));
236
+ }
237
+ }
238
+ catch (e) {
239
+ // A read that ends because the socket was replaced is the upgrade
240
+ // happening, not a failure to report.
241
+ if (!this._upgrading && !this._destroyed)
242
+ this.emit('error', e);
243
+ }
244
+ }
245
+ }
@@ -0,0 +1,15 @@
1
+ import type { Maybe, OID } from '../types.js';
2
+ /**
3
+ * The types to encode a Bind's parameters against, given what the caller
4
+ * declared and what the server resolved.
5
+ *
6
+ * Returns `declared` unchanged unless some parameter can be improved, so
7
+ * the common call allocates nothing.
8
+ *
9
+ * @param declared What went into Parse - `0`/undefined where this client
10
+ * declared nothing and left the type to the server.
11
+ * @param params The values about to be written.
12
+ * @param resolved What the server answered for this statement, in order.
13
+ * @returns The types to hand `getBindMessage`.
14
+ */
15
+ export declare function resolvedBindTypes(declared: Maybe<Maybe<OID>[]>, params: Maybe<Maybe<any>[]>, resolved: Maybe<OID[]>): Maybe<Maybe<OID>[]>;
@@ -0,0 +1,85 @@
1
+ import { DataTypeOIDs } from '../constants.js';
2
+ import { arrayLeaf } from './array-leaf.js';
3
+ /**
4
+ * The types a `Date` is allowed to be encoded into binary against, by
5
+ * whether the value is an array.
6
+ */
7
+ /* Deliberately short. `time` and `timetz` are left out because
8
+ TimeType.encodeBinary() multiplies the whole epoch by 1000 and
9
+ overflows for any ordinary Date - measured, every one of four test
10
+ instants answered 22008 "time out of range", where the text path
11
+ stores the wall clock correctly. Nothing else here may be reached
12
+ until it has been checked the same way. */
13
+ const DATE_SCALARS = new Set([
14
+ DataTypeOIDs.timestamptz,
15
+ DataTypeOIDs.timestamp,
16
+ DataTypeOIDs.date,
17
+ ]);
18
+ const DATE_ARRAYS = new Set([
19
+ DataTypeOIDs._timestamptz,
20
+ DataTypeOIDs._timestamp,
21
+ DataTypeOIDs._date,
22
+ ]);
23
+ /**
24
+ * The types to encode a Bind's parameters against, given what the caller
25
+ * declared and what the server resolved.
26
+ *
27
+ * Returns `declared` unchanged unless some parameter can be improved, so
28
+ * the common call allocates nothing.
29
+ *
30
+ * @param declared What went into Parse - `0`/undefined where this client
31
+ * declared nothing and left the type to the server.
32
+ * @param params The values about to be written.
33
+ * @param resolved What the server answered for this statement, in order.
34
+ * @returns The types to hand `getBindMessage`.
35
+ */
36
+ /* A Date goes out as text because nothing but the server knows whether
37
+ the column is an instant or a wall clock - see `isUnspecifiedParam()`.
38
+ Once the statement has a name, the server has already said which, and
39
+ saying it does not have to be repeated: a prepared statement's
40
+ parameter types are fixed at Parse, so a later Bind may send binary
41
+ against them with no re-Parse. Checked on the wire: Parse with no
42
+ declared types resolves `$1` to 1184, and a Bind of eight binary bytes
43
+ against that statement reads back correctly.
44
+
45
+ Worth it only because the text is wide and the binary is not. Measured
46
+ over a loopback connection, 20 000 elements:
47
+
48
+ timestamptz[] text 625 KB / 11.61ms binary 234 KB / 3.79ms
49
+
50
+ The same measurement is why this is not done for numbers: an int4[] of
51
+ ordinary values is 380 KB of text against 781 KB of binary, because
52
+ the binary array format carries a 4-byte length per element whatever
53
+ its magnitude - there, text is both smaller and twice as fast.
54
+
55
+ Binary and text store the same value, which is the thing that had to
56
+ be true before any of this: checked for timestamptz, timestamp and
57
+ date, over four instants spanning a DST boundary and including
58
+ milliseconds, and for the array forms of two of them. */
59
+ export function resolvedBindTypes(declared, params, resolved) {
60
+ if (!resolved || !params)
61
+ return declared;
62
+ const l = params.length;
63
+ let out;
64
+ let i;
65
+ for (i = 0; i < l; i++) {
66
+ // A type the caller named is an answer, not a guess - left alone.
67
+ if (declared?.[i])
68
+ continue;
69
+ const oid = resolved[i];
70
+ if (!oid)
71
+ continue;
72
+ const v = params[i];
73
+ if (!(arrayLeaf(v) instanceof Date))
74
+ continue;
75
+ // An array value needs an array type and a scalar needs a scalar
76
+ // one: getBindMessage() picks its encoder from the type alone and
77
+ // would hand an array to a scalar encoder.
78
+ if (!(Array.isArray(v) ? DATE_ARRAYS : DATE_SCALARS).has(oid))
79
+ continue;
80
+ if (!out)
81
+ out = declared ? declared.slice() : new Array(l).fill(0);
82
+ out[i] = oid;
83
+ }
84
+ return out || declared;
85
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Whether this client is running inside a Cloudflare Worker.
3
+ *
4
+ * @returns True on workerd, false on Node.js, Bun and anything else.
5
+ */
6
+ export declare function isWorkerd(): boolean;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Whether this client is running inside a Cloudflare Worker.
3
+ *
4
+ * @returns True on workerd, false on Node.js, Bun and anything else.
5
+ */
6
+ /* The documented tell, and the one `pg` uses: since the
7
+ `global_navigator` compatibility flag went on, workerd defines
8
+ `navigator.userAgent` and sets it to exactly this. Node and Bun define
9
+ `navigator` too, with a userAgent of their own, so the test is for the
10
+ whole string rather than a substring of it.
11
+
12
+ Asked on each call rather than read once at load: it is reached once
13
+ per connection, where the cost is nothing, and a value frozen at
14
+ module load cannot be tested from both sides. */
15
+ export function isWorkerd() {
16
+ return (typeof navigator === 'object' &&
17
+ navigator !== null &&
18
+ navigator.userAgent === 'Cloudflare-Workers');
19
+ }
@@ -1,5 +1,6 @@
1
1
  import { arrayCalculateDim } from './array-calculatedim.js';
2
2
  import { arrayLeaf } from './array-leaf.js';
3
+ import { unspecifiedText } from './unspecified-value.js';
3
4
  /**
4
5
  * What a number's own text looks like: digits, one optional dot, an
5
6
  * optional exponent, an optional leading minus. An element that matches
@@ -66,7 +67,11 @@ export function stringifyArrayLiteral(value, options, encode) {
66
67
  const isNumber = typeof x === 'number' || typeof x === 'bigint';
67
68
  if (encode)
68
69
  x = encode(x, options || {});
69
- const s = '' + x;
70
+ // With an encoder the element's text is the type's to produce;
71
+ // without one this is an undeclared array, where a value that
72
+ // knows how to write itself does, and a plain object goes out as
73
+ // JSON rather than as `[object Object]`.
74
+ const s = encode ? '' + x : unspecifiedText(x);
70
75
  out += isNumber && BARE_NUMBER.test(s) ? s : escapeArrayItem(s);
71
76
  }
72
77
  return out + '}';
@@ -241,7 +246,9 @@ export function writeArrayLiteral(io, value, options, encode) {
241
246
  // of its spelling - the same rule the string path states: a
242
247
  // string's own text is quoted whatever it looks like.
243
248
  const isNumber = typeof x === 'number' || typeof x === 'bigint';
244
- s = '' + x;
249
+ // This lane is only entered with no element encoder, so the same
250
+ // rules the string path applies without one apply here.
251
+ s = unspecifiedText(x);
245
252
  const bare = isNumber && BARE_NUMBER.test(s);
246
253
  if (!bare)
247
254
  s =
@@ -30,6 +30,9 @@
30
30
  * bigint[] = integer[]`, and `numeric[] = double precision[]` the
31
31
  * same. The scalars are left declared because they do have those
32
32
  * operators - `1::int8 = $1` and `1.5::numeric = $1` both resolve.
33
+ * That is true of the operators *inside* the numeric family and not
34
+ * of the casts out of it, which is what `needsNumericParam()` below
35
+ * exists for.
33
36
  *
34
37
  * A scalar number, a boolean, a Buffer, and an array of anything but
35
38
  * numbers keep their declared types: an array of booleans, of Buffers
@@ -64,3 +67,14 @@
64
67
  * answer rather than a guess.
65
68
  */
66
69
  export declare function isUnspecifiedParam(v: any): boolean;
70
+ /**
71
+ * Whether a number must be declared `numeric` rather than the `float8`
72
+ * its value suggests.
73
+ *
74
+ * True for a finite non-integer, false for everything else - an integer,
75
+ * a bigint, a non-finite number, and anything that is not a number.
76
+ *
77
+ * @param v The value a parameter is about to be declared for.
78
+ * @returns Whether to declare `numeric` instead of asking `determine()`.
79
+ */
80
+ export declare function needsNumericParam(v: any): boolean;
@@ -31,6 +31,9 @@ import { arrayLeaf } from './array-leaf.js';
31
31
  * bigint[] = integer[]`, and `numeric[] = double precision[]` the
32
32
  * same. The scalars are left declared because they do have those
33
33
  * operators - `1::int8 = $1` and `1.5::numeric = $1` both resolve.
34
+ * That is true of the operators *inside* the numeric family and not
35
+ * of the casts out of it, which is what `needsNumericParam()` below
36
+ * exists for.
34
37
  *
35
38
  * A scalar number, a boolean, a Buffer, and an array of anything but
36
39
  * numbers keep their declared types: an array of booleans, of Buffers
@@ -70,3 +73,40 @@ export function isUnspecifiedParam(v) {
70
73
  return true;
71
74
  return Array.isArray(v) && (typeof x === 'number' || typeof x === 'bigint');
72
75
  }
76
+ /**
77
+ * Whether a number must be declared `numeric` rather than the `float8`
78
+ * its value suggests.
79
+ *
80
+ * True for a finite non-integer, false for everything else - an integer,
81
+ * a bigint, a non-finite number, and anything that is not a number.
82
+ *
83
+ * @param v The value a parameter is about to be declared for.
84
+ * @returns Whether to declare `numeric` instead of asking `determine()`.
85
+ */
86
+ /* `determine()` answers by value, so 12 is int4 and 12.34 is float8 -
87
+ and `int4 -> money` is an assignment cast while `float8 -> money` has
88
+ no pg_cast row at all. So `select ($1::money)::text` worked with 12
89
+ and failed 42846 with 12.34: the same SQL, the same column, decided by
90
+ whether the amount happened to have a fractional part, which for money
91
+ it usually does. A test written with 12 passed and production failed
92
+ on 12.34. `numeric -> money` is an assignment cast, which an explicit
93
+ `::money` accepts, and `numeric -> float8` is implicit, so a float8
94
+ column is unaffected.
95
+
96
+ Measured, inserting into a float8 column over a loopback connection,
97
+ three interleaved rounds of 3000: 0.197ms a call against float8's
98
+ 0.198, and 51 bytes on the socket against 47. Round-tripping a double
99
+ through numeric is exact - checked against float8 for 0.1, 5e-324,
100
+ 1.7976931348623157e308, 0.30000000000000004 and pi, among others.
101
+
102
+ Non-finite values keep float8 deliberately: `numeric` only grew
103
+ Infinity in PostgreSQL 14, and NaN would be the only one of the three
104
+ that worked on 12 and 13.
105
+
106
+ This does not close `$1::interval` from a number, which fails the same
107
+ way - there is no cast to interval from any numeric type, so only an
108
+ undeclared parameter reaches it. An integer scalar is left declared,
109
+ and `new BindParam(0, 5)` is the way to say it. */
110
+ export function needsNumericParam(v) {
111
+ return typeof v === 'number' && Number.isFinite(v) && !Number.isInteger(v);
112
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Follows a value's `toPostgres()` until it reaches one that has none.
3
+ *
4
+ * `toPostgres()` is `pg`'s extension point for a type that knows how to
5
+ * write itself: a library with its own geometry, money or domain class
6
+ * implements it, and the value goes out as that class says rather than
7
+ * as whatever `String()` makes of it. The result is followed rather than
8
+ * returned, because what it hands back may itself be a `Date`, an array,
9
+ * a `Buffer` or another such value.
10
+ *
11
+ * @param v The value a parameter is about to be written from.
12
+ * @returns The value to write, which is `v` itself unless it had one.
13
+ * @throws TypeError if the values form a cycle.
14
+ */
15
+ export declare function resolveToPostgres(v: any): any;
16
+ /**
17
+ * The text an undeclared parameter - or one element of an undeclared
18
+ * array - goes out as.
19
+ *
20
+ * Follows `toPostgres()` first, then writes a plain object as JSON and
21
+ * anything else as its own text.
22
+ *
23
+ * @param v The value to render.
24
+ * @returns What to put on the wire, before any array quoting.
25
+ */
26
+ export declare function unspecifiedText(v: any): string;