tiesdb-client
Version:
156 lines (127 loc) • 5.61 kB
JavaScript
const WebSocketClient = require('websocket').client;
const Builder = require('../request/builder');
const codec = require('../request/codec');
const sqlConverter = require('./sqlconverter');
const Tag = codec.Tag;
const C = require('../request/constants');
const Record = require('../request/record');
class Connection {
constructor(url){
this.socket = new WebSocketClient({
fragmentOutgoingMessages: false,
maxReceivedFrameSize: 4*1024*1024*1024,
maxReceivedMessageSize: 4*1024*1024*1024,
});
this.requestId = 0;
this.requests = {};
this.connecting = false;
this.header = Buffer.from('C001BA5E1225EFFF0000000000000001', 'hex');
this.consistency = C.Consistency.QUORUM;
if(url)
this.connect(url);
}
connect(url) {
if(this.connecting)
throw new Error('Already connecting...');
this.connecting = true;
const self = this;
this.waitForConnection = new Promise(((resolve, reject) => {
self.socket.on('connect', (connection) => {
self.connection = connection;
console.log('connected!');
connection.on('close', () => { console.log('closed!') });
connection.on('error', (error) => { console.log('Connection error: ' + error) });
connection.on('message', (data) => {
if(data.type != 'binary')
throw new Error('Wrong message format from server: ' + data.type);
console.log('Data: ' + data.binaryData.toString('hex'));
let header = data.binaryData.slice(0, 16);
if(Buffer.compare(header, this.header) != 0)
throw new Error('Wrong message header from server: ' + header.toString('hex'));
let body = data.binaryData.slice(16);
let response = codec.decode(body);
console.log(response.toString('xml'));
let msgId = response.getChild('MessageId');
if(!msgId) {
if(response.name === 'Error'){
let errs = response.getChildren('ErrorMessage');
throw new Error(errs.map(e => e.value).join('\n'));
}
throw new Error('Invalid response without MessageId: ' + response.name);
}
let requestId = msgId.value;
let func = this.requests[requestId];
if(!func)
throw new Error('Unknown response MessageId: ' + requestId);
func(response);
delete this.requests[requestId];
});
this.connecting = false;
resolve(connection);
});
self.socket.on('connectFailed', (error) => {
console.log('connect error: ' + error);
this.connecting = false;
reject(error);
});
}));
this.socket.connect(url);
return this.waitForConnection;
}
__sendRequest(/*Tag*/ tagRoot) {
let self = this;
const requestId = self.requestId;
if(!this.connection || !this.connection.connected)
throw new Error('Connection is not open!');
let data = codec.encode(tagRoot);
console.log(tagRoot.name + ': ' + data.toString('hex'));
console.log(tagRoot.toString('xml'));
this.connection.sendBytes(Buffer.concat([this.header, data]));
return new Promise((resolve, reject) => {
self.requests[requestId] = (response, error) => {
if (response)
resolve(response);
else
reject(error);
};
});
}
modify(records, pk) {
let builder = new Builder(records);
let tag = builder.buildModification(pk, ++this.requestId);
return this.__sendRequest(tag);
}
use(tablespace) {
this.tablespace = tablespace;
}
close(code, error) {
if(this.connecting)
throw new Error('Can not close connection while it is connecting');
if(this.connection) {
this.connection.close(code || 1000, error || "");
this.connection = null;
}
}
async recollect(request, pk) {
if(typeof request === 'string'){
let tagRequest = new Tag("RecollectionRequest");
//This is TQL select clause. Convert it to ebml
let ast = sqlConverter.compile(request);
tagRequest.addChild(new Tag({name: 'Consistency', value: this.consistency}));
tagRequest.addChild(new Tag({name: 'MessageId', value: ++this.requestId}));
sqlConverter.makeRequest(tagRequest, ast, this.tablespace);
request = tagRequest;
}
//TODO: sign retrieve request
let result = await this.__sendRequest(request);
let order = sqlConverter.getColumnsOrder(request);
let results = result.getChildren("RecollectionResult");
let records = [];
for(let r of results){
let record = Record.fromRecollectionResult(r, order);
records.push(record);
}
return records;
}
}
module.exports = Connection;