node-firebird 2.8.1 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +268 -7
  2. package/lib/index.d.ts +17 -9
  3. package/lib/index.js +42 -3
  4. package/lib/named-params.d.ts +42 -0
  5. package/lib/named-params.js +133 -0
  6. package/lib/pool.js +1 -1
  7. package/lib/srp.d.ts +3 -3
  8. package/lib/types.d.ts +185 -25
  9. package/lib/uri.d.ts +57 -0
  10. package/lib/uri.js +193 -0
  11. package/lib/wire/connection.d.ts +92 -59
  12. package/lib/wire/connection.js +286 -55
  13. package/lib/wire/const.d.ts +9 -1
  14. package/lib/wire/const.js +21 -9
  15. package/lib/wire/database.d.ts +51 -26
  16. package/lib/wire/database.js +26 -8
  17. package/lib/wire/eventConnection.js +5 -3
  18. package/lib/wire/query-stream.d.ts +18 -0
  19. package/lib/wire/query-stream.js +73 -0
  20. package/lib/wire/serialize.d.ts +18 -2
  21. package/lib/wire/serialize.js +7 -0
  22. package/lib/wire/service.d.ts +42 -0
  23. package/lib/wire/service.js +145 -0
  24. package/lib/wire/socket.d.ts +3 -1
  25. package/lib/wire/socket.js +5 -2
  26. package/lib/wire/statement.d.ts +40 -20
  27. package/lib/wire/statement.js +26 -6
  28. package/lib/wire/transaction.d.ts +32 -18
  29. package/lib/wire/transaction.js +50 -8
  30. package/lib/wire/wire-types.d.ts +116 -0
  31. package/lib/wire/wire-types.js +10 -0
  32. package/lib/wire/xsqlvar.d.ts +18 -18
  33. package/package.json +1 -1
  34. package/src/index.ts +54 -15
  35. package/src/messages.ts +1 -1
  36. package/src/named-params.ts +145 -0
  37. package/src/pool.ts +1 -1
  38. package/src/srp.ts +6 -6
  39. package/src/types.ts +183 -25
  40. package/src/unix-crypt.ts +9 -9
  41. package/src/uri.ts +204 -0
  42. package/src/wire/connection.ts +481 -234
  43. package/src/wire/const.ts +21 -9
  44. package/src/wire/database.ts +75 -43
  45. package/src/wire/eventConnection.ts +8 -5
  46. package/src/wire/query-stream.ts +80 -0
  47. package/src/wire/serialize.ts +29 -0
  48. package/src/wire/service.ts +188 -6
  49. package/src/wire/socket.ts +17 -8
  50. package/src/wire/statement.ts +68 -31
  51. package/src/wire/transaction.ts +85 -33
  52. package/src/wire/wire-types.ts +127 -0
  53. package/src/wire/xsqlvar.ts +9 -7
package/src/index.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import Const from './wire/const';
2
- import { doError, doCallback, fromCallback } from './callback';
2
+ import { doError, doCallback, fromCallback, type Callback } from './callback';
3
3
  import Connection from './wire/connection';
4
4
  import Pool from './pool';
5
5
  import { escape as escapeValue } from './utils';
6
+ import { parseConnectionUri, parseConnectionString, normalizeOptions } from './uri';
6
7
  import type {
7
8
  Options,
8
9
  SvcMgrOptions,
@@ -47,6 +48,36 @@ export const ISOLATION_READ_COMMITTED_READ_ONLY: number[] = Const.ISOLATION_READ
47
48
 
48
49
  export const escape = escapeValue;
49
50
 
51
+ /**
52
+ * Firebird SQL type codes, as seen in `column.type` inside a `typeCast`
53
+ * hook (each code also has a friendly `column.typeName`).
54
+ */
55
+ export const SQL_TYPES: Readonly<Record<string, number>> = Object.freeze({
56
+ SQL_TEXT: Const.SQL_TEXT,
57
+ SQL_VARYING: Const.SQL_VARYING,
58
+ SQL_SHORT: Const.SQL_SHORT,
59
+ SQL_LONG: Const.SQL_LONG,
60
+ SQL_FLOAT: Const.SQL_FLOAT,
61
+ SQL_DOUBLE: Const.SQL_DOUBLE,
62
+ SQL_D_FLOAT: Const.SQL_D_FLOAT,
63
+ SQL_TIMESTAMP: Const.SQL_TIMESTAMP,
64
+ SQL_BLOB: Const.SQL_BLOB,
65
+ SQL_ARRAY: Const.SQL_ARRAY,
66
+ SQL_QUAD: Const.SQL_QUAD,
67
+ SQL_TYPE_TIME: Const.SQL_TYPE_TIME,
68
+ SQL_TYPE_DATE: Const.SQL_TYPE_DATE,
69
+ SQL_INT64: Const.SQL_INT64,
70
+ SQL_INT128: Const.SQL_INT128,
71
+ SQL_TIMESTAMP_TZ: Const.SQL_TIMESTAMP_TZ,
72
+ SQL_TIMESTAMP_TZ_EX: Const.SQL_TIMESTAMP_TZ_EX,
73
+ SQL_TIME_TZ: Const.SQL_TIME_TZ,
74
+ SQL_TIME_TZ_EX: Const.SQL_TIME_TZ_EX,
75
+ SQL_DEC16: Const.SQL_DEC16,
76
+ SQL_DEC34: Const.SQL_DEC34,
77
+ SQL_BOOLEAN: Const.SQL_BOOLEAN,
78
+ SQL_NULL: Const.SQL_NULL,
79
+ });
80
+
50
81
  /**
51
82
  * The most recent Connection created by attach()/create()/attachOrCreate().
52
83
  * Kept for backwards compatibility with the previous CommonJS module where
@@ -54,9 +85,10 @@ export const escape = escapeValue;
54
85
  */
55
86
  export let connection: Connection | undefined;
56
87
 
57
- export function attach(options: Options, callback: DatabaseCallback): void;
88
+ export function attach(options: Options | string, callback: DatabaseCallback): void;
58
89
  export function attach(options: SvcMgrOptions, callback: ServiceManagerCallback): void;
59
90
  export function attach(options: any, callback: any): void {
91
+ options = normalizeOptions(options);
60
92
  var host = options.host || Const.DEFAULT_HOST;
61
93
  var port = options.port || Const.DEFAULT_PORT;
62
94
  var manager = options.manager || false;
@@ -81,8 +113,8 @@ export function attach(options: any, callback: any): void {
81
113
  }, options);
82
114
  }
83
115
 
84
- export function drop(options: Options, callback: SimpleCallback): void {
85
- attach(options, function(err: any, db: any) {
116
+ export function drop(options: Options | string, callback: SimpleCallback): void {
117
+ attach(normalizeOptions(options), function(err: any, db: any) {
86
118
  if (err) {
87
119
  callback({ error: err, message: "Drop error" });
88
120
  return;
@@ -92,7 +124,8 @@ export function drop(options: Options, callback: SimpleCallback): void {
92
124
  });
93
125
  }
94
126
 
95
- export function create(options: Options, callback: DatabaseCallback): void {
127
+ export function create(options: Options | string, callback: DatabaseCallback): void {
128
+ options = normalizeOptions(options);
96
129
  var host = options.host || Const.DEFAULT_HOST;
97
130
  var port = options.port || Const.DEFAULT_PORT;
98
131
  var cnx = connection = new Connection(host, port, function(err: any) {
@@ -111,12 +144,13 @@ export function create(options: Options, callback: DatabaseCallback): void {
111
144
  return;
112
145
  }
113
146
 
114
- cnx.createDatabase(options, callback);
147
+ cnx.createDatabase(options, callback as any);
115
148
  });
116
149
  }, options);
117
150
  }
118
151
 
119
- export function attachOrCreate(options: Options, callback: DatabaseCallback): void {
152
+ export function attachOrCreate(options: Options | string, callback: DatabaseCallback): void {
153
+ options = normalizeOptions(options);
120
154
 
121
155
  var host = options.host || Const.DEFAULT_HOST;
122
156
  var port = options.port || Const.DEFAULT_PORT;
@@ -142,11 +176,13 @@ export function attachOrCreate(options: Options, callback: DatabaseCallback): vo
142
176
  if (!err) {
143
177
  if (self.db)
144
178
  self.db.emit('connect', ret);
145
- doCallback(ret, callback);
179
+ // DatabaseCallback stays permissive (db non-optional) for
180
+ // API users; internally the error path passes no db
181
+ doCallback(ret, callback as Callback<Database>);
146
182
  return;
147
183
  }
148
184
 
149
- cnx.createDatabase(options, callback);
185
+ cnx.createDatabase(options, callback as any);
150
186
  });
151
187
  });
152
188
 
@@ -154,10 +190,13 @@ export function attachOrCreate(options: Options, callback: DatabaseCallback): vo
154
190
  }
155
191
 
156
192
  // Pooling
157
- export function pool(max: number, options: Options): ConnectionPool {
158
- return new Pool(attach, max, Object.assign({}, options, { isPool: true }));
193
+ export function pool(max: number, options: Options | string): ConnectionPool {
194
+ return new Pool(attach, max, Object.assign({}, normalizeOptions(options), { isPool: true }));
159
195
  }
160
196
 
197
+ export { parseConnectionUri, parseConnectionString };
198
+ export { parseNamedPlaceholders } from './named-params';
199
+
161
200
  /*
162
201
  * Promise / async-await API.
163
202
  * Wrappers over the callback functions above; the callback API stays
@@ -166,19 +205,19 @@ export function pool(max: number, options: Options): ConnectionPool {
166
205
  */
167
206
 
168
207
  export function attachAsync(options: SvcMgrOptions): Promise<ServiceManager>;
169
- export function attachAsync(options: Options): Promise<Database>;
208
+ export function attachAsync(options: Options | string): Promise<Database>;
170
209
  export function attachAsync(options: any): Promise<any> {
171
210
  return fromCallback(function(cb) { attach(options, cb); });
172
211
  }
173
212
 
174
- export function createAsync(options: Options): Promise<Database> {
213
+ export function createAsync(options: Options | string): Promise<Database> {
175
214
  return fromCallback(function(cb) { create(options, cb); });
176
215
  }
177
216
 
178
- export function attachOrCreateAsync(options: Options): Promise<Database> {
217
+ export function attachOrCreateAsync(options: Options | string): Promise<Database> {
179
218
  return fromCallback(function(cb) { attachOrCreate(options, cb); });
180
219
  }
181
220
 
182
- export function dropAsync(options: Options): Promise<void> {
221
+ export function dropAsync(options: Options | string): Promise<void> {
183
222
  return fromCallback(function(cb) { drop(options, cb); });
184
223
  }
package/src/messages.ts CHANGED
@@ -138,7 +138,7 @@ export const lookupMessages = function(status: FbStatusItem[], messageFile: stri
138
138
  buffer = Buffer.alloc(bucket_size);
139
139
 
140
140
  var i = 0;
141
- var text;
141
+ var text: string | undefined;
142
142
 
143
143
  function loop() {
144
144
  lookup(status[i], function(line) {
@@ -0,0 +1,145 @@
1
+ /***************************************
2
+ *
3
+ * Named placeholders (:name → ?)
4
+ *
5
+ ***************************************/
6
+
7
+ /**
8
+ * Result of scanning a SQL string for named placeholders.
9
+ */
10
+ export interface ParsedNamedPlaceholders {
11
+ /** SQL with every named placeholder replaced by a positional "?". */
12
+ sql: string;
13
+ /**
14
+ * Placeholder names in positional order (a repeated name appears once
15
+ * per occurrence), or null when the SQL contains none.
16
+ */
17
+ names: string[] | null;
18
+ }
19
+
20
+ const IDENT_START = /[A-Za-z_]/;
21
+ const IDENT_PART = /[A-Za-z0-9_$]/;
22
+
23
+ // Parsing is pure string work, so identical SQL (the common case with
24
+ // query builders and hot paths) is scanned only once.
25
+ const CACHE_MAX = 100;
26
+ const cache = new Map<string, ParsedNamedPlaceholders>();
27
+
28
+ /**
29
+ * Scan `sql` for named placeholders (`:name`) and rewrite them to positional
30
+ * `?` markers, returning the rewritten SQL and the names in positional
31
+ * order. Placeholders inside string literals ('...'), quoted identifiers
32
+ * ("..."), line comments (--), block comments and Firebird alternative
33
+ * string literals (q'{...}') are left untouched.
34
+ *
35
+ * Note: the scanner has no SQL grammar — inside an EXECUTE BLOCK body every
36
+ * `:variable` reference looks like a placeholder too. Use positional params
37
+ * (or per-call `namedPlaceholders: false`) for EXECUTE BLOCK.
38
+ */
39
+ export function parseNamedPlaceholders(sql: string): ParsedNamedPlaceholders {
40
+ var cached = cache.get(sql);
41
+ if (cached)
42
+ return cached;
43
+
44
+ var out = '';
45
+ var names: string[] = [];
46
+ var i = 0;
47
+ var n = sql.length;
48
+
49
+ while (i < n) {
50
+ var c = sql[i];
51
+
52
+ if (c === "'" || c === '"') {
53
+ // string literal or quoted identifier; doubled quotes escape
54
+ var quote = c;
55
+ var end = i + 1;
56
+ while (end < n) {
57
+ if (sql[end] === quote) {
58
+ if (sql[end + 1] === quote) {
59
+ end += 2;
60
+ continue;
61
+ }
62
+ end++;
63
+ break;
64
+ }
65
+ end++;
66
+ }
67
+ out += sql.slice(i, end);
68
+ i = end;
69
+ } else if (c === '-' && sql[i + 1] === '-') {
70
+ var eol = sql.indexOf('\n', i);
71
+ if (eol === -1) eol = n;
72
+ out += sql.slice(i, eol);
73
+ i = eol;
74
+ } else if (c === '/' && sql[i + 1] === '*') {
75
+ var close = sql.indexOf('*/', i + 2);
76
+ close = close === -1 ? n : close + 2;
77
+ out += sql.slice(i, close);
78
+ i = close;
79
+ } else if ((c === 'q' || c === 'Q') && sql[i + 1] === "'" && i + 2 < n &&
80
+ (i === 0 || !IDENT_PART.test(sql[i - 1]))) {
81
+ // Firebird 3+ alternative string literal: q'{...}' / q'!...!'
82
+ var open = sql[i + 2];
83
+ var closer = open === '(' ? ')'
84
+ : open === '[' ? ']'
85
+ : open === '{' ? '}'
86
+ : open === '<' ? '>'
87
+ : open;
88
+ var stop = sql.indexOf(closer + "'", i + 3);
89
+ stop = stop === -1 ? n : stop + 2;
90
+ out += sql.slice(i, stop);
91
+ i = stop;
92
+ } else if (c === ':' && i + 1 < n && IDENT_START.test(sql[i + 1])) {
93
+ var end2 = i + 2;
94
+ while (end2 < n && IDENT_PART.test(sql[end2]))
95
+ end2++;
96
+ names.push(sql.slice(i + 1, end2));
97
+ out += '?';
98
+ i = end2;
99
+ } else {
100
+ out += c;
101
+ i++;
102
+ }
103
+ }
104
+
105
+ var result: ParsedNamedPlaceholders = names.length
106
+ ? { sql: out, names: names }
107
+ : { sql: sql, names: null };
108
+
109
+ if (cache.size >= CACHE_MAX) {
110
+ cache.delete(cache.keys().next().value as string);
111
+ }
112
+ cache.set(sql, result);
113
+ return result;
114
+ }
115
+
116
+ /**
117
+ * True when `params` is a plain values-by-name object (and not one of the
118
+ * values the driver accepts as a single positional parameter, like Date or
119
+ * Buffer).
120
+ */
121
+ export function isNamedParamsObject(params: any): params is Record<string, any> {
122
+ return params !== null &&
123
+ typeof params === 'object' &&
124
+ !Array.isArray(params) &&
125
+ !Buffer.isBuffer(params) &&
126
+ !(params instanceof Date);
127
+ }
128
+
129
+ /**
130
+ * Map a values-by-name object onto the positional order collected by
131
+ * parseNamedPlaceholders. A name may be bound multiple times; every name
132
+ * must be an own property of `params` (a present key holding null is a
133
+ * NULL parameter, a missing key is an error).
134
+ */
135
+ export function bindNamedParams(names: string[], params: Record<string, any>): any[] {
136
+ var missing: string[] = [];
137
+ var values = names.map(function(name) {
138
+ if (!Object.prototype.hasOwnProperty.call(params, name))
139
+ missing.push(name);
140
+ return params[name];
141
+ });
142
+ if (missing.length)
143
+ throw new Error('Missing value for named placeholder(s): ' + missing.join(', '));
144
+ return values;
145
+ }
package/src/pool.ts CHANGED
@@ -161,7 +161,7 @@ class Pool extends Events.EventEmitter {
161
161
  if ((self.dbinuse + self._creating) >= self.max)
162
162
  return self;
163
163
 
164
- var cb = self.pending.shift();
164
+ const cb = self.pending.shift();
165
165
  if (!cb)
166
166
  return self;
167
167
  if (self.pooldb.length) {
package/src/srp.ts CHANGED
@@ -67,7 +67,7 @@ export function clientSeed(a: bigint = toBigInt(crypto.randomBytes(SRP_KEY_SIZE)
67
67
  * @param b BigInt Server private key.
68
68
  * @returns {{private: BigInt, public: BigInt}}
69
69
  */
70
- export function serverSeed(user: string, password: string, salt: Buffer, b?: bigint | string, hashAlgo: string = 'sha1'): KeyPair {
70
+ export function serverSeed(user: string, password: string, salt: Buffer | string, b?: bigint | string, hashAlgo: string = 'sha1'): KeyPair {
71
71
  if (typeof b === 'string') {
72
72
  hashAlgo = b;
73
73
  b = undefined;
@@ -104,7 +104,7 @@ export function serverSeed(user: string, password: string, salt: Buffer, b?: big
104
104
  * @param b BigInt Server private key.
105
105
  * @returns {BigInt}
106
106
  */
107
- export function serverSession(user: string, password: string, salt: Buffer, A: bigint, B: bigint, b: bigint, hashAlgo: string = 'sha1'): bigint {
107
+ export function serverSession(user: string, password: string, salt: Buffer | string, A: bigint, B: bigint, b: bigint, hashAlgo: string = 'sha1'): bigint {
108
108
  var u = getScramble(A, B, 'sha1');
109
109
  var v = getVerifier(user, password, salt, 'sha1');
110
110
  var vu = modPow(v, u, PRIME.N);
@@ -121,7 +121,7 @@ export function serverSession(user: string, password: string, salt: Buffer, A: b
121
121
  /**
122
122
  * M = H(H(N) xor H(g), H(I), s, A, B, K)
123
123
  */
124
- export function clientProof(user: string, password: string, salt: Buffer, A: bigint, B: bigint, a: bigint, hashAlgo: string = 'sha1'): ClientProof {
124
+ export function clientProof(user: string, password: string, salt: Buffer | string, A: bigint, B: bigint, a: bigint, hashAlgo: string = 'sha1'): ClientProof {
125
125
  var K = clientSession(user, password, salt, A, B, a, 'sha1');
126
126
  var n1, n2;
127
127
 
@@ -222,7 +222,7 @@ function getScramble(A: bigint, B: bigint, hashAlgo: string = 'sha1'): bigint {
222
222
  * @returns Buffer The raw session-key digest (fixed length, may start
223
223
  * with a zero byte — significant for the proof).
224
224
  */
225
- function clientSession(user: string, password: string, salt: Buffer, A: bigint, B: bigint, a: bigint, hashAlgo: string = 'sha1'): Buffer {
225
+ function clientSession(user: string, password: string, salt: Buffer | string, A: bigint, B: bigint, a: bigint, hashAlgo: string = 'sha1'): Buffer {
226
226
  var u = getScramble(A, B, 'sha1');
227
227
  var x = getUserHash(user, salt, password, 'sha1');
228
228
  var gx = modPow(PRIME.g, x, PRIME.N);
@@ -264,7 +264,7 @@ function clientSession(user: string, password: string, salt: Buffer, A: bigint,
264
264
  * @param password string Connection password.
265
265
  * @returns {BigInt}
266
266
  */
267
- function getUserHash(user: string, salt: Buffer, password: string, hashAlgo: string = 'sha1'): bigint {
267
+ function getUserHash(user: string, salt: Buffer | string, password: string, hashAlgo: string = 'sha1'): bigint {
268
268
  var hash1 = getHash(hashAlgo, user.toUpperCase(), ':', password);
269
269
  var hash2 = getHash(hashAlgo, salt, toBuffer(hash1));
270
270
 
@@ -279,7 +279,7 @@ function getUserHash(user: string, salt: Buffer, password: string, hashAlgo: str
279
279
  * @param salt BigInt Connection salt.
280
280
  * @returns {BigInt}
281
281
  */
282
- function getVerifier(user: string, password: string, salt: Buffer, hashAlgo: string = 'sha1'): bigint {
282
+ function getVerifier(user: string, password: string, salt: Buffer | string, hashAlgo: string = 'sha1'): bigint {
283
283
  return modPow(PRIME.g, getUserHash(user, salt, password, hashAlgo), PRIME.N);
284
284
  }
285
285