node-firebird 2.8.1 → 2.9.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/src/types.ts CHANGED
@@ -100,9 +100,22 @@ export type BatchOptions = {
100
100
  chunkSize?: number;
101
101
  };
102
102
 
103
+ /**
104
+ * Positional query parameters (array), or — when named placeholders are
105
+ * enabled via the `namedPlaceholders` connection/query option — values by
106
+ * placeholder name.
107
+ */
108
+ export type QueryParams = any[] | Record<string, any>;
109
+
103
110
  export type QueryOptions = {
104
111
  timeout?: number;
105
112
  scrollable?: boolean;
113
+ /**
114
+ * Per-query override of the `namedPlaceholders` connection option
115
+ * (e.g. disable it for one EXECUTE BLOCK statement whose body uses
116
+ * `:variable` PSQL references).
117
+ */
118
+ namedPlaceholders?: boolean;
106
119
  /**
107
120
  * Abort the query when the signal fires (Firebird 2.5+ / protocol 12+).
108
121
  * If the signal is already aborted the query is not sent at all and the
@@ -118,11 +131,11 @@ export interface Database {
118
131
  detach(callback?: SimpleCallback): Database;
119
132
  transaction(options: TransactionOptions|Isolation|TransactionCallback, callback?: TransactionCallback): Database;
120
133
  newStatement(query: string, callback: (err: Error | null, statement: Statement) => void): Database;
121
- query(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): Database;
122
- execute(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): Database;
134
+ query(query: string, params: QueryParams, callback: QueryCallback, options?: QueryOptions): Database;
135
+ execute(query: string, params: QueryParams, callback: QueryCallback, options?: QueryOptions): Database;
123
136
  /** Bulk-execute in its own transaction, all-or-nothing (Firebird 4.0+). */
124
- executeBatch(query: string, rows: any[][], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): Database;
125
- sequentially(query: string, params: any[], rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
137
+ executeBatch(query: string, rows: QueryParams[], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): Database;
138
+ sequentially(query: string, params: QueryParams, rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
126
139
  drop(callback: SimpleCallback): void;
127
140
  escape(value: any): string;
128
141
  attachEvent(callback: any): this;
@@ -133,10 +146,10 @@ export interface Database {
133
146
 
134
147
  // Promise / async-await API (see README § Promises / async–await).
135
148
  // Result metadata is only available through the callback API.
136
- queryAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
137
- executeAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
138
- executeBatchAsync(query: string, rows: any[][], options?: BatchOptions): Promise<BatchResult>;
139
- sequentiallyAsync(query: string, params: any[] | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
149
+ queryAsync<T = any>(query: string, params?: QueryParams, options?: QueryOptions): Promise<T[]>;
150
+ executeAsync<T = any>(query: string, params?: QueryParams, options?: QueryOptions): Promise<T[]>;
151
+ executeBatchAsync(query: string, rows: QueryParams[], options?: BatchOptions): Promise<BatchResult>;
152
+ sequentiallyAsync(query: string, params: QueryParams | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
140
153
  sequentiallyAsync(query: string, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
141
154
  transactionAsync(options?: TransactionOptions | Isolation): Promise<Transaction>;
142
155
  startTransactionAsync(options?: TransactionOptions | Isolation): Promise<Transaction>;
@@ -158,21 +171,21 @@ export interface Database {
158
171
 
159
172
  export interface Transaction {
160
173
  newStatement(query: string, callback: (err: Error | null, statement: Statement) => void): void;
161
- query(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): void;
162
- execute(query: string, params: any[], callback: QueryCallback, options?: QueryOptions): void;
174
+ query(query: string, params: QueryParams, callback: QueryCallback, options?: QueryOptions): void;
175
+ execute(query: string, params: QueryParams, callback: QueryCallback, options?: QueryOptions): void;
163
176
  /** Bulk-execute within this transaction; per-record failures do not roll back (Firebird 4.0+). */
164
- executeBatch(query: string, rows: any[][], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
165
- sequentially(query: string, params: any[], rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
177
+ executeBatch(query: string, rows: QueryParams[], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
178
+ sequentially(query: string, params: QueryParams, rowCallback: SequentialCallback, callback: SimpleCallback, options?: QueryOptions | boolean): Database;
166
179
  commit(callback?: SimpleCallback): void;
167
180
  commitRetaining(callback?: SimpleCallback): void;
168
181
  rollback(callback?: SimpleCallback): void;
169
182
  rollbackRetaining(callback?: SimpleCallback): void;
170
183
 
171
184
  // Promise / async-await API
172
- queryAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
173
- executeAsync<T = any>(query: string, params?: any[], options?: QueryOptions): Promise<T[]>;
174
- executeBatchAsync(query: string, rows: any[][], options?: BatchOptions): Promise<BatchResult>;
175
- sequentiallyAsync(query: string, params: any[] | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
185
+ queryAsync<T = any>(query: string, params?: QueryParams, options?: QueryOptions): Promise<T[]>;
186
+ executeAsync<T = any>(query: string, params?: QueryParams, options?: QueryOptions): Promise<T[]>;
187
+ executeBatchAsync(query: string, rows: QueryParams[], options?: BatchOptions): Promise<BatchResult>;
188
+ sequentiallyAsync(query: string, params: QueryParams | undefined, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
176
189
  sequentiallyAsync(query: string, rowCallback: SequentialCallback, options?: QueryOptions | boolean): Promise<void>;
177
190
  newStatementAsync(query: string): Promise<Statement>;
178
191
  commitAsync(): Promise<void>;
@@ -185,17 +198,17 @@ export interface Statement {
185
198
  close(callback?: SimpleCallback): void;
186
199
  drop(callback?: SimpleCallback): void;
187
200
  release(callback?: SimpleCallback): void;
188
- execute(transaction: Transaction, params: any[], callback: QueryCallback, options?: QueryOptions): void;
201
+ execute(transaction: Transaction, params: QueryParams, callback: QueryCallback, options?: QueryOptions): void;
189
202
  fetch(transaction: Transaction, count: number, callback: QueryCallback): void;
190
203
  fetchScroll(transaction: Transaction, direction: 'NEXT' | 'PRIOR' | 'FIRST' | 'LAST' | 'ABSOLUTE' | 'RELATIVE' | number, offset: number, count: number, callback: QueryCallback): void;
191
204
  fetchAll(transaction: Transaction, callback: QueryCallback): void;
192
205
 
193
206
  /** Execute this prepared statement once per row (Firebird 4.0+ batch API). */
194
- executeBatch(transaction: Transaction, rows: any[][], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
207
+ executeBatch(transaction: Transaction, rows: QueryParams[], callback?: (err: any, result: BatchResult) => void, options?: BatchOptions): void;
195
208
 
196
209
  // Promise / async-await API
197
- executeAsync(transaction: Transaction, params?: any[], options?: QueryOptions): Promise<any>;
198
- executeBatchAsync(transaction: Transaction, rows: any[][], options?: BatchOptions): Promise<BatchResult>;
210
+ executeAsync(transaction: Transaction, params?: QueryParams, options?: QueryOptions): Promise<any>;
211
+ executeBatchAsync(transaction: Transaction, rows: QueryParams[], options?: BatchOptions): Promise<BatchResult>;
199
212
  fetchAsync(transaction: Transaction, count: number | 'all'): Promise<any>;
200
213
  fetchScrollAsync(transaction: Transaction, direction: 'NEXT' | 'PRIOR' | 'FIRST' | 'LAST' | 'ABSOLUTE' | 'RELATIVE' | number, offset?: number, count?: number): Promise<any>;
201
214
  fetchAllAsync(transaction: Transaction): Promise<any>;
@@ -266,6 +279,16 @@ export interface Options {
266
279
  blobReadChunkSize?: number;
267
280
  wireCrypt?: number; // WIRE_CRYPT_DISABLE or WIRE_CRYPT_ENABLE
268
281
  wireCompression?: boolean;
282
+ /**
283
+ * Enable named placeholders: SQL may use `:name` markers and params may
284
+ * be a values-by-name object (`db.query('... WHERE id = :id', { id: 1 })`).
285
+ * Placeholders are rewritten client-side to positional `?` before
286
+ * preparing; positional arrays keep working unchanged. Off by default
287
+ * because `EXECUTE BLOCK` bodies use `:variable` for PSQL references —
288
+ * with this option on, run such statements with positional params or a
289
+ * per-query `namedPlaceholders: false` override.
290
+ */
291
+ namedPlaceholders?: boolean;
269
292
  pluginName?: string;
270
293
  parallelWorkers?: number;
271
294
  maxInlineBlobSize?: number;
package/src/uri.ts ADDED
@@ -0,0 +1,204 @@
1
+ /***************************************
2
+ *
3
+ * Connection URI strings
4
+ *
5
+ ***************************************/
6
+
7
+ import type { Options } from './types';
8
+
9
+ /**
10
+ * Option keys coerced to boolean when they arrive as URI query parameters.
11
+ * "1"/"true"/"yes"/"on" (case-insensitive) → true, everything else → false.
12
+ */
13
+ const BOOLEAN_KEYS = new Set([
14
+ 'lowercase_keys', 'blobAsText', 'wireCompression', 'manager',
15
+ 'namedPlaceholders',
16
+ ]);
17
+
18
+ /** Option keys coerced to number when they arrive as URI query parameters. */
19
+ const NUMBER_KEYS = new Set([
20
+ 'port', 'pageSize', 'timeout', 'retryConnectionInterval',
21
+ 'blobChunkSize', 'blobReadChunkSize', 'wireCrypt', 'parallelWorkers',
22
+ 'maxInlineBlobSize', 'maxNegotiatedProtocols', 'connectTimeout',
23
+ 'min', 'idleTimeoutMillis',
24
+ ]);
25
+
26
+ function coerce(key: string, value: string): any {
27
+ if (BOOLEAN_KEYS.has(key)) {
28
+ return /^(1|true|yes|on)$/i.test(value);
29
+ }
30
+ if (NUMBER_KEYS.has(key)) {
31
+ var n = Number(value);
32
+ if (Number.isNaN(n)) {
33
+ throw new Error('Invalid numeric value for connection URI option "' + key + '": ' + value);
34
+ }
35
+ return n;
36
+ }
37
+ return value;
38
+ }
39
+
40
+ /**
41
+ * Parse a firebird:// connection URI into an options object.
42
+ *
43
+ * firebird://user:password@host:port/database?option=value&...
44
+ *
45
+ * The database part:
46
+ * firebird://host/employee → alias "employee"
47
+ * firebird://host//var/db/prod.fdb → absolute path "/var/db/prod.fdb"
48
+ * firebird://host/var/db/prod.fdb → "/var/db/prod.fdb" (a database
49
+ * part with slashes is a path —
50
+ * aliases cannot contain "/")
51
+ * firebird://host/C:/db/prod.fdb → Windows path "C:/db/prod.fdb"
52
+ *
53
+ * Credentials and the database path are URL-decoded, so reserved characters
54
+ * can be percent-encoded (e.g. p%40ss for "p@ss"). Query parameters map
55
+ * 1:1 to option keys and are coerced to the option's type (booleans accept
56
+ * 1/true/yes/on). `user` and `password` may be given as query parameters
57
+ * instead of in the authority.
58
+ */
59
+ export function parseConnectionUri(uri: string): Options {
60
+ var url: URL;
61
+ try {
62
+ url = new URL(uri);
63
+ } catch (e) {
64
+ throw new Error('Invalid connection URI: ' + uri);
65
+ }
66
+
67
+ if (url.protocol !== 'firebird:') {
68
+ throw new Error('Unsupported connection URI scheme "' + url.protocol.replace(/:$/, '') +
69
+ '" (expected firebird://...)');
70
+ }
71
+
72
+ var options: any = {};
73
+
74
+ if (url.hostname) {
75
+ // URL keeps IPv6 hostnames bracketed ([::1]); net.connect wants them bare
76
+ options.host = url.hostname.replace(/^\[(.*)\]$/, '$1');
77
+ }
78
+ if (url.port) {
79
+ options.port = Number(url.port);
80
+ }
81
+ if (url.username) {
82
+ options.user = decodeURIComponent(url.username);
83
+ }
84
+ if (url.password) {
85
+ options.password = decodeURIComponent(url.password);
86
+ }
87
+
88
+ var database = decodeURIComponent(url.pathname || '');
89
+ if (database.startsWith('/')) {
90
+ database = database.slice(1);
91
+ }
92
+ // A database part with path separators is a filesystem path, not an
93
+ // alias (aliases cannot contain "/") — restore the leading slash unless
94
+ // it is a Windows drive path or already absolute (double-slash form).
95
+ if (database.includes('/') && !database.startsWith('/') && !/^[A-Za-z]:\//.test(database)) {
96
+ database = '/' + database;
97
+ }
98
+ if (database) {
99
+ options.database = database;
100
+ }
101
+
102
+ url.searchParams.forEach(function(value, key) {
103
+ options[key] = coerce(key, value);
104
+ });
105
+
106
+ return options as Options;
107
+ }
108
+
109
+ /**
110
+ * Parse a traditional ("old style") Firebird connection string:
111
+ *
112
+ * [host[/port]:]{path | alias}
113
+ *
114
+ * employee → alias "employee" (default host)
115
+ * /var/fb/prod.fdb → local path (default host)
116
+ * C:\fbdata\prod.fdb → Windows path — a single character
117
+ * before ":" is a drive letter, not
118
+ * a host (same rule as Firebird)
119
+ * db.example.com:employee → host + alias
120
+ * db.example.com/3051:/var/fb/prod.fdb → host + port + path
121
+ * myserver:C:\fbdata\prod.fdb → host + Windows path
122
+ * [::1]/3050:employee → IPv6 host + port + alias
123
+ *
124
+ * Unlike firebird:// URIs, traditional strings carry no credentials or
125
+ * options — the driver defaults apply (SYSDBA/masterkey, port 3050).
126
+ * The port must be numeric; /etc/services names are not resolved.
127
+ */
128
+ export function parseOldStyleConnectionString(str: string): Options {
129
+ var options: any = {};
130
+ var host: string | null = null;
131
+ var port: string | null = null;
132
+ var database = str;
133
+
134
+ var ipv6 = /^\[([^\]]+)\](?:\/([^:]*))?:(.*)$/.exec(str);
135
+ if (ipv6) {
136
+ host = ipv6[1];
137
+ port = ipv6[2] !== undefined ? ipv6[2] : null;
138
+ database = ipv6[3];
139
+ } else {
140
+ var colon = str.indexOf(':');
141
+ if (colon === 0) {
142
+ throw new Error('Invalid connection string (empty host): ' + str);
143
+ }
144
+ // colon === 1 → single character before ":" is a drive letter;
145
+ // colon === -1 → no host part. Both leave the whole string as database.
146
+ if (colon > 1) {
147
+ var hostPart = str.slice(0, colon);
148
+ database = str.slice(colon + 1);
149
+ var slash = hostPart.indexOf('/');
150
+ if (slash !== -1) {
151
+ host = hostPart.slice(0, slash);
152
+ port = hostPart.slice(slash + 1);
153
+ if (!host) {
154
+ throw new Error('Invalid connection string (empty host): ' + str);
155
+ }
156
+ } else {
157
+ host = hostPart;
158
+ }
159
+ }
160
+ }
161
+
162
+ if (!database) {
163
+ throw new Error('Invalid connection string (empty database): ' + str);
164
+ }
165
+
166
+ if (host) {
167
+ options.host = host;
168
+ }
169
+ if (port !== null) {
170
+ var n = Number(port);
171
+ if (!/^\d+$/.test(port) || n < 1 || n > 65535) {
172
+ throw new Error('Invalid port in connection string "' + str +
173
+ '" (service names are not supported — use a numeric port)');
174
+ }
175
+ options.port = n;
176
+ }
177
+ options.database = database;
178
+
179
+ return options as Options;
180
+ }
181
+
182
+ const URI_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*:\/\//;
183
+
184
+ /**
185
+ * Parse any connection string the driver accepts: a firebird:// URI, or a
186
+ * traditional [host[/port]:]database string when there is no scheme.
187
+ */
188
+ export function parseConnectionString(str: string): Options {
189
+ return URI_SCHEME.test(str)
190
+ ? parseConnectionUri(str)
191
+ : parseOldStyleConnectionString(str);
192
+ }
193
+
194
+ /**
195
+ * Accept either an options object or a connection string (firebird:// URI
196
+ * or traditional host[/port]:database) everywhere options are taken.
197
+ * Strings are parsed; objects pass through unchanged.
198
+ */
199
+ export function normalizeOptions<T>(options: T | string): T {
200
+ if (typeof options === 'string') {
201
+ return parseConnectionString(options) as T;
202
+ }
203
+ return options;
204
+ }
@@ -90,6 +90,12 @@ class Connection {
90
90
  this._isDetach = false;
91
91
  this._isUsed = false;
92
92
  this._pooled = options.isPool||false;
93
+ // Credentials may be absent (e.g. a traditional host:database
94
+ // connection string) — apply the driver defaults once here, so every
95
+ // auth path (op_connect CNCT block, SRP proof, legacy cont_auth) sees
96
+ // the same values.
97
+ if (options && !options.user) options.user = Const.DEFAULT_USER;
98
+ if (options && !options.password) options.password = Const.DEFAULT_PASSWORD;
93
99
  if (options && options.blobChunkSize > 65535) options.blobChunkSize = 65535;
94
100
  if (options && options.blobReadChunkSize > 65535) options.blobReadChunkSize = 65535;
95
101
  this.options = options;
@@ -426,8 +432,17 @@ class Connection {
426
432
  const canDefer = defer && this.accept.protocolVersion >= Const.PROTOCOL_VERSION11;
427
433
 
428
434
  self._socket.write(self._msg.getData(), canDefer);
429
- if (canDefer && callback) {
430
- callback();
435
+ if (canDefer) {
436
+ // A deferred packet sits in the socket buffer until the next
437
+ // non-deferred write flushes it, but the server still answers it
438
+ // with its own op_response (delivered along with that next
439
+ // exchange). Queue a placeholder to consume that response —
440
+ // otherwise the queue pairs it with the NEXT request and every
441
+ // later response is off by one. The op itself is fire-and-forget,
442
+ // so complete the caller right away.
443
+ self._queue.push(undefined);
444
+ if (callback)
445
+ callback();
431
446
  } else {
432
447
  self._queue.push(callback);
433
448
  }
@@ -4,7 +4,8 @@
4
4
  *
5
5
  ***************************************/
6
6
 
7
- import { fromCallback } from '../callback';
7
+ import { doError, fromCallback } from '../callback';
8
+ import { bindNamedParams, isNamedParamsObject } from '../named-params';
8
9
 
9
10
  class Statement {
10
11
  connection: any;
@@ -15,6 +16,12 @@ class Statement {
15
16
  options: any;
16
17
  handle: number;
17
18
  plan: string;
19
+ /**
20
+ * Placeholder names in positional order when this statement was
21
+ * prepared from SQL with named placeholders (namedPlaceholders on),
22
+ * null/undefined otherwise. Set by Transaction.newStatement.
23
+ */
24
+ namedParams?: string[] | null;
18
25
  [key: string]: any;
19
26
 
20
27
  constructor(connection: any) {
@@ -44,6 +51,15 @@ class Statement {
44
51
  params = undefined;
45
52
  }
46
53
 
54
+ if (this.namedParams && isNamedParamsObject(params)) {
55
+ try {
56
+ params = bindNamedParams(this.namedParams, params);
57
+ } catch (err) {
58
+ doError(err, callback);
59
+ return;
60
+ }
61
+ }
62
+
47
63
  this.options = options;
48
64
  this.connection.executeStatement(transaction, this, params, callback, options);
49
65
  }
@@ -71,9 +87,22 @@ class Statement {
71
87
 
72
88
  /**
73
89
  * Execute this statement once per row via the Firebird 4 batch API
74
- * (protocol 16+). `rows` is an array of parameter arrays.
90
+ * (protocol 16+). `rows` is an array of parameter arrays — or, when the
91
+ * statement was prepared with named placeholders, of values-by-name
92
+ * objects (the two forms can be mixed).
75
93
  */
76
94
  executeBatch(transaction: any, rows: any[][], callback?: any, options?: any): void {
95
+ var names = this.namedParams;
96
+ if (names && Array.isArray(rows)) {
97
+ try {
98
+ rows = rows.map(function(row: any) {
99
+ return isNamedParamsObject(row) ? bindNamedParams(names as string[], row) : row;
100
+ });
101
+ } catch (err) {
102
+ doError(err, callback);
103
+ return;
104
+ }
105
+ }
77
106
  this.connection.executeBatch(transaction, this, rows, callback, options);
78
107
  }
79
108
 
@@ -1,4 +1,5 @@
1
1
  import { doCallback, doError, fromCallback } from '../callback';
2
+ import { parseNamedPlaceholders } from '../named-params';
2
3
  import { noop } from '../utils';
3
4
  import Const from './const';
4
5
 
@@ -51,15 +52,41 @@ class Transaction {
51
52
  this.db = connection.db;
52
53
  }
53
54
 
54
- newStatement(query: string, callback: (err: any, statement?: any) => void): void {
55
+ /** Per-call options.namedPlaceholders overrides the connection option. */
56
+ private namedPlaceholdersEnabled(options?: any): boolean {
57
+ if (options && options.namedPlaceholders !== undefined)
58
+ return !!options.namedPlaceholders;
59
+ return !!(this.connection.options && this.connection.options.namedPlaceholders);
60
+ }
61
+
62
+ newStatement(query: string, callback: (err: any, statement?: any) => void, options?: any): void {
55
63
  var cnx = this.connection;
56
64
  var self = this;
65
+
66
+ // With namedPlaceholders on, prepare the positional rewrite and
67
+ // remember the name order on the statement so statement.execute can
68
+ // accept a values-by-name object. The rewritten SQL is the cache key.
69
+ var names: string[] | null = null;
70
+ if (this.namedPlaceholdersEnabled(options)) {
71
+ var parsed = parseNamedPlaceholders(query);
72
+ if (parsed.names) {
73
+ query = parsed.sql;
74
+ names = parsed.names;
75
+ }
76
+ }
77
+
78
+ var deliver = function(err: any, statement?: any) {
79
+ if (statement)
80
+ statement.namedParams = names;
81
+ callback(err, statement);
82
+ };
83
+
57
84
  var query_cache = cnx.getCachedQuery(query);
58
85
 
59
86
  if (query_cache) {
60
- callback(null, query_cache);
87
+ deliver(null, query_cache);
61
88
  } else {
62
- cnx.prepare(self, query, false, callback);
89
+ cnx.prepare(self, query, false, deliver);
63
90
  }
64
91
  }
65
92
 
@@ -148,7 +175,7 @@ class Transaction {
148
175
  }
149
176
 
150
177
  }, options);
151
- });
178
+ }, options);
152
179
  }
153
180
 
154
181
  sequentially(query: string, params?: any, on?: any, callback?: any, options: any = {}): this {
@@ -255,7 +282,7 @@ class Transaction {
255
282
  if (callback)
256
283
  callback(err, result);
257
284
  }, options);
258
- });
285
+ }, options);
259
286
  }
260
287
 
261
288
  executeBatchAsync(query: string, rows: any[][], options?: any): Promise<any> {