postgrejs 3.3.0 → 3.5.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +55 -1
  2. package/README.md +68 -7
  3. package/connection/connection.d.ts +134 -0
  4. package/connection/connection.js +213 -2
  5. package/connection/cursor.d.ts +8 -0
  6. package/connection/cursor.js +17 -0
  7. package/connection/intl-connection.d.ts +118 -3
  8. package/connection/intl-connection.js +615 -31
  9. package/connection/pool.d.ts +16 -0
  10. package/connection/pool.js +24 -0
  11. package/connection/prepared-statement.d.ts +64 -1
  12. package/connection/prepared-statement.js +95 -5
  13. package/data-types/float4-type.js +2 -1
  14. package/data-types/float8-type.js +2 -1
  15. package/data-types/int2-type.js +2 -1
  16. package/data-types/int4-type.js +2 -1
  17. package/data-types/int8-type.js +2 -1
  18. package/data-types/numeric-type.js +7 -0
  19. package/data-types/oid-type.js +2 -1
  20. package/index.d.ts +3 -0
  21. package/index.js +3 -0
  22. package/interfaces/batch-result.d.ts +42 -0
  23. package/interfaces/batch-result.js +1 -0
  24. package/interfaces/copy-from-rows-options.d.ts +58 -0
  25. package/interfaces/copy-from-rows-options.js +1 -0
  26. package/interfaces/database-connection-params.d.ts +25 -0
  27. package/interfaces/pipeline-request.d.ts +16 -0
  28. package/interfaces/pipeline-request.js +1 -0
  29. package/interfaces/query-options.d.ts +6 -0
  30. package/package.json +5 -2
  31. package/protocol/database-error.d.ts +16 -0
  32. package/protocol/database-error.js +15 -0
  33. package/protocol/pg-socket.d.ts +76 -0
  34. package/protocol/pg-socket.js +130 -12
  35. package/util/assert-integer.d.ts +43 -0
  36. package/util/assert-integer.js +58 -0
  37. package/util/copy-from-rows.d.ts +56 -0
  38. package/util/copy-from-rows.js +95 -0
  39. package/util/encode-copy-binary.d.ts +48 -0
  40. package/util/encode-copy-binary.js +112 -0
@@ -70,6 +70,22 @@ export declare class Pool extends SafeEventEmitter {
70
70
  * Executes a query
71
71
  */
72
72
  query(sql: string | QueryRequest, options?: PoolQueryOptions): Promise<QueryResult>;
73
+ /**
74
+ * Takes a connection out of the pool, runs `fn` inside a transaction on
75
+ * it, and gives the connection back however that ends.
76
+ *
77
+ * ```ts
78
+ * await pool.transaction(async tx => {
79
+ * await tx.query('insert into orders (total) values ($1)', { params: [total] });
80
+ * await tx.query('update stock set n = n - 1 where sku = $1', { params: [sku] });
81
+ * });
82
+ * ```
83
+ *
84
+ * A transaction cannot be spread over pool.query() calls - each of those
85
+ * is free to pick a different connection, and a transaction lives on
86
+ * one. This is the way to get several statements onto the same one.
87
+ */
88
+ transaction<T>(fn: (connection: Connection) => Promise<T>): Promise<T>;
73
89
  prepare(sql: string, options?: StatementPrepareOptions): Promise<PreparedStatement>;
74
90
  release(connection: Connection): Promise<void>;
75
91
  listen(channel: string, callback: NotificationCallback): Promise<void>;
@@ -222,6 +222,30 @@ export class Pool extends SafeEventEmitter {
222
222
  slot.load--;
223
223
  }
224
224
  }
225
+ /**
226
+ * Takes a connection out of the pool, runs `fn` inside a transaction on
227
+ * it, and gives the connection back however that ends.
228
+ *
229
+ * ```ts
230
+ * await pool.transaction(async tx => {
231
+ * await tx.query('insert into orders (total) values ($1)', { params: [total] });
232
+ * await tx.query('update stock set n = n - 1 where sku = $1', { params: [sku] });
233
+ * });
234
+ * ```
235
+ *
236
+ * A transaction cannot be spread over pool.query() calls - each of those
237
+ * is free to pick a different connection, and a transaction lives on
238
+ * one. This is the way to get several statements onto the same one.
239
+ */
240
+ async transaction(fn) {
241
+ const connection = await this.acquire();
242
+ try {
243
+ return await connection.transaction(fn);
244
+ }
245
+ finally {
246
+ await this.release(connection);
247
+ }
248
+ }
225
249
  async prepare(sql, options) {
226
250
  const connection = await this.acquire();
227
251
  const statement = await connection.prepare(sql, options);
@@ -1,3 +1,4 @@
1
+ import type { BatchResult } from '../interfaces/batch-result.js';
1
2
  import type { QueryOptions } from '../interfaces/query-options.js';
2
3
  import type { QueryResult } from '../interfaces/query-result.js';
3
4
  import type { StatementPrepareOptions } from '../interfaces/statement-prepare-options.js';
@@ -20,7 +21,69 @@ export declare class PreparedStatement extends SafeEventEmitter implements Async
20
21
  get sql(): string;
21
22
  get paramTypes(): Maybe<Maybe<OID>[]>;
22
23
  execute(options?: QueryOptions): Promise<QueryResult>;
24
+ /**
25
+ * Runs this statement once per parameter set, sending every Bind/Execute
26
+ * before waiting for any of them and closing the lot with a single Sync.
27
+ *
28
+ * ```ts
29
+ * const st = await connection.prepare('update users set name = $1 where id = $2');
30
+ * const batch = await st.executeBatch([
31
+ * ['John', 1],
32
+ * ['Jane', 2],
33
+ * ['Bob', 3],
34
+ * ]);
35
+ * batch.results.map(r => r.rowsAffected); // [1, 1, 0]
36
+ * batch.totalRowsAffected; // 2
37
+ * ```
38
+ *
39
+ * Three things follow from the single Sync, and they are the reasons to
40
+ * reach for this over a loop or a Promise.all() of execute() calls:
41
+ *
42
+ * - **Speed.** Per-set Sync makes the server close an implicit
43
+ * transaction and answer ReadyForQuery every time. Collapsing that to
44
+ * one turned 1000 updates from 194ms (pipelined execute() calls) into
45
+ * 21ms locally, with socket reads dropping from 972 to 3.
46
+ * - **One transaction.** Unless an explicit transaction is already open,
47
+ * the whole batch commits or rolls back together. This differs from N
48
+ * separate execute() calls, where each commits on its own.
49
+ * - **A failing set stops the rest.** PostgreSQL discards everything
50
+ * between an error and the Sync, so sets after a rejected one never
51
+ * run. The error carries `failedIndex` (which set was rejected) and
52
+ * `batchResults` (the ones that had completed) so the caller can see
53
+ * how far it got.
54
+ *
55
+ * Every set runs the same statement, so results map to sets by position.
56
+ * Sets that return rows get them decoded into `rows` exactly as
57
+ * `execute()` would, `rowDecoder` included; `fetchCount` does not apply,
58
+ * since a portal suspended mid-batch would break that mapping - each set
59
+ * runs to completion and buffers its rows.
60
+ *
61
+ * For a bulk INSERT specifically, `connection.copyFrom()` is faster
62
+ * still, and a single `UPDATE ... FROM (VALUES ...)` beats this for bulk
63
+ * updates of one shape - at the cost of PostgreSQL's 65535-parameter
64
+ * ceiling. This is the general answer when neither fits.
65
+ *
66
+ * @param paramSets One array of bind parameters per execution.
67
+ * @param options Applied to every set - the same options `execute()`
68
+ * takes, minus `fetchCount` and `cursor`, which a batch cannot honour.
69
+ */
70
+ executeBatch(paramSets: any[][], options?: QueryOptions): Promise<BatchResult>;
71
+ protected _executeBatchWithTransaction(paramSets: any[][], options: QueryOptions): Promise<BatchResult>;
23
72
  protected _executeWithTransaction(options?: QueryOptions): Promise<QueryResult>;
73
+ /**
74
+ * Runs `fn` inside whatever transaction handling the connection's config
75
+ * and `options` call for: an implicit BEGIN when autoCommit is off, a
76
+ * SAVEPOINT to roll back to when one is already open, a COMMIT when the
77
+ * call is the one turning autoCommit back on.
78
+ *
79
+ * Generic over `fn` rather than calling _execute() directly so that
80
+ * executeBatch() gets the same handling for the batch as a whole. Per
81
+ * set would be wrong twice over: the savepoints alone would add two
82
+ * statements per set, and the batch already shares one transaction by
83
+ * virtue of its single Sync, so a per-set savepoint could not roll one
84
+ * set back independently anyway.
85
+ */
86
+ protected _withTransaction<T>(options: QueryOptions | undefined, fn: (inlineSavepoint?: string) => Promise<T>, canInlineSavepoint?: boolean): Promise<T>;
24
87
  close(): Promise<void>;
25
88
  /**
26
89
  * Asks the server to cancel whatever this statement's connection is
@@ -28,7 +91,7 @@ export declare class PreparedStatement extends SafeEventEmitter implements Async
28
91
  * option, which reports the abort to the caller that asked for it.
29
92
  */
30
93
  cancel(): Promise<void>;
31
- protected _execute(options?: QueryOptions): Promise<QueryResult>;
94
+ protected _execute(options?: QueryOptions, savepoint?: string): Promise<QueryResult>;
32
95
  /**
33
96
  * Called by Cursor.close() instead of separately awaiting
34
97
  * Portal.close() then this.close(). Returns false (no wire action taken,
@@ -55,7 +55,89 @@ export class PreparedStatement extends SafeEventEmitter {
55
55
  return withAbortSignal(options.signal, () => intlCon.cancel(), () => this._executeWithTransaction(options));
56
56
  return this._executeWithTransaction(options);
57
57
  }
58
+ /**
59
+ * Runs this statement once per parameter set, sending every Bind/Execute
60
+ * before waiting for any of them and closing the lot with a single Sync.
61
+ *
62
+ * ```ts
63
+ * const st = await connection.prepare('update users set name = $1 where id = $2');
64
+ * const batch = await st.executeBatch([
65
+ * ['John', 1],
66
+ * ['Jane', 2],
67
+ * ['Bob', 3],
68
+ * ]);
69
+ * batch.results.map(r => r.rowsAffected); // [1, 1, 0]
70
+ * batch.totalRowsAffected; // 2
71
+ * ```
72
+ *
73
+ * Three things follow from the single Sync, and they are the reasons to
74
+ * reach for this over a loop or a Promise.all() of execute() calls:
75
+ *
76
+ * - **Speed.** Per-set Sync makes the server close an implicit
77
+ * transaction and answer ReadyForQuery every time. Collapsing that to
78
+ * one turned 1000 updates from 194ms (pipelined execute() calls) into
79
+ * 21ms locally, with socket reads dropping from 972 to 3.
80
+ * - **One transaction.** Unless an explicit transaction is already open,
81
+ * the whole batch commits or rolls back together. This differs from N
82
+ * separate execute() calls, where each commits on its own.
83
+ * - **A failing set stops the rest.** PostgreSQL discards everything
84
+ * between an error and the Sync, so sets after a rejected one never
85
+ * run. The error carries `failedIndex` (which set was rejected) and
86
+ * `batchResults` (the ones that had completed) so the caller can see
87
+ * how far it got.
88
+ *
89
+ * Every set runs the same statement, so results map to sets by position.
90
+ * Sets that return rows get them decoded into `rows` exactly as
91
+ * `execute()` would, `rowDecoder` included; `fetchCount` does not apply,
92
+ * since a portal suspended mid-batch would break that mapping - each set
93
+ * runs to completion and buffers its rows.
94
+ *
95
+ * For a bulk INSERT specifically, `connection.copyFrom()` is faster
96
+ * still, and a single `UPDATE ... FROM (VALUES ...)` beats this for bulk
97
+ * updates of one shape - at the cost of PostgreSQL's 65535-parameter
98
+ * ceiling. This is the general answer when neither fits.
99
+ *
100
+ * @param paramSets One array of bind parameters per execution.
101
+ * @param options Applied to every set - the same options `execute()`
102
+ * takes, minus `fetchCount` and `cursor`, which a batch cannot honour.
103
+ */
104
+ async executeBatch(paramSets, options = {}) {
105
+ if (!Array.isArray(paramSets))
106
+ throw new TypeError('executeBatch() requires an array of parameter sets');
107
+ if (options.cursor)
108
+ throw new Error('executeBatch() cannot return a cursor - a batch runs every set to ' +
109
+ 'completion under one Sync, so there is no portal left to fetch from');
110
+ const intlCon = getIntlConnection(this.connection);
111
+ if (!paramSets.length)
112
+ return { results: [], totalRowsAffected: 0 };
113
+ if (options.signal)
114
+ return withAbortSignal(options.signal, () => intlCon.cancel(), () => this._executeBatchWithTransaction(paramSets, options));
115
+ return this._executeBatchWithTransaction(paramSets, options);
116
+ }
117
+ _executeBatchWithTransaction(paramSets, options) {
118
+ const intlCon = getIntlConnection(this.connection);
119
+ return this._withTransaction(options, () => intlCon.executeBatchReused(this.name, this._fields, this.paramTypes, paramSets, options));
120
+ }
58
121
  async _executeWithTransaction(options = {}) {
122
+ // The Bind+Execute+Sync path can carry the rollbackOnError savepoint
123
+ // in its own round trip; the cursor path cannot, because the portal it
124
+ // opens outlives this call and the savepoint has to outlive it too.
125
+ return this._withTransaction(options, savepoint => this._execute(options, savepoint), !(options.cursor && this._fields));
126
+ }
127
+ /**
128
+ * Runs `fn` inside whatever transaction handling the connection's config
129
+ * and `options` call for: an implicit BEGIN when autoCommit is off, a
130
+ * SAVEPOINT to roll back to when one is already open, a COMMIT when the
131
+ * call is the one turning autoCommit back on.
132
+ *
133
+ * Generic over `fn` rather than calling _execute() directly so that
134
+ * executeBatch() gets the same handling for the batch as a whole. Per
135
+ * set would be wrong twice over: the savepoints alone would add two
136
+ * statements per set, and the batch already shares one transaction by
137
+ * virtue of its single Sync, so a per-set savepoint could not roll one
138
+ * set back independently anyway.
139
+ */
140
+ async _withTransaction(options = {}, fn, canInlineSavepoint = false) {
59
141
  const intlCon = getIntlConnection(this.connection);
60
142
  const transactionCommand = this.sql.match(/^(\bBEGIN\b|\bCOMMIT\b|\bSTART\b|\bROLLBACK|SAVEPOINT|RELEASE\b)/i);
61
143
  let beginFirst = false;
@@ -77,13 +159,21 @@ export class PreparedStatement extends SafeEventEmitter {
77
159
  const rollbackOnError = !transactionCommand &&
78
160
  intlCon.inTransaction &&
79
161
  (options?.rollbackOnError ?? intlCon.config.rollbackOnError ?? true);
80
- if (rollbackOnError && intlCon.inTransaction)
162
+ // When `fn` can put the savepoint on the wire itself, SAVEPOINT and
163
+ // RELEASE ride along inside its single Sync instead of costing a round
164
+ // trip each - measured at 51us for the pair that way against 511us as
165
+ // two separate round trips. The error path is unchanged either way:
166
+ // PostgreSQL discards everything between a failed statement and the
167
+ // Sync, so an inlined RELEASE never runs, and ROLLBACK TO goes out on
168
+ // its own below exactly as before.
169
+ const inlineSavepoint = canInlineSavepoint && rollbackOnError && intlCon.inTransaction;
170
+ if (rollbackOnError && intlCon.inTransaction && !inlineSavepoint)
81
171
  await intlCon.execute('SAVEPOINT ' + this._onErrorSavePoint);
82
172
  try {
83
- const result = await this._execute(options);
173
+ const result = await fn(inlineSavepoint ? this._onErrorSavePoint : undefined);
84
174
  if (commitLast)
85
175
  await intlCon.execute('COMMIT');
86
- else if (rollbackOnError && intlCon.inTransaction) {
176
+ else if (rollbackOnError && intlCon.inTransaction && !inlineSavepoint) {
87
177
  await intlCon.execute('RELEASE ' + this._onErrorSavePoint + ';');
88
178
  }
89
179
  return result;
@@ -112,7 +202,7 @@ export class PreparedStatement extends SafeEventEmitter {
112
202
  async cancel() {
113
203
  return getIntlConnection(this.connection).cancel();
114
204
  }
115
- async _execute(options = {}) {
205
+ async _execute(options = {}, savepoint) {
116
206
  const intlCon = getIntlConnection(this.connection);
117
207
  if (options.cursor && this._fields) {
118
208
  intlCon.ref();
@@ -142,7 +232,7 @@ export class PreparedStatement extends SafeEventEmitter {
142
232
  // unnamed portal, reusing the RowDescription prepare() already fetched
143
233
  // instead of re-Describing (and re-Close-ing) a fresh named portal on
144
234
  // every call.
145
- return intlCon.executeReused(this.name, this._fields, this.paramTypes, options.params, options);
235
+ return intlCon.executeReused(this.name, this._fields, this.paramTypes, options.params, options, savepoint);
146
236
  }
147
237
  /**
148
238
  * Called by Cursor.close() instead of separately awaiting
@@ -1,4 +1,5 @@
1
1
  import { DataTypeOIDs } from '../constants.js';
2
+ import { assertCoercedNumber } from '../util/assert-integer.js';
2
3
  import { fastParseFloatBuffer } from '../util/fast-parsefloat.js';
3
4
  export const Float4Type = {
4
5
  name: 'float4',
@@ -8,7 +9,7 @@ export const Float4Type = {
8
9
  return '' + v;
9
10
  },
10
11
  encodeBinary(buf, v) {
11
- buf.writeFloatBE(typeof v === 'number' ? v : parseFloat(v));
12
+ buf.writeFloatBE(typeof v === 'number' ? v : assertCoercedNumber(parseFloat(v), 'float4'));
12
13
  },
13
14
  decodeBinary(v, offset = 0) {
14
15
  return v.readFloatBE(offset);
@@ -1,4 +1,5 @@
1
1
  import { DataTypeOIDs } from '../constants.js';
2
+ import { assertCoercedNumber } from '../util/assert-integer.js';
2
3
  import { fastParseFloatBuffer } from '../util/fast-parsefloat.js';
3
4
  export const Float8Type = {
4
5
  name: 'float8',
@@ -9,7 +10,7 @@ export const Float8Type = {
9
10
  return '' + v;
10
11
  },
11
12
  encodeBinary(buf, v) {
12
- buf.writeDoubleBE(typeof v === 'number' ? v : parseFloat(v));
13
+ buf.writeDoubleBE(typeof v === 'number' ? v : assertCoercedNumber(parseFloat(v), 'float8'));
13
14
  },
14
15
  decodeBinary(v, offset = 0) {
15
16
  return v.readDoubleBE(offset);
@@ -1,4 +1,5 @@
1
1
  import { DataTypeOIDs } from '../constants.js';
2
+ import { assertInteger } from '../util/assert-integer.js';
2
3
  import { fastParseInt, fastParseIntBuffer } from '../util/fast-parseint.js';
3
4
  export const Int2Type = {
4
5
  name: 'int2',
@@ -8,7 +9,7 @@ export const Int2Type = {
8
9
  return '' + v;
9
10
  },
10
11
  encodeBinary(buf, v) {
11
- buf.writeInt16BE(fastParseInt(v));
12
+ buf.writeInt16BE(assertInteger(fastParseInt(v), 'int2'));
12
13
  },
13
14
  decodeBinary(v, offset = 0) {
14
15
  return v.readInt16BE(offset);
@@ -1,4 +1,5 @@
1
1
  import { DataTypeOIDs } from '../constants.js';
2
+ import { assertInteger } from '../util/assert-integer.js';
2
3
  import { fastParseInt, fastParseIntBuffer } from '../util/fast-parseint.js';
3
4
  export const Int4Type = {
4
5
  name: 'int4',
@@ -8,7 +9,7 @@ export const Int4Type = {
8
9
  return '' + v;
9
10
  },
10
11
  encodeBinary(buf, v) {
11
- buf.writeInt32BE(fastParseInt(v));
12
+ buf.writeInt32BE(assertInteger(fastParseInt(v), 'int4'));
12
13
  },
13
14
  decodeBinary(v, offset = 0) {
14
15
  return v.readInt32BE(offset);
@@ -1,4 +1,5 @@
1
1
  import { DataTypeOIDs } from '../constants.js';
2
+ import { assertBigIntSource } from '../util/assert-integer.js';
2
3
  import { readBigInt64BE } from '../util/bigint-methods.js';
3
4
  import { fastParseIntBuffer } from '../util/fast-parseint.js';
4
5
  const maxSafeInteger = BigInt(Number.MAX_SAFE_INTEGER);
@@ -10,7 +11,7 @@ export const Int8Type = {
10
11
  return '' + v;
11
12
  },
12
13
  encodeBinary(buf, v) {
13
- buf.writeBigInt64BE(v);
14
+ buf.writeBigInt64BE(assertBigIntSource(v, 'int8'));
14
15
  },
15
16
  decodeBinary(buf, offset = 0) {
16
17
  const v = typeof buf.readBigInt64BE === 'function'
@@ -1,4 +1,5 @@
1
1
  import { DataTypeOIDs } from '../constants.js';
2
+ import { assertCoercedNumber } from '../util/assert-integer.js';
2
3
  import { fastParseFloatBuffer } from '../util/fast-parsefloat.js';
3
4
  const NUMERIC_NEG = 0x4000;
4
5
  const NUMERIC_NAN = 0xc000;
@@ -37,6 +38,12 @@ export const NumericType = {
37
38
  return writeHeader(0, 0, NUMERIC_PINF, 0);
38
39
  if (str === '-Infinity')
39
40
  return writeHeader(0, 0, NUMERIC_NINF, 0);
41
+ // Anything with no numeric reading at all - `{}` stringifies to
42
+ // "[object Object]", `true` to "true" - would otherwise be walked
43
+ // digit by digit below and stored as 0.
44
+ // Number('') is 0, not NaN, so an empty string - what `[]` and `null`
45
+ // stringify to - would slip past the NaN check below.
46
+ assertCoercedNumber(str.length ? Number(str) : NaN, 'numeric');
40
47
  // Exponent form has no place in the wire format; go through Number to
41
48
  // get it back into plain notation. toFixed() cannot do this itself -
42
49
  // per spec it falls back to exponential notation once the magnitude
@@ -1,4 +1,5 @@
1
1
  import { DataTypeNames, DataTypeOIDs } from '../constants.js';
2
+ import { assertInteger } from '../util/assert-integer.js';
2
3
  import { fastParseInt, fastParseIntBuffer } from '../util/fast-parseint.js';
3
4
  export const OidType = {
4
5
  name: 'oid',
@@ -8,7 +9,7 @@ export const OidType = {
8
9
  return '' + v;
9
10
  },
10
11
  encodeBinary(buf, v) {
11
- buf.writeUInt32BE(fastParseInt(v));
12
+ buf.writeUInt32BE(assertInteger(fastParseInt(v), 'oid'));
12
13
  },
13
14
  decodeBinary(v, offset = 0) {
14
15
  return v.readUInt32BE(offset);
package/index.d.ts CHANGED
@@ -8,13 +8,16 @@ export * from './connection/pool.js';
8
8
  export * from './connection/prepared-statement.js';
9
9
  export * from './constants.js';
10
10
  export * from './data-type-map.js';
11
+ export * from './interfaces/batch-result.js';
11
12
  export * from './interfaces/command-result.js';
13
+ export * from './interfaces/copy-from-rows-options.js';
12
14
  export * from './interfaces/data-mapping-options.js';
13
15
  export * from './interfaces/data-type.js';
14
16
  export * from './interfaces/database-connection-params.js';
15
17
  export * from './interfaces/field-info.js';
16
18
  export * from './interfaces/function-call-options.js';
17
19
  export * from './interfaces/function-call-result.js';
20
+ export * from './interfaces/pipeline-request.js';
18
21
  export * from './interfaces/query-options.js';
19
22
  export * from './interfaces/query-result.js';
20
23
  export * from './interfaces/script-execute-options.js';
package/index.js CHANGED
@@ -8,13 +8,16 @@ export * from './connection/pool.js';
8
8
  export * from './connection/prepared-statement.js';
9
9
  export * from './constants.js';
10
10
  export * from './data-type-map.js';
11
+ export * from './interfaces/batch-result.js';
11
12
  export * from './interfaces/command-result.js';
13
+ export * from './interfaces/copy-from-rows-options.js';
12
14
  export * from './interfaces/data-mapping-options.js';
13
15
  export * from './interfaces/data-type.js';
14
16
  export * from './interfaces/database-connection-params.js';
15
17
  export * from './interfaces/field-info.js';
16
18
  export * from './interfaces/function-call-options.js';
17
19
  export * from './interfaces/function-call-result.js';
20
+ export * from './interfaces/pipeline-request.js';
18
21
  export * from './interfaces/query-options.js';
19
22
  export * from './interfaces/query-result.js';
20
23
  export * from './interfaces/script-execute-options.js';
@@ -0,0 +1,42 @@
1
+ import type { FieldInfo } from './field-info.js';
2
+ /**
3
+ * What one parameter set in a batch produced. There is exactly one of
4
+ * these per set handed to `PreparedStatement.executeBatch()`, in the same
5
+ * order - the server answers each Execute with its own CommandComplete,
6
+ * so the mapping is positional and does not depend on the statement.
7
+ */
8
+ export interface BatchCommandResult {
9
+ /** The command tag PostgreSQL reported for this set (INSERT/UPDATE/...). */
10
+ command?: string;
11
+ /** Rows affected, for INSERT/UPDATE/DELETE - as in QueryResult. */
12
+ rowsAffected?: number;
13
+ /**
14
+ * Rows this set returned, decoded the same way `execute()` decodes them
15
+ * (including any `rowDecoder` given in the options). Only present when
16
+ * the statement actually returns rows - a plain UPDATE without RETURNING
17
+ * leaves this undefined rather than setting it to an empty array.
18
+ */
19
+ rows?: any[];
20
+ }
21
+ /**
22
+ * The result of `PreparedStatement.executeBatch()`.
23
+ *
24
+ * `results` is always the full set of *completed* sets. On success that is
25
+ * every set that was submitted; when the server rejects one, the batch
26
+ * stops there (see the class doc) and the completed prefix is attached to
27
+ * the thrown error as `batchResults` instead.
28
+ */
29
+ export interface BatchResult {
30
+ /** One entry per parameter set, in submission order. */
31
+ results: BatchCommandResult[];
32
+ /** Sum of every set's `rowsAffected`, for the common bulk-write case. */
33
+ totalRowsAffected: number;
34
+ /**
35
+ * Column descriptions, when the statement returns rows. One copy for the
36
+ * whole batch rather than per set: every set runs the same prepared
37
+ * statement, so the shape cannot differ between them.
38
+ */
39
+ fields?: FieldInfo[];
40
+ /** Wall time for the whole batch, when `timing` is enabled. */
41
+ executeTime?: number;
42
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,58 @@
1
+ import type { DataTypeMap } from '../data-type-map.js';
2
+ import type { OID } from '../types.js';
3
+ import type { DataMappingOptions } from './data-mapping-options.js';
4
+ export interface CopyFromRowsOptions extends DataMappingOptions {
5
+ /** Type registry used to encode values; defaults to GlobalTypeMap. */
6
+ typeMap?: DataTypeMap;
7
+ /**
8
+ * Destination columns, in the order the rows supply them. Omitted means
9
+ * every column of the table, in table order - the same default `COPY
10
+ * table FROM STDIN` has without a column list.
11
+ */
12
+ columns?: string[];
13
+ /**
14
+ * Type OIDs for those columns, skipping the round trip that would
15
+ * otherwise ask the server for them.
16
+ *
17
+ * Only worth setting to avoid that probe on a hot path, and only when the
18
+ * OIDs are known to match the table: binary COPY does no conversion, so a
19
+ * wrong OID here is not a mismatch the server can reconcile - it either
20
+ * rejects the stream or, for two types that happen to share a width,
21
+ * stores the bytes as the wrong value.
22
+ */
23
+ columnTypes?: OID[];
24
+ /**
25
+ * Bytes to accumulate before sending a CopyData message. Defaults to
26
+ * 128KB; anything from 64KB to 256KB measures much the same, while a
27
+ * message per row is roughly ten times slower.
28
+ */
29
+ chunkSize?: number;
30
+ /**
31
+ * What to do with a value the destination column's type cannot encode -
32
+ * `'abc'` for an integer column, `{}` for a numeric one.
33
+ *
34
+ * `'throw'` (default) aborts the copy, naming the row and column. The
35
+ * other two exist for a load big enough that abandoning it over one bad
36
+ * record costs more than the record is worth: `'null'` writes NULL in
37
+ * that column and keeps the row, `'skip'` drops the row entirely. Both
38
+ * report how many they touched, so a tolerant load still says what it
39
+ * swallowed rather than looking clean.
40
+ *
41
+ * None of them apply to NaN or Infinity in a float or numeric column -
42
+ * PostgreSQL stores those as values distinct from NULL, so they encode
43
+ * normally and are never "invalid".
44
+ */
45
+ onInvalidValue?: 'throw' | 'null' | 'skip';
46
+ }
47
+ /** What `copyFromRows()` reports back. */
48
+ export interface CopyFromRowsResult {
49
+ /** Rows actually sent to the server. */
50
+ rowCount: number;
51
+ /**
52
+ * Rows dropped under `onInvalidValue: 'skip'`. Always 0 otherwise, and a
53
+ * non-zero value here is the only sign a tolerant load lost anything.
54
+ */
55
+ skippedRows: number;
56
+ /** Values replaced with NULL under `onInvalidValue: 'null'`. */
57
+ nulledValues: number;
58
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -83,6 +83,31 @@ export interface DatabaseConnectionParams {
83
83
  * @default false
84
84
  */
85
85
  autoCommit?: boolean;
86
+ /**
87
+ * Reuse a server-side prepared statement for SQL this connection has run
88
+ * before, instead of parsing it again every time.
89
+ *
90
+ * On by default. A repeated query then costs Bind/Execute rather than
91
+ * Parse/Bind/Describe/Execute, which on fifty concurrent calls of one
92
+ * statement measured 0.97ms against 3.13ms.
93
+ *
94
+ * Turn it off when named prepared statements cannot survive between
95
+ * calls - the usual case being PgBouncer in transaction pooling mode
96
+ * before 1.21, which hands each transaction a different backend, so a
97
+ * statement prepared on one is missing on the next.
98
+ *
99
+ * @default true
100
+ */
101
+ prepare?: boolean;
102
+ /**
103
+ * How many prepared statements one connection keeps. The least recently
104
+ * used is closed when the limit is reached, so an application that
105
+ * builds SQL text dynamically cannot accumulate statements on the server
106
+ * without bound.
107
+ *
108
+ * @default 64
109
+ */
110
+ preparedStatementCacheSize?: number;
86
111
  /**
87
112
  * When on, if a statement in a transaction block generates an error,
88
113
  * the error is ignored and the transaction continues.
@@ -0,0 +1,16 @@
1
+ import type { Maybe, OID } from '../types.js';
2
+ /**
3
+ * One statement in a `Connection.pipeline()` call. The `sql` tag's own
4
+ * output satisfies this shape, so a pipeline can be written straight from
5
+ * tagged templates without any conversion.
6
+ */
7
+ export interface PipelineRequest {
8
+ sql: string;
9
+ params?: any[];
10
+ /**
11
+ * Parameter type OIDs for this statement. Per statement rather than per
12
+ * pipeline: the statements differ, so one shared list could only ever be
13
+ * right for one of them.
14
+ */
15
+ paramTypes?: Maybe<OID>[];
16
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -16,6 +16,12 @@ export interface QueryOptions extends DataMappingOptions {
16
16
  * @default false
17
17
  */
18
18
  objectRows?: boolean;
19
+ /**
20
+ * Overrides the connection's `prepare` setting for this call - `false`
21
+ * to keep a one-off statement out of the cache, `true` to use it on a
22
+ * connection that has caching off.
23
+ */
24
+ prepare?: boolean;
19
25
  /**
20
26
  * Controls how each row's raw column data is turned into the value handed back to the
21
27
  * caller. `'array'` and `'object'` select the built-in decoders (the same output
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "postgrejs",
3
3
  "description": "Professional PostgreSQL client NodeJS",
4
- "version": "3.3.0",
4
+ "version": "3.5.0",
5
5
  "author": "Panates",
6
6
  "license": "BSD-3-Clause",
7
7
  "dependencies": {
@@ -29,7 +29,10 @@
29
29
  },
30
30
  "homepage": "https://www.postgrejs.com/",
31
31
  "contributors": [
32
- "Eray Hanoglu <e.hanoglu@panates.com>"
32
+ "Eray Hanoglu <e.hanoglu@panates.com>",
33
+ "Ilker Gurelli <i.gurelli@panates.com>",
34
+ "Onur Tokel <o.tokel@panates.com>",
35
+ "Bircan Yuruk <b.yuruk@panates.com>"
33
36
  ],
34
37
  "repository": {
35
38
  "type": "git",
@@ -1,5 +1,21 @@
1
+ import type { BatchCommandResult } from '../interfaces/batch-result.js';
1
2
  import { Protocol } from './protocol.js';
2
3
  export declare class DatabaseError extends Error {
4
+ /**
5
+ * Which entry of a multi-statement submission the server rejected -
6
+ * PreparedStatement.executeBatch()'s parameter sets, or the statements
7
+ * given to Connection.pipeline(). Entries after this index never ran:
8
+ * PostgreSQL discards everything between an error and the single Sync
9
+ * that closes such a submission.
10
+ */
11
+ failedIndex?: number;
12
+ /**
13
+ * The sets that had already completed when the batch failed, in
14
+ * submission order - only set by PreparedStatement.executeBatch(). They
15
+ * did run, but the batch shares one implicit transaction, so unless an
16
+ * explicit transaction was already open they are rolled back with it.
17
+ */
18
+ batchResults?: BatchCommandResult[];
3
19
  severity?: string;
4
20
  code?: string;
5
21
  detail?: string;