@miatechnet/node-odbc 2.4.10-multiresult.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/lib/Pool.js ADDED
@@ -0,0 +1,470 @@
1
+ const EventEmitter = require('events');
2
+ const async = require('async');
3
+ const binary = require('@mapbox/node-pre-gyp');
4
+ const path = require('path');
5
+
6
+ const { Connection } = require('./Connection');
7
+
8
+ const bindingPath = binary.find(path.resolve(path.join(__dirname, '../package.json')));
9
+
10
+ const odbc = require(bindingPath);
11
+
12
+ const REUSE_CONNECTIONS_DEFAULT = true;
13
+ const INITIAL_SIZE_DEFAULT = 10;
14
+ const INCREMENT_SIZE_DEFAULT = 10;
15
+ const MAX_SIZE_DEFAULT = Number.MAX_SAFE_INTEGER;
16
+ const SHRINK_DEFAULT = true;
17
+ const CONNECTION_TIMEOUT_DEFAULT = 0;
18
+ const LOGIN_TIMEOUT_DEFAULT = 0;
19
+ const FETCH_ARRAY_DEFAULT = false;
20
+ const MAX_ACTIVELY_CONNECTING = 1;
21
+
22
+ // A queue for tracking the connections
23
+ class ConnectionQueue
24
+ {
25
+ static queuedConnections = [];
26
+ static activelyConnectingCount = 0;
27
+ static maxActivelyConnecting = MAX_ACTIVELY_CONNECTING;
28
+
29
+ static enqueue = async function(pool, promiseGenerator, configObject)
30
+ {
31
+ ConnectionQueue.queuedConnections.push({
32
+ pool,
33
+ promiseGenerator,
34
+ configObject
35
+ });
36
+ ConnectionQueue.dequeue();
37
+ return;
38
+ }
39
+
40
+ static dequeue = async function()
41
+ {
42
+ if (this.activelyConnectingCount >= this.maxActivelyConnecting) {
43
+ return;
44
+ }
45
+ const item = this.queuedConnections.shift();
46
+ if (!item) {
47
+ return;
48
+ }
49
+ ConnectionQueue.activelyConnectingCount++;
50
+ try {
51
+ await item.promiseGenerator(item.configObject);
52
+ } catch (error) {
53
+ // Errors are handled in the promiseGenerator through emitting a
54
+ // "connectionError" event. Error thrown and caught here simply to resolve
55
+ // the promise generated by the promiseGenerator.
56
+ }
57
+ ConnectionQueue.activelyConnectingCount--;
58
+ ConnectionQueue.dequeue();
59
+ return;
60
+ }
61
+ }
62
+
63
+ class Pool {
64
+
65
+ constructor(connectionString) {
66
+
67
+ this.connectionConfig = {};
68
+ this.waitingConnectionWork = [];
69
+ this.isOpen = false;
70
+ this.freeConnections = [];
71
+ this.connectionsBeingCreatedCount = 0;
72
+ this.poolSize = 0;
73
+
74
+ // Keeps track of when connections have sucessfully connected
75
+ this.connectionEmitter = new EventEmitter();
76
+
77
+ // Fires when a connection has been made
78
+ this.connectionEmitter.on('connected', (connection) => {
79
+
80
+ // A connection has finished connecting, but there is some waiting call
81
+ // to connect() that is waiting for a connection. shift() that work from
82
+ // the front of the waitingConnectionWork queue, then either call the
83
+ // callback function provided by the user, or resolve the Promise that was
84
+ // returned to the user.
85
+ let connectionWork = this.waitingConnectionWork.shift();
86
+
87
+ // A connection finished connecting, and there was no work waiting for
88
+ // that connection to be made. Simply add it to the array of
89
+ // freeConnections, and the next time work comes in it can simply be
90
+ // retrieved from there.
91
+ if (typeof connectionWork == 'undefined')
92
+ {
93
+ this.freeConnections.push(connection);
94
+ return;
95
+ }
96
+
97
+ // If the user passed a callback function, then call the function with
98
+ // no error in the first parameter and the connection in the second
99
+ // parameter
100
+ if (typeof connectionWork == 'function')
101
+ {
102
+ return connectionWork(null, connection);
103
+ }
104
+
105
+ // If the user didn't pass a callback function, we returned a promise to
106
+ // them. Resolve that promise with the connection that was just created.
107
+ // Promise (stored resolve function)
108
+ return connectionWork.resolveFunction(connection);
109
+ });
110
+
111
+ // Fires when a connection has errored. If there is no work waiting, then
112
+ // the pool will simply be empty until the next time work is requested, at
113
+ // which point there WILL be waiting work and they will get an error.
114
+ this.connectionEmitter.on('connectionError', (error) => {
115
+ let connectionWork = this.waitingConnectionWork.shift();
116
+ if (typeof connectionWork == 'undefined')
117
+ {
118
+ return;
119
+ }
120
+ // If the user passed a callback function, then call the function with
121
+ // the error in the first parameter
122
+ if (typeof connectionWork == 'function')
123
+ {
124
+ return connectionWork(error, undefined);
125
+ // If the user didn't pass a callback function, we returned a promise to
126
+ // them. Reject that promise with the generated error
127
+ } else {
128
+ // Promise (stored resolve function)
129
+ return connectionWork.rejectFunction(error);
130
+ }
131
+ });
132
+
133
+ // connectionString is a string, so use defaults for all of the
134
+ // configuration options.
135
+ if (typeof connectionString === 'string') {
136
+ this.connectionConfig.connectionString = connectionString;
137
+
138
+ this.reuseConnections = REUSE_CONNECTIONS_DEFAULT;
139
+ this.initialSize = INITIAL_SIZE_DEFAULT;
140
+ this.incrementSize = INCREMENT_SIZE_DEFAULT;
141
+ this.maxSize = MAX_SIZE_DEFAULT;
142
+ this.shrink = SHRINK_DEFAULT;
143
+ this.connectionConfig.connectionTimeout = CONNECTION_TIMEOUT_DEFAULT;
144
+ this.connectionConfig.loginTimeout = LOGIN_TIMEOUT_DEFAULT;
145
+ this.connectionConfig.fetchArray = FETCH_ARRAY_DEFAULT;
146
+ }
147
+ // connectionString is an object, so ensure that connectionString is a
148
+ // property on that object and then copy over any configuration options.
149
+ else if (typeof connectionString === 'object')
150
+ {
151
+ const configObject = connectionString;
152
+ if (!Object.prototype.hasOwnProperty.call(configObject, 'connectionString')) {
153
+ throw new TypeError('Pool configuration object must contain "connectionString" key');
154
+ }
155
+
156
+ this.connectionConfig.connectionString = configObject.connectionString;
157
+
158
+ // reuseConnections
159
+ this.reuseConnections = configObject.reuseConnections !== undefined ? configObject.reuseConnections : REUSE_CONNECTIONS_DEFAULT;
160
+
161
+ // initialSize
162
+ this.initialSize = configObject.initialSize !== undefined ? configObject.initialSize : INITIAL_SIZE_DEFAULT;
163
+
164
+ // incrementSize
165
+ this.incrementSize = configObject.incrementSize !== undefined ? configObject.incrementSize : INCREMENT_SIZE_DEFAULT;
166
+
167
+ // maxSize
168
+ this.maxSize = configObject.maxSize !== undefined ? configObject.maxSize : MAX_SIZE_DEFAULT;
169
+
170
+ // shrink
171
+ this.shrink = configObject.shrink !== undefined ? configObject.shrink : SHRINK_DEFAULT;
172
+
173
+ // connectionTimeout
174
+ this.connectionConfig.connectionTimeout = configObject.connectionTimeout !== undefined ? configObject.connectionTimeout : CONNECTION_TIMEOUT_DEFAULT;
175
+
176
+ // loginTimeout
177
+ this.connectionConfig.loginTimeout = configObject.loginTimeout !== undefined ? configObject.loginTimeout : LOGIN_TIMEOUT_DEFAULT;
178
+
179
+ // fetchArray
180
+ this.connectionConfig.fetchArray = configObject.fetchArray || FETCH_ARRAY_DEFAULT;
181
+
182
+ // connectingQueueMax
183
+ // unlike other configuration values, this one is set statically on the
184
+ // ConnectionQueue object and not on the Pool intance
185
+ if (configObject.maxActivelyConnecting !== undefined)
186
+ {
187
+ ConnectionQueue.maxActivelyConnecting = configObject.maxActivelyConnecting
188
+ }
189
+ }
190
+ // connectionString was neither a string nor and object, so throw an error.
191
+ else
192
+ {
193
+ throw TypeError('Pool constructor must passed a connection string or a configuration object');
194
+ }
195
+ }
196
+
197
+ // returns a open connection, ready to use.
198
+ // should overwrite the 'close' function of the connection, and rename it is 'nativeClose', so
199
+ // that that close can still be called.
200
+ async connect(callback = undefined) {
201
+
202
+ let connection;
203
+
204
+ if (this.freeConnections.length == 0) {
205
+
206
+ // If the number of connections waiting is more (shouldn't happen) or
207
+ // equal to the number of connections connecting, and the number of
208
+ // connections in the pool, in the process of connecting, and that will be
209
+ // added is less than the maximum number of allowable connections, then
210
+ // we will need to create MORE connections.
211
+ if (this.connectionsBeingCreatedCount <= this.waitingConnectionWork.length &&
212
+ this.poolSize + this.connectionsBeingCreatedCount + this.incrementSize <= this.maxSize)
213
+ {
214
+ this.increasePoolSize(this.incrementSize);
215
+ }
216
+
217
+ // If no callback was provided when connect was called, we need to create
218
+ // a promise to return back. We also need to save off the resolve function
219
+ // of that promises callback, so that we can call it to resolve the
220
+ // function we returned
221
+ if (typeof callback == 'undefined') {
222
+ let resolveConnectionPromise;
223
+ let rejectConnectionPromise;
224
+
225
+ const promise = new Promise((resolve, reject) => {
226
+ resolveConnectionPromise = resolve;
227
+ rejectConnectionPromise = reject;
228
+ });
229
+ const promiseObj = {
230
+ promise: promise,
231
+ resolveFunction: resolveConnectionPromise,
232
+ rejectFunction: rejectConnectionPromise,
233
+ }
234
+ // push the promise onto the waitingConnectionWork queue, then return
235
+ // it to the user
236
+ this.waitingConnectionWork.push(promiseObj);
237
+ return promise;
238
+ }
239
+ // If a callback was provided, we can just add that to the
240
+ // waitingConnectionWork queue, then return undefined to the user. Their
241
+ // callback will execute when a connection is ready
242
+ else
243
+ {
244
+ this.waitingConnectionWork.push(callback)
245
+ return undefined;
246
+ }
247
+ }
248
+ // Else, there was a free connection available for the user, so either
249
+ // return an immediately resolved promise, or call their callback
250
+ // immediately.
251
+ else
252
+ {
253
+ connection = this.freeConnections.pop();
254
+
255
+ // promise...
256
+ if (typeof callback === 'undefined') {
257
+ return Promise.resolve(connection);
258
+ } else {
259
+ // ...or callback
260
+ return callback(null, connection);
261
+ }
262
+ }
263
+ };
264
+
265
+ async query(sql, params, opts, cb) {
266
+ // determine the parameters passed
267
+ let callback = cb;
268
+ let parameters = params;
269
+ let options = opts;
270
+
271
+ if (typeof callback === 'undefined') {
272
+ if (typeof options === 'function')
273
+ {
274
+ callback = options;
275
+ if (typeof parameters === 'object' && !Array.isArray(parameters))
276
+ {
277
+ options = parameters;
278
+ parameters = null;
279
+ } else {
280
+ options = null;
281
+ }
282
+ } else if (
283
+ typeof options === 'undefined' &&
284
+ typeof parameters === 'function')
285
+ {
286
+ callback = parameters;
287
+ options = null;
288
+ parameters = null;
289
+ }
290
+ }
291
+
292
+ if (typeof callback !== 'function') {
293
+ return new Promise((resolve, reject) => {
294
+ this.connect((error, connection) => {
295
+ if (error) {
296
+ reject(error);
297
+ }
298
+
299
+ connection.query(sql, parameters, options, (error, result) => {
300
+ if (error) {
301
+ reject(error);
302
+ } else {
303
+ resolve(result);
304
+ }
305
+ connection.close();
306
+ });
307
+ });
308
+ });
309
+ }
310
+
311
+ // ...or callback
312
+ this.connect((error, connection) => {
313
+ if (error) {
314
+ throw error;
315
+ }
316
+
317
+ return connection.query(sql, parameters, options, (error, result) => {
318
+ // after running, close the connection whether error or not
319
+ process.nextTick(() => {
320
+ callback(error, result);
321
+ });
322
+ connection.close();
323
+ });
324
+ });
325
+ }
326
+
327
+ // close the pool and all of the connections
328
+ async close(callback = undefined) {
329
+ const connections = [...this.freeConnections];
330
+ this.freeConnections.length = 0;
331
+ this.isOpen = false;
332
+
333
+ if (typeof callback === 'undefined') {
334
+ return new Promise((resolve, reject) => {
335
+ async.each(connections, (connection, cb) => {
336
+ connection.nativeClose((error) => {
337
+ this.poolSize--;
338
+ cb(error);
339
+ });
340
+ }, (error) => {
341
+ if (error) {
342
+ reject(error);
343
+ } else {
344
+ resolve(error);
345
+ }
346
+ });
347
+ });
348
+ }
349
+
350
+ async.each(this.freeConnections, (connection, cb) => {
351
+ connection.nativeClose((error) => {
352
+ this.poolSize--;
353
+ cb(error);
354
+ });
355
+ }, error => callback(error));
356
+ }
357
+
358
+ async init(callback = undefined) {
359
+ if (!this.isOpen) {
360
+ this.isOpen = true;
361
+ // promise...
362
+ if (typeof callback === 'undefined') {
363
+ return new Promise(async (resolve, reject) => {
364
+ try {
365
+ await this.increasePoolSize(this.initialSize);
366
+ // Try to get one connection during init to make sure there were
367
+ // no errors with the connection string
368
+ const connection = await this.connect();
369
+ this.freeConnections.unshift(connection);
370
+ resolve();
371
+ } catch (error) {
372
+ reject(error);
373
+ }
374
+ });
375
+ }
376
+
377
+ // ...or callback
378
+ try {
379
+ await this.increasePoolSize(this.initialSize);
380
+ let connection = await this.connect();
381
+ connection.close();
382
+ } catch (error) {
383
+ return callback(error);
384
+ }
385
+ return callback(null);
386
+ }
387
+ return undefined;
388
+ }
389
+
390
+ generateConnectPromise = function(connectionConfig) {
391
+ return new Promise((resolve, reject) => {
392
+ odbc.connect(connectionConfig, (error, nativeConnection) => {
393
+ if (error) {
394
+ // When there is a connection error, emit a "connectionError" event
395
+ // with the error that is then either handled by any waiting work, or
396
+ // is silently swallowed if no work is waiting. This is so that a bad
397
+ // attempt at connecting doesn't throw an uncatchable error when there
398
+ // is no user work, crashing the entire application.
399
+ //
400
+ // Catchable errors occur:
401
+ // 1. When a pool is initialized and there was a problem with the
402
+ // first connection (no network, bad connection string, etc.)
403
+ // 2. When a user is attempting to do work and there are no active
404
+ // connections, the pool will attempt to connect, emit the error, and
405
+ // send it to the user work that was waiting (either as an error
406
+ // parameter in the user's callback, or as a thrown error that the
407
+ // user has to catch.)
408
+ this.pool.connectionEmitter.emit('connectionError', error);
409
+
410
+ // This reject is swallowed in the ConnectionQueue's dequeue function
411
+ // in all cases, and is simply to end the Promise that was generated
412
+ // by generateConnectPromise.
413
+ reject(error);
414
+ return;
415
+ }
416
+
417
+ this.pool.connectionsBeingCreatedCount--;
418
+ this.pool.poolSize++;
419
+ let connection = new Connection(nativeConnection);
420
+ connection.nativeClose = connection.close;
421
+
422
+ if (this.pool.reuseConnections) {
423
+ connection.close = async (closeCallback = undefined) => {
424
+ this.pool.connectionEmitter.emit('connected', connection);
425
+
426
+ if (typeof closeCallback === 'undefined') {
427
+ return new Promise((resolve, reject) => {
428
+ resolve();
429
+ })
430
+ }
431
+
432
+ return closeCallback(null);
433
+ };
434
+ } else {
435
+ connection.close = async (closeCallback = undefined) => {
436
+ this.pool.increasePoolSize(1);
437
+ if (typeof closeCallback === 'undefined') {
438
+ return new Promise((resolve, reject) => {
439
+ connection.nativeClose((error, result) => {
440
+ this.pool.poolSize--;
441
+ if (error) {
442
+ reject(error);
443
+ } else {
444
+ resolve(result);
445
+ }
446
+ });
447
+ });
448
+ }
449
+
450
+ connection.nativeClose(closeCallback);
451
+ }
452
+ }
453
+
454
+ this.pool.connectionEmitter.emit('connected', connection);
455
+ resolve();
456
+ });
457
+ });
458
+ }
459
+
460
+ // odbc.connect runs on an AsyncWorker, so this is truly non-blocking
461
+ async increasePoolSize(count) {
462
+ this.connectionsBeingCreatedCount += count;
463
+ for (let i = 0; i < count; i++)
464
+ {
465
+ ConnectionQueue.enqueue(this, this.generateConnectPromise, this.connectionConfig);
466
+ }
467
+ }
468
+ }
469
+
470
+ module.exports.Pool = Pool;
@@ -0,0 +1,207 @@
1
+ const { Cursor } = require('./Cursor');
2
+
3
+ class Statement {
4
+
5
+ STATEMENT_CLOSED_ERROR = 'Statement has already been closed!';
6
+
7
+ constructor(odbcStatement) {
8
+ this.odbcStatement = odbcStatement;
9
+ }
10
+
11
+ /**
12
+ * Prepare an SQL statement template to which parameters can be bound and the statement then executed.
13
+ * @param {string} sql - The SQL statement template to prepare, with or without unspecified parameters.
14
+ * @param {function} [callback] - The callback function that returns the result. If omitted, uses a Promise.
15
+ * @returns {undefined|Promise}
16
+ */
17
+ prepare(sql, callback = undefined) {
18
+ if (typeof sql !== 'string'
19
+ || (typeof callback !== 'function' && typeof callback !== 'undefined')) {
20
+ throw new TypeError('[node-odbc]: Incorrect function signature for call to statement.prepare({string}, {function}[optional]).');
21
+ }
22
+
23
+ // promise...
24
+ if (typeof callback === 'undefined') {
25
+ if (!this.odbcStatement)
26
+ {
27
+ throw new Error(Statement.STATEMENT_CLOSED_ERROR);
28
+ }
29
+
30
+ return new Promise((resolve, reject) => {
31
+ this.odbcStatement.prepare(sql, (error, result) => {
32
+ if (error) {
33
+ reject(error);
34
+ } else {
35
+ resolve(result);
36
+ }
37
+ });
38
+ });
39
+ }
40
+
41
+ // ...or callback
42
+ if (!this.odbcStatement)
43
+ {
44
+ callback(new Error(Statement.STATEMENT_CLOSED_ERROR));
45
+ } else {
46
+ this.odbcStatement.prepare(sql, callback);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Bind parameters on the previously prepared SQL statement template.
52
+ * @param {*[]} parameters - The parameters to bind to the previously prepared SQL statement.
53
+ * @param {function} [callback] - The callback function that returns the result. If omitted, uses a Promise.
54
+ * @return {undefined|Promise}
55
+ */
56
+ bind(parameters, callback = undefined) {
57
+ if (!Array.isArray(parameters)
58
+ || (typeof callback !== 'function' && typeof callback !== 'undefined')) {
59
+ throw new TypeError('[node-odbc]: Incorrect function signature for call to statement.bind({array}, {function}[optional]).');
60
+ }
61
+
62
+ // promise...
63
+ if (typeof callback === 'undefined') {
64
+ if (!this.odbcStatement)
65
+ {
66
+ throw new Error(Statement.STATEMENT_CLOSED_ERROR);
67
+ }
68
+
69
+ return new Promise((resolve, reject) => {
70
+ this.odbcStatement.bind(parameters, (error, result) => {
71
+ if (error) {
72
+ reject(error);
73
+ } else {
74
+ resolve(result);
75
+ }
76
+ });
77
+ });
78
+ }
79
+
80
+ // ...or callback
81
+ if (!this.odbcStatement)
82
+ {
83
+ callback(new Error(Statement.STATEMENT_CLOSED_ERROR));
84
+ } else {
85
+ this.odbcStatement.bind(parameters, callback);
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Executes the prepared SQL statement template with the bound parameters, returning the result.
91
+ * @param {function} [callback] - The callback function that returns the result. If omitted, uses a Promise.
92
+ */
93
+ execute(options, callback = undefined) {
94
+
95
+ if (options === undefined)
96
+ {
97
+ options = null;
98
+ }
99
+
100
+ if (typeof options === 'function' && callback === undefined) {
101
+ callback = options;
102
+ options = null
103
+ }
104
+
105
+ if ((typeof callback !== 'function' && typeof callback !== 'undefined')
106
+ || typeof options !== 'object' && typeof options !== 'undefined' ) {
107
+ throw new TypeError('[node-odbc]: Incorrect function signature for call to statement.execute({object}[optional], {function}[optional]).');
108
+ }
109
+
110
+ // Promise...
111
+ if (typeof callback === 'undefined') {
112
+ if (!this.odbcStatement)
113
+ {
114
+ throw new Error(Statement.STATEMENT_CLOSED_ERROR);
115
+ }
116
+
117
+ return new Promise((resolve, reject) => {
118
+ this.odbcStatement.execute(options, (error, result) => {
119
+ if (error) {
120
+ reject(error);
121
+ } else {
122
+ if (options &&
123
+ (
124
+ options.hasOwnProperty('fetchSize') ||
125
+ options.hasOwnProperty('cursor')
126
+ )
127
+ )
128
+ {
129
+ const cursor = new Cursor(result);
130
+ resolve(cursor);
131
+ }
132
+ else
133
+ {
134
+ resolve(result);
135
+ }
136
+ }
137
+ });
138
+ });
139
+ }
140
+
141
+ // ...or callback
142
+ if (!this.odbcStatement)
143
+ {
144
+ callback(new Error(Statement.STATEMENT_CLOSED_ERROR));
145
+ } else {
146
+ process.nextTick(() => {
147
+ if (options &&
148
+ (
149
+ options.hasOwnProperty('fetchSize') ||
150
+ options.hasOwnProperty('cursor')
151
+ )
152
+ )
153
+ {
154
+ this.odbcStatement.execute(options, (error, result) => {
155
+ if (error) {
156
+ return callback(error);
157
+ }
158
+
159
+ const cursor = new Cursor(result);
160
+ return callback(error, cursor);
161
+ });
162
+ }
163
+ else
164
+ {
165
+ this.odbcStatement.execute(options, callback);
166
+ }
167
+ });
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Closes the statement, deleting the prepared statement and freeing the handle, making further
173
+ * calls on the object invalid.
174
+ * @param {function} [callback] - The callback function that returns the result. If omitted, uses a Promise.
175
+ */
176
+ close(callback = undefined) {
177
+ if (typeof callback !== 'function' && typeof callback !== 'undefined') {
178
+ throw new TypeError('[node-odbc]: Incorrect function signature for call to statement.close({function}[optional]).');
179
+ }
180
+
181
+ if (typeof callback === 'undefined') {
182
+ if (!this.odbcStatement) {
183
+ throw new Error(Statement.STATEMENT_CLOSED_ERROR);
184
+ }
185
+ return new Promise((resolve, reject) => {
186
+ this.odbcStatement.close((error) => {
187
+ if (error) {
188
+ reject(error);
189
+ } else {
190
+ this.odbcStatement = null;
191
+ resolve();
192
+ }
193
+ });
194
+ });
195
+ }
196
+
197
+ // ...or callback
198
+ return this.odbcStatement.close((error) => {
199
+ if (!error) {
200
+ this.odbcStatement = null;
201
+ }
202
+ callback(error);
203
+ });
204
+ }
205
+ }
206
+
207
+ module.exports.Statement = Statement;
Binary file