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/lib/uri.js ADDED
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ /***************************************
3
+ *
4
+ * Connection URI strings
5
+ *
6
+ ***************************************/
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.parseConnectionUri = parseConnectionUri;
9
+ exports.parseOldStyleConnectionString = parseOldStyleConnectionString;
10
+ exports.parseConnectionString = parseConnectionString;
11
+ exports.normalizeOptions = normalizeOptions;
12
+ /**
13
+ * Option keys coerced to boolean when they arrive as URI query parameters.
14
+ * "1"/"true"/"yes"/"on" (case-insensitive) → true, everything else → false.
15
+ */
16
+ const BOOLEAN_KEYS = new Set([
17
+ 'lowercase_keys', 'blobAsText', 'wireCompression', 'manager',
18
+ 'namedPlaceholders',
19
+ ]);
20
+ /** Option keys coerced to number when they arrive as URI query parameters. */
21
+ const NUMBER_KEYS = new Set([
22
+ 'port', 'pageSize', 'timeout', 'retryConnectionInterval',
23
+ 'blobChunkSize', 'blobReadChunkSize', 'wireCrypt', 'parallelWorkers',
24
+ 'maxInlineBlobSize', 'maxNegotiatedProtocols', 'connectTimeout',
25
+ 'min', 'idleTimeoutMillis',
26
+ ]);
27
+ function coerce(key, value) {
28
+ if (BOOLEAN_KEYS.has(key)) {
29
+ return /^(1|true|yes|on)$/i.test(value);
30
+ }
31
+ if (NUMBER_KEYS.has(key)) {
32
+ var n = Number(value);
33
+ if (Number.isNaN(n)) {
34
+ throw new Error('Invalid numeric value for connection URI option "' + key + '": ' + value);
35
+ }
36
+ return n;
37
+ }
38
+ return value;
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
+ function parseConnectionUri(uri) {
60
+ var url;
61
+ try {
62
+ url = new URL(uri);
63
+ }
64
+ catch (e) {
65
+ throw new Error('Invalid connection URI: ' + uri);
66
+ }
67
+ if (url.protocol !== 'firebird:') {
68
+ throw new Error('Unsupported connection URI scheme "' + url.protocol.replace(/:$/, '') +
69
+ '" (expected firebird://...)');
70
+ }
71
+ var options = {};
72
+ if (url.hostname) {
73
+ // URL keeps IPv6 hostnames bracketed ([::1]); net.connect wants them bare
74
+ options.host = url.hostname.replace(/^\[(.*)\]$/, '$1');
75
+ }
76
+ if (url.port) {
77
+ options.port = Number(url.port);
78
+ }
79
+ if (url.username) {
80
+ options.user = decodeURIComponent(url.username);
81
+ }
82
+ if (url.password) {
83
+ options.password = decodeURIComponent(url.password);
84
+ }
85
+ var database = decodeURIComponent(url.pathname || '');
86
+ if (database.startsWith('/')) {
87
+ database = database.slice(1);
88
+ }
89
+ // A database part with path separators is a filesystem path, not an
90
+ // alias (aliases cannot contain "/") — restore the leading slash unless
91
+ // it is a Windows drive path or already absolute (double-slash form).
92
+ if (database.includes('/') && !database.startsWith('/') && !/^[A-Za-z]:\//.test(database)) {
93
+ database = '/' + database;
94
+ }
95
+ if (database) {
96
+ options.database = database;
97
+ }
98
+ url.searchParams.forEach(function (value, key) {
99
+ options[key] = coerce(key, value);
100
+ });
101
+ return options;
102
+ }
103
+ /**
104
+ * Parse a traditional ("old style") Firebird connection string:
105
+ *
106
+ * [host[/port]:]{path | alias}
107
+ *
108
+ * employee → alias "employee" (default host)
109
+ * /var/fb/prod.fdb → local path (default host)
110
+ * C:\fbdata\prod.fdb → Windows path — a single character
111
+ * before ":" is a drive letter, not
112
+ * a host (same rule as Firebird)
113
+ * db.example.com:employee → host + alias
114
+ * db.example.com/3051:/var/fb/prod.fdb → host + port + path
115
+ * myserver:C:\fbdata\prod.fdb → host + Windows path
116
+ * [::1]/3050:employee → IPv6 host + port + alias
117
+ *
118
+ * Unlike firebird:// URIs, traditional strings carry no credentials or
119
+ * options — the driver defaults apply (SYSDBA/masterkey, port 3050).
120
+ * The port must be numeric; /etc/services names are not resolved.
121
+ */
122
+ function parseOldStyleConnectionString(str) {
123
+ var options = {};
124
+ var host = null;
125
+ var port = null;
126
+ var database = str;
127
+ var ipv6 = /^\[([^\]]+)\](?:\/([^:]*))?:(.*)$/.exec(str);
128
+ if (ipv6) {
129
+ host = ipv6[1];
130
+ port = ipv6[2] !== undefined ? ipv6[2] : null;
131
+ database = ipv6[3];
132
+ }
133
+ else {
134
+ var colon = str.indexOf(':');
135
+ if (colon === 0) {
136
+ throw new Error('Invalid connection string (empty host): ' + str);
137
+ }
138
+ // colon === 1 → single character before ":" is a drive letter;
139
+ // colon === -1 → no host part. Both leave the whole string as database.
140
+ if (colon > 1) {
141
+ var hostPart = str.slice(0, colon);
142
+ database = str.slice(colon + 1);
143
+ var slash = hostPart.indexOf('/');
144
+ if (slash !== -1) {
145
+ host = hostPart.slice(0, slash);
146
+ port = hostPart.slice(slash + 1);
147
+ if (!host) {
148
+ throw new Error('Invalid connection string (empty host): ' + str);
149
+ }
150
+ }
151
+ else {
152
+ host = hostPart;
153
+ }
154
+ }
155
+ }
156
+ if (!database) {
157
+ throw new Error('Invalid connection string (empty database): ' + str);
158
+ }
159
+ if (host) {
160
+ options.host = host;
161
+ }
162
+ if (port !== null) {
163
+ var n = Number(port);
164
+ if (!/^\d+$/.test(port) || n < 1 || n > 65535) {
165
+ throw new Error('Invalid port in connection string "' + str +
166
+ '" (service names are not supported — use a numeric port)');
167
+ }
168
+ options.port = n;
169
+ }
170
+ options.database = database;
171
+ return options;
172
+ }
173
+ const URI_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*:\/\//;
174
+ /**
175
+ * Parse any connection string the driver accepts: a firebird:// URI, or a
176
+ * traditional [host[/port]:]database string when there is no scheme.
177
+ */
178
+ function parseConnectionString(str) {
179
+ return URI_SCHEME.test(str)
180
+ ? parseConnectionUri(str)
181
+ : parseOldStyleConnectionString(str);
182
+ }
183
+ /**
184
+ * Accept either an options object or a connection string (firebird:// URI
185
+ * or traditional host[/port]:database) everywhere options are taken.
186
+ * Strings are parsed; objects pass through unchanged.
187
+ */
188
+ function normalizeOptions(options) {
189
+ if (typeof options === 'string') {
190
+ return parseConnectionString(options);
191
+ }
192
+ return options;
193
+ }
@@ -84,6 +84,14 @@ class Connection {
84
84
  this._isDetach = false;
85
85
  this._isUsed = false;
86
86
  this._pooled = options.isPool || false;
87
+ // Credentials may be absent (e.g. a traditional host:database
88
+ // connection string) — apply the driver defaults once here, so every
89
+ // auth path (op_connect CNCT block, SRP proof, legacy cont_auth) sees
90
+ // the same values.
91
+ if (options && !options.user)
92
+ options.user = const_1.default.DEFAULT_USER;
93
+ if (options && !options.password)
94
+ options.password = const_1.default.DEFAULT_PASSWORD;
87
95
  if (options && options.blobChunkSize > 65535)
88
96
  options.blobChunkSize = 65535;
89
97
  if (options && options.blobReadChunkSize > 65535)
@@ -339,8 +347,17 @@ class Connection {
339
347
  }
340
348
  const canDefer = defer && this.accept.protocolVersion >= const_1.default.PROTOCOL_VERSION11;
341
349
  self._socket.write(self._msg.getData(), canDefer);
342
- if (canDefer && callback) {
343
- callback();
350
+ if (canDefer) {
351
+ // A deferred packet sits in the socket buffer until the next
352
+ // non-deferred write flushes it, but the server still answers it
353
+ // with its own op_response (delivered along with that next
354
+ // exchange). Queue a placeholder to consume that response —
355
+ // otherwise the queue pairs it with the NEXT request and every
356
+ // later response is off by one. The op itself is fire-and-forget,
357
+ // so complete the caller right away.
358
+ self._queue.push(undefined);
359
+ if (callback)
360
+ callback();
344
361
  }
345
362
  else {
346
363
  self._queue.push(callback);
@@ -12,6 +12,12 @@ declare class Statement {
12
12
  options: any;
13
13
  handle: number;
14
14
  plan: string;
15
+ /**
16
+ * Placeholder names in positional order when this statement was
17
+ * prepared from SQL with named placeholders (namedPlaceholders on),
18
+ * null/undefined otherwise. Set by Transaction.newStatement.
19
+ */
20
+ namedParams?: string[] | null;
15
21
  [key: string]: any;
16
22
  constructor(connection: any);
17
23
  close(callback?: (err?: any) => void): void;
@@ -23,7 +29,9 @@ declare class Statement {
23
29
  fetchAll(transaction: any, callback: (err: any, result?: any) => void): void;
24
30
  /**
25
31
  * Execute this statement once per row via the Firebird 4 batch API
26
- * (protocol 16+). `rows` is an array of parameter arrays.
32
+ * (protocol 16+). `rows` is an array of parameter arrays — or, when the
33
+ * statement was prepared with named placeholders, of values-by-name
34
+ * objects (the two forms can be mixed).
27
35
  */
28
36
  executeBatch(transaction: any, rows: any[][], callback?: any, options?: any): void;
29
37
  executeAsync(transaction: any, params?: any, options?: any): Promise<any>;
@@ -5,6 +5,7 @@
5
5
  *
6
6
  ***************************************/
7
7
  const callback_1 = require("../callback");
8
+ const named_params_1 = require("../named-params");
8
9
  class Statement {
9
10
  constructor(connection) {
10
11
  this.connection = connection;
@@ -28,6 +29,15 @@ class Statement {
28
29
  callback = params;
29
30
  params = undefined;
30
31
  }
32
+ if (this.namedParams && (0, named_params_1.isNamedParamsObject)(params)) {
33
+ try {
34
+ params = (0, named_params_1.bindNamedParams)(this.namedParams, params);
35
+ }
36
+ catch (err) {
37
+ (0, callback_1.doError)(err, callback);
38
+ return;
39
+ }
40
+ }
31
41
  this.options = options;
32
42
  this.connection.executeStatement(transaction, this, params, callback, options);
33
43
  }
@@ -51,9 +61,23 @@ class Statement {
51
61
  }
52
62
  /**
53
63
  * Execute this statement once per row via the Firebird 4 batch API
54
- * (protocol 16+). `rows` is an array of parameter arrays.
64
+ * (protocol 16+). `rows` is an array of parameter arrays — or, when the
65
+ * statement was prepared with named placeholders, of values-by-name
66
+ * objects (the two forms can be mixed).
55
67
  */
56
68
  executeBatch(transaction, rows, callback, options) {
69
+ var names = this.namedParams;
70
+ if (names && Array.isArray(rows)) {
71
+ try {
72
+ rows = rows.map(function (row) {
73
+ return (0, named_params_1.isNamedParamsObject)(row) ? (0, named_params_1.bindNamedParams)(names, row) : row;
74
+ });
75
+ }
76
+ catch (err) {
77
+ (0, callback_1.doError)(err, callback);
78
+ return;
79
+ }
80
+ }
57
81
  this.connection.executeBatch(transaction, this, rows, callback, options);
58
82
  }
59
83
  /* Promise / async-await API — wrappers over the callback methods above. */
@@ -4,7 +4,9 @@ declare class Transaction {
4
4
  handle: number;
5
5
  [key: string]: any;
6
6
  constructor(connection: any);
7
- newStatement(query: string, callback: (err: any, statement?: any) => void): void;
7
+ /** Per-call options.namedPlaceholders overrides the connection option. */
8
+ private namedPlaceholdersEnabled;
9
+ newStatement(query: string, callback: (err: any, statement?: any) => void, options?: any): void;
8
10
  execute(query: string, params?: any, callback?: any, options?: any): void;
9
11
  sequentially(query: string, params?: any, on?: any, callback?: any, options?: any): this;
10
12
  query(query: string, params?: any, callback?: any, options?: any): void;
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  const callback_1 = require("../callback");
6
+ const named_params_1 = require("../named-params");
6
7
  const utils_1 = require("../utils");
7
8
  const const_1 = __importDefault(require("./const"));
8
9
  /***************************************
@@ -45,15 +46,37 @@ class Transaction {
45
46
  this.connection = connection;
46
47
  this.db = connection.db;
47
48
  }
48
- newStatement(query, callback) {
49
+ /** Per-call options.namedPlaceholders overrides the connection option. */
50
+ namedPlaceholdersEnabled(options) {
51
+ if (options && options.namedPlaceholders !== undefined)
52
+ return !!options.namedPlaceholders;
53
+ return !!(this.connection.options && this.connection.options.namedPlaceholders);
54
+ }
55
+ newStatement(query, callback, options) {
49
56
  var cnx = this.connection;
50
57
  var self = this;
58
+ // With namedPlaceholders on, prepare the positional rewrite and
59
+ // remember the name order on the statement so statement.execute can
60
+ // accept a values-by-name object. The rewritten SQL is the cache key.
61
+ var names = null;
62
+ if (this.namedPlaceholdersEnabled(options)) {
63
+ var parsed = (0, named_params_1.parseNamedPlaceholders)(query);
64
+ if (parsed.names) {
65
+ query = parsed.sql;
66
+ names = parsed.names;
67
+ }
68
+ }
69
+ var deliver = function (err, statement) {
70
+ if (statement)
71
+ statement.namedParams = names;
72
+ callback(err, statement);
73
+ };
51
74
  var query_cache = cnx.getCachedQuery(query);
52
75
  if (query_cache) {
53
- callback(null, query_cache);
76
+ deliver(null, query_cache);
54
77
  }
55
78
  else {
56
- cnx.prepare(self, query, false, callback);
79
+ cnx.prepare(self, query, false, deliver);
57
80
  }
58
81
  }
59
82
  execute(query, params, callback, options) {
@@ -124,7 +147,7 @@ class Transaction {
124
147
  break;
125
148
  }
126
149
  }, options);
127
- });
150
+ }, options);
128
151
  }
129
152
  sequentially(query, params, on, callback, options = {}) {
130
153
  if (params instanceof Function) {
@@ -221,7 +244,7 @@ class Transaction {
221
244
  if (callback)
222
245
  callback(err, result);
223
246
  }, options);
224
- });
247
+ }, options);
225
248
  }
226
249
  executeBatchAsync(query, rows, options) {
227
250
  var self = this;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-firebird",
3
- "version": "2.8.1",
3
+ "version": "2.9.0",
4
4
  "description": "Pure JavaScript and Asynchronous Firebird client for Node.js.",
5
5
  "keywords": [
6
6
  "firebird",
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ import { doError, doCallback, fromCallback } 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,
@@ -54,9 +55,10 @@ export const escape = escapeValue;
54
55
  */
55
56
  export let connection: Connection | undefined;
56
57
 
57
- export function attach(options: Options, callback: DatabaseCallback): void;
58
+ export function attach(options: Options | string, callback: DatabaseCallback): void;
58
59
  export function attach(options: SvcMgrOptions, callback: ServiceManagerCallback): void;
59
60
  export function attach(options: any, callback: any): void {
61
+ options = normalizeOptions(options);
60
62
  var host = options.host || Const.DEFAULT_HOST;
61
63
  var port = options.port || Const.DEFAULT_PORT;
62
64
  var manager = options.manager || false;
@@ -81,8 +83,8 @@ export function attach(options: any, callback: any): void {
81
83
  }, options);
82
84
  }
83
85
 
84
- export function drop(options: Options, callback: SimpleCallback): void {
85
- attach(options, function(err: any, db: any) {
86
+ export function drop(options: Options | string, callback: SimpleCallback): void {
87
+ attach(normalizeOptions(options), function(err: any, db: any) {
86
88
  if (err) {
87
89
  callback({ error: err, message: "Drop error" });
88
90
  return;
@@ -92,7 +94,8 @@ export function drop(options: Options, callback: SimpleCallback): void {
92
94
  });
93
95
  }
94
96
 
95
- export function create(options: Options, callback: DatabaseCallback): void {
97
+ export function create(options: Options | string, callback: DatabaseCallback): void {
98
+ options = normalizeOptions(options);
96
99
  var host = options.host || Const.DEFAULT_HOST;
97
100
  var port = options.port || Const.DEFAULT_PORT;
98
101
  var cnx = connection = new Connection(host, port, function(err: any) {
@@ -116,7 +119,8 @@ export function create(options: Options, callback: DatabaseCallback): void {
116
119
  }, options);
117
120
  }
118
121
 
119
- export function attachOrCreate(options: Options, callback: DatabaseCallback): void {
122
+ export function attachOrCreate(options: Options | string, callback: DatabaseCallback): void {
123
+ options = normalizeOptions(options);
120
124
 
121
125
  var host = options.host || Const.DEFAULT_HOST;
122
126
  var port = options.port || Const.DEFAULT_PORT;
@@ -154,10 +158,13 @@ export function attachOrCreate(options: Options, callback: DatabaseCallback): vo
154
158
  }
155
159
 
156
160
  // Pooling
157
- export function pool(max: number, options: Options): ConnectionPool {
158
- return new Pool(attach, max, Object.assign({}, options, { isPool: true }));
161
+ export function pool(max: number, options: Options | string): ConnectionPool {
162
+ return new Pool(attach, max, Object.assign({}, normalizeOptions(options), { isPool: true }));
159
163
  }
160
164
 
165
+ export { parseConnectionUri, parseConnectionString };
166
+ export { parseNamedPlaceholders } from './named-params';
167
+
161
168
  /*
162
169
  * Promise / async-await API.
163
170
  * Wrappers over the callback functions above; the callback API stays
@@ -166,19 +173,19 @@ export function pool(max: number, options: Options): ConnectionPool {
166
173
  */
167
174
 
168
175
  export function attachAsync(options: SvcMgrOptions): Promise<ServiceManager>;
169
- export function attachAsync(options: Options): Promise<Database>;
176
+ export function attachAsync(options: Options | string): Promise<Database>;
170
177
  export function attachAsync(options: any): Promise<any> {
171
178
  return fromCallback(function(cb) { attach(options, cb); });
172
179
  }
173
180
 
174
- export function createAsync(options: Options): Promise<Database> {
181
+ export function createAsync(options: Options | string): Promise<Database> {
175
182
  return fromCallback(function(cb) { create(options, cb); });
176
183
  }
177
184
 
178
- export function attachOrCreateAsync(options: Options): Promise<Database> {
185
+ export function attachOrCreateAsync(options: Options | string): Promise<Database> {
179
186
  return fromCallback(function(cb) { attachOrCreate(options, cb); });
180
187
  }
181
188
 
182
- export function dropAsync(options: Options): Promise<void> {
189
+ export function dropAsync(options: Options | string): Promise<void> {
183
190
  return fromCallback(function(cb) { drop(options, cb); });
184
191
  }
@@ -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
+ }