postgrejs 2.12.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/CHANGELOG.md +814 -0
- package/LICENSE +21 -0
- package/README.md +200 -0
- package/cjs/connection/bind-param.js +10 -0
- package/cjs/connection/connection.js +279 -0
- package/cjs/connection/cursor.js +93 -0
- package/cjs/connection/intl-connection.js +243 -0
- package/cjs/connection/pool.js +226 -0
- package/cjs/connection/portal.js +148 -0
- package/cjs/connection/prepared-statement.js +217 -0
- package/cjs/constants.js +250 -0
- package/cjs/data-type-map.js +119 -0
- package/cjs/data-types/bool-type.js +27 -0
- package/cjs/data-types/box-type.js +52 -0
- package/cjs/data-types/bytea-type.js +29 -0
- package/cjs/data-types/char-type.js +27 -0
- package/cjs/data-types/circle-type.js +48 -0
- package/cjs/data-types/date-type.js +67 -0
- package/cjs/data-types/float4-type.js +25 -0
- package/cjs/data-types/float8-type.js +25 -0
- package/cjs/data-types/int2-type.js +26 -0
- package/cjs/data-types/int2-vector-type.js +38 -0
- package/cjs/data-types/int4-type.js +26 -0
- package/cjs/data-types/int8-type.js +31 -0
- package/cjs/data-types/json-type.js +38 -0
- package/cjs/data-types/jsonb-type.js +42 -0
- package/cjs/data-types/lseg-type.js +52 -0
- package/cjs/data-types/numeric-type.js +125 -0
- package/cjs/data-types/oid-type.js +32 -0
- package/cjs/data-types/point-type.js +39 -0
- package/cjs/data-types/time-type.js +54 -0
- package/cjs/data-types/timestamp-type.js +81 -0
- package/cjs/data-types/timestamptz-type.js +73 -0
- package/cjs/data-types/uuid-type.js +39 -0
- package/cjs/data-types/varchar-type.js +27 -0
- package/cjs/index.js +41 -0
- package/cjs/interfaces/command-result.js +2 -0
- package/cjs/interfaces/data-mapping-options.js +2 -0
- package/cjs/interfaces/data-type.js +2 -0
- package/cjs/interfaces/database-connection-params.js +2 -0
- package/cjs/interfaces/field-info.js +2 -0
- package/cjs/interfaces/query-options.js +2 -0
- package/cjs/interfaces/query-result.js +2 -0
- package/cjs/interfaces/script-execute-options.js +2 -0
- package/cjs/interfaces/script-result.js +2 -0
- package/cjs/interfaces/statement-prepare-options.js +2 -0
- package/cjs/package.json +3 -0
- package/cjs/protocol/backend.js +256 -0
- package/cjs/protocol/buffer-reader.js +78 -0
- package/cjs/protocol/database-error.js +17 -0
- package/cjs/protocol/frontend.js +218 -0
- package/cjs/protocol/pg-socket.js +345 -0
- package/cjs/protocol/protocol.js +71 -0
- package/cjs/protocol/sasl.js +108 -0
- package/cjs/protocol/smart-buffer.js +195 -0
- package/cjs/safe-event-emitter.js +17 -0
- package/cjs/types.js +2 -0
- package/cjs/util/array-calculatedim.js +19 -0
- package/cjs/util/bigint-methods.js +42 -0
- package/cjs/util/config-from-env.js +30 -0
- package/cjs/util/connection-config.js +65 -0
- package/cjs/util/convert-row-to-object.js +13 -0
- package/cjs/util/decode-binaryarray.js +39 -0
- package/cjs/util/encode-binaryarray.js +45 -0
- package/cjs/util/escape-literal.js +27 -0
- package/cjs/util/fast-parseint.js +32 -0
- package/cjs/util/get-parsers.js +42 -0
- package/cjs/util/parse-array.js +61 -0
- package/cjs/util/parse-datetime.js +38 -0
- package/cjs/util/parse-row.js +11 -0
- package/cjs/util/parse-time.js +29 -0
- package/cjs/util/stringify-arrayliteral.js +38 -0
- package/cjs/util/stringify-for-sql.js +30 -0
- package/cjs/util/wrap-row-description.js +35 -0
- package/esm/connection/bind-param.js +6 -0
- package/esm/connection/connection.js +275 -0
- package/esm/connection/cursor.js +86 -0
- package/esm/connection/intl-connection.js +238 -0
- package/esm/connection/pool.js +222 -0
- package/esm/connection/portal.js +144 -0
- package/esm/connection/prepared-statement.js +213 -0
- package/esm/constants.js +247 -0
- package/esm/data-type-map.js +115 -0
- package/esm/data-types/bool-type.js +24 -0
- package/esm/data-types/box-type.js +49 -0
- package/esm/data-types/bytea-type.js +23 -0
- package/esm/data-types/char-type.js +24 -0
- package/esm/data-types/circle-type.js +45 -0
- package/esm/data-types/date-type.js +64 -0
- package/esm/data-types/float4-type.js +22 -0
- package/esm/data-types/float8-type.js +22 -0
- package/esm/data-types/int2-type.js +23 -0
- package/esm/data-types/int2-vector-type.js +35 -0
- package/esm/data-types/int4-type.js +23 -0
- package/esm/data-types/int8-type.js +28 -0
- package/esm/data-types/json-type.js +35 -0
- package/esm/data-types/jsonb-type.js +39 -0
- package/esm/data-types/lseg-type.js +49 -0
- package/esm/data-types/numeric-type.js +121 -0
- package/esm/data-types/oid-type.js +29 -0
- package/esm/data-types/point-type.js +36 -0
- package/esm/data-types/time-type.js +51 -0
- package/esm/data-types/timestamp-type.js +78 -0
- package/esm/data-types/timestamptz-type.js +70 -0
- package/esm/data-types/uuid-type.js +36 -0
- package/esm/data-types/varchar-type.js +24 -0
- package/esm/index.js +25 -0
- package/esm/interfaces/command-result.js +1 -0
- package/esm/interfaces/data-mapping-options.js +1 -0
- package/esm/interfaces/data-type.js +1 -0
- package/esm/interfaces/database-connection-params.js +1 -0
- package/esm/interfaces/field-info.js +1 -0
- package/esm/interfaces/query-options.js +1 -0
- package/esm/interfaces/query-result.js +1 -0
- package/esm/interfaces/script-execute-options.js +1 -0
- package/esm/interfaces/script-result.js +1 -0
- package/esm/interfaces/statement-prepare-options.js +1 -0
- package/esm/protocol/backend.js +252 -0
- package/esm/protocol/buffer-reader.js +74 -0
- package/esm/protocol/database-error.js +13 -0
- package/esm/protocol/frontend.js +214 -0
- package/esm/protocol/pg-socket.js +338 -0
- package/esm/protocol/protocol.js +68 -0
- package/esm/protocol/sasl.js +102 -0
- package/esm/protocol/smart-buffer.js +168 -0
- package/esm/safe-event-emitter.js +13 -0
- package/esm/types.js +1 -0
- package/esm/util/array-calculatedim.js +15 -0
- package/esm/util/bigint-methods.js +37 -0
- package/esm/util/config-from-env.js +26 -0
- package/esm/util/connection-config.js +57 -0
- package/esm/util/convert-row-to-object.js +9 -0
- package/esm/util/decode-binaryarray.js +35 -0
- package/esm/util/encode-binaryarray.js +41 -0
- package/esm/util/escape-literal.js +23 -0
- package/esm/util/fast-parseint.js +28 -0
- package/esm/util/get-parsers.js +38 -0
- package/esm/util/parse-array.js +57 -0
- package/esm/util/parse-datetime.js +34 -0
- package/esm/util/parse-row.js +7 -0
- package/esm/util/parse-time.js +25 -0
- package/esm/util/stringify-arrayliteral.js +34 -0
- package/esm/util/stringify-for-sql.js +25 -0
- package/esm/util/wrap-row-description.js +31 -0
- package/package.json +54 -0
- package/typings/connection/bind-param.d.ts +6 -0
- package/typings/connection/connection.d.ts +110 -0
- package/typings/connection/cursor.d.ts +25 -0
- package/typings/connection/intl-connection.d.ts +40 -0
- package/typings/connection/pool.d.ts +52 -0
- package/typings/connection/portal.d.ts +23 -0
- package/typings/connection/prepared-statement.d.ts +27 -0
- package/typings/constants.d.ts +130 -0
- package/typings/data-type-map.d.ts +11 -0
- package/typings/data-types/bool-type.d.ts +3 -0
- package/typings/data-types/box-type.d.ts +3 -0
- package/typings/data-types/bytea-type.d.ts +3 -0
- package/typings/data-types/char-type.d.ts +3 -0
- package/typings/data-types/circle-type.d.ts +3 -0
- package/typings/data-types/date-type.d.ts +3 -0
- package/typings/data-types/float4-type.d.ts +3 -0
- package/typings/data-types/float8-type.d.ts +3 -0
- package/typings/data-types/int2-type.d.ts +3 -0
- package/typings/data-types/int2-vector-type.d.ts +3 -0
- package/typings/data-types/int4-type.d.ts +3 -0
- package/typings/data-types/int8-type.d.ts +3 -0
- package/typings/data-types/json-type.d.ts +3 -0
- package/typings/data-types/jsonb-type.d.ts +3 -0
- package/typings/data-types/lseg-type.d.ts +3 -0
- package/typings/data-types/numeric-type.d.ts +12 -0
- package/typings/data-types/oid-type.d.ts +4 -0
- package/typings/data-types/point-type.d.ts +3 -0
- package/typings/data-types/time-type.d.ts +3 -0
- package/typings/data-types/timestamp-type.d.ts +3 -0
- package/typings/data-types/timestamptz-type.d.ts +3 -0
- package/typings/data-types/uuid-type.d.ts +3 -0
- package/typings/data-types/varchar-type.d.ts +3 -0
- package/typings/index.d.ts +25 -0
- package/typings/interfaces/command-result.d.ts +28 -0
- package/typings/interfaces/data-mapping-options.d.ts +9 -0
- package/typings/interfaces/data-type.d.ts +31 -0
- package/typings/interfaces/database-connection-params.d.ts +38 -0
- package/typings/interfaces/field-info.d.ts +46 -0
- package/typings/interfaces/query-options.d.ts +46 -0
- package/typings/interfaces/query-result.d.ts +8 -0
- package/typings/interfaces/script-execute-options.d.ts +26 -0
- package/typings/interfaces/script-result.d.ts +15 -0
- package/typings/interfaces/statement-prepare-options.d.ts +13 -0
- package/typings/protocol/backend.d.ts +9 -0
- package/typings/protocol/buffer-reader.d.ts +18 -0
- package/typings/protocol/database-error.d.ts +20 -0
- package/typings/protocol/frontend.d.ts +59 -0
- package/typings/protocol/pg-socket.d.ts +51 -0
- package/typings/protocol/protocol.d.ts +180 -0
- package/typings/protocol/sasl.d.ts +13 -0
- package/typings/protocol/smart-buffer.d.ts +39 -0
- package/typings/safe-event-emitter.d.ts +5 -0
- package/typings/types.d.ts +15 -0
- package/typings/util/array-calculatedim.d.ts +1 -0
- package/typings/util/bigint-methods.d.ts +3 -0
- package/typings/util/config-from-env.d.ts +2 -0
- package/typings/util/connection-config.d.ts +3 -0
- package/typings/util/convert-row-to-object.d.ts +2 -0
- package/typings/util/decode-binaryarray.d.ts +4 -0
- package/typings/util/encode-binaryarray.d.ts +5 -0
- package/typings/util/escape-literal.d.ts +1 -0
- package/typings/util/fast-parseint.d.ts +1 -0
- package/typings/util/get-parsers.d.ts +4 -0
- package/typings/util/parse-array.d.ts +5 -0
- package/typings/util/parse-datetime.d.ts +1 -0
- package/typings/util/parse-row.d.ts +3 -0
- package/typings/util/parse-time.d.ts +3 -0
- package/typings/util/stringify-arrayliteral.d.ts +3 -0
- package/typings/util/stringify-for-sql.d.ts +4 -0
- package/typings/util/wrap-row-description.d.ts +5 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import DoublyLinked from 'doublylinked';
|
|
2
|
+
import { TaskQueue } from 'power-tasks';
|
|
3
|
+
import { SafeEventEmitter } from '../safe-event-emitter.js';
|
|
4
|
+
import { convertRowToObject } from '../util/convert-row-to-object.js';
|
|
5
|
+
import { parseRow } from '../util/parse-row.js';
|
|
6
|
+
export class Cursor extends SafeEventEmitter {
|
|
7
|
+
constructor(statement, portal, fields, parsers, queryOptions) {
|
|
8
|
+
super();
|
|
9
|
+
this._taskQueue = new TaskQueue({ concurrency: 1 });
|
|
10
|
+
this._rows = new DoublyLinked();
|
|
11
|
+
this._closed = false;
|
|
12
|
+
this._statement = statement;
|
|
13
|
+
this._portal = portal;
|
|
14
|
+
this._parsers = parsers;
|
|
15
|
+
this._queryOptions = queryOptions;
|
|
16
|
+
this.fields = fields;
|
|
17
|
+
}
|
|
18
|
+
get rowType() {
|
|
19
|
+
return this._queryOptions.objectRows ? 'object' : 'array';
|
|
20
|
+
}
|
|
21
|
+
get isClosed() {
|
|
22
|
+
return this._closed;
|
|
23
|
+
}
|
|
24
|
+
async next() {
|
|
25
|
+
if (!this._rows.length) {
|
|
26
|
+
if (this._closed)
|
|
27
|
+
return;
|
|
28
|
+
await this._fetchRows();
|
|
29
|
+
}
|
|
30
|
+
return this._rows.shift();
|
|
31
|
+
}
|
|
32
|
+
async fetch(nRows) {
|
|
33
|
+
const out = [];
|
|
34
|
+
if (this._closed)
|
|
35
|
+
return out;
|
|
36
|
+
for (let i = 0; i < nRows; i++) {
|
|
37
|
+
if (!this._rows.length)
|
|
38
|
+
await this._fetchRows();
|
|
39
|
+
if (this._rows.length)
|
|
40
|
+
out.push(this._rows.shift());
|
|
41
|
+
else
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
async close() {
|
|
47
|
+
if (this._closed)
|
|
48
|
+
return;
|
|
49
|
+
await this._portal.close();
|
|
50
|
+
await this._statement.close();
|
|
51
|
+
this.emit('close');
|
|
52
|
+
this._closed = true;
|
|
53
|
+
}
|
|
54
|
+
async _fetchRows() {
|
|
55
|
+
if (this._closed)
|
|
56
|
+
return;
|
|
57
|
+
const portal = this._portal;
|
|
58
|
+
await this._taskQueue
|
|
59
|
+
.enqueue(async () => {
|
|
60
|
+
const queryOptions = this._queryOptions;
|
|
61
|
+
const r = await portal.execute(queryOptions.fetchCount || 100);
|
|
62
|
+
if (r && r.rows && r.rows.length) {
|
|
63
|
+
if (this._parsers) {
|
|
64
|
+
const objectRows = queryOptions.objectRows;
|
|
65
|
+
const fields = this.fields;
|
|
66
|
+
const rows = r.rows;
|
|
67
|
+
for (let i = 0; i < rows.length; i++) {
|
|
68
|
+
const row = rows[i];
|
|
69
|
+
parseRow(this._parsers, row, this._queryOptions);
|
|
70
|
+
if (objectRows)
|
|
71
|
+
rows[i] = convertRowToObject(fields, row);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
this._rows.push(...r.rows);
|
|
75
|
+
this.emit('fetch', r.rows);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
await this.close();
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
.toPromise();
|
|
82
|
+
}
|
|
83
|
+
[Symbol.asyncDispose]() {
|
|
84
|
+
return this.close();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { TaskQueue } from 'power-tasks';
|
|
2
|
+
import { coerceToBoolean } from 'putil-varhelpers';
|
|
3
|
+
import { ConnectionState } from '../constants.js';
|
|
4
|
+
import { GlobalTypeMap } from '../data-type-map.js';
|
|
5
|
+
import { PgSocket } from '../protocol/pg-socket.js';
|
|
6
|
+
import { Protocol } from '../protocol/protocol.js';
|
|
7
|
+
import { SafeEventEmitter } from '../safe-event-emitter.js';
|
|
8
|
+
import { getConnectionConfig } from '../util/connection-config.js';
|
|
9
|
+
import { convertRowToObject } from '../util/convert-row-to-object.js';
|
|
10
|
+
import { escapeLiteral } from '../util/escape-literal.js';
|
|
11
|
+
import { getParsers } from '../util/get-parsers.js';
|
|
12
|
+
import { parseRow } from '../util/parse-row.js';
|
|
13
|
+
import { wrapRowDescription } from '../util/wrap-row-description.js';
|
|
14
|
+
const DataFormat = Protocol.DataFormat;
|
|
15
|
+
export class IntlConnection extends SafeEventEmitter {
|
|
16
|
+
constructor(config) {
|
|
17
|
+
super();
|
|
18
|
+
this._refCount = 0;
|
|
19
|
+
this.transactionStatus = 'I';
|
|
20
|
+
this.statementQueue = new TaskQueue({ concurrency: 1 });
|
|
21
|
+
this._config = Object.freeze(getConnectionConfig(config));
|
|
22
|
+
this.socket = new PgSocket(this._config);
|
|
23
|
+
this.socket.on('error', err => this._onError(err));
|
|
24
|
+
this.socket.on('close', () => this.emit('close'));
|
|
25
|
+
this.socket.on('notification', payload => this.emit('notification', payload));
|
|
26
|
+
this.socket.on('connecting', () => this.emit('connecting'));
|
|
27
|
+
this._onErrorSavePoint = 'SP_' + Math.round(Math.random() * 100000000);
|
|
28
|
+
}
|
|
29
|
+
get config() {
|
|
30
|
+
return this._config;
|
|
31
|
+
}
|
|
32
|
+
get inTransaction() {
|
|
33
|
+
return this.transactionStatus === 'T' || this.transactionStatus === 'E';
|
|
34
|
+
}
|
|
35
|
+
get state() {
|
|
36
|
+
return this.socket.state;
|
|
37
|
+
}
|
|
38
|
+
get refCount() {
|
|
39
|
+
return this._refCount;
|
|
40
|
+
}
|
|
41
|
+
get processID() {
|
|
42
|
+
return this.socket.processID;
|
|
43
|
+
}
|
|
44
|
+
get secretKey() {
|
|
45
|
+
return this.socket.secretKey;
|
|
46
|
+
}
|
|
47
|
+
get sessionParameters() {
|
|
48
|
+
return this.socket.sessionParameters;
|
|
49
|
+
}
|
|
50
|
+
async connect() {
|
|
51
|
+
if (this.socket.state === ConnectionState.READY)
|
|
52
|
+
return;
|
|
53
|
+
await new Promise((resolve, reject) => {
|
|
54
|
+
const handleConnectError = err => reject(err);
|
|
55
|
+
this.socket.once('ready', () => {
|
|
56
|
+
this.socket.removeListener('error', handleConnectError);
|
|
57
|
+
resolve();
|
|
58
|
+
});
|
|
59
|
+
this.socket.once('error', handleConnectError);
|
|
60
|
+
this.socket.connect();
|
|
61
|
+
});
|
|
62
|
+
let startupCommand = '';
|
|
63
|
+
if (this.config.schema)
|
|
64
|
+
startupCommand += 'SET search_path = ' + escapeLiteral(this.config.schema) + ';';
|
|
65
|
+
if (this.config.timezone)
|
|
66
|
+
startupCommand += 'SET timezone TO ' + escapeLiteral(this.config.timezone) + ';';
|
|
67
|
+
if (startupCommand)
|
|
68
|
+
await this.execute(startupCommand, { autoCommit: true });
|
|
69
|
+
this.emit('ready');
|
|
70
|
+
}
|
|
71
|
+
async close() {
|
|
72
|
+
if (this.state === ConnectionState.CLOSED)
|
|
73
|
+
return;
|
|
74
|
+
this.statementQueue.clearQueue();
|
|
75
|
+
return new Promise(resolve => {
|
|
76
|
+
if (this.socket.state === ConnectionState.CLOSED)
|
|
77
|
+
return;
|
|
78
|
+
this.socket.once('close', resolve);
|
|
79
|
+
this.socket.sendTerminateMessage(() => {
|
|
80
|
+
this.socket.close();
|
|
81
|
+
this.emit('close');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async execute(sql, options, cb) {
|
|
86
|
+
this.assertConnected();
|
|
87
|
+
return this.statementQueue
|
|
88
|
+
.enqueue(async () => {
|
|
89
|
+
const transactionCommand = sql.match(/^(\bBEGIN\b|\bCOMMIT\b|\bSTART\b|\bROLLBACK|SAVEPOINT|RELEASE\b)/i);
|
|
90
|
+
let beginFirst = false;
|
|
91
|
+
let commitLast = false;
|
|
92
|
+
if (!transactionCommand) {
|
|
93
|
+
if (!this.inTransaction &&
|
|
94
|
+
(options?.autoCommit != null ? options?.autoCommit : this.config.autoCommit) === false) {
|
|
95
|
+
beginFirst = true;
|
|
96
|
+
}
|
|
97
|
+
if (this.inTransaction && options?.autoCommit)
|
|
98
|
+
commitLast = true;
|
|
99
|
+
}
|
|
100
|
+
if (beginFirst)
|
|
101
|
+
await this._execute('BEGIN');
|
|
102
|
+
const rollbackOnError = !transactionCommand &&
|
|
103
|
+
(options?.rollbackOnError != null
|
|
104
|
+
? options.rollbackOnError
|
|
105
|
+
: coerceToBoolean(this.config.rollbackOnError, true));
|
|
106
|
+
if (this.inTransaction && rollbackOnError)
|
|
107
|
+
await this._execute('SAVEPOINT ' + this._onErrorSavePoint);
|
|
108
|
+
try {
|
|
109
|
+
const result = await this._execute(sql, options, cb);
|
|
110
|
+
if (commitLast)
|
|
111
|
+
await this._execute('COMMIT');
|
|
112
|
+
else if (this.inTransaction && rollbackOnError)
|
|
113
|
+
await this._execute('RELEASE ' + this._onErrorSavePoint + ';');
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
if (this.inTransaction && rollbackOnError)
|
|
118
|
+
await this._execute('ROLLBACK TO ' + this._onErrorSavePoint + ';');
|
|
119
|
+
throw e;
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
.toPromise();
|
|
123
|
+
}
|
|
124
|
+
async startTransaction() {
|
|
125
|
+
if (!this.inTransaction)
|
|
126
|
+
await this.execute('BEGIN');
|
|
127
|
+
}
|
|
128
|
+
async savepoint(name) {
|
|
129
|
+
if (!(name && name.match(/^[a-zA-Z]\w+$/)))
|
|
130
|
+
throw new Error(`Invalid savepoint "${name}`);
|
|
131
|
+
await this.execute('BEGIN; SAVEPOINT ' + name);
|
|
132
|
+
}
|
|
133
|
+
async commit() {
|
|
134
|
+
if (this.inTransaction)
|
|
135
|
+
await this.execute('COMMIT');
|
|
136
|
+
}
|
|
137
|
+
async rollback() {
|
|
138
|
+
if (this.inTransaction)
|
|
139
|
+
await this.execute('ROLLBACK');
|
|
140
|
+
}
|
|
141
|
+
async rollbackToSavepoint(name) {
|
|
142
|
+
if (!(name && name.match(/^[a-zA-Z]\w+$/)))
|
|
143
|
+
throw new Error(`Invalid savepoint "${name}`);
|
|
144
|
+
await this.execute('ROLLBACK TO SAVEPOINT ' + name, { autoCommit: false });
|
|
145
|
+
}
|
|
146
|
+
async releaseSavepoint(name) {
|
|
147
|
+
if (!(name && name.match(/^[a-zA-Z]\w+$/)))
|
|
148
|
+
throw new Error(`Invalid savepoint "${name}`);
|
|
149
|
+
await this.execute('RELEASE SAVEPOINT ' + name, { autoCommit: false });
|
|
150
|
+
}
|
|
151
|
+
ref() {
|
|
152
|
+
this._refCount++;
|
|
153
|
+
}
|
|
154
|
+
unref() {
|
|
155
|
+
this._refCount--;
|
|
156
|
+
return !this._refCount;
|
|
157
|
+
}
|
|
158
|
+
assertConnected() {
|
|
159
|
+
if (this.state === ConnectionState.CLOSING)
|
|
160
|
+
throw new Error('Connection is closing');
|
|
161
|
+
if (this.state === ConnectionState.CLOSED)
|
|
162
|
+
throw new Error('Connection closed');
|
|
163
|
+
}
|
|
164
|
+
async _execute(sql, options, cb) {
|
|
165
|
+
this.ref();
|
|
166
|
+
try {
|
|
167
|
+
const startTime = Date.now();
|
|
168
|
+
const result = {
|
|
169
|
+
totalCommands: 0,
|
|
170
|
+
totalTime: 0,
|
|
171
|
+
results: [],
|
|
172
|
+
};
|
|
173
|
+
const opts = options || {};
|
|
174
|
+
this.socket.sendQueryMessage(sql);
|
|
175
|
+
let currentStart = Date.now();
|
|
176
|
+
let parsers;
|
|
177
|
+
let current = { command: undefined };
|
|
178
|
+
let fields;
|
|
179
|
+
const typeMap = opts.typeMap || GlobalTypeMap;
|
|
180
|
+
return await this.socket.capture(async (code, msg, done) => {
|
|
181
|
+
switch (code) {
|
|
182
|
+
case Protocol.BackendMessageCode.NoticeResponse:
|
|
183
|
+
case Protocol.BackendMessageCode.CopyInResponse:
|
|
184
|
+
case Protocol.BackendMessageCode.CopyOutResponse:
|
|
185
|
+
case Protocol.BackendMessageCode.EmptyQueryResponse:
|
|
186
|
+
break;
|
|
187
|
+
case Protocol.BackendMessageCode.RowDescription:
|
|
188
|
+
fields = msg.fields;
|
|
189
|
+
parsers = getParsers(typeMap, fields);
|
|
190
|
+
current.fields = wrapRowDescription(typeMap, fields, DataFormat.text);
|
|
191
|
+
current.rows = [];
|
|
192
|
+
break;
|
|
193
|
+
case Protocol.BackendMessageCode.DataRow:
|
|
194
|
+
let row = msg.columns.map((x) => x.toString('utf8'));
|
|
195
|
+
parseRow(parsers, row, opts);
|
|
196
|
+
if (opts.objectRows && current.fields)
|
|
197
|
+
row = convertRowToObject(current.fields, row);
|
|
198
|
+
if (cb)
|
|
199
|
+
cb('row', row);
|
|
200
|
+
current.rows = current.rows || [];
|
|
201
|
+
current.rows.push(row);
|
|
202
|
+
break;
|
|
203
|
+
case Protocol.BackendMessageCode.CommandComplete:
|
|
204
|
+
// Ignore BEGIN command that we added to sql
|
|
205
|
+
current.command = msg.command;
|
|
206
|
+
if (current.command === 'DELETE' || current.command === 'INSERT' || current.command === 'UPDATE')
|
|
207
|
+
current.rowsAffected = msg.rowCount;
|
|
208
|
+
current.executeTime = Date.now() - currentStart;
|
|
209
|
+
if (current.rows)
|
|
210
|
+
current.rowType = opts.objectRows && current.fields ? 'object' : 'array';
|
|
211
|
+
result.results.push(current);
|
|
212
|
+
if (cb)
|
|
213
|
+
cb('command-complete', current);
|
|
214
|
+
current = { command: undefined };
|
|
215
|
+
currentStart = Date.now();
|
|
216
|
+
break;
|
|
217
|
+
case Protocol.BackendMessageCode.ReadyForQuery:
|
|
218
|
+
this.transactionStatus = msg.status;
|
|
219
|
+
result.totalTime = Date.now() - startTime;
|
|
220
|
+
// Ignore COMMIT command that we added to sql
|
|
221
|
+
result.totalCommands = result.results.length;
|
|
222
|
+
done(undefined, result);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
this.unref();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
_onError(err) {
|
|
231
|
+
if (this.socket.state !== ConnectionState.READY)
|
|
232
|
+
return;
|
|
233
|
+
this.emit('error', err);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
export function getIntlConnection(connection) {
|
|
237
|
+
return connection._intlCon;
|
|
238
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { Pool as LightningPool } from 'lightning-pool';
|
|
2
|
+
import { coerceToBoolean, coerceToInt } from 'putil-varhelpers';
|
|
3
|
+
import { ConnectionState } from '../constants.js';
|
|
4
|
+
import { SafeEventEmitter } from '../safe-event-emitter.js';
|
|
5
|
+
import { getConnectionConfig } from '../util/connection-config.js';
|
|
6
|
+
import { Connection } from './connection.js';
|
|
7
|
+
import { getIntlConnection, IntlConnection } from './intl-connection.js';
|
|
8
|
+
export class Pool extends SafeEventEmitter {
|
|
9
|
+
constructor(config) {
|
|
10
|
+
super();
|
|
11
|
+
this._notificationListeners = new SafeEventEmitter();
|
|
12
|
+
const cfg = getConnectionConfig(config);
|
|
13
|
+
this.config = Object.freeze(cfg);
|
|
14
|
+
const poolOptions = {};
|
|
15
|
+
poolOptions.acquireMaxRetries = coerceToInt(cfg.acquireMaxRetries, 0);
|
|
16
|
+
poolOptions.acquireRetryWait = coerceToInt(cfg.acquireRetryWait, 2000);
|
|
17
|
+
poolOptions.acquireTimeoutMillis = coerceToInt(cfg.acquireTimeoutMillis, 0);
|
|
18
|
+
poolOptions.idleTimeoutMillis = coerceToInt(cfg.idleTimeoutMillis, 30000);
|
|
19
|
+
poolOptions.max = coerceToInt(cfg.max, 10);
|
|
20
|
+
poolOptions.maxQueue = coerceToInt(cfg.maxQueue, 1000);
|
|
21
|
+
poolOptions.max = coerceToInt(cfg.max, 10);
|
|
22
|
+
poolOptions.min = coerceToInt(cfg.min, 0);
|
|
23
|
+
poolOptions.minIdle = coerceToInt(cfg.minIdle, 0);
|
|
24
|
+
poolOptions.validation = coerceToBoolean(cfg.validation, false);
|
|
25
|
+
const poolFactory = {
|
|
26
|
+
create: async () => {
|
|
27
|
+
/* istanbul ignore next */
|
|
28
|
+
if (this.listenerCount('debug'))
|
|
29
|
+
this.emit('debug', {
|
|
30
|
+
location: 'Pool.factory.create',
|
|
31
|
+
pool: this,
|
|
32
|
+
message: `new connection creating`,
|
|
33
|
+
});
|
|
34
|
+
const intlCon = new IntlConnection(cfg);
|
|
35
|
+
await intlCon.connect();
|
|
36
|
+
intlCon.on('close', () => this._pool.destroy(intlCon));
|
|
37
|
+
/* istanbul ignore next */
|
|
38
|
+
if (this.listenerCount('debug'))
|
|
39
|
+
this.emit('debug', {
|
|
40
|
+
location: 'Pool.factory.create',
|
|
41
|
+
pool: this,
|
|
42
|
+
message: `[${intlCon.processID}] connection created`,
|
|
43
|
+
});
|
|
44
|
+
return intlCon;
|
|
45
|
+
},
|
|
46
|
+
destroy: intlCon => {
|
|
47
|
+
/* istanbul ignore next */
|
|
48
|
+
if (this.listenerCount('debug'))
|
|
49
|
+
this.emit('debug', {
|
|
50
|
+
location: 'Pool.factory.destroy',
|
|
51
|
+
pool: this,
|
|
52
|
+
message: `[${intlCon.processID}] connection destroy`,
|
|
53
|
+
});
|
|
54
|
+
return intlCon.close();
|
|
55
|
+
},
|
|
56
|
+
reset: async (intlCon) => {
|
|
57
|
+
/* istanbul ignore next */
|
|
58
|
+
if (this.listenerCount('debug'))
|
|
59
|
+
this.emit('debug', {
|
|
60
|
+
location: 'Pool.factory.reset',
|
|
61
|
+
pool: this,
|
|
62
|
+
message: `[${intlCon.processID}] connection reset`,
|
|
63
|
+
});
|
|
64
|
+
try {
|
|
65
|
+
if (intlCon.state === ConnectionState.READY) {
|
|
66
|
+
await intlCon.execute('ROLLBACK;UNLISTEN *');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
intlCon.removeAllListeners();
|
|
71
|
+
intlCon.once('close', () => this._pool.destroy(intlCon));
|
|
72
|
+
intlCon._refCount = 0;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
validate: async (intlCon) => {
|
|
76
|
+
/* istanbul ignore next */
|
|
77
|
+
if (this.listenerCount('debug'))
|
|
78
|
+
this.emit('debug', {
|
|
79
|
+
location: 'Pool.factory.validate',
|
|
80
|
+
pool: this,
|
|
81
|
+
message: `[${intlCon.processID}] connection validate`,
|
|
82
|
+
});
|
|
83
|
+
if (intlCon.state !== ConnectionState.READY)
|
|
84
|
+
throw new Error('Connection is not active');
|
|
85
|
+
await intlCon.execute('select 1;');
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
this._pool = new LightningPool(poolFactory, poolOptions);
|
|
89
|
+
this._pool.on('return', (...args) => this.emit('release', ...args));
|
|
90
|
+
this._pool.on('error', (...args) => this.emit('error', ...args));
|
|
91
|
+
this._pool.on('acquire', (...args) => this.emit('acquire', ...args));
|
|
92
|
+
this._pool.on('destroy', (...args) => this.emit('destroy', ...args));
|
|
93
|
+
this._pool.start();
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Returns number of connections that are currently acquired
|
|
97
|
+
*/
|
|
98
|
+
get acquiredConnections() {
|
|
99
|
+
return this._pool.acquired;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Returns number of unused connections in the pool
|
|
103
|
+
*/
|
|
104
|
+
get idleConnections() {
|
|
105
|
+
return this._pool.available;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Returns total number of connections in the pool regardless of whether they are idle or in use
|
|
109
|
+
*/
|
|
110
|
+
get totalConnections() {
|
|
111
|
+
return this._pool.size;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Obtains a connection from the connection pool
|
|
115
|
+
*/
|
|
116
|
+
async acquire() {
|
|
117
|
+
const intlCon = await this._pool.acquire();
|
|
118
|
+
/* istanbul ignore next */
|
|
119
|
+
if (this.listenerCount('debug'))
|
|
120
|
+
this.emit('debug', {
|
|
121
|
+
location: 'Pool.acquire',
|
|
122
|
+
pool: this,
|
|
123
|
+
message: `[${intlCon.processID}] acquired`,
|
|
124
|
+
});
|
|
125
|
+
const connection = new Connection(this, intlCon);
|
|
126
|
+
/* istanbul ignore next */
|
|
127
|
+
if (this.listenerCount('debug'))
|
|
128
|
+
connection.on('debug', (...args) => this.emit('debug', ...args));
|
|
129
|
+
return connection;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Shuts down the pool and destroys all resources.
|
|
133
|
+
*/
|
|
134
|
+
async close(terminateWait) {
|
|
135
|
+
this._notificationListeners.removeAllListeners();
|
|
136
|
+
await this._notificationConnection?.close(terminateWait);
|
|
137
|
+
const ms = terminateWait == null ? 10000 : terminateWait;
|
|
138
|
+
return this._pool.closeAsync(ms);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Executes a script
|
|
142
|
+
*/
|
|
143
|
+
async execute(sql, options) {
|
|
144
|
+
const connection = await this.acquire();
|
|
145
|
+
try {
|
|
146
|
+
return await connection.execute(sql, options);
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
await this.release(connection);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Executes a query
|
|
154
|
+
*/
|
|
155
|
+
async query(sql, options) {
|
|
156
|
+
const connection = await this.acquire();
|
|
157
|
+
try {
|
|
158
|
+
return await connection.query(sql, options);
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
await this.release(connection);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async prepare(sql, options) {
|
|
165
|
+
const connection = await this.acquire();
|
|
166
|
+
const statement = await connection.prepare(sql, options);
|
|
167
|
+
statement.once('close', () => this._pool.release(getIntlConnection(connection)));
|
|
168
|
+
return statement;
|
|
169
|
+
}
|
|
170
|
+
release(connection) {
|
|
171
|
+
return this._pool.releaseAsync(getIntlConnection(connection));
|
|
172
|
+
}
|
|
173
|
+
async listen(channel, callback) {
|
|
174
|
+
if (!/^[A-Z]\w+$/i.test(channel))
|
|
175
|
+
throw new TypeError(`Invalid channel name`);
|
|
176
|
+
this._notificationListeners.on(channel, callback);
|
|
177
|
+
await this._initNotificationConnection();
|
|
178
|
+
}
|
|
179
|
+
async unListen(channel) {
|
|
180
|
+
if (!/^[A-Z]\w+$/i.test(channel))
|
|
181
|
+
throw new TypeError(`Invalid channel name`);
|
|
182
|
+
this._notificationListeners.removeAllListeners(channel);
|
|
183
|
+
if (!this._notificationListeners.eventNames().length) {
|
|
184
|
+
await this.unListenAll();
|
|
185
|
+
}
|
|
186
|
+
else if (this._notificationConnection)
|
|
187
|
+
await this._notificationConnection.unListen(channel);
|
|
188
|
+
}
|
|
189
|
+
async unListenAll() {
|
|
190
|
+
this._notificationListeners.removeAllListeners();
|
|
191
|
+
if (this._notificationConnection) {
|
|
192
|
+
const conn = this._notificationConnection;
|
|
193
|
+
this._notificationConnection = undefined;
|
|
194
|
+
await conn.close();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async _initNotificationConnection() {
|
|
198
|
+
if (this._notificationConnection)
|
|
199
|
+
return;
|
|
200
|
+
const conn = (this._notificationConnection = new Connection(this.config));
|
|
201
|
+
// Reconnect on connection lost
|
|
202
|
+
conn.on('close', () => reConnect());
|
|
203
|
+
const registerEvents = async () => {
|
|
204
|
+
const channels = this._notificationListeners.eventNames();
|
|
205
|
+
for (const channel of channels) {
|
|
206
|
+
const fns = this._notificationListeners.listeners(channel);
|
|
207
|
+
for (const fn of fns) {
|
|
208
|
+
await conn.listen(channel, fn);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
const reConnect = async () => {
|
|
213
|
+
setTimeout(() => {
|
|
214
|
+
if (!this._notificationListeners.eventNames().length)
|
|
215
|
+
return;
|
|
216
|
+
conn.connect().catch(() => reConnect());
|
|
217
|
+
}, 500).unref();
|
|
218
|
+
};
|
|
219
|
+
await conn.connect();
|
|
220
|
+
await registerEvents();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { DEFAULT_COLUMN_FORMAT } from '../constants.js';
|
|
2
|
+
import { GlobalTypeMap } from '../data-type-map.js';
|
|
3
|
+
import { Protocol } from '../protocol/protocol.js';
|
|
4
|
+
import { getIntlConnection } from './intl-connection.js';
|
|
5
|
+
export class Portal {
|
|
6
|
+
constructor(statement, name) {
|
|
7
|
+
this._columnFormat = DEFAULT_COLUMN_FORMAT;
|
|
8
|
+
this._statement = statement;
|
|
9
|
+
this._name = name;
|
|
10
|
+
}
|
|
11
|
+
get connection() {
|
|
12
|
+
return this._statement.connection;
|
|
13
|
+
}
|
|
14
|
+
get name() {
|
|
15
|
+
return this._name;
|
|
16
|
+
}
|
|
17
|
+
async bind(params, queryOptions) {
|
|
18
|
+
const intoCon = getIntlConnection(this.connection);
|
|
19
|
+
intoCon.ref();
|
|
20
|
+
try {
|
|
21
|
+
const socket = intoCon.socket;
|
|
22
|
+
this._columnFormat = queryOptions.columnFormat != null ? queryOptions.columnFormat : Protocol.DataFormat.binary;
|
|
23
|
+
socket.sendBindMessage({
|
|
24
|
+
typeMap: queryOptions.typeMap || GlobalTypeMap,
|
|
25
|
+
statement: this._statement.name,
|
|
26
|
+
portal: this.name,
|
|
27
|
+
paramTypes: this._statement.paramTypes,
|
|
28
|
+
params,
|
|
29
|
+
queryOptions,
|
|
30
|
+
});
|
|
31
|
+
socket.sendFlushMessage();
|
|
32
|
+
return await socket.capture(async (code, msg, done) => {
|
|
33
|
+
switch (code) {
|
|
34
|
+
case Protocol.BackendMessageCode.BindComplete:
|
|
35
|
+
done();
|
|
36
|
+
break;
|
|
37
|
+
case Protocol.BackendMessageCode.NoticeResponse:
|
|
38
|
+
break;
|
|
39
|
+
default:
|
|
40
|
+
done(new Error(`Server returned unexpected response message (${String.fromCharCode(code)})`));
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
intoCon.unref();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async retrieveFields() {
|
|
49
|
+
const intoCon = getIntlConnection(this.connection);
|
|
50
|
+
intoCon.ref();
|
|
51
|
+
try {
|
|
52
|
+
const socket = intoCon.socket;
|
|
53
|
+
socket.sendDescribeMessage({ type: 'P', name: this.name });
|
|
54
|
+
socket.sendFlushMessage();
|
|
55
|
+
return await socket.capture(async (code, msg, done) => {
|
|
56
|
+
switch (code) {
|
|
57
|
+
case Protocol.BackendMessageCode.NoticeResponse:
|
|
58
|
+
break;
|
|
59
|
+
case Protocol.BackendMessageCode.NoData:
|
|
60
|
+
done();
|
|
61
|
+
break;
|
|
62
|
+
case Protocol.BackendMessageCode.RowDescription:
|
|
63
|
+
done(undefined, msg.fields);
|
|
64
|
+
break;
|
|
65
|
+
default:
|
|
66
|
+
done(new Error(`Server returned unexpected response message (${String.fromCharCode(code)})`));
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
intoCon.unref();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async execute(fetchCount) {
|
|
75
|
+
const intoCon = getIntlConnection(this.connection);
|
|
76
|
+
intoCon.ref();
|
|
77
|
+
try {
|
|
78
|
+
const socket = intoCon.socket;
|
|
79
|
+
socket.sendExecuteMessage({ portal: this.name, fetchCount: fetchCount || 100 });
|
|
80
|
+
socket.sendFlushMessage();
|
|
81
|
+
const rows = [];
|
|
82
|
+
return await socket.capture(async (code, msg, done) => {
|
|
83
|
+
switch (code) {
|
|
84
|
+
case Protocol.BackendMessageCode.NoticeResponse:
|
|
85
|
+
break;
|
|
86
|
+
case Protocol.BackendMessageCode.NoData:
|
|
87
|
+
done(undefined, { code });
|
|
88
|
+
break;
|
|
89
|
+
case Protocol.BackendMessageCode.DataRow:
|
|
90
|
+
if (Array.isArray(this._columnFormat)) {
|
|
91
|
+
rows.push(msg.columns.map((buf, i) => this._columnFormat[i] === Protocol.DataFormat.text ? buf.toString('utf8') : buf));
|
|
92
|
+
}
|
|
93
|
+
else if (this._columnFormat === Protocol.DataFormat.binary)
|
|
94
|
+
rows.push(msg.columns);
|
|
95
|
+
else
|
|
96
|
+
rows.push(msg.columns.map((buf) => buf.toString('utf8')));
|
|
97
|
+
break;
|
|
98
|
+
case Protocol.BackendMessageCode.PortalSuspended:
|
|
99
|
+
done(undefined, { code, rows });
|
|
100
|
+
break;
|
|
101
|
+
case Protocol.BackendMessageCode.CommandComplete:
|
|
102
|
+
done(undefined, {
|
|
103
|
+
code,
|
|
104
|
+
rows,
|
|
105
|
+
command: msg.command,
|
|
106
|
+
rowCount: msg.rowCount,
|
|
107
|
+
});
|
|
108
|
+
break;
|
|
109
|
+
default:
|
|
110
|
+
done(new Error(`Server returned unexpected response message (${String.fromCharCode(code)})`));
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
intoCon.unref();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async close() {
|
|
119
|
+
const intoCon = getIntlConnection(this.connection);
|
|
120
|
+
intoCon.ref();
|
|
121
|
+
try {
|
|
122
|
+
const socket = intoCon.socket;
|
|
123
|
+
socket.sendCloseMessage({ type: 'P', name: this.name });
|
|
124
|
+
socket.sendSyncMessage();
|
|
125
|
+
return await socket.capture(async (code, msg, done) => {
|
|
126
|
+
switch (code) {
|
|
127
|
+
case Protocol.BackendMessageCode.NoticeResponse:
|
|
128
|
+
break;
|
|
129
|
+
case Protocol.BackendMessageCode.CloseComplete:
|
|
130
|
+
break;
|
|
131
|
+
case Protocol.BackendMessageCode.ReadyForQuery:
|
|
132
|
+
intoCon.transactionStatus = msg.status;
|
|
133
|
+
done();
|
|
134
|
+
break;
|
|
135
|
+
default:
|
|
136
|
+
done(new Error(`Server returned unexpected response message (${String.fromCharCode(code)})`));
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
intoCon.unref();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|