monetdb 1.3.4 → 2.0.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.
Files changed (55) hide show
  1. package/.github/workflows/Linux.yml +3 -3
  2. package/.github/workflows/docs.yml +79 -0
  3. package/.github/workflows/macos.yml +3 -3
  4. package/.github/workflows/monetdb-versions.yml +43 -0
  5. package/README.md +44 -514
  6. package/docs/components/alert.tsx +10 -0
  7. package/docs/components/info.tsx +6 -0
  8. package/docs/next.config.js +24 -0
  9. package/docs/package-lock.json +5069 -0
  10. package/docs/package.json +22 -0
  11. package/docs/pages/_app.js +9 -0
  12. package/docs/pages/_meta.json +18 -0
  13. package/docs/pages/apis/_meta.json +4 -0
  14. package/docs/pages/apis/connection.mdx +60 -0
  15. package/docs/pages/apis/result.mdx +39 -0
  16. package/docs/pages/filetransfer.mdx +43 -0
  17. package/docs/pages/index.mdx +27 -0
  18. package/docs/pages/prepstmt.mdx +13 -0
  19. package/docs/pages/result.mdx +41 -0
  20. package/docs/theme.config.js +35 -0
  21. package/docs/v1/README.md +532 -0
  22. package/package.json +16 -20
  23. package/src/PrepareStatement.ts +37 -0
  24. package/src/connection.ts +125 -0
  25. package/src/defaults.ts +11 -0
  26. package/src/file-transfer.ts +173 -0
  27. package/src/index.ts +3 -0
  28. package/src/mapi.ts +1016 -0
  29. package/src/monetize.ts +67 -0
  30. package/test/connection.ts +43 -0
  31. package/test/exec-queries.ts +112 -0
  32. package/test/filetransfer.ts +94 -0
  33. package/test/prepare-statement.ts +27 -0
  34. package/test/query-stream.ts +41 -0
  35. package/test/tmp/.gitignore +4 -0
  36. package/tsconfig.json +24 -0
  37. package/.travis.yml +0 -11
  38. package/dist/mapi.d.ts +0 -58
  39. package/dist/mapi.js +0 -250
  40. package/dist/mapi.js.map +0 -1
  41. package/dist/tsconfig.tsbuildinfo +0 -1
  42. package/foo.js +0 -16
  43. package/index.js +0 -5
  44. package/src/mapi-connection.js +0 -784
  45. package/src/monetdb-connection.js +0 -385
  46. package/src/utils.js +0 -27
  47. package/test/common.js +0 -45
  48. package/test/install-monetdb.sh +0 -11
  49. package/test/monetdb_stream.js +0 -106
  50. package/test/start-monetdb.sh +0 -38
  51. package/test/test.js +0 -908
  52. package/test/test_connection.js +0 -290
  53. /package/docs/{README.v0.md → v0/README.v0.md} +0 -0
  54. /package/docs/{MapiConnection.md → v1/MapiConnection.md} +0 -0
  55. /package/docs/{v1-notes.md → v1/v1-notes.md} +0 -0
@@ -1,784 +0,0 @@
1
- /**
2
- * Author: Robin Cijvat <robin.cijvat@monetdbsolutions.com>
3
- */
4
-
5
- 'use strict';
6
-
7
- const net = require('net');
8
- const crypto = require('crypto');
9
- const Q = require('q');
10
- const EventEmitter = require("events").EventEmitter;
11
-
12
- const StringDecoder = require('string_decoder').StringDecoder;
13
- const decoder = new StringDecoder('utf8');
14
-
15
- const utils = require('./utils');
16
-
17
- /**
18
- * <hannes@cwi.nl>
19
- */
20
- function __sha512(str) {
21
- return crypto.createHash('sha512').update(str).digest('hex');
22
- }
23
-
24
- /**
25
- * <hannes@cwi.nl>
26
- */
27
- function _hdrline(line) {
28
- return line.substr(2, line.indexOf('#')-3).split(',\t');
29
- }
30
-
31
-
32
- module.exports = function MapiConnection(options) {
33
- var self = this;
34
-
35
- // private vars and functions
36
- var _socket = null;
37
- var _state = 'disconnected';
38
- var _connectDeferred = null;
39
- var _reconnecting = false;
40
- var _messageQueue = _emptyMessageQueue('main');
41
- var _messageQueueDisconnected = _emptyMessageQueue('disconnected');
42
- var _msgLoopRunning = false;
43
- var _closeDeferred = null;
44
- var _curMessage = null;
45
- var _mapiBlockSize = 8190; // monetdb ./common/stream/stream.h’: #define BLOCK (8 * 1024 - 2)
46
- var _readLeftOver = 0;
47
- var _readFinal = false;
48
- var _readStr = '';
49
- var header = null;
50
- var flag_query = false;
51
- var flag_header_treated = false;
52
- var _errorDetectionRegex = /(\\n!|^!)(.*)/g;
53
-
54
- var _failPermanently = false; // only used for testing
55
-
56
- function _emptyMessageQueue(tag) {
57
- var queue = [];
58
- queue.tag = tag;
59
- return queue;
60
- }
61
-
62
- function _debug(msg) {
63
- if(options.debug) {
64
- options.debugFn(options.logger, msg);
65
- }
66
- }
67
-
68
- function _setState(state) {
69
- _debug('Setting state to ' + state + '..');
70
- _state = state;
71
- }
72
-
73
- function _nextMessage() {
74
- if (!_messageQueue.length) {
75
- _debug('No more messages in main message queue.. stopping message loop.');
76
- _msgLoopRunning = false;
77
- if (!_messageQueueDisconnected.length && _closeDeferred) {
78
- self.destroy();
79
- _closeDeferred.resolve();
80
- }
81
- return;
82
- }
83
-
84
- if(_state == 'disconnected') return; // will be called again after reconnect
85
-
86
- _curMessage = _messageQueue.shift();
87
- _debug('Starting next request from main message queue: ' + _curMessage.message);
88
- _sendMessage(_curMessage.message);
89
- }
90
-
91
- /**
92
- * <hannes@cwi.nl>
93
- *
94
- * Send a packaged message
95
- *
96
- * @param message An object containing a message property and a deferred property
97
- * @private
98
- */
99
- function _sendMessage(message) {
100
- if (options.debugMapi) {
101
- options.debugMapiFn(options.logger, 'TX', message);
102
- }
103
-
104
- var buf = new Buffer.from(message, 'utf8');
105
- var final = 0;
106
- while (final == 0) {
107
- var bs = Math.min(buf.length, _mapiBlockSize - 2);
108
- var sendbuf = buf.slice(0, bs);
109
- buf = buf.slice(bs);
110
- if (buf.length == 0) {
111
- final = 1;
112
- }
113
-
114
- _debug('Writing ' + bs + ' bytes, final=' + final);
115
-
116
- var hdrbuf = new Buffer.alloc(2);
117
- hdrbuf.writeInt16LE((bs << 1) | final, 0);
118
- if(!_socket) return;
119
- _socket.write(Buffer.concat([hdrbuf, sendbuf]));
120
- }
121
- }
122
-
123
- /**
124
- * <hannes@cwi.nl>
125
- *
126
- * Read incoming data and construct the original messages from it
127
- *
128
- * @param data Data that follows from a net.socket data event
129
- * @private
130
- */
131
- function _handleData(data) {
132
- /* we need to read a header obviously */
133
- if (_readLeftOver == 0) {
134
- var hdr = data.readUInt16LE(0);
135
- _readLeftOver = (hdr >> 1);
136
- _readFinal = (hdr & 1) == 1;
137
- data = data.slice(2);
138
- }
139
- _debug('Reading ' + _readLeftOver + ' bytes, final=' + _readFinal);
140
-
141
- /* what is in the buffer is not necessary the entire block */
142
- var read_cnt = Math.min(data.length, _readLeftOver);
143
- try {
144
- //String concat involve problems with 2 bytes characters like é ou à
145
- //_readStr = _readStr + data.toString('utf8', 0, read_cnt);
146
-
147
- var buf = Buffer.allocUnsafe(read_cnt).fill(0);
148
- data.copy(buf, 0, 0, read_cnt);
149
- _readStr = _readStr + decoder.write(buf);
150
-
151
- //mode stream detection
152
- if (_curMessage){
153
- if (_curMessage.deferred.promise.on){
154
- _handleDataStream();
155
- }
156
- }
157
- } catch(e) {
158
- if(options.warning) {
159
- options.warningFn(options.logger, 'Could not append read buffer to query result');
160
- }
161
- }
162
- _readLeftOver -= read_cnt;
163
-
164
- /* if there is something left to read, we will be called again */
165
- if (_readLeftOver > 0) {
166
- return;
167
- }
168
-
169
- /* pass on reassembled messages */
170
- if (_readLeftOver == 0 && _readFinal) {
171
- _handleResponse(_readStr);
172
- //Stream mode
173
- if (_curMessage){
174
- if (_curMessage.deferred.promise.on){
175
- _curMessage.deferred.promise.emit('end', null);
176
- _curMessage.deferred.resolve({});
177
- }
178
- }
179
- _readStr = '';
180
- header = null;
181
- flag_query = false;
182
- flag_header_treated = false;
183
- }
184
-
185
- /* also, the buffer might contain more blocks or parts thereof */
186
- if (data.length > read_cnt) {
187
- var leftover = Buffer.allocUnsafe(data.length - read_cnt).fill(0);
188
- data.copy(leftover, 0, read_cnt, data.length);
189
- _handleData(leftover);
190
- }
191
- }
192
-
193
- /**
194
- *
195
- *
196
- * Consume message received from _handleData and emit header and data event : used by querystream
197
- *
198
- * @param none
199
- * @private
200
- */
201
- function _handleDataStream() {
202
- if (_readStr.charAt(0) == '&') {
203
- flag_query = true;
204
- let lines = _readStr.split('\n');
205
- //header contains only 5 lines
206
- //we parse only if we have at least 5 lines
207
- //otherwise next call will concat data with last _readStr
208
- if (lines.length>5){
209
- header =_parseHeader(_readStr);
210
- _curMessage.deferred.promise.emit('header', header);
211
- //we consume 5 lines of _readStr
212
- let headerLineNb = 5;
213
- let idx = _readStr.indexOf('\n');
214
- let realidx;
215
- while (headerLineNb > 0) {
216
- headerLineNb--;
217
- realidx = idx;
218
- idx = _readStr.indexOf('\n', idx + 1);
219
- }
220
- _readStr = _readStr.substring( realidx+1,_readStr.length);
221
- //we set the flag for the header as we will be called again
222
- flag_header_treated = true;
223
- //we keep the header informations in _curMessage
224
- _curMessage.header = header;
225
- }
226
- }
227
- //Called again - we began to extract data
228
- if (flag_query && flag_header_treated){
229
- let idx = _readStr.indexOf('\n');
230
- let realidx=0;
231
- //we consume lines of _readStr
232
- while (idx != -1) {
233
- realidx = idx;
234
- idx = _readStr.indexOf('\n', idx + 1);
235
- }
236
- //Sometimes, the buffer contains more data than _readLeftOver (data.length>_readLeftOver)
237
- //see the end of _handleData : in that case, we call again _handleData
238
- //to concat the last string in _readStr but this is not a full line with \n
239
- //we must not emit data in that case (realidx==0), otherwise, data will be emitted twice
240
- if (realidx>0){
241
- let extract = _readStr.substring(0, realidx);
242
- _readStr = _readStr.substring(realidx+1,_readStr.length);
243
- _curMessage.deferred.promise.emit('data', _parseTuples(_curMessage.header.column_types, extract.split('\n')));
244
- }
245
- }
246
- }
247
-
248
- /**
249
- * <hannes@cwi.nl>
250
- *
251
- * Whenever a full response is received from the server, this response is passed to
252
- * this function. The main idea of this function is that it sets the object state to
253
- * ready as soon as the server let us know that the authentication succeeded.
254
- * Basically, the first time this function is called, it will receive a challenge from the server.
255
- * It will respond with authentication details. This *might* happen more than once, until at some point
256
- * we receive either an authentication error or an empty line (prompt). This empty line indicates
257
- * all is well, and state will be set to ready then.
258
- *
259
- * @param response The response received from the server
260
- * @private
261
- */
262
- function _handleResponse(response) {
263
- if (options.debugMapi) {
264
- options.debugMapiFn(options.logger, 'RX', response);
265
- }
266
-
267
- /* prompt, good */
268
- if (response == '' && _state === 'started') {
269
- _setState('ready');
270
- // do not resolve _curMessage here, since this prompt should only happen directly after
271
- // authentication, which circumvents the _curMessage.
272
- return _nextMessage();
273
- }
274
-
275
- /* monetdbd redirect, ignore. We will get another challenge soon */
276
- if (response.charAt(0) == '^') {
277
- return;
278
- }
279
-
280
- if (_state == 'started') {
281
- /* error message during authentication? */
282
- if (response.charAt(0) == '!') {
283
- response = new Error('Error: ' + response.substring(1, response.length - 1));
284
- _connectDeferred && _connectDeferred.reject(response);
285
- _setState('disconnected');
286
- return _curMessage && _curMessage.deferred.reject(response);
287
- }
288
-
289
- // means we get the challenge from the server
290
- var authch = response.split(':');
291
- var salt = authch[0];
292
- var dbname = authch[1]; // Contains 'merovingian' if monetdbd is used. We do not use this value.
293
-
294
- /* In theory, the server tells us which hashes it likes.
295
- In practice, we know it always likes sha512 , so... */
296
- var pwhash = __sha512(__sha512(options.password) + salt);
297
- var counterResponse = 'LIT:' + options.user + ':{SHA512}' + pwhash + ':' +
298
- options.language + ':' + options.dbname + ':';
299
- _sendMessage(counterResponse);
300
- return;
301
- }
302
-
303
- /* error message */
304
- var errorMatch;
305
- if (errorMatch = response.match(_errorDetectionRegex)) {
306
- var error = errorMatch[0];
307
- //stream mode
308
- if (_curMessage.deferred.promise.on){
309
- _curMessage.deferred.promise.emit('error',new Error(error.substring(1, error.length)));
310
- }
311
-
312
- _curMessage.deferred.reject(new Error(error.substring(1, error.length)));
313
- }
314
-
315
- /* query result */
316
- else if (response.charAt(0) == '&') {
317
- _curMessage.deferred.resolve(_parseResponse(response));
318
- }
319
-
320
- else {
321
- _curMessage && _curMessage.deferred.resolve({});
322
- }
323
-
324
- _nextMessage();
325
- }
326
-
327
- /**
328
- * <hannes@cwi.nl>
329
- *
330
- * Parse a response that was reconstructed from the net.socket stream.
331
- *
332
- * @param msg Reconstructed message
333
- * @returns a response structure, see documentation
334
- * @private
335
- */
336
-
337
- function _parseResponse(msg) {
338
- var lines = msg.split('\n');
339
- var resp = {};
340
- var tpe = lines[0].charAt(1);
341
-
342
- /* table result, we only like Q_TABLE and Q_PREPARE for now */
343
- if (tpe == 1 || tpe == 5) {
344
- var hdrf = lines[0].split(' ');
345
-
346
- resp.type='table';
347
- resp.queryid = parseInt(hdrf[1]);
348
- resp.rows = parseInt(hdrf[2]);
349
- resp.cols = parseInt(hdrf[3]);
350
-
351
- var table_names = _hdrline(lines[1]);
352
- var column_names = _hdrline(lines[2]);
353
- var column_types = _hdrline(lines[3]);
354
- var type_lengths = _hdrline(lines[4]);
355
-
356
- resp.structure = [];
357
- resp.col = {};
358
- for (var i = 0; i < table_names.length; i++) {
359
- var colinfo = {
360
- table : table_names[i],
361
- column : column_names[i],
362
- type : column_types[i],
363
- typelen : parseInt(type_lengths[i]),
364
- index : i
365
- };
366
- resp.col[colinfo.column] = colinfo.index;
367
- resp.structure.push(colinfo);
368
- }
369
- resp.data = _parseTuples(column_types, lines.slice(5, lines.length-1));
370
- }
371
- else if (tpe == 2) {
372
- resp.affected_rows = parseInt(lines[0].split(' ')[1]);
373
- }
374
-
375
- return resp;
376
- }
377
-
378
- /**
379
- *
380
- *
381
- * Parse a header that was reconstructed from the net.socket stream.
382
- *
383
- * @param msg Reconstructed message
384
- * @returns a header structure, see documentation
385
- * @private
386
- */
387
- function _parseHeader(msg) {
388
- var lines = msg.split('\n');
389
- var resp = {};
390
- var tpe = lines[0].charAt(1);
391
-
392
- /* table result, we only like Q_TABLE and Q_PREPARE for now */
393
- if (tpe == 1 || tpe == 5) {
394
- var hdrf = lines[0].split(' ');
395
-
396
- resp.type='table';
397
- resp.queryid = parseInt(hdrf[1]);
398
- resp.rows = parseInt(hdrf[2]);
399
- resp.cols = parseInt(hdrf[3]);
400
-
401
- var table_names = _hdrline(lines[1]);
402
- var column_names = _hdrline(lines[2]);
403
- var column_types = _hdrline(lines[3]);
404
- var type_lengths = _hdrline(lines[4]);
405
-
406
- resp.structure = [];
407
- resp.col = {};
408
- for (var i = 0; i < table_names.length; i++) {
409
- var colinfo = {
410
- table : table_names[i],
411
- column : column_names[i],
412
- type : column_types[i],
413
- typelen : parseInt(type_lengths[i]),
414
- index : i
415
- };
416
- resp.col[colinfo.column] = colinfo.index;
417
- resp.structure.push(colinfo);
418
- }
419
- //resp.data = _parseTuples(column_types, lines.slice(5, lines.length-1));
420
- resp.column_types = column_types;
421
- }
422
- return resp;
423
- }
424
- /**
425
- * <hannes@cwi.nl>
426
- *
427
- * Parse the tuples part of a server response.
428
- *
429
- * @private
430
- */
431
- function _parseTuples(types, lines) {
432
- var state = 'INCRAP';
433
- var resultarr = [];
434
- let endQuotes = 0;
435
- lines.forEach(function(line) {
436
- var resultline = [];
437
- var cCol = 0;
438
- var curtok = '';
439
- /* mostly adapted from clients/R/MonetDB.R/src/mapisplit.c */
440
- for (var curPos = 2; curPos < line.length - 1; curPos++) {
441
- var chr = line.charAt(curPos);
442
- switch (state) {
443
- case 'INCRAP':
444
- if (chr != '\t' && chr != ',' && chr != ' ') {
445
- if (chr == '"') {
446
- state = 'INQUOTES';
447
- } else {
448
- state = 'INTOKEN';
449
- curtok += chr;
450
- }
451
- }
452
- break;
453
- case 'INTOKEN':
454
- if (chr == ',' || curPos == line.length - 2) {
455
- if (curtok == 'NULL' && endQuotes === 0) {
456
- resultline.push(null);
457
-
458
- } else {
459
- switch(types[cCol]) {
460
- case 'boolean':
461
- resultline.push(curtok == 'true');
462
- break;
463
- case 'tinyint':
464
- case 'smallint':
465
- case 'int':
466
- case 'wrd':
467
- case 'bigint':
468
- resultline.push(parseInt(curtok));
469
- break;
470
- case 'real':
471
- case 'double':
472
- case 'decimal':
473
- resultline.push(parseFloat(curtok));
474
- break;
475
- case 'json':
476
- try {
477
- resultline.push(JSON.parse(curtok));
478
- } catch(e) {
479
- resultline.push(curtok);
480
- }
481
- break;
482
- default:
483
- // we need to unescape double quotes
484
- //valPtr = valPtr.replace(/[^\\]\\"/g, '"');
485
- resultline.push(curtok);
486
- break;
487
- }
488
- }
489
- cCol++;
490
- state = 'INCRAP';
491
- curtok = '';
492
- endQuotes = 0;
493
- } else {
494
- curtok += chr;
495
- }
496
- break;
497
- case 'ESCAPED':
498
- state = 'INQUOTES';
499
- switch(chr) {
500
- case 't': curtok += '\t'; break;
501
- case 'n': curtok += '\n'; break;
502
- case 'r': curtok += '\r'; break;
503
- default: curtok += chr;
504
- }
505
- break;
506
- case 'INQUOTES':
507
- if (chr == '"') {
508
- state = 'INTOKEN';
509
- endQuotes++;
510
- break;
511
- }
512
- if (chr == '\\') {
513
- state = 'ESCAPED';
514
- break;
515
- }
516
- curtok += chr;
517
- break;
518
- }
519
- }
520
- resultarr.push(resultline);
521
- });
522
- return resultarr;
523
- }
524
-
525
- function _reconnect(attempt) {
526
- if(attempt > options.maxReconnects) {
527
- // reached limit
528
- if (options.warnings) {
529
- options.warningFn(options.logger, 'Attempted to reconnect for ' + (attempt-1) + ' times.. We are giving up now.');
530
- }
531
- _reconnecting = false;
532
- return self.destroy('Failed to connect to MonetDB server');
533
- }
534
-
535
- // not reached limit: attempt a reconnect
536
-
537
- // always destroy socket, since if reconnecting, we always want to remove listeners and stop all traffic
538
- _destroySocket();
539
-
540
-
541
- if(options.warnings) {
542
- options.warningFn(options.logger, 'Reconnect attempt ' + attempt + '/' + options.maxReconnects + ' in ' + (options.reconnectTimeout/1000) + ' sec..');
543
- }
544
- setTimeout(function() {
545
- self.connect().then(function() {
546
- if(options.warnings) {
547
- options.warningFn(options.logger, 'Reconnection succeeded.');
548
- }
549
- _reconnecting = false;
550
- }, function(err) {
551
- if(options.warnings) {
552
- options.warningFn(options.logger, 'Could not connect to MonetDB: ' + err);
553
- }
554
- _messageQueue = [];
555
- _reconnect(attempt+1);
556
- });
557
- }, options.reconnectTimeout);
558
- }
559
-
560
- function _onData(data) {
561
- if(_state == 'connected') {
562
- _state = 'started';
563
- }
564
- _handleData(data);
565
- }
566
- function _onError(err) {
567
- if(_state == 'disconnected') {
568
- // there must have been a connection error, since the error handler was called
569
- // before the net.connect callback
570
- _connectDeferred.reject(new Error(err));
571
- }
572
- if(options.warnings) {
573
- options.warningFn(options.logger, 'Socket error occurred: ' + err.toString());
574
- }
575
- }
576
- function _onClose() {
577
- _setState('disconnected');
578
-
579
- if(!_reconnecting) {
580
- _reconnecting = true;
581
-
582
- if (_curMessage) {
583
- _messageQueue.unshift(_curMessage);
584
- _curMessage = null;
585
- }
586
-
587
- // transfer messages in queue to another variable
588
- _messageQueueDisconnected = _messageQueue;
589
- _messageQueueDisconnected.tag = 'disconnected';
590
- _messageQueue = _emptyMessageQueue('main');
591
- _reconnect(1);
592
- }
593
- }
594
-
595
- function _destroySocket() {
596
- if(_socket) {
597
- _socket.removeListener('data', _onData);
598
- _socket.removeListener('error', _onError);
599
- _socket.removeListener('close', _onClose);
600
- _socket.destroy();
601
- }
602
- _socket = null;
603
- }
604
-
605
- function _resumeMsgLoop() {
606
- /* if message loop is not running, we need to start it again */
607
- if (!_msgLoopRunning) {
608
- _debug('Message loop was not running; starting message loop..');
609
- _msgLoopRunning = true;
610
- _nextMessage();
611
- }
612
- }
613
-
614
- function _request(message, queue, streamflag) {
615
- var defer = Q.defer();
616
- if (streamflag) {
617
- const emitter = new EventEmitter();
618
- defer.promise.on = emitter.on;
619
- defer.promise.emit = emitter.emit;
620
- }
621
-
622
- if(_state == 'destroyed') defer.reject(new Error('Cannot accept request: connection was destroyed.'));
623
- else {
624
- _debug('Pushing request into ' + queue.tag + ' message queue: ' + message);
625
- queue.push({
626
- message: message,
627
- deferred: defer
628
- });
629
- _resumeMsgLoop();
630
- }
631
- if(options.debugRequests) {
632
- return defer.promise.then(function(res) {
633
- options.debugRequestFn(options.logger, message, null, res);
634
- return res;
635
- }, function(err) {
636
- options.debugRequestFn(options.logger, message, err, null);
637
- throw err;
638
- });
639
- }
640
- return defer.promise;
641
- }
642
-
643
-
644
- // public vars and functions
645
-
646
- /**
647
- * Get the current state of the connection. Possible states:
648
- * - disconnected: There is currently no open connection, either because it has never
649
- * been opened yet, or because a reconnect is going on
650
- * - connected: There is an open connection to the server, but authentication has not
651
- * finished yet.
652
- * - started: An initial message has been received from the server after connecting
653
- * - ready: There is an open connection to the server, and we have successfully
654
- * authenticated. The connection is ready to accept queries.
655
- * - destroyed: The connection is destroyed, either because it was explicitly destroyed
656
- * by a call to {destroy}, or because of a failure to keep the connection open.
657
- * @returns {string}
658
- */
659
- self.getState = function() {
660
- return _state;
661
- };
662
-
663
- /**
664
- * <hannes@cwi.nl>
665
- */
666
- self.connect = function() {
667
- _connectDeferred = Q.defer();
668
- if(_failPermanently) _connectDeferred.reject(new Error('Failure to connect simulated by testing..'));
669
- else if(_state == 'destroyed') _connectDeferred.reject(new Error('Failed to connect: This connection was destroyed.'));
670
- else {
671
- // set up the connection
672
-
673
- // We set msgLoopRunning to true, so any requests we do will not start the message loop.
674
- // We wait for an initial message from the server, which will trigger authentication,
675
- // and eventually trigger the nextMessage method.
676
- _msgLoopRunning = true;
677
- _socket = net.connect(options.port, options.host, function () {
678
- // Connected to the socket!
679
- _setState('connected');
680
-
681
- // We give the server some time to initiate traffic on this socket.
682
- var waitingTime = 1000;
683
- setTimeout(function() {
684
- if(_state === 'connected') {
685
- // server has still sent no initial message.. reconnect and do nothing further
686
- _reconnect(1);
687
- }
688
- }, waitingTime);
689
-
690
- // And we already fill the message queue with things that have to be done when authentication completes.
691
- _request('Xreply_size -1', _messageQueue);
692
- const auto_commit = Boolean(options.autoCommit) ? 1 : 0;
693
- _request(`Xauto_commit ${auto_commit}`, _messageQueue);
694
-
695
-
696
- // Set the time zone interval, we do not check whether or not that succeeds.
697
- _request(utils.packQuery("SET TIME ZONE INTERVAL '" + options.timezoneOffset + "' MINUTE"), _messageQueue);
698
-
699
- var schemaReq = Q.when(true);
700
- // Set the schema, if other than 'sys'
701
- if(options.defaultSchema !== 'sys') {
702
- schemaReq = _request(utils.packQuery('SET SCHEMA ' + options.defaultSchema), _messageQueue);
703
- }
704
- // try to execute a simple query, after the schema has been set (if required at all) and resolve/reject connection promise
705
- return schemaReq.then(function() {
706
- return _request(utils.packQuery('SELECT 42'), _messageQueue);
707
- }).then(function () {
708
- // At this point, the message queue should be empty, since 'select 42' was the
709
- // last request placed by the connect method, and that one has been successfully
710
- // completed.
711
- // Requests that have arrived in the meantime are stored in messageQueueDisconnected.
712
- // Swap these queues, and resume the msg loop
713
- _messageQueue = _messageQueueDisconnected;
714
- _messageQueue.tag = 'main';
715
- _messageQueueDisconnected = _emptyMessageQueue('disconnected');
716
- _resumeMsgLoop();
717
- _connectDeferred.resolve();
718
- }, function (err) {
719
- if (options.warnings) {
720
- options.warningFn(options.logger, 'Error on opening connection: ' + err);
721
- }
722
- _connectDeferred.reject(new Error('Could not connect to MonetDB: ' + err));
723
- }).done();
724
- });
725
- _socket.on('data', _onData);
726
- _socket.on('error', _onError);
727
- _socket.on('close', _onClose);
728
- }
729
-
730
- return _connectDeferred.promise;
731
- };
732
-
733
- self.request = function(message, streamflag) {
734
- if(options.warnings && !_connectDeferred) {
735
- options.warningFn(options.logger, 'Request received before a call to connect. This request will not be processed until you have called connect.');
736
- }
737
- return _request(message, _state == 'disconnected' ? _messageQueueDisconnected : _messageQueue, (streamflag === true));
738
- };
739
-
740
- self.close = function() {
741
- _closeDeferred = Q.defer();
742
- if(_state == 'destroyed') _closeDeferred.resolve();
743
- else if(!_msgLoopRunning) {
744
- self.destroy();
745
- _closeDeferred.resolve();
746
- }
747
- return _closeDeferred.promise;
748
- };
749
-
750
- self.end = function(data = null, cb = null) {
751
- if(_socket) {
752
- _socket.end(data, cb);
753
- }
754
- };
755
-
756
- /**
757
- *
758
- * @param msg message that will be passed to the error handlers of the pending queries.
759
- */
760
- self.destroy = function(msg) {
761
- _destroySocket();
762
- _setState('destroyed');
763
- function failQuery(message) {
764
- message.deferred.reject(new Error(msg ? msg : 'Connection destroyed'));
765
- }
766
- _curMessage && failQuery(_curMessage);
767
- _messageQueue.forEach(failQuery);
768
- _messageQueueDisconnected && _messageQueueDisconnected.forEach(failQuery);
769
-
770
- _messageQueue = _emptyMessageQueue('main');
771
- _messageQueueDisconnected = _emptyMessageQueue('disconnected');
772
- };
773
-
774
-
775
- if(options.testing) {
776
- self.socketError = function(statusCode, permanently) {
777
- if(!_socket) throw new Error('Socket not initialized yet');
778
- _socket.end();
779
- _socket.emit('error', statusCode);
780
- _socket.emit('close', true);
781
- _failPermanently = permanently;
782
- }
783
- }
784
- };