-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrhino.js
executable file
·293 lines (269 loc) · 10 KB
/
rhino.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
const tarn = require('tarn');
const Log = require('./log.js');
const Connection = require('./connection.js');
const ConnectedQuery = require('./connected-query.js');
const BulkQuery = require('./bulk-query.js');
const Transaction = require('./transaction.js');
const Result = require('./result.js');
const Query = require('./query.js');
/**
* Please refer to: {@link https://github.com/Vincit/tarn.js|Tarn on GitHub}
* @typedef Rhino.PoolConfiguration
* @property {Number} [max=1]
* @property {Number} [min=0]
* @property {Number} [acquireTimeoutMillis=30000]
* @property {Number} [createTimeoutMillis=30000]
* @property {Number} [idleTimeoutMillis=30000]
* @property {Number} [reapIntervalMillis=1000]
* @property {Number} [createRetryIntervalMillis=200]
*/
/**
*
* @typedef Rhino.RhinoBaseConfiguration
* @property {Rhino.PoolConfiguration} [pool]
* @property {Log.LogConfiguration} [logging]
*/
/**
* Rhino's configuration fully implements all configuration properties from `tedious`.
* @see {@link http://tediousjs.github.io/tedious/api-connection.html#function_newConnection|Tedious}
* @see {@link https://github.com/Vincit/tarn.js|Tarn}
* @typedef {Connection.TediousConfiguration | Rhino.RhinoBaseConfiguration} Rhino.RhinoConfiguration
*/
/**
* Rhino is a managed Microsoft SQL Server driver powered by tedious and node-pool. This class defines functionality
* to execute queries and utlize transactions. Under the hood it handles all connection pooling, including opening
* and closing of connections to the database.
*
* You can use multiple instances of the Rhino class in your application - each one can utilize a different
* configuration.
*/
class Rhino {
/**
* Constructs a `Rhino` instance using the specified `config` values.
* @param {Rhino.RhinoConfiguration} [config] - Configuration values to use in this `Rhino` instance. Any properties not
* explicitly specified will use the default values.
*/
constructor(config) {
/**
* @type {Rhino.RhinoConfiguration}
*/
this.config = Rhino.defaultConfig(config);
/**
* @type {Log}
*/
this.log = new Log(this.config.logging);
/**
* @type {tarn.Pool}
* @private
*/
this._pool = this._createPool(this.config.pool);
//start listening for process hangups
this._handleProcessTermination();
}
/**
* Listens to process termination events so the rhino instance can cleanup resources.
* @see {@link Rhino+destroy}
* @private
*/
_handleProcessTermination() {
let self = this;
process.on('exit', () => {
self.destroy();
});
}
/**
* Destroys internal pooled resources in this instance. This is called automatically when the process exits.
* @param {Function} [done] - Callback function when the destruction is complete.
*/
async destroy(done) {
if (this._pool) {
if (this._pool && this._pool.used && this._pool.used.length) {
for (let r of this._pool.used) {
await this._destroyResource(r.resource);
}
}
this._pool.destroy();
if (done) {
done();
}
} else if (done) {
done();
}
}
/**
* Creates a pool instance meant for internal use by the active `Rhino` instance.
* @param {genericPool.Options} config - The pool configuration options.
* @returns {genericPool.Pool}
* @private
*/
_createPool(config) {
config = Object.assign(
{
min: 0,
max: 10
},
config,
{
propagateCreateError: true,
create: this._createResource.bind(this),
destroy: this._destroyResource.bind(this),
validate: this._validateResource.bind(this)
});
return new tarn.Pool(config);
}
/**
* Pool factory function to return a connection resource.
* @returns {Connection}
* @private
*/
_createResource() {
this.log.debug('Pool creating resource...');
let r = new Connection(this.config, this.log);
return r.connect();
}
/**
* Pool factory function destroy a resource.
* @param {Connection} resource - The resource to be destroyed.
* @private
*/
async _destroyResource(resource) {
this.log.debug('Pool destroying resource...');
if (resource) {
await resource.disconnect();
}
}
/**
* Pool factory function to validate a resource.
* @param {Connection} resource - The resource to be validated.
* @private
*/
async _validateResource(resource) {
}
/**
* Attempts to connect to the database. This method utilizes the internal connection pool, and will return `true`
* if a connection is already opened and active. If the connection cannot be established for any reason, including
* an error, a `false` is returned.
*
* Note that if an error occurs in this function call, it is *not* thrown, but it will be logged normally.
*
* @returns {Boolean} Returns `true` when a connection was successfully aquired. A `false` value is returned if the
* connection cannot be aquired for any reason.
*/
async ping() {
try {
let res = await this._pool.acquire().promise;
await this._pool.release(res);
return true;
} catch (err) {
this.log.error(err);
}
return false;
}
transaction() {
return new Transaction(this._pool);
}
/**
* Runs a SQL statement on the database and returns the results.
* @param {String} sql - The SQL statement to execute.
* @param {Map.<String,*>|Object} [params] - Optional parameters `Object` or `Map` that will be added to the
* "in" parameters of the query. Keys and property names are used as the parameter name, and the value as the
* parameter values.
* @returns {ConnectedQuery|Promise.<Result>}
*/
query(sql, params) {
return new ConnectedQuery(this._pool).sql(sql, params);
}
/**
* Creates a new bulk-loading query that can be used to rapidly insert large amounts of data.
* @param {String} tableName - The name of the table to perform the bulk insert.
* @param {BulkQuery.Options} options - Options to pass to the bulk query.
* @returns {BulkQuery}
*/
bulk(tableName, options) {
return new BulkQuery(tableName, options, this._pool);
}
/**
* This function creates a new `Rhino` instance to act as a pool for executing database queries. You can create
* multiple `Rhino` instances to manage multiple pools of connections or for different databases.
*
* @example
* const rhino = require('rhino');
*
* let pool1 = rhino.create({
* server: 'server-001',
* database: 'databaseA'
* ...
* });
* let pool2 = rhino.create({
* server: 'server-002',
* database: 'databaseB'
* ...
* });
* @param {RhinoConfiguration} [config] - Configuration values to use in this `Rhino` instance. Any properties not
* explicitly specified will use the default values.
* @returns {Rhino}
*/
static create(config) {
return new Rhino(config);
}
/**
* Returns a default `RhinoConfiguration` object. Default values are first searched for in environmental variables
* then, if not found, with hard-coded default values.
* @param {RhinoConfiguration} [config] - Optional configuration value overrides.
* @returns {RhinoConfiguration}
*/
static defaultConfig(config) {
let dc = {
server: process.env.RHINO_MSSQL_HOST || process.env.RHINO_MSSQL_SERVER || 'localhost',
};
if (config) {
dc = Object.assign(dc, config);
}
//build options
dc.options = {
encrypt: process.env.RHINO_MSSQL_ENCRYPT || false,
connectionRetries: 3,
trustServerCertificate: process.env.RHINO_MSSQL_TRUST_CERT || false,
validateBulkLoadParameters: true
};
if (process.env.RHINO_MSSQL_INSTANCE || process.env.RHINO_MSSQL_INSTANCE_NAME) {
dc.options.instanceName = process.env.RHINO_MSSQL_INSTANCE || process.env.RHINO_MSSQL_INSTANCE_NAME;
}
//only use a port when the instance name is not present or explicity defined.
if (!dc.options.instanceName || process.env.RHINO_MSSQL_PORT) {
dc.options.port = process.env.RHINO_MSSQL_PORT || 1433;
}
if (process.env.RHINO_MSSQL_DATABASE) {
dc.options.database = process.env.RHINO_MSSQL_DATABASE;
}
if (process.env.RHINO_MSSQL_APP_NAME) {
dc.options.appName = process.env.RHINO_MSSQL_APP_NAME;
}
if (config && config.options) {
dc.options = Object.assign(dc.options, config.options);
}
//build authentication
dc.authentication = {
type: process.env.RHINO_MSSQL_AUTH_TYPE || 'default',
options: {
userName: process.env.RHINO_MSSQL_USER || process.env.RHINO_MSSQL_AUTH_USER || null,
password: process.env.RHINO_MSSQL_PASSWORD || process.env.RHINO_MSSQL_AUTH_PASSWORD || null,
domain: process.env.RHINO_MSSQL_DOMAIN || process.env.RHINO_MSSQL_AUTH_DOMAIN || null
}
};
if (config && config.authentication) {
dc.authentication = Object.assign(dc.authentication, config.authentication);
}
//build logging
dc.logging = {
mode: (typeof process.env.RHINO_LOGGING !== 'undefined' ? process.env.RHINO_LOGGING : false)
};
if (config && config.logging) {
dc.logging = Object.assign(dc.logging, config.logging);
}
//done
return dc;
}
}
Rhino.Types = Query.TYPE;
module.exports = Rhino;