monetdb 1.3.3 → 2.0.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/.github/workflows/Linux.yml +45 -0
- package/.github/workflows/docs.yml +79 -0
- package/.github/workflows/macos.yml +43 -0
- package/.github/workflows/monetdb-versions.yml +43 -0
- package/README.md +43 -512
- package/docs/components/alert.tsx +10 -0
- package/docs/components/info.tsx +6 -0
- package/docs/next.config.js +24 -0
- package/docs/package-lock.json +5069 -0
- package/docs/package.json +22 -0
- package/docs/pages/_app.js +9 -0
- package/docs/pages/_meta.json +16 -0
- package/docs/pages/apis/_meta.json +4 -0
- package/docs/pages/apis/connection.mdx +60 -0
- package/docs/pages/apis/result.mdx +39 -0
- package/docs/pages/index.mdx +27 -0
- package/docs/theme.config.js +35 -0
- package/docs/v1/README.md +532 -0
- package/package.json +17 -21
- package/src/PrepareStatement.ts +37 -0
- package/src/connection.ts +125 -0
- package/src/defaults.ts +13 -0
- package/src/file-transfer.ts +173 -0
- package/src/index.ts +3 -0
- package/src/mapi.ts +1016 -0
- package/src/monetize.ts +67 -0
- package/test/connection.ts +43 -0
- package/test/exec-queries.ts +100 -0
- package/test/filetransfer.ts +94 -0
- package/test/prepare-statement.ts +27 -0
- package/test/query-stream.ts +41 -0
- package/test/tmp/.gitignore +4 -0
- package/tsconfig.json +24 -0
- package/.travis.yml +0 -11
- package/index.js +0 -5
- package/src/mapi-connection.js +0 -784
- package/src/monetdb-connection.js +0 -385
- package/src/utils.js +0 -27
- package/test/common.js +0 -45
- package/test/install-monetdb.sh +0 -11
- package/test/monetdb_stream.js +0 -106
- package/test/start-monetdb.sh +0 -38
- package/test/test.js +0 -908
- package/test/test_connection.js +0 -290
- /package/docs/{README.v0.md → v0/README.v0.md} +0 -0
- /package/docs/{MapiConnection.md → v1/MapiConnection.md} +0 -0
- /package/docs/{v1-notes.md → v1/v1-notes.md} +0 -0
package/src/mapi-connection.js
DELETED
@@ -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(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(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 = new Buffer(read_cnt);
|
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 = new Buffer(data.length - read_cnt);
|
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
|
-
};
|