vv-db-connection 0.0.0-stage → 1.0.1
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/LICENSE +21 -0
- package/README.md +193 -2
- package/distjs/src/db/adapters/fb.d.ts +14 -0
- package/distjs/src/db/adapters/fb.js +361 -0
- package/distjs/src/db/adapters/fb.js.map +1 -0
- package/distjs/src/db/adapters/mssql.d.ts +14 -0
- package/distjs/src/db/adapters/mssql.js +155 -0
- package/distjs/src/db/adapters/mssql.js.map +1 -0
- package/distjs/src/db/adapters/ora.d.ts +15 -0
- package/distjs/src/db/adapters/ora.js +137 -0
- package/distjs/src/db/adapters/ora.js.map +1 -0
- package/distjs/src/db/adapters/pg.d.ts +16 -0
- package/distjs/src/db/adapters/pg.js +153 -0
- package/distjs/src/db/adapters/pg.js.map +1 -0
- package/distjs/src/db/connection.d.ts +49 -0
- package/distjs/src/db/connection.js +5 -0
- package/distjs/src/db/connection.js.map +1 -0
- package/distjs/src/db/index.d.ts +16 -0
- package/distjs/src/db/index.js +131 -0
- package/distjs/src/db/index.js.map +1 -0
- package/distjs/src/db/kind.d.ts +2 -0
- package/distjs/src/db/kind.js +2 -0
- package/distjs/src/db/kind.js.map +1 -0
- package/distjs/src/db/placeholders.d.ts +1 -0
- package/distjs/src/db/placeholders.js +17 -0
- package/distjs/src/db/placeholders.js.map +1 -0
- package/distjs/src/db/types.d.ts +40 -0
- package/distjs/src/db/types.js +2 -0
- package/distjs/src/db/types.js.map +1 -0
- package/distjs/src/index.d.ts +4 -0
- package/distjs/src/index.js +2 -0
- package/distjs/src/index.js.map +1 -0
- package/distjs/src/tresult.d.ts +7 -0
- package/distjs/src/tresult.js +2 -0
- package/distjs/src/tresult.js.map +1 -0
- package/package.json +52 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vitalii Vasilev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,194 @@
|
|
|
1
|
-
#
|
|
1
|
+
# vv-db-connection
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A single connection layer over **MSSQL**, **Oracle**, **PostgreSQL** and **Firebird**. One `createDbConnection` factory, one unified query interface — the rest of the application never touches driver-specific code.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm i vv-db-connection
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Supported databases
|
|
10
|
+
|
|
11
|
+
| `kind` | Driver | Default port |
|
|
12
|
+
|-----------|-----------------|--------------|
|
|
13
|
+
| `mssql` | `mssql` | 1433 |
|
|
14
|
+
| `pg` | `pg` | 5432 |
|
|
15
|
+
| `ora` | `oracledb` | 1521 |
|
|
16
|
+
| `fb` | `node-firebird` | 3050 |
|
|
17
|
+
|
|
18
|
+
## Initialization
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { createDbConnection } from 'vv-db-connection'
|
|
22
|
+
import type { TConnectionParam } from 'vv-db-connection'
|
|
23
|
+
|
|
24
|
+
const connection: TConnectionParam = {
|
|
25
|
+
kind: 'pg',
|
|
26
|
+
host: 'localhost',
|
|
27
|
+
port: 5432,
|
|
28
|
+
database: 'app',
|
|
29
|
+
user: 'admin',
|
|
30
|
+
password: 'secret',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const db = createDbConnection(connection)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`createDbConnection(connection)` picks the adapter from `connection.kind` and returns a `DbConnection`. The connection is passed as a plain object — its schema and types are owned by this package. Create as many connections as you need, in parallel, each with its own driver and pool:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const src = createDbConnection(mssqlConnection) // source
|
|
40
|
+
const dst = createDbConnection(pgConnection) // target
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The connection is opened lazily — `createDbConnection` only holds the config; the first `exec`/`ping`/`execStream` call opens it.
|
|
44
|
+
|
|
45
|
+
### Connection fields
|
|
46
|
+
|
|
47
|
+
| `kind` | Type | Fields |
|
|
48
|
+
|-----------|--------------|-------------------------------------------------------------------------------|
|
|
49
|
+
| `mssql` | `TConnMssql` | `server`, `port?`, `database?`, `auth`, `trustServerCertificate?`, `encrypt?` |
|
|
50
|
+
| `pg` | `TConnPg` | `host`, `port?`, `database`, `user`, `password`, `ssl?` |
|
|
51
|
+
| `ora` | `TConnOra` | `host`, `port?`, `service`, `user`, `password`, `privilege?` |
|
|
52
|
+
| `fb` | `TConnFb` | `host`, `port?`, `database`, `user`, `password`, `charset?` |
|
|
53
|
+
|
|
54
|
+
MSSQL `auth` is a union: `{ kind: 'mssql', user, password }` or `{ kind: 'ntlm', domain, user, password }`.
|
|
55
|
+
|
|
56
|
+
Oracle `privilege` is one of `SYSDBA | SYSOPER | SYSASM | SYSBACKUP | SYSDG | SYSKM | SYSRAC`; the connection string is assembled internally as `host:port/service`.
|
|
57
|
+
|
|
58
|
+
Firebird `charset` is `'UTF8'` (default) or `'WIN1251'`.
|
|
59
|
+
|
|
60
|
+
## API
|
|
61
|
+
|
|
62
|
+
### `exec` — buffered query
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const result = await db.exec('SELECT * FROM orders WHERE status = :status', [
|
|
66
|
+
{ name: 'status', value: 'open' },
|
|
67
|
+
])
|
|
68
|
+
|
|
69
|
+
if (!result.ok) {
|
|
70
|
+
console.error(result.error)
|
|
71
|
+
} else {
|
|
72
|
+
for (const resultSet of result.result) {
|
|
73
|
+
console.log(resultSet.schema) // TDbCol[]
|
|
74
|
+
console.log(resultSet.rows) // object[]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Collects all rows into memory and returns `TResult<TDbRes[]>`. Each element of the array is one result set (relevant for multi-statement queries on MSSQL).
|
|
80
|
+
|
|
81
|
+
### `execStream` — streaming query
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
await db.execStream(query, params, (event) => {
|
|
85
|
+
if (event.kind === 'schema') { /* event.schema: TDbCol[] */ }
|
|
86
|
+
if (event.kind === 'row') { /* event.row: object */ }
|
|
87
|
+
if (event.kind === 'finish') { /* event.error?: string */ }
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Fires the callback incrementally as data arrives — for large result sets where buffering everything is undesirable. Events are always emitted in this order: `schema` → `row` × N → `finish`.
|
|
92
|
+
|
|
93
|
+
A query that goes silent is cut off on every database: no data within 12 hours after the query was sent, or no new data within 12 hours after the previous portion, ends it with `finish` carrying a `timeout: ...` error, and the call returns at once. The adapter is told to drop its connection (an `AbortSignal`) and is not waited for, so a driver stuck in its own teardown cannot hold the caller. `exec` goes through the same path and has the same limits.
|
|
94
|
+
|
|
95
|
+
### `execTxDynamic` — transaction
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const result = await db.execTxDynamic(async (exec) => {
|
|
99
|
+
await exec('INSERT INTO t (id) VALUES (:id)', [{ name: 'id', value: 1 }])
|
|
100
|
+
const res = await exec('SELECT MAX(id) AS id FROM t', [])
|
|
101
|
+
// res.rows: Record<string, unknown>[]
|
|
102
|
+
}, timeoutMs)
|
|
103
|
+
|
|
104
|
+
// result: TResult<void>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Opens a transaction, hands the callback an `exec` bound to it, and commits once the callback resolves. Anything thrown inside rolls back and comes out as `{ ok: false, error }`. Statements use the same `:name` parameters, and every call returns `{ rows }`, so a value generated inside the transaction can be read back before commit.
|
|
108
|
+
|
|
109
|
+
`timeoutMs` is optional (`0` or omitted — no limit). On expiry the connection is torn down and the call resolves as `{ ok: false, error: 'TIMEOUT' }`.
|
|
110
|
+
|
|
111
|
+
A dedicated connection is opened for the transaction and closed afterwards, on the commit and rollback paths alike. Firebird runs with `READ COMMITTED` isolation; the other adapters use the driver default.
|
|
112
|
+
|
|
113
|
+
### `ping`
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const result = await db.ping() // TResult<null>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
A minimal read-only query that requires no privileges; returns `{ ok: true }` on success. Useful for waiting for the database to come up.
|
|
120
|
+
|
|
121
|
+
### `version`
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
const result = await db.version() // TResult<string>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Queries the server version string.
|
|
128
|
+
|
|
129
|
+
### `kind` / `title` / `user`
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
db.kind() // 'pg' | 'mssql' | 'ora' | 'fb'
|
|
133
|
+
db.title() // e.g. "localhost:5432 [mydb]"
|
|
134
|
+
db.user() // e.g. "admin"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`kind` is the discriminator of the active connection (`TDbKind`); `title` and `user` are a human-readable description for log output.
|
|
138
|
+
|
|
139
|
+
## Query parameters
|
|
140
|
+
|
|
141
|
+
All methods accept named parameters in `:name` syntax, regardless of the underlying driver. Each adapter translates them to the driver's native format:
|
|
142
|
+
|
|
143
|
+
| Driver | Native format | Notes |
|
|
144
|
+
|------------|--------------------------------------|-------|
|
|
145
|
+
| MSSQL | `@name` (mssql `request.input`) | `:name` converted to `@name` |
|
|
146
|
+
| PostgreSQL | `$1, $2, …` | same `:name` used multiple times maps to the same `$N` |
|
|
147
|
+
| Oracle | `:name` (native bind) | passed as a named bind object |
|
|
148
|
+
| Firebird | `?` (positional) | replaced in left-to-right order of appearance |
|
|
149
|
+
|
|
150
|
+
In Firebird a `:name` inside a string literal, a quoted identifier or a comment is left as it is. In an `EXECUTE BLOCK` only the header — everything up to the `AS` that closes it — is treated as placeholders; past that begins PSQL, where `:name` refers to a local variable or a block parameter and is passed through untouched.
|
|
151
|
+
|
|
152
|
+
## Data types
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
type TDbCol = {
|
|
156
|
+
name: string
|
|
157
|
+
kind: string // driver-specific type name
|
|
158
|
+
len?: number
|
|
159
|
+
scale?: number
|
|
160
|
+
precision?: number
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
type TDbRes = {
|
|
164
|
+
schema: TDbCol[]
|
|
165
|
+
rows: object[]
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
type TDbStreamEvent =
|
|
169
|
+
| { kind: 'schema'; schema: TDbCol[] }
|
|
170
|
+
| { kind: 'row'; row: object }
|
|
171
|
+
| { kind: 'finish'; error?: string }
|
|
172
|
+
|
|
173
|
+
type TDbTxResult = { rows: Record<string, unknown>[] }
|
|
174
|
+
type TDbTxExec = (query: string, params: TDbParam[]) => Promise<TDbTxResult>
|
|
175
|
+
type TDbTxCb = (exec: TDbTxExec) => Promise<void>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Unlike `exec`, a statement inside a transaction returns a single set of rows — no schema and no multi-result-set handling.
|
|
179
|
+
|
|
180
|
+
## Adapter notes
|
|
181
|
+
|
|
182
|
+
**MSSQL** — `request.stream = true`, rows delivered via events without driver-side buffering. A new `ConnectionPool` is created per query and closed in the `done` handler.
|
|
183
|
+
|
|
184
|
+
**PostgreSQL** — non-streaming `client.query()` (buffered inside `pg`). A fresh `Client` per query, disconnected in a `finally` block. A backend does not notice its client is gone until it has something to send, so an aborted query is cancelled from a connection of its own (`pg_cancel_backend`).
|
|
185
|
+
|
|
186
|
+
**Oracle** — `conn.queryStream()` with `OUT_FORMAT_OBJECT`, rows arrive as plain objects. A connection is obtained from the default pool and closed in a `finally` block.
|
|
187
|
+
|
|
188
|
+
**Firebird** — `node-firebird` has no streaming API; `db.sequentially()` calls the row callback once per row without loading the full result into memory. Column schema is inferred from the keys of the first row (types reported as `"unknown"`). Text blobs are fetched as text (`blobAsText`); binary blobs (`SUB_TYPE 0`) are not supported. With `charset: WIN1251` blob segments are re-encoded on the way in, because the driver decodes them with a hardcoded UTF8. The blobs of one fetch batch are read one after another, not all at once as the driver does, and rows are handed over batch by batch instead of being collected into an array first. A lost connection ends the query with `fb: connection lost`: on a closed socket the driver would quietly attach anew and leave the read waiting for an answer that never comes, so that reconnect is cancelled and the driver's `error` / `reconnect` events are taken as the same loss.
|
|
189
|
+
|
|
190
|
+
The Firebird adapter patches several `node-firebird` internals (its wire protocol layer) to work around bugs with Firebird 6 — see the note block in `src/db/adapters/fb.ts` for the full rationale.
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { IDbAdapter, TDbParam, TDbStreamEvent, TDbTxCb } from '../types.js';
|
|
2
|
+
import type { TConnFb } from '../connection.js';
|
|
3
|
+
import type { TResult } from '../../tresult.js';
|
|
4
|
+
export declare class FbAdapter implements IDbAdapter {
|
|
5
|
+
private readonly p;
|
|
6
|
+
constructor(p: TConnFb);
|
|
7
|
+
kind(): 'fb';
|
|
8
|
+
title(): string;
|
|
9
|
+
user(): string;
|
|
10
|
+
versionQuery(): string;
|
|
11
|
+
pingQuery(): string;
|
|
12
|
+
execStream(query: string, params: TDbParam[], callback: (event: TDbStreamEvent) => void, signal?: AbortSignal): Promise<void>;
|
|
13
|
+
execTxDynamic(cb: TDbTxCb, timeoutMs?: number): Promise<TResult<void>>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import Firebird from 'node-firebird';
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
// Windows-1251 → Unicode code point map for bytes 0x80–0xFF
|
|
4
|
+
const WIN1251 = [
|
|
5
|
+
0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021, // 0x80-0x87
|
|
6
|
+
0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F, // 0x88-0x8F
|
|
7
|
+
0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, // 0x90-0x97
|
|
8
|
+
0xFFFD, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F, // 0x98-0x9F
|
|
9
|
+
0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7, // 0xA0-0xA7
|
|
10
|
+
0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407, // 0xA8-0xAF
|
|
11
|
+
0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7, // 0xB0-0xB7
|
|
12
|
+
0x0451, 0x2116, 0x0454, 0x00BB, 0x0458, 0x0405, 0x0455, 0x0457, // 0xB8-0xBF
|
|
13
|
+
0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, // 0xC0-0xC7
|
|
14
|
+
0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, // 0xC8-0xCF
|
|
15
|
+
0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, // 0xD0-0xD7
|
|
16
|
+
0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, // 0xD8-0xDF
|
|
17
|
+
0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, // 0xE0-0xE7
|
|
18
|
+
0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, // 0xE8-0xEF
|
|
19
|
+
0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, // 0xF0-0xF7
|
|
20
|
+
0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, // 0xF8-0xFF
|
|
21
|
+
];
|
|
22
|
+
function win1251Decode(buf) {
|
|
23
|
+
let s = '';
|
|
24
|
+
for (let i = 0; i < buf.length; i++) {
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
26
|
+
const b = buf[i];
|
|
27
|
+
s += String.fromCodePoint(b < 0x80 ? b : WIN1251[b - 0x80]);
|
|
28
|
+
}
|
|
29
|
+
return s;
|
|
30
|
+
}
|
|
31
|
+
// Module-level charset state — set once per adapter init, used by XdrReader patch below
|
|
32
|
+
let fbDecodeCharset = 'UTF8';
|
|
33
|
+
(() => {
|
|
34
|
+
const _require = createRequire(import.meta.url);
|
|
35
|
+
const FbWireConn = _require('node-firebird/lib/wire/connection');
|
|
36
|
+
const FbConst = _require('node-firebird/lib/wire/const');
|
|
37
|
+
FbWireConn.prototype.closeBlob = function (blob, callback) {
|
|
38
|
+
const msg = this._msg;
|
|
39
|
+
msg.pos = 0;
|
|
40
|
+
msg.addInt(FbConst.op_close_blob);
|
|
41
|
+
msg.addInt(blob.handle);
|
|
42
|
+
this._queueEvent(callback); // no defer — op_close_blob response must be consumed
|
|
43
|
+
};
|
|
44
|
+
const FbSerialize = _require('node-firebird/lib/wire/serialize');
|
|
45
|
+
FbSerialize.XdrReader.prototype.readText = function (len, _encoding) {
|
|
46
|
+
if (len <= 0)
|
|
47
|
+
return '';
|
|
48
|
+
const bytes = this.buffer.slice(this.pos, this.pos + len);
|
|
49
|
+
this.pos += (len + 3) & ~3;
|
|
50
|
+
return fbDecodeCharset === 'WIN1251' ? win1251Decode(bytes) : bytes.toString('utf8');
|
|
51
|
+
};
|
|
52
|
+
const readSegment = FbSerialize.BlrReader.prototype.readSegment;
|
|
53
|
+
FbSerialize.BlrReader.prototype.readSegment = function () {
|
|
54
|
+
const segment = readSegment.call(this);
|
|
55
|
+
if (fbDecodeCharset !== 'WIN1251' || !Buffer.isBuffer(segment))
|
|
56
|
+
return segment;
|
|
57
|
+
return Buffer.from(win1251Decode(segment), 'utf8');
|
|
58
|
+
};
|
|
59
|
+
// fetchAll patch, see the note above the block for why it is here.
|
|
60
|
+
FbWireConn.prototype.fetchAll = function (statement, transaction, callback) {
|
|
61
|
+
const self = this;
|
|
62
|
+
const asStream = Boolean(statement.custom?.asStream && statement.custom?.on);
|
|
63
|
+
const all = [];
|
|
64
|
+
let index = 0;
|
|
65
|
+
const next = () => self.fetch(statement, transaction, FbConst.DEFAULT_FETCHSIZE, loop);
|
|
66
|
+
const loop = (err, ret) => {
|
|
67
|
+
if (err) {
|
|
68
|
+
callback(err);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!ret?.data?.length) {
|
|
72
|
+
if (ret?.fetched)
|
|
73
|
+
callback(undefined, all);
|
|
74
|
+
else
|
|
75
|
+
next();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
void (async () => {
|
|
79
|
+
try {
|
|
80
|
+
for (const readBlob of (ret.arrBlob ?? [])) {
|
|
81
|
+
const blob = await readBlob(transaction);
|
|
82
|
+
ret.data[blob.row][blob.column] = blob.value;
|
|
83
|
+
}
|
|
84
|
+
for (const row of ret.data) {
|
|
85
|
+
if (asStream)
|
|
86
|
+
statement.custom.on(row, index++);
|
|
87
|
+
else
|
|
88
|
+
all.push(row);
|
|
89
|
+
}
|
|
90
|
+
if (ret.fetched)
|
|
91
|
+
callback(undefined, all);
|
|
92
|
+
else
|
|
93
|
+
next();
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
callback(e);
|
|
97
|
+
}
|
|
98
|
+
})();
|
|
99
|
+
};
|
|
100
|
+
next();
|
|
101
|
+
};
|
|
102
|
+
})();
|
|
103
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
104
|
+
// A string literal, a quoted identifier, a line or a block comment - the places where
|
|
105
|
+
// what looks like a placeholder is just text. Everything else is code.
|
|
106
|
+
const FB_LITERAL = /'(?:[^']|'')*'|"(?:[^"]|"")*"|--[^\n]*|\/\*[\s\S]*?\*\//;
|
|
107
|
+
// Index of the AS that closes the header of an EXECUTE BLOCK, or -1. Both lists the
|
|
108
|
+
// header may carry - the input parameters and RETURNS - are parenthesized, so the AS
|
|
109
|
+
// we want is the first one outside any parentheses, and outside literals and comments.
|
|
110
|
+
function execBlockBodyAt(query) {
|
|
111
|
+
if (!/^\s*EXECUTE\s+BLOCK\b/i.test(query))
|
|
112
|
+
return -1;
|
|
113
|
+
const scan = new RegExp(`${FB_LITERAL.source}|[()]|\\bAS\\b`, 'gi');
|
|
114
|
+
let depth = 0;
|
|
115
|
+
for (let m = scan.exec(query); m !== null; m = scan.exec(query)) {
|
|
116
|
+
const token = m[0];
|
|
117
|
+
if (token === '(') {
|
|
118
|
+
depth++;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (token === ')') {
|
|
122
|
+
depth--;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (depth === 0 && /^as$/i.test(token))
|
|
126
|
+
return m.index;
|
|
127
|
+
}
|
|
128
|
+
return -1;
|
|
129
|
+
}
|
|
130
|
+
// Converts :name placeholders to ? (positional) and collects values in order.
|
|
131
|
+
// For EXECUTE BLOCK statements only the header is processed - past the AS begins PSQL,
|
|
132
|
+
// where :name is a reference to a local variable or to a parameter of the block itself
|
|
133
|
+
// and must be left alone. That AS is not always followed by BEGIN: declarations of local
|
|
134
|
+
// variables sit between the two, and a header that ends with "as declare variable x"
|
|
135
|
+
// used to be missed, which turned every variable reference in the body into a parameter
|
|
136
|
+
// nobody had passed and failed the query with "param not found".
|
|
137
|
+
function buildQuery(query, params) {
|
|
138
|
+
const values = [];
|
|
139
|
+
const bodyAt = execBlockBodyAt(query);
|
|
140
|
+
const codeTo = bodyAt < 0 ? query.length : bodyAt;
|
|
141
|
+
const scan = new RegExp(`${FB_LITERAL.source}|:(\\w+)`, 'g');
|
|
142
|
+
const sql = query.replace(scan, (token, name, at) => {
|
|
143
|
+
if (name === undefined || at >= codeTo)
|
|
144
|
+
return token;
|
|
145
|
+
const found = params.find((p) => p.name === name);
|
|
146
|
+
if (!found)
|
|
147
|
+
throw new Error(`fb: param not found: ${name}`);
|
|
148
|
+
values.push(found.value);
|
|
149
|
+
return '?';
|
|
150
|
+
});
|
|
151
|
+
return { sql, values };
|
|
152
|
+
}
|
|
153
|
+
// A lost connection must end the query with an error, and node-firebird does not let it.
|
|
154
|
+
// On a closed socket it waits a second, quietly attaches anew and moves the callbacks
|
|
155
|
+
// still waiting for an answer onto the new connection - but the requests behind them
|
|
156
|
+
// were sent on the old one, so the answer never comes. The read stops without an error
|
|
157
|
+
// and the job never ends: a restart of the server, an attachment killed on its side, a
|
|
158
|
+
// network failure - it is the same silent hang every time. Here the close is seen right
|
|
159
|
+
// after the driver has scheduled that reconnect, the reconnect is cancelled, and the
|
|
160
|
+
// query ends with an error. The "error" and "reconnect" events of the database are taken
|
|
161
|
+
// as the same loss. The "error" listener also stays for good: the driver emits it from
|
|
162
|
+
// the socket handler, and with nobody listening it would be thrown from there.
|
|
163
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
164
|
+
function watchConnection(db, onLost) {
|
|
165
|
+
// The typings leave out that Database is an EventEmitter.
|
|
166
|
+
const events = db;
|
|
167
|
+
const conn = events.connection;
|
|
168
|
+
events.on('error', (err) => onLost(`fb: connection lost: ${String(err)}`));
|
|
169
|
+
events.on('reconnect', () => onLost('fb: connection lost, the driver reattached on its own'));
|
|
170
|
+
conn?._socket?.on('close', () => {
|
|
171
|
+
// Set by our own detach at the end of the query - a close that was asked for.
|
|
172
|
+
if (conn._isDetach)
|
|
173
|
+
return;
|
|
174
|
+
clearTimeout(conn._retry_connection_id);
|
|
175
|
+
onLost('fb: connection lost');
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
// Drops the connection at once, without a detach the server would have to answer.
|
|
179
|
+
function dropConnection(db) {
|
|
180
|
+
const conn = db.connection;
|
|
181
|
+
if (!conn)
|
|
182
|
+
return;
|
|
183
|
+
conn._isDetach = true;
|
|
184
|
+
clearTimeout(conn._retry_connection_id);
|
|
185
|
+
try {
|
|
186
|
+
conn._socket?.destroy();
|
|
187
|
+
}
|
|
188
|
+
catch { }
|
|
189
|
+
}
|
|
190
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
191
|
+
export class FbAdapter {
|
|
192
|
+
p;
|
|
193
|
+
constructor(p) {
|
|
194
|
+
this.p = p;
|
|
195
|
+
fbDecodeCharset = p.charset ?? 'UTF8';
|
|
196
|
+
}
|
|
197
|
+
kind() { return 'fb'; }
|
|
198
|
+
title() { return `${this.p.host}:${this.p.port || 'DEFAULT'} [${this.p.database}]`; }
|
|
199
|
+
user() { return this.p.user; }
|
|
200
|
+
versionQuery() { return "SELECT rdb$get_context('SYSTEM', 'ENGINE_VERSION') AS ver FROM rdb$database"; }
|
|
201
|
+
pingQuery() { return 'SELECT 1 AS p FROM rdb$database'; }
|
|
202
|
+
async execStream(query, params, callback, signal) {
|
|
203
|
+
const { sql, values } = buildQuery(query, params);
|
|
204
|
+
const options = {
|
|
205
|
+
host: this.p.host,
|
|
206
|
+
port: this.p.port ?? 3050,
|
|
207
|
+
database: this.p.database,
|
|
208
|
+
user: this.p.user,
|
|
209
|
+
password: this.p.password,
|
|
210
|
+
encoding: (this.p.charset ?? 'UTF8'),
|
|
211
|
+
// Without it a text blob arrives as an async reader function instead of a
|
|
212
|
+
// value, and JSON.stringify silently drops such a field from the row.
|
|
213
|
+
blobAsText: true,
|
|
214
|
+
};
|
|
215
|
+
return new Promise((resolve) => {
|
|
216
|
+
let done = false;
|
|
217
|
+
let db;
|
|
218
|
+
const finish = (error) => {
|
|
219
|
+
if (done)
|
|
220
|
+
return;
|
|
221
|
+
done = true;
|
|
222
|
+
signal?.removeEventListener('abort', onAbort);
|
|
223
|
+
callback({ kind: 'finish', error });
|
|
224
|
+
resolve();
|
|
225
|
+
};
|
|
226
|
+
const onAbort = () => {
|
|
227
|
+
if (db)
|
|
228
|
+
dropConnection(db);
|
|
229
|
+
finish(String(signal?.reason ?? 'aborted'));
|
|
230
|
+
};
|
|
231
|
+
if (signal?.aborted) {
|
|
232
|
+
finish(String(signal.reason ?? 'aborted'));
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
signal?.addEventListener('abort', onAbort);
|
|
236
|
+
Firebird.attach(options, (attachErr, attached) => {
|
|
237
|
+
if (attachErr) {
|
|
238
|
+
finish(String(attachErr));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// Given up while the attach was on its way - nobody is waiting for this one.
|
|
242
|
+
if (done) {
|
|
243
|
+
dropConnection(attached);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
db = attached;
|
|
247
|
+
watchConnection(db, (error) => {
|
|
248
|
+
dropConnection(attached);
|
|
249
|
+
finish(error);
|
|
250
|
+
});
|
|
251
|
+
let schemaEmitted = false;
|
|
252
|
+
db.sequentially(sql, values, (row, _index) => {
|
|
253
|
+
if (done)
|
|
254
|
+
return;
|
|
255
|
+
if (!schemaEmitted) {
|
|
256
|
+
schemaEmitted = true;
|
|
257
|
+
const schema = Object.keys(row).map((name) => ({ name, kind: 'unknown' }));
|
|
258
|
+
callback({ kind: 'schema', schema });
|
|
259
|
+
}
|
|
260
|
+
callback({ kind: 'row', row });
|
|
261
|
+
}, (seqErr) => {
|
|
262
|
+
if (done)
|
|
263
|
+
return;
|
|
264
|
+
attached.detach();
|
|
265
|
+
finish(seqErr ? String(seqErr) : undefined);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
async execTxDynamic(cb, timeoutMs) {
|
|
271
|
+
const options = {
|
|
272
|
+
host: this.p.host,
|
|
273
|
+
port: this.p.port ?? 3050,
|
|
274
|
+
database: this.p.database,
|
|
275
|
+
user: this.p.user,
|
|
276
|
+
password: this.p.password,
|
|
277
|
+
encoding: (this.p.charset ?? 'UTF8'),
|
|
278
|
+
// Without it a text blob arrives as an async reader function instead of a
|
|
279
|
+
// value, and JSON.stringify silently drops such a field from the row.
|
|
280
|
+
blobAsText: true,
|
|
281
|
+
};
|
|
282
|
+
const db = await new Promise((resolve, reject) => {
|
|
283
|
+
Firebird.attach(options, (err, d) => { if (err)
|
|
284
|
+
reject(err);
|
|
285
|
+
else
|
|
286
|
+
resolve(d); });
|
|
287
|
+
});
|
|
288
|
+
const txWork = async () => {
|
|
289
|
+
try {
|
|
290
|
+
const transaction = await new Promise((resolve, reject) => {
|
|
291
|
+
db.transaction(Firebird.ISOLATION_READ_COMMITTED, (err, tx) => { if (err)
|
|
292
|
+
reject(err);
|
|
293
|
+
else
|
|
294
|
+
resolve(tx); });
|
|
295
|
+
});
|
|
296
|
+
try {
|
|
297
|
+
const exec = (query, params) => {
|
|
298
|
+
const { sql: sqlStr, values } = buildQuery(query, params);
|
|
299
|
+
return new Promise((resolve, reject) => {
|
|
300
|
+
// node-firebird may throw inside its native code before calling our
|
|
301
|
+
// callback (e.g. when processing BLOB params). Catch that here so
|
|
302
|
+
// the promise always settles and the try/catch above can rollback.
|
|
303
|
+
const onUncaught = (err) => reject(err);
|
|
304
|
+
process.once('uncaughtException', onUncaught);
|
|
305
|
+
transaction.query(sqlStr, values, (err, rows) => {
|
|
306
|
+
process.removeListener('uncaughtException', onUncaught);
|
|
307
|
+
if (err)
|
|
308
|
+
reject(err);
|
|
309
|
+
else
|
|
310
|
+
resolve({ rows: rows ?? [] });
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
};
|
|
314
|
+
await cb(exec);
|
|
315
|
+
await new Promise((resolve, reject) => {
|
|
316
|
+
transaction.commit((err) => { if (err)
|
|
317
|
+
reject(err);
|
|
318
|
+
else
|
|
319
|
+
resolve(); });
|
|
320
|
+
});
|
|
321
|
+
db.detach();
|
|
322
|
+
return { ok: true, result: undefined };
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
try {
|
|
326
|
+
await new Promise((resolve) => { transaction.rollback(() => resolve()); });
|
|
327
|
+
}
|
|
328
|
+
catch { }
|
|
329
|
+
db.detach();
|
|
330
|
+
return { ok: false, error: String(err) };
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
catch (err) {
|
|
334
|
+
try {
|
|
335
|
+
db.detach();
|
|
336
|
+
}
|
|
337
|
+
catch { }
|
|
338
|
+
return { ok: false, error: String(err) };
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
if (!timeoutMs)
|
|
342
|
+
return txWork();
|
|
343
|
+
let timer;
|
|
344
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
345
|
+
timer = setTimeout(() => {
|
|
346
|
+
try {
|
|
347
|
+
db.detach();
|
|
348
|
+
}
|
|
349
|
+
catch { }
|
|
350
|
+
resolve({ ok: false, error: 'TIMEOUT' });
|
|
351
|
+
}, timeoutMs);
|
|
352
|
+
});
|
|
353
|
+
try {
|
|
354
|
+
return await Promise.race([txWork(), timeoutPromise]);
|
|
355
|
+
}
|
|
356
|
+
finally {
|
|
357
|
+
clearTimeout(timer);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
//# sourceMappingURL=fb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fb.js","sourceRoot":"","sources":["../../../../src/db/adapters/fb.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,eAAe,CAAA;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAA;AAKtC,4DAA4D;AAC5D,MAAM,OAAO,GAAsB;IAClC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;IAC5E,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;IAC5E,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;IAC5E,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;IAC5E,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;IAC5E,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;IAC5E,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;IAC5E,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;IAC5E,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;IAC5E,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;IAC5E,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;IAC5E,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;IAC5E,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;IAC5E,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;IAC5E,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;IAC5E,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;CAC5E,CAAA;AAED,SAAS,aAAa,CAAC,GAAW;IACjC,IAAI,CAAC,GAAG,EAAE,CAAA;IACV,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,oEAAoE;QACpE,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAE,CAAA;QACjB,CAAC,IAAI,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAE,CAAC,CAAA;IAC7D,CAAC;IACD,OAAO,CAAC,CAAA;AACT,CAAC;AAED,wFAAwF;AACxF,IAAI,eAAe,GAAuB,MAAM,CAmC/C;AAAA,CAAC,GAAS,EAAE;IACZ,MAAM,QAAQ,GAAU,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAA;IACtD,MAAM,UAAU,GAAQ,QAAQ,CAAC,mCAAmC,CAAC,CAAA;IACrE,MAAM,OAAO,GAAW,QAAQ,CAAC,8BAA8B,CAAC,CAAA;IAChE,UAAU,CAAC,SAAS,CAAC,SAAS,GAAG,UAAU,IAAS,EAAE,QAAa;QAClE,MAAM,GAAG,GAAQ,IAAI,CAAC,IAAI,CAAA;QAC1B,GAAG,CAAC,GAAG,GAAG,CAAC,CAAA;QACX,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAA;QACjC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACvB,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAA,CAAC,qDAAqD;IACjF,CAAC,CAAA;IAED,MAAM,WAAW,GAAQ,QAAQ,CAAC,kCAAkC,CAAC,CAAA;IACrE,WAAW,CAAC,SAAS,CAAC,SAAS,CAAC,QAAQ,GAAG,UAAU,GAAW,EAAE,SAAiB;QAClF,IAAI,GAAG,IAAI,CAAC;YAAE,OAAO,EAAE,CAAA;QACvB,MAAM,KAAK,GAAW,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,CAAA;QACjE,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QAC1B,OAAO,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IACrF,CAAC,CAAA;IAED,MAAM,WAAW,GAAG,WAAW,CAAC,SAAS,CAAC,SAAS,CAAC,WAAW,CAAA;IAC/D,WAAW,CAAC,SAAS,CAAC,SAAS,CAAC,WAAW,GAAG;QAC7C,MAAM,OAAO,GAAW,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC9C,IAAI,eAAe,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,OAAO,OAAO,CAAA;QAC9E,OAAO,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAA;IACnD,CAAC,CAAA;IAED,mEAAmE;IACnE,UAAU,CAAC,SAAS,CAAC,QAAQ,GAAG,UAAU,SAAc,EAAE,WAAgB,EAAE,QAAa;QACxF,MAAM,IAAI,GAAO,IAAI,CAAA;QACrB,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,IAAI,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;QAC5E,MAAM,GAAG,GAAU,EAAE,CAAA;QACrB,IAAI,KAAK,GAAU,CAAC,CAAA;QAEpB,MAAM,IAAI,GAAG,GAAS,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,WAAW,EAAE,OAAO,CAAC,iBAAiB,EAAE,IAAI,CAAC,CAAA;QAE5F,MAAM,IAAI,GAAG,CAAC,GAAQ,EAAE,GAAQ,EAAQ,EAAE;YACzC,IAAI,GAAG,EAAE,CAAC;gBAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;gBAAC,OAAM;YAAC,CAAC;YAClC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;gBACxB,IAAI,GAAG,EAAE,OAAO;oBAAE,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;;oBACxB,IAAI,EAAE,CAAA;gBACxB,OAAM;YACP,CAAC;YAED,KAAK,CAAC,KAAK,IAAmB,EAAE;gBAC/B,IAAI,CAAC;oBACJ,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;wBAC5C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAA;wBACxC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAA;oBAC7C,CAAC;oBACD,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;wBAC5B,IAAI,QAAQ;4BAAE,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,CAAA;;4BACjC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;oBAC5B,CAAC;oBACD,IAAI,GAAG,CAAC,OAAO;wBAAE,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;;wBACxB,IAAI,EAAE,CAAA;gBACxB,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACZ,QAAQ,CAAC,CAAC,CAAC,CAAA;gBACZ,CAAC;YACF,CAAC,CAAC,EAAE,CAAA;QACL,CAAC,CAAA;QAED,IAAI,EAAE,CAAA;IACP,CAAC,CAAA;AACF,CAAC,CAAC,EAAE,CAAA;AACJ,sDAAsD;AAEtD,sFAAsF;AACtF,uEAAuE;AACvE,MAAM,UAAU,GAAG,yDAAyD,CAAA;AAE5E,oFAAoF;AACpF,qFAAqF;AACrF,uFAAuF;AACvF,SAAS,eAAe,CAAC,KAAa;IACrC,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,CAAA;IACpD,MAAM,IAAI,GAAG,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,MAAM,gBAAgB,EAAE,IAAI,CAAC,CAAA;IACnE,IAAI,KAAK,GAAI,CAAC,CAAA;IACd,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACjE,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QAClB,IAAI,KAAK,KAAK,GAAG,EAAmB,CAAC;YAAC,KAAK,EAAE,CAAC;YAAC,SAAQ;QAAC,CAAC;QACzD,IAAI,KAAK,KAAK,GAAG,EAAmB,CAAC;YAAC,KAAK,EAAE,CAAC;YAAC,SAAQ;QAAC,CAAC;QACzD,IAAI,KAAK,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC,KAAK,CAAA;IACvD,CAAC;IACD,OAAO,CAAC,CAAC,CAAA;AACV,CAAC;AAED,8EAA8E;AAC9E,uFAAuF;AACvF,uFAAuF;AACvF,yFAAyF;AACzF,qFAAqF;AACrF,wFAAwF;AACxF,iEAAiE;AACjE,SAAS,UAAU,CAAC,KAAa,EAAE,MAAkB;IACpD,MAAM,MAAM,GAAc,EAAE,CAAA;IAC5B,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,CAAA;IACrC,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;IACjD,MAAM,IAAI,GAAK,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,MAAM,UAAU,EAAE,GAAG,CAAC,CAAA;IAE9D,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,KAAa,EAAE,IAAwB,EAAE,EAAU,EAAE,EAAE;QACvF,IAAI,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,MAAM;YAAE,OAAO,KAAK,CAAA;QACpD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;QACjD,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,EAAE,CAAC,CAAA;QAC3D,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACxB,OAAO,GAAG,CAAA;IACX,CAAC,CAAC,CAAA;IACF,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAA;AACvB,CAAC;AAED,yFAAyF;AACzF,sFAAsF;AACtF,qFAAqF;AACrF,uFAAuF;AACvF,uFAAuF;AACvF,wFAAwF;AACxF,qFAAqF;AACrF,yFAAyF;AACzF,uFAAuF;AACvF,+EAA+E;AAC/E,uDAAuD;AACvD,SAAS,eAAe,CAAC,EAAqB,EAAE,MAA+B;IAC9E,0DAA0D;IAC1D,MAAM,MAAM,GAAQ,EAAE,CAAA;IACtB,MAAM,IAAI,GAAU,MAAM,CAAC,UAAU,CAAA;IACrC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAY,EAAE,EAAE,CAAC,MAAM,CAAC,wBAAwB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;IACnF,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,uDAAuD,CAAC,CAAC,CAAA;IAC7F,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QAC/B,8EAA8E;QAC9E,IAAI,IAAI,CAAC,SAAS;YAAE,OAAM;QAC1B,YAAY,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;QACvC,MAAM,CAAC,qBAAqB,CAAC,CAAA;IAC9B,CAAC,CAAC,CAAA;AACH,CAAC;AAED,kFAAkF;AAClF,SAAS,cAAc,CAAC,EAAqB;IAC5C,MAAM,IAAI,GAAS,EAAU,CAAC,UAAU,CAAA;IACxC,IAAI,CAAC,IAAI;QAAE,OAAM;IACjB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;IACrB,YAAY,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;IACvC,IAAI,CAAC;QAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,CAAA;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,CAAC;AAC1C,CAAC;AACD,sDAAsD;AAEtD,MAAM,OAAO,SAAS;IACQ,CAAC;IAA9B,YAA6B,CAAU;iBAAV,CAAC;QAC7B,eAAe,GAAG,CAAC,CAAC,OAAO,IAAI,MAAM,CAAA;IACtC,CAAC;IAED,IAAI,KAAW,OAAO,IAAI,CAAA,CAAC,CAAC;IAC5B,KAAK,KAAa,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAA,CAAC,CAAC;IAC5F,IAAI,KAAa,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA,CAAC,CAAC;IACrC,YAAY,KAAa,OAAO,6EAA6E,CAAA,CAAC,CAAC;IAC/G,SAAS,KAAa,OAAO,iCAAiC,CAAA,CAAC,CAAC;IAEhE,KAAK,CAAC,UAAU,CAAC,KAAa,EAAE,MAAkB,EAAE,QAAyC,EAAE,MAAoB;QAClH,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;QACjD,MAAM,OAAO,GAAqB;YACjC,IAAI,EAAM,IAAI,CAAC,CAAC,CAAC,IAAI;YACrB,IAAI,EAAM,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI;YAC7B,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ;YACzB,IAAI,EAAM,IAAI,CAAC,CAAC,CAAC,IAAI;YACrB,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ;YACzB,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,MAAM,CAAmC;YACtE,0EAA0E;YAC1E,sEAAsE;YACtE,UAAU,EAAE,IAAI;SAChB,CAAA;QACD,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACpC,IAAI,IAAI,GAAG,KAAK,CAAA;YAChB,IAAI,EAAiC,CAAA;YACrC,MAAM,MAAM,GAAG,CAAC,KAAc,EAAQ,EAAE;gBACvC,IAAI,IAAI;oBAAE,OAAM;gBAChB,IAAI,GAAG,IAAI,CAAA;gBACX,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;gBAC7C,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;gBACnC,OAAO,EAAE,CAAA;YACV,CAAC,CAAA;YACD,MAAM,OAAO,GAAG,GAAS,EAAE;gBAC1B,IAAI,EAAE;oBAAE,cAAc,CAAC,EAAE,CAAC,CAAA;gBAC1B,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,IAAI,SAAS,CAAC,CAAC,CAAA;YAC5C,CAAC,CAAA;YACD,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC,CAAC;gBAAC,OAAM;YAAC,CAAC;YAC3E,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YAE1C,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE;gBAChD,IAAI,SAAS,EAAE,CAAC;oBAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;oBAAC,OAAM;gBAAC,CAAC;gBACpD,6EAA6E;gBAC7E,IAAI,IAAI,EAAE,CAAC;oBAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;oBAAC,OAAM;gBAAC,CAAC;gBAC9C,EAAE,GAAG,QAAQ,CAAA;gBACb,eAAe,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE;oBAC7B,cAAc,CAAC,QAAQ,CAAC,CAAA;oBACxB,MAAM,CAAC,KAAK,CAAC,CAAA;gBACd,CAAC,CAAC,CAAA;gBACF,IAAI,aAAa,GAAG,KAAK,CAAA;gBACzB,EAAE,CAAC,YAAY,CACd,GAAG,EACH,MAAM,EACN,CAAC,GAA4B,EAAE,MAAc,EAAE,EAAE;oBAChD,IAAI,IAAI;wBAAE,OAAM;oBAChB,IAAI,CAAC,aAAa,EAAE,CAAC;wBACpB,aAAa,GAAG,IAAI,CAAA;wBACpB,MAAM,MAAM,GAAa,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;wBACpF,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAA;oBACrC,CAAC;oBACD,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;gBAC/B,CAAC,EACD,CAAC,MAAc,EAAE,EAAE;oBAClB,IAAI,IAAI;wBAAE,OAAM;oBAChB,QAAQ,CAAC,MAAM,EAAE,CAAA;oBACjB,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;gBAC5C,CAAC,CACD,CAAA;YACF,CAAC,CAAC,CAAA;QACH,CAAC,CAAC,CAAA;IACH,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,EAAW,EAAE,SAAkB;QAClD,MAAM,OAAO,GAAqB;YACjC,IAAI,EAAM,IAAI,CAAC,CAAC,CAAC,IAAI;YACrB,IAAI,EAAM,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI;YAC7B,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ;YACzB,IAAI,EAAM,IAAI,CAAC,CAAC,CAAC,IAAI;YACrB,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ;YACzB,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,MAAM,CAAmC;YACtE,0EAA0E;YAC1E,sEAAsE;YACtE,UAAU,EAAE,IAAI;SAChB,CAAA;QACD,MAAM,EAAE,GAAG,MAAM,IAAI,OAAO,CAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACnE,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,GAAG,IAAI,GAAG;gBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;gBAAM,OAAO,CAAC,CAAC,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;QAChF,CAAC,CAAC,CAAA;QACF,MAAM,MAAM,GAAG,KAAK,IAA4B,EAAE;YACjD,IAAI,CAAC;gBACJ,MAAM,WAAW,GAAG,MAAM,IAAI,OAAO,CAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;oBAC/E,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,wBAAwB,EAAE,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,GAAG,IAAI,GAAG;wBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;wBAAM,OAAO,CAAC,EAAE,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;gBAC3G,CAAC,CAAC,CAAA;gBACF,IAAI,CAAC;oBACJ,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,MAAkB,EAAwB,EAAE;wBACxE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;wBACzD,OAAO,IAAI,OAAO,CAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;4BACnD,oEAAoE;4BACpE,kEAAkE;4BAClE,mEAAmE;4BACnE,MAAM,UAAU,GAAG,CAAC,GAAU,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;4BAC9C,OAAO,CAAC,IAAI,CAAC,mBAAmB,EAAE,UAAU,CAAC,CAAA;4BAC7C,WAAW,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAAiB,EAAE,IAA+B,EAAE,EAAE;gCACxF,OAAO,CAAC,cAAc,CAAC,mBAAmB,EAAE,UAAU,CAAC,CAAA;gCACvD,IAAI,GAAG;oCAAE,MAAM,CAAC,GAAG,CAAC,CAAA;;oCACf,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC,CAAA;4BACnC,CAAC,CAAC,CAAA;wBACH,CAAC,CAAC,CAAA;oBACH,CAAC,CAAA;oBACD,MAAM,EAAE,CAAC,IAAI,CAAC,CAAA;oBACd,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;wBAC3C,WAAW,CAAC,MAAM,CAAC,CAAC,GAAiB,EAAE,EAAE,GAAG,IAAI,GAAG;4BAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;4BAAM,OAAO,EAAE,CAAA,CAAC,CAAC,CAAC,CAAA;oBACpF,CAAC,CAAC,CAAA;oBACF,EAAE,CAAC,MAAM,EAAE,CAAA;oBACX,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;gBACvC,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,IAAI,CAAC;wBAAC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,GAAG,WAAW,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;oBAAC,CAAC;oBAAC,MAAM,CAAC,CAAA,CAAC;oBAChG,EAAE,CAAC,MAAM,EAAE,CAAA;oBACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAA;gBACzC,CAAC;YACF,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACd,IAAI,CAAC;oBAAC,EAAE,CAAC,MAAM,EAAE,CAAA;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAA,CAAC;gBAC5B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAA;YACzC,CAAC;QACF,CAAC,CAAA;QACD,IAAI,CAAC,SAAS;YAAE,OAAO,MAAM,EAAE,CAAA;QAC/B,IAAI,KAAgD,CAAA;QACpD,MAAM,cAAc,GAAG,IAAI,OAAO,CAAgB,CAAC,OAAO,EAAE,EAAE;YAC7D,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBACvB,IAAI,CAAC;oBAAC,EAAE,CAAC,MAAM,EAAE,CAAA;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAA,CAAC;gBAC5B,OAAO,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAA;YACzC,CAAC,EAAE,SAAS,CAAC,CAAA;QACd,CAAC,CAAC,CAAA;QACF,IAAI,CAAC;YACJ,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,cAAc,CAAC,CAAC,CAAA;QACtD,CAAC;gBAAS,CAAC;YACV,YAAY,CAAC,KAAK,CAAC,CAAA;QACpB,CAAC;IACF,CAAC;CACD","sourcesContent":["import Firebird from 'node-firebird'\nimport { createRequire } from 'module'\nimport type { IDbAdapter, TDbCol, TDbParam, TDbStreamEvent, TDbTxCb, TDbTxResult } from '../types'\nimport type { TConnFb } from '../connection'\nimport type { TResult } from '../../tresult'\n\n// Windows-1251 → Unicode code point map for bytes 0x80–0xFF\nconst WIN1251: readonly number[] = [\n\t0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021, // 0x80-0x87\n\t0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F, // 0x88-0x8F\n\t0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, // 0x90-0x97\n\t0xFFFD, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F, // 0x98-0x9F\n\t0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7, // 0xA0-0xA7\n\t0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407, // 0xA8-0xAF\n\t0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7, // 0xB0-0xB7\n\t0x0451, 0x2116, 0x0454, 0x00BB, 0x0458, 0x0405, 0x0455, 0x0457, // 0xB8-0xBF\n\t0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, // 0xC0-0xC7\n\t0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, // 0xC8-0xCF\n\t0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, // 0xD0-0xD7\n\t0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, // 0xD8-0xDF\n\t0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, // 0xE0-0xE7\n\t0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, // 0xE8-0xEF\n\t0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, // 0xF0-0xF7\n\t0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, // 0xF8-0xFF\n]\n\nfunction win1251Decode(buf: Buffer): string {\n\tlet s = ''\n\tfor (let i = 0; i < buf.length; i++) {\n\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\tconst b = buf[i]!\n\t\ts += String.fromCodePoint(b < 0x80 ? b : WIN1251[b - 0x80]!)\n\t}\n\treturn s\n}\n\n// Module-level charset state — set once per adapter init, used by XdrReader patch below\nlet fbDecodeCharset: 'UTF8' | 'WIN1251' = 'UTF8'\n\n// node-firebird bug with Firebird 6 (protocol >= V11): Connection.prototype.closeBlob\n// uses _queueEvent(callback, defer=true), which calls the callback immediately without\n// pushing to the response queue. The server still sends op_response for op_close_blob,\n// leaving an unconsumed response in the XDR stream. This shifts all subsequent response\n// reads by one: allocateAndPrepareStatement's lazy_count=2 consumes the close_blob\n// response + allocate response instead of allocate + prepare, so ret.buffer is undefined\n// and describe() crashes with \"Cannot read properties of undefined (reading 'length')\".\n// Fix: override closeBlob to queue the response so it is consumed before the next op.\n//\n// XdrReader.readText patch: node-firebird hardcodes DEFAULT_ENCODING='UTF8' for all text\n// columns, ignoring the column charset. Firebird databases with NONE-charset columns send\n// raw bytes (e.g. WIN1251). We intercept readText to decode with the correct charset\n// instead of blindly calling buffer.toString('utf8'), which produces U+FFFD for WIN1251.\n//\n// Connection.fetchAll patch: with blobAsText the driver reads every text blob of a\n// fetch batch (200 rows) at once - Promise.all over arrBlob - so up to 200 sequences of\n// open_blob / get_segment... / close_blob are interleaved on one connection. The driver\n// is not built for that: one FIFO of response callbacks and one shared write buffer for\n// all of them. Responses start going missing, the fetch loop is never called again, and\n// the read hangs for good with no error at all - the export just stops after the first\n// batches and the job never ends. Here the blobs of a batch are read one after another,\n// which is not only sound but far faster (3000 rows with a 16 KB blob: hangs vs 3.7 s).\n// Rows are also no longer accumulated in a growing array in stream mode - execStream\n// throws that array away, and on a large export it is a copy of the whole data set,\n// blobs included, held in memory next to the file being written.\n//\n// BlrReader.readSegment patch: text blobs never reach readText - node-firebird reads them\n// segment by segment and decodes every segment with the same hardcoded UTF8. Here we\n// cannot change the decoding, so we re-encode WIN1251 bytes into UTF-8 and let that\n// hardcoded decode produce the right text. readSegment is used for blobs only; on a\n// WIN1251 connection a binary blob would be re-encoded too, but those are not read - they\n// come as reader functions and are dropped before they reach the data files.\n/* eslint-disable @typescript-eslint/no-explicit-any */\n;((): void => {\n\tconst _require = createRequire(import.meta.url)\n\tconst FbWireConn: any = _require('node-firebird/lib/wire/connection')\n\tconst FbConst: any = _require('node-firebird/lib/wire/const')\n\tFbWireConn.prototype.closeBlob = function (blob: any, callback: any): void {\n\t\tconst msg: any = this._msg\n\t\tmsg.pos = 0\n\t\tmsg.addInt(FbConst.op_close_blob)\n\t\tmsg.addInt(blob.handle)\n\t\tthis._queueEvent(callback) // no defer — op_close_blob response must be consumed\n\t}\n\n\tconst FbSerialize: any = _require('node-firebird/lib/wire/serialize')\n\tFbSerialize.XdrReader.prototype.readText = function (len: number, _encoding: string): string {\n\t\tif (len <= 0) return ''\n\t\tconst bytes: Buffer = this.buffer.slice(this.pos, this.pos + len)\n\t\tthis.pos += (len + 3) & ~3\n\t\treturn fbDecodeCharset === 'WIN1251' ? win1251Decode(bytes) : bytes.toString('utf8')\n\t}\n\n\tconst readSegment = FbSerialize.BlrReader.prototype.readSegment\n\tFbSerialize.BlrReader.prototype.readSegment = function (): Buffer {\n\t\tconst segment: Buffer = readSegment.call(this)\n\t\tif (fbDecodeCharset !== 'WIN1251' || !Buffer.isBuffer(segment)) return segment\n\t\treturn Buffer.from(win1251Decode(segment), 'utf8')\n\t}\n\n\t// fetchAll patch, see the note above the block for why it is here.\n\tFbWireConn.prototype.fetchAll = function (statement: any, transaction: any, callback: any): void {\n\t\tconst self = this\n\t\tconst asStream = Boolean(statement.custom?.asStream && statement.custom?.on)\n\t\tconst all: any[] = []\n\t\tlet index = 0\n\n\t\tconst next = (): void => self.fetch(statement, transaction, FbConst.DEFAULT_FETCHSIZE, loop)\n\n\t\tconst loop = (err: any, ret: any): void => {\n\t\t\tif (err) { callback(err); return }\n\t\t\tif (!ret?.data?.length) {\n\t\t\t\tif (ret?.fetched) callback(undefined, all)\n\t\t\t\telse next()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tvoid (async (): Promise<void> => {\n\t\t\t\ttry {\n\t\t\t\t\tfor (const readBlob of (ret.arrBlob ?? [])) {\n\t\t\t\t\t\tconst blob = await readBlob(transaction)\n\t\t\t\t\t\tret.data[blob.row][blob.column] = blob.value\n\t\t\t\t\t}\n\t\t\t\t\tfor (const row of ret.data) {\n\t\t\t\t\t\tif (asStream) statement.custom.on(row, index++)\n\t\t\t\t\t\telse all.push(row)\n\t\t\t\t\t}\n\t\t\t\t\tif (ret.fetched) callback(undefined, all)\n\t\t\t\t\telse next()\n\t\t\t\t} catch (e) {\n\t\t\t\t\tcallback(e)\n\t\t\t\t}\n\t\t\t})()\n\t\t}\n\n\t\tnext()\n\t}\n})()\n/* eslint-enable @typescript-eslint/no-explicit-any */\n\n// A string literal, a quoted identifier, a line or a block comment - the places where\n// what looks like a placeholder is just text. Everything else is code.\nconst FB_LITERAL = /'(?:[^']|'')*'|\"(?:[^\"]|\"\")*\"|--[^\\n]*|\\/\\*[\\s\\S]*?\\*\\//\n\n// Index of the AS that closes the header of an EXECUTE BLOCK, or -1. Both lists the\n// header may carry - the input parameters and RETURNS - are parenthesized, so the AS\n// we want is the first one outside any parentheses, and outside literals and comments.\nfunction execBlockBodyAt(query: string): number {\n\tif (!/^\\s*EXECUTE\\s+BLOCK\\b/i.test(query)) return -1\n\tconst scan = new RegExp(`${FB_LITERAL.source}|[()]|\\\\bAS\\\\b`, 'gi')\n\tlet depth = 0\n\tfor (let m = scan.exec(query); m !== null; m = scan.exec(query)) {\n\t\tconst token = m[0]\n\t\tif (token === '(') { depth++; continue }\n\t\tif (token === ')') { depth--; continue }\n\t\tif (depth === 0 && /^as$/i.test(token)) return m.index\n\t}\n\treturn -1\n}\n\n// Converts :name placeholders to ? (positional) and collects values in order.\n// For EXECUTE BLOCK statements only the header is processed - past the AS begins PSQL,\n// where :name is a reference to a local variable or to a parameter of the block itself\n// and must be left alone. That AS is not always followed by BEGIN: declarations of local\n// variables sit between the two, and a header that ends with \"as declare variable x\"\n// used to be missed, which turned every variable reference in the body into a parameter\n// nobody had passed and failed the query with \"param not found\".\nfunction buildQuery(query: string, params: TDbParam[]): { sql: string; values: unknown[] } {\n\tconst values: unknown[] = []\n\tconst bodyAt = execBlockBodyAt(query)\n\tconst codeTo = bodyAt < 0 ? query.length : bodyAt\n\tconst scan = new RegExp(`${FB_LITERAL.source}|:(\\\\w+)`, 'g')\n\n\tconst sql = query.replace(scan, (token: string, name: string | undefined, at: number) => {\n\t\tif (name === undefined || at >= codeTo) return token\n\t\tconst found = params.find((p) => p.name === name)\n\t\tif (!found) throw new Error(`fb: param not found: ${name}`)\n\t\tvalues.push(found.value)\n\t\treturn '?'\n\t})\n\treturn { sql, values }\n}\n\n// A lost connection must end the query with an error, and node-firebird does not let it.\n// On a closed socket it waits a second, quietly attaches anew and moves the callbacks\n// still waiting for an answer onto the new connection - but the requests behind them\n// were sent on the old one, so the answer never comes. The read stops without an error\n// and the job never ends: a restart of the server, an attachment killed on its side, a\n// network failure - it is the same silent hang every time. Here the close is seen right\n// after the driver has scheduled that reconnect, the reconnect is cancelled, and the\n// query ends with an error. The \"error\" and \"reconnect\" events of the database are taken\n// as the same loss. The \"error\" listener also stays for good: the driver emits it from\n// the socket handler, and with nobody listening it would be thrown from there.\n/* eslint-disable @typescript-eslint/no-explicit-any */\nfunction watchConnection(db: Firebird.Database, onLost: (error: string) => void): void {\n\t// The typings leave out that Database is an EventEmitter.\n\tconst events: any = db\n\tconst conn: any = events.connection\n\tevents.on('error', (err: unknown) => onLost(`fb: connection lost: ${String(err)}`))\n\tevents.on('reconnect', () => onLost('fb: connection lost, the driver reattached on its own'))\n\tconn?._socket?.on('close', () => {\n\t\t// Set by our own detach at the end of the query - a close that was asked for.\n\t\tif (conn._isDetach) return\n\t\tclearTimeout(conn._retry_connection_id)\n\t\tonLost('fb: connection lost')\n\t})\n}\n\n// Drops the connection at once, without a detach the server would have to answer.\nfunction dropConnection(db: Firebird.Database): void {\n\tconst conn: any = (db as any).connection\n\tif (!conn) return\n\tconn._isDetach = true\n\tclearTimeout(conn._retry_connection_id)\n\ttry { conn._socket?.destroy() } catch { }\n}\n/* eslint-enable @typescript-eslint/no-explicit-any */\n\nexport class FbAdapter implements IDbAdapter {\n\tconstructor(private readonly p: TConnFb) {\n\t\tfbDecodeCharset = p.charset ?? 'UTF8'\n\t}\n\n\tkind(): 'fb' { return 'fb' }\n\ttitle(): string { return `${this.p.host}:${this.p.port || 'DEFAULT'} [${this.p.database}]` }\n\tuser(): string { return this.p.user }\n\tversionQuery(): string { return \"SELECT rdb$get_context('SYSTEM', 'ENGINE_VERSION') AS ver FROM rdb$database\" }\n\tpingQuery(): string { return 'SELECT 1 AS p FROM rdb$database' }\n\n\tasync execStream(query: string, params: TDbParam[], callback: (event: TDbStreamEvent) => void, signal?: AbortSignal): Promise<void> {\n\t\tconst { sql, values } = buildQuery(query, params)\n\t\tconst options: Firebird.Options = {\n\t\t\thost: this.p.host,\n\t\t\tport: this.p.port ?? 3050,\n\t\t\tdatabase: this.p.database,\n\t\t\tuser: this.p.user,\n\t\t\tpassword: this.p.password,\n\t\t\tencoding: (this.p.charset ?? 'UTF8') as Firebird.SupportedCharacterSet,\n\t\t\t// Without it a text blob arrives as an async reader function instead of a\n\t\t\t// value, and JSON.stringify silently drops such a field from the row.\n\t\t\tblobAsText: true,\n\t\t}\n\t\treturn new Promise<void>((resolve) => {\n\t\t\tlet done = false\n\t\t\tlet db: Firebird.Database | undefined\n\t\t\tconst finish = (error?: string): void => {\n\t\t\t\tif (done) return\n\t\t\t\tdone = true\n\t\t\t\tsignal?.removeEventListener('abort', onAbort)\n\t\t\t\tcallback({ kind: 'finish', error })\n\t\t\t\tresolve()\n\t\t\t}\n\t\t\tconst onAbort = (): void => {\n\t\t\t\tif (db) dropConnection(db)\n\t\t\t\tfinish(String(signal?.reason ?? 'aborted'))\n\t\t\t}\n\t\t\tif (signal?.aborted) { finish(String(signal.reason ?? 'aborted')); return }\n\t\t\tsignal?.addEventListener('abort', onAbort)\n\n\t\t\tFirebird.attach(options, (attachErr, attached) => {\n\t\t\t\tif (attachErr) { finish(String(attachErr)); return }\n\t\t\t\t// Given up while the attach was on its way - nobody is waiting for this one.\n\t\t\t\tif (done) { dropConnection(attached); return }\n\t\t\t\tdb = attached\n\t\t\t\twatchConnection(db, (error) => {\n\t\t\t\t\tdropConnection(attached)\n\t\t\t\t\tfinish(error)\n\t\t\t\t})\n\t\t\t\tlet schemaEmitted = false\n\t\t\t\tdb.sequentially(\n\t\t\t\t\tsql,\n\t\t\t\t\tvalues,\n\t\t\t\t\t(row: Record<string, unknown>, _index: number) => {\n\t\t\t\t\t\tif (done) return\n\t\t\t\t\t\tif (!schemaEmitted) {\n\t\t\t\t\t\t\tschemaEmitted = true\n\t\t\t\t\t\t\tconst schema: TDbCol[] = Object.keys(row).map((name) => ({ name, kind: 'unknown' }))\n\t\t\t\t\t\t\tcallback({ kind: 'schema', schema })\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcallback({ kind: 'row', row })\n\t\t\t\t\t},\n\t\t\t\t\t(seqErr?: Error) => {\n\t\t\t\t\t\tif (done) return\n\t\t\t\t\t\tattached.detach()\n\t\t\t\t\t\tfinish(seqErr ? String(seqErr) : undefined)\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t})\n\t\t})\n\t}\n\n\tasync execTxDynamic(cb: TDbTxCb, timeoutMs?: number): Promise<TResult<void>> {\n\t\tconst options: Firebird.Options = {\n\t\t\thost: this.p.host,\n\t\t\tport: this.p.port ?? 3050,\n\t\t\tdatabase: this.p.database,\n\t\t\tuser: this.p.user,\n\t\t\tpassword: this.p.password,\n\t\t\tencoding: (this.p.charset ?? 'UTF8') as Firebird.SupportedCharacterSet,\n\t\t\t// Without it a text blob arrives as an async reader function instead of a\n\t\t\t// value, and JSON.stringify silently drops such a field from the row.\n\t\t\tblobAsText: true,\n\t\t}\n\t\tconst db = await new Promise<Firebird.Database>((resolve, reject) => {\n\t\t\tFirebird.attach(options, (err, d) => { if (err) reject(err); else resolve(d) })\n\t\t})\n\t\tconst txWork = async (): Promise<TResult<void>> => {\n\t\t\ttry {\n\t\t\t\tconst transaction = await new Promise<Firebird.Transaction>((resolve, reject) => {\n\t\t\t\t\tdb.transaction(Firebird.ISOLATION_READ_COMMITTED, (err, tx) => { if (err) reject(err); else resolve(tx) })\n\t\t\t\t})\n\t\t\t\ttry {\n\t\t\t\t\tconst exec = (query: string, params: TDbParam[]): Promise<TDbTxResult> => {\n\t\t\t\t\t\tconst { sql: sqlStr, values } = buildQuery(query, params)\n\t\t\t\t\t\treturn new Promise<TDbTxResult>((resolve, reject) => {\n\t\t\t\t\t\t\t// node-firebird may throw inside its native code before calling our\n\t\t\t\t\t\t\t// callback (e.g. when processing BLOB params). Catch that here so\n\t\t\t\t\t\t\t// the promise always settles and the try/catch above can rollback.\n\t\t\t\t\t\t\tconst onUncaught = (err: Error) => reject(err)\n\t\t\t\t\t\t\tprocess.once('uncaughtException', onUncaught)\n\t\t\t\t\t\t\ttransaction.query(sqlStr, values, (err: Error | null, rows: Record<string, unknown>[]) => {\n\t\t\t\t\t\t\t\tprocess.removeListener('uncaughtException', onUncaught)\n\t\t\t\t\t\t\t\tif (err) reject(err)\n\t\t\t\t\t\t\t\telse resolve({ rows: rows ?? [] })\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tawait cb(exec)\n\t\t\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\t\t\ttransaction.commit((err: Error | null) => { if (err) reject(err); else resolve() })\n\t\t\t\t\t})\n\t\t\t\t\tdb.detach()\n\t\t\t\t\treturn { ok: true, result: undefined }\n\t\t\t\t} catch (err) {\n\t\t\t\t\ttry { await new Promise<void>((resolve) => { transaction.rollback(() => resolve()) }) } catch {}\n\t\t\t\t\tdb.detach()\n\t\t\t\t\treturn { ok: false, error: String(err) }\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\ttry { db.detach() } catch {}\n\t\t\t\treturn { ok: false, error: String(err) }\n\t\t\t}\n\t\t}\n\t\tif (!timeoutMs) return txWork()\n\t\tlet timer: ReturnType<typeof setTimeout> | undefined\n\t\tconst timeoutPromise = new Promise<TResult<void>>((resolve) => {\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\ttry { db.detach() } catch {}\n\t\t\t\tresolve({ ok: false, error: 'TIMEOUT' })\n\t\t\t}, timeoutMs)\n\t\t})\n\t\ttry {\n\t\t\treturn await Promise.race([txWork(), timeoutPromise])\n\t\t} finally {\n\t\t\tclearTimeout(timer)\n\t\t}\n\t}\n}\n"]}
|