From ddd68596269290d607725aa71f6f834d5dc65642 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Apr 2014 11:30:02 +0100 Subject: [PATCH 001/308] fix #26 and add test case --- lib/protocol/serializer.js | 20 ++++++++------------ test/bugs/26-number-strings.js | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 test/bugs/26-number-strings.js diff --git a/lib/protocol/serializer.js b/lib/protocol/serializer.js index 131136f..0303437 100644 --- a/lib/protocol/serializer.js +++ b/lib/protocol/serializer.js @@ -61,8 +61,14 @@ function serializeDocument (document, isMap) { * @return {String} The serialized value. */ function serializeValue (value) { - if (isMD5(value)) { - return '\"\"' + value.replace(/\\/, "\\\\").replace(/"/g, "\\\"") + "\"\""; + if (typeof value === 'string') { + if (value[0] == +value[0]) { + // strings that start with numbers must be double quoted. + return '""' + value.replace(/\\/, "\\\\").replace(/"/g, '\\"') + '""'; + } + else { + return '"' + value.replace(/\\/, "\\\\").replace(/"/g, '\\"') + '"'; + } } else if (''+value === value) { return '\"' + value.replace(/\\/, "\\\\").replace(/"/g, "\\\"") + "\""; @@ -125,16 +131,6 @@ function serializeObject (value) { -/** - * Determine whether the given value is a valid MD5 hash. - * @param {String} value The value to check - * @return {Boolean} true if the value is a valid md5, otherwise false. - */ -function isMD5 (value) { - return /^[0-9a-f]{32}$/i.test(value); -} - - // export the public methods diff --git a/test/bugs/26-number-strings.js b/test/bugs/26-number-strings.js new file mode 100644 index 0000000..fc34413 --- /dev/null +++ b/test/bugs/26-number-strings.js @@ -0,0 +1,22 @@ +describe("Bug #26: Issue while adding IP as a value to a Vertex", function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_26') + .bind(this) + .then(function () { + return this.db.class.create('Host'); + }) + .then(function (item) { + this.class = item; + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_26'); + }); + + it('should insert an IP into the database, using a dynamic field', function () { + return this.db.insert().into('Host').set({ip: '127.0.0.1'}).one() + .then(function (host) { + host.ip.should.equal('127.0.0.1'); + }) + }) +}); \ No newline at end of file From 628817d5d016d6497e7090a5ce265976cfe55fa7 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Apr 2014 11:44:00 +0100 Subject: [PATCH 002/308] Closes #25, could not replicate --- test/bugs/25-property-create.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 test/bugs/25-property-create.js diff --git a/test/bugs/25-property-create.js b/test/bugs/25-property-create.js new file mode 100644 index 0000000..fa93588 --- /dev/null +++ b/test/bugs/25-property-create.js @@ -0,0 +1,28 @@ +describe("Bug #25: Create undefined in Myclass.property.create", function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_25') + .bind(this) + .then(function () { + return this.db.class.create('Member', 'V'); + }) + .then(function (item) { + this.class = item; + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_25'); + }); + + it('Let me create a property immediately after creating a class', function () { + var values = { + name: 'name', + type: 'String', + mandatory: true, + max: 65 + }; + return this.class.property.create(values) + .then(function (prop) { + prop.should.have.properties(values); + }) + }); +}); \ No newline at end of file From 0b6fccb6bbb3133ed19100b235a302cc594c9a68 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Apr 2014 13:14:56 +0100 Subject: [PATCH 003/308] deserializer tweaks --- lib/protocol/deserializer.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/protocol/deserializer.js b/lib/protocol/deserializer.js index 59f863c..f45e9b2 100644 --- a/lib/protocol/deserializer.js +++ b/lib/protocol/deserializer.js @@ -37,30 +37,29 @@ function deserializeDocument (serialized, document, isMap) { serialized = serialized.trim(); document = document || {}; - var classIndex = serialized.indexOf("@"), - indexOfColon = serialized.indexOf(":"), - fieldIndex, field, commaIndex, value; + var classIndex = serialized.indexOf("@"), + indexOfColon = serialized.indexOf(":"); if (~classIndex && (indexOfColon > classIndex || !~indexOfColon)) { - document['@class'] = serialized.substr(0, classIndex); - serialized = serialized.substr(classIndex + 1); + document['@class'] = serialized.substring(0, classIndex); + serialized = serialized.substring(classIndex + 1); } if (!isMap) { document['@type'] = 'd'; } - var fieldIndex; + var fieldIndex, field, commaIndex, value; while (~(fieldIndex = serialized.indexOf(':'))) { - field = serialized.substr(0, fieldIndex); - serialized = serialized.substr(fieldIndex + 1); - if (field.charAt(0) === "\"" && field[field.length - 1] === "\"") { + field = serialized.substring(0, fieldIndex); + serialized = serialized.substring(fieldIndex + 1); + if (field.charAt(0) === "\"" && field.charAt(field.length - 1) === "\"") { field = field.substring(1, field.length - 1); } commaIndex = findCommaIndex(serialized); - value = serialized.substr(0, commaIndex); - serialized = serialized.substr(commaIndex + 1); + value = serialized.substring(0, commaIndex); + serialized = serialized.substring(commaIndex + 1); value = deserializeValue(value); document[field] = value; } @@ -113,7 +112,7 @@ function deserializeValue (value) { } var firstChar = value.charAt(0), - lastChar = value[value.length - 1], + lastChar = value.charAt(value.length - 1), values; if (firstChar === "\"") { From 19691c5cf61e5d62b69e802cfe426c16aed4196e Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Apr 2014 13:15:42 +0100 Subject: [PATCH 004/308] add initial rest protocol dependencies and structure --- lib/rest-protocol/operation.js | 39 +++++++++++++++++++++++++ lib/rest-protocol/operations/connect.js | 28 ++++++++++++++++++ package.json | 5 ++-- 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 lib/rest-protocol/operation.js create mode 100644 lib/rest-protocol/operations/connect.js diff --git a/lib/rest-protocol/operation.js b/lib/rest-protocol/operation.js new file mode 100644 index 0000000..22e4f1c --- /dev/null +++ b/lib/rest-protocol/operation.js @@ -0,0 +1,39 @@ +var utils = require('../utils'); + +function Operation (data) { + this.data = data || {}; +} + +/** + * # Operations + * + * The base class for operations, provides a simple DSL for defining + * the steps required to send a command to the server, and then read + * the response. + * + * Each operation should implement the `write()` and `read()` methods. + * + * @param {Object} data The data for the operation. + */ +function Operation (data) { + this.status = Operation.PENDING; + this.writeOps = []; + this.readOps = []; + this.stack = [{}]; + this.data = data || {}; +} + +module.exports = Operation; + +// operation statuses + + +Operation.PENDING = 0; +Operation.WRITTEN = 1; +Operation.READING = 2; +Operation.COMPLETE = 3; +Operation.ERROR = 4; +Operation.PUSH_DATA = 5; + +// make it easy to inherit from the base class +Operation.extend = utils.extend; diff --git a/lib/rest-protocol/operations/connect.js b/lib/rest-protocol/operations/connect.js new file mode 100644 index 0000000..715c9d3 --- /dev/null +++ b/lib/rest-protocol/operations/connect.js @@ -0,0 +1,28 @@ +var Operation = require('../operation'), + constants = require('../constants'), + npmPackage = require('../../../package.json'); + +module.exports = Operation.extend({ + id: 'REQUEST_CONNECT', + opCode: 2, + writer: function () { + return { + + }; + + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId || -1) + .writeString(npmPackage.name) + .writeString(npmPackage.version) + .writeShort(+constants.PROTOCOL_VERSION) + .writeString('') // client id + .writeString(this.data.username) + .writeString(this.data.password); + }, + reader: function () { + this + .readStatus('status') + .readInt('sessionId'); + } +}); \ No newline at end of file diff --git a/package.json b/package.json index abdfa58..1baebd8 100755 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ }, "dependencies": { "bluebird": "*", - "yargs": "*" + "yargs": "*", + "request": "~2.34.0" }, "devDependencies": { "mocha": ">=1.18.2", @@ -74,4 +75,4 @@ "url": "http://www.apache.org/licenses/LICENSE-2.0" } ] -} \ No newline at end of file +} From 4c4e9a614f7ed8352dd61959e84b7f3e5ab8e9ea Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Apr 2014 14:04:39 +0100 Subject: [PATCH 005/308] serializer / deserializer performance tweaks --- lib/protocol/deserializer.js | 33 +++++++++++++++++---------------- lib/protocol/serializer.js | 10 ++++------ 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/lib/protocol/deserializer.js b/lib/protocol/deserializer.js index f45e9b2..434e0aa 100644 --- a/lib/protocol/deserializer.js +++ b/lib/protocol/deserializer.js @@ -38,28 +38,29 @@ function deserializeDocument (serialized, document, isMap) { document = document || {}; var classIndex = serialized.indexOf("@"), - indexOfColon = serialized.indexOf(":"); + indexOfColon = serialized.indexOf(":"), + fieldIndex, field, commaIndex, value; if (~classIndex && (indexOfColon > classIndex || !~indexOfColon)) { - document['@class'] = serialized.substring(0, classIndex); - serialized = serialized.substring(classIndex + 1); + document['@class'] = serialized.substr(0, classIndex); + serialized = serialized.substr(classIndex + 1); } if (!isMap) { document['@type'] = 'd'; } - var fieldIndex, field, commaIndex, value; + var fieldIndex; while (~(fieldIndex = serialized.indexOf(':'))) { - field = serialized.substring(0, fieldIndex); - serialized = serialized.substring(fieldIndex + 1); + field = serialized.substr(0, fieldIndex); + serialized = serialized.substr(fieldIndex + 1); if (field.charAt(0) === "\"" && field.charAt(field.length - 1) === "\"") { field = field.substring(1, field.length - 1); } commaIndex = findCommaIndex(serialized); - value = serialized.substring(0, commaIndex); - serialized = serialized.substring(commaIndex + 1); + value = serialized.substr(0, commaIndex); + serialized = serialized.substr(commaIndex + 1); value = deserializeValue(value); document[field] = value; } @@ -117,26 +118,26 @@ function deserializeValue (value) { if (firstChar === "\"") { // string - return value.substr(1, value.length - 2).replace(/\\"/g, "\"").replace(/\\\\/, "\\"); + return value.substring(1, value.length - 1).replace(/\\"/g, "\"").replace(/\\\\/, "\\"); } else if (firstChar === '%' && lastChar === ';') { return deserializeBag(value); } else if (lastChar === 't' || lastChar === 'a') { // date - return new Date(parseInt(value.substr(0, value.length - 1))); + return new Date(parseInt(value.substring(0, value.length))); } else if (firstChar === '(') { // object / document - return deserializeDocument(value.substr(1, value.length - 2)); + return deserializeDocument(value.substring(1, value.length - 1)); } if (firstChar === '{') { // map - return deserializeDocument(value.substr(1, value.length - 2), {}, true); + return deserializeDocument(value.substring(1, value.length - 1), {}, true); } else if (firstChar === '[' || firstChar === '<') { //process Set <...> like List [...] - values = splitList(value.substr(1, value.length - 2)); + values = splitList(value.substring(1, value.length - 1)); for (var i = 0, length = values.length; i < length; i++) { values[i] = deserializeValue(values[i]); } @@ -144,15 +145,15 @@ function deserializeValue (value) { } else if (lastChar === 'b') { // byte - return String.fromCharCode(parseInt(value.substr(0, value.length - 1))); + return String.fromCharCode(+(value.substring(0, value.length))); } else if (lastChar === 'l' || lastChar === 's' || lastChar === 'c') { // integer - return parseInt(value.substr(0, value.length - 1), 10); + return parseInt(value.substring(0, value.length), 10); } else if (lastChar === 'f' || lastChar === 'd') { // float / decimal - return parseFloat(value.substr(0, value.length - 1)); + return parseFloat(value.substring(0, value.length)); } else if (+value == value) { // integer diff --git a/lib/protocol/serializer.js b/lib/protocol/serializer.js index 0303437..e302e61 100644 --- a/lib/protocol/serializer.js +++ b/lib/protocol/serializer.js @@ -61,7 +61,8 @@ function serializeDocument (document, isMap) { * @return {String} The serialized value. */ function serializeValue (value) { - if (typeof value === 'string') { + var type = typeof value; + if (type === 'string') { if (value[0] == +value[0]) { // strings that start with numbers must be double quoted. return '""' + value.replace(/\\/, "\\\\").replace(/"/g, '\\"') + '""'; @@ -70,13 +71,10 @@ function serializeValue (value) { return '"' + value.replace(/\\/, "\\\\").replace(/"/g, '\\"') + '"'; } } - else if (''+value === value) { - return '\"' + value.replace(/\\/, "\\\\").replace(/"/g, "\\\"") + "\""; - } - else if (+value === value) { + else if (type === 'number') { return ~value.toString().indexOf('.') ? value + 'f' : value; } - else if (value === true || value === false) { + else if (type === 'boolean') { return value ? true : false; } else if (Object.prototype.toString.call(value) === '[object Date]') { From 93181f199282a794c38d24a79a051723f8477258 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Apr 2014 14:56:49 +0100 Subject: [PATCH 006/308] move connection and connection pool to binary transport --- lib/server/index.js | 4 ++-- lib/{server => transport/binary}/connection-pool.js | 0 lib/{server => transport/binary}/connection.js | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) rename lib/{server => transport/binary}/connection-pool.js (100%) rename lib/{server => transport/binary}/connection.js (98%) diff --git a/lib/server/index.js b/lib/server/index.js index 329740a..ea46fc9 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -1,5 +1,5 @@ -var ConnectionPool = require('./connection-pool'), - Connection = require('./connection'), +var ConnectionPool = require('../transport/binary/connection-pool'), + Connection = require('../transport/binary/connection'), utils = require('../utils'), errors = require('../errors'), Db = require('../db/index'), diff --git a/lib/server/connection-pool.js b/lib/transport/binary/connection-pool.js similarity index 100% rename from lib/server/connection-pool.js rename to lib/transport/binary/connection-pool.js diff --git a/lib/server/connection.js b/lib/transport/binary/connection.js similarity index 98% rename from lib/server/connection.js rename to lib/transport/binary/connection.js index 70ff9fb..be2ca09 100644 --- a/lib/server/connection.js +++ b/lib/transport/binary/connection.js @@ -1,9 +1,9 @@ var net = require('net') util = require('util'), - utils = require('../utils'), - errors = require('../errors'), - Operation = require('../protocol/operation'), - operations = require('../protocol/operations'), + utils = require('../../utils'), + errors = require('../../errors'), + Operation = require('../../protocol/operation'), + operations = require('../../protocol/operations'), EventEmitter = require('events').EventEmitter, Promise = require('bluebird'); From 5a1c4ea3c798f1772a63ea2ea44a34f18c3a435e Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Apr 2014 15:12:14 +0100 Subject: [PATCH 007/308] move existing connection related methods to binary transport from server --- lib/server/index.js | 117 ++---------- lib/transport/binary/index.js | 173 ++++++++++++++++++ test/server/server-test.js | 28 --- .../transport/binary/binary-transport-test.js | 28 +++ 4 files changed, 218 insertions(+), 128 deletions(-) create mode 100644 lib/transport/binary/index.js create mode 100644 test/transport/binary/binary-transport-test.js diff --git a/lib/server/index.js b/lib/server/index.js index ea46fc9..8a4ac86 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -1,5 +1,6 @@ var ConnectionPool = require('../transport/binary/connection-pool'), Connection = require('../transport/binary/connection'), + BinaryTransport = require('../transport/binary'), utils = require('../utils'), errors = require('../errors'), Db = require('../db/index'), @@ -15,9 +16,8 @@ var ConnectionPool = require('../transport/binary/connection-pool'), * @param {String|Object} options The server URL, or configuration object */ function Server (options) { - this.applySettings(options || {}); + this.configure(options || {}); this.init(); - this.augment('config', require('./config')); EventEmitter.call(this); this.setMaxListeners(Infinity); @@ -39,24 +39,21 @@ module.exports = Server; * @param {Object} config The configuration for the server. * @return {Server} The configured server object. */ -Server.prototype.applySettings = function (options) { - - this.connecting = false; - this.closing = false; +Server.prototype.configure = function (config) { - this.host = options.host || options.hostname || 'localhost'; - this.port = options.port || 2424; - this.username = options.username || 'root'; - this.password = options.password || ''; + this.configureLogger(config.logger || {}); + this.configureTransport(config); +}; - this.sessionId = -1; - this.configureLogger(options.logger || {}); - if (options.pool) { - this.configurePool(options.pool); - } - else { - this.configureConnection(); - } +/** + * Configure the transport for the server. + * + * @param {Object} config The server config. + * @return {Server} The configured server object. + */ +Server.prototype.configureTransport = function (config) { + this.transport = new BinaryTransport(config); + return this; }; /** @@ -75,45 +72,6 @@ Server.prototype.configureLogger = function (config) { }; -/** - * Configure a connection for the server. - * - * @return {Server} The server instance with the configured connection. - */ -Server.prototype.configureConnection = function () { - this.connection = new Connection({ - host: this.host, - port: this.port, - logger: this.logger - }); - this.connection.on('update-config', function (config) { - this.logger.debug('updating config...'); - this.serverCluster = config; - }.bind(this)); - return this; -}; - -/** - * Configure a connection pool for the server. - * - * @param {Object} config The connection pool config - * @return {Server} The server instance with the configured connection pool. - */ -Server.prototype.configurePool = function (config) { - this.pool = new ConnectionPool({ - host: this.host, - port: this.port, - logger: this.logger, - max: config.max - }); - this.pool.on('update-config', function (config) { - this.logger.debug('updating config...'); - this.serverCluster = config; - }.bind(this)); - return this; -}; - - /** * Initialize the server instance. */ @@ -121,33 +79,6 @@ Server.prototype.init = function () { }; -/** - * Connect to the server. - * - * @promise {Server} The connected server instance. - */ -Server.prototype.connect = function () { - if (this.sessionId !== -1) - return Promise.resolve(this); - - if (this.connecting) { - return this.connecting; - } - - this.connecting = (this.pool || this.connection).send('connect', { - username: this.username, - password: this.password - }) - .bind(this) - .then(function (response) { - this.logger.debug('got session id: ' + response.sessionId); - this.sessionId = response.sessionId; - return this; - }); - - return this.connecting; -}; - /** * Send an operation to the server, * @@ -156,18 +87,7 @@ Server.prototype.connect = function () { * @promise {Mixed} The result of the operation. */ Server.prototype.send = function (operation, options) { - options = options || {}; - if (~this.sessionId) { - options.sessionId = options.sessionId != null ? options.sessionId : this.sessionId; - return (this.pool || this.connection).send(operation, options); - } - else { - return this.connect() - .then(function (server) { - options.sessionId = options.sessionId != null ? options.sessionId : this.sessionId; - return (server.pool || server.connection).send(operation, options); - }); - } + return this.transport.send(operation, options); }; /** @@ -176,10 +96,7 @@ Server.prototype.send = function (operation, options) { * @return {Server} the disconnected server instance */ Server.prototype.close = function () { - if (!this.closing && this.socket) { - this.closing = false; - this.sessionId = -1; - } + this.transport.close(); return this; } diff --git a/lib/transport/binary/index.js b/lib/transport/binary/index.js new file mode 100644 index 0000000..dfeb291 --- /dev/null +++ b/lib/transport/binary/index.js @@ -0,0 +1,173 @@ +var ConnectionPool = require('./connection-pool'), + Connection = require('./connection'), + utils = require('../../utils'), + errors = require('../../errors'), + Db = require('../../db/index'), + Promise = require('bluebird'), + net = require('net'), + util = require('util'), + EventEmitter = require('events').EventEmitter; + + +/** + * # Binary Transport + * + * @param {Object} config The configuration for the transport. + */ +function BinaryTransport (config) { + EventEmitter.call(this); + this.setMaxListeners(Infinity); + this.configure(config || {}); +} + +util.inherits(BinaryTransport, EventEmitter); + +BinaryTransport.extend = utils.extend; +BinaryTransport.prototype.augment = utils.augment; + + +module.exports = BinaryTransport; + + +/** + * Configure the transport. + * + * @param {Object} config The transport configuration. + */ +BinaryTransport.prototype.configure = function (config) { + this.connecting = false; + this.closing = false; + + this.host = config.host || config.hostname || 'localhost'; + this.port = config.port || 2424; + this.username = config.username || 'root'; + this.password = config.password || ''; + + this.sessionId = -1; + this.configureLogger(config.logger || {}); + if (config.pool) { + this.configurePool(config.pool); + } + else { + this.configureConnection(); + } +}; + +/** + * Configure the logger for the transport. + * + * @param {Object} config The logger config + * @return {BinaryTransport} The transport instance with the configured logger. + */ +BinaryTransport.prototype.configureLogger = function (config) { + this.logger = { + error: config.error || console.error.bind(console), + log: config.log || console.log.bind(console), + debug: config.debug || function () {} // do not log debug by default + }; + return this; +}; + + +/** + * Configure a connection for the transport. + * + * @return {BinaryTransport} The transport instance with the configured connection. + */ +BinaryTransport.prototype.configureConnection = function () { + this.connection = new Connection({ + host: this.host, + port: this.port, + logger: this.logger + }); + this.connection.on('update-config', function (config) { + this.logger.debug('updating config...'); + this.transportCluster = config; + }.bind(this)); + return this; +}; + +/** + * Configure a connection pool for the transport. + * + * @param {Object} config The connection pool config + * @return {BinaryTransport} The transport instance with the configured connection pool. + */ +BinaryTransport.prototype.configurePool = function (config) { + this.pool = new ConnectionPool({ + host: this.host, + port: this.port, + logger: this.logger, + max: config.max + }); + this.pool.on('update-config', function (config) { + this.logger.debug('updating config...'); + this.serverCluster = config; + }.bind(this)); + return this; +}; + + + +/** + * Connect to the server. + * + * @promise {BinaryTransport} The connected transport instance. + */ +BinaryTransport.prototype.connect = function () { + if (this.sessionId !== -1) + return Promise.resolve(this); + + if (this.connecting) { + return this.connecting; + } + + this.connecting = (this.pool || this.connection).send('connect', { + username: this.username, + password: this.password + }) + .bind(this) + .then(function (response) { + this.logger.debug('got session id: ' + response.sessionId); + this.sessionId = response.sessionId; + return this; + }); + + return this.connecting; +}; + +/** + * Send an operation to the server, + * + * @param {Integer} operation The id of the operation to send. + * @param {Object} options The options for the operation. + * @promise {Mixed} The result of the operation. + */ +BinaryTransport.prototype.send = function (operation, options) { + options = options || {}; + if (~this.sessionId) { + options.sessionId = options.sessionId != null ? options.sessionId : this.sessionId; + return (this.pool || this.connection).send(operation, options); + } + else { + return this.connect() + .then(function (server) { + options.sessionId = options.sessionId != null ? options.sessionId : this.sessionId; + return (server.pool || server.connection).send(operation, options); + }); + } +}; + + +/** + * Close the connection to the server. + * + * @return {Server} the disconnected server instance + */ +BinaryTransport.prototype.close = function () { + if (!this.closing && this.socket) { + this.closing = false; + this.sessionId = -1; + } + return this; +} \ No newline at end of file diff --git a/test/server/server-test.js b/test/server/server-test.js index 04e35ff..713e2cc 100644 --- a/test/server/server-test.js +++ b/test/server/server-test.js @@ -1,33 +1,5 @@ var errors = LIB.errors; - -describe("Server", function () { - describe('Server::connect()', function () { - it("should negotiate a connection", function () { - return TEST_SERVER.connect() - .then(function (server) { - server.sessionId.should.be.above(-1); - }); - }); - }); - describe('Server::send()', function () { - it("should handle errors correctly", function () { - return TEST_SERVER.send('db-open', { - name: 'not_an_existing_database', - type: 'graph', - username: 'admin', - password: 'admin' - }) - .then(function (response) { - throw new Error('Should Not Happen!'); - }) - .catch(errors.Request, function (e) { - e.type.should.equal('com.orientechnologies.orient.core.exception.OConfigurationException'); - return true; - }); - }) - }); -}); describe('Server::create()', function () { it("should create a new database", function () { return TEST_SERVER.create({ diff --git a/test/transport/binary/binary-transport-test.js b/test/transport/binary/binary-transport-test.js new file mode 100644 index 0000000..e12832f --- /dev/null +++ b/test/transport/binary/binary-transport-test.js @@ -0,0 +1,28 @@ +var errors = LIB.errors; +describe("Binary Transport", function () { + describe('BinaryTransport::connect()', function () { + it("should negotiate a connection", function () { + return TEST_SERVER.transport.connect() + .then(function (server) { + server.sessionId.should.be.above(-1); + }); + }); + }); + describe('BinaryTransport::send()', function () { + it("should handle errors correctly", function () { + return TEST_SERVER.transport.send('db-open', { + name: 'not_an_existing_database', + type: 'graph', + username: 'admin', + password: 'admin' + }) + .then(function (response) { + throw new Error('Should Not Happen!'); + }) + .catch(errors.Request, function (e) { + e.type.should.equal('com.orientechnologies.orient.core.exception.OConfigurationException'); + return true; + }); + }) + }); +}); \ No newline at end of file From 8b4f1ff0f5ec53c1e05665b5b8811c8408d47f7b Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Apr 2014 15:21:59 +0100 Subject: [PATCH 008/308] move protocol to binary transport --- lib/index.js | 2 +- lib/transport/binary/connection.js | 4 ++-- lib/transport/binary/index.js | 2 ++ lib/{ => transport/binary}/protocol/constants.js | 0 lib/{ => transport/binary}/protocol/deserializer.js | 2 +- lib/{ => transport/binary}/protocol/index.js | 0 lib/{ => transport/binary}/protocol/long.js | 0 lib/{ => transport/binary}/protocol/operation-queue.js | 2 +- lib/{ => transport/binary}/protocol/operation.js | 4 ++-- lib/{ => transport/binary}/protocol/operations/command.js | 0 lib/{ => transport/binary}/protocol/operations/config-get.js | 0 lib/{ => transport/binary}/protocol/operations/config-list.js | 0 lib/{ => transport/binary}/protocol/operations/config-set.js | 0 lib/{ => transport/binary}/protocol/operations/connect.js | 2 +- .../binary}/protocol/operations/datacluster-add.js | 0 .../binary}/protocol/operations/datacluster-count.js | 0 .../binary}/protocol/operations/datacluster-datarange.js | 0 .../binary}/protocol/operations/datacluster-drop.js | 0 .../binary}/protocol/operations/datasegment-add.js | 0 .../binary}/protocol/operations/datasegment-drop.js | 0 lib/{ => transport/binary}/protocol/operations/db-close.js | 0 .../binary}/protocol/operations/db-countrecords.js | 0 lib/{ => transport/binary}/protocol/operations/db-create.js | 0 lib/{ => transport/binary}/protocol/operations/db-delete.js | 0 lib/{ => transport/binary}/protocol/operations/db-exists.js | 0 lib/{ => transport/binary}/protocol/operations/db-list.js | 0 lib/{ => transport/binary}/protocol/operations/db-open.js | 2 +- lib/{ => transport/binary}/protocol/operations/db-reload.js | 0 lib/{ => transport/binary}/protocol/operations/db-size.js | 0 lib/{ => transport/binary}/protocol/operations/index.js | 0 .../binary}/protocol/operations/record-clean-out.js | 2 +- .../binary}/protocol/operations/record-create.js | 2 +- .../binary}/protocol/operations/record-delete.js | 2 +- lib/{ => transport/binary}/protocol/operations/record-load.js | 4 ++-- .../binary}/protocol/operations/record-metadata.js | 2 +- .../binary}/protocol/operations/record-update.js | 2 +- lib/{ => transport/binary}/protocol/serializer.js | 2 +- lib/{ => transport/binary}/protocol/writer.js | 0 lib/transport/index.js | 1 + test/protocol/operation-test.js | 2 +- test/protocol/operations/db-operations-test.js | 3 +-- 41 files changed, 22 insertions(+), 20 deletions(-) rename lib/{ => transport/binary}/protocol/constants.js (100%) rename lib/{ => transport/binary}/protocol/deserializer.js (99%) rename lib/{ => transport/binary}/protocol/index.js (100%) rename lib/{ => transport/binary}/protocol/long.js (100%) rename lib/{ => transport/binary}/protocol/operation-queue.js (99%) rename lib/{ => transport/binary}/protocol/operation.js (99%) rename lib/{ => transport/binary}/protocol/operations/command.js (100%) rename lib/{ => transport/binary}/protocol/operations/config-get.js (100%) rename lib/{ => transport/binary}/protocol/operations/config-list.js (100%) rename lib/{ => transport/binary}/protocol/operations/config-set.js (100%) rename lib/{ => transport/binary}/protocol/operations/connect.js (91%) rename lib/{ => transport/binary}/protocol/operations/datacluster-add.js (100%) rename lib/{ => transport/binary}/protocol/operations/datacluster-count.js (100%) rename lib/{ => transport/binary}/protocol/operations/datacluster-datarange.js (100%) rename lib/{ => transport/binary}/protocol/operations/datacluster-drop.js (100%) rename lib/{ => transport/binary}/protocol/operations/datasegment-add.js (100%) rename lib/{ => transport/binary}/protocol/operations/datasegment-drop.js (100%) rename lib/{ => transport/binary}/protocol/operations/db-close.js (100%) rename lib/{ => transport/binary}/protocol/operations/db-countrecords.js (100%) rename lib/{ => transport/binary}/protocol/operations/db-create.js (100%) rename lib/{ => transport/binary}/protocol/operations/db-delete.js (100%) rename lib/{ => transport/binary}/protocol/operations/db-exists.js (100%) rename lib/{ => transport/binary}/protocol/operations/db-list.js (100%) rename lib/{ => transport/binary}/protocol/operations/db-open.js (95%) rename lib/{ => transport/binary}/protocol/operations/db-reload.js (100%) rename lib/{ => transport/binary}/protocol/operations/db-size.js (100%) rename lib/{ => transport/binary}/protocol/operations/index.js (100%) rename lib/{ => transport/binary}/protocol/operations/record-clean-out.js (95%) rename lib/{ => transport/binary}/protocol/operations/record-create.js (95%) rename lib/{ => transport/binary}/protocol/operations/record-delete.js (96%) rename lib/{ => transport/binary}/protocol/operations/record-load.js (97%) rename lib/{ => transport/binary}/protocol/operations/record-metadata.js (95%) rename lib/{ => transport/binary}/protocol/operations/record-update.js (96%) rename lib/{ => transport/binary}/protocol/serializer.js (98%) rename lib/{ => transport/binary}/protocol/writer.js (100%) create mode 100644 lib/transport/index.js diff --git a/lib/index.js b/lib/index.js index 7a5d73a..4b74fa8 100755 --- a/lib/index.js +++ b/lib/index.js @@ -8,7 +8,7 @@ Oriento.RecordID = Oriento.RecordId = Oriento.RID = require('./recordid'); Oriento.Server = require('./server'); Oriento.Db = require('./db'); -Oriento.protocol = require('./protocol'); +Oriento.transport = require('./transport'); Oriento.errors = require('./errors'); Oriento.Migration = require('./migration'); Oriento.CLI = require('./cli'); diff --git a/lib/transport/binary/connection.js b/lib/transport/binary/connection.js index be2ca09..95a7826 100644 --- a/lib/transport/binary/connection.js +++ b/lib/transport/binary/connection.js @@ -2,8 +2,8 @@ var net = require('net') util = require('util'), utils = require('../../utils'), errors = require('../../errors'), - Operation = require('../../protocol/operation'), - operations = require('../../protocol/operations'), + Operation = require('./protocol/operation'), + operations = require('./protocol/operations'), EventEmitter = require('events').EventEmitter, Promise = require('bluebird'); diff --git a/lib/transport/binary/index.js b/lib/transport/binary/index.js index dfeb291..7fde59f 100644 --- a/lib/transport/binary/index.js +++ b/lib/transport/binary/index.js @@ -25,6 +25,8 @@ util.inherits(BinaryTransport, EventEmitter); BinaryTransport.extend = utils.extend; BinaryTransport.prototype.augment = utils.augment; +BinaryTransport.protocol = require('./protocol'); + module.exports = BinaryTransport; diff --git a/lib/protocol/constants.js b/lib/transport/binary/protocol/constants.js similarity index 100% rename from lib/protocol/constants.js rename to lib/transport/binary/protocol/constants.js diff --git a/lib/protocol/deserializer.js b/lib/transport/binary/protocol/deserializer.js similarity index 99% rename from lib/protocol/deserializer.js rename to lib/transport/binary/protocol/deserializer.js index 59f863c..6ce1d20 100644 --- a/lib/protocol/deserializer.js +++ b/lib/transport/binary/protocol/deserializer.js @@ -1,4 +1,4 @@ -var RecordID = require('../recordid'), +var RecordID = require('../../../recordid'), Long = require('./long').Long diff --git a/lib/protocol/index.js b/lib/transport/binary/protocol/index.js similarity index 100% rename from lib/protocol/index.js rename to lib/transport/binary/protocol/index.js diff --git a/lib/protocol/long.js b/lib/transport/binary/protocol/long.js similarity index 100% rename from lib/protocol/long.js rename to lib/transport/binary/protocol/long.js diff --git a/lib/protocol/operation-queue.js b/lib/transport/binary/protocol/operation-queue.js similarity index 99% rename from lib/protocol/operation-queue.js rename to lib/transport/binary/protocol/operation-queue.js index 94e6757..5d4ad4a 100644 --- a/lib/protocol/operation-queue.js +++ b/lib/transport/binary/protocol/operation-queue.js @@ -2,7 +2,7 @@ var Promise = require('bluebird'), Operation = require('./operation'), operations = require('./operations'), Emitter = require('events').EventEmitter, - errors = require('../errors'), + errors = require('../../../errors'), util = require('util'); function OperationQueue (socket) { diff --git a/lib/protocol/operation.js b/lib/transport/binary/protocol/operation.js similarity index 99% rename from lib/protocol/operation.js rename to lib/transport/binary/protocol/operation.js index c34b2f9..90348a5 100644 --- a/lib/protocol/operation.js +++ b/lib/transport/binary/protocol/operation.js @@ -1,7 +1,7 @@ var constants = require('./constants'), - utils = require('../utils'), + utils = require('../../../utils'), Long = require('./long').Long, - errors = require('../errors'), + errors = require('../../../errors'), deserializer = require('./deserializer'); diff --git a/lib/protocol/operations/command.js b/lib/transport/binary/protocol/operations/command.js similarity index 100% rename from lib/protocol/operations/command.js rename to lib/transport/binary/protocol/operations/command.js diff --git a/lib/protocol/operations/config-get.js b/lib/transport/binary/protocol/operations/config-get.js similarity index 100% rename from lib/protocol/operations/config-get.js rename to lib/transport/binary/protocol/operations/config-get.js diff --git a/lib/protocol/operations/config-list.js b/lib/transport/binary/protocol/operations/config-list.js similarity index 100% rename from lib/protocol/operations/config-list.js rename to lib/transport/binary/protocol/operations/config-list.js diff --git a/lib/protocol/operations/config-set.js b/lib/transport/binary/protocol/operations/config-set.js similarity index 100% rename from lib/protocol/operations/config-set.js rename to lib/transport/binary/protocol/operations/config-set.js diff --git a/lib/protocol/operations/connect.js b/lib/transport/binary/protocol/operations/connect.js similarity index 91% rename from lib/protocol/operations/connect.js rename to lib/transport/binary/protocol/operations/connect.js index 6e9edf0..e45ecca 100644 --- a/lib/protocol/operations/connect.js +++ b/lib/transport/binary/protocol/operations/connect.js @@ -1,6 +1,6 @@ var Operation = require('../operation'), constants = require('../constants'), - npmPackage = require('../../../package.json'); + npmPackage = require('../../../../../package.json'); module.exports = Operation.extend({ id: 'REQUEST_CONNECT', diff --git a/lib/protocol/operations/datacluster-add.js b/lib/transport/binary/protocol/operations/datacluster-add.js similarity index 100% rename from lib/protocol/operations/datacluster-add.js rename to lib/transport/binary/protocol/operations/datacluster-add.js diff --git a/lib/protocol/operations/datacluster-count.js b/lib/transport/binary/protocol/operations/datacluster-count.js similarity index 100% rename from lib/protocol/operations/datacluster-count.js rename to lib/transport/binary/protocol/operations/datacluster-count.js diff --git a/lib/protocol/operations/datacluster-datarange.js b/lib/transport/binary/protocol/operations/datacluster-datarange.js similarity index 100% rename from lib/protocol/operations/datacluster-datarange.js rename to lib/transport/binary/protocol/operations/datacluster-datarange.js diff --git a/lib/protocol/operations/datacluster-drop.js b/lib/transport/binary/protocol/operations/datacluster-drop.js similarity index 100% rename from lib/protocol/operations/datacluster-drop.js rename to lib/transport/binary/protocol/operations/datacluster-drop.js diff --git a/lib/protocol/operations/datasegment-add.js b/lib/transport/binary/protocol/operations/datasegment-add.js similarity index 100% rename from lib/protocol/operations/datasegment-add.js rename to lib/transport/binary/protocol/operations/datasegment-add.js diff --git a/lib/protocol/operations/datasegment-drop.js b/lib/transport/binary/protocol/operations/datasegment-drop.js similarity index 100% rename from lib/protocol/operations/datasegment-drop.js rename to lib/transport/binary/protocol/operations/datasegment-drop.js diff --git a/lib/protocol/operations/db-close.js b/lib/transport/binary/protocol/operations/db-close.js similarity index 100% rename from lib/protocol/operations/db-close.js rename to lib/transport/binary/protocol/operations/db-close.js diff --git a/lib/protocol/operations/db-countrecords.js b/lib/transport/binary/protocol/operations/db-countrecords.js similarity index 100% rename from lib/protocol/operations/db-countrecords.js rename to lib/transport/binary/protocol/operations/db-countrecords.js diff --git a/lib/protocol/operations/db-create.js b/lib/transport/binary/protocol/operations/db-create.js similarity index 100% rename from lib/protocol/operations/db-create.js rename to lib/transport/binary/protocol/operations/db-create.js diff --git a/lib/protocol/operations/db-delete.js b/lib/transport/binary/protocol/operations/db-delete.js similarity index 100% rename from lib/protocol/operations/db-delete.js rename to lib/transport/binary/protocol/operations/db-delete.js diff --git a/lib/protocol/operations/db-exists.js b/lib/transport/binary/protocol/operations/db-exists.js similarity index 100% rename from lib/protocol/operations/db-exists.js rename to lib/transport/binary/protocol/operations/db-exists.js diff --git a/lib/protocol/operations/db-list.js b/lib/transport/binary/protocol/operations/db-list.js similarity index 100% rename from lib/protocol/operations/db-list.js rename to lib/transport/binary/protocol/operations/db-list.js diff --git a/lib/protocol/operations/db-open.js b/lib/transport/binary/protocol/operations/db-open.js similarity index 95% rename from lib/protocol/operations/db-open.js rename to lib/transport/binary/protocol/operations/db-open.js index 7c144ae..7965370 100644 --- a/lib/protocol/operations/db-open.js +++ b/lib/transport/binary/protocol/operations/db-open.js @@ -1,6 +1,6 @@ var Operation = require('../operation'), constants = require('../constants'), - npmPackage = require('../../../package.json'); + npmPackage = require('../../../../../package.json'); module.exports = Operation.extend({ id: 'REQUEST_DB_OPEN', diff --git a/lib/protocol/operations/db-reload.js b/lib/transport/binary/protocol/operations/db-reload.js similarity index 100% rename from lib/protocol/operations/db-reload.js rename to lib/transport/binary/protocol/operations/db-reload.js diff --git a/lib/protocol/operations/db-size.js b/lib/transport/binary/protocol/operations/db-size.js similarity index 100% rename from lib/protocol/operations/db-size.js rename to lib/transport/binary/protocol/operations/db-size.js diff --git a/lib/protocol/operations/index.js b/lib/transport/binary/protocol/operations/index.js similarity index 100% rename from lib/protocol/operations/index.js rename to lib/transport/binary/protocol/operations/index.js diff --git a/lib/protocol/operations/record-clean-out.js b/lib/transport/binary/protocol/operations/record-clean-out.js similarity index 95% rename from lib/protocol/operations/record-clean-out.js rename to lib/transport/binary/protocol/operations/record-clean-out.js index f4871a2..3dbb55c 100644 --- a/lib/protocol/operations/record-clean-out.js +++ b/lib/transport/binary/protocol/operations/record-clean-out.js @@ -1,6 +1,6 @@ var Operation = require('../operation'), constants = require('../constants'), - RID = require('../../recordid'), + RID = require('../../../../recordid'), serializer = require('../serializer'); module.exports = Operation.extend({ diff --git a/lib/protocol/operations/record-create.js b/lib/transport/binary/protocol/operations/record-create.js similarity index 95% rename from lib/protocol/operations/record-create.js rename to lib/transport/binary/protocol/operations/record-create.js index 4f73123..a5e4ba5 100644 --- a/lib/protocol/operations/record-create.js +++ b/lib/transport/binary/protocol/operations/record-create.js @@ -1,6 +1,6 @@ var Operation = require('../operation'), constants = require('../constants'), - RID = require('../../recordid'), + RID = require('../../../../recordid'), serializer = require('../serializer'); module.exports = Operation.extend({ diff --git a/lib/protocol/operations/record-delete.js b/lib/transport/binary/protocol/operations/record-delete.js similarity index 96% rename from lib/protocol/operations/record-delete.js rename to lib/transport/binary/protocol/operations/record-delete.js index 168c5d5..9f43cb8 100644 --- a/lib/protocol/operations/record-delete.js +++ b/lib/transport/binary/protocol/operations/record-delete.js @@ -1,6 +1,6 @@ var Operation = require('../operation'), constants = require('../constants'), - RID = require('../../recordid'), + RID = require('../../../../recordid'), serializer = require('../serializer'); module.exports = Operation.extend({ diff --git a/lib/protocol/operations/record-load.js b/lib/transport/binary/protocol/operations/record-load.js similarity index 97% rename from lib/protocol/operations/record-load.js rename to lib/transport/binary/protocol/operations/record-load.js index 6e9241f..ef289eb 100644 --- a/lib/protocol/operations/record-load.js +++ b/lib/transport/binary/protocol/operations/record-load.js @@ -1,9 +1,9 @@ var Operation = require('../operation'), constants = require('../constants'), - RID = require('../../recordid'), + RID = require('../../../../recordid'), serializer = require('../serializer'), deserializer = require('../deserializer'), - errors = require('../../errors'); + errors = require('../../../../errors'); module.exports = Operation.extend({ id: 'REQUEST_RECORD_LOAD', diff --git a/lib/protocol/operations/record-metadata.js b/lib/transport/binary/protocol/operations/record-metadata.js similarity index 95% rename from lib/protocol/operations/record-metadata.js rename to lib/transport/binary/protocol/operations/record-metadata.js index f111b8a..4afecf4 100644 --- a/lib/protocol/operations/record-metadata.js +++ b/lib/transport/binary/protocol/operations/record-metadata.js @@ -1,6 +1,6 @@ var Operation = require('../operation'), constants = require('../constants'), - RID = require('../../recordid'), + RID = require('../../../../recordid'), serializer = require('../serializer'); module.exports = Operation.extend({ diff --git a/lib/protocol/operations/record-update.js b/lib/transport/binary/protocol/operations/record-update.js similarity index 96% rename from lib/protocol/operations/record-update.js rename to lib/transport/binary/protocol/operations/record-update.js index 493efe9..aca7076 100644 --- a/lib/protocol/operations/record-update.js +++ b/lib/transport/binary/protocol/operations/record-update.js @@ -1,6 +1,6 @@ var Operation = require('../operation'), constants = require('../constants'), - RID = require('../../recordid'), + RID = require('../../../../recordid'), serializer = require('../serializer'); module.exports = Operation.extend({ diff --git a/lib/protocol/serializer.js b/lib/transport/binary/protocol/serializer.js similarity index 98% rename from lib/protocol/serializer.js rename to lib/transport/binary/protocol/serializer.js index 0303437..d6dddd7 100644 --- a/lib/protocol/serializer.js +++ b/lib/transport/binary/protocol/serializer.js @@ -1,4 +1,4 @@ -var RecordID = require('../recordid'); +var RecordID = require('../../../recordid'); /** * Serialize a record and return it as a buffer. diff --git a/lib/protocol/writer.js b/lib/transport/binary/protocol/writer.js similarity index 100% rename from lib/protocol/writer.js rename to lib/transport/binary/protocol/writer.js diff --git a/lib/transport/index.js b/lib/transport/index.js new file mode 100644 index 0000000..46bf936 --- /dev/null +++ b/lib/transport/index.js @@ -0,0 +1 @@ +exports.Binary = exports.BinaryTransport = require('./binary'); \ No newline at end of file diff --git a/test/protocol/operation-test.js b/test/protocol/operation-test.js index 836e75a..69f0279 100644 --- a/test/protocol/operation-test.js +++ b/test/protocol/operation-test.js @@ -1,4 +1,4 @@ -var Operation = LIB.protocol.Operation; +var Operation = LIB.transport.Binary.protocol.Operation; describe('Operation', function () { diff --git a/test/protocol/operations/db-operations-test.js b/test/protocol/operations/db-operations-test.js index bac8b3b..ecd0dab 100644 --- a/test/protocol/operations/db-operations-test.js +++ b/test/protocol/operations/db-operations-test.js @@ -1,5 +1,4 @@ -var path = require('path'), - dbSessionId = -1, +var dbSessionId = -1, dataCluster = -1, dataSegment = -1, serverCluster = {}, From 455974f5e57d30f69739e4855d9180c99c16b267 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Apr 2014 15:25:17 +0100 Subject: [PATCH 009/308] move binary protocol tests, increase test depth in package.json --- package.json | 6 +++--- test/{ => transport/binary}/protocol/operation-test.js | 0 .../binary}/protocol/operations/config-operations-test.js | 0 .../binary}/protocol/operations/db-operations-test.js | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename test/{ => transport/binary}/protocol/operation-test.js (100%) rename test/{ => transport/binary}/protocol/operations/config-operations-test.js (100%) rename test/{ => transport/binary}/protocol/operations/db-operations-test.js (100%) diff --git a/package.json b/package.json index 1baebd8..53b007d 100755 --- a/package.json +++ b/package.json @@ -65,9 +65,9 @@ "oriento": "./bin/oriento" }, "scripts": { - "test": "echo \"\n\nNOTICE: If tests fail, please ensure you've set the correct credentials in test/test-server.json\n\n\"; node ./node_modules/mocha/bin/mocha ./test/index.js ./test/**/*.js ./test/**/**/*.js --reporter=spec -t 10000", - "watch": "node ./node_modules/mocha/bin/mocha ./test/index.js ./test/**/*.js ./test/**/**/*.js --reporter=spec -t 10000 --watch", - "coverage": "./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha ./test/index.js ./test/**/*.js ./test/**/**/*.js --reporter=spec" + "test": "echo \"\n\nNOTICE: If tests fail, please ensure you've set the correct credentials in test/test-server.json\n\n\"; node ./node_modules/mocha/bin/mocha ./test/index.js ./test/**/*.js ./test/**/**/*.js ./test/**/**/**/*.js ./test/**/**/**/**/*.js --reporter=spec -t 10000", + "watch": "node ./node_modules/mocha/bin/mocha ./test/index.js ./test/**/*.js ./test/**/**/*.js ./test/**/**/**/*.js ./test/**/**/**/**/*.js --reporter=spec -t 10000 --watch", + "coverage": "./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha ./test/index.js ./test/**/*.js ./test/**/**/*.js ./test/**/**/**/*.js ./test/**/**/**/**/*.js --reporter=spec" }, "licenses": [ { diff --git a/test/protocol/operation-test.js b/test/transport/binary/protocol/operation-test.js similarity index 100% rename from test/protocol/operation-test.js rename to test/transport/binary/protocol/operation-test.js diff --git a/test/protocol/operations/config-operations-test.js b/test/transport/binary/protocol/operations/config-operations-test.js similarity index 100% rename from test/protocol/operations/config-operations-test.js rename to test/transport/binary/protocol/operations/config-operations-test.js diff --git a/test/protocol/operations/db-operations-test.js b/test/transport/binary/protocol/operations/db-operations-test.js similarity index 100% rename from test/protocol/operations/db-operations-test.js rename to test/transport/binary/protocol/operations/db-operations-test.js From 69ac2bac0d4c9ae3e97a199d13318b9344364abe Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Apr 2014 15:35:32 +0100 Subject: [PATCH 010/308] move rest-protocol to new directory structure --- lib/{rest-protocol => transport/rest/protocol}/operation.js | 0 .../rest/protocol}/operations/connect.js | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename lib/{rest-protocol => transport/rest/protocol}/operation.js (100%) rename lib/{rest-protocol => transport/rest/protocol}/operations/connect.js (100%) diff --git a/lib/rest-protocol/operation.js b/lib/transport/rest/protocol/operation.js similarity index 100% rename from lib/rest-protocol/operation.js rename to lib/transport/rest/protocol/operation.js diff --git a/lib/rest-protocol/operations/connect.js b/lib/transport/rest/protocol/operations/connect.js similarity index 100% rename from lib/rest-protocol/operations/connect.js rename to lib/transport/rest/protocol/operations/connect.js From 3d14c16540c8aef60e24d374a03664dd30185637 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Apr 2014 15:44:41 +0100 Subject: [PATCH 011/308] add REST_SERVER, CREATE_REST_DB, DELETE_REST_DB for testing rest api --- test/index.js | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/test/index.js b/test/index.js index 7e5c741..628397d 100644 --- a/test/index.js +++ b/test/index.js @@ -11,17 +11,40 @@ global.TEST_SERVER_CONFIG = require('./test-server.json'); global.LIB = require('../lib'); -global.TEST_SERVER = new LIB.Server(TEST_SERVER_CONFIG); +global.TEST_SERVER = new LIB.Server({ + host: TEST_SERVER_CONFIG.host, + port: TEST_SERVER_CONFIG.port, + username: TEST_SERVER_CONFIG.username, + password: TEST_SERVER_CONFIG.password, + transport: 'binary' +}); -// Uncomment the following line to enable debug logging +global.REST_SERVER = new LIB.Server({ + host: TEST_SERVER_CONFIG.host, + port: TEST_SERVER_CONFIG.port, + username: TEST_SERVER_CONFIG.username, + password: TEST_SERVER_CONFIG.password, + transport: 'binary' +}); + +// Uncomment the following lines to enable debug logging // global.TEST_SERVER.logger.debug = console.log.bind(console, '[ORIENTDB]'); +// global.REST_SERVER.logger.debug = console.log.bind(console, '[ORIENTDB]'); + + +global.CREATE_TEST_DB = createTestDb.bind(null, TEST_SERVER); +global.DELETE_TEST_DB = deleteTestDb.bind(null, TEST_SERVER); + +global.CREATE_REST_DB = createTestDb.bind(null, REST_SERVER); +global.DELETE_REST_DB = deleteTestDb.bind(null, REST_SERVER); + -global.CREATE_TEST_DB = function (context, name) { - return TEST_SERVER.exists(name, 'memory') +function createTestDb(server, context, name) { + return server.exists(name, 'memory') .then(function (exists) { if (exists) { - return TEST_SERVER.delete({ + return server.delete({ name: name, storage: 'memory' }); @@ -31,7 +54,7 @@ global.CREATE_TEST_DB = function (context, name) { } }) .then(function () { - return TEST_SERVER.create({ + return server.create({ name: name, type: 'graph', storage: 'memory' @@ -41,13 +64,13 @@ global.CREATE_TEST_DB = function (context, name) { context.db = db; return undefined; }); -}; +} -global.DELETE_TEST_DB = function (name) { - return TEST_SERVER.exists(name, 'memory') +function deleteTestDb (server, name) { + return server.exists(name, 'memory') .then(function (exists) { if (exists) { - return TEST_SERVER.delete({ + return server.delete({ name: name, storage: 'memory' }); @@ -59,4 +82,4 @@ global.DELETE_TEST_DB = function (name) { .then(function () { return undefined; }); -}; \ No newline at end of file +} \ No newline at end of file From 6de6976e31ebb5645ae9b7421bfa84d8a764690c Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Apr 2014 15:46:46 +0100 Subject: [PATCH 012/308] add config key for selecting the correct transport --- lib/server/index.js | 11 +++++++---- lib/transport/rest/index.js | 0 lib/transport/rest/protocol/operation.js | 10 +++------- 3 files changed, 10 insertions(+), 11 deletions(-) create mode 100644 lib/transport/rest/index.js diff --git a/lib/server/index.js b/lib/server/index.js index 8a4ac86..01183af 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -1,6 +1,7 @@ var ConnectionPool = require('../transport/binary/connection-pool'), Connection = require('../transport/binary/connection'), BinaryTransport = require('../transport/binary'), + RestTransport = require('../transport/rest'), utils = require('../utils'), errors = require('../errors'), Db = require('../db/index'), @@ -31,8 +32,6 @@ Server.prototype.augment = utils.augment; module.exports = Server; - - /** * Configure the server instance. * @@ -40,7 +39,6 @@ module.exports = Server; * @return {Server} The configured server object. */ Server.prototype.configure = function (config) { - this.configureLogger(config.logger || {}); this.configureTransport(config); }; @@ -52,7 +50,12 @@ Server.prototype.configure = function (config) { * @return {Server} The configured server object. */ Server.prototype.configureTransport = function (config) { - this.transport = new BinaryTransport(config); + if (config.transport === 'rest') { + this.transport = new RestTransport(config); + } + else { + this.transport = new BinaryTransport(config); + } return this; }; diff --git a/lib/transport/rest/index.js b/lib/transport/rest/index.js new file mode 100644 index 0000000..e69de29 diff --git a/lib/transport/rest/protocol/operation.js b/lib/transport/rest/protocol/operation.js index 22e4f1c..ede9dca 100644 --- a/lib/transport/rest/protocol/operation.js +++ b/lib/transport/rest/protocol/operation.js @@ -1,13 +1,9 @@ -var utils = require('../utils'); - -function Operation (data) { - this.data = data || {}; -} +var utils = require('../../../utils'); /** - * # Operations + * # REST Operations * - * The base class for operations, provides a simple DSL for defining + * The base class for REST operations, provides a simple DSL for defining * the steps required to send a command to the server, and then read * the response. * From 1b903891383e5b4b162b9718bf0f4ebb38c1d0f5 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Apr 2014 20:51:19 +0100 Subject: [PATCH 013/308] small bug fix in binary transport --- lib/transport/binary/index.js | 4 ++-- .../rest/protocol/operations/{connect.js => db-open.js} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename lib/transport/rest/protocol/operations/{connect.js => db-open.js} (100%) diff --git a/lib/transport/binary/index.js b/lib/transport/binary/index.js index 7fde59f..f8de910 100644 --- a/lib/transport/binary/index.js +++ b/lib/transport/binary/index.js @@ -147,7 +147,7 @@ BinaryTransport.prototype.connect = function () { */ BinaryTransport.prototype.send = function (operation, options) { options = options || {}; - if (~this.sessionId) { + if (~this.sessionId || options.sessionId != null) { options.sessionId = options.sessionId != null ? options.sessionId : this.sessionId; return (this.pool || this.connection).send(operation, options); } @@ -172,4 +172,4 @@ BinaryTransport.prototype.close = function () { this.sessionId = -1; } return this; -} \ No newline at end of file +}; \ No newline at end of file diff --git a/lib/transport/rest/protocol/operations/connect.js b/lib/transport/rest/protocol/operations/db-open.js similarity index 100% rename from lib/transport/rest/protocol/operations/connect.js rename to lib/transport/rest/protocol/operations/db-open.js From f9fc97b04a2f0e8dab4d53c0e8222c3a3d4732ed Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Apr 2014 20:51:51 +0100 Subject: [PATCH 014/308] export the rest transport too --- lib/transport/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/transport/index.js b/lib/transport/index.js index 46bf936..f448f84 100644 --- a/lib/transport/index.js +++ b/lib/transport/index.js @@ -1 +1,2 @@ -exports.Binary = exports.BinaryTransport = require('./binary'); \ No newline at end of file +exports.Binary = exports.BinaryTransport = require('./binary'); +exports.Rest = exports.RestTransport = require('./rest'); \ No newline at end of file From 9f0d98f8dbc7d499c5c4ded687c94fddc2d6cb3d Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Apr 2014 23:17:38 +0100 Subject: [PATCH 015/308] add rest transport structure --- lib/transport/rest/index.js | 153 ++++++++++++++++++ lib/transport/rest/protocol/index.js | 2 + lib/transport/rest/protocol/operation.js | 1 + .../rest/protocol/operations/index.js | 2 + test/index.js | 4 +- test/test-server.json | 1 + 6 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 lib/transport/rest/protocol/index.js create mode 100644 lib/transport/rest/protocol/operations/index.js diff --git a/lib/transport/rest/index.js b/lib/transport/rest/index.js index e69de29..c7abc24 100644 --- a/lib/transport/rest/index.js +++ b/lib/transport/rest/index.js @@ -0,0 +1,153 @@ +var utils = require('../../utils'), + errors = require('../../errors'), + Db = require('../../db/index'), + Promise = require('bluebird'), + request = require('request'), + requestAsync = Promise.promisify(request), + util = require('util'), + operations = require('./protocol/operations'), + EventEmitter = require('events').EventEmitter, + npmPackage = require('../../../package.json');; + + +/** + * # Binary Transport + * + * @param {Object} config The configuration for the transport. + */ +function RestTransport (config) { + EventEmitter.call(this); + this.setMaxListeners(Infinity); + this.configure(config || {}); +} + +util.inherits(RestTransport, EventEmitter); + +RestTransport.extend = utils.extend; +RestTransport.prototype.augment = utils.augment; + +RestTransport.protocol = require('./protocol'); + + +module.exports = RestTransport; + + +/** + * Configure the transport. + * + * @param {Object} config The transport configuration. + */ +RestTransport.prototype.configure = function (config) { + this.connecting = false; + this.closing = false; + + this.host = config.host || config.hostname || 'localhost'; + this.port = config.port || 2424; + this.username = config.username || 'root'; + this.password = config.password || ''; + + this.sessionId = -1; + this.configureLogger(config.logger || {}); +}; + +/** + * Configure the logger for the transport. + * + * @param {Object} config The logger config + * @return {RestTransport} The transport instance with the configured logger. + */ +RestTransport.prototype.configureLogger = function (config) { + this.logger = { + error: config.error || console.error.bind(console), + log: config.log || console.log.bind(console), + debug: config.debug || function () {} // do not log debug by default + }; + return this; +}; + + +/** + * Send an operation to the server, + * + * @param {Integer} operation The id of the operation to send. + * @param {Object} options The options for the operation. + * @promise {Mixed} The result of the operation. + */ +RestTransport.prototype.send = function (operation, options) { + options = options || {}; + return this.process(operation, options); +}; + + +/** + * Close the connection to the server. + * + * @return {Server} the disconnected server instance + */ +RestTransport.prototype.close = function () { + if (!this.closing && this.socket) { + this.closing = false; + this.sessionId = -1; + } + return this; +}; + + +RestTransport.prototype.process = function (op, params) { + if (typeof op === 'string') { + op = new operations[op](params || {}); + } + + var prepared = this.prepareRequest(op); + + return requestAsync(prepared) + .spread(this.handleResponse.bind(this, op, prepared)) + .bind(op) + .then(op.processResponse); +}; + +RestTransport.prototype.prepareRequest = function (op) { + var config = op.requestConfig(); + config.url = 'http://' + this.host + ':' + this.port + (config.url || '/'); + config.headers = config.headers || {}; + config.headers['User-Agent'] = npmPackage.name + ' v' + npmPackage.version; + if (!op.jar && !this.jar) { + config.jar = this.jar = request.jar(); + } + else { + config.jar = op.jar || this.jar; + } + + if (!config.jar.getCookieString('OSESSIONID')) { + this.applyAuth(config); + } + + return config; +}; + +RestTransport.prototype.applyAuth = function (config) { + config.auth = { + username: this.username, + password: this.password + }; + return config; +} + +RestTransport.prototype.handleResponse = function (op, prepared, response) { + if (response.statusCode === 401) { + console.log('auth err') + return requestAsync(this.applyAuth(prepared)) + .bind(this) + .spread(function (response) { + if (response.statusCode > 399) { + return Promise.reject(new errors.Request('Authorization Error')); + } + else { + return JSON.parse(response.body); + } + }) + } + else { + return JSON.parse(response.body); + } +} \ No newline at end of file diff --git a/lib/transport/rest/protocol/index.js b/lib/transport/rest/protocol/index.js new file mode 100644 index 0000000..dc9bc0d --- /dev/null +++ b/lib/transport/rest/protocol/index.js @@ -0,0 +1,2 @@ +exports.operations = require('./operations'); +exports.Operation = require('./operation'); \ No newline at end of file diff --git a/lib/transport/rest/protocol/operation.js b/lib/transport/rest/protocol/operation.js index ede9dca..3808f83 100644 --- a/lib/transport/rest/protocol/operation.js +++ b/lib/transport/rest/protocol/operation.js @@ -33,3 +33,4 @@ Operation.PUSH_DATA = 5; // make it easy to inherit from the base class Operation.extend = utils.extend; + diff --git a/lib/transport/rest/protocol/operations/index.js b/lib/transport/rest/protocol/operations/index.js new file mode 100644 index 0000000..ed8634f --- /dev/null +++ b/lib/transport/rest/protocol/operations/index.js @@ -0,0 +1,2 @@ +exports['db-open'] = require('./db-open'); +exports['record-load'] = require('./record-load'); \ No newline at end of file diff --git a/test/index.js b/test/index.js index 628397d..088c5d5 100644 --- a/test/index.js +++ b/test/index.js @@ -21,10 +21,10 @@ global.TEST_SERVER = new LIB.Server({ global.REST_SERVER = new LIB.Server({ host: TEST_SERVER_CONFIG.host, - port: TEST_SERVER_CONFIG.port, + port: TEST_SERVER_CONFIG.httpPort, username: TEST_SERVER_CONFIG.username, password: TEST_SERVER_CONFIG.password, - transport: 'binary' + transport: 'rest' }); // Uncomment the following lines to enable debug logging diff --git a/test/test-server.json b/test/test-server.json index e88d2c2..a2dcfb1 100644 --- a/test/test-server.json +++ b/test/test-server.json @@ -1,6 +1,7 @@ { "host": "localhost", "port": 2424, + "httpPort": 2480, "username": "root", "password": "3BA5DB89CC6206DBF835B36B70FF8A0EDCEFA617A229F0D44D1D726ABA04216A" } \ No newline at end of file From 919ca39473d51b73d9233c4d17c030ac5ea62fd8 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Apr 2014 23:26:21 +0100 Subject: [PATCH 016/308] add db-open and record-load rest ops --- .../rest/protocol/operations/db-open.js | 41 ++++++++++--------- .../rest/protocol/operations/record-load.js | 20 +++++++++ 2 files changed, 41 insertions(+), 20 deletions(-) create mode 100644 lib/transport/rest/protocol/operations/record-load.js diff --git a/lib/transport/rest/protocol/operations/db-open.js b/lib/transport/rest/protocol/operations/db-open.js index 715c9d3..712b091 100644 --- a/lib/transport/rest/protocol/operations/db-open.js +++ b/lib/transport/rest/protocol/operations/db-open.js @@ -1,28 +1,29 @@ var Operation = require('../operation'), - constants = require('../constants'), - npmPackage = require('../../../package.json'); + npmPackage = require('../../../../../package.json'); module.exports = Operation.extend({ - id: 'REQUEST_CONNECT', + id: 'REQUEST_DB_OPEN', opCode: 2, - writer: function () { + requestConfig: function () { return { - + method: 'GET', + url: '/database/' + this.data.name }; - - this - .writeByte(this.opCode) - .writeInt(this.data.sessionId || -1) - .writeString(npmPackage.name) - .writeString(npmPackage.version) - .writeShort(+constants.PROTOCOL_VERSION) - .writeString('') // client id - .writeString(this.data.username) - .writeString(this.data.password); }, - reader: function () { - this - .readStatus('status') - .readInt('sessionId'); + processResponse: function (response) { + return { + status: { + code: 0, + sessionId: -1 + }, + sessionId: -1, + totalClusters: response.clusters.length, + clusters: response.clusters, + serverCluster: {}, + release: response.server.version + ' (build ' + response.server.build + ')', + classes: response.classes, + indexes: response.indexes, + config: response.config + }; } -}); \ No newline at end of file +}); diff --git a/lib/transport/rest/protocol/operations/record-load.js b/lib/transport/rest/protocol/operations/record-load.js new file mode 100644 index 0000000..e8122ab --- /dev/null +++ b/lib/transport/rest/protocol/operations/record-load.js @@ -0,0 +1,20 @@ +var Operation = require('../operation'), + npmPackage = require('../../../../../package.json'); + +module.exports = Operation.extend({ + id: 'REQUEST_RECORD_LOAD', + opCode: 30, + requestConfig: function () { + var url = '/document/' + this.data.database + '/' + this.data.cluster + ':' + this.data.position; + if (this.data.fetchPlan) { + url += '/' + this.data.fetchPlan; + } + return { + method: 'GET', + url: url + }; + }, + processResponse: function (response) { + return response; + } +}); From a4743b11094df90bd07b2b8f050d071e2fe57fc3 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Apr 2014 23:31:07 +0100 Subject: [PATCH 017/308] move long to lib root --- lib/{transport/binary/protocol => }/long.js | 0 lib/transport/binary/protocol/deserializer.js | 2 +- lib/transport/binary/protocol/operation.js | 2 +- lib/transport/binary/protocol/writer.js | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename lib/{transport/binary/protocol => }/long.js (100%) diff --git a/lib/transport/binary/protocol/long.js b/lib/long.js similarity index 100% rename from lib/transport/binary/protocol/long.js rename to lib/long.js diff --git a/lib/transport/binary/protocol/deserializer.js b/lib/transport/binary/protocol/deserializer.js index 6ce1d20..8b0fe66 100644 --- a/lib/transport/binary/protocol/deserializer.js +++ b/lib/transport/binary/protocol/deserializer.js @@ -1,5 +1,5 @@ var RecordID = require('../../../recordid'), - Long = require('./long').Long + Long = require('../../../long').Long /** diff --git a/lib/transport/binary/protocol/operation.js b/lib/transport/binary/protocol/operation.js index 90348a5..15d3e00 100644 --- a/lib/transport/binary/protocol/operation.js +++ b/lib/transport/binary/protocol/operation.js @@ -1,6 +1,6 @@ var constants = require('./constants'), utils = require('../../../utils'), - Long = require('./long').Long, + Long = require('../../../long').Long, errors = require('../../../errors'), deserializer = require('./deserializer'); diff --git a/lib/transport/binary/protocol/writer.js b/lib/transport/binary/protocol/writer.js index 40c57ef..2a3c829 100644 --- a/lib/transport/binary/protocol/writer.js +++ b/lib/transport/binary/protocol/writer.js @@ -1,4 +1,4 @@ -var Long = require('./long').Long, +var Long = require('../../../long').Long, constants = require('./constants'); /** From 82b9c797c06b7d6c0c679957ee37831daf94395e Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 00:00:15 +0100 Subject: [PATCH 018/308] add REST deserializer --- lib/transport/rest/index.js | 5 +- lib/transport/rest/protocol/deserializer.js | 61 +++++++++++++++++++++ lib/transport/rest/protocol/index.js | 1 + 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 lib/transport/rest/protocol/deserializer.js diff --git a/lib/transport/rest/index.js b/lib/transport/rest/index.js index c7abc24..cec9462 100644 --- a/lib/transport/rest/index.js +++ b/lib/transport/rest/index.js @@ -5,6 +5,7 @@ var utils = require('../../utils'), request = require('request'), requestAsync = Promise.promisify(request), util = require('util'), + deserializer = require('./protocol/deserializer'), operations = require('./protocol/operations'), EventEmitter = require('events').EventEmitter, npmPackage = require('../../../package.json');; @@ -143,11 +144,11 @@ RestTransport.prototype.handleResponse = function (op, prepared, response) { return Promise.reject(new errors.Request('Authorization Error')); } else { - return JSON.parse(response.body); + return deserializer.deserializeDocument(response.body); } }) } else { - return JSON.parse(response.body); + return deserializer.deserializeDocument(response.body); } } \ No newline at end of file diff --git a/lib/transport/rest/protocol/deserializer.js b/lib/transport/rest/protocol/deserializer.js new file mode 100644 index 0000000..3f0dd37 --- /dev/null +++ b/lib/transport/rest/protocol/deserializer.js @@ -0,0 +1,61 @@ +var RecordID = require('../../../recordid'), + Long = require('../../../long').Long + + +/** + * Deserialize a given serialized document. + * + * @param {String} serialized The serialized document. + * @return {Object} The deserialized document + */ +function deserializeDocument (serialized) { + return JSON.parse(serialized, function (key, value) { + if (key === '@rid') { + return new RecordID(value); + } + else if (key === '@fieldTypes') { + applyFieldTypes(this); + } + else { + return value; + } + }); +} + +function applyFieldTypes (subject) { + var types = subject['@fieldTypes'].split(','), + total = types.length, + i, parts, fieldName, type; + for (i = 0; i < total; i++) { + parts = types[i].split('='); + fieldName = parts[0]; + type = parts[1]; + subject[fieldName] = applyFieldType(type, subject[fieldName]); + } + return subject; +} + +function applyFieldType (type, value) { + switch (type) { + case 'f': // float + return parseFloat(value); + case 'l': // long + return Long.fromString(value); + case 's': //short + return +value; + case 'a': // date + case 't': // datetime + return new Date(value); + case 'e': // set + case 'c': // decimal + case 'd': // double + case 'b': // byte + return value; + default: + return value; + } +} + +// export the public methods + +exports.deserializeDocument = deserializeDocument; \ No newline at end of file diff --git a/lib/transport/rest/protocol/index.js b/lib/transport/rest/protocol/index.js index dc9bc0d..a670577 100644 --- a/lib/transport/rest/protocol/index.js +++ b/lib/transport/rest/protocol/index.js @@ -1,2 +1,3 @@ exports.operations = require('./operations'); +exports.deserializer = require('./deserializer'); exports.Operation = require('./operation'); \ No newline at end of file From a83e45bd692c21e90f18181a0fc381a458c8c157 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 00:00:48 +0100 Subject: [PATCH 019/308] normalize record load response --- lib/transport/rest/protocol/operations/record-load.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/transport/rest/protocol/operations/record-load.js b/lib/transport/rest/protocol/operations/record-load.js index e8122ab..020b886 100644 --- a/lib/transport/rest/protocol/operations/record-load.js +++ b/lib/transport/rest/protocol/operations/record-load.js @@ -15,6 +15,10 @@ module.exports = Operation.extend({ }; }, processResponse: function (response) { + return { + status: {code: 0, sessionId: -1}, + records: [response] + }; return response; } }); From 223613075ff98077f843688a9d9dcb503d2e0428 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 00:16:59 +0100 Subject: [PATCH 020/308] remove duplicate return --- lib/transport/rest/protocol/operations/record-load.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/transport/rest/protocol/operations/record-load.js b/lib/transport/rest/protocol/operations/record-load.js index 020b886..94d6d47 100644 --- a/lib/transport/rest/protocol/operations/record-load.js +++ b/lib/transport/rest/protocol/operations/record-load.js @@ -19,6 +19,5 @@ module.exports = Operation.extend({ status: {code: 0, sessionId: -1}, records: [response] }; - return response; } }); From 93d7b8d441c95779e459e9f64ecd1344c71bf0ae Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 01:31:13 +0100 Subject: [PATCH 021/308] deserializer fixes, use try --- lib/transport/rest/protocol/deserializer.js | 30 ++++++++++++--------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/transport/rest/protocol/deserializer.js b/lib/transport/rest/protocol/deserializer.js index 3f0dd37..e9e8b4d 100644 --- a/lib/transport/rest/protocol/deserializer.js +++ b/lib/transport/rest/protocol/deserializer.js @@ -1,5 +1,6 @@ var RecordID = require('../../../recordid'), - Long = require('../../../long').Long + errors = require('../../../errors'), + Long = require('../../../long').Long; /** @@ -9,17 +10,22 @@ var RecordID = require('../../../recordid'), * @return {Object} The deserialized document */ function deserializeDocument (serialized) { - return JSON.parse(serialized, function (key, value) { - if (key === '@rid') { - return new RecordID(value); - } - else if (key === '@fieldTypes') { - applyFieldTypes(this); - } - else { - return value; - } - }); + try { + return JSON.parse(serialized, function (key, value) { + if (key === '@rid') { + return new RecordID(value); + } + else if (key === '@fieldTypes') { + applyFieldTypes(this); + } + else { + return value; + } + }); + } + catch (e) { + throw new errors.Request(serialized); + } } function applyFieldTypes (subject) { From ccd6efa60513bd962c0f46cf105c0663c5c27772 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 01:32:15 +0100 Subject: [PATCH 022/308] add query prepare() and related functions --- lib/utils.js | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index cc745ea..1ceed64 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,3 +1,5 @@ +var RID = require('./recordid'); + /** * Make it easy to extend classes. * @@ -102,4 +104,64 @@ exports.clone = function (item) { cloned[key] = item[key]; } return cloned; -} \ No newline at end of file +}; + +/** + * Escape the given input for use in a query. + * + * @param {String} input The input to escape. + * @return {String} The escaped input. + */ +exports.escape = function (input) { + return ('' + input).replace(/([^'\\]*(?:\\.[^'\\]*)*)'/g, '$1\\"'); +}; + +/** + * Prepare a query. + * + * @param {String} query The query to prepare. + * @param {Object} params The bound parameters for the query. + * @return {String} The prepared query. + */ +exports.prepare = function (query, params) { + if (!params) return query; + var pattern = /"(\\[\s\S]|[^"])*"|'(\\[\s\S]|[^'])*'|:([A-Za-z_-]+|\/\*[\s\S]*?\*\/)/; + return query.replace(pattern, function (all, double, single, param) { + if (param) { + return exports.encode(params[param]); + } + else { + return all; + } + }) +}; + +/** + * Encode a value for use in a query, escaping and quoting it if required. + * + * @param {Mixed} value The value to encode. + * @return {Mixed} The encoded value. + */ +exports.encode = function (value) { + if (value == null) { + return 'null' + } + else if (typeof value === 'number') { + return value; + } + else if (typeof value === 'boolean') { + return value; + } + else if (typeof value === 'string') { + return '"' + exports.escape(value) + '"'; + } + else if (value instanceof RID) { + return value.toString(); + } + else if (Array.isArray(value)) { + return '[' + value.map(exports.encode) + ']'; + } + else { + return JSON.stringify(value); + } +}; \ No newline at end of file From 6813ceff9576b82cb1c5c112ab97ba3c65c25222 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 01:39:35 +0100 Subject: [PATCH 023/308] include the database name in operation options for rest api compat --- lib/db/index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index 6853be1..a6ea814 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -105,6 +105,7 @@ Db.prototype.send = function (operation, data) { .then(function () { data = data || {}; data.sessionId = this.sessionId; + data.database = this.name; this.server.logger.debug('sending operation ' + operation + ' for database ' + this.name); return this.server.send(operation, data); }); @@ -345,9 +346,7 @@ Db.prototype.delete = function () { * @param {String} input The input to escape. * @return {String} The escaped input. */ -Db.prototype.escape = function (input) { - return ('' + input).replace(/([^'\\]*(?:\\.[^'\\]*)*)'/g, '$1\\"'); -}; +Db.prototype.escape = utils.escape; /** * Flatten an array of arrays From c7d1e1747d1095194f138993799132b57aeca1bd Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 01:42:43 +0100 Subject: [PATCH 024/308] add command operation --- lib/transport/rest/index.js | 1 - .../rest/protocol/operations/command.js | 29 +++++++++++++++++++ .../rest/protocol/operations/index.js | 3 +- 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 lib/transport/rest/protocol/operations/command.js diff --git a/lib/transport/rest/index.js b/lib/transport/rest/index.js index cec9462..635964d 100644 --- a/lib/transport/rest/index.js +++ b/lib/transport/rest/index.js @@ -136,7 +136,6 @@ RestTransport.prototype.applyAuth = function (config) { RestTransport.prototype.handleResponse = function (op, prepared, response) { if (response.statusCode === 401) { - console.log('auth err') return requestAsync(this.applyAuth(prepared)) .bind(this) .spread(function (response) { diff --git a/lib/transport/rest/protocol/operations/command.js b/lib/transport/rest/protocol/operations/command.js new file mode 100644 index 0000000..d17e5aa --- /dev/null +++ b/lib/transport/rest/protocol/operations/command.js @@ -0,0 +1,29 @@ +var Operation = require('../operation'), + utils = require('../../../../utils'); + +module.exports = Operation.extend({ + id: 'REQUEST_COMMAND', + opCode: 41, + requestConfig: function () { + var prepared = utils.prepare(this.data.query, this.data.params), + url = '/command/' + this.data.database + '/sql/' + prepared; + if (this.data.limit) { + url += '/' + this.data.limit; + } + return { + method: 'POST', + url: url + }; + }, + processResponse: function (response) { + return { + status: {code: 0, sessionId: -1}, + results: [ + { + type: 'l', + content: response.result + } + ] + }; + } +}); diff --git a/lib/transport/rest/protocol/operations/index.js b/lib/transport/rest/protocol/operations/index.js index ed8634f..a68150b 100644 --- a/lib/transport/rest/protocol/operations/index.js +++ b/lib/transport/rest/protocol/operations/index.js @@ -1,2 +1,3 @@ exports['db-open'] = require('./db-open'); -exports['record-load'] = require('./record-load'); \ No newline at end of file +exports['record-load'] = require('./record-load'); +exports['command'] = require('./command'); \ No newline at end of file From a7edbd52e7abd131a9685c656c3ac4a80821e818 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 01:59:50 +0100 Subject: [PATCH 025/308] add rest tests --- .../transport/rest/operations/command-test.js | 26 ++++++++ .../transport/rest/operations/db-open-test.js | 34 +++++++++++ .../rest/operations/record-load-test.js | 59 +++++++++++++++++++ test/transport/rest/rest-transport-test.js | 20 +++++++ 4 files changed, 139 insertions(+) create mode 100644 test/transport/rest/operations/command-test.js create mode 100644 test/transport/rest/operations/db-open-test.js create mode 100644 test/transport/rest/operations/record-load-test.js create mode 100644 test/transport/rest/rest-transport-test.js diff --git a/test/transport/rest/operations/command-test.js b/test/transport/rest/operations/command-test.js new file mode 100644 index 0000000..738cf14 --- /dev/null +++ b/test/transport/rest/operations/command-test.js @@ -0,0 +1,26 @@ +var Promise = require('bluebird'); + +describe('Rest Operations - Command', function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_rest_command'); + }); + after(function () { + return DELETE_TEST_DB('testdb_rest_command'); + }); + + it('should execute a query', function () { + var config = { + database: 'testdb_rest_command', + class: 'com.orientechnologies.orient.core.sql.query.OSQLSynchQuery', + limit: 2, + query: 'SELECT * FROM OUser', + mode: 's' + }; + return Promise.all([REST_SERVER.send('command', config), this.db.send('command', config)]) + .spread(function (fromRest, fromBinary) { + fromRest.results.length.should.equal(fromBinary.results.length); + fromRest.results[0].content.length.should.equal(fromBinary.results[0].content.length); + }); + }); + +}); diff --git a/test/transport/rest/operations/db-open-test.js b/test/transport/rest/operations/db-open-test.js new file mode 100644 index 0000000..2ba5e99 --- /dev/null +++ b/test/transport/rest/operations/db-open-test.js @@ -0,0 +1,34 @@ +var Promise = require('bluebird'); + +describe('Rest Operations - Db Open', function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_rest_dbopen'); + }); + after(function () { + return DELETE_TEST_DB('testdb_rest_dbopen'); + }); + + it('should open the database', function () { + var params = { + sessionId: -1, + name: 'testdb_rest_dbopen', + type: 'graph', + username: 'admin', + password: 'admin' + }; + return Promise.all([REST_SERVER.send('db-open', params), TEST_SERVER.send('db-open', params)]) + .spread(function (fromRest, fromBinary) { + fromRest.release.should.equal(fromBinary.release); + fromRest.totalClusters.should.equal(fromBinary.totalClusters); + fromRest.clusters.length.should.equal(fromBinary.clusters.length); + fromRest.clusters.map(pluck('name')).sort().should.eql(fromBinary.clusters.map(pluck('name')).sort()); + }); + }) +}); + + +function pluck (key) { + return function (item) { + return item[key]; + } +} \ No newline at end of file diff --git a/test/transport/rest/operations/record-load-test.js b/test/transport/rest/operations/record-load-test.js new file mode 100644 index 0000000..07b20dc --- /dev/null +++ b/test/transport/rest/operations/record-load-test.js @@ -0,0 +1,59 @@ +var Promise = require('bluebird'); + +describe('Rest Operations - Record Load', function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_rest_record_load'); + }); + after(function () { + return DELETE_TEST_DB('testdb_rest_record_load'); + }); + + it('should load a record', function () { + var params = { + sessionId: -1, + database: 'testdb_rest_record_load', + cluster: 5, + position: 0 + }; + return Promise.all([REST_SERVER.send('record-load', params), this.db.send('record-load', params)]) + .spread(function (fromRest, fromBinary) { + fromRest = fromRest.records[0]; + fromBinary = fromBinary.records[0]; + fromRest['@rid'].should.eql(fromBinary['@rid']); + fromRest['@version'].should.equal(fromBinary['@version']); + fromRest['@type'].should.equal(fromBinary['@type']); + fromRest.name.should.equal(fromBinary.name); + fromRest.status.should.equal(fromBinary.status); + fromRest.roles.length.should.equal(fromBinary.roles.length); + }); + }); + + it('should load a record with a fetch plan', function () { + var params = { + sessionId: -1, + database: 'testdb_rest_record_load', + cluster: 5, + position: 0, + fetchPlan: 'roles:1' + }; + return Promise.all([REST_SERVER.send('record-load', params), this.db.send('record-load', params)]) + .spread(function (fromRest, fromBinary) { + fromRest.records[0]['@rid'].should.eql(fromBinary.records[0]['@rid']); + fromRest.records[0]['@version'].should.equal(fromBinary.records[0]['@version']); + fromRest.records[0]['@type'].should.equal(fromBinary.records[0]['@type']); + fromRest.records[0].name.should.equal(fromBinary.records[0].name); + fromRest.records[0].status.should.equal(fromBinary.records[0].status); + fromRest.records[0].roles.length.should.equal(fromBinary.records[0].roles.length); + fromRest.records[0].roles[0]['@rid'].should.eql(fromBinary.records[0].roles[0]); + fromRest.records[0].roles[0]['@rid'].should.eql(fromBinary.records[1]['@rid']); + fromRest.records[0].roles[0].name.should.eql(fromBinary.records[1].name); + }); + }); +}); + + +function pluck (key) { + return function (item) { + return item[key]; + } +} \ No newline at end of file diff --git a/test/transport/rest/rest-transport-test.js b/test/transport/rest/rest-transport-test.js new file mode 100644 index 0000000..9c745d7 --- /dev/null +++ b/test/transport/rest/rest-transport-test.js @@ -0,0 +1,20 @@ +var errors = LIB.errors; +describe("Rest Transport", function () { + describe('RestTransport::send()', function () { + it("should handle errors correctly", function () { + return REST_SERVER.transport.send('db-open', { + name: 'not_an_existing_database', + type: 'graph', + username: 'admin', + password: 'admin' + }) + .then(function (response) { + throw new Error('Should Not Happen!'); + }) + .catch(errors.Request, function (e) { + e.message.should.equal('Authorization Error'); + return true; + }); + }) + }); +}); \ No newline at end of file From f71a9dc8c020fc20ea71d090286e068cb58016af Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 02:01:50 +0100 Subject: [PATCH 026/308] add an initial test for #27 --- test/bugs/27-slow.js | 87 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 test/bugs/27-slow.js diff --git a/test/bugs/27-slow.js b/test/bugs/27-slow.js new file mode 100644 index 0000000..f417290 --- /dev/null +++ b/test/bugs/27-slow.js @@ -0,0 +1,87 @@ +describe("Bug #27: Slow compared to Restful API", function () { + var LIMIT = 10000; + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_27_slow') + .bind(this) + .then(function () { + return this.db.class.create('School', 'V'); + }) + .then(function (item) { + this.class = item; + return item.property.create([ + { + name: 'name', + type: 'String', + mandator: true + }, + { + name: 'address', + type: 'String' + } + ]) + }) + .then(function () { + var rows = [], + total = LIMIT, + i, row; + for (i = 0; i < total; i++) { + row = { + name: 'School ' + i, + address: (122 + i) + ' Fake Street' + }; + rows.push(row); + } + return this.class.create(rows); + }) + .then(function (results) { + results.length.should.equal(LIMIT); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_27_slow'); + }); + + it('should load a lot of records quickly, using the binary raw command interface', function () { + var start = Date.now(); + return this.db.send('command', { + database: 'testdb_bug_27_slow', + class: 'com.orientechnologies.orient.core.sql.query.OSQLSynchQuery', + limit: 20000, + query: 'SELECT * FROM School', + mode: 's' + }) + .then(function (response) { + var stop = Date.now(); + response.results[0].content.length.should.equal(LIMIT); + console.log('Binary Protocol Took ', (stop - start) + 'ms,', Math.round((LIMIT / (stop - start)) * 1000), 'documents per second') + }) + }); + + it('should load a lot of records quickly, using the rest raw command interface', function () { + var start = Date.now(); + return REST_SERVER.send('command', { + database: 'testdb_bug_27_slow', + class: 'com.orientechnologies.orient.core.sql.query.OSQLSynchQuery', + limit: 20000, + query: 'SELECT * FROM School', + mode: 's' + }) + .then(function (response) { + var stop = Date.now(); + response.results[0].content.length.should.equal(LIMIT); + console.log('Rest Protocol Took ', (stop - start) + 'ms,', Math.round((LIMIT / (stop - start)) * 1000), 'documents per second') + }) + }); + + it('should load a lot of records quickly', function () { + var start = Date.now(); + return this.db.select().from('School').all() + .then(function (results) { + var stop = Date.now(); + results.length.should.equal(LIMIT); + console.log('Binary DB Api Took ', (stop - start) + 'ms,', Math.round((LIMIT / (stop - start)) * 1000), 'documents per second') + }) + }); + + +}); \ No newline at end of file From 0a0991e0c7a99a8d26909a2ce8a69a73e627d8f7 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 12:43:46 +0100 Subject: [PATCH 027/308] prove record load is slower via rest --- test/bugs/27-slow.js | 52 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/test/bugs/27-slow.js b/test/bugs/27-slow.js index f417290..ca8ecf2 100644 --- a/test/bugs/27-slow.js +++ b/test/bugs/27-slow.js @@ -1,5 +1,5 @@ describe("Bug #27: Slow compared to Restful API", function () { - var LIMIT = 10000; + var LIMIT = 5000; before(function () { return CREATE_TEST_DB(this, 'testdb_bug_27_slow') .bind(this) @@ -46,7 +46,7 @@ describe("Bug #27: Slow compared to Restful API", function () { return this.db.send('command', { database: 'testdb_bug_27_slow', class: 'com.orientechnologies.orient.core.sql.query.OSQLSynchQuery', - limit: 20000, + limit: LIMIT * 2, query: 'SELECT * FROM School', mode: 's' }) @@ -62,7 +62,7 @@ describe("Bug #27: Slow compared to Restful API", function () { return REST_SERVER.send('command', { database: 'testdb_bug_27_slow', class: 'com.orientechnologies.orient.core.sql.query.OSQLSynchQuery', - limit: 20000, + limit: LIMIT * 2, query: 'SELECT * FROM School', mode: 's' }) @@ -83,5 +83,51 @@ describe("Bug #27: Slow compared to Restful API", function () { }) }); + it('should load a lot of records, one at a time, using binary', function () { + var start = Date.now(); + var cluster = this.class.defaultClusterId, + promises = [], + i; + + for (i = 0; i < LIMIT; i++) { + promises.push(this.db.send('record-load', { + database: 'testdb_bug_27_slow', + cluster: cluster, + position: i + })); + } + + return Promise.all(promises) + .then(function (results) { + var stop = Date.now(); + results.length.should.equal(LIMIT); + console.log('Binary Record Load Took ', (stop - start) + 'ms,', Math.round((LIMIT / (stop - start)) * 1000), 'documents per second') + }) + }); + + it('should load a lot of records, one at a time, using rest', function () { + var start = Date.now(); + var cluster = this.class.defaultClusterId, + promises = [], + i; + + for (i = 0; i < LIMIT; i++) { + promises.push(REST_SERVER.send('record-load', { + database: 'testdb_bug_27_slow', + cluster: cluster, + position: i + })); + } + + return Promise.all(promises) + .then(function (results) { + var stop = Date.now(); + results.length.should.equal(LIMIT); + console.log('Rest Record Load Took ', (stop - start) + 'ms,', Math.round((LIMIT / (stop - start)) * 1000), 'documents per second') + }) + }); + + + }); \ No newline at end of file From d9bd6449347e03f92c9837de59f5a24d218eaf71 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 15:55:16 +0100 Subject: [PATCH 028/308] add jshint and jshint ignore --- .jshintignore | 2 ++ .jshintrc | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 .jshintignore create mode 100644 .jshintrc diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 0000000..f79d3d3 --- /dev/null +++ b/.jshintignore @@ -0,0 +1,2 @@ +lib/long.js +lib/errors/*.js \ No newline at end of file diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..0b64b82 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,79 @@ +{ + "maxerr" : 50, + + "bitwise" : false, + "camelcase" : true, + "curly" : true, + "eqeqeq" : false, + "forin" : true, + "immed" : true, + "indent" : 2, + "latedef" : false, + "newcap" : true, + "noarg" : true, + "noempty" : true, + "nonew" : true, + "plusplus" : false, + "quotmark" : false, + "undef" : true, + "unused" : false, // $scope variables are often created but not read in js files + "strict" : true, + "devel" : true, + "node" : true, + "maxlen" :120, + "nonbsp" :true, + + "trailing" : true, + "maxparams" : false, + "maxdepth" : false, + "maxstatements" : false, + "maxcomplexity" : false, + + "asi" : false, + "boss" : false, + "debug" : false, + "eqnull" : true, + "esnext" : false, + "moz" : false, + + "evil" : false, + "expr" : false, + "funcscope" : false, + "globalstrict" : true, + "iterator" : false, + "lastsemic" : false, + "laxbreak" : false, + "laxcomma" : false, + "loopfunc" : true, + "multistr" : false, + "proto" : false, + "scripturl" : false, + "smarttabs" : false, + "shadow" : false, + "sub" : false, + "supernew" : false, + "validthis" : true, + + "browser" : false, + "couch" : false, + "dojo" : false, + "jquery" : false, + "mootools" : false, + "nonstandard" : false, + "prototypejs" : false, + "rhino" : false, + "worker" : false, + "wsh" : false, + "yui" : false, + + "globals" : { + "angular" : false, + "$" : false, + "sinon" : false, + "describe" : false, + "beforeEach" : false, + "inject" : false, + "it" : false, + "expect" : false + } +} \ No newline at end of file From 62b8aaff154fe5a6a07a3a382282149e3c4a102f Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 15:56:22 +0100 Subject: [PATCH 029/308] js lint fixes --- lib/cli/command.js | 11 ++- lib/cli/commands/db.js | 2 + lib/cli/commands/migrate.js | 10 ++- lib/cli/index.js | 2 + lib/db/class/custom.js | 6 +- lib/db/class/index.js | 18 ++-- lib/db/class/property.js | 26 ++++-- lib/db/cluster.js | 11 ++- lib/db/edge/index.js | 12 ++- lib/db/index.js | 35 +++++--- lib/db/index/index.js | 29 +++++-- lib/db/query.js | 6 +- lib/db/record.js | 43 +++++++--- lib/db/statement.js | 46 ++++++---- lib/db/vertex/index.js | 13 ++- lib/long.js | 4 +- lib/migration/index.js | 6 +- lib/migration/manager.js | 25 ++++-- lib/recordid.js | 39 ++++++--- lib/server/config.js | 2 + lib/server/index.js | 15 +++- lib/transport/binary/connection-pool.js | 7 +- lib/transport/binary/connection.js | 21 +++-- lib/transport/binary/index.js | 5 +- lib/transport/binary/protocol/constants.js | 2 + lib/transport/binary/protocol/deserializer.js | 18 ++-- lib/transport/binary/protocol/index.js | 1 + .../binary/protocol/operation-queue.js | 20 +++-- lib/transport/binary/protocol/operation.js | 85 +++++++++++++------ .../binary/protocol/operations/command.js | 5 +- .../binary/protocol/operations/config-get.js | 2 + .../binary/protocol/operations/config-list.js | 2 + .../binary/protocol/operations/config-set.js | 2 + .../binary/protocol/operations/connect.js | 2 + .../protocol/operations/datacluster-add.js | 2 + .../protocol/operations/datacluster-count.js | 2 + .../operations/datacluster-datarange.js | 2 + .../protocol/operations/datacluster-drop.js | 2 + .../protocol/operations/datasegment-add.js | 4 +- .../protocol/operations/datasegment-drop.js | 2 + .../binary/protocol/operations/db-close.js | 7 +- .../protocol/operations/db-countrecords.js | 4 +- .../binary/protocol/operations/db-create.js | 2 + .../binary/protocol/operations/db-delete.js | 2 + .../binary/protocol/operations/db-exists.js | 2 + .../binary/protocol/operations/db-list.js | 6 +- .../binary/protocol/operations/db-open.js | 1 + .../binary/protocol/operations/db-reload.js | 4 +- .../binary/protocol/operations/db-size.js | 4 +- .../binary/protocol/operations/index.js | 2 + .../protocol/operations/record-clean-out.js | 4 +- .../protocol/operations/record-create.js | 2 + .../protocol/operations/record-delete.js | 2 + .../binary/protocol/operations/record-load.js | 7 +- .../protocol/operations/record-metadata.js | 2 + .../protocol/operations/record-update.js | 2 + lib/transport/binary/protocol/serializer.js | 20 +++-- lib/transport/binary/protocol/writer.js | 2 + lib/transport/index.js | 2 + lib/transport/rest/index.js | 10 ++- lib/transport/rest/protocol/deserializer.js | 2 + lib/transport/rest/protocol/index.js | 2 + lib/transport/rest/protocol/operation.js | 2 + .../rest/protocol/operations/command.js | 2 + .../rest/protocol/operations/db-open.js | 2 + .../rest/protocol/operations/index.js | 2 + .../rest/protocol/operations/record-load.js | 2 + lib/utils.js | 34 +++++--- 68 files changed, 486 insertions(+), 196 deletions(-) diff --git a/lib/cli/command.js b/lib/cli/command.js index 286a00e..59caa9a 100644 --- a/lib/cli/command.js +++ b/lib/cli/command.js @@ -1,3 +1,5 @@ +"use strict"; + var utils = require('../utils'); /** @@ -18,8 +20,9 @@ function Command (server, options) { return this._db; }, set: function (val) { - if (typeof val === 'string') + if (typeof val === 'string') { val = this.server.use(val); + } this._db = val; } }); @@ -38,10 +41,12 @@ Command.prototype.requiredArgv = ['password']; */ Command.prototype.run = function () { var subcommand = this.options._[1]; - if (!subcommand || subcommand === 'run' || typeof this[subcommand] !== 'function') + if (!subcommand || subcommand === 'run' || typeof this[subcommand] !== 'function') { return this.help(); - else + } + else { return this[subcommand].apply(this, this.options._.slice(2)); + } }; /** diff --git a/lib/cli/commands/db.js b/lib/cli/commands/db.js index 1a7ba83..da1fca6 100644 --- a/lib/cli/commands/db.js +++ b/lib/cli/commands/db.js @@ -1,3 +1,5 @@ +"use strict"; + /** * Create a database. * diff --git a/lib/cli/commands/migrate.js b/lib/cli/commands/migrate.js index 0cc0a53..22f11a5 100644 --- a/lib/cli/commands/migrate.js +++ b/lib/cli/commands/migrate.js @@ -1,3 +1,5 @@ +"use strict"; + var path = require('path'), MigrationManager = require('../../migration/manager'); @@ -52,7 +54,9 @@ exports.applied = function () { */ exports.create = function () { var name = Array.prototype.join.call(arguments, ' '); - if (!name) throw new Error('Name is required'); + if (!name) { + throw new Error('Name is required'); + } console.log('Creating a new migration called: ' + name); return this.manager().create(name) .then(function (filename) { @@ -76,7 +80,7 @@ exports.up = function (limit) { results.forEach(function (item) { console.log('\t\t' + item); }); - }) + }); }; @@ -96,5 +100,5 @@ exports.down = function (limit) { results.forEach(function (item) { console.log('\t\t' + item); }); - }) + }); }; \ No newline at end of file diff --git a/lib/cli/index.js b/lib/cli/index.js index 7145f5d..8dd250a 100644 --- a/lib/cli/index.js +++ b/lib/cli/index.js @@ -1,3 +1,5 @@ +"use strict"; + var Promise = require('bluebird'), fs = Promise.promisifyAll(require('fs')), path = require('path'), diff --git a/lib/db/class/custom.js b/lib/db/class/custom.js index ef55fb9..22e53ea 100644 --- a/lib/db/class/custom.js +++ b/lib/db/class/custom.js @@ -1,3 +1,5 @@ +"use strict"; + var Promise = require('bluebird'), Class = require('./index'); @@ -52,7 +54,9 @@ exports.set = function (key, value) { statements.push(this.db.query('ALTER CLASS ' + this.name + ' CUSTOM ' + key + '=' + obj[key])); } else { - statements.push(this.class.db.query('ALTER PROPERTY ' + this.class.name + '.' + this.name + ' CUSTOM ' + key + '=' + obj[key])); + statements.push(this.class.db.query( + 'ALTER PROPERTY ' + this.class.name + '.' + this.name + ' CUSTOM ' + key + '=' + obj[key] + )); } } return Promise.all(statements) diff --git a/lib/db/class/index.js b/lib/db/class/index.js index b5e9269..224854a 100644 --- a/lib/db/class/index.js +++ b/lib/db/class/index.js @@ -1,3 +1,5 @@ +"use strict"; + var Promise = require('bluebird'), Property = require('./property'), RID = require('../../recordid'), @@ -10,7 +12,9 @@ var Promise = require('bluebird'), */ function Class (config) { config = config || {}; - if (!(this instanceof Class)) return new Class(config); + if (!(this instanceof Class)) { + return new Class(config); + } this.augment('property', Property); this.augment('custom', require('./custom')); this.configure(config); @@ -170,8 +174,9 @@ exports.cached = false; * @promise {Object[]} An array of class objects. */ exports.list = function (refresh) { - if (!refresh && this.class.cached) + if (!refresh && this.class.cached) { return Promise.resolve(this.class.cached.items); + } return this.send('record-load', { cluster: 0, @@ -180,10 +185,12 @@ exports.list = function (refresh) { .bind(this) .then(function (response) { var record = response.records[0]; - if (!record || !record.classes) + if (!record || !record.classes) { return []; - else + } + else { return record.classes; + } }) .then(this.class.cacheData) .then(function () { @@ -257,8 +264,9 @@ exports.get = function (name, refresh) { return this.class.cached.names[name] || Promise.reject(new errors.Request('No such class: ' + name)); }); } - else + else { return Promise.reject(new errors.Request('No such class: ' + name)); + } }; /** diff --git a/lib/db/class/property.js b/lib/db/class/property.js index 52d51e0..8e38a0a 100644 --- a/lib/db/class/property.js +++ b/lib/db/class/property.js @@ -1,3 +1,5 @@ +"use strict"; + var Promise = require('bluebird'), utils = require('../../utils'), errors = require('../../errors'); @@ -8,7 +10,9 @@ var Promise = require('bluebird'), */ function Property (config) { config = config || {}; - if (!(this instanceof Property)) return new Property(config); + if (!(this instanceof Property)) { + return new Property(config); + } this.augment('custom', require('./custom')); this.configure(config); } @@ -92,7 +96,9 @@ Property.list = function () { * @promise {Object} The created property. */ Property.create = function (config, reload) { - if (reload == null) reload = true; + if (reload == null) { + reload = true; + } if (Array.isArray(config)) { return Promise.all(config.map(function (item) { @@ -100,10 +106,12 @@ Property.create = function (config, reload) { }, this)) .bind(this) .then(function (results) { - if (reload) + if (reload) { return this.reload(); - else + } + else { return this; + } }) .then(function () { var total = config.length, @@ -164,8 +172,9 @@ Property.get = function (name) { i, item; for (i = 0; i < total; i++) { item = this.properties[i]; - if (item.name === name) + if (item.name === name) { return Promise.resolve(item); + } } return Promise.resolve(null); @@ -183,7 +192,9 @@ Property.update = function (property, reload) { prefix = 'ALTER PROPERTY ' + this.name + '.' + property.name + ' ', keys, total, key, i; - if (reload == null) reload = true; + if (reload == null) { + reload = true; + } if (property.linkedClass !== undefined) { promises.push(this.db.exec(prefix + 'LINKEDCLASS ' + property.linkedClass)); @@ -222,8 +233,9 @@ Property.update = function (property, reload) { return Promise.all(promises) .bind(this) .then(function () { - if (reload) + if (reload) { return this.reload(); + } }) .then(function () { return this.property.get(property.name); diff --git a/lib/db/cluster.js b/lib/db/cluster.js index 51a2fb3..a95cf87 100644 --- a/lib/db/cluster.js +++ b/lib/db/cluster.js @@ -1,3 +1,5 @@ +"use strict"; + var Promise = require('bluebird'), errors = require('../errors'); @@ -14,8 +16,9 @@ exports.cached = false; * @promise {Object[]} An array of cluster objects. */ exports.list = function (refresh) { - if (!refresh && this.cluster.cached) + if (!refresh && this.cluster.cached) { return Promise.resolve(this.cluster.cached.items); + } if (this.sessionId) { // db is already open, reload @@ -92,8 +95,9 @@ exports.getByName = function (name, refresh) { return this.cluster.cached.names[name]; }); } - else + else { return Promise.resolve(undefined); + } }; /** @@ -114,8 +118,9 @@ exports.getById = function (id, refresh) { return this.cluster.cached.ids[id]; }); } - else + else { return Promise.resolve(undefined); + } }; /** diff --git a/lib/db/edge/index.js b/lib/db/edge/index.js index 2e0132d..fb792da 100644 --- a/lib/db/edge/index.js +++ b/lib/db/edge/index.js @@ -1,3 +1,5 @@ +"use strict"; + var RID = require('../../recordid'); // db.edge.from('#1:1').to('#2:1').create('Foo') @@ -36,6 +38,7 @@ exports.to = function (to) { */ function fluent (args) { args = args || {}; + /*jshint validthis:true */ return { to: function (to) { args.to = to; @@ -71,8 +74,9 @@ function createEdge (db, config, from, to) { attributes = config[1]; command += ' ' + className + ' FROM ' + edgeReference(from) + ' TO ' + edgeReference(to); - if (attributes) + if (attributes) { command += ' CONTENT ' + JSON.stringify(attributes); + } return db.query(command); } @@ -146,10 +150,12 @@ function edgeReference (ref) { } else if (typeof ref === 'string') { // could be either an sql statement or an RID - if (rid = RID.parse(ref)) + if ((rid = RID.parse(ref))) { return rid; - else + } + else { return '(' + ref + ')'; + } } else if (ref instanceof RID) { return ref; diff --git a/lib/db/index.js b/lib/db/index.js index a6ea814..8399726 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -1,3 +1,5 @@ +"use strict"; + var utils = require('../utils'), errors = require('../errors'), Promise = require('bluebird'), @@ -11,8 +13,9 @@ var utils = require('../utils'), * @param {Object} config The optional configuration for the database. */ function Db (config) { - if (!config) + if (!config) { throw new errors.Config('Database object requires configuration'); + } this.configure(config); this.init(); this.augment('cluster', require('./cluster')); @@ -40,13 +43,9 @@ Db.prototype.configure = function (config) { this.server = config.server; - this.type = config.type === 'document' - ? 'document' - : 'graph'; + this.type = (config.type === 'document' ? 'document' : 'graph'); - this.storage = (config.storage === 'plocal' || config.storage === 'memory') - ? config.storage - : 'plocal'; + this.storage = ((config.storage === 'plocal' || config.storage === 'memory') ? config.storage : 'plocal'); this.username = config.username || 'admin'; this.password = config.password || 'admin'; @@ -118,8 +117,9 @@ Db.prototype.send = function (operation, data) { * @promise {Db} The database with reloaded configuration. */ Db.prototype.reload = function () { - if (this.sessionId === -1) + if (this.sessionId === -1) { return this.open(); + } this.server.logger.debug('Reloading database information'); return this.send('db-reload') .bind(this) @@ -196,8 +196,9 @@ Db.prototype.query = function (command, options) { return this.exec(command, options) .bind(this) .then(function (response) { - if (response.results.length === 0) + if (response.results.length === 0) { return []; + } return response.results .map(this.normalizeResult, this) .reduce(flatten, []) @@ -236,8 +237,12 @@ Db.prototype.query = function (command, options) { */ Db.prototype.normalizeResult = function (result) { var value; - if (!result) return result; - if (Array.isArray(result)) return result.map(this.normalizeResult, this); + if (!result) { + return result; + } + if (Array.isArray(result)) { + return result.map(this.normalizeResult, this); + } if (result.type === 'r' || result.type === 'f') { return this.normalizeResultContent(result.content, this); } @@ -261,8 +266,12 @@ Db.prototype.normalizeResult = function (result) { */ Db.prototype.normalizeResultContent = function (content) { var value; - if (!content) return null; - if (Array.isArray(content)) return content.map(this.normalizeResultContent, this); + if (!content) { + return null; + } + else if (Array.isArray(content)) { + return content.map(this.normalizeResultContent, this); + } if (content.type === 'd') { value = content.value || {}; diff --git a/lib/db/index/index.js b/lib/db/index/index.js index befca10..ba10229 100644 --- a/lib/db/index/index.js +++ b/lib/db/index/index.js @@ -1,3 +1,5 @@ +"use strict"; + var Promise = require('bluebird'), utils = require('../../utils'), errors = require('../../errors'), @@ -9,7 +11,9 @@ var Promise = require('bluebird'), */ function Index (config) { config = config || {}; - if (!(this instanceof Index)) return new Index(config); + if (!(this instanceof Index)) { + return new Index(config); + } this.configure(config); } @@ -42,7 +46,10 @@ Index.prototype.add = function (args) { args = Array.prototype.slice.call(arguments); } return Promise.map(args, function (item) { - return this.db.query('INSERT INTO index:' + this.name + ' (key, rid) VALUES ("' + this.db.escape(item.key) + '", ' + this.db.escape(item.rid) + ')'); + return this.db.query( + 'INSERT INTO index:' + this.name + + ' (key, rid) VALUES ("' + this.db.escape(item.key) + '", ' + this.db.escape(item.rid) + ')' + ); }.bind(this)); }; @@ -68,7 +75,11 @@ Index.prototype.get = function (key) { * @promise {Index} The index object. */ Index.prototype.set = function (key, value) { - return this.db.query('INSERT INTO index:' + this.name + ' (key, rid) VALUES ("' + this.db.escape(key) + '", ' + this.db.escape(value['@rid'] || value) + ')') + return this.db.query( + 'INSERT INTO index:' + this.name + + ' (key, rid) VALUES ("' + this.db.escape(key) + '", ' + this.db.escape(value['@rid'] || value) + + ')' + ) .return(this); }; @@ -113,8 +124,9 @@ Index.cached = false; * @promise {Object[]} An array of index objects. */ Index.list = function (refresh) { - if (!refresh && this.index.cached) + if (!refresh && this.index.cached) { return Promise.resolve(this.index.cached.items); + } return this.send('record-load', { cluster: 0, @@ -123,10 +135,12 @@ Index.list = function (refresh) { .bind(this) .then(function (response) { var record = response.records[0]; - if (!record || !record.indexes) + if (!record || !record.indexes) { return []; - else + } + else { return record.indexes; + } }) .then(this.index.cacheData) .then(function () { @@ -206,8 +220,9 @@ Index.get = function (name, refresh) { return this.index.cached.names[name] || Promise.reject(new errors.Request('No such index: ' + name)); }); } - else + else { return Promise.reject(new errors.Request('No such index: ' + name)); + } }; /** diff --git a/lib/db/query.js b/lib/db/query.js index 450e8f6..9dfd3fe 100644 --- a/lib/db/query.js +++ b/lib/db/query.js @@ -1,3 +1,5 @@ +"use strict"; + var Statement = require('./statement'); module.exports = exports = Statement.extend({ @@ -100,7 +102,9 @@ module.exports = exports = Statement.extend({ * @promise {Mixed} The query results. */ exec: function (params) { - if (params) this.addParams(params); + if (params) { + this.addParams(params); + } return this.db.query(this.buildStatement(), this.buildOptions()) .bind(this) .then(this._processResults); diff --git a/lib/db/record.js b/lib/db/record.js index 4df37e2..c201c49 100644 --- a/lib/db/record.js +++ b/lib/db/record.js @@ -1,3 +1,5 @@ +"use strict"; + var Promise = require('bluebird'), RID = require('../recordid'), errors = require('../errors'); @@ -67,8 +69,9 @@ exports.get = function (record, options) { return Promise.all(record.map(this.record.read, this)); } var extracted = extractRecordId(record), - rid = extracted[0], - record = extracted[1]; + rid = extracted[0]; + + record = extracted[1]; options = options || {}; @@ -84,12 +87,15 @@ exports.get = function (record, options) { }) .bind(this) .then(function (response) { - if (response.records.length === 0) + if (response.records.length === 0) { return Promise.reject(new errors.Request('No such record')); - else if (response.records.length === 1) + } + else if (response.records.length === 1) { return response.records[0]; - else + } + else { return this.record.resolveReferences(response.records[0], response.records.slice(1)); + } }); }; @@ -134,6 +140,7 @@ function recordIdResolver () { return obj; } else if (Array.isArray(obj)) { + /*jshint validthis:true */ return obj.map(replaceRecordIds.bind(this, records)); } else if (obj instanceof RID && records[obj]) { @@ -154,16 +161,20 @@ function recordIdResolver () { for (i = 0; i < total; i++) { key = keys[i]; value = obj[key]; - if (!value || typeof value !== 'object' || key[0] === '@') continue; + if (!value || typeof value !== 'object' || key[0] === '@') { + continue; + } if (value instanceof RID) { - if (records[value]) + if (records[value]) { obj[key] = records[value]; + } } else if (Array.isArray(value)) { obj[key] = value.map(replaceRecordIds.bind(this, records)); } - else + else { obj[key] = replaceRecordIds(records, value); + } } return obj; } @@ -180,8 +191,9 @@ exports.meta = function (record, options) { return Promise.all(record.map(this.record.read, this)); } var extracted = extractRecordId(record), - rid = extracted[0], - record = extracted[1]; + rid = extracted[0]; + + record = extracted[1]; options = options || {}; @@ -211,9 +223,10 @@ exports.meta = function (record, options) { exports.update = function (record, options) { var extracted = extractRecordId(record), rid = extracted[0], - record = extracted[1], promise, data; + record = extracted[1]; + options = options || {}; if (!rid) { @@ -241,8 +254,9 @@ exports.update = function (record, options) { return found; }); } - else + else { promise = Promise.resolve(record); + } return promise .bind(this) @@ -268,8 +282,9 @@ exports.delete = function (record, options) { return Promise.reject(new errors.Operation('Cannot delete - no record specified')); } var extracted = extractRecordId(record), - rid = extracted[0], - record = extracted[1]; + rid = extracted[0]; + + record = extracted[1]; options = options || {}; diff --git a/lib/db/statement.js b/lib/db/statement.js index 89aadd2..eeee15c 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -1,3 +1,5 @@ +"use strict"; + var RID = require('../recordid'), utils = require('../utils'); @@ -210,10 +212,12 @@ Statement.prototype.buildStatement = function () { statement.push('FROM'); statement.push(state.from.map(function (item) { if (typeof item === 'string') { - if (/(\s+)/.test(item)) + if (/(\s+)/.test(item)) { return '(' + item + ')'; - else + } + else { return item; + } } else { return ''+item; @@ -225,10 +229,12 @@ Statement.prototype.buildStatement = function () { statement.push('INTO'); statement.push(state.into.map(function (item) { if (typeof item === 'string') { - if (/(\s+)/.test(item)) + if (/(\s+)/.test(item)) { return '(' + item + ')'; - else + } + else { return item; + } } else { return ''+item; @@ -241,10 +247,12 @@ Statement.prototype.buildStatement = function () { statement.push(state.set.map(function (item) { var interim; if (typeof item === 'string') { - if (/(\s+)/.test(item)) + if (/(\s+)/.test(item)) { return '(' + item + ')'; - else + } + else { return item; + } } else { return this._objectToSet(item); @@ -294,10 +302,12 @@ Statement.prototype.buildStatement = function () { statement.push('GROUP BY'); statement.push(state.group.map(function (item) { if (typeof item === 'string') { - if (/(\s+)/.test(item)) + if (/(\s+)/.test(item)) { return '(' + item + ')'; - else + } + else { return item; + } } else { return ''+item; @@ -309,10 +319,12 @@ Statement.prototype.buildStatement = function () { statement.push('ORDER BY'); statement.push(state.order.map(function (item) { if (typeof item === 'string') { - if (/(\s+)/.test(item)) + if (/(\s+)/.test(item)) { return '(' + item + ')'; - else + } + else { return item; + } } else { return ''+item; @@ -321,10 +333,10 @@ Statement.prototype.buildStatement = function () { } if (state.limit) { - statement.push('LIMIT ' + (+state.limit)) + statement.push('LIMIT ' + (+state.limit)); } if (state.offset) { - statement.push('OFFSET ' + (+state.offset)) + statement.push('OFFSET ' + (+state.offset)); } return statement.join(' '); }; @@ -336,8 +348,9 @@ Statement.prototype.buildStatement = function () { */ Statement.prototype.buildOptions = function () { var opts = {}; - if (this._state.params) + if (this._state.params) { opts.params = this._state.params; + } if (this._state.fetchPlan) { opts.fetchPlan = this._state.fetchPlan.reduce(function (list, item) { var keys, total, key, i; @@ -382,7 +395,7 @@ Statement.prototype._objectToCondition = function (obj) { else { return '(' + conditions.join(' AND ') + ')'; } -} +}; Statement.prototype._objectToSet = function (obj) { var expressions = [], @@ -409,7 +422,7 @@ Statement.prototype._objectToSet = function (obj) { else { return expressions.join(', '); } -} +}; function paramify (key) { return key.replace(/([^A-Za-z0-9])/g, ''); @@ -432,13 +445,12 @@ function clause (name) { function whereClause (operator) { return function (condition, params) { - this._state.where = this._state.where || []; this._state.where.push([operator, condition]); if (params) { this.addParams(params); } return this; - } + }; } diff --git a/lib/db/vertex/index.js b/lib/db/vertex/index.js index 83a9dc9..9aba0cb 100644 --- a/lib/db/vertex/index.js +++ b/lib/db/vertex/index.js @@ -1,3 +1,5 @@ +"use strict"; + var Promise = require('bluebird'), RID = require('../../recordid'), errors = require('../../errors'); @@ -9,12 +11,14 @@ var Promise = require('bluebird'), * @promise {Object} The created vertex. */ exports.create = function (config) { - if (Array.isArray(config)) return Promise.map(config, this.vertex.create, this); + if (Array.isArray(config)) { + return Promise.map(config, this.vertex.create, this); + } var command = 'CREATE VERTEX', promise, cluster, className, attributes; config = vertexConfig(config); className = config[0]; - cluster = config[1] + cluster = config[1]; attributes = config[2]; if (cluster && +cluster == cluster) { promise = Promise.resolve(cluster); @@ -38,7 +42,7 @@ exports.create = function (config) { if (attributes) { command += " CONTENT " + JSON.stringify(attributes); } - return this.query(command) + return this.query(command); }) .then(function (results) { return results[0]; @@ -92,8 +96,9 @@ function vertexConfig (config) { if (config['@class']) { className = config['@class']; } - if (config['@cluster']) + if (config['@cluster']) { cluster = config['@cluster']; + } } return [className, cluster, obj]; } \ No newline at end of file diff --git a/lib/long.js b/lib/long.js index a87d057..cac5e75 100755 --- a/lib/long.js +++ b/lib/long.js @@ -1,3 +1,5 @@ +"use strict"; + // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -41,7 +43,7 @@ */ function Long(low, high) { if(!(this instanceof Long)) return new Long(low, high); - + this._bsontype = 'Long'; /** * @type {number} diff --git a/lib/migration/index.js b/lib/migration/index.js index 1846fa1..01d9dfe 100644 --- a/lib/migration/index.js +++ b/lib/migration/index.js @@ -1,3 +1,5 @@ +"use strict"; + var Promise = require('bluebird'), errors = require('../errors'), utils = require('../utils'), @@ -13,7 +15,9 @@ function Migration (config) { this.name = ''; this.server = null; this.db = null; - if (config) this.configure(config); + if (config) { + this.configure(config); + } } Migration.extend = utils.extend; diff --git a/lib/migration/manager.js b/lib/migration/manager.js index 3e9f3ec..b1ee49f 100644 --- a/lib/migration/manager.js +++ b/lib/migration/manager.js @@ -1,3 +1,5 @@ +"use strict"; + var Promise = require('bluebird'), errors = require('../errors'), utils = require('../utils'), @@ -14,7 +16,9 @@ function MigrationManager (config) { this.db = null; this.dir = process.cwd(); this.className = 'Migration'; - if (config) this.configure(config); + if (config) { + this.configure(config); + } } MigrationManager.extend = utils.extend; @@ -117,8 +121,9 @@ MigrationManager.prototype.list = function () { break; } } - if (!found) + if (!found) { pending.push(item); + } } return pending; }); @@ -191,10 +196,12 @@ MigrationManager.prototype.up = function (limit) { return this.list() .bind(this) .filter(function (item, index) { - if (limit && index >= limit) + if (limit && index >= limit) { return false; - else + } + else { return true; + } }) .reduce(function (accumulator, name) { return this.applyMigration(name) @@ -202,7 +209,7 @@ MigrationManager.prototype.up = function (limit) { accumulator.push(name); return accumulator; }); - }, []) + }, []); }; @@ -222,10 +229,12 @@ MigrationManager.prototype.down = function (limit) { return items.reverse(); }) .filter(function (item, index) { - if (limit && index >= limit) + if (limit && index >= limit) { return false; - else + } + else { return true; + } }) .reduce(function (accumulator, name) { return this.revertMigration(name) @@ -233,7 +242,7 @@ MigrationManager.prototype.down = function (limit) { accumulator.push(name); return accumulator; }); - }, []) + }, []); }; diff --git a/lib/recordid.js b/lib/recordid.js index a7fb1b5..3d3a58d 100644 --- a/lib/recordid.js +++ b/lib/recordid.js @@ -1,3 +1,5 @@ +"use strict"; + /** * # Record ID * @@ -26,8 +28,9 @@ * */ function RecordID (input) { - if (!(this instanceof RecordID)) + if (!(this instanceof RecordID)) { return new RecordID(input); + } this.cluster = null; this.position = null; @@ -35,8 +38,9 @@ function RecordID (input) { if (input) { if (typeof input === 'string') { var parsed = RecordID.parse(input); - if (parsed) + if (parsed) { return parsed; + } } else if (Array.isArray(input)) { return input.map(function (item) { @@ -44,8 +48,12 @@ function RecordID (input) { }); } else if (typeof input === 'object') { - if (input.cluster == +input.cluster) this.cluster = +input.cluster; - if (input.position == +input.position) this.position = +input.position; + if (input.cluster == +input.cluster) { + this.cluster = +input.cluster; + } + if (input.position == +input.position) { + this.position = +input.position; + } } } } @@ -71,28 +79,34 @@ RecordID.prototype.isValid = function () { * Parse a record id into a RecordID object. * * @param {String|Array|Object} input The input to parse. - * @return {RecordID|RecordID[]|Boolean} The parsed RecordID instance(s) or false if the record id could not be parsed + * @return {RecordID|RecordID[]|Boolean} The parsed RecordID instance(s) + * or false if the record id could not be parsed */ RecordID.parse = function (input) { if (input && typeof input === 'object') { - if (Array.isArray(input)) + if (Array.isArray(input)) { return input.map(RecordID.parse) .filter(function (item) { return item; }); - else if (input.cluster != null && input.position != null) + } + else if (input.cluster != null && input.position != null) { return new RecordID(input); - else + } + else { return false; + } } var matches = /^#(-?\d+):(-?\d+)$/.exec(input); - if (!matches) + if (!matches) { return false; - else + } + else { return new RecordID({ cluster: +matches[1], position: +matches[2] }); + } }; /** @@ -110,8 +124,9 @@ RecordID.isValid = function (input) { } else if (input && Array.isArray(input)) { for (i = 0; i < total; i++) { - if (!RecordID.isValid(input[i])) + if (!RecordID.isValid(input[i])) { return false; + } } return i ? true : false; } @@ -121,7 +136,7 @@ RecordID.isValid = function (input) { else { return false; } -} +}; /** * Return a record id for a given cluster and position. diff --git a/lib/server/config.js b/lib/server/config.js index 739af44..5b9916b 100644 --- a/lib/server/config.js +++ b/lib/server/config.js @@ -1,3 +1,5 @@ +"use strict"; + /** * Get a configuration value from the server. * diff --git a/lib/server/index.js b/lib/server/index.js index 01183af..9e21dd9 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -1,3 +1,5 @@ +"use strict"; + var ConnectionPool = require('../transport/binary/connection-pool'), Connection = require('../transport/binary/connection'), BinaryTransport = require('../transport/binary'), @@ -101,7 +103,7 @@ Server.prototype.send = function (operation, options) { Server.prototype.close = function () { this.transport.close(); return this; -} +}; // # Database Related Methods @@ -112,7 +114,9 @@ Server.prototype.close = function () { * @return {Db} The database instance. */ Server.prototype.use = function (config) { - if (!config) throw new errors.Config('Cannot use a database without a name.'); + if (!config) { + throw new errors.Config('Cannot use a database without a name.'); + } if (typeof config === 'string') { config = { @@ -124,7 +128,9 @@ Server.prototype.use = function (config) { config.server = this; } - if (!config.name) throw new errors.Config('Cannot use a database without a name.'); + if (!config.name) { + throw new errors.Config('Cannot use a database without a name.'); + } return new Db(config); }; @@ -157,8 +163,9 @@ Server.prototype.create = function (config) { return Promise.reject(new errors.Config('Cannot create database, no name specified.')); } - if (config.type !== 'document' && config.type !== 'graph') + if (config.type !== 'document' && config.type !== 'graph') { config.type = 'graph'; + } if (config.storage !== 'local' && config.storage !== 'plocal' && config.storage !== 'memory') { config.storage = 'plocal'; diff --git a/lib/transport/binary/connection-pool.js b/lib/transport/binary/connection-pool.js index fc8716e..5fbd072 100644 --- a/lib/transport/binary/connection-pool.js +++ b/lib/transport/binary/connection-pool.js @@ -1,3 +1,5 @@ +"use strict"; + var Connection = require('./connection'), util = require('util'), EventEmitter = require('events').EventEmitter; @@ -21,7 +23,10 @@ module.exports = ConnectionPool; * @return {Connection} A connection instance. */ ConnectionPool.prototype.dip = function () { - if (++this.index >= this.max) this.index = 0; + if (++this.index >= this.max) { + this.index = 0; + } + if (this.index > this.connections.length - 1) { this.connections.push(this.createConnection()); } diff --git a/lib/transport/binary/connection.js b/lib/transport/binary/connection.js index 95a7826..6049249 100644 --- a/lib/transport/binary/connection.js +++ b/lib/transport/binary/connection.js @@ -1,4 +1,6 @@ -var net = require('net') +"use strict"; + +var net = require('net'), util = require('util'), utils = require('../../utils'), errors = require('../../errors'), @@ -104,7 +106,7 @@ Connection.prototype._sendOp = function (op, params) { */ Connection.prototype.cancel = function (err) { var item, op; - while (item = this.queue.shift()) { + while ((item = this.queue.shift())) { op = item[0]; item[1].reject(err); } @@ -149,10 +151,12 @@ Connection.prototype.negotiateConnection = function () { this.logger.debug('connection closed during negotiation: ' + err); this.socket.removeAllListeners(); this.connecting = false; - if (err) + if (err) { reject(new errors.Connection(err.code, err.message)); - else + } + else { reject(new errors.Connection(0, 'Socket Closed')); + } }.bind(this)); }.bind(this)); @@ -210,7 +214,7 @@ Connection.prototype.negotiateProtocol = function () { Connection.prototype.bindToSocket = function () { this.socket.on('data', this.handleSocketData.bind(this)); this.socket.on('error', this.handleSocketError.bind(this)); - this.socket.on('close', this.handleSocketClose.bind(this)) + this.socket.on('close', this.handleSocketClose.bind(this)); this.socket.on('end', this.handleSocketEnd.bind(this)); }; @@ -301,7 +305,7 @@ Connection.prototype.destroySocket = function () { Connection.prototype.process = function (buffer, offset) { var code, parsed, result, status, item, op, deferred, err; offset = offset || 0; - while (item = this.queue.shift()) { + while ((item = this.queue.shift())) { op = item[0]; deferred = item[1]; parsed = op.consume(buffer, offset); @@ -324,7 +328,7 @@ Connection.prototype.process = function (buffer, offset) { else if (status === Operation.ERROR) { if (result.status.error) { // this is likely a recoverable error - deferred.reject(result.status.error) + deferred.reject(result.status.error); } else { // cannot recover, reject everything and let the application decide what to do @@ -334,8 +338,9 @@ Connection.prototype.process = function (buffer, offset) { this.emit('error', err); } } - else + else { deferred.reject(new errors.Protocol('Unsupported operation status: ' + status)); + } } return offset; }; \ No newline at end of file diff --git a/lib/transport/binary/index.js b/lib/transport/binary/index.js index f8de910..5c69e5c 100644 --- a/lib/transport/binary/index.js +++ b/lib/transport/binary/index.js @@ -1,3 +1,5 @@ +"use strict"; + var ConnectionPool = require('./connection-pool'), Connection = require('./connection'), utils = require('../../utils'), @@ -117,8 +119,9 @@ BinaryTransport.prototype.configurePool = function (config) { * @promise {BinaryTransport} The connected transport instance. */ BinaryTransport.prototype.connect = function () { - if (this.sessionId !== -1) + if (this.sessionId !== -1) { return Promise.resolve(this); + } if (this.connecting) { return this.connecting; diff --git a/lib/transport/binary/protocol/constants.js b/lib/transport/binary/protocol/constants.js index d88077e..bde444d 100644 --- a/lib/transport/binary/protocol/constants.js +++ b/lib/transport/binary/protocol/constants.js @@ -1,3 +1,5 @@ +"use strict"; + exports.PROTOCOL_VERSION = 19; exports.BYTES_LONG = 8; diff --git a/lib/transport/binary/protocol/deserializer.js b/lib/transport/binary/protocol/deserializer.js index ac5e99b..f3c3cb8 100644 --- a/lib/transport/binary/protocol/deserializer.js +++ b/lib/transport/binary/protocol/deserializer.js @@ -1,5 +1,7 @@ +"use strict"; + var RecordID = require('../../../recordid'), - Long = require('../../../long').Long + Long = require('../../../long').Long; /** @@ -22,7 +24,7 @@ var END_DELIMITERS = { '}': '{', ')': '(', '>': '<' -} +}; /** * Deserialize a given serialized document. @@ -33,7 +35,9 @@ var END_DELIMITERS = { * @return {Object} The deserialized document */ function deserializeDocument (serialized, document, isMap) { - if (serialized == null) return serialized; + if (serialized == null) { + return serialized; + } serialized = serialized.trim(); document = document || {}; @@ -50,8 +54,6 @@ function deserializeDocument (serialized, document, isMap) { document['@type'] = 'd'; } - var fieldIndex; - while (~(fieldIndex = serialized.indexOf(':'))) { field = serialized.substr(0, fieldIndex); serialized = serialized.substr(fieldIndex + 1); @@ -66,7 +68,7 @@ function deserializeDocument (serialized, document, isMap) { } return document; -}; +} /** * Deserialize a bag of values. @@ -114,7 +116,7 @@ function deserializeValue (value) { var firstChar = value.charAt(0), lastChar = value.charAt(value.length - 1), - values; + values, rid; if (firstChar === "\"") { // string @@ -165,7 +167,7 @@ function deserializeValue (value) { else { return value; } -}; +} /** diff --git a/lib/transport/binary/protocol/index.js b/lib/transport/binary/protocol/index.js index 753164d..f022070 100644 --- a/lib/transport/binary/protocol/index.js +++ b/lib/transport/binary/protocol/index.js @@ -1,3 +1,4 @@ +"use strict"; exports.Operation = require('./operation'); exports.OperationQueue = require('./operation-queue'); exports.constants = require('./constants'); diff --git a/lib/transport/binary/protocol/operation-queue.js b/lib/transport/binary/protocol/operation-queue.js index 5d4ad4a..d29b353 100644 --- a/lib/transport/binary/protocol/operation-queue.js +++ b/lib/transport/binary/protocol/operation-queue.js @@ -1,3 +1,5 @@ +"use strict"; + var Promise = require('bluebird'), Operation = require('./operation'), operations = require('./operations'), @@ -10,7 +12,9 @@ function OperationQueue (socket) { this.items = []; this.writes = []; this.remaining = null; - if (socket) this.bindToSocket(); + if (socket) { + this.bindToSocket(); + } Emitter.call(this); } @@ -59,7 +63,7 @@ OperationQueue.prototype.add = function (op, params) { */ OperationQueue.prototype.cancel = function (err) { var item, op, deferred; - while (item = this.items.shift()) { + while ((item = this.items.shift())) { op = item[0]; deferred = item[1]; deferred.reject(err); @@ -72,10 +76,11 @@ OperationQueue.prototype.cancel = function (err) { */ OperationQueue.prototype.bindToSocket = function (socket) { var total, i; - if (socket) + if (socket) { this.socket = socket; + } this.socket.on('data', this.handleChunk.bind(this)); - if (total = this.writes.length) { + if ((total = this.writes.length)) { if (this.socket.connected) { for (i = 0; i < total; i++) { this.socket.write(this.writes[i]); @@ -138,7 +143,7 @@ OperationQueue.prototype.handleChunk = function (data) { OperationQueue.prototype.process = function (buffer, offset) { var code, parsed, result, status, item, op, deferred, err; offset = offset || 0; - while (item = this.items.shift()) { + while ((item = this.items.shift())) { op = item[0]; deferred = item[1]; parsed = op.consume(buffer, offset); @@ -161,7 +166,7 @@ OperationQueue.prototype.process = function (buffer, offset) { else if (status === Operation.ERROR) { if (result.status.error) { // this is likely a recoverable error - deferred.reject(result.status.error) + deferred.reject(result.status.error); } else { // cannot recover, reject everything and let the application decide what to do @@ -171,8 +176,9 @@ OperationQueue.prototype.process = function (buffer, offset) { this.emit('error', err); } } - else + else { deferred.reject(new errors.Protocol('Unsupported operation status: ' + status)); + } } return offset; }; \ No newline at end of file diff --git a/lib/transport/binary/protocol/operation.js b/lib/transport/binary/protocol/operation.js index 15d3e00..0116a5e 100644 --- a/lib/transport/binary/protocol/operation.js +++ b/lib/transport/binary/protocol/operation.js @@ -1,3 +1,5 @@ +"use strict"; + var constants = require('./constants'), utils = require('../../../utils'), Long = require('../../../long').Long, @@ -62,7 +64,9 @@ Operation.prototype.reader = function () { */ Operation.prototype.buffer = function () { - if (!this.writeOps.length) this.writer(); + if (!this.writeOps.length) { + this.writer(); + } var total = this.writeOps.length, size = 0, @@ -349,7 +353,7 @@ Operation.prototype.consume = function (buffer, offset) { if (this.readOps.length === 0) { return [Operation.COMPLETE, offset, this.stack[0]]; } - while (item = this.readOps.shift()) { + while ((item = this.readOps.shift())) { context = this.stack[this.stack.length - 1]; if (typeof item === 'function') { // this is a nop, just execute it. @@ -384,7 +388,9 @@ Operation.prototype.consume = function (buffer, offset) { */ Operation.prototype.canRead = function (type, buffer, offset) { var length = buffer.length; - if (offset > length) return false; + if (offset > length) { + return false; + } switch (type) { case 'Array': case 'Error': @@ -403,14 +409,17 @@ Operation.prototype.canRead = function (type, buffer, offset) { return length >= offset + constants.BYTES_INT; case 'Bytes': case 'String': - if (length <= offset + constants.BYTES_INT) + if (length <= offset + constants.BYTES_INT) { return false; - else + } + else { return length >= offset + constants.BYTES_INT + buffer.readInt32BE(offset); + } + break; default: return false; } -} +}; /** @@ -426,8 +435,9 @@ Operation.prototype.canRead = function (type, buffer, offset) { */ Operation.prototype.parseByte = function (buffer, offset, context, fieldName, reader) { context[fieldName] = buffer.readUInt8(offset); - if (reader) + if (reader) { reader.call(this, context, fieldName); + } return 1; }; @@ -444,8 +454,9 @@ Operation.prototype.parseByte = function (buffer, offset, context, fieldName, re */ Operation.prototype.parseChar = function (buffer, offset, context, fieldName, reader) { context[fieldName] = String.fromCharCode(buffer.readUInt8(offset)); - if (reader) + if (reader) { reader.call(this, context, fieldName); + } return 1; }; @@ -462,8 +473,9 @@ Operation.prototype.parseChar = function (buffer, offset, context, fieldName, re */ Operation.prototype.parseBoolean = function (buffer, offset, context, fieldName, reader) { context[fieldName] = Boolean(buffer.readUInt8(offset)); - if (reader) + if (reader) { reader.call(this, context, fieldName); + } return 1; }; @@ -482,8 +494,9 @@ Operation.prototype.parseBoolean = function (buffer, offset, context, fieldName, Operation.prototype.parseShort = function (buffer, offset, context, fieldName, reader) { context[fieldName] = buffer.readInt16BE(offset); - if (reader) + if (reader) { reader.call(this, context, fieldName); + } return constants.BYTES_SHORT; }; @@ -500,8 +513,9 @@ Operation.prototype.parseShort = function (buffer, offset, context, fieldName, r */ Operation.prototype.parseInt = function (buffer, offset, context, fieldName, reader) { context[fieldName] = buffer.readInt32BE(offset); - if (reader) + if (reader) { reader.call(this, context, fieldName); + } return constants.BYTES_INT; }; @@ -524,8 +538,9 @@ Operation.prototype.parseLong = function (buffer, offset, context, fieldName, re ) .toNumber(); - if (reader) + if (reader) { reader.call(this, context, fieldName); + } return constants.BYTES_LONG; }; @@ -543,12 +558,15 @@ Operation.prototype.parseLong = function (buffer, offset, context, fieldName, re Operation.prototype.parseBytes = function (buffer, offset, context, fieldName, reader) { var length = buffer.readInt32BE(offset); offset += constants.BYTES_INT; - if (length < 0) + if (length < 0) { context[fieldName] = null; - else + } + else { context[fieldName] = buffer.slice(offset, offset + length); - if (reader) + } + if (reader) { reader.call(this, context, fieldName); + } return length > 0 ? length + constants.BYTES_INT : constants.BYTES_INT; }; @@ -567,12 +585,15 @@ Operation.prototype.parseBytes = function (buffer, offset, context, fieldName, r Operation.prototype.parseString = function (buffer, offset, context, fieldName, reader) { var length = buffer.readInt32BE(offset); offset += constants.BYTES_INT; - if (length < 0) + if (length < 0) { context[fieldName] = null; - else + } + else { context[fieldName] = buffer.toString('utf8', offset, offset + length); - if (reader) + } + if (reader) { reader.call(this, context, fieldName); + } return length > 0 ? length + constants.BYTES_INT : constants.BYTES_INT; }; @@ -592,17 +613,20 @@ Operation.prototype.parseRecord = function (buffer, offset, context, fieldName, record = {}; this.readOps = []; this.stack.push(record); - if (Array.isArray(context[fieldName])) + if (Array.isArray(context[fieldName])) { context[fieldName].push(record); - else + } + else { context[fieldName] = record; + } this.readShort('classId', function (record, fieldName) { if (record[fieldName] === -1) { record.value = new errors.Protocol('No class for record, cannot proceed.'); this.stack.pop(); this.readOps.push(function () { - if (reader) + if (reader) { reader.call(this, context, fieldName); + } }); this.readOps.push.apply(this.readOps, remainingOps); return; @@ -611,8 +635,9 @@ Operation.prototype.parseRecord = function (buffer, offset, context, fieldName, record.value = null; this.stack.pop(); this.readOps.push(function () { - if (reader) + if (reader) { reader.call(this, context, fieldName); + } }); this.readOps.push.apply(this.readOps, remainingOps); return; @@ -625,8 +650,9 @@ Operation.prototype.parseRecord = function (buffer, offset, context, fieldName, .readOps.push(function () { this.stack.pop(); this.readOps.push(function () { - if (reader) + if (reader) { reader.call(this, context, fieldName); + } }); this.readOps.push.apply(this.readOps, remainingOps); }); @@ -641,8 +667,9 @@ Operation.prototype.parseRecord = function (buffer, offset, context, fieldName, data[key] = deserializer.deserializeDocument(data[key]); this.stack.pop(); this.readOps.push(function () { - if (reader) + if (reader) { reader.call(this, context, fieldName); + } }); this.readOps.push.apply(this.readOps, remainingOps); }); @@ -684,7 +711,7 @@ Operation.prototype.parseCollection = function (buffer, offset, context, fieldNa } this.readOps.push.apply(this.readOps, remainingOps); return 4; -} +}; /** @@ -782,11 +809,12 @@ Operation.prototype.parseError = function (buffer, offset, context, fieldName, r }); }); } - }) + }); } readItem.call(this); - if (reader) + if (reader) { reader.call(this, context, fieldName); + } return 0; }; @@ -815,8 +843,9 @@ Operation.prototype.parsePushedData = function (buffer, offset, context, fieldNa default: console.log('unsupported pushed data format: ' + asString); } - if (reader) + if (reader) { reader.call(this, context, fieldName); + } return length + constants.BYTES_INT; }; diff --git a/lib/transport/binary/protocol/operations/command.js b/lib/transport/binary/protocol/operations/command.js index 778ab5e..b3c7c83 100644 --- a/lib/transport/binary/protocol/operations/command.js +++ b/lib/transport/binary/protocol/operations/command.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'), serializer = require('../serializer'), @@ -23,7 +25,8 @@ module.exports = Operation.extend({ writer.writeString(this.data.class), writer.writeString(this.data.query) ]; - if (this.data.class === 'com.orientechnologies.orient.core.sql.query.OSQLSynchQuery' || this.data.class === 'com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery') { + if (this.data.class === 'com.orientechnologies.orient.core.sql.query.OSQLSynchQuery' || + this.data.class === 'com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery') { buffers.push( writer.writeInt(this.data.limit), writer.writeString(this.data.fetchPlan || '') diff --git a/lib/transport/binary/protocol/operations/config-get.js b/lib/transport/binary/protocol/operations/config-get.js index e99fb33..a5e3b74 100644 --- a/lib/transport/binary/protocol/operations/config-get.js +++ b/lib/transport/binary/protocol/operations/config-get.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); diff --git a/lib/transport/binary/protocol/operations/config-list.js b/lib/transport/binary/protocol/operations/config-list.js index c898aab..e4cab31 100644 --- a/lib/transport/binary/protocol/operations/config-list.js +++ b/lib/transport/binary/protocol/operations/config-list.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); diff --git a/lib/transport/binary/protocol/operations/config-set.js b/lib/transport/binary/protocol/operations/config-set.js index 5ad4ec4..686dcca 100644 --- a/lib/transport/binary/protocol/operations/config-set.js +++ b/lib/transport/binary/protocol/operations/config-set.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); diff --git a/lib/transport/binary/protocol/operations/connect.js b/lib/transport/binary/protocol/operations/connect.js index e45ecca..73f98a0 100644 --- a/lib/transport/binary/protocol/operations/connect.js +++ b/lib/transport/binary/protocol/operations/connect.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'), npmPackage = require('../../../../../package.json'); diff --git a/lib/transport/binary/protocol/operations/datacluster-add.js b/lib/transport/binary/protocol/operations/datacluster-add.js index 0cb7d74..bbff494 100644 --- a/lib/transport/binary/protocol/operations/datacluster-add.js +++ b/lib/transport/binary/protocol/operations/datacluster-add.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); diff --git a/lib/transport/binary/protocol/operations/datacluster-count.js b/lib/transport/binary/protocol/operations/datacluster-count.js index 6aa7a87..2859a63 100644 --- a/lib/transport/binary/protocol/operations/datacluster-count.js +++ b/lib/transport/binary/protocol/operations/datacluster-count.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); diff --git a/lib/transport/binary/protocol/operations/datacluster-datarange.js b/lib/transport/binary/protocol/operations/datacluster-datarange.js index 59daf65..9caae51 100644 --- a/lib/transport/binary/protocol/operations/datacluster-datarange.js +++ b/lib/transport/binary/protocol/operations/datacluster-datarange.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); diff --git a/lib/transport/binary/protocol/operations/datacluster-drop.js b/lib/transport/binary/protocol/operations/datacluster-drop.js index aaea905..89b3ea5 100644 --- a/lib/transport/binary/protocol/operations/datacluster-drop.js +++ b/lib/transport/binary/protocol/operations/datacluster-drop.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); diff --git a/lib/transport/binary/protocol/operations/datasegment-add.js b/lib/transport/binary/protocol/operations/datasegment-add.js index 7e2819e..371f158 100644 --- a/lib/transport/binary/protocol/operations/datasegment-add.js +++ b/lib/transport/binary/protocol/operations/datasegment-add.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); @@ -16,6 +18,6 @@ module.exports = Operation.extend({ reader: function () { this .readStatus('status') - .readInt('id') + .readInt('id'); } }); \ No newline at end of file diff --git a/lib/transport/binary/protocol/operations/datasegment-drop.js b/lib/transport/binary/protocol/operations/datasegment-drop.js index 4037c51..396eba7 100644 --- a/lib/transport/binary/protocol/operations/datasegment-drop.js +++ b/lib/transport/binary/protocol/operations/datasegment-drop.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); diff --git a/lib/transport/binary/protocol/operations/db-close.js b/lib/transport/binary/protocol/operations/db-close.js index e874f5d..ba291e8 100644 --- a/lib/transport/binary/protocol/operations/db-close.js +++ b/lib/transport/binary/protocol/operations/db-close.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); @@ -7,8 +9,7 @@ module.exports = Operation.extend({ writer: function () { this .writeByte(this.opCode) - .writeInt(this.data.sessionId || -1) + .writeInt(this.data.sessionId || -1); }, - reader: function () { - } + reader: function () {} }); \ No newline at end of file diff --git a/lib/transport/binary/protocol/operations/db-countrecords.js b/lib/transport/binary/protocol/operations/db-countrecords.js index dfbd726..a290a89 100644 --- a/lib/transport/binary/protocol/operations/db-countrecords.js +++ b/lib/transport/binary/protocol/operations/db-countrecords.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); @@ -7,7 +9,7 @@ module.exports = Operation.extend({ writer: function () { this .writeByte(this.opCode) - .writeInt(this.data.sessionId) + .writeInt(this.data.sessionId); }, reader: function () { this diff --git a/lib/transport/binary/protocol/operations/db-create.js b/lib/transport/binary/protocol/operations/db-create.js index 7ac02b9..e99b764 100644 --- a/lib/transport/binary/protocol/operations/db-create.js +++ b/lib/transport/binary/protocol/operations/db-create.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); diff --git a/lib/transport/binary/protocol/operations/db-delete.js b/lib/transport/binary/protocol/operations/db-delete.js index c53e69b..04e5b58 100644 --- a/lib/transport/binary/protocol/operations/db-delete.js +++ b/lib/transport/binary/protocol/operations/db-delete.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); diff --git a/lib/transport/binary/protocol/operations/db-exists.js b/lib/transport/binary/protocol/operations/db-exists.js index cba9d8d..29c1e40 100644 --- a/lib/transport/binary/protocol/operations/db-exists.js +++ b/lib/transport/binary/protocol/operations/db-exists.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); diff --git a/lib/transport/binary/protocol/operations/db-list.js b/lib/transport/binary/protocol/operations/db-list.js index 833d58a..8262b5e 100644 --- a/lib/transport/binary/protocol/operations/db-list.js +++ b/lib/transport/binary/protocol/operations/db-list.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); @@ -7,13 +9,13 @@ module.exports = Operation.extend({ writer: function () { this .writeByte(this.opCode) - .writeInt(this.data.sessionId || -1) + .writeInt(this.data.sessionId || -1); }, reader: function () { this .readStatus('status') .readObject('databases', function (data, fieldName) { data[fieldName] = data[fieldName].databases; - }) + }); } }); \ No newline at end of file diff --git a/lib/transport/binary/protocol/operations/db-open.js b/lib/transport/binary/protocol/operations/db-open.js index 7965370..e595de3 100644 --- a/lib/transport/binary/protocol/operations/db-open.js +++ b/lib/transport/binary/protocol/operations/db-open.js @@ -1,3 +1,4 @@ +"use strict"; var Operation = require('../operation'), constants = require('../constants'), npmPackage = require('../../../../../package.json'); diff --git a/lib/transport/binary/protocol/operations/db-reload.js b/lib/transport/binary/protocol/operations/db-reload.js index 1aed6d5..64561f2 100644 --- a/lib/transport/binary/protocol/operations/db-reload.js +++ b/lib/transport/binary/protocol/operations/db-reload.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); @@ -7,7 +9,7 @@ module.exports = Operation.extend({ writer: function () { this .writeByte(this.opCode) - .writeInt(this.data.sessionId || -1) + .writeInt(this.data.sessionId || -1); }, reader: function () { this diff --git a/lib/transport/binary/protocol/operations/db-size.js b/lib/transport/binary/protocol/operations/db-size.js index d064b04..e83b6b3 100644 --- a/lib/transport/binary/protocol/operations/db-size.js +++ b/lib/transport/binary/protocol/operations/db-size.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); @@ -7,7 +9,7 @@ module.exports = Operation.extend({ writer: function () { this .writeByte(this.opCode) - .writeInt(this.data.sessionId) + .writeInt(this.data.sessionId); }, reader: function () { this diff --git a/lib/transport/binary/protocol/operations/index.js b/lib/transport/binary/protocol/operations/index.js index 0166eb4..f549b17 100644 --- a/lib/transport/binary/protocol/operations/index.js +++ b/lib/transport/binary/protocol/operations/index.js @@ -1,3 +1,5 @@ +"use strict"; /*jshint sub:true*/ + exports['connect'] = require('./connect'); exports['db-open'] = require('./db-open'); exports['db-create'] = require('./db-create'); diff --git a/lib/transport/binary/protocol/operations/record-clean-out.js b/lib/transport/binary/protocol/operations/record-clean-out.js index 3dbb55c..a140348 100644 --- a/lib/transport/binary/protocol/operations/record-clean-out.js +++ b/lib/transport/binary/protocol/operations/record-clean-out.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'), RID = require('../../../../recordid'), @@ -28,6 +30,6 @@ module.exports = Operation.extend({ reader: function () { this .readStatus('status') - .readBoolean('success') + .readBoolean('success'); } }); \ No newline at end of file diff --git a/lib/transport/binary/protocol/operations/record-create.js b/lib/transport/binary/protocol/operations/record-create.js index a5e4ba5..ea83566 100644 --- a/lib/transport/binary/protocol/operations/record-create.js +++ b/lib/transport/binary/protocol/operations/record-create.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'), RID = require('../../../../recordid'), diff --git a/lib/transport/binary/protocol/operations/record-delete.js b/lib/transport/binary/protocol/operations/record-delete.js index 9f43cb8..a246628 100644 --- a/lib/transport/binary/protocol/operations/record-delete.js +++ b/lib/transport/binary/protocol/operations/record-delete.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'), RID = require('../../../../recordid'), diff --git a/lib/transport/binary/protocol/operations/record-load.js b/lib/transport/binary/protocol/operations/record-load.js index ef289eb..d4e6fc0 100644 --- a/lib/transport/binary/protocol/operations/record-load.js +++ b/lib/transport/binary/protocol/operations/record-load.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'), RID = require('../../../../recordid'), @@ -80,7 +82,7 @@ module.exports = Operation.extend({ } this.stack.pop(); this.readPayload(records, ender); - }) + }); break; case 2: // a sub record @@ -94,7 +96,6 @@ module.exports = Operation.extend({ break; case -3: throw new errors.Protocol('ClassID ' + data[fieldName] + ' is not supported.'); - break; default: this .readChar('type') @@ -109,7 +110,7 @@ module.exports = Operation.extend({ this.readPayload(records, ender); }); } - }) + }); break; default: this.readPayload(records, ender); diff --git a/lib/transport/binary/protocol/operations/record-metadata.js b/lib/transport/binary/protocol/operations/record-metadata.js index 4afecf4..2d0ccb3 100644 --- a/lib/transport/binary/protocol/operations/record-metadata.js +++ b/lib/transport/binary/protocol/operations/record-metadata.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'), RID = require('../../../../recordid'), diff --git a/lib/transport/binary/protocol/operations/record-update.js b/lib/transport/binary/protocol/operations/record-update.js index aca7076..c128025 100644 --- a/lib/transport/binary/protocol/operations/record-update.js +++ b/lib/transport/binary/protocol/operations/record-update.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'), RID = require('../../../../recordid'), diff --git a/lib/transport/binary/protocol/serializer.js b/lib/transport/binary/protocol/serializer.js index 10adceb..3b3c28e 100644 --- a/lib/transport/binary/protocol/serializer.js +++ b/lib/transport/binary/protocol/serializer.js @@ -1,3 +1,5 @@ +"use strict"; + var RecordID = require('../../../recordid'); /** @@ -53,7 +55,7 @@ function serializeDocument (document, isMap) { } return result; -}; +} /** * Serialize a given value according to its type. @@ -86,9 +88,10 @@ function serializeValue (value) { else if (value === Object(value)) { return serializeObject(value); } - else + else { return ''; -}; + } +} /** @@ -119,13 +122,16 @@ function serializeArray (value) { * @return {String} The serialized value. */ function serializeObject (value) { - if (value instanceof RecordID) + if (value instanceof RecordID) { return value.toString(); - else if (value['@type'] === 'd') + } + else if (value['@type'] === 'd') { return '(' + serializeDocument(value, false) + ')'; - else + } + else { return '{' + serializeDocument(value, true) + '}'; -}; + } +} diff --git a/lib/transport/binary/protocol/writer.js b/lib/transport/binary/protocol/writer.js index 2a3c829..cc6a9a0 100644 --- a/lib/transport/binary/protocol/writer.js +++ b/lib/transport/binary/protocol/writer.js @@ -1,3 +1,5 @@ +"use strict"; + var Long = require('../../../long').Long, constants = require('./constants'); diff --git a/lib/transport/index.js b/lib/transport/index.js index f448f84..fb30c9e 100644 --- a/lib/transport/index.js +++ b/lib/transport/index.js @@ -1,2 +1,4 @@ +"use strict"; + exports.Binary = exports.BinaryTransport = require('./binary'); exports.Rest = exports.RestTransport = require('./rest'); \ No newline at end of file diff --git a/lib/transport/rest/index.js b/lib/transport/rest/index.js index 635964d..c784c54 100644 --- a/lib/transport/rest/index.js +++ b/lib/transport/rest/index.js @@ -1,3 +1,5 @@ +"use strict"; + var utils = require('../../utils'), errors = require('../../errors'), Db = require('../../db/index'), @@ -8,7 +10,7 @@ var utils = require('../../utils'), deserializer = require('./protocol/deserializer'), operations = require('./protocol/operations'), EventEmitter = require('events').EventEmitter, - npmPackage = require('../../../package.json');; + npmPackage = require('../../../package.json'); /** @@ -132,7 +134,7 @@ RestTransport.prototype.applyAuth = function (config) { password: this.password }; return config; -} +}; RestTransport.prototype.handleResponse = function (op, prepared, response) { if (response.statusCode === 401) { @@ -145,9 +147,9 @@ RestTransport.prototype.handleResponse = function (op, prepared, response) { else { return deserializer.deserializeDocument(response.body); } - }) + }); } else { return deserializer.deserializeDocument(response.body); } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/lib/transport/rest/protocol/deserializer.js b/lib/transport/rest/protocol/deserializer.js index e9e8b4d..e64c016 100644 --- a/lib/transport/rest/protocol/deserializer.js +++ b/lib/transport/rest/protocol/deserializer.js @@ -1,3 +1,5 @@ +"use strict"; + var RecordID = require('../../../recordid'), errors = require('../../../errors'), Long = require('../../../long').Long; diff --git a/lib/transport/rest/protocol/index.js b/lib/transport/rest/protocol/index.js index a670577..5155f2f 100644 --- a/lib/transport/rest/protocol/index.js +++ b/lib/transport/rest/protocol/index.js @@ -1,3 +1,5 @@ +"use strict"; + exports.operations = require('./operations'); exports.deserializer = require('./deserializer'); exports.Operation = require('./operation'); \ No newline at end of file diff --git a/lib/transport/rest/protocol/operation.js b/lib/transport/rest/protocol/operation.js index 3808f83..22c4793 100644 --- a/lib/transport/rest/protocol/operation.js +++ b/lib/transport/rest/protocol/operation.js @@ -1,3 +1,5 @@ +"use strict"; + var utils = require('../../../utils'); /** diff --git a/lib/transport/rest/protocol/operations/command.js b/lib/transport/rest/protocol/operations/command.js index d17e5aa..ec4b83c 100644 --- a/lib/transport/rest/protocol/operations/command.js +++ b/lib/transport/rest/protocol/operations/command.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), utils = require('../../../../utils'); diff --git a/lib/transport/rest/protocol/operations/db-open.js b/lib/transport/rest/protocol/operations/db-open.js index 712b091..3deb498 100644 --- a/lib/transport/rest/protocol/operations/db-open.js +++ b/lib/transport/rest/protocol/operations/db-open.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), npmPackage = require('../../../../../package.json'); diff --git a/lib/transport/rest/protocol/operations/index.js b/lib/transport/rest/protocol/operations/index.js index a68150b..4db9221 100644 --- a/lib/transport/rest/protocol/operations/index.js +++ b/lib/transport/rest/protocol/operations/index.js @@ -1,3 +1,5 @@ +"use strict"; /*jshint sub:true*/ + exports['db-open'] = require('./db-open'); exports['record-load'] = require('./record-load'); exports['command'] = require('./command'); \ No newline at end of file diff --git a/lib/transport/rest/protocol/operations/record-load.js b/lib/transport/rest/protocol/operations/record-load.js index 94d6d47..e8aa001 100644 --- a/lib/transport/rest/protocol/operations/record-load.js +++ b/lib/transport/rest/protocol/operations/record-load.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), npmPackage = require('../../../../../package.json'); diff --git a/lib/utils.js b/lib/utils.js index 1ceed64..8af5ed0 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,3 +1,5 @@ +"use strict"; + var RID = require('./recordid'); /** @@ -28,29 +30,35 @@ exports.extend = function (source) { var parent = this, child; - if (source.hasOwnProperty('constructor')) + if (source.hasOwnProperty('constructor')) { child = source.constructor; - else + } + else { child = function () { return parent.apply(this, arguments); }; + } var Surrogate = function () { this.constructor = child; }; Surrogate.prototype = parent.prototype; - child.prototype = new Surrogate; + child.prototype = new Surrogate(); var keys, key, i, limit; for (keys = Object.keys(parent), key = null, i = 0, limit = keys.length; i < limit; i++) { key = keys[i]; - if (key !== 'prototype') + if (key !== 'prototype') { child[key] = parent[key]; + } } for (keys = Object.keys(source), key = null, i = 0, limit = keys.length; i < limit; i++) { key = keys[i]; - if (key.charCodeAt(0) === 64) // @ + if (key.charCodeAt(0) === 64) { + // @ child[key.slice(1)] = source[key]; - else if (key !== 'constructor') + } + else if (key !== 'constructor') { child.prototype[key] = source[key]; + } } child.__super__ = child; @@ -90,10 +98,12 @@ exports.augment = function (name, props) { * @return {Mixed} The cloned item. */ exports.clone = function (item) { - if (Object(item) !== item) + if (Object(item) !== item) { return item; - else if (Array.isArray(item)) + } + else if (Array.isArray(item)) { return item.slice(); + } var keys = Object.keys(item), total = keys.length, @@ -124,7 +134,9 @@ exports.escape = function (input) { * @return {String} The prepared query. */ exports.prepare = function (query, params) { - if (!params) return query; + if (!params) { + return query; + } var pattern = /"(\\[\s\S]|[^"])*"|'(\\[\s\S]|[^'])*'|:([A-Za-z_-]+|\/\*[\s\S]*?\*\/)/; return query.replace(pattern, function (all, double, single, param) { if (param) { @@ -133,7 +145,7 @@ exports.prepare = function (query, params) { else { return all; } - }) + }); }; /** @@ -144,7 +156,7 @@ exports.prepare = function (query, params) { */ exports.encode = function (value) { if (value == null) { - return 'null' + return 'null'; } else if (typeof value === 'number') { return value; From 02e05ea100cd3a46d9284ed3c6dd32d336dd2b31 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 15:56:48 +0100 Subject: [PATCH 030/308] fix reference to leaky global --- test/bugs/27-slow.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/bugs/27-slow.js b/test/bugs/27-slow.js index ca8ecf2..9512c8c 100644 --- a/test/bugs/27-slow.js +++ b/test/bugs/27-slow.js @@ -1,3 +1,5 @@ +var Promise = require('bluebird'); + describe("Bug #27: Slow compared to Restful API", function () { var LIMIT = 5000; before(function () { From 36e2cc6f5ed3a600726d4c7c176d35a454116dd6 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 15:58:41 +0100 Subject: [PATCH 031/308] add jshint dev dependency, runner --- package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 53b007d..d7f620a 100755 --- a/package.json +++ b/package.json @@ -52,7 +52,8 @@ "mocha": ">=1.18.2", "should": "*", "expect.js": "*", - "istanbul": "*" + "istanbul": "*", + "jshint": "~2.5.0" }, "main": "./lib/index.js", "directories": { @@ -67,7 +68,8 @@ "scripts": { "test": "echo \"\n\nNOTICE: If tests fail, please ensure you've set the correct credentials in test/test-server.json\n\n\"; node ./node_modules/mocha/bin/mocha ./test/index.js ./test/**/*.js ./test/**/**/*.js ./test/**/**/**/*.js ./test/**/**/**/**/*.js --reporter=spec -t 10000", "watch": "node ./node_modules/mocha/bin/mocha ./test/index.js ./test/**/*.js ./test/**/**/*.js ./test/**/**/**/*.js ./test/**/**/**/**/*.js --reporter=spec -t 10000 --watch", - "coverage": "./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha ./test/index.js ./test/**/*.js ./test/**/**/*.js ./test/**/**/**/*.js ./test/**/**/**/**/*.js --reporter=spec" + "coverage": "./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha ./test/index.js ./test/**/*.js ./test/**/**/*.js ./test/**/**/**/*.js ./test/**/**/**/**/*.js --reporter=spec", + "lint": "./node_modules/.bin/jshint ./lib" }, "licenses": [ { From 1fb22fd11237e79c0f1cf9c9f0d904c2797e4f66 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 16:02:09 +0100 Subject: [PATCH 032/308] tighten up dependency versions, don't use * --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index d7f620a..025d33a 100755 --- a/package.json +++ b/package.json @@ -44,15 +44,15 @@ "url": "http://github.com/codemix/oriento.git" }, "dependencies": { - "bluebird": "*", - "yargs": "*", + "bluebird": "~1.2.3", + "yargs": "~1.2.1", "request": "~2.34.0" }, "devDependencies": { - "mocha": ">=1.18.2", - "should": "*", - "expect.js": "*", - "istanbul": "*", + "mocha": "~1.18.2", + "should": "~3.3.1", + "expect.js": "~0.3.1", + "istanbul": "~0.2.7", "jshint": "~2.5.0" }, "main": "./lib/index.js", From a704edd50f570f7f0ccd7866ff66ba07e362b0a0 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 16:09:44 +0100 Subject: [PATCH 033/308] make jshint run on pretest --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 025d33a..c53ca77 100755 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "oriento": "./bin/oriento" }, "scripts": { + "pretest": "./node_modules/.bin/jshint ./lib", "test": "echo \"\n\nNOTICE: If tests fail, please ensure you've set the correct credentials in test/test-server.json\n\n\"; node ./node_modules/mocha/bin/mocha ./test/index.js ./test/**/*.js ./test/**/**/*.js ./test/**/**/**/*.js ./test/**/**/**/**/*.js --reporter=spec -t 10000", "watch": "node ./node_modules/mocha/bin/mocha ./test/index.js ./test/**/*.js ./test/**/**/*.js ./test/**/**/**/*.js ./test/**/**/**/**/*.js --reporter=spec -t 10000 --watch", "coverage": "./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha ./test/index.js ./test/**/*.js ./test/**/**/*.js ./test/**/**/**/*.js ./test/**/**/**/**/*.js --reporter=spec", From bb36978649591712f4a60248febc20d0ba5d6ce2 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 16:16:34 +0100 Subject: [PATCH 034/308] fix jshint errors in errors --- .jshintignore | 3 +-- lib/errors/base.js | 18 +++++++++++++----- lib/errors/config.js | 2 ++ lib/errors/connection.js | 2 ++ lib/errors/index.js | 2 ++ lib/errors/operation.js | 2 ++ lib/errors/protocol.js | 2 ++ lib/errors/record.js | 2 ++ lib/errors/request.js | 2 ++ 9 files changed, 28 insertions(+), 7 deletions(-) diff --git a/.jshintignore b/.jshintignore index f79d3d3..3403c9a 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1,2 +1 @@ -lib/long.js -lib/errors/*.js \ No newline at end of file +lib/long.js \ No newline at end of file diff --git a/lib/errors/base.js b/lib/errors/base.js index e12bdaf..25a6920 100644 --- a/lib/errors/base.js +++ b/lib/errors/base.js @@ -1,16 +1,22 @@ +"use strict"; + /** * A custom error class */ function OrientDBError () { this.init.apply(this, arguments); Error.call(this); - Error.captureStackTrace(this, arguments.callee); + Error.captureStackTrace(this, this.constructor); } /** * Extend the native error class. * @type {Object} */ -OrientDBError.prototype.__proto__ = Error.prototype; +OrientDBError.prototype = Object.create(Error.prototype, { + constructor: { + value: OrientDBError + } +}); /** * The name of the error. @@ -34,9 +40,11 @@ OrientDBError.prototype.init = function (message) { OrientDBError.inherit = function (init) { var parent = this; var child = function () { return parent.apply(this, arguments); }; - var Surrogate = function () {this.constructor = child; }; - Surrogate.prototype = parent.prototype; - child.prototype = new Surrogate; + child.prototype = Object.create(parent.prototype, { + constructor: { + value: child + } + }); child.prototype.init = init; child.prototype.name = init.name; diff --git a/lib/errors/config.js b/lib/errors/config.js index 5700812..65e1bed 100644 --- a/lib/errors/config.js +++ b/lib/errors/config.js @@ -1,3 +1,5 @@ +"use strict"; + var OrientDBError = require('./base'); module.exports = OrientDBError.inherit(function ConfigError (message, data) { diff --git a/lib/errors/connection.js b/lib/errors/connection.js index 826f46a..5cd3359 100644 --- a/lib/errors/connection.js +++ b/lib/errors/connection.js @@ -1,3 +1,5 @@ +"use strict"; + var OrientDBError = require('./base'); module.exports = OrientDBError.inherit(function ConnectionError (code, message, data) { diff --git a/lib/errors/index.js b/lib/errors/index.js index f29622f..3cab68d 100644 --- a/lib/errors/index.js +++ b/lib/errors/index.js @@ -1,3 +1,5 @@ +"use strict"; + exports.Base = exports.OrientDB = exports.OrientDBError = require('./base'); exports.Connection = exports.ConnectionError = require('./connection'); exports.Protocol = exports.ProtocolError = require('./protocol'); diff --git a/lib/errors/operation.js b/lib/errors/operation.js index 2755431..4a4bd4a 100644 --- a/lib/errors/operation.js +++ b/lib/errors/operation.js @@ -1,3 +1,5 @@ +"use strict"; + var OrientDBError = require('./base'); module.exports = OrientDBError.inherit(function OperationError (message, data) { diff --git a/lib/errors/protocol.js b/lib/errors/protocol.js index d80463c..398e369 100644 --- a/lib/errors/protocol.js +++ b/lib/errors/protocol.js @@ -1,3 +1,5 @@ +"use strict"; + var OrientDBError = require('./base'); module.exports = OrientDBError.inherit(function ProtocolError (message, data) { diff --git a/lib/errors/record.js b/lib/errors/record.js index d64eb91..9182976 100644 --- a/lib/errors/record.js +++ b/lib/errors/record.js @@ -1,3 +1,5 @@ +"use strict"; + var OrientDBError = require('./base'); module.exports = OrientDBError.inherit(function RecordError (code, message, data) { diff --git a/lib/errors/request.js b/lib/errors/request.js index a408eeb..3bc0090 100644 --- a/lib/errors/request.js +++ b/lib/errors/request.js @@ -1,3 +1,5 @@ +"use strict"; + var OperationError = require('./operation'); module.exports = OperationError.inherit(function RequestError (message, data) { From 1762d2c97db2e61a223f7fa729ffacfd3ce199b2 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 24 Apr 2014 16:18:10 +0100 Subject: [PATCH 035/308] nicer extend() --- lib/utils.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 8af5ed0..ab55357 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -37,9 +37,11 @@ exports.extend = function (source) { child = function () { return parent.apply(this, arguments); }; } - var Surrogate = function () { this.constructor = child; }; - Surrogate.prototype = parent.prototype; - child.prototype = new Surrogate(); + child.prototype = Object.create(parent.prototype, { + constructor: { + value: child + } + }); var keys, key, i, limit; From a3a04bb67be5f30400ff7480bd7c02c8c526b2d7 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 25 Apr 2014 21:57:22 +0100 Subject: [PATCH 036/308] proper fix for #26 --- lib/transport/binary/protocol/deserializer.js | 5 ++-- .../binary/protocol/operations/command.js | 30 +++++++++++++++++-- lib/transport/binary/protocol/serializer.js | 8 +---- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/lib/transport/binary/protocol/deserializer.js b/lib/transport/binary/protocol/deserializer.js index f3c3cb8..3e216af 100644 --- a/lib/transport/binary/protocol/deserializer.js +++ b/lib/transport/binary/protocol/deserializer.js @@ -118,11 +118,12 @@ function deserializeValue (value) { lastChar = value.charAt(value.length - 1), values, rid; - if (firstChar === "\"") { + if (firstChar === '"') { // string - return value.substring(1, value.length - 1).replace(/\\"/g, "\"").replace(/\\\\/, "\\"); + return value.substring(1, value.length - 1).replace(/\\"/g, '"').replace(/\\\\/, '\\'); } else if (firstChar === '%' && lastChar === ';') { + // RID bag return deserializeBag(value); } else if (lastChar === 't' || lastChar === 'a') { diff --git a/lib/transport/binary/protocol/operations/command.js b/lib/transport/binary/protocol/operations/command.js index b3c7c83..334a574 100644 --- a/lib/transport/binary/protocol/operations/command.js +++ b/lib/transport/binary/protocol/operations/command.js @@ -33,7 +33,7 @@ module.exports = Operation.extend({ ); if (this.data.params) { - buffers.push(writer.writeString(serializer.serializeDocument(this.data.params))); + buffers.push(writer.writeString(serializeParams(this.data.params))); } else { buffers.push(writer.writeInt(0)); @@ -43,7 +43,7 @@ module.exports = Operation.extend({ if (this.data.params) { buffers.push( writer.writeBoolean(true), - writer.writeString(serializer.serializeDocument(this.data.params)) + writer.writeString(serializeParams(this.data.params)) ); } else { @@ -133,3 +133,29 @@ module.exports = Operation.extend({ } }); +/** + * Serialze the parameters for a query. + * + * > Note: There is a bug in OrientDB where special kinds of string values + * > need to be twice quoted *in parameters*. Hence the need for this specialist function. + * + * @param {Object} data The data to serialize. + * @return {String} The serialized data. + */ +function serializeParams (data) { + var keys = Object.keys(data.params), + total = keys.length, + c, i, key, value; + + for (i = 0; i < total; i++) { + key = keys[i]; + value = data.params[key]; + if (typeof value === 'string') { + c = value.charAt(0); + if (c === '#' || c === '<' || c === '[' || c === '(' || c === '{' || c === '0' || +c) { + data.params[key] = '"' + value + '"'; + } + } + } + return serializer.serializeDocument(data); +} \ No newline at end of file diff --git a/lib/transport/binary/protocol/serializer.js b/lib/transport/binary/protocol/serializer.js index 3b3c28e..1bb1d3e 100644 --- a/lib/transport/binary/protocol/serializer.js +++ b/lib/transport/binary/protocol/serializer.js @@ -65,13 +65,7 @@ function serializeDocument (document, isMap) { function serializeValue (value) { var type = typeof value; if (type === 'string') { - if (value[0] == +value[0]) { - // strings that start with numbers must be double quoted. - return '""' + value.replace(/\\/, "\\\\").replace(/"/g, '\\"') + '""'; - } - else { - return '"' + value.replace(/\\/, "\\\\").replace(/"/g, '\\"') + '"'; - } + return '"' + value.replace(/\\/, "\\\\").replace(/"/g, '\\"') + '"'; } else if (type === 'number') { return ~value.toString().indexOf('.') ? value + 'f' : value; From d8e33b64b726da27d64afd9fed7faf3994500fe3 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 25 Apr 2014 22:00:44 +0100 Subject: [PATCH 037/308] fix typo [ci skip] --- lib/transport/binary/protocol/operations/command.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/transport/binary/protocol/operations/command.js b/lib/transport/binary/protocol/operations/command.js index 334a574..44ede28 100644 --- a/lib/transport/binary/protocol/operations/command.js +++ b/lib/transport/binary/protocol/operations/command.js @@ -134,7 +134,7 @@ module.exports = Operation.extend({ }); /** - * Serialze the parameters for a query. + * Serialize the parameters for a query. * * > Note: There is a bug in OrientDB where special kinds of string values * > need to be twice quoted *in parameters*. Hence the need for this specialist function. From 9be12079d483253e0a663bc7a328a3e7e73b772a Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 25 Apr 2014 23:38:58 +0100 Subject: [PATCH 038/308] pull in new deserializer --- .../binary/protocol/new-deserializer.js | 507 ++++++++++++++++++ test/index.js | 9 +- .../binary/protocol/new-deserializer-test.js | 213 ++++++++ 3 files changed, 727 insertions(+), 2 deletions(-) create mode 100644 lib/transport/binary/protocol/new-deserializer.js create mode 100644 test/transport/binary/protocol/new-deserializer-test.js diff --git a/lib/transport/binary/protocol/new-deserializer.js b/lib/transport/binary/protocol/new-deserializer.js new file mode 100644 index 0000000..e384feb --- /dev/null +++ b/lib/transport/binary/protocol/new-deserializer.js @@ -0,0 +1,507 @@ +"use strict"; + +var RID = require('../../../recordid'); + +/** + * Deserialize the given record and return an object containing the values. + * + * @param {String} input The serialized record. + * @return {Object} The deserialized record. + */ +function deserialize (input) { + var record = { + '@type': 'd' + }, + chunk = eatFirstKey(input), + key, value; + if (chunk[2]) { + // this is actually a class name + record['@class'] = chunk[0]; + input = chunk[1]; + chunk = eatKey(input); + key = chunk[0]; + input = chunk[1]; + } + else { + key = chunk[0]; + input = chunk[1]; + } + + // read the first value. + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + record[key] = value; + + while (input.length) { + if (input.charAt(0) === ',') { + input = input.slice(1); + } + else { + break; + } + chunk = eatKey(input); + key = chunk[0]; + input = chunk[1]; + if (input.length) { + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + record[key] = value; + } + else { + record[key] = null; + } + } + + return record; +} + +/** + * Consume the first field key, which could be a class name. + * + * @param {String} input The input to parse. + * @return {[String, String]} The collected key, and any remaining input. + */ +function eatFirstKey (input) { + var length = input.length, + collected = '', + isClassName = false, + result, c, i; + + if (input.charAt(0) === '"') { + result = eatString(input.slice(1)); + return [result[0], result[1].slice(1)]; + } + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === '@') { + isClassName = true; + break; + } + else if (c === ':') { + break; + } + else { + collected += c; + } + } + + return [collected, input.slice(i + 1), isClassName]; +} + + +/** + * Consume a field key, which may or may not be quoted. + * + * @param {String} input The input to parse. + * @return {[String, String]} The collected key, and any remaining input. + */ +function eatKey (input) { + var length = input.length, + collected = '', + result, c, i; + + if (input.charAt(0) === '"') { + result = eatString(input.slice(1)); + return [result[0], result[1].slice(1)]; + } + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === ':') { + break; + } + else { + collected += c; + } + } + + return [collected, input.slice(i + 1)]; +} + + + +/** + * Consume a field value. + * + * @param {String} input The input to parse. + * @return {[Mixed, String]} The collected value, and any remaining input. + */ +function eatValue (input) { + var c, n; + c = input.charAt(0); + while (c === ' ') { + input = input.slice(1); + c = input.charAt(0); + } + + if (!input.length || c === ',') { + // this is a null field. + return [null, input]; + } + else if (c === '"') { + return eatString(input.slice(1)); + } + else if (c === '#') { + return eatRID(input.slice(1)); + } + else if (c === '[') { + return eatArray(input.slice(1)); + } + else if (c === '<') { + return eatSet(input.slice(1)); + } + else if (c === '{') { + return eatMap(input.slice(1)); + } + else if (c === '(') { + return eatRecord(input.slice(1)); + } + else if (c === '%') { + return eatBag(input.slice(1)); + } + else if (c === '0' || +c) { + return eatNumber(input); + } + else if (c === 'n' && input.slice(0, 4) === 'null') { + return [null, input.slice(4)]; + } + else if (c === 't' && input.slice(0, 4) === 'true') { + return [true, input.slice(4)]; + } + else if (c === 'f' && input.slice(0, 5) === 'false') { + return [false, input.slice(5)]; + } + else { + return [null, input]; + } +} + +/** + * Consume a string + * + * @param {String} input The input to parse. + * @return {[String, String]} The collected string, and any remaining input. + */ +function eatString (input) { + var length = input.length, + collected = '', + c, i; + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === '\\') { + // escape, skip to the next character + i++; + collected += input.charAt(i); + continue; + } + else if (c === '"') { + break; + } + else { + collected += c; + } + } + + return [collected, input.slice(i + 1)]; +} + +/** + * Consume a number. + * + * If the number has a suffix, consume it also and instantiate the right type, e.g. for dates + * + * @param {String} input The input to parse. + * @return {[Mixed, String]} The collected number, and any remaining input. + */ +function eatNumber (input) { + var length = input.length, + collected = '', + c, i; + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === '.' || c === '0' || +c) { + collected += c; + } + else { + break; + } + } + + collected = +collected; + input = input.slice(i); + + c = input.charAt(0); + + if (c === 'a' || c === 't') { + collected = new Date(collected); + input = input.slice(1); + } + else if (c === 'b' || c === 's' || c === 'l' || c === 'f' || c == 'd' || c === 'c') { + input = input.slice(1); + } + + return [collected, input]; +} + +/** + * Consume a Record ID. + * + * @param {String} input The input to parse. + * @return {[RID, String]} The collected record id, and any remaining input. + */ +function eatRID (input) { + var length = input.length, + collected = '', + cluster, c, i; + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (cluster === undefined && c === ':') { + cluster = +collected; + collected = ''; + } + else if (c === '0' || +c) { + collected += c; + } + else { + break; + } + } + + return [new RID({cluster: cluster, position: +collected}), input.slice(i)]; +} + + +/** + * Consume an array. + * + * @param {String} input The input to parse. + * @return {[Array, String]} The collected array, and any remaining input. + */ +function eatArray (input) { + var length = input.length, + array = [], + chunk, c; + + while (input.length) { + c = input.charAt(0); + if (c === ',') { + input = input.slice(1); + } + else if (c === ']') { + input = input.slice(1); + break; + } + chunk = eatValue(input); + array.push(chunk[0]); + input = chunk[1]; + } + return [array, input]; +} + + +/** + * Consume a set. + * + * @param {String} input The input to parse. + * @return {[Array, String]} The collected set, and any remaining input. + */ +function eatSet (input) { + var length = input.length, + set = [], + chunk, c; + + while (input.length) { + c = input.charAt(0); + if (c === ',') { + input = input.slice(1); + } + else if (c === '>') { + input = input.slice(1); + break; + } + chunk = eatValue(input); + set.push(chunk[0]); + input = chunk[1]; + } + + return [set, input]; +} + +/** + * Consume a map (object). + * + * @param {String} input The input to parse. + * @return {[Object, String]} The collected map, and any remaining input. + */ +function eatMap (input) { + var length = input.length, + map = {}, + key, value, chunk, c; + + while (input.length) { + c = input.charAt(0); + if (c === ' ') { + input = input.slice(1); + continue; + } + if (c === ',') { + input = input.slice(1); + } + else if (c === '}') { + input = input.slice(1); + break; + } + + chunk = eatKey(input); + key = chunk[0]; + input = chunk[1]; + if (input.length) { + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + map[key] = value; + } + else { + map[key] = null; + } + } + + return [map, input]; +} + +/** + * Consume an embedded record. + * + * @param {String} input The input to parse. + * @return {[Object, String]} The collected record, and any remaining input. + */ +function eatRecord (input) { + var record = {'@type': 'd'}, + chunk, c, key, value; + + while (input.length) { + c = input.charAt(0); + if (c === ' ') { + input = input.slice(1); + continue; + } + else if (c === ')') { + // empty record. + input = input.slice(1); + return [record, input]; + } + else { + break; + } + } + + chunk = eatFirstKey(input); + + if (chunk[2]) { + // this is actually a class name + record['@class'] = chunk[0]; + input = chunk[1]; + chunk = eatKey(input); + while (input.length) { + c = input.charAt(0); + if (c === ' ') { + input = input.slice(1); + continue; + } + else if (c === ')') { + // empty record. + input = input.slice(1); + return [record, input]; + } + else { + break; + } + } + key = chunk[0]; + input = chunk[1]; + } + else { + key = chunk[0]; + input = chunk[1]; + } + + // read the first value. + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + record[key] = value; + + while (input.length) { + c = input.charAt(0); + if (c === ' ') { + input = input.slice(1); + continue; + } + if (c === ',') { + input = input.slice(1); + } + else if (c === ')') { + input = input.slice(1); + break; + } + chunk = eatKey(input); + key = chunk[0]; + input = chunk[1]; + if (input.length) { + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + record[key] = value; + } + else { + record[key] = null; + } + } + + return [record, input]; +} + +/** + * Consume a RID Bag. + * + * @param {String} input The input to parse. + * @return {[Object, String]} The collected bag, and any remaining input. + */ +function eatBag (input) { + var length = input.length, + collected = '', + i, bag, chunk, c; + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === ';') { + break; + } + else { + collected += c; + } + } + input = input.slice(i + 1); + + return [collected, input]; +} + + + +exports.deserialize = deserialize; +exports.eatKey = eatKey; +exports.eatValue = eatValue; +exports.eatString = eatString; +exports.eatNumber = eatNumber; +exports.eatRID = eatRID; +exports.eatArray = eatArray; +exports.eatSet = eatSet; +exports.eatMap = eatMap; +exports.eatRecord = eatRecord; +exports.eatBag = eatBag; diff --git a/test/index.js b/test/index.js index 088c5d5..233d469 100644 --- a/test/index.js +++ b/test/index.js @@ -1,6 +1,8 @@ // test bootstrap -var Promise = require('bluebird'); +var Promise = require('bluebird'), + path = require('path'); + Promise.longStackTraces(); global.expect = require('expect.js'), @@ -9,7 +11,10 @@ global.should = require('should'); global.TEST_SERVER_CONFIG = require('./test-server.json'); -global.LIB = require('../lib'); +global.LIB_ROOT = path.resolve(__dirname, '..', 'lib'); + +global.LIB = require(LIB_ROOT); + global.TEST_SERVER = new LIB.Server({ host: TEST_SERVER_CONFIG.host, diff --git a/test/transport/binary/protocol/new-deserializer-test.js b/test/transport/binary/protocol/new-deserializer-test.js new file mode 100644 index 0000000..ec79214 --- /dev/null +++ b/test/transport/binary/protocol/new-deserializer-test.js @@ -0,0 +1,213 @@ +var deserializer = require(LIB_ROOT + '/transport/binary/protocol/new-deserializer'); + +describe("New Deserializer", function () { + it('should go fast!', function () { + var limit = 100000, + input = 'OUser@foo:123,baz:"bazz\\"za",int: 1234,true:true,false:false,null:null,date:123456a,rid:#12:10', + size = input.length * limit, + start = Date.now(); + + for (var i = 0; i < limit; i++) { + deserializer.deserialize(input); + } + + var stop = Date.now(), + total = (stop - start) / 1000; + + console.log('Done in ' + total + 's, ', (limit / total).toFixed(3), 'documents / sec', (((size / total) / 1024) / 1024).toFixed(3), ' Mb / sec') + }); + + describe('deserialize()', function () { + it('should parse a very simple record', function () { + var input = 'OUser@foo:123,baz:"bazx\\"za",int:1234,true:true,false:false,null:null,date:123456a,rid:#12:10,array:[1,2,3,4,5],twice:"\"127.0.0.1\""'; + var parsed = deserializer.deserialize(input); + console.log(parsed); + }); + }); + describe('eatString()', function () { + it('should eat a string', function () { + var input = 'this is a string"'; + var parsed = deserializer.eatString(input); + parsed[0].should.equal('this is a string'); + parsed[1].length.should.equal(0); + }); + it('should eat a string which contains escaped double quotes', function () { + var input = 'this \\"is\\" a string"'; + var parsed = deserializer.eatString(input); + parsed[0].should.equal('this "is" a string'); + parsed[1].length.should.equal(0); + }); + }); + describe('eatNumber()', function () { + it('should eat an integer', function () { + var input = '1234,'; + var parsed = deserializer.eatNumber(input); + parsed[0].should.equal(1234); + parsed[1].length.should.equal(1); + }); + it('should eat a number with a decimal point', function () { + var input = '1234.567,'; + var parsed = deserializer.eatNumber(input); + parsed[0].should.equal(1234.567); + parsed[1].length.should.equal(1); + }); + it('should eat a float', function () { + var input = '1234f'; + var parsed = deserializer.eatNumber(input); + parsed[0].should.equal(1234); + parsed[1].length.should.equal(0); + }); + it('should eat a date', function () { + var input = '1a'; + var parsed = deserializer.eatNumber(input); + parsed[0].should.eql(new Date(1)); + parsed[1].length.should.equal(0); + }); + }); + describe('eatRID()', function () { + it('should eat a record id', function () { + var input = '12:10'; + var parsed = deserializer.eatRID(input); + parsed[0].toString().should.equal('#12:10'); + parsed[1].length.should.equal(0); + }); + it('should eat a record id, with a trailing comma', function () { + var input = '12:10,'; + var parsed = deserializer.eatRID(input); + parsed[0].toString().should.equal('#12:10'); + parsed[1].length.should.equal(1); + }); + }); + describe('eatArray()', function () { + it('should eat an array', function () { + var input = '1,2,3]'; + var parsed = deserializer.eatArray(input); + parsed[0].should.eql([1,2,3]); + parsed[1].length.should.equal(0); + }); + it('should eat an empty array', function () { + var input = ']'; + var parsed = deserializer.eatArray(input); + parsed[0].should.eql([]); + parsed[1].length.should.equal(0); + }); + it('should eat an empty array, with a trailing comma', function () { + var input = '],'; + var parsed = deserializer.eatArray(input); + parsed[0].should.eql([]); + parsed[1].length.should.equal(1); + }); + }); + describe('eatSet()', function () { + it('should eat a set', function () { + var input = '1,2,3>'; + var parsed = deserializer.eatSet(input); + parsed[0].should.eql([1,2,3]); + parsed[1].length.should.equal(0); + }); + it('should eat an empty set', function () { + var input = '>'; + var parsed = deserializer.eatSet(input); + parsed[0].should.eql([]); + parsed[1].length.should.equal(0); + }); + it('should eat an empty set, with a trailing comma', function () { + var input = '>,'; + var parsed = deserializer.eatSet(input); + parsed[0].should.eql([]); + parsed[1].length.should.equal(1); + }); + }); + describe('eatMap()', function () { + it('should eat a map', function () { + var input = '"key":"value","key2":2,"null": null}'; + var parsed = deserializer.eatMap(input); + parsed[0].should.eql({key: 'value', key2: 2, null: null}); + parsed[1].length.should.equal(0); + }); + it('should eat an empty map', function () { + var input = '}'; + var parsed = deserializer.eatMap(input); + parsed[0].should.eql({}); + parsed[1].length.should.equal(0); + }); + it('should eat an empty map, with a trailing comma', function () { + var input = '},'; + var parsed = deserializer.eatMap(input); + parsed[0].should.eql({}); + parsed[1].length.should.equal(1); + }); + }); + describe('eatRecord()', function () { + it('should eat a record', function () { + var input = 'key:"value",key2:2,null:)'; + var parsed = deserializer.eatRecord(input); + parsed[0].should.eql({'@type': 'd', key: 'value', key2: 2, null: null}); + parsed[1].length.should.equal(0); + }); + it('should eat an empty record', function () { + var input = ')'; + var parsed = deserializer.eatRecord(input); + parsed[0].should.eql({'@type': 'd'}); + parsed[1].length.should.equal(0); + }); + it('should eat an empty record with a class name', function () { + var input = 'foo@)'; + var parsed = deserializer.eatRecord(input); + parsed[0].should.eql({ + '@type': 'd', + '@class': 'foo' + }); + parsed[1].length.should.equal(0); + }); + it('should eat an empty record, with a trailing comma', function () { + var input = '),'; + var parsed = deserializer.eatRecord(input); + parsed[0].should.eql({'@type': 'd'}); + parsed[1].length.should.equal(1); + }); + }); + describe('eatBag()', function () { + it('should eat a RID bag', function () { + var input = 'aGVsbG8gd29ybGQ=;'; + var parsed = deserializer.eatBag(input); + parsed[0].should.eql('aGVsbG8gd29ybGQ='); + parsed[1].length.should.equal(0); + }); + it('should eat a RID bag with a trailing comma', function () { + var input = 'aGVsbG8gd29ybGQ=;,'; + var parsed = deserializer.eatBag(input); + parsed[0].should.eql('aGVsbG8gd29ybGQ='); + parsed[1].length.should.equal(1); + }); + }); + describe('eatKey', function () { + it('should eat an unquoted key', function () { + var input = 'mykey:123'; + var parsed = deserializer.eatKey(input); + parsed[0].should.equal('mykey'); + parsed[1].length.should.equal(3); + }); + + it('should eat a quoted key', function () { + var input = '"mykey":123'; + var parsed = deserializer.eatKey(input); + parsed[0].should.equal('mykey'); + parsed[1].length.should.equal(3); + }); + }); + describe('eatValue', function () { + it('should eat a null value', function () { + var input = ','; + var parsed = deserializer.eatValue(input); + expect(parsed[0]).to.equal(null); + parsed[1].length.should.equal(1); + }); + it('should eat a string value', function () { + var input = '"foo bar"'; + var parsed = deserializer.eatValue(input); + parsed[0].should.equal('foo bar'); + parsed[1].length.should.equal(0); + }); + }); +}); \ No newline at end of file From e7f14767b171e9148ddb23300ac110e03d37a468 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sat, 26 Apr 2014 00:37:52 +0100 Subject: [PATCH 039/308] integrate the new deserializer --- lib/transport/binary/protocol/deserializer.js | 638 +++++++++++++----- .../binary/protocol/new-deserializer.js | 507 -------------- lib/transport/binary/protocol/operation.js | 6 +- .../binary/protocol/operations/command.js | 1 - .../binary/protocol/operations/record-load.js | 4 +- ...erializer-test.js => deserializer-test.js} | 21 +- 6 files changed, 496 insertions(+), 681 deletions(-) delete mode 100644 lib/transport/binary/protocol/new-deserializer.js rename test/transport/binary/protocol/{new-deserializer-test.js => deserializer-test.js} (94%) diff --git a/lib/transport/binary/protocol/deserializer.js b/lib/transport/binary/protocol/deserializer.js index 3e216af..3e3bbb9 100644 --- a/lib/transport/binary/protocol/deserializer.js +++ b/lib/transport/binary/protocol/deserializer.js @@ -1,227 +1,537 @@ "use strict"; -var RecordID = require('../../../recordid'), - Long = require('../../../long').Long; - +var RID = require('../../../recordid'); /** - * A map of start delimiters to their end delimiters. - * @type {Object} - */ -var START_DELIMITERS = { - '[': ']', - '{': '}', - '(': ')', - '<': '>' -}; - -/** - * A map of end delimiters to their start delimiters. - * @type {Object} - */ -var END_DELIMITERS = { - ']': '[', - '}': '{', - ')': '(', - '>': '<' -}; - -/** - * Deserialize a given serialized document. + * Deserialize the given record and return an object containing the values. * - * @param {String} serialized The serialized document. - * @param {Object} document The optional document to apply the unserialized values to. - * @param {Boolean} isMap If this is a map of values - * @return {Object} The deserialized document + * @param {String} input The serialized record. + * @return {Object} The deserialized record. */ -function deserializeDocument (serialized, document, isMap) { - if (serialized == null) { - return serialized; +function deserialize (input) { + var record = {'@type': 'd'}, + chunk, key, value; + if (!input) { + return null; + } + chunk = eatFirstKey(input); + if (chunk[2]) { + // this is actually a class name + record['@class'] = chunk[0]; + input = chunk[1]; + chunk = eatKey(input); + key = chunk[0]; + input = chunk[1]; + } + else { + key = chunk[0]; + input = chunk[1]; } - serialized = serialized.trim(); - document = document || {}; - var classIndex = serialized.indexOf("@"), - indexOfColon = serialized.indexOf(":"), - fieldIndex, field, commaIndex, value; + // read the first value. + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + record[key] = value; - if (~classIndex && (indexOfColon > classIndex || !~indexOfColon)) { - document['@class'] = serialized.substr(0, classIndex); - serialized = serialized.substr(classIndex + 1); + while (input.length) { + if (input.charAt(0) === ',') { + input = input.slice(1); + } + else { + break; + } + chunk = eatKey(input); + key = chunk[0]; + input = chunk[1]; + if (input.length) { + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + record[key] = value; + } + else { + record[key] = null; + } } - if (!isMap) { - document['@type'] = 'd'; + return record; +} + +/** + * Consume the first field key, which could be a class name. + * + * @param {String} input The input to parse. + * @return {[String, String]} The collected key, and any remaining input. + */ +function eatFirstKey (input) { + var length = input.length, + collected = '', + isClassName = false, + result, c, i; + + if (input.charAt(0) === '"') { + result = eatString(input.slice(1)); + return [result[0], result[1].slice(1)]; } - while (~(fieldIndex = serialized.indexOf(':'))) { - field = serialized.substr(0, fieldIndex); - serialized = serialized.substr(fieldIndex + 1); - if (field.charAt(0) === "\"" && field.charAt(field.length - 1) === "\"") { - field = field.substring(1, field.length - 1); + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === '@') { + isClassName = true; + break; + } + else if (c === ':') { + break; + } + else { + collected += c; } - commaIndex = findCommaIndex(serialized); - value = serialized.substr(0, commaIndex); - serialized = serialized.substr(commaIndex + 1); - value = deserializeValue(value); - document[field] = value; } - return document; + return [collected, input.slice(i + 1), isClassName]; } + /** - * Deserialize a bag of values. + * Consume a field key, which may or may not be quoted. * - * @param {String} value The serialized bag. - * @return {RecordID[]} The unserialized bag. + * @param {String} input The input to parse. + * @return {[String, String]} The collected key, and any remaining input. */ -function deserializeBag (value) { - var buffer = new Buffer(value.slice(1,-1), 'base64'), - offset = 0, - status = buffer.readUInt8(offset++), - total = buffer.readUInt32BE(offset), - content = [], - i, cluster, position; - - offset += 4; - for (i = 0; i < total; i++) { - cluster = buffer.readInt16BE(offset); - offset += 2; - position = Long.fromBits(buffer.readUInt32BE(offset + 4), buffer.readInt32BE(offset)).toNumber(); - offset += 8; - content.push(new RecordID({ - cluster: cluster, - position: position - })); - } - return content; +function eatKey (input) { + var length = input.length, + collected = '', + result, c, i; + + if (input.charAt(0) === '"') { + result = eatString(input.slice(1)); + return [result[0], result[1].slice(1)]; + } + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === ':') { + break; + } + else { + collected += c; + } + } + + return [collected, input.slice(i + 1)]; } + + /** - * Deserialize a given value into the right type. + * Consume a field value. * - * @param {String} value The serialized value to unserialize. - * @return {Mixed} The deserialized value. + * @param {String} input The input to parse. + * @return {[Mixed, String]} The collected value, and any remaining input. */ -function deserializeValue (value) { - value = (''+value).trim(); - if(value === '') { - return null; +function eatValue (input) { + var c, n; + c = input.charAt(0); + while (c === ' ' && input.length) { + input = input.slice(1); + c = input.charAt(0); } - if (value === 'true' || value === 'false') { - return value === 'true'; + if (!input.length || c === ',') { + // this is a null field. + return [null, input]; } - - var firstChar = value.charAt(0), - lastChar = value.charAt(value.length - 1), - values, rid; - - if (firstChar === '"') { - // string - return value.substring(1, value.length - 1).replace(/\\"/g, '"').replace(/\\\\/, '\\'); + else if (c === '"') { + return eatString(input.slice(1)); } - else if (firstChar === '%' && lastChar === ';') { - // RID bag - return deserializeBag(value); + else if (c === '#') { + return eatRID(input.slice(1)); } - else if (lastChar === 't' || lastChar === 'a') { - // date - return new Date(parseInt(value.substring(0, value.length))); + else if (c === '[') { + return eatArray(input.slice(1)); } - else if (firstChar === '(') { - // object / document - return deserializeDocument(value.substring(1, value.length - 1)); + else if (c === '<') { + return eatSet(input.slice(1)); } - if (firstChar === '{') { - // map - return deserializeDocument(value.substring(1, value.length - 1), {}, true); + else if (c === '{') { + return eatMap(input.slice(1)); } - else if (firstChar === '[' || firstChar === '<') { - //process Set <...> like List [...] - values = splitList(value.substring(1, value.length - 1)); - for (var i = 0, length = values.length; i < length; i++) { - values[i] = deserializeValue(values[i]); - } - return values; + else if (c === '(') { + return eatRecord(input.slice(1)); + } + else if (c === '%') { + return eatBag(input.slice(1)); } - else if (lastChar === 'b') { - // byte - return String.fromCharCode(+(value.substring(0, value.length))); + else if (c === '_') { + return eatBinary(input.slice(1)); } - else if (lastChar === 'l' || lastChar === 's' || lastChar === 'c') { - // integer - return parseInt(value.substring(0, value.length), 10); + else if (c === '-' || c === '0' || +c) { + return eatNumber(input); } - else if (lastChar === 'f' || lastChar === 'd') { - // float / decimal - return parseFloat(value.substring(0, value.length)); + else if (c === 'n' && input.slice(0, 4) === 'null') { + return [null, input.slice(4)]; } - else if (+value == value) { - // integer - return +value; + else if (c === 't' && input.slice(0, 4) === 'true') { + return [true, input.slice(4)]; } - else if (value && (rid = RecordID.parse(value))) { - return rid; + else if (c === 'f' && input.slice(0, 5) === 'false') { + return [false, input.slice(5)]; } else { - return value; + return [null, input]; + } +} + +/** + * Consume a string + * + * @param {String} input The input to parse. + * @return {[String, String]} The collected string, and any remaining input. + */ +function eatString (input) { + var length = input.length, + collected = '', + c, i; + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === '\\') { + // escape, skip to the next character + i++; + collected += input.charAt(i); + continue; + } + else if (c === '"') { + break; + } + else { + collected += c; + } + } + + return [collected, input.slice(i + 1)]; +} + +/** + * Consume a number. + * + * If the number has a suffix, consume it also and instantiate the right type, e.g. for dates + * + * @param {String} input The input to parse. + * @return {[Mixed, String]} The collected number, and any remaining input. + */ +function eatNumber (input) { + var length = input.length, + collected = '', + c, i; + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === '-' || c === '.' || c === '0' || +c) { + collected += c; + } + else { + break; + } + } + + collected = +collected; + input = input.slice(i); + + c = input.charAt(0); + + if (c === 'a' || c === 't') { + collected = new Date(collected); + input = input.slice(1); + } + else if (c === 'b' || c === 's' || c === 'l' || c === 'f' || c == 'd' || c === 'c') { + input = input.slice(1); + } + + return [collected, input]; +} + +/** + * Consume a Record ID. + * + * @param {String} input The input to parse. + * @return {[RID, String]} The collected record id, and any remaining input. + */ +function eatRID (input) { + var length = input.length, + collected = '', + cluster, c, i; + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (cluster === undefined && c === ':') { + cluster = +collected; + collected = ''; + } + else if (c === '0' || +c) { + collected += c; + } + else { + break; + } + } + + return [new RID({cluster: cluster, position: +collected}), input.slice(i)]; +} + + +/** + * Consume an array. + * + * @param {String} input The input to parse. + * @return {[Array, String]} The collected array, and any remaining input. + */ +function eatArray (input) { + var length = input.length, + array = [], + chunk, c; + + while (input.length) { + c = input.charAt(0); + if (c === ',') { + input = input.slice(1); + } + else if (c === ']') { + input = input.slice(1); + break; + } + chunk = eatValue(input); + array.push(chunk[0]); + input = chunk[1]; } + return [array, input]; } /** - * Find the index of the first comma in the given serialized input, - * disregarding values that appear within delimiters. + * Consume a set. + * + * @param {String} input The input to parse. + * @return {[Array, String]} The collected set, and any remaining input. + */ +function eatSet (input) { + var length = input.length, + set = [], + chunk, c; + + while (input.length) { + c = input.charAt(0); + if (c === ',') { + input = input.slice(1); + } + else if (c === '>') { + input = input.slice(1); + break; + } + chunk = eatValue(input); + set.push(chunk[0]); + input = chunk[1]; + } + + return [set, input]; +} + +/** + * Consume a map (object). + * + * @param {String} input The input to parse. + * @return {[Object, String]} The collected map, and any remaining input. + */ +function eatMap (input) { + var length = input.length, + map = {}, + key, value, chunk, c; + + while (input.length) { + c = input.charAt(0); + if (c === ' ') { + input = input.slice(1); + continue; + } + if (c === ',') { + input = input.slice(1); + } + else if (c === '}') { + input = input.slice(1); + break; + } + + chunk = eatKey(input); + key = chunk[0]; + input = chunk[1]; + if (input.length) { + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + map[key] = value; + } + else { + map[key] = null; + } + } + + return [map, input]; +} + +/** + * Consume an embedded record. * - * @param {String} serialized The serialized value to inspect. - * @return {Integer} The index of the first comma. + * @param {String} input The input to parse. + * @return {[Object, String]} The collected record, and any remaining input. */ -function findCommaIndex (serialized) { - var delimiters = [], - current, prev, i, length; - for (i = 0, length = serialized.length; i < length; i++) { - current = serialized[i]; - prev = delimiters[delimiters.length - 1]; - if (current === "," && delimiters.length === 0) { - return i; +function eatRecord (input) { + var record = {'@type': 'd'}, + chunk, c, key, value; + + while (input.length) { + c = input.charAt(0); + if (c === ' ') { + input = input.slice(1); + continue; + } + else if (c === ')') { + // empty record. + input = input.slice(1); + return [record, input]; + } + else { + break; + } + } + + chunk = eatFirstKey(input); + + if (chunk[2]) { + // this is actually a class name + record['@class'] = chunk[0]; + input = chunk[1]; + chunk = eatKey(input); + while (input.length) { + c = input.charAt(0); + if (c === ' ') { + input = input.slice(1); + continue; + } + else if (c === ')') { + // empty record. + input = input.slice(1); + return [record, input]; + } + else { + break; + } + } + key = chunk[0]; + input = chunk[1]; + } + else { + key = chunk[0]; + input = chunk[1]; + } + + // read the first value. + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + record[key] = value; + + while (input.length) { + c = input.charAt(0); + if (c === ' ') { + input = input.slice(1); + continue; } - else if (START_DELIMITERS[current] && prev !== "\"") { - delimiters.push(current); + if (c === ',') { + input = input.slice(1); } - else if (END_DELIMITERS[current] && prev !== "\"" && (current === START_DELIMITERS[prev] || current === '"')) { - delimiters.pop(); + else if (c === ')') { + input = input.slice(1); + break; } - else if (current === "\"" && prev === "\"" && i > 0 && serialized[i - 1] !== "\\") { - delimiters.pop(); + chunk = eatKey(input); + key = chunk[0]; + input = chunk[1]; + if (input.length) { + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + record[key] = value; } - else if (current === "\"" && prev !== "\"") { - delimiters.push(current); + else { + record[key] = null; } } - return serialized.length; + + return [record, input]; } /** - * Split a serialized list into items that can then be parsed individually. - * @param {String} value The list value to split. - * @return {String[]} The split items, ready to be parsed + * Consume a RID Bag. + * + * @param {String} input The input to parse. + * @return {[Object, String]} The collected bag, and any remaining input. */ -function splitList (value) { - var result = [], - commaAt; - while (value.length) { - commaAt = findCommaIndex(value); - result.push(value.substr(0, commaAt)); - value = value.substr(commaAt + 1); - } - return result; +function eatBag (input) { + var length = input.length, + collected = '', + i, bag, chunk, c; + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === ';') { + break; + } + else { + collected += c; + } + } + input = input.slice(i + 1); + + return [collected, input]; } -// export the public methods +/** + * Consume a binary buffer. + * + * @param {String} input The input to parse. + * @return {[Object, String]} The collected bag, and any remaining input. + */ +function eatBinary (input) { + var length = input.length, + collected = '', + i, bag, chunk, c; + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === '_' || c === ',' || c === ')' || c === '>' || c === '}' || c === ']') { + break; + } + else { + collected += c; + } + } + input = input.slice(i + 1); + + return [collected, input]; +} + -exports.deserializeValue = deserializeValue; -exports.deserializeDocument = deserializeDocument; \ No newline at end of file +exports.deserialize = deserialize; +exports.eatKey = eatKey; +exports.eatValue = eatValue; +exports.eatString = eatString; +exports.eatNumber = eatNumber; +exports.eatRID = eatRID; +exports.eatArray = eatArray; +exports.eatSet = eatSet; +exports.eatMap = eatMap; +exports.eatRecord = eatRecord; +exports.eatBag = eatBag; diff --git a/lib/transport/binary/protocol/new-deserializer.js b/lib/transport/binary/protocol/new-deserializer.js deleted file mode 100644 index e384feb..0000000 --- a/lib/transport/binary/protocol/new-deserializer.js +++ /dev/null @@ -1,507 +0,0 @@ -"use strict"; - -var RID = require('../../../recordid'); - -/** - * Deserialize the given record and return an object containing the values. - * - * @param {String} input The serialized record. - * @return {Object} The deserialized record. - */ -function deserialize (input) { - var record = { - '@type': 'd' - }, - chunk = eatFirstKey(input), - key, value; - if (chunk[2]) { - // this is actually a class name - record['@class'] = chunk[0]; - input = chunk[1]; - chunk = eatKey(input); - key = chunk[0]; - input = chunk[1]; - } - else { - key = chunk[0]; - input = chunk[1]; - } - - // read the first value. - chunk = eatValue(input); - value = chunk[0]; - input = chunk[1]; - record[key] = value; - - while (input.length) { - if (input.charAt(0) === ',') { - input = input.slice(1); - } - else { - break; - } - chunk = eatKey(input); - key = chunk[0]; - input = chunk[1]; - if (input.length) { - chunk = eatValue(input); - value = chunk[0]; - input = chunk[1]; - record[key] = value; - } - else { - record[key] = null; - } - } - - return record; -} - -/** - * Consume the first field key, which could be a class name. - * - * @param {String} input The input to parse. - * @return {[String, String]} The collected key, and any remaining input. - */ -function eatFirstKey (input) { - var length = input.length, - collected = '', - isClassName = false, - result, c, i; - - if (input.charAt(0) === '"') { - result = eatString(input.slice(1)); - return [result[0], result[1].slice(1)]; - } - - for (i = 0; i < length; i++) { - c = input.charAt(i); - if (c === '@') { - isClassName = true; - break; - } - else if (c === ':') { - break; - } - else { - collected += c; - } - } - - return [collected, input.slice(i + 1), isClassName]; -} - - -/** - * Consume a field key, which may or may not be quoted. - * - * @param {String} input The input to parse. - * @return {[String, String]} The collected key, and any remaining input. - */ -function eatKey (input) { - var length = input.length, - collected = '', - result, c, i; - - if (input.charAt(0) === '"') { - result = eatString(input.slice(1)); - return [result[0], result[1].slice(1)]; - } - - for (i = 0; i < length; i++) { - c = input.charAt(i); - if (c === ':') { - break; - } - else { - collected += c; - } - } - - return [collected, input.slice(i + 1)]; -} - - - -/** - * Consume a field value. - * - * @param {String} input The input to parse. - * @return {[Mixed, String]} The collected value, and any remaining input. - */ -function eatValue (input) { - var c, n; - c = input.charAt(0); - while (c === ' ') { - input = input.slice(1); - c = input.charAt(0); - } - - if (!input.length || c === ',') { - // this is a null field. - return [null, input]; - } - else if (c === '"') { - return eatString(input.slice(1)); - } - else if (c === '#') { - return eatRID(input.slice(1)); - } - else if (c === '[') { - return eatArray(input.slice(1)); - } - else if (c === '<') { - return eatSet(input.slice(1)); - } - else if (c === '{') { - return eatMap(input.slice(1)); - } - else if (c === '(') { - return eatRecord(input.slice(1)); - } - else if (c === '%') { - return eatBag(input.slice(1)); - } - else if (c === '0' || +c) { - return eatNumber(input); - } - else if (c === 'n' && input.slice(0, 4) === 'null') { - return [null, input.slice(4)]; - } - else if (c === 't' && input.slice(0, 4) === 'true') { - return [true, input.slice(4)]; - } - else if (c === 'f' && input.slice(0, 5) === 'false') { - return [false, input.slice(5)]; - } - else { - return [null, input]; - } -} - -/** - * Consume a string - * - * @param {String} input The input to parse. - * @return {[String, String]} The collected string, and any remaining input. - */ -function eatString (input) { - var length = input.length, - collected = '', - c, i; - - for (i = 0; i < length; i++) { - c = input.charAt(i); - if (c === '\\') { - // escape, skip to the next character - i++; - collected += input.charAt(i); - continue; - } - else if (c === '"') { - break; - } - else { - collected += c; - } - } - - return [collected, input.slice(i + 1)]; -} - -/** - * Consume a number. - * - * If the number has a suffix, consume it also and instantiate the right type, e.g. for dates - * - * @param {String} input The input to parse. - * @return {[Mixed, String]} The collected number, and any remaining input. - */ -function eatNumber (input) { - var length = input.length, - collected = '', - c, i; - - for (i = 0; i < length; i++) { - c = input.charAt(i); - if (c === '.' || c === '0' || +c) { - collected += c; - } - else { - break; - } - } - - collected = +collected; - input = input.slice(i); - - c = input.charAt(0); - - if (c === 'a' || c === 't') { - collected = new Date(collected); - input = input.slice(1); - } - else if (c === 'b' || c === 's' || c === 'l' || c === 'f' || c == 'd' || c === 'c') { - input = input.slice(1); - } - - return [collected, input]; -} - -/** - * Consume a Record ID. - * - * @param {String} input The input to parse. - * @return {[RID, String]} The collected record id, and any remaining input. - */ -function eatRID (input) { - var length = input.length, - collected = '', - cluster, c, i; - - for (i = 0; i < length; i++) { - c = input.charAt(i); - if (cluster === undefined && c === ':') { - cluster = +collected; - collected = ''; - } - else if (c === '0' || +c) { - collected += c; - } - else { - break; - } - } - - return [new RID({cluster: cluster, position: +collected}), input.slice(i)]; -} - - -/** - * Consume an array. - * - * @param {String} input The input to parse. - * @return {[Array, String]} The collected array, and any remaining input. - */ -function eatArray (input) { - var length = input.length, - array = [], - chunk, c; - - while (input.length) { - c = input.charAt(0); - if (c === ',') { - input = input.slice(1); - } - else if (c === ']') { - input = input.slice(1); - break; - } - chunk = eatValue(input); - array.push(chunk[0]); - input = chunk[1]; - } - return [array, input]; -} - - -/** - * Consume a set. - * - * @param {String} input The input to parse. - * @return {[Array, String]} The collected set, and any remaining input. - */ -function eatSet (input) { - var length = input.length, - set = [], - chunk, c; - - while (input.length) { - c = input.charAt(0); - if (c === ',') { - input = input.slice(1); - } - else if (c === '>') { - input = input.slice(1); - break; - } - chunk = eatValue(input); - set.push(chunk[0]); - input = chunk[1]; - } - - return [set, input]; -} - -/** - * Consume a map (object). - * - * @param {String} input The input to parse. - * @return {[Object, String]} The collected map, and any remaining input. - */ -function eatMap (input) { - var length = input.length, - map = {}, - key, value, chunk, c; - - while (input.length) { - c = input.charAt(0); - if (c === ' ') { - input = input.slice(1); - continue; - } - if (c === ',') { - input = input.slice(1); - } - else if (c === '}') { - input = input.slice(1); - break; - } - - chunk = eatKey(input); - key = chunk[0]; - input = chunk[1]; - if (input.length) { - chunk = eatValue(input); - value = chunk[0]; - input = chunk[1]; - map[key] = value; - } - else { - map[key] = null; - } - } - - return [map, input]; -} - -/** - * Consume an embedded record. - * - * @param {String} input The input to parse. - * @return {[Object, String]} The collected record, and any remaining input. - */ -function eatRecord (input) { - var record = {'@type': 'd'}, - chunk, c, key, value; - - while (input.length) { - c = input.charAt(0); - if (c === ' ') { - input = input.slice(1); - continue; - } - else if (c === ')') { - // empty record. - input = input.slice(1); - return [record, input]; - } - else { - break; - } - } - - chunk = eatFirstKey(input); - - if (chunk[2]) { - // this is actually a class name - record['@class'] = chunk[0]; - input = chunk[1]; - chunk = eatKey(input); - while (input.length) { - c = input.charAt(0); - if (c === ' ') { - input = input.slice(1); - continue; - } - else if (c === ')') { - // empty record. - input = input.slice(1); - return [record, input]; - } - else { - break; - } - } - key = chunk[0]; - input = chunk[1]; - } - else { - key = chunk[0]; - input = chunk[1]; - } - - // read the first value. - chunk = eatValue(input); - value = chunk[0]; - input = chunk[1]; - record[key] = value; - - while (input.length) { - c = input.charAt(0); - if (c === ' ') { - input = input.slice(1); - continue; - } - if (c === ',') { - input = input.slice(1); - } - else if (c === ')') { - input = input.slice(1); - break; - } - chunk = eatKey(input); - key = chunk[0]; - input = chunk[1]; - if (input.length) { - chunk = eatValue(input); - value = chunk[0]; - input = chunk[1]; - record[key] = value; - } - else { - record[key] = null; - } - } - - return [record, input]; -} - -/** - * Consume a RID Bag. - * - * @param {String} input The input to parse. - * @return {[Object, String]} The collected bag, and any remaining input. - */ -function eatBag (input) { - var length = input.length, - collected = '', - i, bag, chunk, c; - - for (i = 0; i < length; i++) { - c = input.charAt(i); - if (c === ';') { - break; - } - else { - collected += c; - } - } - input = input.slice(i + 1); - - return [collected, input]; -} - - - -exports.deserialize = deserialize; -exports.eatKey = eatKey; -exports.eatValue = eatValue; -exports.eatString = eatString; -exports.eatNumber = eatNumber; -exports.eatRID = eatRID; -exports.eatArray = eatArray; -exports.eatSet = eatSet; -exports.eatMap = eatMap; -exports.eatRecord = eatRecord; -exports.eatBag = eatBag; diff --git a/lib/transport/binary/protocol/operation.js b/lib/transport/binary/protocol/operation.js index 0116a5e..db5c6e5 100644 --- a/lib/transport/binary/protocol/operation.js +++ b/lib/transport/binary/protocol/operation.js @@ -276,7 +276,7 @@ Operation.prototype.readError = function (fieldName, reader) { */ Operation.prototype.readObject = function (fieldName, reader) { this.readOps.push(['String', [fieldName, function (data, fieldName) { - data[fieldName] = deserializer.deserializeValue('{'+data[fieldName]+'}'); + data[fieldName] = deserializer.deserialize(data[fieldName]); if (reader) { reader.call(this, data, fieldName); } @@ -664,7 +664,7 @@ Operation.prototype.parseRecord = function (buffer, offset, context, fieldName, .readLong('position') .readInt('version') .readString('value', function (data, key) { - data[key] = deserializer.deserializeDocument(data[key]); + data[key] = deserializer.deserialize(data[key]); this.stack.pop(); this.readOps.push(function () { if (reader) { @@ -838,7 +838,7 @@ Operation.prototype.parsePushedData = function (buffer, offset, context, fieldNa asString = buffer.toString('utf8', offset, offset + length); switch (asString.charAt(0)) { case 'R': - context[fieldName] = deserializer.deserializeValue('{' + asString.slice(1) + '}'); + context[fieldName] = deserializer.deserialize(asString.slice(1)); break; default: console.log('unsupported pushed data format: ' + asString); diff --git a/lib/transport/binary/protocol/operations/command.js b/lib/transport/binary/protocol/operations/command.js index 44ede28..73d589d 100644 --- a/lib/transport/binary/protocol/operations/command.js +++ b/lib/transport/binary/protocol/operations/command.js @@ -3,7 +3,6 @@ var Operation = require('../operation'), constants = require('../constants'), serializer = require('../serializer'), - deserializer = require('../deserializer'), writer = require('../writer'); module.exports = Operation.extend({ diff --git a/lib/transport/binary/protocol/operations/record-load.js b/lib/transport/binary/protocol/operations/record-load.js index d4e6fc0..93123d9 100644 --- a/lib/transport/binary/protocol/operations/record-load.js +++ b/lib/transport/binary/protocol/operations/record-load.js @@ -78,7 +78,7 @@ module.exports = Operation.extend({ data.cluster = this.data.cluster; data.position = this.data.position; if (data[fieldName] === 'd') { - data.content = deserializer.deserializeValue('{' + data.content + '}'); + data.content = deserializer.deserialize(data.content); } this.stack.pop(); this.readPayload(records, ender); @@ -104,7 +104,7 @@ module.exports = Operation.extend({ .readInt('version') .readString('content', function (data, fieldName) { if (data.type === 'd') { - data.content = deserializer.deserializeValue('{' + data.content + '}'); + data.content = deserializer.deserialize(data.content); } this.stack.pop(); this.readPayload(records, ender); diff --git a/test/transport/binary/protocol/new-deserializer-test.js b/test/transport/binary/protocol/deserializer-test.js similarity index 94% rename from test/transport/binary/protocol/new-deserializer-test.js rename to test/transport/binary/protocol/deserializer-test.js index ec79214..d844f76 100644 --- a/test/transport/binary/protocol/new-deserializer-test.js +++ b/test/transport/binary/protocol/deserializer-test.js @@ -1,6 +1,6 @@ -var deserializer = require(LIB_ROOT + '/transport/binary/protocol/new-deserializer'); +var deserializer = require(LIB_ROOT + '/transport/binary/protocol/deserializer'); -describe("New Deserializer", function () { +describe("Deserializer", function () { it('should go fast!', function () { var limit = 100000, input = 'OUser@foo:123,baz:"bazz\\"za",int: 1234,true:true,false:false,null:null,date:123456a,rid:#12:10', @@ -19,9 +19,22 @@ describe("New Deserializer", function () { describe('deserialize()', function () { it('should parse a very simple record', function () { - var input = 'OUser@foo:123,baz:"bazx\\"za",int:1234,true:true,false:false,null:null,date:123456a,rid:#12:10,array:[1,2,3,4,5],twice:"\"127.0.0.1\""'; + var input = 'OUser@foo:123,baz:"bazx\\"za",int:1234,true:true,false:false,null:null,date:123456a,rid:#12:10,array:[1,2,3,4,5],twice:"\\"127.0.0.1\\""'; var parsed = deserializer.deserialize(input); - console.log(parsed); + parsed.should.eql({ + '@type': 'd', + '@class': 'OUser', + foo: 123, + baz: 'bazx"za', + int: 1234, + true: true, + false: false, + null: null, + date: new Date(123456), + rid: new LIB.RID('#12:10'), + array: [1, 2, 3, 4, 5], + twice: '"127.0.0.1"' + }); }); }); describe('eatString()', function () { From f8c98b7215d9e4b84dbaf616cd88e2132887f87b Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sat, 26 Apr 2014 01:21:00 +0100 Subject: [PATCH 040/308] eatBinary() should return a buffer --- lib/transport/binary/protocol/deserializer.js | 3 ++- .../binary/protocol/deserializer-test.js | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/transport/binary/protocol/deserializer.js b/lib/transport/binary/protocol/deserializer.js index 3e3bbb9..03ba761 100644 --- a/lib/transport/binary/protocol/deserializer.js +++ b/lib/transport/binary/protocol/deserializer.js @@ -519,7 +519,7 @@ function eatBinary (input) { } input = input.slice(i + 1); - return [collected, input]; + return [new Buffer(collected, 'base64'), input]; } @@ -535,3 +535,4 @@ exports.eatSet = eatSet; exports.eatMap = eatMap; exports.eatRecord = eatRecord; exports.eatBag = eatBag; +exports.eatBinary = eatBinary; \ No newline at end of file diff --git a/test/transport/binary/protocol/deserializer-test.js b/test/transport/binary/protocol/deserializer-test.js index d844f76..b627b3a 100644 --- a/test/transport/binary/protocol/deserializer-test.js +++ b/test/transport/binary/protocol/deserializer-test.js @@ -194,6 +194,22 @@ describe("Deserializer", function () { parsed[1].length.should.equal(1); }); }); + describe('eatBinary()', function () { + it('should eat a binary field', function () { + var input = new Buffer('Hello World', 'utf8'); + var parsed = deserializer.eatBinary(input.toString('base64') + '_'); + parsed[0].should.be.instanceOf(Buffer); + parsed[0].should.eql(input); + parsed[1].length.should.equal(0); + }); + it('should eat an empty binary field', function () { + var input = new Buffer('', 'utf8'); + var parsed = deserializer.eatBinary(input.toString('base64') + '_'); + parsed[0].should.be.instanceOf(Buffer); + parsed[0].should.eql(input); + parsed[1].length.should.equal(0); + }); + }); describe('eatKey', function () { it('should eat an unquoted key', function () { var input = 'mykey:123'; From 2160cff766646e2216a45304a713d4e34de998f4 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 27 Apr 2014 19:13:57 +0100 Subject: [PATCH 041/308] allow overriding the default db storage type --- test/index.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/test/index.js b/test/index.js index 233d469..d0ac662 100644 --- a/test/index.js +++ b/test/index.js @@ -45,13 +45,14 @@ global.DELETE_REST_DB = deleteTestDb.bind(null, REST_SERVER); -function createTestDb(server, context, name) { - return server.exists(name, 'memory') +function createTestDb(server, context, name, type) { + type = type || 'memory'; + return server.exists(name, type) .then(function (exists) { if (exists) { return server.delete({ name: name, - storage: 'memory' + storage: type }); } else { @@ -62,7 +63,7 @@ function createTestDb(server, context, name) { return server.create({ name: name, type: 'graph', - storage: 'memory' + storage: type }); }) .then(function (db) { @@ -71,13 +72,14 @@ function createTestDb(server, context, name) { }); } -function deleteTestDb (server, name) { - return server.exists(name, 'memory') +function deleteTestDb (server, name, type) { + type = type || 'memory'; + return server.exists(name, type) .then(function (exists) { if (exists) { return server.delete({ name: name, - storage: 'memory' + storage: type }); } else { From 901b421590550eeddb8bd57cbddd5ec48cb75a2e Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 27 Apr 2014 19:14:34 +0100 Subject: [PATCH 042/308] add a new RIDBag class --- lib/bag.js | 188 ++++++++++++++++++++++++++++++++++++++++++ test/core/bag-test.js | 151 +++++++++++++++++++++++++++++++++ 2 files changed, 339 insertions(+) create mode 100644 lib/bag.js create mode 100644 test/core/bag-test.js diff --git a/lib/bag.js b/lib/bag.js new file mode 100644 index 0000000..28ce3a9 --- /dev/null +++ b/lib/bag.js @@ -0,0 +1,188 @@ +"use strict"; + +var RID = require('./recordid'), + Long = require('./long').Long; + +/** + * # RID Bag + * + * A bag of Record IDs, can come in two formats: + * + * * embedded - just a list of record ids. + * * tree based - a remote tree based data structure + * + * for more details on the RID Bag structure, see https://github.com/orientechnologies/orientdb/wiki/RidBag + * + * + * @param {String} serialized The base64 encoded RID Bag + */ +function Bag (serialized) { + this.serialized = serialized; + this.uuid = null; + this._content = []; + this._buffer = null; + this._type = null; + this._offset = 0; + this._current = -1; + this._length = null; +} + +Bag.BAG_EMBEDDED = 0; +Bag.BAG_TREE = 1; + +module.exports = Bag; + + +Object.defineProperties(Bag.prototype, { + /** + * The bag type. + * @type {String} + */ + type: { + get: function () { + if (this._type === null) { + this._parse(); + } + return this._type; + } + }, + /** + * The size of the bag. + * @type {Integer} + */ + length: { + get: function () { + if (this._length === null) { + this._parse(); + } + return this._length; + } + } +}); + + +/** + * Parse the bag content. + */ +Bag.prototype._parse = function () { + var buffer = new Buffer(this.serialized, 'base64'), + mode = buffer.readUInt8(0); + + if ((mode & 1) === 1) { + this._type = Bag.BAG_EMBEDDED; + } + else { + this._type = Bag.BAG_TREE; + } + + if ((mode & 2) === 2) { + this.uuid = buffer.slice(1, 16); + this._offset = 17; + } + else { + this._offset = 1; + } + + + if (this._type === Bag.BAG_EMBEDDED) { + this._length = buffer.readUInt32BE(this._offset); + this._offset += 4; + } + else { + this._fileId = readLong(buffer, this._offset); + this._offset += 8; + this._pageIndex = readLong(buffer, this._offset); + this._offset += 8; + this._pageOffset = buffer.readUInt32BE(this._offset); + this._offset += 4; + this._length = buffer.readUInt32BE(this._offset); + this._offset += 4; + this._changeSize = buffer.readUInt32BE(this._offset); + this._offset += 4; + } + this._buffer = buffer; +}; + +/** + * Return a representation of the bag that can be serialized to JSON. + * + * @return {Array} The JSON representation of the bag. + */ +Bag.prototype.toJSON = function () { + if (this.type === Bag.BAG_EMBEDDED) { + return this.all(); + } + else { + return undefined; // because we don't yet know how to serialize a tree bag to JSON. + } +}; + +/** + * Retrieve the next RID in the bag, or null if we're at the end. + * + * @return {RID|null} The next Record ID, or null. + */ +Bag.prototype.next = function () { + var rid; + if (!this._buffer) { + this._parse(); + } + if (this._type === Bag.BAG_EMBEDDED) { + if (this._current >= this._length - 1) { + return null; + } + this._current++; + rid = this._consume(); + this._content.push(rid); + return rid; + } +}; + + +/** + * Retreive all the RIDs in the bag. + * + * @return {RID[]} The record ids. + */ +Bag.prototype.all = function () { + var length = this.length; + if (this._content.length !== length) { + while(this.next()); // jshint ignore:line + } + return this._content; +}; + +/** + * Consume the next RID in the bag. + * + * @return {RID|null} The next record id, or null if the bag is exhausted. + */ +Bag.prototype._consume = function () { + var rid; + if (this._type === Bag.BAG_EMBEDDED) { + if (this._offset >= this._buffer.length) { + return null; + } + rid = new RID(); + rid.cluster = this._buffer.readInt16BE(this._offset); + this._offset += 2; + rid.position = readLong(this._buffer, this._offset); + this._offset +=8; + return rid; + } +}; + + +/** + * Read a Long from the given buffer. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading at. + * @return {Number} The Long number. + */ +function readLong (buffer, offset) { + return Long.fromBits( + buffer.readUInt32BE(offset + 4), + buffer.readInt32BE(offset) + ).toNumber(); +} \ No newline at end of file diff --git a/test/core/bag-test.js b/test/core/bag-test.js new file mode 100644 index 0000000..270a2a2 --- /dev/null +++ b/test/core/bag-test.js @@ -0,0 +1,151 @@ +describe("RID Bag", function () { + describe('Embedded Bag', function () { + before(function () { + var self = this; + return CREATE_TEST_DB(this, 'testdb_dbapi_rid_bag_embedded') + .bind(this) + .then(function () { + return this.db.class.create('Person', 'V'); + }) + .then(function (Person) { + this.Person = Person; + return this.db.class.create('Knows', 'E'); + }) + .then(function (Knows) { + this.Knows = Knows; + return this.Person.create({ + name: 'John Smith' + }); + }) + .then(function (subject) { + var limit = 10, + i; + this.subject = subject; + this.people = []; + for (i = 0; i < limit; i++) { + this.people.push({ + name: 'Friend ' + i + }); + } + return this.Person.create(this.people); + }) + .then(function () { + return this.db.edge + .from(this.subject['@rid']) + .to('SELECT * FROM Person WHERE name LIKE "Friend%"') + .create('Knows'); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_dbapi_rid_bag_embedded'); + }); + + beforeEach(function () { + return this.db + .select() + .from(this.subject['@rid']) + .fetch({'*': 2}) + .one() + .bind(this) + .then(function (record) { + this.bag = record.out_Knows; + }); + }); + + it('should load a bag', function () { + this.bag.should.be.an.instanceOf(LIB.Bag) + this.bag.type.should.equal(LIB.Bag.BAG_EMBEDDED); + expect(this.bag.uuid).to.equal(null); + this.bag.length.should.equal(10); + }); + + it('should iterate the contents in the bag', function () { + var length = this.bag.length, + i = 0, + item; + while((item = this.bag.next())) { + item.should.be.an.instanceOf(LIB.RID); + i++; + } + i.should.equal(10); + }); + + it('should return all the contents of the bag', function () { + var contents = this.bag.all(); + contents.length.should.equal(10); + contents.forEach(function (item) { + item.should.be.an.instanceOf(LIB.RID); + }); + }); + + it('should return the right JSON representation', function () { + var json = JSON.stringify(this.bag) + decoded = JSON.parse(json); + decoded.length.should.equal(10); + decoded.forEach(function (item) { + (typeof item).should.equal('string'); + }); + }); + }); + + describe('Tree Bag', function () { + before(function () { + var self = this; + return CREATE_TEST_DB(this, 'testdb_dbapi_rid_bag_tree', 'plocal') + .bind(this) + .then(function () { + return this.db.class.create('Person', 'V'); + }) + .then(function (Person) { + this.Person = Person; + return this.db.class.create('Knows', 'E'); + }) + .then(function (Knows) { + this.Knows = Knows; + return this.Person.create({ + name: 'John Smith' + }); + }) + .then(function (subject) { + var limit = 120, + i; + this.subject = subject; + this.people = []; + for (i = 0; i < limit; i++) { + this.people.push({ + name: 'Friend ' + i + }); + } + return this.Person.create(this.people); + }) + .then(function () { + return this.db.edge + .from(this.subject['@rid']) + .to('SELECT * FROM Person WHERE name LIKE "Friend%"') + .create('Knows'); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_dbapi_rid_bag_tree', ''); + }); + + beforeEach(function () { + return this.db + .select() + .from(this.subject['@rid']) + .fetch({'*': 2}) + .one() + .bind(this) + .then(function (record) { + this.bag = record.out_Knows; + }); + }); + + it('should load a bag', function () { + this.bag.should.be.an.instanceOf(LIB.Bag) + this.bag.type.should.equal(LIB.Bag.BAG_TREE); + expect(this.bag.uuid).to.equal(null); + this.bag.length.should.equal(120); + }); + }); +}); \ No newline at end of file From 923103161606949ea99a5ec66ea60327ff2acc8f Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 27 Apr 2014 19:15:11 +0100 Subject: [PATCH 043/308] export the RIDBag class --- lib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index 4b74fa8..1080c4e 100755 --- a/lib/index.js +++ b/lib/index.js @@ -5,7 +5,7 @@ function Oriento (config) { } Oriento.RecordID = Oriento.RecordId = Oriento.RID = require('./recordid'); - +Oriento.RIDBag = Oriento.Bag = require('./bag'); Oriento.Server = require('./server'); Oriento.Db = require('./db'); Oriento.transport = require('./transport'); From f63c18f6d49183adefdc1988fff7e851237e2c86 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 27 Apr 2014 19:15:28 +0100 Subject: [PATCH 044/308] deserializer should return a bag instance --- lib/transport/binary/protocol/deserializer.js | 5 +++-- test/transport/binary/protocol/deserializer-test.js | 10 ++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/transport/binary/protocol/deserializer.js b/lib/transport/binary/protocol/deserializer.js index 03ba761..716ebdc 100644 --- a/lib/transport/binary/protocol/deserializer.js +++ b/lib/transport/binary/protocol/deserializer.js @@ -1,6 +1,7 @@ "use strict"; -var RID = require('../../../recordid'); +var RID = require('../../../recordid'), + Bag = require('../../../bag'); /** * Deserialize the given record and return an object containing the values. @@ -493,7 +494,7 @@ function eatBag (input) { } input = input.slice(i + 1); - return [collected, input]; + return [new Bag(collected), input]; } diff --git a/test/transport/binary/protocol/deserializer-test.js b/test/transport/binary/protocol/deserializer-test.js index b627b3a..e67761c 100644 --- a/test/transport/binary/protocol/deserializer-test.js +++ b/test/transport/binary/protocol/deserializer-test.js @@ -182,15 +182,17 @@ describe("Deserializer", function () { }); describe('eatBag()', function () { it('should eat a RID bag', function () { - var input = 'aGVsbG8gd29ybGQ=;'; + var input = 'AQAAAAoACwAAAAAAAAACAAsAAAAAAAAAAQALAAAAAAAAAAoACwAAAAAAAAAJAAsAAAAAAAAACAALAAAAAAAAAAcACwAAAAAAAAAGAAsAAAAAAAAABQALAAAAAAAAAAQACwAAAAAAAAAD;'; var parsed = deserializer.eatBag(input); - parsed[0].should.eql('aGVsbG8gd29ybGQ='); + parsed[0].should.be.an.instanceOf(LIB.Bag); + parsed[0].length.should.equal(10); parsed[1].length.should.equal(0); }); it('should eat a RID bag with a trailing comma', function () { - var input = 'aGVsbG8gd29ybGQ=;,'; + var input = 'AQAAAAoACwAAAAAAAAACAAsAAAAAAAAAAQALAAAAAAAAAAoACwAAAAAAAAAJAAsAAAAAAAAACAALAAAAAAAAAAcACwAAAAAAAAAGAAsAAAAAAAAABQALAAAAAAAAAAQACwAAAAAAAAAD;,'; var parsed = deserializer.eatBag(input); - parsed[0].should.eql('aGVsbG8gd29ybGQ='); + parsed[0].should.be.an.instanceOf(LIB.Bag); + parsed[0].length.should.equal(10); parsed[1].length.should.equal(1); }); }); From 0f3656f4a20fe336658cdd68b15a38df8a95d6b6 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 27 Apr 2014 19:28:51 +0100 Subject: [PATCH 045/308] since a bag is not a real array, `size` is a better term than `length` --- lib/bag.js | 14 +++++++------- test/core/bag-test.js | 6 +++--- .../transport/binary/protocol/deserializer-test.js | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/bag.js b/lib/bag.js index 28ce3a9..ad302a4 100644 --- a/lib/bag.js +++ b/lib/bag.js @@ -24,7 +24,7 @@ function Bag (serialized) { this._type = null; this._offset = 0; this._current = -1; - this._length = null; + this._size = null; } Bag.BAG_EMBEDDED = 0; @@ -50,12 +50,12 @@ Object.defineProperties(Bag.prototype, { * The size of the bag. * @type {Integer} */ - length: { + size: { get: function () { - if (this._length === null) { + if (this._size === null) { this._parse(); } - return this._length; + return this._size; } } }); @@ -85,7 +85,7 @@ Bag.prototype._parse = function () { if (this._type === Bag.BAG_EMBEDDED) { - this._length = buffer.readUInt32BE(this._offset); + this._size = buffer.readUInt32BE(this._offset); this._offset += 4; } else { @@ -95,7 +95,7 @@ Bag.prototype._parse = function () { this._offset += 8; this._pageOffset = buffer.readUInt32BE(this._offset); this._offset += 4; - this._length = buffer.readUInt32BE(this._offset); + this._size = buffer.readUInt32BE(this._offset); this._offset += 4; this._changeSize = buffer.readUInt32BE(this._offset); this._offset += 4; @@ -128,7 +128,7 @@ Bag.prototype.next = function () { this._parse(); } if (this._type === Bag.BAG_EMBEDDED) { - if (this._current >= this._length - 1) { + if (this._current >= this._size - 1) { return null; } this._current++; diff --git a/test/core/bag-test.js b/test/core/bag-test.js index 270a2a2..e9db04b 100644 --- a/test/core/bag-test.js +++ b/test/core/bag-test.js @@ -56,11 +56,11 @@ describe("RID Bag", function () { this.bag.should.be.an.instanceOf(LIB.Bag) this.bag.type.should.equal(LIB.Bag.BAG_EMBEDDED); expect(this.bag.uuid).to.equal(null); - this.bag.length.should.equal(10); + this.bag.size.should.equal(10); }); it('should iterate the contents in the bag', function () { - var length = this.bag.length, + var size = this.bag.size, i = 0, item; while((item = this.bag.next())) { @@ -145,7 +145,7 @@ describe("RID Bag", function () { this.bag.should.be.an.instanceOf(LIB.Bag) this.bag.type.should.equal(LIB.Bag.BAG_TREE); expect(this.bag.uuid).to.equal(null); - this.bag.length.should.equal(120); + this.bag.size.should.equal(120); }); }); }); \ No newline at end of file diff --git a/test/transport/binary/protocol/deserializer-test.js b/test/transport/binary/protocol/deserializer-test.js index e67761c..7008568 100644 --- a/test/transport/binary/protocol/deserializer-test.js +++ b/test/transport/binary/protocol/deserializer-test.js @@ -185,14 +185,14 @@ describe("Deserializer", function () { var input = 'AQAAAAoACwAAAAAAAAACAAsAAAAAAAAAAQALAAAAAAAAAAoACwAAAAAAAAAJAAsAAAAAAAAACAALAAAAAAAAAAcACwAAAAAAAAAGAAsAAAAAAAAABQALAAAAAAAAAAQACwAAAAAAAAAD;'; var parsed = deserializer.eatBag(input); parsed[0].should.be.an.instanceOf(LIB.Bag); - parsed[0].length.should.equal(10); + parsed[0].size.should.equal(10); parsed[1].length.should.equal(0); }); it('should eat a RID bag with a trailing comma', function () { var input = 'AQAAAAoACwAAAAAAAAACAAsAAAAAAAAAAQALAAAAAAAAAAoACwAAAAAAAAAJAAsAAAAAAAAACAALAAAAAAAAAAcACwAAAAAAAAAGAAsAAAAAAAAABQALAAAAAAAAAAQACwAAAAAAAAAD;,'; var parsed = deserializer.eatBag(input); parsed[0].should.be.an.instanceOf(LIB.Bag); - parsed[0].length.should.equal(10); + parsed[0].size.should.equal(10); parsed[1].length.should.equal(1); }); }); From 1da3493315c9e98591ce624e243185396289182e Mon Sep 17 00:00:00 2001 From: Samson Radu Date: Mon, 28 Apr 2014 00:11:21 +0300 Subject: [PATCH 046/308] freeze/release operations WIP --- lib/protocol/operations/db-freeze.js | 16 ++++++++++++++ lib/protocol/operations/db-release.js | 16 ++++++++++++++ lib/protocol/operations/index.js | 4 +++- lib/server/index.js | 30 ++++++++++++++++++--------- test/server/server-test.js | 20 +++++++++++++++++- 5 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 lib/protocol/operations/db-freeze.js create mode 100644 lib/protocol/operations/db-release.js diff --git a/lib/protocol/operations/db-freeze.js b/lib/protocol/operations/db-freeze.js new file mode 100644 index 0000000..3185ec5 --- /dev/null +++ b/lib/protocol/operations/db-freeze.js @@ -0,0 +1,16 @@ +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_FREEZE', + opCode: 94, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId) + .writeString(this.data.name); + }, + reader: function () { + this.readStatus('status'); + } +}); diff --git a/lib/protocol/operations/db-release.js b/lib/protocol/operations/db-release.js new file mode 100644 index 0000000..39c2900 --- /dev/null +++ b/lib/protocol/operations/db-release.js @@ -0,0 +1,16 @@ +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_RELEASE', + opCode: 95, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId) + .writeString(this.data.name); + }, + reader: function () { + this.readStatus('status'); + } +}); diff --git a/lib/protocol/operations/index.js b/lib/protocol/operations/index.js index 0166eb4..6ef65f4 100644 --- a/lib/protocol/operations/index.js +++ b/lib/protocol/operations/index.js @@ -7,6 +7,8 @@ exports['db-size'] = require('./db-size'); exports['db-countrecords'] = require('./db-countrecords'); exports['db-reload'] = require('./db-reload'); exports['db-list'] = require('./db-list'); +exports['db-freeze'] = require('./db-freeze'); +exports['db-release'] = require('./db-release'); exports['db-close'] = require('./db-close'); @@ -30,4 +32,4 @@ exports['command'] = require('./command'); exports['config-list'] = require('./config-list'); exports['config-get'] = require('./config-get'); -exports['config-set'] = require('./config-set'); \ No newline at end of file +exports['config-set'] = require('./config-set'); diff --git a/lib/server/index.js b/lib/server/index.js index c5827eb..7a67f2e 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -343,21 +343,31 @@ Server.prototype.exist = Server.prototype.exists; /** * Freeze the database with the given name. * - * @param {String} databaseName The name of the database to freeze. + * @param {String} name The name of the database to freeze. * @return {Object} The response from the server. */ -Server.prototype.freeze = function (databaseName) { - // @todo implementation - throw new Error('Not yet implemented!'); -}; +Server.prototype.freeze = function (name) { + if (!name) { + return Promise.reject(new errors.Config('Cannot freeze, no database specified.')); + } + this.logger.debug('Freeze database ' + name); + return this.send('db-freeze', name) + .return(true); +} /** * Release the database with the given name. * - * @param {String} databaseName The name of the database to release. + * @param {String} name The name of the database to release. * @return {Object} The response from the server. */ -Server.prototype.release = function (databaseName) { - // @todo implementation - throw new Error('Not yet implemented!'); -}; + +Server.prototype.release = function (name) { + if (!name) { + return Promise.reject(new errors.Config('Cannot release, no database specified.')); + } + this.logger.debug('Release database ' + name); + return this.send('db-release', name) + .return(true); + +} diff --git a/test/server/server-test.js b/test/server/server-test.js index 507f8a4..9353efa 100644 --- a/test/server/server-test.js +++ b/test/server/server-test.js @@ -43,6 +43,24 @@ describe('Server::create()', function () { }, done).done(); }); }); +describe('Server::freeze()', function () { + it("should freeze", function (done) { + TEST_SERVER.freeze("testdb_server") + .then(function (response) { + response.should.be.true; + done(); + }, done).done(); + }); +}); +describe('Server::release()', function () { + it("should release", function (done) { + TEST_SERVER.release("testdb_server") + .then(function (response) { + response.should.be.true; + done(); + }, done).done(); + }); +}); describe('Server::list()', function () { it("should list the existing databases", function (done) { TEST_SERVER.list() @@ -107,4 +125,4 @@ describe('Server::config.get', function () { done(); }, done).done(); }); -}); \ No newline at end of file +}); From 4c1c1d13819f5be31b657ba7fe1958ff0b517ee4 Mon Sep 17 00:00:00 2001 From: Samson Radu Date: Mon, 28 Apr 2014 00:22:00 +0300 Subject: [PATCH 047/308] param type object --- lib/server/index.js | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/lib/server/index.js b/lib/server/index.js index 7a67f2e..35f74e6 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -227,7 +227,7 @@ Server.prototype.create = function (config) { } else { config = { - name: config.name || config.name, + name: config.name, type: (config.type || config.type), storage: config.storage || config.storage || 'plocal' }; @@ -270,7 +270,7 @@ Server.prototype.delete = function (config) { } else { config = { - name: config.name || config.name, + name: config.name, storage: config.storage || config.storage || 'plocal' }; } @@ -347,11 +347,22 @@ Server.prototype.exist = Server.prototype.exists; * @return {Object} The response from the server. */ Server.prototype.freeze = function (name) { - if (!name) { + var config; + if (typeof name === 'object' && name.name) { + config = name; + name = config.name; + } + else { + config = { + name: name, + }; + } + + if (!config.name) { return Promise.reject(new errors.Config('Cannot freeze, no database specified.')); } - this.logger.debug('Freeze database ' + name); - return this.send('db-freeze', name) + this.logger.debug('Freeze database ' + config.name); + return this.send('db-freeze', config) .return(true); } @@ -363,11 +374,21 @@ Server.prototype.freeze = function (name) { */ Server.prototype.release = function (name) { - if (!name) { + var config; + if (typeof name === 'object' && name.name) { + config = name; + name = config.name; + } + else { + config = { + name: name, + }; + } + if (!config.name) { return Promise.reject(new errors.Config('Cannot release, no database specified.')); } - this.logger.debug('Release database ' + name); - return this.send('db-release', name) + this.logger.debug('Release database ' + config.name); + return this.send('db-release', config) .return(true); } From 2e8e2d742b222ebb6f511b8094faf17ea2b0e2ae Mon Sep 17 00:00:00 2001 From: Samson Radu Date: Mon, 28 Apr 2014 00:34:37 +0300 Subject: [PATCH 048/308] merge conflicts --- lib/protocol/operations/db-freeze.js | 3 ++- lib/protocol/operations/db-release.js | 3 ++- lib/server/index.js | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/protocol/operations/db-freeze.js b/lib/protocol/operations/db-freeze.js index 3185ec5..798206d 100644 --- a/lib/protocol/operations/db-freeze.js +++ b/lib/protocol/operations/db-freeze.js @@ -8,7 +8,8 @@ module.exports = Operation.extend({ this .writeByte(this.opCode) .writeInt(this.data.sessionId) - .writeString(this.data.name); + .writeString(this.data.name) + .writeString(this.data.storage || 'local'); }, reader: function () { this.readStatus('status'); diff --git a/lib/protocol/operations/db-release.js b/lib/protocol/operations/db-release.js index 39c2900..b6c4f79 100644 --- a/lib/protocol/operations/db-release.js +++ b/lib/protocol/operations/db-release.js @@ -8,7 +8,8 @@ module.exports = Operation.extend({ this .writeByte(this.opCode) .writeInt(this.data.sessionId) - .writeString(this.data.name); + .writeString(this.data.name) + .writeString(this.data.storage || 'local'); }, reader: function () { this.readStatus('status'); diff --git a/lib/server/index.js b/lib/server/index.js index a2b4818..ce413c6 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -278,10 +278,12 @@ Server.prototype.freeze = function (name) { if (typeof name === 'object' && name.name) { config = name; name = config.name; + storageType = storageType || config.storage; } else { config = { name: name, + storage: 'plocal' }; } @@ -305,10 +307,12 @@ Server.prototype.release = function (name) { if (typeof name === 'object' && name.name) { config = name; name = config.name; + storageType = storageType || config.storage; } else { config = { name: name, + storage: 'plocal' }; } if (!config.name) { From 625c6d6b81aa8ae0ec7e422ced8bd329dc896d42 Mon Sep 17 00:00:00 2001 From: Samson Radu Date: Mon, 28 Apr 2014 00:42:24 +0300 Subject: [PATCH 049/308] refactor --- lib/server/index.js | 8 ++------ .../binary}/protocol/operations/db-freeze.js | 4 +++- .../binary}/protocol/operations/db-release.js | 4 +++- 3 files changed, 8 insertions(+), 8 deletions(-) rename lib/{ => transport/binary}/protocol/operations/db-freeze.js (84%) rename lib/{ => transport/binary}/protocol/operations/db-release.js (84%) diff --git a/lib/server/index.js b/lib/server/index.js index ce413c6..1d78f32 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -277,8 +277,6 @@ Server.prototype.freeze = function (name) { var config; if (typeof name === 'object' && name.name) { config = name; - name = config.name; - storageType = storageType || config.storage; } else { config = { @@ -293,7 +291,7 @@ Server.prototype.freeze = function (name) { this.logger.debug('Freeze database ' + config.name); return this.send('db-freeze', config) .return(true); -} +}; /** * Release the database with the given name. @@ -306,8 +304,6 @@ Server.prototype.release = function (name) { var config; if (typeof name === 'object' && name.name) { config = name; - name = config.name; - storageType = storageType || config.storage; } else { config = { @@ -322,4 +318,4 @@ Server.prototype.release = function (name) { return this.send('db-release', config) .return(true); -} +}; diff --git a/lib/protocol/operations/db-freeze.js b/lib/transport/binary/protocol/operations/db-freeze.js similarity index 84% rename from lib/protocol/operations/db-freeze.js rename to lib/transport/binary/protocol/operations/db-freeze.js index 798206d..6c9cba4 100644 --- a/lib/protocol/operations/db-freeze.js +++ b/lib/transport/binary/protocol/operations/db-freeze.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); @@ -9,7 +11,7 @@ module.exports = Operation.extend({ .writeByte(this.opCode) .writeInt(this.data.sessionId) .writeString(this.data.name) - .writeString(this.data.storage || 'local'); + .writeString(this.data.storage || 'plocal'); }, reader: function () { this.readStatus('status'); diff --git a/lib/protocol/operations/db-release.js b/lib/transport/binary/protocol/operations/db-release.js similarity index 84% rename from lib/protocol/operations/db-release.js rename to lib/transport/binary/protocol/operations/db-release.js index b6c4f79..daeada5 100644 --- a/lib/protocol/operations/db-release.js +++ b/lib/transport/binary/protocol/operations/db-release.js @@ -1,3 +1,5 @@ +"use strict"; + var Operation = require('../operation'), constants = require('../constants'); @@ -9,7 +11,7 @@ module.exports = Operation.extend({ .writeByte(this.opCode) .writeInt(this.data.sessionId) .writeString(this.data.name) - .writeString(this.data.storage || 'local'); + .writeString(this.data.storage || 'plocal'); }, reader: function () { this.readStatus('status'); From ed498fe31ed0f316b7dea041de25cd0da9017810 Mon Sep 17 00:00:00 2001 From: Samson Radu Date: Mon, 28 Apr 2014 01:08:27 +0300 Subject: [PATCH 050/308] refactor --- lib/server/index.js | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/lib/server/index.js b/lib/server/index.js index 1d78f32..f9d8c62 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -244,7 +244,7 @@ Server.prototype.list = function () { * Determine whether a database exists with the given name. * * @param {String} name The database name. - * @param {String} storageType The storage type, defaults to `local`. + * @param {String} storageType The storage type, defaults to `plocal`. * @promise {Boolean} true if the database exists. */ Server.prototype.exists = function (name, storageType) { @@ -270,24 +270,23 @@ Server.prototype.exist = Server.prototype.exists; /** * Freeze the database with the given name. * - * @param {String} name The name of the database to freeze. + * @param {String} name The database name. + * @param {String} storageType The storage type, defaults to `plocal`. * @return {Object} The response from the server. */ -Server.prototype.freeze = function (name) { +Server.prototype.freeze = function (name, storageType) { var config; - if (typeof name === 'object' && name.name) { - config = name; - } - else { - config = { - name: name, - storage: 'plocal' - }; - } + + storageType = storageType || 'plocal'; + config = { + name: name, + storage: storageType.toLowerCase() + }; if (!config.name) { return Promise.reject(new errors.Config('Cannot freeze, no database specified.')); } + this.logger.debug('Freeze database ' + config.name); return this.send('db-freeze', config) .return(true); @@ -296,24 +295,24 @@ Server.prototype.freeze = function (name) { /** * Release the database with the given name. * - * @param {String} name The name of the database to release. + * @param {String} name The database name. + * @param {String} storageType The storage type, defaults to `plocal`. * @return {Object} The response from the server. */ -Server.prototype.release = function (name) { +Server.prototype.release = function (name, storageType) { var config; - if (typeof name === 'object' && name.name) { - config = name; - } - else { - config = { - name: name, - storage: 'plocal' - }; - } + + storageType = storageType || 'plocal'; + config = { + name: name, + storage: storageType.toLowerCase() + }; + if (!config.name) { return Promise.reject(new errors.Config('Cannot release, no database specified.')); } + this.logger.debug('Release database ' + config.name); return this.send('db-release', config) .return(true); From bd32c8110f1e90de9edee5df39697b5a4ccf4e76 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 28 Apr 2014 00:04:08 +0100 Subject: [PATCH 051/308] add initial transaction command and (failing) test --- lib/db/index.js | 13 +- lib/db/transaction.js | 214 ++++++++++++++++++ .../binary/protocol/operations/index.js | 1 + .../binary/protocol/operations/tx-commit.js | 67 ++++++ test/db/transaction-test.js | 45 ++++ 5 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 lib/db/transaction.js create mode 100644 lib/transport/binary/protocol/operations/tx-commit.js create mode 100644 test/db/transaction-test.js diff --git a/lib/db/index.js b/lib/db/index.js index 8399726..16635df 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -4,7 +4,8 @@ var utils = require('../utils'), errors = require('../errors'), Promise = require('bluebird'), RID = require('../recordid'), - Query = require('./query'); + Query = require('./query'), + Transaction = require('./transaction'); /** @@ -129,6 +130,16 @@ Db.prototype.reload = function () { }); }; +/** + * Begin a new transaction. + * + * @return {Transaction} The transaction instance. + */ +Db.prototype.begin = function () { + this.transactionId++; + return new Transaction(this, this.transactionId); +}; + /** * Execute an SQL query against the database and retreive the raw, parsed response. diff --git a/lib/db/transaction.js b/lib/db/transaction.js new file mode 100644 index 0000000..b6b89ca --- /dev/null +++ b/lib/db/transaction.js @@ -0,0 +1,214 @@ +"use strict"; + +var Promise = require('bluebird'), + RID = require('../recordid'), + errors = require('../errors'); + +/** + * # Transactions + * + * + * @param {Db} db The database the transaction is for. + * @param {Integer} id The transaction id. + */ +function Transaction (db, id) { + this.db = db; + this.id = id; + this._pos = -1; + this._creates = []; + this._updates = []; + this._deletes = []; +} + +module.exports = Transaction; + +/** + * Commit the transaction. + * + * @promise {Object} The results of the transaction. + */ +Transaction.prototype.commit = function () { + return this.db.send('tx-commit', { + creates: this._creates.map(first), + updates: this._updates.map(first), + deletes: this._deletes.map(first) + }) + .then(function (result) { + return result; + }); + + function first (item) { return item[0]; } +}; + +/** + * Insert the given record into the database. + * + * @param {Object} record The record to insert. + * @promise {Object} The inserted record. + */ +Transaction.prototype.create = function (record) { + if (Array.isArray(record)) { + return Promise.all(record.map(this.create, this)); + } + var deferred = Promise.defer(); + record['@rid'] = new RID({ + cluster: -1, + position: (--this._pos) + }); + this._creates.push([record, deferred]); + return deferred.promise; +}; + + +/** + * Resolve references to child records for the given record. + * + * @param {Object} record The primary record. + * @param {Object[]} children The child records. + * @return {Object} The primary record with references replaced. + */ +Transaction.prototype.resolveReferences = function (record, children) { + var total = children.length, + indexed = {}, + replaceRecordIds = recordIdResolver(), + child, i; + + for (i = 0; i < total; i++) { + child = children[i]; + indexed[child['@rid']] = child; + } + + for (i = 0; i < total; i++) { + child = children[i]; + replaceRecordIds(indexed, child); + } + + return replaceRecordIds(indexed, record); +}; + +function recordIdResolver () { + var seen = {}; + return replaceRecordIds; + /** + * Replace references to records with their instances, where possible. + * + * @param {Object} records The map of record ids to record instances. + * @param {Object} obj The object to replace references within + * @return {Object} The object with references replaced. + */ + function replaceRecordIds (records, obj) { + if (!obj || typeof obj !== 'object') { + return obj; + } + else if (Array.isArray(obj)) { + /*jshint validthis:true */ + return obj.map(replaceRecordIds.bind(this, records)); + } + else if (obj instanceof RID && records[obj]) { + return records[obj]; + } + if (obj['@rid']) { + if (seen[obj['@rid']]) { + return seen[obj['@rid']]; + } + else { + seen[obj['@rid']] = obj; + } + } + var keys = Object.keys(obj), + total = keys.length, + i, key, value; + + for (i = 0; i < total; i++) { + key = keys[i]; + value = obj[key]; + if (!value || typeof value !== 'object' || key[0] === '@') { + continue; + } + if (value instanceof RID) { + if (records[value]) { + obj[key] = records[value]; + } + } + else if (Array.isArray(value)) { + obj[key] = value.map(replaceRecordIds.bind(this, records)); + } + else { + obj[key] = replaceRecordIds(records, value); + } + } + return obj; + } +} + +/** + * Update the given record. + * + * @param {Object} record The record to update. + * @param {Object} options The query options. + * @promise {Object} The updated record. + */ +Transaction.prototype.update = function (record, options) { + var extracted = extractRecordId(record), + rid = extracted[0], + promise, data; + + record = extracted[1]; + + options = options || {}; + + if (!rid) { + return Promise.reject(new errors.Operation('Cannot update record - record ID is not specified or invalid.')); + } + +}; + +/** + * Delete the given record. + * + * @param {String|RID|Object} record The record or record id to delete. + * @param {Object} options The query options. + * @promise {Object} The deleted record object. + */ +Transaction.prototype.delete = function (record, options) { + if (!record) { + return Promise.reject(new errors.Operation('Cannot delete - no record specified')); + } + var extracted = extractRecordId(record), + rid = extracted[0]; + + record = extracted[1]; + + options = options || {}; + + if (!rid) { + return Promise.reject(new errors.Operation('Cannot delete - no record id specified')); + } + +}; + +/** + * Extract the record id and record from the given argument. + * + * @param {String|RID|Object} record The record. + * @return {[RID, Object]} The record id and object. + */ +function extractRecordId (record) { + var rid = false; + if (typeof record === 'string') { + rid = RID.parse(record); + record = { + '@rid': rid + }; + } + else if (record instanceof RID) { + rid = record; + record = { + '@rid': rid + }; + } + else if (record['@rid']) { + record['@rid'] = rid = RID.parse(record['@rid']); + } + return [rid, record]; +} \ No newline at end of file diff --git a/lib/transport/binary/protocol/operations/index.js b/lib/transport/binary/protocol/operations/index.js index f549b17..e635fde 100644 --- a/lib/transport/binary/protocol/operations/index.js +++ b/lib/transport/binary/protocol/operations/index.js @@ -29,6 +29,7 @@ exports['record-delete'] = require('./record-delete'); exports['record-clean-out'] = require('./record-clean-out'); exports['command'] = require('./command'); +exports['tx-commit'] = require('./tx-commit'); exports['config-list'] = require('./config-list'); exports['config-get'] = require('./config-get'); diff --git a/lib/transport/binary/protocol/operations/tx-commit.js b/lib/transport/binary/protocol/operations/tx-commit.js new file mode 100644 index 0000000..5e14854 --- /dev/null +++ b/lib/transport/binary/protocol/operations/tx-commit.js @@ -0,0 +1,67 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + RID = require('../../../../recordid'), + serializer = require('../serializer'); + +module.exports = Operation.extend({ + id: 'REQUEST_TX_COMMIT', + opCode: 60, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId || -1) + .writeInt(this.data.txId) + .writeByte(this.data.txLog); // use transaction log + + + // creates + var total = this.data.creates.length, + item, i; + + for (i = 0; i < total; i++) { + item = this.data.creates[i][0]; + this.writeByte(1); // mark the start of an entry. + this.writeByte(3); // create. + this.writeShort(item['@rid'].cluster); + this.writeLong(item['@rid'].position); + this.writeByte(constants.RECORD_TYPES[item['@type'] || 'd'] || 100); // document by default + this.writeBytes(serializer.encodeRecordData(item)); + } + + // updates + total = this.data.updates.length; + + for (i = 0; i < total; i++) { + item = this.data.updates[i][0]; + this.writeByte(1); // mark the start of an entry. + this.writeByte(1); // update. + this.writeShort(item['@rid'].cluster); + this.writeLong(item['@rid'].position); + this.writeByte(constants.RECORD_TYPES[item['@type'] || 'd'] || 100); // document by default + this.writeInt(item['@version'] || 0); + this.writeBytes(serializer.encodeRecordData(item)); + } + + // deletes + total = this.data.deletes.length; + + for (i = 0; i < total; i++) { + item = this.data.deletes[i][0]; + this.writeByte(1); // mark the start of an entry. + this.writeByte(2); // delete + this.writeShort(item['@rid'].cluster); + this.writeLong(item['@rid'].position); + this.writeByte(constants.RECORD_TYPES[item['@type'] || 'd'] || 100); // document by default + this.writeInt(item['@version'] || 0); + } + + this.writeByte(0); // no more documents + }, + reader: function () { + this + .readStatus('status') + .readInt('sessionId'); + } +}); \ No newline at end of file diff --git a/test/db/transaction-test.js b/test/db/transaction-test.js new file mode 100644 index 0000000..9d704a7 --- /dev/null +++ b/test/db/transaction-test.js @@ -0,0 +1,45 @@ +var Transaction = require('../../lib/db/transaction'); + +describe("Database API - Transaction", function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_dbapi_tx'); + }); + after(function () { + return DELETE_TEST_DB('testdb_dbapi_tx'); + }); + describe("Db::begin()", function () { + it('should return a new transaction instance', function () { + var tx = this.db.begin(); + tx.should.be.an.instanceOf(Transaction); + tx.id.should.be.above(0); + }); + }); + describe("Db::transaction.create()", function () { + beforeEach(function () { + this.tx = this.db.begin(); + }); + it('should create a single record', function () { + var completed = false; + this.tx.create({ + '@class': 'OUser', + name: 'testuser', + password: 'testpassword', + status: 'ACTIVE' + }) + .then(function (record) { + record['@rid'].should.be.an.instanceOf(LIB.RID); + completed = true; + }); + + return this.tx.commit() + .then(function (results) { + completed.should.be.true; + }); + + }); + }); + describe("Db::transaction.delete()", function () { + + }); + +}); \ No newline at end of file From 4492aa615c07b997360139ac586ce90571cd6661 Mon Sep 17 00:00:00 2001 From: Samson Radu Date: Mon, 28 Apr 2014 21:21:32 +0300 Subject: [PATCH 052/308] test added todo --- test/server/db-test.js | 47 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 test/server/db-test.js diff --git a/test/server/db-test.js b/test/server/db-test.js new file mode 100644 index 0000000..b595f1b --- /dev/null +++ b/test/server/db-test.js @@ -0,0 +1,47 @@ +describe("Database commands", function () { + before(function () { + return CREATE_TEST_DB(this, 'test_db_commands', 'plocal') + .bind(this) + .then(function (db) { + this.db = db; + }); + }); + after(function () { + return DELETE_TEST_DB('test_db_commands'); + }); + describe('Server::freeze()', function () { + it("should freeze", function (done) { + TEST_SERVER.freeze("test_db_commands", "plocal") + .then(function (response) { + response.should.be.true; + done(); + }, done).done(); + }); + it("should allow only read-only operations", function(){ + return this.db.record.create({ + '@class': 'OUser', + name: 'testuserx', + password: 'testpasswordx', + status: 'ACTIVE' + }).bind(this) + .then(function (record) { + //@TODO implement assertion + //Should this be null ? + createdRID = record['@rid']; + return this.db.record.delete(createdRID); + }); + + }); + }); + describe('Server::release()', function () { + it("should release", function (done) { + TEST_SERVER.release("test_db_commands", "plocal") + .then(function (response) { + response.should.be.true; + done(); + }, done).done(); + }); + }); + + +}); From 37720127f0204e2e025565372b5c789887408257 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 28 Apr 2014 19:23:46 +0100 Subject: [PATCH 053/308] WIP better transaction support --- lib/db/transaction.js | 35 ++++++++++-- lib/transport/binary/protocol/operation.js | 3 +- .../binary/protocol/operations/tx-commit.js | 53 ++++++++++++++++++- test/db/transaction-test.js | 27 +++++++--- 4 files changed, 101 insertions(+), 17 deletions(-) diff --git a/lib/db/transaction.js b/lib/db/transaction.js index b6b89ca..1ca711f 100644 --- a/lib/db/transaction.js +++ b/lib/db/transaction.js @@ -28,16 +28,41 @@ module.exports = Transaction; * @promise {Object} The results of the transaction. */ Transaction.prototype.commit = function () { - return this.db.send('tx-commit', { - creates: this._creates.map(first), - updates: this._updates.map(first), - deletes: this._deletes.map(first) + return this.db.cluster.list() + .bind(this) + .then(function () { + var ids = {}; + return [ + this._creates.map(resolveClusterId, this), + this._updates.map(resolveClusterId, this), + this._deletes.map(resolveClusterId, this) + ]; + function resolveClusterId (item) { + var className = (item[0]['@class'] || '').toLowerCase(); + if (className) { + if (ids[className] == null) { + ids[className] = this.db.cluster.cached.names[className].id; + } + item[0]['@rid'].cluster = ids[className]; + } + return item; + } + }) + .spread(function (creates, updates, deletes) { + return this.db.send('tx-commit', { + storageType: this.db.storage, + txLog: true, + txId: this.id, + creates: creates, + updates: updates, + deletes: deletes + }); }) .then(function (result) { + console.log(result); return result; }); - function first (item) { return item[0]; } }; /** diff --git a/lib/transport/binary/protocol/operation.js b/lib/transport/binary/protocol/operation.js index db5c6e5..62197b0 100644 --- a/lib/transport/binary/protocol/operation.js +++ b/lib/transport/binary/protocol/operation.js @@ -155,8 +155,7 @@ Operation.prototype.writeInt = function (data) { Operation.prototype.writeLong = function (data) { this.writeOps.push([constants.BYTES_LONG, function (buffer, offset) { data = Long.fromNumber(data); - - buffer.fill(0, offset, constants.BYTES_LONG); + buffer.fill(0, offset, offset + constants.BYTES_LONG); buffer.writeInt32BE(data.high_, offset); buffer.writeInt32BE(data.low_, offset + constants.BYTES_INT); diff --git a/lib/transport/binary/protocol/operations/tx-commit.js b/lib/transport/binary/protocol/operations/tx-commit.js index 5e14854..865185b 100644 --- a/lib/transport/binary/protocol/operations/tx-commit.js +++ b/lib/transport/binary/protocol/operations/tx-commit.js @@ -56,12 +56,61 @@ module.exports = Operation.extend({ this.writeByte(constants.RECORD_TYPES[item['@type'] || 'd'] || 100); // document by default this.writeInt(item['@version'] || 0); } - this.writeByte(0); // no more documents + this.writeString(''); }, reader: function () { this .readStatus('status') - .readInt('sessionId'); + .readInt('totalCreated') + .readArray('created', function (data) { + var items = [], + i; + for (i = 0; i < data.totalCreated; i++) { + items.push(function () { + this + .readShort('tmpCluster') + .readLong('tmpPosition') + .readShort('cluster') + .readLong('position'/*, function (data) { + data.position = Math.abs(data.position); + }*/); + }); + } + return items; + }) + .readInt('totalUpdated') + .readArray('updated', function (data) { + var items = [], + i; + for (i = 0; i < data.totalUpdated; i++) { + items.push(function () { + this + .readShort('cluster') + .readLong('position') + .readInt('version'); + }); + } + return items; + }); + + if (this.data.storageType !== 'memory') { + this.readInt('totalChanges') + .readArray('changes', function (data) { + var items = [], + i; + for (i = 0; i < data.totalChanges; i++) { + items.push(function () { + this + .readLong('uuidHigh') + .readLong('uuidLow') + .readLong('fileId') + .readLong('pageIndex') + .readInt('pageOffset'); + }); + } + return items; + }); + } } }); \ No newline at end of file diff --git a/test/db/transaction-test.js b/test/db/transaction-test.js index 9d704a7..b5137e7 100644 --- a/test/db/transaction-test.js +++ b/test/db/transaction-test.js @@ -1,8 +1,12 @@ var Transaction = require('../../lib/db/transaction'); -describe("Database API - Transaction", function () { +describe.only("Database API - Transaction", function () { before(function () { - return CREATE_TEST_DB(this, 'testdb_dbapi_tx'); + return CREATE_TEST_DB(this, 'testdb_dbapi_tx') + .bind(this) + .then(function () { + return this.db.class.create('TestClass', 'V'); + }); }); after(function () { return DELETE_TEST_DB('testdb_dbapi_tx'); @@ -18,19 +22,27 @@ describe("Database API - Transaction", function () { beforeEach(function () { this.tx = this.db.begin(); }); - it('should create a single record', function () { + it.only('should create a single record', function () { var completed = false; this.tx.create({ - '@class': 'OUser', - name: 'testuser', - password: 'testpassword', - status: 'ACTIVE' + '@class': 'TestClass', + name: 'item1' }) .then(function (record) { record['@rid'].should.be.an.instanceOf(LIB.RID); completed = true; }); + this.tx.create({ + '@class': 'TestClass', + name: 'item2' + }) + + this.tx.create({ + '@class': 'TestClass', + name: 'item3' + }) + return this.tx.commit() .then(function (results) { completed.should.be.true; @@ -39,7 +51,6 @@ describe("Database API - Transaction", function () { }); }); describe("Db::transaction.delete()", function () { - }); }); \ No newline at end of file From 1fae3d0a847473d3816d46e29b56b0f26f9fbd78 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 28 Apr 2014 19:28:46 +0100 Subject: [PATCH 054/308] test fixes --- test/server/db-test.js | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/test/server/db-test.js b/test/server/db-test.js index b595f1b..92845e3 100644 --- a/test/server/db-test.js +++ b/test/server/db-test.js @@ -1,21 +1,16 @@ describe("Database commands", function () { before(function () { - return CREATE_TEST_DB(this, 'test_db_commands', 'plocal') - .bind(this) - .then(function (db) { - this.db = db; - }); + return CREATE_TEST_DB(this, 'test_db_commands', 'plocal'); }); after(function () { return DELETE_TEST_DB('test_db_commands'); }); describe('Server::freeze()', function () { - it("should freeze", function (done) { - TEST_SERVER.freeze("test_db_commands", "plocal") + it("should freeze", function () { + return TEST_SERVER.freeze("test_db_commands", "plocal") .then(function (response) { response.should.be.true; - done(); - }, done).done(); + }); }); it("should allow only read-only operations", function(){ return this.db.record.create({ @@ -23,23 +18,22 @@ describe("Database commands", function () { name: 'testuserx', password: 'testpasswordx', status: 'ACTIVE' - }).bind(this) + }) + .bind(this) .then(function (record) { - //@TODO implement assertion - //Should this be null ? - createdRID = record['@rid']; - return this.db.record.delete(createdRID); + throw new Error('Should never happen!'); + }) + .catch(LIB.errors.Request, function (e) { + return true; }); - }); }); describe('Server::release()', function () { - it("should release", function (done) { - TEST_SERVER.release("test_db_commands", "plocal") + it("should release", function () { + return TEST_SERVER.release("test_db_commands", "plocal") .then(function (response) { response.should.be.true; - done(); - }, done).done(); + }); }); }); From 111be6b9be49bec7dbbf42268bc7419edc4d1e45 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 28 Apr 2014 19:40:00 +0100 Subject: [PATCH 055/308] test formatting --- test/server/db-test.js | 2 +- test/server/server-test.js | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/test/server/db-test.js b/test/server/db-test.js index 92845e3..9b0126c 100644 --- a/test/server/db-test.js +++ b/test/server/db-test.js @@ -3,7 +3,7 @@ describe("Database commands", function () { return CREATE_TEST_DB(this, 'test_db_commands', 'plocal'); }); after(function () { - return DELETE_TEST_DB('test_db_commands'); + return DELETE_TEST_DB('test_db_commands', 'plocal'); }); describe('Server::freeze()', function () { it("should freeze", function () { diff --git a/test/server/server-test.js b/test/server/server-test.js index 390b19b..53d40ce 100644 --- a/test/server/server-test.js +++ b/test/server/server-test.js @@ -13,21 +13,19 @@ describe('Server::create()', function () { }); }); describe('Server::freeze()', function () { - it("should freeze", function (done) { - TEST_SERVER.freeze("testdb_server") + it("should freeze", function () { + return TEST_SERVER.freeze("testdb_server") .then(function (response) { response.should.be.true; - done(); - }, done).done(); + }); }); }); describe('Server::release()', function () { - it("should release", function (done) { - TEST_SERVER.release("testdb_server") + it("should release", function () { + return TEST_SERVER.release("testdb_server") .then(function (response) { response.should.be.true; - done(); - }, done).done(); + }); }); }); describe('Server::list()', function () { From 65081504dc7ec683f88f2763467773dbbdea0d5e Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 28 Apr 2014 21:38:04 +0100 Subject: [PATCH 056/308] handle missing classes properly --- lib/db/record.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/db/record.js b/lib/db/record.js index c201c49..947e224 100644 --- a/lib/db/record.js +++ b/lib/db/record.js @@ -12,6 +12,11 @@ var Promise = require('bluebird'), * @promise {Object} The inserted record. */ exports.create = function (record, options) { + if (Array.isArray(record)) { + return Promise.all(record.map(function (record) { + return this.record.create(record, options); + }, this)); + } var className = record['@class'] || '', rid, promise; @@ -27,7 +32,9 @@ exports.create = function (record, options) { else if (className !== '') { promise = this.cluster.getByName(className); } - + else { + return Promise.reject(new errors.Operation('Cannot create record - cluster ID and/or class is invalid.')); + } return promise .bind(this) .then(function (cluster) { From 4dc0fe69ec1d819123901cd54fd9e462ed81ad9e Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 28 Apr 2014 21:38:13 +0100 Subject: [PATCH 057/308] transaction fixes --- lib/db/transaction.js | 170 ++++++----------- .../binary/protocol/operations/tx-commit.js | 10 +- test/db/transaction-test.js | 176 ++++++++++++++++-- 3 files changed, 224 insertions(+), 132 deletions(-) diff --git a/lib/db/transaction.js b/lib/db/transaction.js index 1ca711f..8c96bc0 100644 --- a/lib/db/transaction.js +++ b/lib/db/transaction.js @@ -38,12 +38,12 @@ Transaction.prototype.commit = function () { this._deletes.map(resolveClusterId, this) ]; function resolveClusterId (item) { - var className = (item[0]['@class'] || '').toLowerCase(); + var className = (item['@class'] || '').toLowerCase(); if (className) { if (ids[className] == null) { ids[className] = this.db.cluster.cached.names[className].id; } - item[0]['@rid'].cluster = ids[className]; + item['@rid'].cluster = ids[className]; } return item; } @@ -56,160 +56,112 @@ Transaction.prototype.commit = function () { creates: creates, updates: updates, deletes: deletes + }) + .then(function (response) { + return [creates, updates, deletes, response]; }); }) - .then(function (result) { - console.log(result); - return result; + .spread(function (creates, updates, deletes, response) { + return { + created: response.created.map(function (result) { + var total = creates.length, + item, i; + for (i = 0; i < total; i++) { + item = creates[i]; + if (item['@rid'].cluster === result.tmpCluster && + item['@rid'].position === result.tmpPosition) { + + item['@rid'].cluster = result.cluster; + item['@rid'].position = result.position; + return item; + } + } + }) + .filter(notEmpty), + updated: response.updated.map(function (result) { + var total = updates.length, + item, i; + for (i = 0; i < total; i++) { + item = updates[i]; + if (item['@rid'].cluster === result.cluster && + item['@rid'].position === result.position) { + + item['@version'] = result.version; + return item; + } + } + }) + .filter(notEmpty), + deleted: deletes.filter(notEmpty) + }; }); - }; +function notEmpty (item) { return item; } + /** * Insert the given record into the database. * * @param {Object} record The record to insert. - * @promise {Object} The inserted record. + * @return {Transaction} The transaction instance. */ Transaction.prototype.create = function (record) { if (Array.isArray(record)) { - return Promise.all(record.map(this.create, this)); + return record.map(this.create, this); } - var deferred = Promise.defer(); record['@rid'] = new RID({ cluster: -1, position: (--this._pos) }); - this._creates.push([record, deferred]); - return deferred.promise; + this._creates.push(record); + return this; }; -/** - * Resolve references to child records for the given record. - * - * @param {Object} record The primary record. - * @param {Object[]} children The child records. - * @return {Object} The primary record with references replaced. - */ -Transaction.prototype.resolveReferences = function (record, children) { - var total = children.length, - indexed = {}, - replaceRecordIds = recordIdResolver(), - child, i; - - for (i = 0; i < total; i++) { - child = children[i]; - indexed[child['@rid']] = child; - } - - for (i = 0; i < total; i++) { - child = children[i]; - replaceRecordIds(indexed, child); - } - - return replaceRecordIds(indexed, record); -}; - -function recordIdResolver () { - var seen = {}; - return replaceRecordIds; - /** - * Replace references to records with their instances, where possible. - * - * @param {Object} records The map of record ids to record instances. - * @param {Object} obj The object to replace references within - * @return {Object} The object with references replaced. - */ - function replaceRecordIds (records, obj) { - if (!obj || typeof obj !== 'object') { - return obj; - } - else if (Array.isArray(obj)) { - /*jshint validthis:true */ - return obj.map(replaceRecordIds.bind(this, records)); - } - else if (obj instanceof RID && records[obj]) { - return records[obj]; - } - if (obj['@rid']) { - if (seen[obj['@rid']]) { - return seen[obj['@rid']]; - } - else { - seen[obj['@rid']] = obj; - } - } - var keys = Object.keys(obj), - total = keys.length, - i, key, value; - - for (i = 0; i < total; i++) { - key = keys[i]; - value = obj[key]; - if (!value || typeof value !== 'object' || key[0] === '@') { - continue; - } - if (value instanceof RID) { - if (records[value]) { - obj[key] = records[value]; - } - } - else if (Array.isArray(value)) { - obj[key] = value.map(replaceRecordIds.bind(this, records)); - } - else { - obj[key] = replaceRecordIds(records, value); - } - } - return obj; - } -} - /** * Update the given record. * * @param {Object} record The record to update. - * @param {Object} options The query options. - * @promise {Object} The updated record. + * @return {Transaction} The transaction instance. */ -Transaction.prototype.update = function (record, options) { +Transaction.prototype.update = function (record) { + if (Array.isArray(record)) { + return record.map(this.update, this); + } var extracted = extractRecordId(record), - rid = extracted[0], - promise, data; + rid = extracted[0]; record = extracted[1]; - options = options || {}; - if (!rid) { - return Promise.reject(new errors.Operation('Cannot update record - record ID is not specified or invalid.')); + throw new errors.Operation('Cannot update record - record ID is not specified or invalid.'); } + this._updates.push(record); + return this; }; /** * Delete the given record. * - * @param {String|RID|Object} record The record or record id to delete. - * @param {Object} options The query options. - * @promise {Object} The deleted record object. + * @param {String|RID|Object} record The record or record id to delete. + * @return {Transaction} The transaction instance. */ -Transaction.prototype.delete = function (record, options) { - if (!record) { - return Promise.reject(new errors.Operation('Cannot delete - no record specified')); +Transaction.prototype.delete = function (record) { + if (Array.isArray(record)) { + return record.map(this.delete, this); } var extracted = extractRecordId(record), rid = extracted[0]; record = extracted[1]; - options = options || {}; - if (!rid) { - return Promise.reject(new errors.Operation('Cannot delete - no record id specified')); + throw new errors.Operation('Cannot delete record - record ID is not specified or invalid.'); } + this._deletes.push(record); + return this; }; /** diff --git a/lib/transport/binary/protocol/operations/tx-commit.js b/lib/transport/binary/protocol/operations/tx-commit.js index 865185b..63fda65 100644 --- a/lib/transport/binary/protocol/operations/tx-commit.js +++ b/lib/transport/binary/protocol/operations/tx-commit.js @@ -21,7 +21,7 @@ module.exports = Operation.extend({ item, i; for (i = 0; i < total; i++) { - item = this.data.creates[i][0]; + item = this.data.creates[i]; this.writeByte(1); // mark the start of an entry. this.writeByte(3); // create. this.writeShort(item['@rid'].cluster); @@ -34,7 +34,7 @@ module.exports = Operation.extend({ total = this.data.updates.length; for (i = 0; i < total; i++) { - item = this.data.updates[i][0]; + item = this.data.updates[i]; this.writeByte(1); // mark the start of an entry. this.writeByte(1); // update. this.writeShort(item['@rid'].cluster); @@ -48,7 +48,7 @@ module.exports = Operation.extend({ total = this.data.deletes.length; for (i = 0; i < total; i++) { - item = this.data.deletes[i][0]; + item = this.data.deletes[i]; this.writeByte(1); // mark the start of an entry. this.writeByte(2); // delete this.writeShort(item['@rid'].cluster); @@ -72,9 +72,7 @@ module.exports = Operation.extend({ .readShort('tmpCluster') .readLong('tmpPosition') .readShort('cluster') - .readLong('position'/*, function (data) { - data.position = Math.abs(data.position); - }*/); + .readLong('position'); }); } return items; diff --git a/test/db/transaction-test.js b/test/db/transaction-test.js index b5137e7..286fdbc 100644 --- a/test/db/transaction-test.js +++ b/test/db/transaction-test.js @@ -1,6 +1,6 @@ var Transaction = require('../../lib/db/transaction'); -describe.only("Database API - Transaction", function () { +describe("Database API - Transaction", function () { before(function () { return CREATE_TEST_DB(this, 'testdb_dbapi_tx') .bind(this) @@ -18,39 +18,181 @@ describe.only("Database API - Transaction", function () { tx.id.should.be.above(0); }); }); - describe("Db::transaction.create()", function () { - beforeEach(function () { - this.tx = this.db.begin(); + + describe('Db::commit()', function () { + before(function () { + return this.db.record.create([ + { + '@class': 'TestClass', + name: 'item1' + }, + { + '@class': 'TestClass', + name: 'item2' + } + ]) + .bind(this) + .spread(function (first, second) { + this.first = first; + this.second = second; + }); }); - it.only('should create a single record', function () { - var completed = false; + it('should perform a single action', function () { + this.tx = this.db.begin(); this.tx.create({ + '@class': 'TestClass', + name: 'item3' + }); + return this.tx.commit() + .then(function (results) { + results.created.length.should.equal(1); + results.updated.length.should.equal(0); + results.deleted.length.should.equal(0); + }); + }); + it('should perform multiple actions', function () { + this.tx = this.db.begin(); + this.first.wat = 'wat'; + this.tx + .create({ + '@class': 'TestClass', + name: 'item4' + }) + .delete(this.second) + .update(this.first); + + return this.tx.commit() + .then(function (results) { + results.created.length.should.equal(1); + results.updated.length.should.equal(1); + results.deleted.length.should.equal(1); + }); + }); + }); + + describe("Db::transaction.create()", function () { + it('should create a single record', function () { + this.tx = this.db.begin(); + return this.tx + .create({ '@class': 'TestClass', name: 'item1' }) - .then(function (record) { - record['@rid'].should.be.an.instanceOf(LIB.RID); - completed = true; + .commit() + .bind(this) + .then(function (results) { + results.created.length.should.equal(1); + results.updated.length.should.equal(0); + results.deleted.length.should.equal(0); }); + }); - this.tx.create({ + it('should create multiple records', function () { + this.tx = this.db.begin(); + return this.tx + .create({ + '@class': 'TestClass', + name: 'item1' + }) + .create({ '@class': 'TestClass', name: 'item2' }) - - this.tx.create({ + .create({ '@class': 'TestClass', name: 'item3' }) - - return this.tx.commit() + .commit() .then(function (results) { - completed.should.be.true; + results.created.length.should.equal(3); + results.updated.length.should.equal(0); + results.deleted.length.should.equal(0); + }); + }); + }); + describe("Db::transaction.update()", function () { + beforeEach(function () { + this.tx = this.db.begin(); + return this.db.record.create([ + { + '@class': 'TestClass', + name: 'updateMe1' + }, + { + '@class': 'TestClass', + name: 'updateMe2' + } + ]) + .bind(this) + .spread(function (first, second) { + this.first = first; + this.second = second; + }); + }); + it('should update a single record', function () { + this.first.foo = 'foo'; + return this.tx + .update(this.first) + .commit() + .then(function (results) { + results.created.length.should.equal(0); + results.updated.length.should.equal(1); + results.deleted.length.should.equal(0); + }); + }); + it('should update multiple records', function () { + this.first.foo = 'foo'; + this.second.baz = 'baz'; + return this.tx + .update(this.first) + .update(this.second) + .commit() + .then(function (results) { + results.created.length.should.equal(0); + results.updated.length.should.equal(2); + results.deleted.length.should.equal(0); }); - }); }); describe("Db::transaction.delete()", function () { + beforeEach(function () { + this.tx = this.db.begin(); + return this.db.record.create([ + { + '@class': 'TestClass', + name: 'deleteMe1' + }, + { + '@class': 'TestClass', + name: 'deleteMe2' + } + ]) + .bind(this) + .spread(function (first, second) { + this.first = first; + this.second = second; + }); + }); + it('should delete a single record', function () { + return this.tx + .delete(this.first) + .commit() + .then(function (results) { + results.created.length.should.equal(0); + results.updated.length.should.equal(0); + results.deleted.length.should.equal(1); + }); + }); + it('should delete multiple records', function () { + return this.tx + .delete(this.first) + .delete(this.second) + .commit() + .then(function (results) { + results.created.length.should.equal(0); + results.updated.length.should.equal(0); + results.deleted.length.should.equal(2); + }); + }); }); - }); \ No newline at end of file From fe8760796678fa7684730702f23e3cace05cb913 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 28 Apr 2014 21:40:53 +0100 Subject: [PATCH 058/308] add transactions to readme --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index b32390b..98461ce 100644 --- a/README.md +++ b/README.md @@ -273,6 +273,22 @@ db.record.delete('#1:1') }); ``` +### Transactions + +```js +db.begin() +.create({'@class': 'MyClass', name: 'me'}) +.create({'@class': 'MyOtherClass', name: 'wat?'}) +.update(myRecord) +.delete(someOtherRecord) +.commit() +.then(function (results) { + console.log('Created ', results.created); + console.log('Updated ', results.updated); + console.log('Deleted ', results.deleted); +}) +``` + ### Listing all the classes in the database ```js From dc26eed7cd7f14bdbf1e2aaef8d13f3ff9ba92a0 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 28 Apr 2014 21:47:45 +0100 Subject: [PATCH 059/308] increase rid bag test timeout --- test/core/bag-test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/core/bag-test.js b/test/core/bag-test.js index e9db04b..a803ff0 100644 --- a/test/core/bag-test.js +++ b/test/core/bag-test.js @@ -90,6 +90,7 @@ describe("RID Bag", function () { describe('Tree Bag', function () { before(function () { + this.timeout(20000); var self = this; return CREATE_TEST_DB(this, 'testdb_dbapi_rid_bag_tree', 'plocal') .bind(this) From 4d85f39e373971f7aa6152bc318cae6be9a25384 Mon Sep 17 00:00:00 2001 From: Samson Radu Date: Tue, 29 Apr 2014 20:47:39 +0300 Subject: [PATCH 060/308] add after-release test --- test/server/db-test.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/test/server/db-test.js b/test/server/db-test.js index 9b0126c..4312cac 100644 --- a/test/server/db-test.js +++ b/test/server/db-test.js @@ -1,9 +1,10 @@ describe("Database commands", function () { + this.timeout(30000); before(function () { return CREATE_TEST_DB(this, 'test_db_commands', 'plocal'); }); after(function () { - return DELETE_TEST_DB('test_db_commands', 'plocal'); + return DELETE_TEST_DB('test_db_commands'); }); describe('Server::freeze()', function () { it("should freeze", function () { @@ -15,8 +16,8 @@ describe("Database commands", function () { it("should allow only read-only operations", function(){ return this.db.record.create({ '@class': 'OUser', - name: 'testuserx', - password: 'testpasswordx', + name: 'testuser1', + password: 'testpassword1', status: 'ACTIVE' }) .bind(this) @@ -35,7 +36,20 @@ describe("Database commands", function () { response.should.be.true; }); }); + it("should allow record creation", function(){ + return this.db.record.create({ + '@class': 'OUser', + name: 'testuser2', + password: 'testpassword2', + status: 'ACTIVE' + }) + .bind(this) + .then(function (record) { + return true; + }) + .catch(LIB.errors.Request, function (e) { + throw new Error('Should never happen!'); + }); + }); }); - - }); From a8f783d84cf330241d2b65ea07be6d10b7e6d3d1 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 30 Apr 2014 17:02:20 +0100 Subject: [PATCH 061/308] numbers are allowed in parameter names --- lib/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index ab55357..845b8a1 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -139,7 +139,7 @@ exports.prepare = function (query, params) { if (!params) { return query; } - var pattern = /"(\\[\s\S]|[^"])*"|'(\\[\s\S]|[^'])*'|:([A-Za-z_-]+|\/\*[\s\S]*?\*\/)/; + var pattern = /"(\\[\s\S]|[^"])*"|'(\\[\s\S]|[^'])*'|:([A-Za-z0-9_-]+|\/\*[\s\S]*?\*\/)/; return query.replace(pattern, function (all, double, single, param) { if (param) { return exports.encode(params[param]); From cb8868a63bc8f98f8922078737973858187cff6f Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 30 Apr 2014 17:03:07 +0100 Subject: [PATCH 062/308] Transaction related sql helpers, reach 250 tests, yay! --- lib/db/statement.js | 74 ++++++++++++++++++++++++++++++++++++- test/db/statement-test.js | 77 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 2 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index eeee15c..28132bd 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -145,12 +145,47 @@ Statement.prototype.limit = function (value) { /** * Specify the fetch plan for the statement. * - * @param {String|Object} args The fetch plan clause + * @param {String|Object} args The fetch plan clause. * @return {Statement} The statement object. */ Statement.prototype.fetch = clause('fetchPlan'); +/** + * Assign a value to a variable within an SQL statement. + * + * > Note: The value will **not** be encoded as it may contain arbitrary SQL expressions. + * > use `Oriento.utils.encode()` if you need to allow safe values here. + * + * @param {String} name The name of the variable to assign + * @param {String|Statement} value The value of the variable, can be an SQL statement. + * @return {Statement} The statement object. + */ +Statement.prototype.let = function (name, value) { + this._state['let'] = this._state['let'] || []; + this._state['let'].push([name, value]); + return this; +}; + +/** + * Specifiy a lock clause for the statement. + * + * @param {String|Object} args The lock clause. + * @return {Statement} The statement object. + */ +Statement.prototype.lock = clause('lock'); + +/** + * Commit a transaction. + * + * @param {Integer} retryLimit The maximum number of times to retry, defaults to 0. + * @return {Statement} The statement object. + */ +Statement.prototype.commit = function (retryLimit) { + this._state.commit = retryLimit || 0; + return this; +}; + /** * Add the given parameter to the query. * @@ -183,12 +218,22 @@ Statement.prototype.addParams = function (params) { /** * Build the statement. - * @return {String} The SQL statement. + * @return {String} The SQL statement. */ Statement.prototype.buildStatement = function () { var statement = [], state = this._state; + if (state.commit !== undefined) { + statement.push('BEGIN\n'); + } + + if (state.let && state.let.length) { + statement.push(state.let.map(function (item) { + return "LET " + item[0] + ' = ' + item[1]; + }).join('\n')); + } + if (state.traverse && state.traverse.length) { statement.push('TRAVERSE'); statement.push(state.traverse.join(', ')); @@ -338,10 +383,35 @@ Statement.prototype.buildStatement = function () { if (state.offset) { statement.push('OFFSET ' + (+state.offset)); } + + if (state.lock) { + statement.push('LOCK ' + state.lock.join(',')); + } + + if (state.commit !== undefined) { + statement.push('COMMIT'); + if (state.commit) { + statement.push('RETRY ' + (+state.commit) + '\n'); + } + else { + statement.push('\n'); + } + } + return statement.join(' '); }; +/** + * Return a string version of the statement, with parameters prepared and bound. + * + * @return {String} The prepared statement. + */ +Statement.prototype.toString = function () { + return utils.prepare(this.buildStatement(), this._state.params); +}; + + /** * Build the options for the statement. * @return {Object} The SQL statement options. diff --git a/test/db/statement-test.js b/test/db/statement-test.js index 17d8b8b..7c7ad39 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -12,6 +12,72 @@ describe("Database API - Statement", function () { this.statement = new Statement(this.db); }); + describe('Statement::let()', function () { + it('should let a variable equal a subexpression', function () { + var sub = (new Statement(this.db)).select('name').from('OUser').where({status: 'ACTIVE'}); + this.statement + .let('names', sub) + .buildStatement() + .should + .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"'); + }); + it('should let a variable equal a subexpression, more than once', function () { + var sub1 = (new Statement(this.db)).select('name').from('OUser').where({status: 'ACTIVE'}), + sub2 = (new Statement(this.db)).select('status').from('OUser'); + this.statement + .let('names', sub1) + .let('statuses', sub2) + .buildStatement() + .should + .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"\nLET statuses = SELECT status FROM OUser'); + }); + it('should let a variable equal a subexpression, more than once, using locks', function () { + var sub1 = (new Statement(this.db)).select('name').from('OUser').where({status: 'ACTIVE'}), + sub2 = (new Statement(this.db)).select('status').from('OUser').lock('record'); + this.statement + .let('names', sub1) + .let('statuses', sub2) + .buildStatement() + .should + .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"\nLET statuses = SELECT status FROM OUser LOCK record'); + }); + }); + + describe('Statement::commit()', function () { + it('should generate an empty transaction', function () { + this.statement + .commit() + .buildStatement() + .should + .equal('BEGIN\n COMMIT \n'); + }); + it('should generate an empty transaction, with retries', function () { + this.statement + .commit(100) + .buildStatement() + .should + .equal('BEGIN\n COMMIT RETRY 100\n'); + }); + it('should generate an update transaction', function () { + this.statement + .update('OUser') + .set({name: 'name'}) + .commit() + .toString() + .should + .equal('BEGIN\n UPDATE OUser SET name = "name" COMMIT \n'); + }); + it('should generate an update transaction, with retries', function () { + this.statement + .update('OUser') + .set({name: 'name'}) + .commit(100) + .toString() + .should + .equal('BEGIN\n UPDATE OUser SET name = "name" COMMIT RETRY 100\n'); + }); + }); + describe('Statement::select()', function () { it('should select all the columns by default', function () { this.statement.select(); @@ -89,4 +155,15 @@ describe("Database API - Statement", function () { this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE ((1=1) OR 2=2 OR 3=3 OR 4=4) AND 5=5'); }); }); + + describe('Statement::lock()', function () { + it('should lock a record', function () { + this.statement.update('OUser').lock('record'); + this.statement.buildStatement().should.equal('UPDATE OUser LOCK record'); + }); + it('should lock a record with an expression', function () { + this.statement.update('OUser').where('1=1').lock('record'); + this.statement.buildStatement().should.equal('UPDATE OUser WHERE 1=1 LOCK record'); + }); + }) }); \ No newline at end of file From 20000a0b7362501584e4d946f33aa0044e4fad03 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 30 Apr 2014 19:43:30 +0100 Subject: [PATCH 063/308] add support for return() --- lib/db/statement.js | 23 +++++++++++++++++------ test/db/statement-test.js | 24 +++++++++++++++++------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index 28132bd..8ec6b12 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -186,6 +186,17 @@ Statement.prototype.commit = function (retryLimit) { return this; }; +/** + * Return a certain variable. + * + * @param {String} name The name of the variable to return. + * @return {Statement} The statement object. + */ +Statement.prototype.return = function (name) { + this._state.return = name; + return this; +}; + /** * Add the given parameter to the query. * @@ -225,13 +236,13 @@ Statement.prototype.buildStatement = function () { state = this._state; if (state.commit !== undefined) { - statement.push('BEGIN\n'); + statement.push('BEGIN'); } if (state.let && state.let.length) { statement.push(state.let.map(function (item) { return "LET " + item[0] + ' = ' + item[1]; - }).join('\n')); + }).join(' ')); } if (state.traverse && state.traverse.length) { @@ -391,13 +402,13 @@ Statement.prototype.buildStatement = function () { if (state.commit !== undefined) { statement.push('COMMIT'); if (state.commit) { - statement.push('RETRY ' + (+state.commit) + '\n'); - } - else { - statement.push('\n'); + statement.push('RETRY ' + (+state.commit)); } } + if (state.return) { + statement.push('RETURN ' + state.return); + } return statement.join(' '); }; diff --git a/test/db/statement-test.js b/test/db/statement-test.js index 7c7ad39..6af5a91 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -29,7 +29,7 @@ describe("Database API - Statement", function () { .let('statuses', sub2) .buildStatement() .should - .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"\nLET statuses = SELECT status FROM OUser'); + .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE" LET statuses = SELECT status FROM OUser'); }); it('should let a variable equal a subexpression, more than once, using locks', function () { var sub1 = (new Statement(this.db)).select('name').from('OUser').where({status: 'ACTIVE'}), @@ -39,24 +39,24 @@ describe("Database API - Statement", function () { .let('statuses', sub2) .buildStatement() .should - .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"\nLET statuses = SELECT status FROM OUser LOCK record'); + .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE" LET statuses = SELECT status FROM OUser LOCK record'); }); }); - describe('Statement::commit()', function () { + describe('Statement::commit() and Statement::return()', function () { it('should generate an empty transaction', function () { this.statement .commit() .buildStatement() .should - .equal('BEGIN\n COMMIT \n'); + .equal('BEGIN COMMIT'); }); it('should generate an empty transaction, with retries', function () { this.statement .commit(100) .buildStatement() .should - .equal('BEGIN\n COMMIT RETRY 100\n'); + .equal('BEGIN COMMIT RETRY 100'); }); it('should generate an update transaction', function () { this.statement @@ -65,7 +65,7 @@ describe("Database API - Statement", function () { .commit() .toString() .should - .equal('BEGIN\n UPDATE OUser SET name = "name" COMMIT \n'); + .equal('BEGIN UPDATE OUser SET name = "name" COMMIT'); }); it('should generate an update transaction, with retries', function () { this.statement @@ -74,7 +74,17 @@ describe("Database API - Statement", function () { .commit(100) .toString() .should - .equal('BEGIN\n UPDATE OUser SET name = "name" COMMIT RETRY 100\n'); + .equal('BEGIN UPDATE OUser SET name = "name" COMMIT RETRY 100'); + }); + it('should generate an update transaction, with returns', function () { + var sub = (new Statement(this.db)).update('OUser').set({name: 'name'}); + this.statement + .let('names', sub) + .commit() + .return('$names') + .toString() + .should + .equal('BEGIN LET names = UPDATE OUser SET name = "name" COMMIT RETURN $names'); }); }); From bdd231dd6d592e3019faf20a43b324c643152121 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 30 Apr 2014 21:55:47 +0100 Subject: [PATCH 064/308] don't overwrite the class name if it's specified --- lib/db/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index 16635df..6bfa900 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -149,14 +149,14 @@ Db.prototype.begin = function () { * @promise {Mixed} The results of the query / command. */ Db.prototype.exec = function (query, options) { + options = options || {}; var data = { query: query, mode: 's', fetchPlan: '', limit: -1, - class: 'com.orientechnologies.orient.core.sql.OCommandSQL' + class: options.class || 'com.orientechnologies.orient.core.sql.OCommandSQL' }; - options = options || {}; if (options.fetchPlan && typeof options.fetchPlan === 'string') { data.fetchPlan = options.fetchPlan; @@ -170,7 +170,7 @@ Db.prototype.exec = function (query, options) { data.mode = options.mode; } - if (data.mode === 'a') { + if (data.mode === 'a' && !options.class) { data.class = 'com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery'; } From af6a00c7850046b8e2aeba8b4be34ddab9c73e7f Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 30 Apr 2014 21:56:18 +0100 Subject: [PATCH 065/308] semi colons to separate transaction query parts --- lib/db/statement.js | 10 +++++++--- test/db/statement-test.js | 10 +++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index 8ec6b12..8bebe2c 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -236,7 +236,7 @@ Statement.prototype.buildStatement = function () { state = this._state; if (state.commit !== undefined) { - statement.push('BEGIN'); + statement.push('BEGIN;'); } if (state.let && state.let.length) { @@ -400,14 +400,15 @@ Statement.prototype.buildStatement = function () { } if (state.commit !== undefined) { - statement.push('COMMIT'); + statement.push(';COMMIT'); if (state.commit) { statement.push('RETRY ' + (+state.commit)); } + statement.push(';'); } if (state.return) { - statement.push('RETURN ' + state.return); + statement.push('RETURN ' + state.return + ';'); } return statement.join(' '); }; @@ -451,6 +452,9 @@ Statement.prototype.buildOptions = function () { return list; }, []).join(' '); } + if (this._state.commit !== undefined) { + opts.class = 'com.orientechnologies.orient.core.command.script.OCommandScript'; + } return opts; }; diff --git a/test/db/statement-test.js b/test/db/statement-test.js index 6af5a91..d56a281 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -49,14 +49,14 @@ describe("Database API - Statement", function () { .commit() .buildStatement() .should - .equal('BEGIN COMMIT'); + .equal('BEGIN; ;COMMIT ;'); }); it('should generate an empty transaction, with retries', function () { this.statement .commit(100) .buildStatement() .should - .equal('BEGIN COMMIT RETRY 100'); + .equal('BEGIN; ;COMMIT RETRY 100 ;'); }); it('should generate an update transaction', function () { this.statement @@ -65,7 +65,7 @@ describe("Database API - Statement", function () { .commit() .toString() .should - .equal('BEGIN UPDATE OUser SET name = "name" COMMIT'); + .equal('BEGIN; UPDATE OUser SET name = "name" ;COMMIT ;'); }); it('should generate an update transaction, with retries', function () { this.statement @@ -74,7 +74,7 @@ describe("Database API - Statement", function () { .commit(100) .toString() .should - .equal('BEGIN UPDATE OUser SET name = "name" COMMIT RETRY 100'); + .equal('BEGIN; UPDATE OUser SET name = "name" ;COMMIT RETRY 100 ;'); }); it('should generate an update transaction, with returns', function () { var sub = (new Statement(this.db)).update('OUser').set({name: 'name'}); @@ -84,7 +84,7 @@ describe("Database API - Statement", function () { .return('$names') .toString() .should - .equal('BEGIN LET names = UPDATE OUser SET name = "name" COMMIT RETURN $names'); + .equal('BEGIN; LET names = UPDATE OUser SET name = "name" ;COMMIT ; RETURN $names;'); }); }); From a4675a9f000dbdf6002272f7f1df30be71dcd1ab Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 30 Apr 2014 21:57:12 +0100 Subject: [PATCH 066/308] expose let() to start a transactional query --- lib/db/index.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/db/index.js b/lib/db/index.js index 6bfa900..8de1497 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -360,6 +360,17 @@ Db.prototype.delete = function () { return query.delete.apply(query, arguments); }; + +/** + * Create a transactional query. + * + * @return {Query} The query instance. + */ +Db.prototype.let = function () { + var query = this.createQuery(); + return query.let.apply(query, arguments); +}; + /** * Escape the given input. * From 9682cbf5507dc300386a49aac111f8330e0ee45e Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 30 Apr 2014 21:58:46 +0100 Subject: [PATCH 067/308] WIP support for OCommandScript, test failing [ci skip] --- .../binary/protocol/operations/command.js | 27 +++++++++++++++---- test/db/query-test.js | 13 +++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/lib/transport/binary/protocol/operations/command.js b/lib/transport/binary/protocol/operations/command.js index 73d589d..ab4d98b 100644 --- a/lib/transport/binary/protocol/operations/command.js +++ b/lib/transport/binary/protocol/operations/command.js @@ -9,7 +9,7 @@ module.exports = Operation.extend({ id: 'REQUEST_COMMAND', opCode: 41, writer: function () { - if (this.data.mode === 'a') { + if (this.data.mode === 'a' && !this.data.class) { this.data.class = 'com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery'; } this @@ -20,13 +20,12 @@ module.exports = Operation.extend({ }, serializeQuery: function () { - var buffers = [ - writer.writeString(this.data.class), - writer.writeString(this.data.query) - ]; + var buffers = [writer.writeString(this.data.class)]; + if (this.data.class === 'com.orientechnologies.orient.core.sql.query.OSQLSynchQuery' || this.data.class === 'com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery') { buffers.push( + writer.writeString(this.data.query), writer.writeInt(this.data.limit), writer.writeString(this.data.fetchPlan || '') ); @@ -38,7 +37,25 @@ module.exports = Operation.extend({ buffers.push(writer.writeInt(0)); } } + else if (this.data.class === 'com.orientechnologies.orient.core.command.script.OCommandScript') { + buffers.push( + writer.writeString(this.data.language || 'sql'), + writer.writeString(this.data.query) + ); + + if (this.data.params && this.data.params.params && Object.keys(this.data.params.params).length) { + buffers.push( + writer.writeBoolean(true), + writer.writeString(serializeParams(this.data.params)) + ); + } + else { + buffers.push(writer.writeBoolean(false)); + } + buffers.push(writer.writeByte(0)); + } else { + buffers.push(writer.writeString(this.data.query)); if (this.data.params) { buffers.push( writer.writeBoolean(true), diff --git a/test/db/query-test.js b/test/db/query-test.js index a777b10..586a6f8 100644 --- a/test/db/query-test.js +++ b/test/db/query-test.js @@ -304,4 +304,17 @@ describe("Database API - Query", function () { }); }); }); + + describe('Transactional Queries', function () { + it('should execute a simple transaction', function () { + return this.db + .update('OUser') + .set({newField: true}) + .commit() + .all() + .then(function (results) { + console.log(results); + }); + }); + }); }); \ No newline at end of file From b14484beea279700f30dea370c10a560eda72c3f Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 2 May 2014 22:04:14 +0100 Subject: [PATCH 068/308] use shortened class name --- lib/db/statement.js | 2 +- lib/transport/binary/protocol/operations/command.js | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index 8bebe2c..d773bd3 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -453,7 +453,7 @@ Statement.prototype.buildOptions = function () { }, []).join(' '); } if (this._state.commit !== undefined) { - opts.class = 'com.orientechnologies.orient.core.command.script.OCommandScript'; + opts.class = 's'; } return opts; }; diff --git a/lib/transport/binary/protocol/operations/command.js b/lib/transport/binary/protocol/operations/command.js index ab4d98b..5b6861f 100644 --- a/lib/transport/binary/protocol/operations/command.js +++ b/lib/transport/binary/protocol/operations/command.js @@ -22,7 +22,8 @@ module.exports = Operation.extend({ serializeQuery: function () { var buffers = [writer.writeString(this.data.class)]; - if (this.data.class === 'com.orientechnologies.orient.core.sql.query.OSQLSynchQuery' || + if (this.data.class === 'q' || + this.data.class === 'com.orientechnologies.orient.core.sql.query.OSQLSynchQuery' || this.data.class === 'com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery') { buffers.push( writer.writeString(this.data.query), @@ -37,12 +38,13 @@ module.exports = Operation.extend({ buffers.push(writer.writeInt(0)); } } - else if (this.data.class === 'com.orientechnologies.orient.core.command.script.OCommandScript') { + else if ( + this.data.class === 's' || + this.data.class === 'com.orientechnologies.orient.core.command.script.OCommandScript') { buffers.push( writer.writeString(this.data.language || 'sql'), writer.writeString(this.data.query) ); - if (this.data.params && this.data.params.params && Object.keys(this.data.params.params).length) { buffers.push( writer.writeBoolean(true), From 41c8f00acc3dbfbeaac897baffedd8a07d2437cb Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 2 May 2014 22:06:05 +0100 Subject: [PATCH 069/308] only run failing test for now --- test/db/query-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/db/query-test.js b/test/db/query-test.js index 586a6f8..d37b343 100644 --- a/test/db/query-test.js +++ b/test/db/query-test.js @@ -305,7 +305,7 @@ describe("Database API - Query", function () { }); }); - describe('Transactional Queries', function () { + describe.only('Transactional Queries', function () { it('should execute a simple transaction', function () { return this.db .update('OUser') From 0170af36c1fe3c61cba2eaa2421381e2cf1ee3f6 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sat, 3 May 2014 00:49:23 +0100 Subject: [PATCH 070/308] skip test due to 1.7RC2 bug --- test/db/query-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/db/query-test.js b/test/db/query-test.js index d37b343..cad9205 100644 --- a/test/db/query-test.js +++ b/test/db/query-test.js @@ -305,7 +305,7 @@ describe("Database API - Query", function () { }); }); - describe.only('Transactional Queries', function () { + describe.skip('Transactional Queries', function () { it('should execute a simple transaction', function () { return this.db .update('OUser') From 250169abcd68bc1c08db1b8da816d5689d0d58ac Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sat, 3 May 2014 22:29:54 +0100 Subject: [PATCH 071/308] support overriding the comparison operator for where clauses --- lib/db/statement.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index d773bd3..9b4e3e8 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -320,7 +320,8 @@ Statement.prototype.buildStatement = function () { statement.push('WHERE'); statement.push(state.where.reduce(function (accumulator, item) { var op = item[0], - condition = item[1]; + condition = item[1], + comparisonOperator = item[2]; if (condition == null) { accumulator[0] = op; @@ -328,7 +329,7 @@ Statement.prototype.buildStatement = function () { } if (typeof condition === 'object') { - condition = this._objectToCondition(condition); + condition = this._objectToCondition(condition, comparisonOperator); } if (condition === false) { @@ -458,16 +459,17 @@ Statement.prototype.buildOptions = function () { return opts; }; -Statement.prototype._objectToCondition = function (obj) { +Statement.prototype._objectToCondition = function (obj, operator) { var conditions = [], params = {}, keys = Object.keys(obj), total = keys.length, key, i, paramName; + operator = operator || '='; for (i = 0; i < total; i++) { key = keys[i]; paramName = 'param' + paramify(key) + (this._state.paramIndex++); - conditions.push(key + ' = :' + paramName); + conditions.push(key + ' ' + operator + ' :' + paramName); this.addParam(paramName, obj[key] instanceof RID ? ''+obj[key] : obj[key]); } @@ -528,10 +530,11 @@ function clause (name) { }; } -function whereClause (operator) { +function whereClause (operator, comparisonOperator) { + comparisonOperator = comparisonOperator || '='; return function (condition, params) { this._state.where = this._state.where || []; - this._state.where.push([operator, condition]); + this._state.where.push([operator, condition, comparisonOperator]); if (params) { this.addParams(params); } From 20c1a915a82522cc67ffb2c4ee04c9dde7f51ac4 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sat, 3 May 2014 22:36:36 +0100 Subject: [PATCH 072/308] add Statement::containsText() --- lib/db/statement.js | 8 ++++++++ test/db/statement-test.js | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/db/statement.js b/lib/db/statement.js index 9b4e3e8..9f0cbef 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -88,6 +88,14 @@ Statement.prototype.set = clause('set'); */ Statement.prototype.where = whereClause('and'); +/** + * Specifiy a where clause, using the `CONTAINSTEXT` comparison operator. + * + * @param {Object} condition The map of field names to values. + * @return {Statement} The statement object. + */ +Statement.prototype.containsText = whereClause('and', 'CONTAINSTEXT'); + /** * Specify an `AND` condition. * diff --git a/test/db/statement-test.js b/test/db/statement-test.js index d56a281..e0c69f5 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -166,6 +166,16 @@ describe("Database API - Statement", function () { }); }); + describe('Statement::containsText()', function () { + it('should build a where clause with a map of values', function () { + this.statement.select().from('OUser').containsText({ + name: 'root', + foo: 'bar' + }); + this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE (name CONTAINSTEXT :paramname0 AND foo CONTAINSTEXT :paramfoo1)'); + }); + }); + describe('Statement::lock()', function () { it('should lock a record', function () { this.statement.update('OUser').lock('record'); From 6055be1a3b40409b2315ec69d09c8cd7ead78067 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sat, 3 May 2014 22:37:45 +0100 Subject: [PATCH 073/308] update README, fix #45 --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 98461ce..83aee5d 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,15 @@ db.select().from('OUser').where({status: 'ACTIVE'}).all() }); ``` +### Query Builder: Text Search + +```js +db.select().from('OUser').containsText({name: 'er'}).all() +.then(function (users) { + console.log('found users', users); +}); +``` + ### Query Builder: Select Records with Fetch Plan ```js From d3a6a52b588cb02d16568b9158e7c072b27a8c69 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 4 May 2014 00:19:13 +0100 Subject: [PATCH 074/308] Remove 'alpha' warning --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 83aee5d..645666c 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,6 @@ A lightweight node.js driver for [orientdb](http://www.orientechnologies.com/ori [![Build Status](https://travis-ci.org/codemix/oriento.svg?branch=master)](https://travis-ci.org/codemix/oriento) -> **status: alpha** -> This is work in progress, alpha quality software. -> Please [report any bugs](https://github.com/codemix/oriento/issues) you find so that we can improve the library for everyone. - # Supported Versions From 81ff4eff05bc7bebfd32ef24b4ccf740b2355df8 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 5 May 2014 14:08:56 +0100 Subject: [PATCH 075/308] download snapshots via maven --- .travis.yml | 8 +++++++- ci/initialize-ci.sh | 2 +- ci/odb-shared.sh | 44 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9d128e4..b4bd9b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,4 +2,10 @@ language: node_js node_js: - "0.10" before_script: - - sh -c ./ci/initialize-ci.sh 1.7-rc2 + - ./ci/initialize-ci.sh $ORIENTDB_VERSION +env: + - ORIENTDB_VERSION=1.7-rc2 + - ORIENTDB_VERSION=1.7-SNAPSHOT +matrix: + allow_failures: + - env: ORIENTDB_VERSION=1.7-SNAPSHOT \ No newline at end of file diff --git a/ci/initialize-ci.sh b/ci/initialize-ci.sh index 2bc09df..f836425 100755 --- a/ci/initialize-ci.sh +++ b/ci/initialize-ci.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash PARENT_DIR=$(dirname $(cd "$(dirname "$0")"; pwd)) CI_DIR="$PARENT_DIR/ci/environment" diff --git a/ci/odb-shared.sh b/ci/odb-shared.sh index 8cfed09..bf342fe 100755 --- a/ci/odb-shared.sh +++ b/ci/odb-shared.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash odb_compare_version () { # TODO: this function does not handle well versions with additional rank @@ -36,8 +36,15 @@ odb_download () { } odb_download_server () { - #http://www.orientdb.org/portal/function/portal/download/phpuser@unknown.com/%20/%20/%20/%20/unknown/orientdb-community-1.6.2.tar.gz/false/false + if [[ $1 == *SNAPSHOT ]]; then + odb_download_via_mvn $1 $2; + else + odb_download_via_website $1 $2; + fi; + +} +odb_download_via_website () { COMMIT_HASH=$(git rev-parse HEAD) DOWN_USER=oriento+travis${COMMIT_HASH}@codemix.com ODB_VERSION=$1 @@ -54,8 +61,6 @@ odb_download_server () { ODB_PACKAGE_URL="http://www.orientdb.org/portal/function/portal/download/${DOWN_USER}/%20/%20/%20/%20/unknown/${ODB_PACKAGE}.${ODB_PACKAGE_EXT}/false/false" ODB_C_PACKAGE=${ODB_PACKAGE}.${ODB_PACKAGE_EXT} - echo ${ODB_PACKAGE_URL} - odb_download $ODB_PACKAGE_URL $CI_DIR ODB_PACKAGE_PATH="${CI_DIR}/${ODB_PACKAGE}.${ODB_PACKAGE_EXT}" @@ -65,3 +70,34 @@ odb_download_server () { tar xf $ODB_PACKAGE_PATH -C $CI_DIR fi } + +odb_download_via_mvn () { + ODB_VERSION=$1 + CI_DIR=$2 + + ODB_PACKAGE="orientdb-community-${ODB_VERSION}" + + + ODB_PACKAGE_EXT="tar.gz" + ODB_C_PACKAGE=${ODB_PACKAGE}.${ODB_PACKAGE_EXT} + + OUTPUT_DIR="${2:-$(pwd)}" + + if [ ! -d "$OUTPUT_DIR" ]; then + mkdir "$OUTPUT_DIR" + fi + if odb_command_exists "mvn" ; then + mvn org.apache.maven.plugins:maven-dependency-plugin:2.8:get -Dartifact=com.orientechnologies:orientdb-community:"${ODB_VERSION}":"${ODB_PACKAGE_EXT}":distribution -DremoteRepositories=https://oss.sonatype.org/content/repositories/snapshots/ -Ddest="$OUTPUT_DIR/$ODB_C_PACKAGE" + else + echo "Cannot download $1 [maven is not installed]" + exit 1 + fi + + ODB_PACKAGE_PATH="${CI_DIR}/${ODB_PACKAGE}.${ODB_PACKAGE_EXT}" + + if [ $ODB_PACKAGE_EXT = "zip" ]; then + unzip -q $ODB_PACKAGE_PATH -d ${CI_DIR} + elif [ $ODB_PACKAGE_EXT = "tar.gz" ]; then + tar xf $ODB_PACKAGE_PATH -C $CI_DIR + fi; +} From 6a398340961e652ffa3db0ec5f46f3fc45a509f3 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sat, 10 May 2014 23:39:45 +0100 Subject: [PATCH 076/308] prepare 0.3.0 release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c53ca77..73cb04c 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node", "node.js" ], - "version": "0.2.1", + "version": "0.3.0", "author": { "name": "Charles Pick", "email": "charles@codemix.com" From 2edf99296addb8a4e0a16d83aa6f42ef791fae70 Mon Sep 17 00:00:00 2001 From: Samson Radu Date: Tue, 13 May 2014 16:23:21 +0300 Subject: [PATCH 077/308] rename server|db|cluster|index .delete() into .drop() --- lib/cli/commands/db.js | 6 +++--- lib/db/class/index.js | 2 +- lib/db/cluster.js | 2 +- lib/db/index/index.js | 6 +++--- lib/server/index.js | 5 +---- test/bugs/27-slow.js | 1 + test/core/bag-test.js | 1 + test/db/class-test.js | 4 ++-- test/db/cluster-test.js | 8 ++++---- test/db/db-test.js | 2 +- test/db/index-test.js | 4 ++-- test/index.js | 6 +++--- test/server/server-test.js | 2 +- 13 files changed, 24 insertions(+), 25 deletions(-) diff --git a/lib/cli/commands/db.js b/lib/cli/commands/db.js index da1fca6..4822f1f 100644 --- a/lib/cli/commands/db.js +++ b/lib/cli/commands/db.js @@ -33,14 +33,14 @@ exports.list = function () { }; /** - * Delete a database. + * Drop a database. * * @param {String} name The name of the database. * @param {String} storage The storage type, defaults to plocal. */ -exports.delete = function (name, storage) { +exports.drop = function (name, storage) { console.log('Deleting database with name: ' + name); - return this.server.delete({ + return this.server.drop({ name: name, storage: storage }) diff --git a/lib/db/class/index.js b/lib/db/class/index.js index 224854a..e42d89d 100644 --- a/lib/db/class/index.js +++ b/lib/db/class/index.js @@ -234,7 +234,7 @@ exports.create = function (name, parentName, cluster) { * @param {String} name The name of the class to delete. * @promise {Db} The database instance. */ -exports.delete = function (name) { +exports.drop = function (name) { return this.exec('DROP CLASS ' + name) .bind(this) .then(function () { diff --git a/lib/db/cluster.js b/lib/db/cluster.js index a95cf87..7d5cd9e 100644 --- a/lib/db/cluster.js +++ b/lib/db/cluster.js @@ -129,7 +129,7 @@ exports.getById = function (id, refresh) { * @param {String} nameOrId The name or id of the cluster. * @promise {Db} The database. */ -exports.delete = function (nameOrId) { +exports.drop = function (nameOrId) { return this.cluster.get(nameOrId) .bind(this) .then(function (cluster) { diff --git a/lib/db/index/index.js b/lib/db/index/index.js index ba10229..3d289a9 100644 --- a/lib/db/index/index.js +++ b/lib/db/index/index.js @@ -187,12 +187,12 @@ Index.create = function (config) { /** - * Delete an index. + * Drop an index. * - * @param {String} name The name of the index to delete. + * @param {String} name The name of the index to drop. * @promise {Db} The database instance. */ -Index.delete = function (name) { +Index.drop = function (name) { return this.exec('DROP INDEX ' + name) .bind(this) .then(function () { diff --git a/lib/server/index.js b/lib/server/index.js index f9d8c62..59941d6 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -186,7 +186,7 @@ Server.prototype.create = function (config) { * @param {String|Object} config The database name or configuration object. * @promise {Mixed} The server response. */ -Server.prototype.delete = function (config) { +Server.prototype.drop = function (config) { config = config || ''; if (typeof config === 'string' || typeof config === 'number') { @@ -210,9 +210,6 @@ Server.prototype.delete = function (config) { .return(true); }; -// deprecated name -Server.prototype.drop = Server.prototype.delete; - /** * List all the databases on the server. * diff --git a/test/bugs/27-slow.js b/test/bugs/27-slow.js index 9512c8c..6fc870a 100644 --- a/test/bugs/27-slow.js +++ b/test/bugs/27-slow.js @@ -1,6 +1,7 @@ var Promise = require('bluebird'); describe("Bug #27: Slow compared to Restful API", function () { + this.timeout(10 * 10000); var LIMIT = 5000; before(function () { return CREATE_TEST_DB(this, 'testdb_bug_27_slow') diff --git a/test/core/bag-test.js b/test/core/bag-test.js index e9db04b..8817b7d 100644 --- a/test/core/bag-test.js +++ b/test/core/bag-test.js @@ -90,6 +90,7 @@ describe("RID Bag", function () { describe('Tree Bag', function () { before(function () { + this.timeout(10 * 10000); var self = this; return CREATE_TEST_DB(this, 'testdb_dbapi_rid_bag_tree', 'plocal') .bind(this) diff --git a/test/db/class-test.js b/test/db/class-test.js index 4857d11..ac08ed9 100644 --- a/test/db/class-test.js +++ b/test/db/class-test.js @@ -38,9 +38,9 @@ describe("Database API - Class", function () { }); }); - describe('Db::class.delete()', function () { + describe('Db::class.drop()', function () { it('should delete a class with the given name', function () { - return this.db.class.delete('TestClass'); + return this.db.class.drop('TestClass'); }); }); diff --git a/test/db/cluster-test.js b/test/db/cluster-test.js index 92b0f69..c80e3cd 100644 --- a/test/db/cluster-test.js +++ b/test/db/cluster-test.js @@ -11,7 +11,7 @@ describe("Database API - Cluster", function () { }); }); after(function () { - return TEST_SERVER.delete({ + return TEST_SERVER.drop({ name: 'testdb_dbapi_cluster', storage: 'memory' }); @@ -86,10 +86,10 @@ describe("Database API - Cluster", function () { }); }); - describe('Db::cluster.delete()', function () { + describe('Db::cluster.drop()', function () { it('should delete a cluster with the given name', function () { - return this.db.cluster.delete('mycluster'); + return this.db.cluster.drop('mycluster'); }); }); -}); \ No newline at end of file +}); diff --git a/test/db/db-test.js b/test/db/db-test.js index 7e07146..f5c3855 100644 --- a/test/db/db-test.js +++ b/test/db/db-test.js @@ -11,7 +11,7 @@ describe("Database API", function () { }); }); after(function () { - return TEST_SERVER.delete({ + return TEST_SERVER.drop({ name: 'testdb_dbapi', storage: 'memory' }); diff --git a/test/db/index-test.js b/test/db/index-test.js index 8a37361..79d03bb 100644 --- a/test/db/index-test.js +++ b/test/db/index-test.js @@ -54,9 +54,9 @@ describe("Database API - Index", function () { }); }); - describe('Db::index.delete()', function () { + describe('Db::index.drop()', function () { it('should delete an index', function () { - return this.db.index.delete('TestClass.name'); + return this.db.index.drop('TestClass.name'); }); }); diff --git a/test/index.js b/test/index.js index d0ac662..b85384e 100644 --- a/test/index.js +++ b/test/index.js @@ -50,7 +50,7 @@ function createTestDb(server, context, name, type) { return server.exists(name, type) .then(function (exists) { if (exists) { - return server.delete({ + return server.drop({ name: name, storage: type }); @@ -77,7 +77,7 @@ function deleteTestDb (server, name, type) { return server.exists(name, type) .then(function (exists) { if (exists) { - return server.delete({ + return server.drop({ name: name, storage: type }); @@ -89,4 +89,4 @@ function deleteTestDb (server, name, type) { .then(function () { return undefined; }); -} \ No newline at end of file +} diff --git a/test/server/server-test.js b/test/server/server-test.js index 53d40ce..d41fa29 100644 --- a/test/server/server-test.js +++ b/test/server/server-test.js @@ -55,7 +55,7 @@ describe('Server::exists()', function () { }); describe('Server::delete()', function () { it("should delete a database", function () { - return TEST_SERVER.delete({ + return TEST_SERVER.drop({ name: 'testdb_server', type: 'graph', storage: 'memory' From 15906440c4d086935915c5375c8a4f67d3080b4f Mon Sep 17 00:00:00 2001 From: Samson Radu Date: Wed, 14 May 2014 00:45:59 +0300 Subject: [PATCH 078/308] limit issue in query execution --- test/db/query-test.js | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/test/db/query-test.js b/test/db/query-test.js index cad9205..c3ac4c3 100644 --- a/test/db/query-test.js +++ b/test/db/query-test.js @@ -1,6 +1,6 @@ var Query = require('../../lib/db/query'); -describe("Database API - Query", function () { +describe.only("Database API - Query", function () { before(function () { return CREATE_TEST_DB(this, 'testdb_dbapi_query'); @@ -304,6 +304,32 @@ describe("Database API - Query", function () { }); }); }); + describe('Db::execute()', function() { + it('should execute a query string (without limit)', function () { + return this.db.exec('select from OUser where name=:name', { + params: { + name: 'reader' + } + }) + .then(function (result){ + result.results[0].content.length.should.be.above(0); + }); + }); + }); + describe('Db::execute()', function() { + it('should execute a query string (with limit)', function () { + return this.db.exec('select from OUser where name=:name', { + params: { + name: 'reader' + }, + limit: 5 + }) + .then(function (result){ + expect(result.results[0].content.length).to.equal(undefined); + }); + }); + }); + describe.skip('Transactional Queries', function () { it('should execute a simple transaction', function () { @@ -317,4 +343,4 @@ describe("Database API - Query", function () { }); }); }); -}); \ No newline at end of file +}); From 8f417e1208d0809393ea1313437e36e196d1f52b Mon Sep 17 00:00:00 2001 From: Samson Radu Date: Wed, 14 May 2014 00:51:28 +0300 Subject: [PATCH 079/308] break test --- test/db/query-test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/db/query-test.js b/test/db/query-test.js index c3ac4c3..c79fae8 100644 --- a/test/db/query-test.js +++ b/test/db/query-test.js @@ -1,6 +1,6 @@ var Query = require('../../lib/db/query'); -describe.only("Database API - Query", function () { +describe("Database API - Query", function () { before(function () { return CREATE_TEST_DB(this, 'testdb_dbapi_query'); @@ -312,11 +312,10 @@ describe.only("Database API - Query", function () { } }) .then(function (result){ + Array.isArray(result.results[0].content).should.be.true; result.results[0].content.length.should.be.above(0); }); }); - }); - describe('Db::execute()', function() { it('should execute a query string (with limit)', function () { return this.db.exec('select from OUser where name=:name', { params: { @@ -325,7 +324,8 @@ describe.only("Database API - Query", function () { limit: 5 }) .then(function (result){ - expect(result.results[0].content.length).to.equal(undefined); + Array.isArray(result.results[0].content).should.be.true; + result.results[0].content.length.should.be.above(0); }); }); }); From 335bf44cedb50d5ccd658edf1e6a68c15164fba4 Mon Sep 17 00:00:00 2001 From: Samson Radu Date: Wed, 14 May 2014 01:14:27 +0300 Subject: [PATCH 080/308] add query execution tests --- README.md | 27 +++++++++++++++++++++++++++ test/db/query-test.js | 13 +++++++------ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 564685b..2521bc2 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,33 @@ var db = server.use({ console.log('Using database: ' + db.name); ``` +### Execute a Query String with Params + +```js +db.query('select from OUser where name=:name', { + params: { + name: 'Radu' + }, + limit: 1 +}).then(function (results){ + console.log(results); +}); + +``` + +### Raw Execution of a Query String with Params + +```js +db.exec('select from OUser where name=:name', { + params: { + name: 'Radu' + } +}).then(function (response){ + console.log(response.results); +}); + +``` + ### Query Builder: Insert Record ```js diff --git a/test/db/query-test.js b/test/db/query-test.js index c79fae8..67b7aa7 100644 --- a/test/db/query-test.js +++ b/test/db/query-test.js @@ -305,7 +305,7 @@ describe("Database API - Query", function () { }); }); describe('Db::execute()', function() { - it('should execute a query string (without limit)', function () { + it('should exec a raw command', function () { return this.db.exec('select from OUser where name=:name', { params: { name: 'reader' @@ -316,16 +316,17 @@ describe("Database API - Query", function () { result.results[0].content.length.should.be.above(0); }); }); - it('should execute a query string (with limit)', function () { - return this.db.exec('select from OUser where name=:name', { + it('should execute a query string', function () { + return this.db.query('select from OUser where name=:name', { params: { name: 'reader' }, - limit: 5 + limit: 1 }) .then(function (result){ - Array.isArray(result.results[0].content).should.be.true; - result.results[0].content.length.should.be.above(0); + Array.isArray(result).should.be.true; + result.length.should.be.above(0); + (result[0]['@class']).should.eql('OUser'); }); }); }); From 7a950ce3ebc90bd153653c2c1f9119b39d301497 Mon Sep 17 00:00:00 2001 From: Samson Radu Date: Wed, 14 May 2014 01:47:26 +0300 Subject: [PATCH 081/308] add new tests. README adjustments. --- README.md | 13 ++++++++++++- test/db/query-test.js | 25 +++++++++++++++++++------ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2521bc2..63a34a6 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,18 @@ var db = server.use({ console.log('Using database: ' + db.name); ``` -### Execute a Query String with Params +### Execute an Insert Query + +```js +db.query('insert into OUser (name, password, status) values ("Radu", "mypassword", "active")') +.then(function (response){ + console.log(response); //an Array of records inserted +}); + +``` + + +### Execute a Select Query with Params ```js db.query('select from OUser where name=:name', { diff --git a/test/db/query-test.js b/test/db/query-test.js index 67b7aa7..e4328d2 100644 --- a/test/db/query-test.js +++ b/test/db/query-test.js @@ -305,10 +305,16 @@ describe("Database API - Query", function () { }); }); describe('Db::execute()', function() { - it('should exec a raw command', function () { + it('should execute an insert query', function () { + return this.db.query('insert into OUser (name, password, status) values ("Radu", "mypassword", "active")') + .then(function (response){ + response[0].name.should.equal('Radu'); + }); + }); + it('should exec a raw select command', function () { return this.db.exec('select from OUser where name=:name', { params: { - name: 'reader' + name: 'Radu' } }) .then(function (result){ @@ -316,10 +322,10 @@ describe("Database API - Query", function () { result.results[0].content.length.should.be.above(0); }); }); - it('should execute a query string', function () { + it('should execute a selectquery string', function () { return this.db.query('select from OUser where name=:name', { params: { - name: 'reader' + name: 'Radu' }, limit: 1 }) @@ -329,9 +335,16 @@ describe("Database API - Query", function () { (result[0]['@class']).should.eql('OUser'); }); }); + it('should execute a delete query', function () { + return this.db.query('delete from OUser where name=:name', { + params: { + name: 'Radu' + } + }).then(function (response){ + response[0].should.eql('1'); + }); + }); }); - - describe.skip('Transactional Queries', function () { it('should execute a simple transaction', function () { return this.db From dafbc1663d8032e0f3fd83602c383d50d0d0c24c Mon Sep 17 00:00:00 2001 From: Samson Radu Date: Wed, 14 May 2014 02:00:02 +0300 Subject: [PATCH 082/308] test adjustments. bind params --- README.md | 11 +++++++++-- test/db/query-test.js | 22 +++++++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 63a34a6..c72a7c8 100644 --- a/README.md +++ b/README.md @@ -119,8 +119,15 @@ console.log('Using database: ' + db.name); ### Execute an Insert Query ```js -db.query('insert into OUser (name, password, status) values ("Radu", "mypassword", "active")') -.then(function (response){ +db.query('insert into OUser (name, password, status) values (:name, :password, :status)', + { + params: { + name: 'Radu', + password: 'mypassword', + status: 'active' + } + } +).then(function (response){ console.log(response); //an Array of records inserted }); diff --git a/test/db/query-test.js b/test/db/query-test.js index e4328d2..d091b14 100644 --- a/test/db/query-test.js +++ b/test/db/query-test.js @@ -304,17 +304,25 @@ describe("Database API - Query", function () { }); }); }); - describe('Db::execute()', function() { + describe('Db::query()', function() { it('should execute an insert query', function () { - return this.db.query('insert into OUser (name, password, status) values ("Radu", "mypassword", "active")') - .then(function (response){ - response[0].name.should.equal('Radu'); + return this.db.query('insert into OUser (name, password, status) values (:name, :password, :status)', + { + params: { + name: 'Samson', + password: 'mypassword', + status: 'active' + } + } + ).then(function (response){ + console.log(response) + response[0].name.should.equal('Samson'); }); }); it('should exec a raw select command', function () { return this.db.exec('select from OUser where name=:name', { params: { - name: 'Radu' + name: 'Samson' } }) .then(function (result){ @@ -325,7 +333,7 @@ describe("Database API - Query", function () { it('should execute a selectquery string', function () { return this.db.query('select from OUser where name=:name', { params: { - name: 'Radu' + name: 'Samson' }, limit: 1 }) @@ -338,7 +346,7 @@ describe("Database API - Query", function () { it('should execute a delete query', function () { return this.db.query('delete from OUser where name=:name', { params: { - name: 'Radu' + name: 'Samson' } }).then(function (response){ response[0].should.eql('1'); From c0fed56b72e8692bf1f74d0da1cb18c7a5033fcd Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sat, 24 May 2014 22:27:29 +0100 Subject: [PATCH 083/308] allow connections to be closed properly --- lib/transport/binary/connection-pool.js | 18 +++++++++++++++++- lib/transport/binary/connection.js | 13 +++++++++++++ lib/transport/binary/index.js | 7 ++----- test/bugs/59-close-connection.js | 22 ++++++++++++++++++++++ 4 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 test/bugs/59-close-connection.js diff --git a/lib/transport/binary/connection-pool.js b/lib/transport/binary/connection-pool.js index 5fbd072..9cf50be 100644 --- a/lib/transport/binary/connection-pool.js +++ b/lib/transport/binary/connection-pool.js @@ -2,7 +2,8 @@ var Connection = require('./connection'), util = require('util'), - EventEmitter = require('events').EventEmitter; + EventEmitter = require('events').EventEmitter, + Promise = require('bluebird'); function ConnectionPool (config) { EventEmitter.call(this); @@ -63,4 +64,19 @@ ConnectionPool.prototype.send = function (operation, options) { .then(function (connection) { return connection.send(operation, options); }); +}; + + +/** + * Close all the connections in the pool. + * + * @return {ConnectionPool} The connection pool with sockets closed. + */ +ConnectionPool.prototype.close = function () { + this.connections.forEach(function (connection) { + connection.close(); + }); + this.connections = []; + this.index = -1; + return this; }; \ No newline at end of file diff --git a/lib/transport/binary/connection.js b/lib/transport/binary/connection.js index 6049249..63cc105 100644 --- a/lib/transport/binary/connection.js +++ b/lib/transport/binary/connection.js @@ -126,6 +126,19 @@ Connection.prototype.createSocket = function () { return socket; }; +/** + * Close the socket. + * + * @return {Connection} The now closed connection. + */ +Connection.prototype.close = function () { + if (this.socket) { + this.socket.end(); + this.socket = null; + } + return this; +}; + /** * Negotiate a connection to the server. * diff --git a/lib/transport/binary/index.js b/lib/transport/binary/index.js index 5c69e5c..81a39cb 100644 --- a/lib/transport/binary/index.js +++ b/lib/transport/binary/index.js @@ -167,12 +167,9 @@ BinaryTransport.prototype.send = function (operation, options) { /** * Close the connection to the server. * - * @return {Server} the disconnected server instance + * @return {BinaryTransport} the disconnected transport instance */ BinaryTransport.prototype.close = function () { - if (!this.closing && this.socket) { - this.closing = false; - this.sessionId = -1; - } + (this.pool || this.connection).close(); return this; }; \ No newline at end of file diff --git a/test/bugs/59-close-connection.js b/test/bugs/59-close-connection.js new file mode 100644 index 0000000..398a1dc --- /dev/null +++ b/test/bugs/59-close-connection.js @@ -0,0 +1,22 @@ +describe("Bug #59: hang on closing server connection", function () { + it('should close the server connection correctly', function () { + var server = new LIB.Server({ + host: TEST_SERVER_CONFIG.host, + port: TEST_SERVER_CONFIG.port, + username: TEST_SERVER_CONFIG.username, + password: TEST_SERVER_CONFIG.password, + transport: 'binary' + }); + + var db = server.use('GratefulDeadConcerts'); + + return db.class.list() + .then(function (classes) { + classes.length.should.be.above(1); + return server.close(); + }) + .then(function (server) { + expect(server.transport.connection.socket).to.equal(null); + }); + }); +}); \ No newline at end of file From b6356e509a859668d08c280f67d1be797efa7f61 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sat, 24 May 2014 22:30:18 +0100 Subject: [PATCH 084/308] clean up --- test/db/query-test.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/db/query-test.js b/test/db/query-test.js index d091b14..4447883 100644 --- a/test/db/query-test.js +++ b/test/db/query-test.js @@ -314,8 +314,7 @@ describe("Database API - Query", function () { status: 'active' } } - ).then(function (response){ - console.log(response) + ).then(function (response){ response[0].name.should.equal('Samson'); }); }); @@ -335,7 +334,7 @@ describe("Database API - Query", function () { params: { name: 'Samson' }, - limit: 1 + limit: 1 }) .then(function (result){ Array.isArray(result).should.be.true; @@ -348,7 +347,7 @@ describe("Database API - Query", function () { params: { name: 'Samson' } - }).then(function (response){ + }).then(function (response){ response[0].should.eql('1'); }); }); From aa075fb6d99283a8622b5144121353d13235de5b Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 27 May 2014 21:41:31 +0100 Subject: [PATCH 085/308] change test password to something less obtuse --- ci/orientdb-server-config.xml | 2 +- test/fixtures/oriento.opts | 2 +- test/test-server.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/orientdb-server-config.xml b/ci/orientdb-server-config.xml index 5e29b58..a402599 100644 --- a/ci/orientdb-server-config.xml +++ b/ci/orientdb-server-config.xml @@ -54,7 +54,7 @@ - + diff --git a/test/fixtures/oriento.opts b/test/fixtures/oriento.opts index f6dfd83..79a3afd 100644 --- a/test/fixtures/oriento.opts +++ b/test/fixtures/oriento.opts @@ -1,3 +1,3 @@ --server=localhost --port=2424 ---password=3BA5DB89CC6206DBF835B36B70FF8A0EDCEFA617A229F0D44D1D726ABA04216A +--password=root diff --git a/test/test-server.json b/test/test-server.json index a2dcfb1..6d36843 100644 --- a/test/test-server.json +++ b/test/test-server.json @@ -3,5 +3,5 @@ "port": 2424, "httpPort": 2480, "username": "root", - "password": "3BA5DB89CC6206DBF835B36B70FF8A0EDCEFA617A229F0D44D1D726ABA04216A" + "password": "root" } \ No newline at end of file From 6eb713e5dce1bdcc8431778db116a75d24359a52 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 27 May 2014 21:46:23 +0100 Subject: [PATCH 086/308] disable snapshot tests for now --- .travis.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index b4bd9b6..55a05b0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,10 @@ before_script: - ./ci/initialize-ci.sh $ORIENTDB_VERSION env: - ORIENTDB_VERSION=1.7-rc2 - - ORIENTDB_VERSION=1.7-SNAPSHOT -matrix: - allow_failures: - - env: ORIENTDB_VERSION=1.7-SNAPSHOT \ No newline at end of file + +## @todo Restore me when snapshot stabilises + +# - ORIENTDB_VERSION=1.7-SNAPSHOT +# matrix: +# allow_failures: +# - env: ORIENTDB_VERSION=1.7-SNAPSHOT \ No newline at end of file From f1635b5fad308b81989c8dab30e0e621697698c0 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 27 May 2014 21:46:55 +0100 Subject: [PATCH 087/308] use property.drop() not property.delete() --- lib/db/class/property.js | 2 +- test/db/property-test.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/db/class/property.js b/lib/db/class/property.js index 8e38a0a..9db2bcf 100644 --- a/lib/db/class/property.js +++ b/lib/db/class/property.js @@ -248,7 +248,7 @@ Property.update = function (property, reload) { * @param {String} name The property name. * @promise {Class} The class instance with property removed. */ -Property.delete = function (name) { +Property.drop = function (name) { return this.db.exec('DROP PROPERTY ' + this.name + '.' + name) .bind(this) .then(this.reload) diff --git a/test/db/property-test.js b/test/db/property-test.js index a342d19..025b7d1 100644 --- a/test/db/property-test.js +++ b/test/db/property-test.js @@ -103,9 +103,9 @@ describe("Database API - Class - Property", function () { }); - describe('Db::class.property.delete()', function () { - it('should delete a property with the given name', function () { - return this.class.property.delete('myprop'); + describe('Db::class.property.drop()', function () { + it('should drop a property with the given name', function () { + return this.class.property.drop('myprop'); }); }); From 03c838661c08d74478f18376741e2eef3cc46b90 Mon Sep 17 00:00:00 2001 From: Gerard Date: Thu, 29 May 2014 13:26:01 +0200 Subject: [PATCH 088/308] Update README.md Add Create Index and Get Index Entry examples in the README --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index c72a7c8..9e65e33 100644 --- a/README.md +++ b/README.md @@ -426,6 +426,27 @@ MyClass.list() }); ``` +### Create a new index for a class property + +```js +db.index.create({ + name: 'MyClass.myProp', + type: 'unique' +}) +.then(function(index){ + console.log('Created index: ', index); +}); +``` + +### Get entry from class property index + +```js +db.index.get('MyClass.myProp') +.then(function (index) { + index.get('foo').then(console.log.bind(console)); +}); +``` + ### Creating a new, empty vertex ```js From fc70e860cd4f5a9803b7916117d2be38e551e9bd Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 30 May 2014 13:07:18 +0100 Subject: [PATCH 089/308] switch to 1.7.1 snapshot for now --- .travis.yml | 9 +-------- ci/odb-shared.sh | 41 +++-------------------------------------- 2 files changed, 4 insertions(+), 46 deletions(-) diff --git a/.travis.yml b/.travis.yml index 55a05b0..68b0da4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,11 +4,4 @@ node_js: before_script: - ./ci/initialize-ci.sh $ORIENTDB_VERSION env: - - ORIENTDB_VERSION=1.7-rc2 - -## @todo Restore me when snapshot stabilises - -# - ORIENTDB_VERSION=1.7-SNAPSHOT -# matrix: -# allow_failures: -# - env: ORIENTDB_VERSION=1.7-SNAPSHOT \ No newline at end of file + - ORIENTDB_VERSION=1.7.1-SNAPSHOT diff --git a/ci/odb-shared.sh b/ci/odb-shared.sh index bf342fe..8fc3614 100755 --- a/ci/odb-shared.sh +++ b/ci/odb-shared.sh @@ -36,49 +36,13 @@ odb_download () { } odb_download_server () { - if [[ $1 == *SNAPSHOT ]]; then - odb_download_via_mvn $1 $2; - else - odb_download_via_website $1 $2; - fi; - -} - -odb_download_via_website () { - COMMIT_HASH=$(git rev-parse HEAD) - DOWN_USER=oriento+travis${COMMIT_HASH}@codemix.com ODB_VERSION=$1 CI_DIR=$2 ODB_PACKAGE="orientdb-community-${ODB_VERSION}" - # We need to resort to tricks to automate our CI environment as much as - # possible since the OrientDB guys keep changing the compressed archive - # format and moving the downloadable packages URLs. Luckily for us, we - # are smart enough to cope with that... at least until the next change. - ODB_PACKAGE_EXT="tar.gz" - ODB_PACKAGE_URL="http://www.orientdb.org/portal/function/portal/download/${DOWN_USER}/%20/%20/%20/%20/unknown/${ODB_PACKAGE}.${ODB_PACKAGE_EXT}/false/false" - ODB_C_PACKAGE=${ODB_PACKAGE}.${ODB_PACKAGE_EXT} - - odb_download $ODB_PACKAGE_URL $CI_DIR - ODB_PACKAGE_PATH="${CI_DIR}/${ODB_PACKAGE}.${ODB_PACKAGE_EXT}" - - if [ $ODB_PACKAGE_EXT = "zip" ]; then - unzip -q $ODB_PACKAGE_PATH -d ${CI_DIR} - elif [ $ODB_PACKAGE_EXT = "tar.gz" ]; then - tar xf $ODB_PACKAGE_PATH -C $CI_DIR - fi -} - -odb_download_via_mvn () { - ODB_VERSION=$1 - CI_DIR=$2 - - ODB_PACKAGE="orientdb-community-${ODB_VERSION}" - - - ODB_PACKAGE_EXT="tar.gz" + ODB_PACKAGE_EXT="zip" ODB_C_PACKAGE=${ODB_PACKAGE}.${ODB_PACKAGE_EXT} OUTPUT_DIR="${2:-$(pwd)}" @@ -86,8 +50,9 @@ odb_download_via_mvn () { if [ ! -d "$OUTPUT_DIR" ]; then mkdir "$OUTPUT_DIR" fi + if odb_command_exists "mvn" ; then - mvn org.apache.maven.plugins:maven-dependency-plugin:2.8:get -Dartifact=com.orientechnologies:orientdb-community:"${ODB_VERSION}":"${ODB_PACKAGE_EXT}":distribution -DremoteRepositories=https://oss.sonatype.org/content/repositories/snapshots/ -Ddest="$OUTPUT_DIR/$ODB_C_PACKAGE" + mvn org.apache.maven.plugins:maven-dependency-plugin:2.8:get -Dartifact=com.orientechnologies:orientdb-community:$ODB_VERSION:$ODB_PACKAGE_EXT:distribution -DremoteRepositories="https://oss.sonatype.org/content/repositories/snapshots/,https://oss.sonatype.org/content/repositories/releases/" -Ddest=$OUTPUT_DIR/$ODB_C_PACKAGE else echo "Cannot download $1 [maven is not installed]" exit 1 From 1c114d7bcc23d00ced95f988200ca47d4e5dda28 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 30 May 2014 13:16:52 +0100 Subject: [PATCH 090/308] fix #65 --- lib/db/index/index.js | 3 +++ test/bugs/65-array-index-create.js | 38 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 test/bugs/65-array-index-create.js diff --git a/lib/db/index/index.js b/lib/db/index/index.js index 3d289a9..7a0091f 100644 --- a/lib/db/index/index.js +++ b/lib/db/index/index.js @@ -155,6 +155,9 @@ Index.list = function (refresh) { * @promise {Object} The created index object. */ Index.create = function (config) { + if (Array.isArray(config)) { + return Promise.map(config, this.index.create.bind(this)); + } var query = 'CREATE INDEX ' + config.name; if (config.class) { diff --git a/test/bugs/65-array-index-create.js b/test/bugs/65-array-index-create.js new file mode 100644 index 0000000..2a348e5 --- /dev/null +++ b/test/bugs/65-array-index-create.js @@ -0,0 +1,38 @@ +describe.only("Bug #65: Passing JSON array to db.index.create", function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_65') + .bind(this) + .then(function () { + return this.db.class.create('Member', 'V'); + }) + .then(function (item) { + this.class = item; + return this.class.property.create([ + { + name: 'name', + type: 'String' + }, + { + name: 'altName', + type: 'String' + } + ]); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_65'); + }); + + it('Let me create multiple indices at once', function () { + return this.db.index.create([ + { + name: 'Member.name', + type: 'unique' + }, + { + name: 'Member.altName', + type: 'unique' + } + ]); + }); +}); \ No newline at end of file From 340238a696668dc978795f2f46b743769501be65 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 30 May 2014 13:55:34 +0100 Subject: [PATCH 091/308] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9e65e33..ecab604 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A lightweight node.js driver for [orientdb](http://www.orientechnologies.com/ori # Supported Versions -Oriento aims to work with version 1.7 of orientdb and later. While it may work with earlier versions, they are not currently supported, [pull requests are welcome!](./CONTRIBUTING.md) +Oriento aims to work with version 1.7.1 of orientdb and later. While it may work with earlier versions, they are not currently supported, [pull requests are welcome!](./CONTRIBUTING.md) @@ -127,7 +127,7 @@ db.query('insert into OUser (name, password, status) values (:name, :password, : status: 'active' } } -).then(function (response){ +).then(function (response){ console.log(response); //an Array of records inserted }); From 4e1b923c64b257ee2b1499425e63d538723382a4 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 30 May 2014 13:55:53 +0100 Subject: [PATCH 092/308] rev version number --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 73cb04c..4069e20 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node", "node.js" ], - "version": "0.3.0", + "version": "0.3.1", "author": { "name": "Charles Pick", "email": "charles@codemix.com" From a754c48b824f159b8a2e7ba9c5767b3b0b54b1e6 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 5 Jun 2014 19:27:37 +0100 Subject: [PATCH 093/308] Update README.md remove mention of connection pooling until issues with thread safety are fixed in orientdb --- README.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/README.md b/README.md index ecab604..ec562bf 100644 --- a/README.md +++ b/README.md @@ -57,23 +57,6 @@ var server = Oriento({ password: 'yourpassword' }); ``` -### Configuring the client to use a connection pool. - -By default oriento uses one socket per server, but it is also possible to use a connection pool. -You should carefully benchmark this against the default setting for your use case, -there are scenarios where a connection pool is actually slightly worse for performance than a single connection. - -```js -var server = Oriento({ - host: 'localhost', - port: 2424, - username: 'root', - password: 'yourpassword', - pool: { - max: 10 // 1 by default - } -}); -``` ### Listing the databases on the server From 33a08a6cd8da00362e98074cfdf976baafa49b42 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 5 Jun 2014 19:31:44 +0100 Subject: [PATCH 094/308] Update .travis.yml --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 68b0da4..1679be6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,5 @@ node_js: before_script: - ./ci/initialize-ci.sh $ORIENTDB_VERSION env: - - ORIENTDB_VERSION=1.7.1-SNAPSHOT + - ORIENTDB_VERSION=1.7.1 + - ORIENTDB_VERSION=1.7.2-SNAPSHOT From f7cde9a6ace970696854c024ea89fcb57df81db2 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 5 Jun 2014 19:35:13 +0100 Subject: [PATCH 095/308] Update 65-array-index-create.js fix stray `.only()` --- test/bugs/65-array-index-create.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/bugs/65-array-index-create.js b/test/bugs/65-array-index-create.js index 2a348e5..8b040ef 100644 --- a/test/bugs/65-array-index-create.js +++ b/test/bugs/65-array-index-create.js @@ -1,4 +1,4 @@ -describe.only("Bug #65: Passing JSON array to db.index.create", function () { +describe("Bug #65: Passing JSON array to db.index.create", function () { before(function () { return CREATE_TEST_DB(this, 'testdb_bug_65') .bind(this) @@ -35,4 +35,4 @@ describe.only("Bug #65: Passing JSON array to db.index.create", function () { } ]); }); -}); \ No newline at end of file +}); From af1007cfdc3239fbb309ca6a8c40271e05731b77 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 9 Jun 2014 10:44:34 +0100 Subject: [PATCH 096/308] disprove #69 --- test/bugs/69-order-by-date.js | 60 +++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 test/bugs/69-order-by-date.js diff --git a/test/bugs/69-order-by-date.js b/test/bugs/69-order-by-date.js new file mode 100644 index 0000000..2bd71af --- /dev/null +++ b/test/bugs/69-order-by-date.js @@ -0,0 +1,60 @@ +describe("Bug #69: Order by date", function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_69') + .bind(this) + .then(function () { + return this.db.class.create('Member', 'V'); + }) + .then(function (item) { + this.class = item; + return this.class.property.create([ + { + name: 'name', + type: 'String' + }, + { + name: 'creation', + type: 'DateTime' + } + ]); + }) + .then(function () { + return this.class.create([ + { + name: 'a', + creation: new Date('2001-01-01') + }, + { + name: 'b', + creation: new Date('2001-01-02') + }, + { + name: 'c', + creation: new Date('2009-01-01') + }, + { + name: 'd', + creation: new Date('2012-01-01') + }, + { + name: 'e', + creation: new Date('2014-09-01') + } + ]) + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_69'); + }); + + it('should order by date', function () { + var query = this.db.select().from('Member').order('creation desc'); + return query.all() + .map(function (result) { + return result.name; + }) + .then(function (results) { + results.should.eql(['e', 'd', 'c', 'b', 'a']); + }); + }); +}); From 1fb98c74729f77d1d77399553eb51bc9889aae37 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 9 Jun 2014 11:00:09 +0100 Subject: [PATCH 097/308] 1.7.2 is no longer snapshot --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1679be6..6919d5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,4 @@ before_script: - ./ci/initialize-ci.sh $ORIENTDB_VERSION env: - ORIENTDB_VERSION=1.7.1 - - ORIENTDB_VERSION=1.7.2-SNAPSHOT + - ORIENTDB_VERSION=1.7.2 From 606774e5d7baeae5ec4c41fe8795c8e08c087c69 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 9 Jun 2014 11:46:29 +0100 Subject: [PATCH 098/308] remove date constructor, add time --- test/bugs/69-order-by-date.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/bugs/69-order-by-date.js b/test/bugs/69-order-by-date.js index 2bd71af..c0e2d1a 100644 --- a/test/bugs/69-order-by-date.js +++ b/test/bugs/69-order-by-date.js @@ -22,23 +22,23 @@ describe("Bug #69: Order by date", function () { return this.class.create([ { name: 'a', - creation: new Date('2001-01-01') + creation: '2001-01-01 00:00:01' }, { name: 'b', - creation: new Date('2001-01-02') + creation: '2001-01-02 12:00:01' }, { name: 'c', - creation: new Date('2009-01-01') + creation: '2009-01-01 00:12:01' }, { name: 'd', - creation: new Date('2012-01-01') + creation: '2012-01-01 21:00:01' }, { name: 'e', - creation: new Date('2014-09-01') + creation: '2014-09-01 00:24:01' } ]) }); From 48c000892a2ad07f5e434db50e57bf9b1c3a7276 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 9 Jun 2014 11:51:03 +0100 Subject: [PATCH 099/308] test using dates on the same day --- test/bugs/69-order-by-date.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bugs/69-order-by-date.js b/test/bugs/69-order-by-date.js index c0e2d1a..1a70392 100644 --- a/test/bugs/69-order-by-date.js +++ b/test/bugs/69-order-by-date.js @@ -34,7 +34,7 @@ describe("Bug #69: Order by date", function () { }, { name: 'd', - creation: '2012-01-01 21:00:01' + creation: '2014-09-01 00:01:01' }, { name: 'e', From f50aae572d5823dc8294aa468b249785dbef878c Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 13 Jun 2014 11:15:02 +0100 Subject: [PATCH 100/308] prepare 0.3.2 --- CHANGELOG.md | 22 ---------------------- package.json | 2 +- 2 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 0bcd21c..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,22 +0,0 @@ -# 0.2.0 - -- add fetch plans in query builder via `fetch()`. -- add support for standalone orientdb, not just dserver. -- better RIDBag support. -- travis ci integration. -- connection pools. -- add `Query::column()` and `Query::transform()`. -- add support for getting / setting custom fields on classes and properties. -- add index support. -- remove in-band `@options` key in record operations. -- switch to mocha 1.8.x (yay promise support). - -# 0.1.0 - -- Graph mode by default. -- Add vertex / edge helpers -- Add db query builder - -# 0.0.1 - -- Total refactor of [node-orientdb](https://github.com/nitrog7/node-orientdb). \ No newline at end of file diff --git a/package.json b/package.json index 4069e20..891f84f 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node", "node.js" ], - "version": "0.3.1", + "version": "0.3.2", "author": { "name": "Charles Pick", "email": "charles@codemix.com" From 3dca994a7cf6dffcfb89cd59cd934669d018d599 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 11 Jul 2014 11:10:50 +0100 Subject: [PATCH 101/308] fix failing test --- test/bugs/59-close-connection.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/test/bugs/59-close-connection.js b/test/bugs/59-close-connection.js index 398a1dc..2b8b012 100644 --- a/test/bugs/59-close-connection.js +++ b/test/bugs/59-close-connection.js @@ -1,14 +1,24 @@ describe("Bug #59: hang on closing server connection", function () { - it('should close the server connection correctly', function () { - var server = new LIB.Server({ - host: TEST_SERVER_CONFIG.host, - port: TEST_SERVER_CONFIG.port, - username: TEST_SERVER_CONFIG.username, - password: TEST_SERVER_CONFIG.password, - transport: 'binary' + var server; + before(function () { + return CREATE_TEST_DB(this, 'testdb_59_close_connection') + .bind(this) + .then(function () { + server = new LIB.Server({ + host: TEST_SERVER_CONFIG.host, + port: TEST_SERVER_CONFIG.port, + username: TEST_SERVER_CONFIG.username, + password: TEST_SERVER_CONFIG.password, + transport: 'binary' + }); }); + }); + after(function () { + return DELETE_TEST_DB('testdb_59_close_connection'); + }); + it('should close the server connection correctly', function () { - var db = server.use('GratefulDeadConcerts'); + var db = server.use('testdb_59_close_connection'); return db.class.list() .then(function (classes) { From 9575c3e4d22f863b4a241d60d94a67e8ab937929 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 11 Jul 2014 11:14:17 +0100 Subject: [PATCH 102/308] fix #84 RID isValid arrays --- lib/recordid.js | 1 + test/bugs/84-rid-isValid-array.js | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 test/bugs/84-rid-isValid-array.js diff --git a/lib/recordid.js b/lib/recordid.js index 3d3a58d..27012e0 100644 --- a/lib/recordid.js +++ b/lib/recordid.js @@ -123,6 +123,7 @@ RecordID.isValid = function (input) { return /^#(-?\d+):(-?\d+)$/.test(input); } else if (input && Array.isArray(input)) { + total = input.length; for (i = 0; i < total; i++) { if (!RecordID.isValid(input[i])) { return false; diff --git a/test/bugs/84-rid-isValid-array.js b/test/bugs/84-rid-isValid-array.js new file mode 100644 index 0000000..426b35e --- /dev/null +++ b/test/bugs/84-rid-isValid-array.js @@ -0,0 +1,6 @@ +describe("Bug #84: Bug in RecordID.isValid with array input", function () { + it('validate array input', function () { + var input = ['#1:23', '#4:56', '#6:79']; + LIB.RID.isValid(input).should.be.true; + }); +}); \ No newline at end of file From 66866799059431d20b99554a6030f07eed755458 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 11 Jul 2014 14:16:52 +0100 Subject: [PATCH 103/308] fix #79 --- .../binary/protocol/operations/command.js | 2 +- test/bugs/79-insert-errors.js | 54 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 test/bugs/79-insert-errors.js diff --git a/lib/transport/binary/protocol/operations/command.js b/lib/transport/binary/protocol/operations/command.js index 5b6861f..4d54fba 100644 --- a/lib/transport/binary/protocol/operations/command.js +++ b/lib/transport/binary/protocol/operations/command.js @@ -170,7 +170,7 @@ function serializeParams (data) { value = data.params[key]; if (typeof value === 'string') { c = value.charAt(0); - if (c === '#' || c === '<' || c === '[' || c === '(' || c === '{' || c === '0' || +c) { + if (c === '.' || c === '#' || c === '<' || c === '[' || c === '(' || c === '{' || c === '0' || +c) { data.params[key] = '"' + value + '"'; } } diff --git a/test/bugs/79-insert-errors.js b/test/bugs/79-insert-errors.js new file mode 100644 index 0000000..7041b0c --- /dev/null +++ b/test/bugs/79-insert-errors.js @@ -0,0 +1,54 @@ +describe("Bug #79: Error when inserting", function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_79') + .bind(this) + .then(function () { + return this.db.class.create('User'); + }) + .then(function (item) { + this.class = item; + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_79'); + }); + + it('Let me create a property immediately after creating a class', function () { + var data = { + "acceptedTerms":true, + "activitiesCount":0, + "appFirstUseDate":{"__type":"Date","iso":"2013-03-26T10:36:23.050Z"}, + "email":"REMOVED@hotmail.com", + "emailVerified":true, + "first_name":"Imogen", + "followerCount":0, + "followingCount":0, + "gender":2, + "goal":2, + "height_unit":1, + "height_val1":5, + "height_val2":3, + "homeEquipment":[4,3,6,105,107], + "last_name":".", + "level":3, + "numReferrals":0, + "postCount":0, + "subscribedToPush":true, + "timezone":"America/New_York", + "unsubscribedFromWorkoutEmails":true, + "username":"imogenxoxo", + "weight":93, + "weight_unit":1, + "createdAt":"2013-03-26T10:38:04.971Z", + "updatedAt":"2014-04-09T17:18:38.577Z", + "objectId":"l402K4JOu4", + "ACL":{"*":{"read":true},"l402K4JOu4":{"read":true,"write":true}}, + "sessionToken":"sue1t43xj80miwi4s3ky49ybo" + }; + + return this.db.insert().into('User').set(data).one() + .then(function (res) { + res.acceptedTerms.should.equal(true); + }); + }); +}); \ No newline at end of file From ca97902c6e0cee560a7818b258cc5b5fba9208bd Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 11 Jul 2014 14:19:28 +0100 Subject: [PATCH 104/308] fix travis --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6919d5e..7e53df8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,5 +4,4 @@ node_js: before_script: - ./ci/initialize-ci.sh $ORIENTDB_VERSION env: - - ORIENTDB_VERSION=1.7.1 - - ORIENTDB_VERSION=1.7.2 + - ORIENTDB_VERSION=1.7.4 From 8cd9241527f9ab021884f4184b34b63b6cb548df Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 11 Jul 2014 15:36:31 +0100 Subject: [PATCH 105/308] update travis to 1.7.5 [ci skip] --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7e53df8..442976d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,4 @@ node_js: before_script: - ./ci/initialize-ci.sh $ORIENTDB_VERSION env: - - ORIENTDB_VERSION=1.7.4 + - ORIENTDB_VERSION=1.7.5 From 310e1d7d8542af57f1d45fa8d34cacec6c7bc187 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 11 Jul 2014 16:03:43 +0100 Subject: [PATCH 106/308] add test for #82 --- test/bugs/82-emojis.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 test/bugs/82-emojis.js diff --git a/test/bugs/82-emojis.js b/test/bugs/82-emojis.js new file mode 100644 index 0000000..667d2a3 --- /dev/null +++ b/test/bugs/82-emojis.js @@ -0,0 +1,35 @@ +describe.only("Bug #82: db.query errors when parsing emojis ", function () { + var rid; + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_82') + .bind(this) + .then(function () { + return this.db.class.create('Emoji'); + }) + .then(function (item) { + this.class = item; + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_82'); + }); + + it('should allow emojis in insert statements', function () { + return this.db.insert().into('Emoji').set({value: '😢😂😭'}).one() + .then(function (result) { + result.should.have.property('@rid'); + rid = result['@rid']; + }); + }); + it('should allow emojis in update statements', function () { + return this.db.update(rid).set({value: 'hello 😢😂😭', foo: 'bar'}).one(); + }); + + it.skip('should allow emojis using db.query() directly', function () { + var query = 'UPDATE ' + rid + ' SET bio="Aiming to be Miranda Kerr, Candice Swanepoel, Adriana Lima, Alessandra Ambrosio, Doutzen Kroes, Erin Heatherton, or Behati Prinsloo 😢😂 foo"'; + return this.db.query(query) + .spread(function (result) { + result.should.equal(1); + }); + }); +}); \ No newline at end of file From 5f8ac82cb9465f68bd401057fa04f26420e3d56d Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 11 Jul 2014 16:12:19 +0100 Subject: [PATCH 107/308] revert to 1.7.4 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 442976d..7e53df8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,4 @@ node_js: before_script: - ./ci/initialize-ci.sh $ORIENTDB_VERSION env: - - ORIENTDB_VERSION=1.7.5 + - ORIENTDB_VERSION=1.7.4 From 3bd3dea0677f8e74d8f6466adf6bb08e8755ed1c Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 11 Jul 2014 16:20:08 +0100 Subject: [PATCH 108/308] add test for #80 [ci skip] --- test/bugs/80-base64.js | 119 +++++++++++++++++++++++++++++++++++++++++ test/bugs/82-emojis.js | 2 +- 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 test/bugs/80-base64.js diff --git a/test/bugs/80-base64.js b/test/bugs/80-base64.js new file mode 100644 index 0000000..107b560 --- /dev/null +++ b/test/bugs/80-base64.js @@ -0,0 +1,119 @@ +describe("Bug #80: Bad Base64 input character decimal 95", function () { + var rid; + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_80') + .bind(this) + .then(function () { + return this.db.class.create('User'); + }) + .then(function (item) { + this.class = item; + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_80'); + }); + + var input = { + "settings": { + "__type": "Pointer", + "className": "Settings", + "objectId": "xdOfM1NauR" + }, + "acceptedTerms": true, + "activitiesCount": 16, + "appFirstUseDate": { + "__type": "Date", + "iso": "2014-06-18T21:01:14.057Z" + }, + "birthday": { + "__type": "Date", + "iso": "1989-01-10T00:00:00.000Z" + }, + "email": "EMAIL_REMOVED@gmail.com", + "equipment": { + "1": [ + 1, + 2, + 3, + 6, + 7, + 10, + 100, + 101, + 105, + 107, + 108, + 109, + 5, + 11, + 23, + 8, + 13, + 22, + 4 + ] + }, + "feedOption": 2, + "followerCount": 0, + "followingCount": 5, + "followingFeedLastReadAt": { + "__type": "Date", + "iso": "2014-06-23T20:00:46.903Z" + }, + "gender": 2, + "goal": 4, + "height_unit": 1, + "height_val1": 5, + "height_val2": 1, + "lastPushNotificationPrompt": { + "__type": "Date", + "iso": "2014-06-22T16:11:51.991Z" + }, + "lastRatePrompt": { + "__type": "Date", + "iso": "2014-06-20T17:06:03.189Z" + }, + "lastVersionUsed": "3.0.0", + "level": 3, + "needsToSeePushNotificationPrompt": false, + "newFeedLastReadAt": { + "__type": "Date", + "iso": "2014-06-19T14:36:09.877Z" + }, + "numReferrals": 0, + "platform": 1, + "popularFeedLastReadAt": { + "__type": "Date", + "iso": "2014-06-20T17:06:13.174Z" + }, + "postCount": 8, + "seenRatePrompt": true, + "stream": "a", + "timezone": "America/Havana", + "unsubscribedFromWorkoutEmails": true, + "username": "USERNAME_REMOVED", + "weight": 120, + "weight_unit": 1, + "createdAt": "2014-06-18T21:01:59.471Z", + "updatedAt": "2014-06-23T20:01:27.888Z", + "objectId": "OBJID", + "ACL": { + "*": { + "read": true + }, + "OBJID": { + "read": true, + "write": true + } + }, + "sessionToken": "36DawJQJgQmmZorP1sRcFAAp3" + }; + + it('should insert the user', function () { + return this.db.insert().into('User').set(input).one() + .then(function (response) { + response.should.have.property('@rid'); + }); + }); +}); \ No newline at end of file diff --git a/test/bugs/82-emojis.js b/test/bugs/82-emojis.js index 667d2a3..1643e31 100644 --- a/test/bugs/82-emojis.js +++ b/test/bugs/82-emojis.js @@ -1,4 +1,4 @@ -describe.only("Bug #82: db.query errors when parsing emojis ", function () { +describe("Bug #82: db.query errors when parsing emojis ", function () { var rid; before(function () { return CREATE_TEST_DB(this, 'testdb_bug_82') From 3885d5d25c2896b1cc1f79e9ea5e3425d470248c Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 11 Jul 2014 18:45:59 +0100 Subject: [PATCH 109/308] fix #81 --- lib/db/edge/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/db/edge/index.js b/lib/db/edge/index.js index fb792da..d593126 100644 --- a/lib/db/edge/index.js +++ b/lib/db/edge/index.js @@ -94,7 +94,7 @@ function createEdge (db, config, from, to) { function deleteEdge (db, config, from, to) { var command = "DELETE EDGE", className = config ? edgeConfig(config)[0] : false; - if (false && className) { + if (className) { command += ' ' + className; } command += ' FROM ' + edgeReference(from) + ' TO ' + edgeReference(to); From c0b1aa669eeec1a69fdf67419beca8d6f3442068 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Jul 2014 12:40:12 +0100 Subject: [PATCH 110/308] fix outdated config to allow SQL batch --- ci/orientdb-server-config.xml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ci/orientdb-server-config.xml b/ci/orientdb-server-config.xml index a402599..ed322be 100644 --- a/ci/orientdb-server-config.xml +++ b/ci/orientdb-server-config.xml @@ -25,7 +25,8 @@ - + + @@ -35,7 +36,11 @@ - + + + + + From ab1c2ffdcdd7fa0a0bbdea4647fc09c867c9aed5 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Jul 2014 12:40:51 +0100 Subject: [PATCH 111/308] fix sql transactions --- lib/db/index.js | 7 +- lib/db/statement.js | 64 +++++++++++-- .../binary/protocol/operations/command.js | 2 +- test/db/query-test.js | 14 +-- test/db/statement-test.js | 16 ++-- test/db/transaction-test.js | 89 ++++++++++++++++++- 6 files changed, 155 insertions(+), 37 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index 8de1497..341d2e4 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -152,7 +152,7 @@ Db.prototype.exec = function (query, options) { options = options || {}; var data = { query: query, - mode: 's', + mode: options.mode || 's', fetchPlan: '', limit: -1, class: options.class || 'com.orientechnologies.orient.core.sql.OCommandSQL' @@ -164,10 +164,7 @@ Db.prototype.exec = function (query, options) { } if (+options.limit == options.limit) { data.limit = +options.limit; - data.mode = 'a'; - } - if (options.mode === 'a') { - data.mode = options.mode; + data.mode = options.mode || 'a'; } if (data.mode === 'a' && !options.class) { diff --git a/lib/db/statement.js b/lib/db/statement.js index 9f0cbef..70d423c 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -64,14 +64,31 @@ Statement.prototype.delete = clause('delete'); */ Statement.prototype.into = clause('into'); +/** + * Create a class, edge or vertex. + * + * @param {String} type Either `class`, `edge` or `vertex`. + * @param {String} name The entity name. + * @return {Statement} The statement object. + */ +Statement.prototype.create = clause('create'); + /** * Use the given record id or class name. * - * @param {String|String[]} args The record id, class name or expression. - * @return {Statement} The statement object. + * @param {String|String[]|Function} args The record id, class name or expression. + * @return {Statement} The statement object. */ Statement.prototype.from = clause('from'); +/** + * A `to` clause, used when creating edges. + * + * @param {String|Function} arg The target to create the edge to. + * @return {Statement} The statement object. + */ +Statement.prototype.to = clause('to'); + /** * Set the given column names to the given values. * @@ -241,19 +258,31 @@ Statement.prototype.addParams = function (params) { */ Statement.prototype.buildStatement = function () { var statement = [], - state = this._state; + state = this._state, + self = this; if (state.commit !== undefined) { - statement.push('BEGIN;'); + statement.push('BEGIN\n'); } if (state.let && state.let.length) { statement.push(state.let.map(function (item) { - return "LET " + item[0] + ' = ' + item[1]; + if (typeof item[1] === 'function') { + var child = new Statement(); + child._state.paramIndex = self._state.paramIndex; + item[1](child); + return 'LET ' + item[0] + ' = ' + child + "\n"; + } + else { + return "LET " + item[0] + ' = ' + item[1] + "\n"; + } }).join(' ')); } - if (state.traverse && state.traverse.length) { + if (state.create && state.create.length) { + statement.push('CREATE ' + state.create.join(' ')); + } + else if (state.traverse && state.traverse.length) { statement.push('TRAVERSE'); statement.push(state.traverse.join(', ')); } @@ -289,6 +318,23 @@ Statement.prototype.buildStatement = function () { }).join(', ')); } + if (state.to && state.to.length) { + statement.push('TO'); + statement.push(state.to.map(function (item) { + if (typeof item === 'string') { + if (/(\s+)/.test(item)) { + return '(' + item + ')'; + } + else { + return item; + } + } + else { + return ''+item; + } + }).join(', ')); + } + if (state.into && state.into.length) { statement.push('INTO'); statement.push(state.into.map(function (item) { @@ -409,15 +455,15 @@ Statement.prototype.buildStatement = function () { } if (state.commit !== undefined) { - statement.push(';COMMIT'); + statement.push('\nCOMMIT'); if (state.commit) { statement.push('RETRY ' + (+state.commit)); } - statement.push(';'); + statement.push('\n'); } if (state.return) { - statement.push('RETURN ' + state.return + ';'); + statement.push('RETURN ' + state.return); } return statement.join(' '); }; diff --git a/lib/transport/binary/protocol/operations/command.js b/lib/transport/binary/protocol/operations/command.js index 4d54fba..a148481 100644 --- a/lib/transport/binary/protocol/operations/command.js +++ b/lib/transport/binary/protocol/operations/command.js @@ -161,7 +161,7 @@ module.exports = Operation.extend({ * @return {String} The serialized data. */ function serializeParams (data) { - var keys = Object.keys(data.params), + var keys = Object.keys(data.params || {}), total = keys.length, c, i, key, value; diff --git a/test/db/query-test.js b/test/db/query-test.js index 4447883..514a0a4 100644 --- a/test/db/query-test.js +++ b/test/db/query-test.js @@ -352,16 +352,4 @@ describe("Database API - Query", function () { }); }); }); - describe.skip('Transactional Queries', function () { - it('should execute a simple transaction', function () { - return this.db - .update('OUser') - .set({newField: true}) - .commit() - .all() - .then(function (results) { - console.log(results); - }); - }); - }); -}); +}); \ No newline at end of file diff --git a/test/db/statement-test.js b/test/db/statement-test.js index e0c69f5..013bc07 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -19,7 +19,7 @@ describe("Database API - Statement", function () { .let('names', sub) .buildStatement() .should - .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"'); + .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"\n'); }); it('should let a variable equal a subexpression, more than once', function () { var sub1 = (new Statement(this.db)).select('name').from('OUser').where({status: 'ACTIVE'}), @@ -29,7 +29,7 @@ describe("Database API - Statement", function () { .let('statuses', sub2) .buildStatement() .should - .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE" LET statuses = SELECT status FROM OUser'); + .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"\n LET statuses = SELECT status FROM OUser\n'); }); it('should let a variable equal a subexpression, more than once, using locks', function () { var sub1 = (new Statement(this.db)).select('name').from('OUser').where({status: 'ACTIVE'}), @@ -39,7 +39,7 @@ describe("Database API - Statement", function () { .let('statuses', sub2) .buildStatement() .should - .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE" LET statuses = SELECT status FROM OUser LOCK record'); + .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"\n LET statuses = SELECT status FROM OUser LOCK record\n'); }); }); @@ -49,14 +49,14 @@ describe("Database API - Statement", function () { .commit() .buildStatement() .should - .equal('BEGIN; ;COMMIT ;'); + .equal('BEGIN\n \nCOMMIT \n'); }); it('should generate an empty transaction, with retries', function () { this.statement .commit(100) .buildStatement() .should - .equal('BEGIN; ;COMMIT RETRY 100 ;'); + .equal('BEGIN\n \nCOMMIT RETRY 100 \n'); }); it('should generate an update transaction', function () { this.statement @@ -65,7 +65,7 @@ describe("Database API - Statement", function () { .commit() .toString() .should - .equal('BEGIN; UPDATE OUser SET name = "name" ;COMMIT ;'); + .equal('BEGIN\n UPDATE OUser SET name = "name" \nCOMMIT \n'); }); it('should generate an update transaction, with retries', function () { this.statement @@ -74,7 +74,7 @@ describe("Database API - Statement", function () { .commit(100) .toString() .should - .equal('BEGIN; UPDATE OUser SET name = "name" ;COMMIT RETRY 100 ;'); + .equal('BEGIN\n UPDATE OUser SET name = "name" \nCOMMIT RETRY 100 \n'); }); it('should generate an update transaction, with returns', function () { var sub = (new Statement(this.db)).update('OUser').set({name: 'name'}); @@ -84,7 +84,7 @@ describe("Database API - Statement", function () { .return('$names') .toString() .should - .equal('BEGIN; LET names = UPDATE OUser SET name = "name" ;COMMIT ; RETURN $names;'); + .equal('BEGIN\n LET names = UPDATE OUser SET name = "name"\n \nCOMMIT \n RETURN $names'); }); }); diff --git a/test/db/transaction-test.js b/test/db/transaction-test.js index 286fdbc..22c59cd 100644 --- a/test/db/transaction-test.js +++ b/test/db/transaction-test.js @@ -1,4 +1,5 @@ -var Transaction = require('../../lib/db/transaction'); +var Transaction = require('../../lib/db/transaction'), + Promise = require('bluebird'); describe("Database API - Transaction", function () { before(function () { @@ -195,4 +196,90 @@ describe("Database API - Transaction", function () { }); }); }); +}); + +describe('Transactional Queries', function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_dbapi_tx_queries') + .bind(this) + .then(function () { + return Promise.all([ + this.db.class.create('TestVertex', 'V'), + this.db.class.create('TestEdge', 'E') + ]); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_dbapi_tx_queries'); + }); + + it('should execute a simple transaction, using a raw query', function () { + return this.db.query('begin\nupdate OUser set someField = true\ncommit', { + class: 's' + }) + .spread(function (result) { + result.should.be.above(2); + }); + }); + it('should execute a simple transaction, using the query builder', function () { + return this.db + .update('OUser') + .set({newField: true}) + .commit() + .all() + .spread(function (result) { + result.should.be.above(2); + }); + }); + + it('should execute a complex transaction, using a raw query', function () { + return this.db.query('begin\nlet vert = create vertex TestVertex set name = "thing"\nlet user = select from OUser where name = "admin"\nlet edge = create edge TestEdge from $vert to $user\ncommit retry 100\nreturn $edge', { + class: 's' + }) + .spread(function (result) { + result['@class'].should.equal('TestEdge'); + }); + }); + it('should execute a complex transaction, using the query builder', function () { + return this.db + .let('vert', 'create vertex TestVertex set name="wat"') + .let('user', 'select from OUser where name="reader"') + .let('edge', 'create edge TestEdge from $vert to $user') + .commit(100) + .return('$edge') + .one() + .then(function (result) { + result['@class'].should.equal('TestEdge'); + }); + }); + it('should execute a complex transaction, using the query builder for let statements', function () { + return this.db + .let('vert', function (s) { + return s + .create('vertex', 'TestVertex') + .set({ + name: "foo" + }); + }) + .let('user', function (s) { + return s + .select() + .from('OUser') + .where({ + name: 'reader' + }); + }) + .let('edge', function (s) { + return s + .create('edge', 'TestEdge') + .from('$vert') + .to('$user') + }) + .commit(100) + .return('$edge') + .one() + .then(function (result) { + result['@class'].should.equal('TestEdge'); + }); + }); }); \ No newline at end of file From ec75131c152bac367254578543d8f6867bfc4ff4 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Jul 2014 20:35:14 +0100 Subject: [PATCH 112/308] add upsert support to query builder --- lib/db/statement.js | 27 ++++++++++++++++++++++++++- test/db/statement-test.js | 11 +++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index 70d423c..64a7362 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -97,6 +97,26 @@ Statement.prototype.to = clause('to'); */ Statement.prototype.set = clause('set'); +/** + * Upsert the records to avoid multiple queries. + * + * @param {String|Object} condition The condition clause, if any. + * @param {Object} params The parameters to bind to the statement, if any. + * @param {String} comparisonOperator The operator to use for comparison, defaults to '='. + * @return {Statement} The statement object. + */ +Statement.prototype.upsert = function (condition, params, comparisonOperator) { + this._state.upsert = true; + if (condition) { + this._state.where = this._state.where || []; + this._state.where.push(['and', condition, comparisonOperator || '=']); + if (params) { + this.addParams(params); + } + } + return this; +}; + /** * Specify the where clause for the statement. * @@ -259,7 +279,8 @@ Statement.prototype.addParams = function (params) { Statement.prototype.buildStatement = function () { var statement = [], state = this._state, - self = this; + self = this, + result; if (state.commit !== undefined) { statement.push('BEGIN\n'); @@ -370,6 +391,10 @@ Statement.prototype.buildStatement = function () { }, this).filter(function (item) { return item; }).join(', ')); } + if (state.upsert) { + statement.push('UPSERT'); + } + if (state.where) { statement.push('WHERE'); statement.push(state.where.reduce(function (accumulator, item) { diff --git a/test/db/statement-test.js b/test/db/statement-test.js index 013bc07..9f5317a 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -185,5 +185,16 @@ describe("Database API - Statement", function () { this.statement.update('OUser').where('1=1').lock('record'); this.statement.buildStatement().should.equal('UPDATE OUser WHERE 1=1 LOCK record'); }); + }); + + describe('Statement::upsert()', function () { + it('should upsert a record', function () { + this.statement.update('OUser').set("foo = 'bar'").upsert().where('1 = 1'); + this.statement.buildStatement().should.equal("UPDATE OUser SET (foo = 'bar') UPSERT WHERE 1 = 1"); + }); + it('should upsert a record, with a where clause', function () { + this.statement.update('OUser').set("foo = 'bar'").upsert('1 = 1'); + this.statement.buildStatement().should.equal("UPDATE OUser SET (foo = 'bar') UPSERT WHERE 1 = 1"); + }); }) }); \ No newline at end of file From 991ad6ffc95451665734a12a0734c7b8b1d72e57 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 23 Jul 2014 21:16:53 +0100 Subject: [PATCH 113/308] fix RID / params bug --- lib/db/statement.js | 2 +- lib/utils.js | 2 +- test/db/statement-test.js | 49 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index 64a7362..0322189 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -289,7 +289,7 @@ Statement.prototype.buildStatement = function () { if (state.let && state.let.length) { statement.push(state.let.map(function (item) { if (typeof item[1] === 'function') { - var child = new Statement(); + var child = new Statement(self.db); child._state.paramIndex = self._state.paramIndex; item[1](child); return 'LET ' + item[0] + ' = ' + child + "\n"; diff --git a/lib/utils.js b/lib/utils.js index 845b8a1..a5288da 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -139,7 +139,7 @@ exports.prepare = function (query, params) { if (!params) { return query; } - var pattern = /"(\\[\s\S]|[^"])*"|'(\\[\s\S]|[^'])*'|:([A-Za-z0-9_-]+|\/\*[\s\S]*?\*\/)/; + var pattern = /"(\\[\s\S]|[^"])*"|'(\\[\s\S]|[^'])*'|:([A-Za-z][A-Za-z0-9_-]*|\/\*[\s\S]*?\*\/)/; return query.replace(pattern, function (all, double, single, param) { if (param) { return exports.encode(params[param]); diff --git a/test/db/statement-test.js b/test/db/statement-test.js index 9f5317a..cd7c799 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -41,6 +41,44 @@ describe("Database API - Statement", function () { .should .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"\n LET statuses = SELECT status FROM OUser LOCK record\n'); }); + + it('should allow RIDs in LET expressions', function () { + var rec1 = { + '@rid': new LIB.RID({ + cluster: 23, + position: 1234567 + }) + }; + var rec2 = { + '@rid': new LIB.RID({ + cluster: 23, + position: 98765432 + }) + }; + this.statement + .let('foo', function (statement) { + return statement.select().from('Foo'); + }) + .let('edge', function (statement) { + return statement + .create('edge', 'E') + .from('$foo') + .to(rec1['@rid']); + }) + .let('updated', function (statement) { + return statement.update(rec2['@rid']).set({foo: 'bar'}); + }) + .commit() + .return('$edge') + .buildStatement() + .should.equal('BEGIN\n\ + LET foo = SELECT * FROM Foo\n\ + LET edge = CREATE edge E FROM $foo TO #23:1234567\n\ + LET updated = UPDATE #23:98765432 SET foo = "bar"\n\ + \n\ +COMMIT \n\ + RETURN $edge'); + }); }); describe('Statement::commit() and Statement::return()', function () { @@ -132,6 +170,17 @@ describe("Database API - Statement", function () { }); }); + describe('Statement::to()', function () { + it('should create an edge', function () { + this.statement.create('EDGE', 'E').from('#5:0').to('#5:1'); + this.statement.buildStatement().should.equal('CREATE EDGE E FROM #5:0 TO #5:1'); + }); + it('should create an edge from a record id to a record id', function () { + this.statement.create('EDGE', 'E').from(LIB.RID('#5:0')).to(LIB.RID('#22:310540')); + this.statement.buildStatement().should.equal('CREATE EDGE E FROM #5:0 TO #22:310540'); + }); + }); + describe('Statement::where(), Statement::and(), Statement::or()', function () { it('should build a where clause with an expression', function () { this.statement.select().from('OUser').where('1=1'); From 3d3aea1790a7eac2f677d386c36e937572055ef4 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 25 Jul 2014 00:22:43 +0100 Subject: [PATCH 114/308] fix #99 --- lib/db/statement.js | 14 ++------------ test/bugs/99-set-with-string.js | 11 +++++++++++ test/db/statement-test.js | 6 +++--- 3 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 test/bugs/99-set-with-string.js diff --git a/lib/db/statement.js b/lib/db/statement.js index 0322189..8fcdef7 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -326,12 +326,7 @@ Statement.prototype.buildStatement = function () { statement.push('FROM'); statement.push(state.from.map(function (item) { if (typeof item === 'string') { - if (/(\s+)/.test(item)) { - return '(' + item + ')'; - } - else { - return item; - } + return item; } else { return ''+item; @@ -378,12 +373,7 @@ Statement.prototype.buildStatement = function () { statement.push(state.set.map(function (item) { var interim; if (typeof item === 'string') { - if (/(\s+)/.test(item)) { - return '(' + item + ')'; - } - else { - return item; - } + return item; } else { return this._objectToSet(item); diff --git a/test/bugs/99-set-with-string.js b/test/bugs/99-set-with-string.js new file mode 100644 index 0000000..acc6da8 --- /dev/null +++ b/test/bugs/99-set-with-string.js @@ -0,0 +1,11 @@ +var Statement = require('../../lib/db/statement'); + +describe("Bug #99: Error using .set() with string", function () { + + it('should allow strings to be specified without parentheses', function () { + var s = new Statement(); + s.update('foo').set('a = 123').toString().should.equal( + 'UPDATE foo SET a = 123' + ); + }); +}); \ No newline at end of file diff --git a/test/db/statement-test.js b/test/db/statement-test.js index cd7c799..76529fe 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -165,7 +165,7 @@ COMMIT \n\ this.statement.buildStatement().should.equal('SELECT * FROM #4:4'); }); it('should select from a subexpression', function () { - this.statement.select().from('SELECT * FROM OUser'); + this.statement.select().from('(SELECT * FROM OUser)'); this.statement.buildStatement().should.equal('SELECT * FROM (SELECT * FROM OUser)'); }); }); @@ -239,11 +239,11 @@ COMMIT \n\ describe('Statement::upsert()', function () { it('should upsert a record', function () { this.statement.update('OUser').set("foo = 'bar'").upsert().where('1 = 1'); - this.statement.buildStatement().should.equal("UPDATE OUser SET (foo = 'bar') UPSERT WHERE 1 = 1"); + this.statement.buildStatement().should.equal("UPDATE OUser SET foo = 'bar' UPSERT WHERE 1 = 1"); }); it('should upsert a record, with a where clause', function () { this.statement.update('OUser').set("foo = 'bar'").upsert('1 = 1'); - this.statement.buildStatement().should.equal("UPDATE OUser SET (foo = 'bar') UPSERT WHERE 1 = 1"); + this.statement.buildStatement().should.equal("UPDATE OUser SET foo = 'bar' UPSERT WHERE 1 = 1"); }); }) }); \ No newline at end of file From e21ec582ac94b6d2e28f1afdee2e4dd7f48d3969 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 30 Jul 2014 01:02:34 +0100 Subject: [PATCH 115/308] fix #82 emojis --- lib/transport/binary/protocol/operation.js | 33 ++++++++++++++++++++-- lib/transport/binary/protocol/writer.js | 27 +++++++++++++++++- test/bugs/82-emojis.js | 13 ++++++--- 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/lib/transport/binary/protocol/operation.js b/lib/transport/binary/protocol/operation.js index 62197b0..99410e1 100644 --- a/lib/transport/binary/protocol/operation.js +++ b/lib/transport/binary/protocol/operation.js @@ -200,15 +200,42 @@ Operation.prototype.writeString = function (data) { if (data == null) { return this.writeInt(-1); } - var length = Buffer.byteLength(data); + var encoded = encodeString(data), + length = encoded.length; this.writeOps.push([constants.BYTES_INT + length, function (buffer, offset) { - buffer.fill(0, offset, offset + constants.BYTES_INT + length); buffer.writeInt32BE(length, offset); - buffer.write(data, offset + constants.BYTES_INT); + encoded.copy(buffer, offset + constants.BYTES_INT); }]); return this; }; +function encodeString (data) { + var length = data.length, + output = new Buffer(length * 3), // worst case, all chars could require 3-byte encodings. + j = 0, // index output + i, c; + + for (i = 0; i < length; i++) { + c = data.charCodeAt(i); + if (c < 0x80) { + // 7-bits done in one byte. + output[j++] = c; + } + else if (c < 0x800) { + // 8-11 bits done in 2 bytes + output[j++] = (0xC0 | c >> 6); + output[j++] = (0x80 | c & 0x3F); + } + else { + // 12-16 bits done in 3 bytes + output[j++] = (0xE0 | c >> 12); + output[j++] = (0x80 | c >> 6 & 0x3F); + output[j++] = (0x80 | c & 0x3F); + } + } + return output.slice(0, j); +} + // # Read Operations diff --git a/lib/transport/binary/protocol/writer.js b/lib/transport/binary/protocol/writer.js index cc6a9a0..5c21cd2 100644 --- a/lib/transport/binary/protocol/writer.js +++ b/lib/transport/binary/protocol/writer.js @@ -79,7 +79,7 @@ function writeString (data) { if (data === null) { return writeInt(-1); } - var stringBuf = new Buffer(data), + var stringBuf = encodeString(data), length = stringBuf.length, buf = new Buffer(constants.BYTES_INT + length); buf.writeInt32BE(length, 0); @@ -87,7 +87,32 @@ function writeString (data) { return buf; } +function encodeString (data) { + var length = data.length, + output = new Buffer(length * 3), // worst case, all chars could require 3-byte encodings. + j = 0, // index output + i, c; + for (i = 0; i < length; i++) { + c = data.charCodeAt(i); + if (c < 0x80) { + // 7-bits done in one byte. + output[j++] = c; + } + else if (c < 0x800) { + // 8-11 bits done in 2 bytes + output[j++] = (0xC0 | c >> 6); + output[j++] = (0x80 | c & 0x3F); + } + else { + // 12-16 bits done in 3 bytes + output[j++] = (0xE0 | c >> 12); + output[j++] = (0x80 | c >> 6 & 0x3F); + output[j++] = (0x80 | c & 0x3F); + } + } + return output.slice(0, j); +} exports.writeByte = writeByte; exports.writeBoolean = writeByte; diff --git a/test/bugs/82-emojis.js b/test/bugs/82-emojis.js index 1643e31..920cd73 100644 --- a/test/bugs/82-emojis.js +++ b/test/bugs/82-emojis.js @@ -25,11 +25,16 @@ describe("Bug #82: db.query errors when parsing emojis ", function () { return this.db.update(rid).set({value: 'hello 😢😂😭', foo: 'bar'}).one(); }); - it.skip('should allow emojis using db.query() directly', function () { - var query = 'UPDATE ' + rid + ' SET bio="Aiming to be Miranda Kerr, Candice Swanepoel, Adriana Lima, Alessandra Ambrosio, Doutzen Kroes, Erin Heatherton, or Behati Prinsloo 😢😂 foo"'; + it('should allow emojis using db.query() directly', function () { + var query = 'UPDATE #5:0 SET bio="😢😂"'; return this.db.query(query) + .bind(this) + .spread(function (result) { + result.should.eql(1); + return this.db.query('SELECT * FROM #5:0'); + }) .spread(function (result) { - result.should.equal(1); + result.bio.should.equal("😢😂"); }); }); -}); \ No newline at end of file +}); From 55c39483e24333e143e8d22ad915ba6151355496 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 30 Jul 2014 13:26:22 +0100 Subject: [PATCH 116/308] prepare 0.4.0 --- README.md | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ec562bf..e56dc0a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Oriento -A lightweight node.js driver for [orientdb](http://www.orientechnologies.com/orientdb/) using orient's binary protocol. +Official [orientdb](http://www.orientechnologies.com/orientdb/) driver for node.js. Fast, lightweight, uses the binary protocol. [![Build Status](https://travis-ci.org/codemix/oriento.svg?branch=master)](https://travis-ci.org/codemix/oriento) diff --git a/package.json b/package.json index 891f84f..fb7d79c 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oriento", - "description": "A fast, lightweight client for the orientdb binary protocol, supporting the latest version of orient.", + "description": "Official node.js driver for OrientDB. Fast, lightweight, uses the binary protocol.", "keywords": [ "orientdb", "orient", @@ -12,7 +12,7 @@ "node", "node.js" ], - "version": "0.3.2", + "version": "0.4.0", "author": { "name": "Charles Pick", "email": "charles@codemix.com" From 416a434307131e86fd0359935cfa86833d90ebed Mon Sep 17 00:00:00 2001 From: Ben Marks Date: Thu, 31 Jul 2014 12:36:00 -0500 Subject: [PATCH 117/308] bam - added handler for close event in Connection.negociateProtocol --- lib/transport/binary/connection.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/transport/binary/connection.js b/lib/transport/binary/connection.js index 63cc105..42881c6 100644 --- a/lib/transport/binary/connection.js +++ b/lib/transport/binary/connection.js @@ -217,6 +217,17 @@ Connection.prototype.negotiateProtocol = function () { this.socket.removeAllListeners(); reject(new errors.Connection(err.code, err.message)); }.bind(this)); + this.socket.once('close', function (err) { + this.logger.debug('connection closed during protocol negotiation: ' + err); + this.socket.removeAllListeners(); + this.connecting = false; + if (err) { + reject(new errors.Connection(err.code, err.message)); + } + else { + reject(new errors.Connection(0, 'Socket Closed')); + } + }.bind(this)); }.bind(this)); }; From 2b6c1c3a24a07bfd12e0f1e0028b8af90556f4ae Mon Sep 17 00:00:00 2001 From: Ben Marks Date: Fri, 1 Aug 2014 09:24:02 -0500 Subject: [PATCH 118/308] bam - changed from tabs to spaces --- lib/transport/binary/connection.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/transport/binary/connection.js b/lib/transport/binary/connection.js index 42881c6..04960a4 100644 --- a/lib/transport/binary/connection.js +++ b/lib/transport/binary/connection.js @@ -217,17 +217,17 @@ Connection.prototype.negotiateProtocol = function () { this.socket.removeAllListeners(); reject(new errors.Connection(err.code, err.message)); }.bind(this)); - this.socket.once('close', function (err) { - this.logger.debug('connection closed during protocol negotiation: ' + err); - this.socket.removeAllListeners(); - this.connecting = false; - if (err) { - reject(new errors.Connection(err.code, err.message)); - } - else { - reject(new errors.Connection(0, 'Socket Closed')); - } - }.bind(this)); + this.socket.once('close', function (err) { + this.logger.debug('connection closed during protocol negotiation: ' + err); + this.socket.removeAllListeners(); + this.connecting = false; + if (err) { + reject(new errors.Connection(err.code, err.message)); + } + else { + reject(new errors.Connection(0, 'Socket Closed')); + } + }.bind(this)); }.bind(this)); }; From 067daf4d45298b4a6f18104f8066a3fe5706ba89 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 13 Aug 2014 11:24:44 +0100 Subject: [PATCH 119/308] regexp should be global --- lib/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index a5288da..c4e18da 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -139,7 +139,7 @@ exports.prepare = function (query, params) { if (!params) { return query; } - var pattern = /"(\\[\s\S]|[^"])*"|'(\\[\s\S]|[^'])*'|:([A-Za-z][A-Za-z0-9_-]*|\/\*[\s\S]*?\*\/)/; + var pattern = /"(\\[\s\S]|[^"])*"|'(\\[\s\S]|[^'])*'|:([A-Za-z][A-Za-z0-9_-]*|\/\*[\s\S]*?\*\/)/g; return query.replace(pattern, function (all, double, single, param) { if (param) { return exports.encode(params[param]); From 9604db2997e99186d0972c99a70282bc3ebc697a Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 13 Aug 2014 11:24:57 +0100 Subject: [PATCH 120/308] add transactions example --- example/transactions.js | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 example/transactions.js diff --git a/example/transactions.js b/example/transactions.js new file mode 100644 index 0000000..d2e3b45 --- /dev/null +++ b/example/transactions.js @@ -0,0 +1,46 @@ +var config = require('../test/test-server.json'), + Oriento = require('../lib'), + oriento = Oriento(config), + db = oriento.use('GratefulDeadConcerts'); + + +db +.let('firstVertex', function (s) { + s + .create('vertex', 'V') + .set({ + foo: 'bar', + when: new Date() + }); +}) +.let('secondVertex', function (s) { + s + .create('vertex', 'V') + .set({ + greeting: 'Hello World', + nested: { + a: 1, + b: 2, + c: 3 + } + }); +}) +.let('joiningEdge', function (s) { + s + .create('edge', 'E') + .from('$firstVertex') + .to('$secondVertex') + .set({ + edgeProp1: 'a', + edgeProp2: 'b', + wat: true + }); +}) +.commit() +.return('$joiningEdge') +.all() +.then(function (results) { + console.log(results); + process.exit(); +}) +.done(); From a9505032dbd4b58f0d2dbee222255597a56a81d3 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 19 Aug 2014 19:36:02 +0100 Subject: [PATCH 121/308] add test for #111 --- test/bugs/111-expand.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 test/bugs/111-expand.js diff --git a/test/bugs/111-expand.js b/test/bugs/111-expand.js new file mode 100644 index 0000000..bde0dec --- /dev/null +++ b/test/bugs/111-expand.js @@ -0,0 +1,37 @@ +var Promise = require('bluebird'); + +describe("Bug #111: expand() returns only RIDs", function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_111') + .bind(this) + .then(function () { + return Promise.map([ + 'CREATE CLASS Widget EXTENDS V', + 'CREATE PROPERTY Widget.name STRING', + 'CREATE INDEX UniqueWidgetName ON Widget (name) UNIQUE', + + 'CREATE CLASS WidgetHasWidget EXTENDS E', + 'CREATE PROPERTY WidgetHasWidget.in LINK Widget', + 'CREATE PROPERTY WidgetHasWidget.out LINK Widget', + 'ALTER PROPERTY WidgetHasWidget.out MANDATORY=true', + 'ALTER PROPERTY WidgetHasWidget.in MANDATORY=true', + 'CREATE INDEX UniqueWidgetHasWidget ON WidgetHasWidget (out, in) UNIQUE', + + 'INSERT INTO Widget SET name = "widget_A"', + 'INSERT INTO Widget SET name = "widget_B"', + + 'CREATE EDGE WidgetHasWidget FROM (SELECT FROM Widget WHERE name = "widget_A") TO (SELECT FROM Widget WHERE name="widget_B")', + 'CREATE EDGE WidgetHasWidget FROM (SELECT FROM Widget WHERE name = "widget_B") TO (SELECT FROM Widget WHERE name="widget_A")' + ], this.db.query.bind(this.db)); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_111'); + }); + it('should return the full record', function () { + return this.db.query('SELECT expand(out(\'WidgetHasWidget\')) FROM Widget WHERE name = "widget_A"') + .spread(function (result) { + result.name.should.equal('widget_B'); + }); + }); +}); \ No newline at end of file From 5457fd044256bda76e1accf93fd919cbfbbf0efd Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 19 Aug 2014 19:36:46 +0100 Subject: [PATCH 122/308] update travis to OrientDB 1.7.8 and run tests on node 0.11 as well as 0.10 --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7e53df8..1ea7577 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: node_js node_js: - "0.10" + - "0.11" before_script: - ./ci/initialize-ci.sh $ORIENTDB_VERSION env: - - ORIENTDB_VERSION=1.7.4 + - ORIENTDB_VERSION=1.7.8 From ce36e3954ddbb685a20dbb907d244fbd8833998f Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 19 Aug 2014 19:58:59 +0100 Subject: [PATCH 123/308] fix jshint Promise issue --- .jshintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.jshintrc b/.jshintrc index 0b64b82..f89c880 100644 --- a/.jshintrc +++ b/.jshintrc @@ -65,7 +65,7 @@ "worker" : false, "wsh" : false, "yui" : false, - + "predef" : ["-Promise"], "globals" : { "angular" : false, "$" : false, From 5ba1f641e79ee1fce26813e0c12490dfdc924731 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 3 Sep 2014 12:31:14 +0100 Subject: [PATCH 124/308] don't test on 0.11 for now --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1ea7577..b3055d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: node_js node_js: - "0.10" - - "0.11" before_script: - ./ci/initialize-ci.sh $ORIENTDB_VERSION env: From 04c87669f993a7c6f2f834ebc28143608ea0455f Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 9 Sep 2014 14:50:18 +0100 Subject: [PATCH 125/308] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e56dc0a..71779d9 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Official [orientdb](http://www.orientechnologies.com/orientdb/) driver for node.js. Fast, lightweight, uses the binary protocol. [![Build Status](https://travis-ci.org/codemix/oriento.svg?branch=master)](https://travis-ci.org/codemix/oriento) - +[![Gitter chat](https://badges.gitter.im/codemix/oriento.png)](https://gitter.im/codemix/oriento) # Supported Versions From 18ff21e5be62424d52c938e1060a7b14fd549121 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 23 Sep 2014 17:50:56 +0100 Subject: [PATCH 126/308] prepare 0.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fb7d79c..e20e145 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node", "node.js" ], - "version": "0.4.0", + "version": "0.5.0", "author": { "name": "Charles Pick", "email": "charles@codemix.com" From 31a85dcc12343f1986ffe220d40389b94db12276 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 25 Sep 2014 13:13:31 +0100 Subject: [PATCH 127/308] disprove #119 --- test/bugs/119-link-rid.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 test/bugs/119-link-rid.js diff --git a/test/bugs/119-link-rid.js b/test/bugs/119-link-rid.js new file mode 100644 index 0000000..1484999 --- /dev/null +++ b/test/bugs/119-link-rid.js @@ -0,0 +1,35 @@ +var Statement = require('../../lib/db/statement'); + +describe("Bug #119: Set link field", function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_119') + .bind(this) + .then(function () { + return this.db.class.create('SomeClass'); + }) + .then(function (item) { + this.class = item; + return this.class.property.create({ + name: 'link', + type: 'Link' + }); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_119'); + }); + it('should insert a RID in a link field', function () { + return this.db + .insert() + .into('SomeClass') + .set({ + nom: 'nom', + link: new LIB.RID('#5:0') + }) + .one() + .then(function (response) { + response.nom.should.equal('nom'); + response.link.should.be.an.instanceOf(LIB.RID); + }); + }); +}); \ No newline at end of file From 0958bd34d8335273315006035b7046b065b8f051 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 25 Sep 2014 20:22:56 +0100 Subject: [PATCH 128/308] enable server-side Javascript for tests --- ci/orientdb-server-config.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/orientdb-server-config.xml b/ci/orientdb-server-config.xml index ed322be..2924c11 100644 --- a/ci/orientdb-server-config.xml +++ b/ci/orientdb-server-config.xml @@ -26,7 +26,7 @@ - + From e928cc0d5bb683a850f28048579e73dd71079652 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 25 Sep 2014 20:23:48 +0100 Subject: [PATCH 129/308] language should be passed to Command op --- lib/db/index.js | 1 + test/db/query-test.js | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/db/index.js b/lib/db/index.js index 341d2e4..3906f4a 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -155,6 +155,7 @@ Db.prototype.exec = function (query, options) { mode: options.mode || 's', fetchPlan: '', limit: -1, + language: options.language, class: options.class || 'com.orientechnologies.orient.core.sql.OCommandSQL' }; diff --git a/test/db/query-test.js b/test/db/query-test.js index 514a0a4..5a2868f 100644 --- a/test/db/query-test.js +++ b/test/db/query-test.js @@ -329,7 +329,16 @@ describe("Database API - Query", function () { result.results[0].content.length.should.be.above(0); }); }); - it('should execute a selectquery string', function () { + it('should execute a script command', function () { + return this.db.exec('123456;', { + language: 'javascript', + class: 's' + }) + .then(function (response) { + response.results.length.should.equal(1); + }); + }); + it('should execute a select query string', function () { return this.db.query('select from OUser where name=:name', { params: { name: 'Samson' From 5770a309b0a5ea0bc40be3e9d69e82838357876a Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 25 Sep 2014 20:44:55 +0100 Subject: [PATCH 130/308] fix #122, add support for DELETE statement qualifiers, e.g. DELETE EDGE --- lib/db/statement.js | 7 ++++++- test/db/statement-test.js | 12 ++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index 8fcdef7..ccd5452 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -319,7 +319,12 @@ Statement.prototype.buildStatement = function () { statement.push('INSERT'); } else if (state.delete) { - statement.push('DELETE'); + if (state.delete.length) { + statement.push('DELETE ' + state.delete.join(' ')); + } + else { + statement.push('DELETE'); + } } if (state.from && state.from.length) { diff --git a/test/db/statement-test.js b/test/db/statement-test.js index 76529fe..77f1998 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -155,6 +155,18 @@ COMMIT \n\ }); }); + describe('Statement::delete()', function () { + it('should delete a record', function () { + this.statement.delete().from('OUser').where({foo: 'bar', greeting: 'hello world'}); + this.statement.buildStatement().should.equal('DELETE FROM OUser WHERE (foo = :paramfoo0 AND greeting = :paramgreeting1)'); + }); + + it('should delete an edge', function () { + this.statement.delete('EDGE', 'foo').from(LIB.RID('#1:23')).to(LIB.RID('#4:56')); + this.statement.buildStatement().should.equal('DELETE EDGE foo FROM #1:23 TO #4:56'); + }); + }); + describe('Statement::from()', function () { it('should select from a class', function () { this.statement.select().from('OUser'); From d123813fb604973f232d0c21ad20b7166b9667b4 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 25 Sep 2014 21:09:40 +0100 Subject: [PATCH 131/308] prepare 0.5.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e20e145..23808a7 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node", "node.js" ], - "version": "0.5.0", + "version": "0.5.1", "author": { "name": "Charles Pick", "email": "charles@codemix.com" From 543aa1def3b6bc856de982bb8dfb1e634e33b777 Mon Sep 17 00:00:00 2001 From: Ram Rajamony Date: Mon, 29 Sep 2014 17:58:29 -0500 Subject: [PATCH 132/308] Added test for floating point numbers in scientific notation --- test/transport/binary/protocol/deserializer-test.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/transport/binary/protocol/deserializer-test.js b/test/transport/binary/protocol/deserializer-test.js index 7008568..1711b76 100644 --- a/test/transport/binary/protocol/deserializer-test.js +++ b/test/transport/binary/protocol/deserializer-test.js @@ -70,6 +70,12 @@ describe("Deserializer", function () { parsed[0].should.equal(1234); parsed[1].length.should.equal(0); }); + it('should eat a float in scientific notation', function () { + var input = '1.234e-04f'; + var parsed = deserializer.eatNumber(input); + parsed[0].should.equal(1.234e-04); + parsed[1].length.should.equal(0); + }); it('should eat a date', function () { var input = '1a'; var parsed = deserializer.eatNumber(input); @@ -241,4 +247,4 @@ describe("Deserializer", function () { parsed[1].length.should.equal(0); }); }); -}); \ No newline at end of file +}); From fe0e1a621eb0803f793b31b9b3c6b957f17a46ed Mon Sep 17 00:00:00 2001 From: Ram Rajamony Date: Mon, 29 Sep 2014 18:00:30 -0500 Subject: [PATCH 133/308] Fixed eatNumber to properly account for floating point numbers in scientific notation --- lib/transport/binary/protocol/deserializer.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/transport/binary/protocol/deserializer.js b/lib/transport/binary/protocol/deserializer.js index 716ebdc..5fd107f 100644 --- a/lib/transport/binary/protocol/deserializer.js +++ b/lib/transport/binary/protocol/deserializer.js @@ -227,14 +227,11 @@ function eatNumber (input) { collected = '', c, i; - for (i = 0; i < length; i++) { - c = input.charAt(i); - if (c === '-' || c === '.' || c === '0' || +c) { - collected += c; - } - else { - break; - } + var regexFP = /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/; + var num = input.match (regexFP); + if (num) { + collected = num[0]; + i = collected.length; } collected = +collected; @@ -536,4 +533,4 @@ exports.eatSet = eatSet; exports.eatMap = eatMap; exports.eatRecord = eatRecord; exports.eatBag = eatBag; -exports.eatBinary = eatBinary; \ No newline at end of file +exports.eatBinary = eatBinary; From d97e4973c2ce10838746fa83a40f230c9389d27b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=99=A9=20Ionic=C4=83=20Biz=C4=83u=20=E2=99=AB=20?= =?UTF-8?q?=E2=99=AA?= Date: Tue, 30 Sep 2014 12:02:08 +0300 Subject: [PATCH 134/308] Removed double space --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71779d9..3b51aba 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ npm test ### Configuring the client. ```js -var Oriento = require('oriento'); +var Oriento = require('oriento'); var server = Oriento({ host: 'localhost', From cf8033f8ceb35536673dacecd46b028c9742701b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=99=A9=20Ionic=C4=83=20Biz=C4=83u=20=E2=99=AB=20?= =?UTF-8?q?=E2=99=AA?= Date: Tue, 30 Sep 2014 12:06:54 +0300 Subject: [PATCH 135/308] Shell highlight --- README.md | 46 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 3b51aba..8ac0006 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Oriento aims to work with version 1.7.1 of orientdb and later. While it may work Install via npm. -``` +```sh npm install oriento ``` @@ -24,12 +24,13 @@ npm install oriento To run the test suite, first invoke the following command within the repo, installing the development dependencies: -``` +```sh npm install ``` Then run the tests: -``` + +```sh npm test ``` @@ -502,7 +503,7 @@ To be useful, oriento requires some arguments to authenticate against the server You can get a list of the supported arguments using `oriento --help`. -``` +```sh -d, --cwd The working directory to use. -h, --host The server hostname or IP address. -p, --port The server port. @@ -524,15 +525,21 @@ For an example of such a file, see [test/fixtures/oriento.opts](./test/fixtures/ ### Listing all the databases on the server. -`oriento db list` +```sh +oriento db list +``` ### Creating a new database -`oriento db create mydb graph plocal` +```sh +oriento db create mydb graph plocal +``` ### Destroying an existing database -`oriento db drop mydb` +```sh +oriento db drop mydb +``` ## Migrations @@ -568,11 +575,15 @@ manager.up(1) To list all the unapplied migrations: -`oriento migrate list` +```sh +oriento migrate list +``` ### Creating a new migration -`oriento migrate create my new migration` +```sh +oriento migrate create my new migration +``` creates a file called something like `m20140318_200948_my_new_migration` which you should edit to specify the migration up and down methods. @@ -581,23 +592,31 @@ creates a file called something like `m20140318_200948_my_new_migration` which y To apply all the migrations: -`oriento migrate up` +```sh +oriento migrate up +``` ### Migrating up by 1 To apply only the first migration: -`oriento migrate up 1` +```sh +oriento migrate up 1 +``` ### Migrating down fully To revert all migrations: -`oriento migrate down` +```sh +oriento migrate down +``` ### Migrating down by 1 -`oriento migrate down 1` +```sh +oriento migrate down 1 +``` # History @@ -609,7 +628,6 @@ In early 2014, [Giraldo Rosales](https://github.com/nitrog7) made a [whole host Later in 2014, codemix refactored the library to make it easier to extend and maintain, and introduced an API similar to [nano](https://github.com/dscape/nano). The result is so different from the original codebase that it warranted its own name and npm package. This also gave us the opportunity to switch to semantic versioning. - # Notes for contributors Please see [CONTRIBUTING](./CONTRIBUTING.md). From ac72a233b5a43cb237b361b546cc170048ad7eea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=99=A9=20Ionic=C4=83=20Biz=C4=83u=20=E2=99=AB=20?= =?UTF-8?q?=E2=99=AA?= Date: Tue, 30 Sep 2014 12:10:46 +0300 Subject: [PATCH 136/308] Replaced
 with Markdown code blocks

---
 CONTRIBUTING.md | 64 ++++++++++++++++++++++++++++++-------------------
 1 file changed, 39 insertions(+), 25 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a93b8a9..70120ac 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -14,13 +14,13 @@ Before running the tests, ensure you've configured your orientdb server to use t
 
 To run the tests:
 
-```
+```sh
 npm test
 ```
 
 To generate the code coverage report, run:
 
-```
+```sh
 npm run coverage
 ```
 
@@ -35,16 +35,20 @@ And have a look at `coverage/lcov-report/index.html`.
 
 
 ### 1. [Fork](http://help.github.com/fork-a-repo/) the oriento repository on github and clone your fork to your development environment
-
+
+```sh
 git clone git@github.com:YOUR-GITHUB-USERNAME/oriento.git
-
+``` + If you have trouble setting up GIT with GitHub in Linux, or are getting errors like "Permission Denied (publickey)", then you must [setup your GIT installation to work with GitHub](http://help.github.com/linux-set-up-git/) ### 2. Add the main oriento repository as an additional git remote called "upstream" Change to the directory where you cloned oriento normally, "oriento". Then enter the following command: -
+
+```sh
 git remote add upstream git://github.com/codemix/oriento.git
-
+``` + ### 3. Make sure there is an issue created for the thing you are working on. @@ -53,9 +57,11 @@ All new features and bug fixes should have an associated issue to provide a sing > For small changes or documentation issues, you don't need to create an issue, a pull request is enough in this case. ### 4. Fetch the latest code from the main oriento branch -
+
+```sh
 git fetch upstream
-
+``` + You should start at this point for every new contribution to make sure you are working on the latest code. ### 5. Create a new branch for your feature based on the current oriento master branch @@ -63,10 +69,11 @@ You should start at this point for every new contribution to make sure you are w > That's very important since you will not be able to submit more than one pull request from your account if you'll use master. Each separate bug fix or change should go in its own branch. Branch names should be descriptive and start with the number of the issue that your code relates to. If you aren't fixing any particular issue, just skip number. For example: -
+
+```sh
 git checkout upstream/master
 git checkout -b 999-name-of-your-branch-goes-here
-
+``` ### 6. Do your magic, write your code Make sure it works and run the tests :) @@ -76,26 +83,33 @@ Unit tests are always welcome. Tested and well covered code greatly simplifies t ### 7. Commit your changes add the files/changes you want to commit to the [staging area](http://gitref.org/basic/#add) with -
+
+```sh
 git add path/to/my/file.js
-
+``` + You can use the -p option to select the changes you want to have in your commit. Commit your changes with a descriptive commit message. Make sure to mention the ticket number with #XXX so github will automatically link your commit with the ticket: -
+
+```sh
 git commit -m "A brief description of this change which fixes #42 goes here"
-
+``` ### 8. Pull the latest oriento code from upstream into your branch -
+
+```sh
 git pull upstream master
-
+``` + This ensures you have the latest code in your branch before you open your pull request. If there are any merge conflicts, you should fix them now and commit the changes again. This ensures that it's easy for the oriento team to merge your changes with one click. ### 9. Having resolved any conflicts, push your code to github -
+
+```sh
 git push -u origin 999-name-of-your-branch-goes-here
-
+``` + The `-u` parameter ensures that your branch will now automatically push and pull from the github branch. That means if you type `git push` the next time it will know where to push to. ### 10. Open a [pull request](http://help.github.com/send-pull-requests/) against upstream. @@ -109,27 +123,27 @@ Someone will review your code, and you might be asked to make some changes, if s ### 12. Cleaning it up After your code was either accepted or declined you can delete branches you've worked with from your local repository and `origin`. -
+
+```sh
 git checkout master
 git branch -D 999-name-of-your-branch-goes-here
 git push origin --delete 999-name-of-your-branch-goes-here
-
+``` ### Command overview (for advanced contributors) -
+```sh
 git clone git@github.com:YOUR-GITHUB-USERNAME/oriento.git
 git remote add upstream git://github.com/codemix/oriento.git
-
-
+
 git fetch upstream
 git checkout upstream/master
 git checkout -b 999-name-of-your-branch-goes-here
 
-/* do your magic, update changelog if needed */
+# do your magic, update changelog if needed
 
 git add path/to/my/file.js
 git commit -m "A brief description of this change which fixes #42 goes here"
 git pull upstream master
 git push -u origin 999-name-of-your-branch-goes-here
-
\ No newline at end of file +``` From 4fa3a33c88fa31f0cbffee3bf41f164aecf3b99a Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 30 Sep 2014 12:00:00 +0100 Subject: [PATCH 137/308] code style tweak --- lib/transport/binary/protocol/deserializer.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/transport/binary/protocol/deserializer.js b/lib/transport/binary/protocol/deserializer.js index 5fd107f..68b3bc0 100644 --- a/lib/transport/binary/protocol/deserializer.js +++ b/lib/transport/binary/protocol/deserializer.js @@ -225,13 +225,13 @@ function eatString (input) { function eatNumber (input) { var length = input.length, collected = '', - c, i; + pattern = /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/, + num, c, i; - var regexFP = /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/; - var num = input.match (regexFP); + num = input.match(pattern); if (num) { - collected = num[0]; - i = collected.length; + collected = num[0]; + i = collected.length; } collected = +collected; From 94d096f8b10563a019d4778fbb090b336400070e Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 12 Oct 2014 23:41:18 +0100 Subject: [PATCH 138/308] add retry / error handling --- lib/transport/binary/index.js | 12 ++++++++++++ test/bugs/110-connection-lifecycle.js | 27 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 test/bugs/110-connection-lifecycle.js diff --git a/lib/transport/binary/index.js b/lib/transport/binary/index.js index 81a39cb..ca74761 100644 --- a/lib/transport/binary/index.js +++ b/lib/transport/binary/index.js @@ -41,6 +41,8 @@ module.exports = BinaryTransport; BinaryTransport.prototype.configure = function (config) { this.connecting = false; this.closing = false; + this.retries = 0; + this.maxRetries = config.maxRetries || 5; this.host = config.host || config.hostname || 'localhost'; this.port = config.port || 2424; @@ -88,6 +90,15 @@ BinaryTransport.prototype.configureConnection = function () { this.logger.debug('updating config...'); this.transportCluster = config; }.bind(this)); + this.connection.on('error', function (err) { + if (this.retries++ > this.maxRetries) { + return this.emit('error', err); + } + this.sessionId = -1; + this.connecting = false; + this.configureConnection(); + + }.bind(this)); return this; }; @@ -135,6 +146,7 @@ BinaryTransport.prototype.connect = function () { .then(function (response) { this.logger.debug('got session id: ' + response.sessionId); this.sessionId = response.sessionId; + this.retries = 0; return this; }); diff --git a/test/bugs/110-connection-lifecycle.js b/test/bugs/110-connection-lifecycle.js new file mode 100644 index 0000000..3d15c3b --- /dev/null +++ b/test/bugs/110-connection-lifecycle.js @@ -0,0 +1,27 @@ +var Statement = require('../../lib/db/statement'); + +describe("Bug #110: Connection lifecycle", function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_110'); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_110'); + }); + it('should not crash if the connection is interrupted', function () { + var promise = this.db.class.list(); + this.db.server.transport.connection.socket.emit('error', {errnum: 100, message: 'ENETDOWN'}); + return promise + .bind(this) + .then(function (classes) { + return this.db.record.get('#5:0'); + }) + .then(function (rec) { + var promise = this.db.record.get('#5:1'); + this.db.server.transport.connection.socket.emit('error', {errnum: 104, message: 'ECONNRESET'}); + return promise; + }) + .then(function (rec) { + (rec['@rid']+'').should.equal('#5:1'); + }); + }); +}); \ No newline at end of file From 7c251799b885b1ee2f0e4dafea38c8abfa0fd001 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 13 Oct 2014 15:40:25 +0100 Subject: [PATCH 139/308] handle database.close() --- lib/db/index.js | 14 ++++++++++++++ lib/transport/binary/index.js | 2 +- test/bugs/110-connection-lifecycle.js | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/db/index.js b/lib/db/index.js index 3906f4a..23240d2 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -91,6 +91,20 @@ Db.prototype.open = function () { }); }; +/** + * Close the database. + * + * @promise {Db} The now closed db instance. + */ +Db.prototype.close = function () { + return this.server.send('db-close') + .bind(this) + .then(function () { + this.sessionId = -1; + return this; + }); +}; + /** * Send the given operation to the server, ensuring the * database is open first. diff --git a/lib/transport/binary/index.js b/lib/transport/binary/index.js index ca74761..7e85a25 100644 --- a/lib/transport/binary/index.js +++ b/lib/transport/binary/index.js @@ -96,8 +96,8 @@ BinaryTransport.prototype.configureConnection = function () { } this.sessionId = -1; this.connecting = false; - this.configureConnection(); + this.configureConnection(); }.bind(this)); return this; }; diff --git a/test/bugs/110-connection-lifecycle.js b/test/bugs/110-connection-lifecycle.js index 3d15c3b..1022248 100644 --- a/test/bugs/110-connection-lifecycle.js +++ b/test/bugs/110-connection-lifecycle.js @@ -24,4 +24,18 @@ describe("Bug #110: Connection lifecycle", function () { (rec['@rid']+'').should.equal('#5:1'); }); }); + it('should close the database connection', function () { + return this.db.close() + .bind(this) + .then(function (db) { + db.should.equal(this.db); + return db.record.get('#5:0'); + }) + .catch(function (err) { + return this.db.record.get('#5:0'); + }) + .then(function (rec) { + (''+rec['@rid']).should.equal('#5:0'); + }); + }); }); \ No newline at end of file From 59d96ed25d8886590c79feff03f089b48377119c Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Tue, 21 Oct 2014 10:33:20 +0100 Subject: [PATCH 140/308] Adds a return clause before where as per OrientDB's documentation (https://github.com/orientechnologies/orientdb/wiki/SQL-Update). The choice of "returning" was made to avoid conflict with the already existing "return". Issue #134 --- lib/db/statement.js | 15 +++++++++++++++ test/db/statement-test.js | 13 ++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index ccd5452..9008f36 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -117,6 +117,17 @@ Statement.prototype.upsert = function (condition, params, comparisonOperator) { return this; }; +/** + * Returning specifies what to return from the query. + * + * @param {String} value Can be COUNT | BEFORE | AFTER | @rid | @this. + * @return {Statement} The statement object. + */ +Statement.prototype.returning = function (value) { + this._state.returning = value; + return this; +}; + /** * Specify the where clause for the statement. * @@ -389,6 +400,10 @@ Statement.prototype.buildStatement = function () { if (state.upsert) { statement.push('UPSERT'); } + + if (state.returning) { + statement.push('RETURN ' + state.returning); + } if (state.where) { statement.push('WHERE'); diff --git a/test/db/statement-test.js b/test/db/statement-test.js index 77f1998..1c58f6d 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -181,6 +181,17 @@ COMMIT \n\ this.statement.buildStatement().should.equal('SELECT * FROM (SELECT * FROM OUser)'); }); }); + + describe('Statement::returning()', function () { + it('should build a return clause', function () { + this.statement.update('#1:1').set({foo: 'bar', greeting: 'hello world'}).returning('AFTER'); + this.statement.buildStatement().should.equal('UPDATE #1:1 SET foo = :paramfoo0, greeting = :paramgreeting1 RETURN AFTER'); + }); + it('should build a return clause before the where clause', function () { + this.statement.update('#1:1').set({foo: 'bar', greeting: 'hello world'}).returning('AFTER').where('1=1'); + this.statement.buildStatement().should.equal('UPDATE #1:1 SET foo = :paramfoo0, greeting = :paramgreeting1 RETURN AFTER WHERE 1=1'); + }); + }); describe('Statement::to()', function () { it('should create an edge', function () { @@ -257,5 +268,5 @@ COMMIT \n\ this.statement.update('OUser').set("foo = 'bar'").upsert('1 = 1'); this.statement.buildStatement().should.equal("UPDATE OUser SET foo = 'bar' UPSERT WHERE 1 = 1"); }); - }) + }); }); \ No newline at end of file From 3f9ca23ab4f4a0ad30143533ac115e1e7b301a32 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Tue, 21 Oct 2014 15:29:10 +0100 Subject: [PATCH 141/308] Issue #134: introduced phpnode suggestions and changed returning method to return in order to make it clearer. It will only take effect for update, insert and delete queries. --- lib/db/statement.js | 27 +++++++++------------------ test/db/statement-test.js | 36 +++++++++++++++++++++++++----------- 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index 9008f36..14cb772 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -117,17 +117,6 @@ Statement.prototype.upsert = function (condition, params, comparisonOperator) { return this; }; -/** - * Returning specifies what to return from the query. - * - * @param {String} value Can be COUNT | BEFORE | AFTER | @rid | @this. - * @return {Statement} The statement object. - */ -Statement.prototype.returning = function (value) { - this._state.returning = value; - return this; -}; - /** * Specify the where clause for the statement. * @@ -243,13 +232,15 @@ Statement.prototype.commit = function (retryLimit) { }; /** - * Return a certain variable. + * Return a certain variable if there is a let clause. + * For update, insert or delete statements it will add a RETURN clause before + * the WHERE clause. * - * @param {String} name The name of the variable to return. + * @param {String} value The name of the variable or what to return. * @return {Statement} The statement object. */ -Statement.prototype.return = function (name) { - this._state.return = name; +Statement.prototype.return = function (value) { + this._state.return = value; return this; }; @@ -401,8 +392,8 @@ Statement.prototype.buildStatement = function () { statement.push('UPSERT'); } - if (state.returning) { - statement.push('RETURN ' + state.returning); + if ((state.update || state.insert || state.delete) && state.return) { + statement.push('RETURN ' + state.return); } if (state.where) { @@ -497,7 +488,7 @@ Statement.prototype.buildStatement = function () { statement.push('\n'); } - if (state.return) { + if (!(state.update || state.insert || state.delete) && state.return) { statement.push('RETURN ' + state.return); } return statement.join(' '); diff --git a/test/db/statement-test.js b/test/db/statement-test.js index 1c58f6d..9261006 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -124,6 +124,16 @@ COMMIT \n\ .should .equal('BEGIN\n LET names = UPDATE OUser SET name = "name"\n \nCOMMIT \n RETURN $names'); }); + it('should generate an update transaction, with returns and a return clause before while', function () { + var sub = (new Statement(this.db)).update('OUser').set({name: 'name'}).return('COUNT'); + this.statement + .let('names', sub) + .commit() + .return('$names') + .toString() + .should + .equal('BEGIN\n LET names = UPDATE OUser SET name = "name" RETURN COUNT\n \nCOMMIT \n RETURN $names'); + }); }); describe('Statement::select()', function () { @@ -181,17 +191,6 @@ COMMIT \n\ this.statement.buildStatement().should.equal('SELECT * FROM (SELECT * FROM OUser)'); }); }); - - describe('Statement::returning()', function () { - it('should build a return clause', function () { - this.statement.update('#1:1').set({foo: 'bar', greeting: 'hello world'}).returning('AFTER'); - this.statement.buildStatement().should.equal('UPDATE #1:1 SET foo = :paramfoo0, greeting = :paramgreeting1 RETURN AFTER'); - }); - it('should build a return clause before the where clause', function () { - this.statement.update('#1:1').set({foo: 'bar', greeting: 'hello world'}).returning('AFTER').where('1=1'); - this.statement.buildStatement().should.equal('UPDATE #1:1 SET foo = :paramfoo0, greeting = :paramgreeting1 RETURN AFTER WHERE 1=1'); - }); - }); describe('Statement::to()', function () { it('should create an edge', function () { @@ -204,6 +203,21 @@ COMMIT \n\ }); }); + describe('Statement::return()', function () { + it('should build a return clause', function () { + this.statement.update('#1:1').set({foo: 'bar', greeting: 'hello world'}).return('AFTER'); + this.statement.buildStatement().should.equal('UPDATE #1:1 SET foo = :paramfoo0, greeting = :paramgreeting1 RETURN AFTER'); + }); + it('should build a return clause before the where clause', function () { + this.statement.delete().from('OUser').return('BEFORE').where({foo: 'bar', greeting: 'hello world'}); + this.statement.buildStatement().should.equal('DELETE FROM OUser RETURN BEFORE WHERE (foo = :paramfoo0 AND greeting = :paramgreeting1)'); + }); + it('should build a return clause after the insert query', function () { + this.statement.insert().into('OUser').set({foo: 'bar', greeting: 'hello world'}).return('AFTER'); + this.statement.buildStatement().should.equal('INSERT INTO OUser SET foo = :paramfoo0, greeting = :paramgreeting1 RETURN AFTER'); + }); + }); + describe('Statement::where(), Statement::and(), Statement::or()', function () { it('should build a where clause with an expression', function () { this.statement.select().from('OUser').where('1=1'); From 1be7743ed351d1ccde686931e3c489fd9cd49c08 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 6 Nov 2014 23:31:57 +0000 Subject: [PATCH 142/308] fix #139, allow embedded queries in FROM --- lib/db/index.js | 10 ++++++++++ lib/db/statement.js | 11 ++++++++++- test/db/statement-test.js | 10 ++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/db/index.js b/lib/db/index.js index 23240d2..de0370d 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -320,6 +320,16 @@ Db.prototype.createQuery = function () { return new Query(this); }; +/** + * Create a create query. + * + * @return {Query} The query instance. + */ +Db.prototype.create = function () { + var query = this.createQuery(); + return query.create.apply(query, arguments); +}; + /** * Create a select query. * diff --git a/lib/db/statement.js b/lib/db/statement.js index ccd5452..b1d8ddb 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -330,7 +330,16 @@ Statement.prototype.buildStatement = function () { if (state.from && state.from.length) { statement.push('FROM'); statement.push(state.from.map(function (item) { - if (typeof item === 'string') { + if (typeof item === 'function') { + var child = new Statement(self.db); + child._state.paramIndex = self._state.paramIndex; + item(child); + return '(' + child.toString() + ')'; + } + else if (item instanceof Statement) { + return '(' + item.toString() + ')'; + } + else if (typeof item === 'string') { return item; } else { diff --git a/test/db/statement-test.js b/test/db/statement-test.js index 77f1998..de84907 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -180,6 +180,16 @@ COMMIT \n\ this.statement.select().from('(SELECT * FROM OUser)'); this.statement.buildStatement().should.equal('SELECT * FROM (SELECT * FROM OUser)'); }); + it('should select from a subquery', function () { + this.statement.select().from((new Statement(this.db).select().from('OUser'))); + this.statement.buildStatement().should.equal('SELECT * FROM (SELECT * FROM OUser)'); + }); + it('should select from a subquery, using a function', function () { + this.statement.select().from(function (s) { + s.select().from('OUser'); + }); + this.statement.buildStatement().should.equal('SELECT * FROM (SELECT * FROM OUser)'); + }); }); describe('Statement::to()', function () { From 67dae022e1302eabcb202324af443eff3183b26f Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 13 Nov 2014 14:47:44 +0000 Subject: [PATCH 143/308] prepare 0.5.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23808a7..4299b26 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node", "node.js" ], - "version": "0.5.1", + "version": "0.5.2", "author": { "name": "Charles Pick", "email": "charles@codemix.com" From 1e1432f76dab1ecd75cf56b2fa726024d9b447ef Mon Sep 17 00:00:00 2001 From: prasad83 Date: Wed, 19 Nov 2014 12:34:30 +0530 Subject: [PATCH 144/308] Binary transport - destroy socket during close Using socket.end is hanging the process - socket.destroy with case-handling seem to be making it work. Refer: http://nodejs.org/api/net.html#net_socket_destroy vs http://nodejs.org/api/net.html#net_socket_end_data_encoding --- lib/transport/binary/connection.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/transport/binary/connection.js b/lib/transport/binary/connection.js index 04960a4..9f54898 100644 --- a/lib/transport/binary/connection.js +++ b/lib/transport/binary/connection.js @@ -133,7 +133,10 @@ Connection.prototype.createSocket = function () { */ Connection.prototype.close = function () { if (this.socket) { - this.socket.end(); + //this.socket.end(); + // to avoid hangup of process we need to destroy socket completely. + this.socket.removeAllListeners(); + this.socket.destroy(); this.socket = null; } return this; @@ -219,7 +222,7 @@ Connection.prototype.negotiateProtocol = function () { }.bind(this)); this.socket.once('close', function (err) { this.logger.debug('connection closed during protocol negotiation: ' + err); - this.socket.removeAllListeners(); + if (this.socket) this.socket.removeAllListeners(); // close earlier could have destroyed set socket to null. this.connecting = false; if (err) { reject(new errors.Connection(err.code, err.message)); @@ -367,4 +370,4 @@ Connection.prototype.process = function (buffer, offset) { } } return offset; -}; \ No newline at end of file +}; From 0ac7395a623fe9679a693c5007afbd2c8fea2f14 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 21 Nov 2014 22:25:52 +0000 Subject: [PATCH 145/308] fix error messages, no more RequestError: null --- lib/transport/binary/protocol/operation.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/transport/binary/protocol/operation.js b/lib/transport/binary/protocol/operation.js index 99410e1..f2e1130 100644 --- a/lib/transport/binary/protocol/operation.js +++ b/lib/transport/binary/protocol/operation.js @@ -805,7 +805,6 @@ Operation.prototype.parseArray = function (buffer, offset, context, fieldName, r Operation.prototype.parseError = function (buffer, offset, context, fieldName, reader) { var err = new errors.Request(); err.previous = []; - // remove any ops we were expecting to run. this.readOps = []; @@ -822,11 +821,9 @@ Operation.prototype.parseError = function (buffer, offset, context, fieldName, r if (data.hasMore) { prev = new errors.Request(); err.previous.push(prev); + this.stack.pop(); this.stack.push(prev); readItem.call(this); - this.readOps.push(function () { - this.stack.pop(); - }); } else { this.readBytes('javaStackTrace', function (data) { From 291e401126af432b696907bc92e40ccdec143ac5 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 21 Nov 2014 22:33:33 +0000 Subject: [PATCH 146/308] style tweaks --- lib/transport/binary/connection.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/transport/binary/connection.js b/lib/transport/binary/connection.js index 9f54898..4e9ca09 100644 --- a/lib/transport/binary/connection.js +++ b/lib/transport/binary/connection.js @@ -133,7 +133,6 @@ Connection.prototype.createSocket = function () { */ Connection.prototype.close = function () { if (this.socket) { - //this.socket.end(); // to avoid hangup of process we need to destroy socket completely. this.socket.removeAllListeners(); this.socket.destroy(); @@ -222,7 +221,9 @@ Connection.prototype.negotiateProtocol = function () { }.bind(this)); this.socket.once('close', function (err) { this.logger.debug('connection closed during protocol negotiation: ' + err); - if (this.socket) this.socket.removeAllListeners(); // close earlier could have destroyed set socket to null. + if (this.socket) { + this.socket.removeAllListeners(); // close earlier could have destroyed & set socket to null. + } this.connecting = false; if (err) { reject(new errors.Connection(err.code, err.message)); From f33a31a7e6c1552cd12e0213e9f2580368f5b45c Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 21 Nov 2014 23:24:46 +0000 Subject: [PATCH 147/308] add RID::equals() --- lib/recordid.js | 23 +++++++++++++++++++++++ test/core/recordid.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 test/core/recordid.js diff --git a/lib/recordid.js b/lib/recordid.js index 27012e0..3d2e449 100644 --- a/lib/recordid.js +++ b/lib/recordid.js @@ -74,6 +74,29 @@ RecordID.prototype.isValid = function () { return this.cluster == +this.cluster && this.position == +this.position; }; +/** + * Determine whether the record id is equal to another. + * + * @param {String|RID} rid The RID to compare with. + * @return {Boolean} If the RID matches, then true. + */ +RecordID.prototype.equals = function (rid) { + if (rid === this) { + return true; + } + else if (typeof rid === 'string') { + return this.toString() === rid; + } + else if (rid instanceof RecordID) { + return rid.cluster === this.cluster && rid.position === this.position; + } + else if ((rid = RecordID.parse(rid))) { + return rid.cluster === this.cluster && rid.position === this.position; + } + else { + return false; + } +}; /** * Parse a record id into a RecordID object. diff --git a/test/core/recordid.js b/test/core/recordid.js new file mode 100644 index 0000000..a64a76c --- /dev/null +++ b/test/core/recordid.js @@ -0,0 +1,28 @@ +describe('RecordID', function () { + var rid = LIB.RID('#1:23'); + + describe('RecordID::equals()', function () { + it('should equal an identical record', function () { + rid.equals(rid).should.be.true; + }); + it('should equal a string representation of the record', function () { + rid.equals("#1:23").should.be.true; + }); + it('should not equal a different record', function () { + rid.equals(LIB.RID("4:56")).should.be.false; + }); + it('should not equal a string representation a different record', function () { + rid.equals("4:56").should.be.false; + }); + it('should equal an identical record expressed as a POJO', function () { + rid.equals({cluster: 1, position: 23}).should.be.true; + }); + it('should not equal a different record expressed as a POJO', function () { + rid.equals({cluster: 4, position: 56}).should.be.false; + }); + it('should not equal nonsense', function () { + rid.equals(false).should.be.false; + rid.equals("blah").should.be.false; + }) + }); +}); \ No newline at end of file From e76413a1b0fc3540e13d334ff3ab1799c014a4ac Mon Sep 17 00:00:00 2001 From: John Le Drew Date: Mon, 24 Nov 2014 12:18:10 +0000 Subject: [PATCH 148/308] List CLI command output current data. Make list command output the correct list of dbs on the server (also removed the help / guidance messages so it just outputs a plain list I can pipe into grep easily) --- lib/cli/commands/db.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/cli/commands/db.js b/lib/cli/commands/db.js index 4822f1f..882f829 100644 --- a/lib/cli/commands/db.js +++ b/lib/cli/commands/db.js @@ -23,11 +23,10 @@ exports.create = function (name, type, storage) { * List the databases on the server. */ exports.list = function () { - console.log('The following databases exist on the server:'); return this.server.list() .then(function (dbs) { - Object.keys(dbs).forEach(function (name) { - console.log('\t\t' + name); + dbs.forEach(function (db) { + console.log(db.name); }); }); }; From 788bd8e09cd3404d304302060a36d34bb08f3d39 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 24 Nov 2014 15:24:31 +0000 Subject: [PATCH 149/308] add db.registerTransformer() --- lib/db/index.js | 30 +++++++++++++++++ test/db/db-test.js | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/lib/db/index.js b/lib/db/index.js index de0370d..6e0cdb3 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -52,6 +52,7 @@ Db.prototype.configure = function (config) { this.password = config.password || 'admin'; this.dataSegments = []; this.transactionId = 0; + this.transformers = config.transformers || {}; return this; }; @@ -302,6 +303,9 @@ Db.prototype.normalizeResultContent = function (content) { cluster: content.cluster, position: content.position }); + if (value['@class']) { + return this.transformDocument(value); + } return value; } else { @@ -309,6 +313,32 @@ Db.prototype.normalizeResultContent = function (content) { } }; +Db.prototype.registerTransformer = function (className, transformer) { + this.transformers[className] = this.transformers[className] || []; + this.transformers[className].push(transformer); + return this; +}; + + +/** + * Transform a document according to its `@class` property, using the registered transformers. + * @param {Object} document The document to transform. + * @return {Mixed} The transformed document. + */ +Db.prototype.transformDocument = function (document) { + var className = document['@class']; + if (this.transformers[className]) { + return this.transformers[className].reduce(function (document, transformer) { + return transformer(document); + }, document); + } + else { + return document; + } +}; + + + // # Query Builder Methods /** diff --git a/test/db/db-test.js b/test/db/db-test.js index f5c3855..6df7dbb 100644 --- a/test/db/db-test.js +++ b/test/db/db-test.js @@ -1,3 +1,5 @@ +var Promise = require('bluebird'); + describe("Database API", function () { before(function () { return TEST_SERVER.create({ @@ -72,4 +74,83 @@ describe("Database API", function () { }); }); }); + + describe('Db::registerTransformer()', function () { + function OUser (data) { + if (!(this instanceof OUser)) { + return new OUser(data); + } + var keys = Object.keys(data), + length = keys.length, + key, i; + for (i = 0; i < length; i++) { + key = keys[i]; + this[key] = data[key]; + } + } + + function ORole (data) { + if (!(this instanceof ORole)) { + return new ORole(data); + } + var keys = Object.keys(data), + length = keys.length, + key, i; + for (i = 0; i < length; i++) { + key = keys[i]; + this[key] = data[key]; + } + } + + + before(function () { + this.db.registerTransformer('OUser', OUser); + this.db.registerTransformer('ORole', ORole); + }); + + it('should register a transformation function for a class', function () { + return Promise.all([ + this.db.select().from('OUser').all(), + this.db.select().from('ORole').all() + ]) + .spread(function (users, roles) { + users.length.should.be.above(0); + users.forEach(function (user) { + user.should.be.an.instanceOf(OUser); + }); + roles.length.should.be.above(0); + roles.forEach(function (role) { + role.should.be.an.instanceOf(ORole); + }); + }); + }); + + it('should transform documents even when they are nested', function () { + return this.db.select().from('OUser').fetch({roles: 1}).all() + .then(function (users) { + users.length.should.be.above(0); + users.forEach(function (user) { + user.should.be.an.instanceOf(OUser); + user.roles.length.should.be.above(0); + user.roles.forEach(function (role) { + role.should.be.an.instanceOf(ORole); + }); + }); + }); + }); + + it('should still allow scalars', function () { + return this.db.select().from('OUser').limit(1).scalar() + .then(function (result) { + result.should.equal('admin'); + }); + }); + + it('should not transform when individual columns are selected', function () { + return this.db.select('name, status').from('OUser').limit(1).one() + .then(function (result) { + result.should.not.be.an.instanceOf(OUser); + }); + }); + }); }); \ No newline at end of file From e129b1d19f1613b7631e157971dc0cdef1753bb1 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 24 Nov 2014 15:41:59 +0000 Subject: [PATCH 150/308] add registerTransformer() docs [ci skip] --- lib/db/index.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/db/index.js b/lib/db/index.js index 6e0cdb3..db36579 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -313,6 +313,15 @@ Db.prototype.normalizeResultContent = function (content) { } }; +/** + * Register a transformer function for documents of the given class. + * This function will be invoked for each document of the specified class + * in all future result sets. + * + * @param {String} className The name of the document class. + * @param {Function} transformer The transformer function. + * @return {Db} The database instance. + */ Db.prototype.registerTransformer = function (className, transformer) { this.transformers[className] = this.transformers[className] || []; this.transformers[className].push(transformer); From 860c974b3f8a390e5d8a46dc855d03538bf36e19 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 26 Nov 2014 09:49:55 +0000 Subject: [PATCH 151/308] ensure prefetched records are resolved via ridbags --- lib/bag.js | 8 +++++++- lib/db/record.js | 6 ++++++ test/core/bag-test.js | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/bag.js b/lib/bag.js index ad302a4..75cbe0d 100644 --- a/lib/bag.js +++ b/lib/bag.js @@ -25,6 +25,7 @@ function Bag (serialized) { this._offset = 0; this._current = -1; this._size = null; + this._prefetchedRecords = null; } Bag.BAG_EMBEDDED = 0; @@ -133,7 +134,12 @@ Bag.prototype.next = function () { } this._current++; rid = this._consume(); - this._content.push(rid); + if (this._prefetchedRecords && this._prefetchedRecords[rid]) { + this._content.push(this._prefetchedRecords[rid]); + } + else { + this._content.push(rid); + } return rid; } }; diff --git a/lib/db/record.js b/lib/db/record.js index 947e224..136b589 100644 --- a/lib/db/record.js +++ b/lib/db/record.js @@ -2,6 +2,7 @@ var Promise = require('bluebird'), RID = require('../recordid'), + RIDBag = require('../bag'), errors = require('../errors'); /** @@ -150,6 +151,11 @@ function recordIdResolver () { /*jshint validthis:true */ return obj.map(replaceRecordIds.bind(this, records)); } + else if (obj instanceof RIDBag) { + /*jshint validthis:true */ + obj._prefetchedRecords = records; + return obj; + } else if (obj instanceof RID && records[obj]) { return records[obj]; } diff --git a/test/core/bag-test.js b/test/core/bag-test.js index a90242a..d2e8297 100644 --- a/test/core/bag-test.js +++ b/test/core/bag-test.js @@ -74,7 +74,7 @@ describe("RID Bag", function () { var contents = this.bag.all(); contents.length.should.equal(10); contents.forEach(function (item) { - item.should.be.an.instanceOf(LIB.RID); + item.should.have.property('@rid'); }); }); @@ -83,7 +83,7 @@ describe("RID Bag", function () { decoded = JSON.parse(json); decoded.length.should.equal(10); decoded.forEach(function (item) { - (typeof item).should.equal('string'); + item.should.have.property('@rid'); }); }); }); From 714be17fa6bea04cc05f1066083bb5ff2ba09f75 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 26 Nov 2014 12:18:40 +0000 Subject: [PATCH 152/308] guarantee migration order --- lib/migration/manager.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/migration/manager.js b/lib/migration/manager.js index b1ee49f..a50b823 100644 --- a/lib/migration/manager.js +++ b/lib/migration/manager.js @@ -81,8 +81,7 @@ MigrationManager.prototype.create = function (config) { * @return {String} The generated JavaScript source code. */ MigrationManager.prototype.generateMigration = function (config) { - var content = 'exports.name = ' + JSON.stringify(config.name) + ';\n\n'; - content += 'exports.db = ' + JSON.stringify(config.db) + ';\n\n'; + var content = '"use strict";\nexports.name = ' + JSON.stringify(config.name) + ';\n\n'; content += 'exports.up = function (db) {\n // @todo implementation\n};\n\n'; content += 'exports.down = function (db) {\n // @todo implementation\n};\n\n'; return content; @@ -103,10 +102,8 @@ MigrationManager.prototype.list = function () { this.listApplied() ]); }) - .then(function (args) { - var available = args[0], - applied = args[1], - pending = [], + .spread(function (available, applied) { + var pending = [], totalAvailable = available.length, totalApplied = applied.length, item, other, i, j, found; @@ -126,6 +123,10 @@ MigrationManager.prototype.list = function () { } } return pending; + }) + .then(function (migrations) { + migrations.sort(); + return migrations; }); }; @@ -226,6 +227,7 @@ MigrationManager.prototype.down = function (limit) { return item.name; }) .then(function (items) { + items.sort(); return items.reverse(); }) .filter(function (item, index) { @@ -302,4 +304,5 @@ MigrationManager.prototype.revertMigration = function (name) { }) .return(result); }); -}; \ No newline at end of file +}; + From 1330b9439e14585227034aa6dfcf0c550339e74b Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 26 Nov 2014 12:22:55 +0000 Subject: [PATCH 153/308] assert migration order --- test/migration/manager-test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/migration/manager-test.js b/test/migration/manager-test.js index 80c737e..82058b2 100644 --- a/test/migration/manager-test.js +++ b/test/migration/manager-test.js @@ -66,6 +66,10 @@ describe("Migration Manager", function () { return this.manager.list() .then(function (migrations) { migrations.length.should.equal(2); + migrations.should.eql([ + 'm20140318_014253_my_test_migration', + 'm20140318_014300_my_second_test_migration' + ]); }); }) }); From fe37c5c7c6bad068a460eb7c3de22c3d210b583c Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 30 Nov 2014 02:44:33 +0000 Subject: [PATCH 154/308] don't cast RID to string --- lib/db/statement.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index 3d5fbd8..5f48d07 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -400,7 +400,7 @@ Statement.prototype.buildStatement = function () { if (state.upsert) { statement.push('UPSERT'); } - + if ((state.update || state.insert || state.delete) && state.return) { statement.push('RETURN ' + state.return); } @@ -472,6 +472,17 @@ Statement.prototype.buildStatement = function () { return item; } } + else if (item && typeof item === 'object') { + var keys = Object.keys(item), + length = keys.length, + parts = new Array(length), + key, i; + for (i = 0; i < length; i++) { + key = keys[i]; + parts.push(key, item[key]); + } + return parts.join(' '); + } else { return ''+item; } @@ -559,7 +570,7 @@ Statement.prototype._objectToCondition = function (obj, operator) { key = keys[i]; paramName = 'param' + paramify(key) + (this._state.paramIndex++); conditions.push(key + ' ' + operator + ' :' + paramName); - this.addParam(paramName, obj[key] instanceof RID ? ''+obj[key] : obj[key]); + this.addParam(paramName, obj[key]); } if (conditions.length === 0) { From 02f92689f1ef1502e1b2d7c3c97192063342beb7 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 30 Nov 2014 02:44:56 +0000 Subject: [PATCH 155/308] fix named param regexp --- lib/utils.js | 4 ++-- test/core/utils.js | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 test/core/utils.js diff --git a/lib/utils.js b/lib/utils.js index c4e18da..7c06567 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -139,10 +139,10 @@ exports.prepare = function (query, params) { if (!params) { return query; } - var pattern = /"(\\[\s\S]|[^"])*"|'(\\[\s\S]|[^'])*'|:([A-Za-z][A-Za-z0-9_-]*|\/\*[\s\S]*?\*\/)/g; + var pattern = /"(\\[\s\S]|[^"])*"|'(\\[\s\S]|[^'])*'|\s:([A-Za-z][A-Za-z0-9_-]*|\/\*[\s\S]*?\*\/)/g; return query.replace(pattern, function (all, double, single, param) { if (param) { - return exports.encode(params[param]); + return ' ' + exports.encode(params[param]); } else { return all; diff --git a/test/core/utils.js b/test/core/utils.js new file mode 100644 index 0000000..f30c7c7 --- /dev/null +++ b/test/core/utils.js @@ -0,0 +1,12 @@ +'use strict'; + +var utils = require('../../lib/utils'); + +describe('utils.prepare', function () { + it("should prepare SQL statements", function () { + utils.prepare("select from index:foo").should.equal("select from index:foo"); + }); + it("should prepare SQL statements with parameters", function () { + utils.prepare("select from index:foo where key = :key", {key: 123}).should.equal("select from index:foo where key = 123"); + }); +}); \ No newline at end of file From 95acdd891b171e18c501f0b7f68ae2a566af0cd2 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 1 Dec 2014 11:49:26 +0000 Subject: [PATCH 156/308] RID fixes --- lib/db/record.js | 3 +-- lib/transport/binary/protocol/operations/command.js | 3 ++- test/db/query-test.js | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/db/record.js b/lib/db/record.js index 136b589..5bebcf5 100644 --- a/lib/db/record.js +++ b/lib/db/record.js @@ -152,7 +152,6 @@ function recordIdResolver () { return obj.map(replaceRecordIds.bind(this, records)); } else if (obj instanceof RIDBag) { - /*jshint validthis:true */ obj._prefetchedRecords = records; return obj; } @@ -167,10 +166,10 @@ function recordIdResolver () { seen[obj['@rid']] = obj; } } + var keys = Object.keys(obj), total = keys.length, i, key, value; - for (i = 0; i < total; i++) { key = keys[i]; value = obj[key]; diff --git a/lib/transport/binary/protocol/operations/command.js b/lib/transport/binary/protocol/operations/command.js index a148481..343400a 100644 --- a/lib/transport/binary/protocol/operations/command.js +++ b/lib/transport/binary/protocol/operations/command.js @@ -3,7 +3,8 @@ var Operation = require('../operation'), constants = require('../constants'), serializer = require('../serializer'), - writer = require('../writer'); + writer = require('../writer'), + RID = require('../../../../recordid'); module.exports = Operation.extend({ id: 'REQUEST_COMMAND', diff --git a/test/db/query-test.js b/test/db/query-test.js index 5a2868f..43151fe 100644 --- a/test/db/query-test.js +++ b/test/db/query-test.js @@ -261,6 +261,13 @@ describe("Database API - Query", function () { user.name.should.equal('reader'); }); }); + it('should select a record by its RID', function () { + return this.db.select().from('OUser').where({'@rid': new LIB.RID('#5:0')}).one() + .then(function (user) { + expect(typeof user).to.equal('object'); + user.name.should.equal('admin'); + }); + }); it('should select a user with a fetch plan', function () { return this.db.select().from('OUser').where({name: 'reader'}).fetch({roles: 3}).one() .then(function (user) { From 2c2a29789373465379a7c6ab5e3bea2f21532be8 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 2 Dec 2014 00:26:07 +0000 Subject: [PATCH 157/308] start support for multiple protocol versions --- lib/transport/binary/connection.js | 34 ++++++++++++------- lib/transport/binary/index.js | 3 -- lib/transport/binary/operation-status.js | 8 +++++ .../{protocol => protocol19}/constants.js | 0 .../{protocol => protocol19}/deserializer.js | 0 .../binary/{protocol => protocol19}/index.js | 0 .../operation-queue.js | 0 .../{protocol => protocol19}/operation.js | 15 ++++---- .../operations/command.js | 0 .../operations/config-get.js | 0 .../operations/config-list.js | 0 .../operations/config-set.js | 0 .../operations/connect.js | 0 .../operations/datacluster-add.js | 0 .../operations/datacluster-count.js | 0 .../operations/datacluster-datarange.js | 0 .../operations/datacluster-drop.js | 0 .../operations/datasegment-add.js | 0 .../operations/datasegment-drop.js | 0 .../operations/db-close.js | 0 .../operations/db-countrecords.js | 0 .../operations/db-create.js | 0 .../operations/db-delete.js | 0 .../operations/db-exists.js | 0 .../operations/db-freeze.js | 0 .../operations/db-list.js | 0 .../operations/db-open.js | 0 .../operations/db-release.js | 0 .../operations/db-reload.js | 0 .../operations/db-size.js | 0 .../operations/index.js | 0 .../operations/record-clean-out.js | 0 .../operations/record-create.js | 0 .../operations/record-delete.js | 0 .../operations/record-load.js | 0 .../operations/record-metadata.js | 0 .../operations/record-update.js | 0 .../operations/tx-commit.js | 0 .../{protocol => protocol19}/serializer.js | 0 .../binary/{protocol => protocol19}/writer.js | 0 .../deserializer-test.js | 2 +- .../operation-test.js | 2 +- .../operations/config-operations-test.js | 0 .../operations/db-operations-test.js | 0 44 files changed, 39 insertions(+), 25 deletions(-) create mode 100644 lib/transport/binary/operation-status.js rename lib/transport/binary/{protocol => protocol19}/constants.js (100%) rename lib/transport/binary/{protocol => protocol19}/deserializer.js (100%) rename lib/transport/binary/{protocol => protocol19}/index.js (100%) rename lib/transport/binary/{protocol => protocol19}/operation-queue.js (100%) rename lib/transport/binary/{protocol => protocol19}/operation.js (98%) rename lib/transport/binary/{protocol => protocol19}/operations/command.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/config-get.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/config-list.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/config-set.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/connect.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/datacluster-add.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/datacluster-count.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/datacluster-datarange.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/datacluster-drop.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/datasegment-add.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/datasegment-drop.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/db-close.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/db-countrecords.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/db-create.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/db-delete.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/db-exists.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/db-freeze.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/db-list.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/db-open.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/db-release.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/db-reload.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/db-size.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/index.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/record-clean-out.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/record-create.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/record-delete.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/record-load.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/record-metadata.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/record-update.js (100%) rename lib/transport/binary/{protocol => protocol19}/operations/tx-commit.js (100%) rename lib/transport/binary/{protocol => protocol19}/serializer.js (100%) rename lib/transport/binary/{protocol => protocol19}/writer.js (100%) rename test/transport/binary/{protocol => protocol19}/deserializer-test.js (99%) rename test/transport/binary/{protocol => protocol19}/operation-test.js (98%) rename test/transport/binary/{protocol => protocol19}/operations/config-operations-test.js (100%) rename test/transport/binary/{protocol => protocol19}/operations/db-operations-test.js (100%) diff --git a/lib/transport/binary/connection.js b/lib/transport/binary/connection.js index 4e9ca09..fb4f7c1 100644 --- a/lib/transport/binary/connection.js +++ b/lib/transport/binary/connection.js @@ -4,8 +4,7 @@ var net = require('net'), util = require('util'), utils = require('../../utils'), errors = require('../../errors'), - Operation = require('./protocol/operation'), - operations = require('./protocol/operations'), + OperationStatus = require('./operation-status'), EventEmitter = require('events').EventEmitter, Promise = require('bluebird'); @@ -18,6 +17,7 @@ function Connection (config) { this.logger = config.logger || {debug: function () {}}; this.setMaxListeners(Infinity); + this.protocol = null; this.queue = []; this.writes = []; this.remaining = null; @@ -41,7 +41,7 @@ Connection.prototype.connect = function () { } this.socket = this.createSocket(); - return this.negotiateConnection(); + return (this.connecting = this.negotiateConnection()); }; /** @@ -71,9 +71,10 @@ Connection.prototype.send = function (op, params) { * @promise {Object} The result of the operation. */ Connection.prototype._sendOp = function (op, params) { + var self = this; return new Promise(function (resolve, reject) { if (typeof op === 'string') { - op = new operations[op](params || {}); + op = new self.protocol.operations[op](params || {}); } // define the write operations op.writer(); @@ -82,20 +83,20 @@ Connection.prototype._sendOp = function (op, params) { var buffer = op.buffer(); - if (this.socket) { - this.socket.write(buffer); + if (self.socket) { + self.socket.write(buffer); } else { - this.writes.push(buffer); + self.writes.push(buffer); } if (op.id === 'REQUEST_DB_CLOSE') { resolve({}); } else { - this.queue.push([op, {resolve: resolve, reject: reject}]); + self.queue.push([op, {resolve: resolve, reject: reject}]); } - }.bind(this)); + }); }; /** @@ -188,6 +189,13 @@ Connection.prototype.negotiateProtocol = function () { this.socket.removeAllListeners('error'); this.protocolVersion = data.readUInt16BE(0); this.logger.debug('server protocol: ' + this.protocolVersion); + console.log('server protocol: ' + this.protocolVersion); + if (this.protocolVersion > 25) { + this.protocol = require('./protocol27'); + } + else { + this.protocol = require('./protocol19'); + } resolve(this); this.connecting = false; this.bindToSocket(); @@ -340,20 +348,20 @@ Connection.prototype.process = function (buffer, offset) { status = parsed[0]; offset = parsed[1]; result = parsed[2]; - if (status === Operation.READING) { + if (status === OperationStatus.READING) { // operation is incomplete, buffer does not contain enough data this.queue.unshift(item); return offset; } - else if (status === Operation.PUSH_DATA) { + else if (status === OperationStatus.PUSH_DATA) { this.emit('update-config', result); this.queue.unshift(item); return offset; } - else if (status === Operation.COMPLETE) { + else if (status === OperationStatus.COMPLETE) { deferred.resolve(result); } - else if (status === Operation.ERROR) { + else if (status === OperationStatus.ERROR) { if (result.status.error) { // this is likely a recoverable error deferred.reject(result.status.error); diff --git a/lib/transport/binary/index.js b/lib/transport/binary/index.js index 7e85a25..e59da6a 100644 --- a/lib/transport/binary/index.js +++ b/lib/transport/binary/index.js @@ -27,9 +27,6 @@ util.inherits(BinaryTransport, EventEmitter); BinaryTransport.extend = utils.extend; BinaryTransport.prototype.augment = utils.augment; -BinaryTransport.protocol = require('./protocol'); - - module.exports = BinaryTransport; diff --git a/lib/transport/binary/operation-status.js b/lib/transport/binary/operation-status.js new file mode 100644 index 0000000..ec971bf --- /dev/null +++ b/lib/transport/binary/operation-status.js @@ -0,0 +1,8 @@ +'use strict'; + +exports.PENDING = 0; +exports.WRITTEN = 1; +exports.READING = 2; +exports.COMPLETE = 3; +exports.ERROR = 4; +exports.PUSH_DATA = 5; \ No newline at end of file diff --git a/lib/transport/binary/protocol/constants.js b/lib/transport/binary/protocol19/constants.js similarity index 100% rename from lib/transport/binary/protocol/constants.js rename to lib/transport/binary/protocol19/constants.js diff --git a/lib/transport/binary/protocol/deserializer.js b/lib/transport/binary/protocol19/deserializer.js similarity index 100% rename from lib/transport/binary/protocol/deserializer.js rename to lib/transport/binary/protocol19/deserializer.js diff --git a/lib/transport/binary/protocol/index.js b/lib/transport/binary/protocol19/index.js similarity index 100% rename from lib/transport/binary/protocol/index.js rename to lib/transport/binary/protocol19/index.js diff --git a/lib/transport/binary/protocol/operation-queue.js b/lib/transport/binary/protocol19/operation-queue.js similarity index 100% rename from lib/transport/binary/protocol/operation-queue.js rename to lib/transport/binary/protocol19/operation-queue.js diff --git a/lib/transport/binary/protocol/operation.js b/lib/transport/binary/protocol19/operation.js similarity index 98% rename from lib/transport/binary/protocol/operation.js rename to lib/transport/binary/protocol19/operation.js index f2e1130..b55eb1c 100644 --- a/lib/transport/binary/protocol/operation.js +++ b/lib/transport/binary/protocol19/operation.js @@ -30,13 +30,14 @@ module.exports = Operation; // operation statuses - -Operation.PENDING = 0; -Operation.WRITTEN = 1; -Operation.READING = 2; -Operation.COMPLETE = 3; -Operation.ERROR = 4; -Operation.PUSH_DATA = 5; +var statuses = require('../operation-status'); + +Operation.PENDING = statuses.PENDING; +Operation.WRITTEN = statuses.WRITTEN; +Operation.READING = statuses.READING; +Operation.COMPLETE = statuses.COMPLETE; +Operation.ERROR = statuses.ERROR; +Operation.PUSH_DATA = statuses.PUSH_DATA; // make it easy to inherit from the base class Operation.extend = utils.extend; diff --git a/lib/transport/binary/protocol/operations/command.js b/lib/transport/binary/protocol19/operations/command.js similarity index 100% rename from lib/transport/binary/protocol/operations/command.js rename to lib/transport/binary/protocol19/operations/command.js diff --git a/lib/transport/binary/protocol/operations/config-get.js b/lib/transport/binary/protocol19/operations/config-get.js similarity index 100% rename from lib/transport/binary/protocol/operations/config-get.js rename to lib/transport/binary/protocol19/operations/config-get.js diff --git a/lib/transport/binary/protocol/operations/config-list.js b/lib/transport/binary/protocol19/operations/config-list.js similarity index 100% rename from lib/transport/binary/protocol/operations/config-list.js rename to lib/transport/binary/protocol19/operations/config-list.js diff --git a/lib/transport/binary/protocol/operations/config-set.js b/lib/transport/binary/protocol19/operations/config-set.js similarity index 100% rename from lib/transport/binary/protocol/operations/config-set.js rename to lib/transport/binary/protocol19/operations/config-set.js diff --git a/lib/transport/binary/protocol/operations/connect.js b/lib/transport/binary/protocol19/operations/connect.js similarity index 100% rename from lib/transport/binary/protocol/operations/connect.js rename to lib/transport/binary/protocol19/operations/connect.js diff --git a/lib/transport/binary/protocol/operations/datacluster-add.js b/lib/transport/binary/protocol19/operations/datacluster-add.js similarity index 100% rename from lib/transport/binary/protocol/operations/datacluster-add.js rename to lib/transport/binary/protocol19/operations/datacluster-add.js diff --git a/lib/transport/binary/protocol/operations/datacluster-count.js b/lib/transport/binary/protocol19/operations/datacluster-count.js similarity index 100% rename from lib/transport/binary/protocol/operations/datacluster-count.js rename to lib/transport/binary/protocol19/operations/datacluster-count.js diff --git a/lib/transport/binary/protocol/operations/datacluster-datarange.js b/lib/transport/binary/protocol19/operations/datacluster-datarange.js similarity index 100% rename from lib/transport/binary/protocol/operations/datacluster-datarange.js rename to lib/transport/binary/protocol19/operations/datacluster-datarange.js diff --git a/lib/transport/binary/protocol/operations/datacluster-drop.js b/lib/transport/binary/protocol19/operations/datacluster-drop.js similarity index 100% rename from lib/transport/binary/protocol/operations/datacluster-drop.js rename to lib/transport/binary/protocol19/operations/datacluster-drop.js diff --git a/lib/transport/binary/protocol/operations/datasegment-add.js b/lib/transport/binary/protocol19/operations/datasegment-add.js similarity index 100% rename from lib/transport/binary/protocol/operations/datasegment-add.js rename to lib/transport/binary/protocol19/operations/datasegment-add.js diff --git a/lib/transport/binary/protocol/operations/datasegment-drop.js b/lib/transport/binary/protocol19/operations/datasegment-drop.js similarity index 100% rename from lib/transport/binary/protocol/operations/datasegment-drop.js rename to lib/transport/binary/protocol19/operations/datasegment-drop.js diff --git a/lib/transport/binary/protocol/operations/db-close.js b/lib/transport/binary/protocol19/operations/db-close.js similarity index 100% rename from lib/transport/binary/protocol/operations/db-close.js rename to lib/transport/binary/protocol19/operations/db-close.js diff --git a/lib/transport/binary/protocol/operations/db-countrecords.js b/lib/transport/binary/protocol19/operations/db-countrecords.js similarity index 100% rename from lib/transport/binary/protocol/operations/db-countrecords.js rename to lib/transport/binary/protocol19/operations/db-countrecords.js diff --git a/lib/transport/binary/protocol/operations/db-create.js b/lib/transport/binary/protocol19/operations/db-create.js similarity index 100% rename from lib/transport/binary/protocol/operations/db-create.js rename to lib/transport/binary/protocol19/operations/db-create.js diff --git a/lib/transport/binary/protocol/operations/db-delete.js b/lib/transport/binary/protocol19/operations/db-delete.js similarity index 100% rename from lib/transport/binary/protocol/operations/db-delete.js rename to lib/transport/binary/protocol19/operations/db-delete.js diff --git a/lib/transport/binary/protocol/operations/db-exists.js b/lib/transport/binary/protocol19/operations/db-exists.js similarity index 100% rename from lib/transport/binary/protocol/operations/db-exists.js rename to lib/transport/binary/protocol19/operations/db-exists.js diff --git a/lib/transport/binary/protocol/operations/db-freeze.js b/lib/transport/binary/protocol19/operations/db-freeze.js similarity index 100% rename from lib/transport/binary/protocol/operations/db-freeze.js rename to lib/transport/binary/protocol19/operations/db-freeze.js diff --git a/lib/transport/binary/protocol/operations/db-list.js b/lib/transport/binary/protocol19/operations/db-list.js similarity index 100% rename from lib/transport/binary/protocol/operations/db-list.js rename to lib/transport/binary/protocol19/operations/db-list.js diff --git a/lib/transport/binary/protocol/operations/db-open.js b/lib/transport/binary/protocol19/operations/db-open.js similarity index 100% rename from lib/transport/binary/protocol/operations/db-open.js rename to lib/transport/binary/protocol19/operations/db-open.js diff --git a/lib/transport/binary/protocol/operations/db-release.js b/lib/transport/binary/protocol19/operations/db-release.js similarity index 100% rename from lib/transport/binary/protocol/operations/db-release.js rename to lib/transport/binary/protocol19/operations/db-release.js diff --git a/lib/transport/binary/protocol/operations/db-reload.js b/lib/transport/binary/protocol19/operations/db-reload.js similarity index 100% rename from lib/transport/binary/protocol/operations/db-reload.js rename to lib/transport/binary/protocol19/operations/db-reload.js diff --git a/lib/transport/binary/protocol/operations/db-size.js b/lib/transport/binary/protocol19/operations/db-size.js similarity index 100% rename from lib/transport/binary/protocol/operations/db-size.js rename to lib/transport/binary/protocol19/operations/db-size.js diff --git a/lib/transport/binary/protocol/operations/index.js b/lib/transport/binary/protocol19/operations/index.js similarity index 100% rename from lib/transport/binary/protocol/operations/index.js rename to lib/transport/binary/protocol19/operations/index.js diff --git a/lib/transport/binary/protocol/operations/record-clean-out.js b/lib/transport/binary/protocol19/operations/record-clean-out.js similarity index 100% rename from lib/transport/binary/protocol/operations/record-clean-out.js rename to lib/transport/binary/protocol19/operations/record-clean-out.js diff --git a/lib/transport/binary/protocol/operations/record-create.js b/lib/transport/binary/protocol19/operations/record-create.js similarity index 100% rename from lib/transport/binary/protocol/operations/record-create.js rename to lib/transport/binary/protocol19/operations/record-create.js diff --git a/lib/transport/binary/protocol/operations/record-delete.js b/lib/transport/binary/protocol19/operations/record-delete.js similarity index 100% rename from lib/transport/binary/protocol/operations/record-delete.js rename to lib/transport/binary/protocol19/operations/record-delete.js diff --git a/lib/transport/binary/protocol/operations/record-load.js b/lib/transport/binary/protocol19/operations/record-load.js similarity index 100% rename from lib/transport/binary/protocol/operations/record-load.js rename to lib/transport/binary/protocol19/operations/record-load.js diff --git a/lib/transport/binary/protocol/operations/record-metadata.js b/lib/transport/binary/protocol19/operations/record-metadata.js similarity index 100% rename from lib/transport/binary/protocol/operations/record-metadata.js rename to lib/transport/binary/protocol19/operations/record-metadata.js diff --git a/lib/transport/binary/protocol/operations/record-update.js b/lib/transport/binary/protocol19/operations/record-update.js similarity index 100% rename from lib/transport/binary/protocol/operations/record-update.js rename to lib/transport/binary/protocol19/operations/record-update.js diff --git a/lib/transport/binary/protocol/operations/tx-commit.js b/lib/transport/binary/protocol19/operations/tx-commit.js similarity index 100% rename from lib/transport/binary/protocol/operations/tx-commit.js rename to lib/transport/binary/protocol19/operations/tx-commit.js diff --git a/lib/transport/binary/protocol/serializer.js b/lib/transport/binary/protocol19/serializer.js similarity index 100% rename from lib/transport/binary/protocol/serializer.js rename to lib/transport/binary/protocol19/serializer.js diff --git a/lib/transport/binary/protocol/writer.js b/lib/transport/binary/protocol19/writer.js similarity index 100% rename from lib/transport/binary/protocol/writer.js rename to lib/transport/binary/protocol19/writer.js diff --git a/test/transport/binary/protocol/deserializer-test.js b/test/transport/binary/protocol19/deserializer-test.js similarity index 99% rename from test/transport/binary/protocol/deserializer-test.js rename to test/transport/binary/protocol19/deserializer-test.js index 1711b76..359f162 100644 --- a/test/transport/binary/protocol/deserializer-test.js +++ b/test/transport/binary/protocol19/deserializer-test.js @@ -1,4 +1,4 @@ -var deserializer = require(LIB_ROOT + '/transport/binary/protocol/deserializer'); +var deserializer = require(LIB_ROOT + '/transport/binary/protocol19/deserializer'); describe("Deserializer", function () { it('should go fast!', function () { diff --git a/test/transport/binary/protocol/operation-test.js b/test/transport/binary/protocol19/operation-test.js similarity index 98% rename from test/transport/binary/protocol/operation-test.js rename to test/transport/binary/protocol19/operation-test.js index 69f0279..0449e86 100644 --- a/test/transport/binary/protocol/operation-test.js +++ b/test/transport/binary/protocol19/operation-test.js @@ -1,4 +1,4 @@ -var Operation = LIB.transport.Binary.protocol.Operation; +var Operation = require(LIB_ROOT + '/transport/binary/protocol19/operation'); describe('Operation', function () { diff --git a/test/transport/binary/protocol/operations/config-operations-test.js b/test/transport/binary/protocol19/operations/config-operations-test.js similarity index 100% rename from test/transport/binary/protocol/operations/config-operations-test.js rename to test/transport/binary/protocol19/operations/config-operations-test.js diff --git a/test/transport/binary/protocol/operations/db-operations-test.js b/test/transport/binary/protocol19/operations/db-operations-test.js similarity index 100% rename from test/transport/binary/protocol/operations/db-operations-test.js rename to test/transport/binary/protocol19/operations/db-operations-test.js From 5f8396ba76c62a132897f87e4cf69b28b29b292e Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 2 Dec 2014 09:33:19 +0000 Subject: [PATCH 158/308] add support for binary protocol v26 --- lib/transport/binary/connection.js | 5 +- lib/transport/binary/protocol26/constants.js | 20 + .../binary/protocol26/deserializer.js | 536 +++++++++++ lib/transport/binary/protocol26/index.js | 7 + .../binary/protocol26/operation-queue.js | 184 ++++ lib/transport/binary/protocol26/operation.js | 875 ++++++++++++++++++ .../binary/protocol26/operations/command.js | 180 ++++ .../protocol26/operations/config-get.js | 20 + .../protocol26/operations/config-list.js | 31 + .../protocol26/operations/config-set.js | 23 + .../binary/protocol26/operations/connect.js | 27 + .../protocol26/operations/datacluster-add.js | 21 + .../operations/datacluster-count.js | 35 + .../operations/datacluster-datarange.js | 23 + .../protocol26/operations/datacluster-drop.js | 22 + .../binary/protocol26/operations/db-close.js | 15 + .../protocol26/operations/db-countrecords.js | 19 + .../binary/protocol26/operations/db-create.js | 20 + .../binary/protocol26/operations/db-delete.js | 19 + .../binary/protocol26/operations/db-exists.js | 23 + .../binary/protocol26/operations/db-freeze.js | 19 + .../binary/protocol26/operations/db-list.js | 21 + .../binary/protocol26/operations/db-open.js | 44 + .../protocol26/operations/db-release.js | 19 + .../binary/protocol26/operations/db-reload.js | 32 + .../binary/protocol26/operations/db-size.js | 19 + .../binary/protocol26/operations/index.js | 34 + .../protocol26/operations/record-clean-out.js | 35 + .../protocol26/operations/record-create.js | 51 + .../protocol26/operations/record-delete.js | 46 + .../protocol26/operations/record-load.js | 120 +++ .../protocol26/operations/record-metadata.js | 35 + .../protocol26/operations/record-update.js | 63 ++ .../binary/protocol26/operations/tx-commit.js | 113 +++ lib/transport/binary/protocol26/serializer.js | 137 +++ lib/transport/binary/protocol26/writer.js | 123 +++ 36 files changed, 3013 insertions(+), 3 deletions(-) create mode 100644 lib/transport/binary/protocol26/constants.js create mode 100644 lib/transport/binary/protocol26/deserializer.js create mode 100644 lib/transport/binary/protocol26/index.js create mode 100644 lib/transport/binary/protocol26/operation-queue.js create mode 100644 lib/transport/binary/protocol26/operation.js create mode 100644 lib/transport/binary/protocol26/operations/command.js create mode 100644 lib/transport/binary/protocol26/operations/config-get.js create mode 100644 lib/transport/binary/protocol26/operations/config-list.js create mode 100644 lib/transport/binary/protocol26/operations/config-set.js create mode 100644 lib/transport/binary/protocol26/operations/connect.js create mode 100644 lib/transport/binary/protocol26/operations/datacluster-add.js create mode 100644 lib/transport/binary/protocol26/operations/datacluster-count.js create mode 100644 lib/transport/binary/protocol26/operations/datacluster-datarange.js create mode 100644 lib/transport/binary/protocol26/operations/datacluster-drop.js create mode 100644 lib/transport/binary/protocol26/operations/db-close.js create mode 100644 lib/transport/binary/protocol26/operations/db-countrecords.js create mode 100644 lib/transport/binary/protocol26/operations/db-create.js create mode 100644 lib/transport/binary/protocol26/operations/db-delete.js create mode 100644 lib/transport/binary/protocol26/operations/db-exists.js create mode 100644 lib/transport/binary/protocol26/operations/db-freeze.js create mode 100644 lib/transport/binary/protocol26/operations/db-list.js create mode 100644 lib/transport/binary/protocol26/operations/db-open.js create mode 100644 lib/transport/binary/protocol26/operations/db-release.js create mode 100644 lib/transport/binary/protocol26/operations/db-reload.js create mode 100644 lib/transport/binary/protocol26/operations/db-size.js create mode 100644 lib/transport/binary/protocol26/operations/index.js create mode 100644 lib/transport/binary/protocol26/operations/record-clean-out.js create mode 100644 lib/transport/binary/protocol26/operations/record-create.js create mode 100644 lib/transport/binary/protocol26/operations/record-delete.js create mode 100644 lib/transport/binary/protocol26/operations/record-load.js create mode 100644 lib/transport/binary/protocol26/operations/record-metadata.js create mode 100644 lib/transport/binary/protocol26/operations/record-update.js create mode 100644 lib/transport/binary/protocol26/operations/tx-commit.js create mode 100644 lib/transport/binary/protocol26/serializer.js create mode 100644 lib/transport/binary/protocol26/writer.js diff --git a/lib/transport/binary/connection.js b/lib/transport/binary/connection.js index fb4f7c1..23a9d61 100644 --- a/lib/transport/binary/connection.js +++ b/lib/transport/binary/connection.js @@ -189,9 +189,8 @@ Connection.prototype.negotiateProtocol = function () { this.socket.removeAllListeners('error'); this.protocolVersion = data.readUInt16BE(0); this.logger.debug('server protocol: ' + this.protocolVersion); - console.log('server protocol: ' + this.protocolVersion); - if (this.protocolVersion > 25) { - this.protocol = require('./protocol27'); + if (this.protocolVersion >= 26) { + this.protocol = require('./protocol26'); } else { this.protocol = require('./protocol19'); diff --git a/lib/transport/binary/protocol26/constants.js b/lib/transport/binary/protocol26/constants.js new file mode 100644 index 0000000..00440c5 --- /dev/null +++ b/lib/transport/binary/protocol26/constants.js @@ -0,0 +1,20 @@ +"use strict"; + +exports.PROTOCOL_VERSION = 26; + +exports.BYTES_LONG = 8; +exports.BYTES_INT = 4; +exports.BYTES_SHORT = 2; +exports.BYTES_BYTE = 1; + +exports.RECORD_TYPES = { + 'd': 100, + 'b': 98, + 'f': 102, + + // duplicated as upper case for fast lookup + + 'D': 100, + 'B': 98, + 'F': 102, +}; \ No newline at end of file diff --git a/lib/transport/binary/protocol26/deserializer.js b/lib/transport/binary/protocol26/deserializer.js new file mode 100644 index 0000000..68b3bc0 --- /dev/null +++ b/lib/transport/binary/protocol26/deserializer.js @@ -0,0 +1,536 @@ +"use strict"; + +var RID = require('../../../recordid'), + Bag = require('../../../bag'); + +/** + * Deserialize the given record and return an object containing the values. + * + * @param {String} input The serialized record. + * @return {Object} The deserialized record. + */ +function deserialize (input) { + var record = {'@type': 'd'}, + chunk, key, value; + if (!input) { + return null; + } + chunk = eatFirstKey(input); + if (chunk[2]) { + // this is actually a class name + record['@class'] = chunk[0]; + input = chunk[1]; + chunk = eatKey(input); + key = chunk[0]; + input = chunk[1]; + } + else { + key = chunk[0]; + input = chunk[1]; + } + + // read the first value. + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + record[key] = value; + + while (input.length) { + if (input.charAt(0) === ',') { + input = input.slice(1); + } + else { + break; + } + chunk = eatKey(input); + key = chunk[0]; + input = chunk[1]; + if (input.length) { + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + record[key] = value; + } + else { + record[key] = null; + } + } + + return record; +} + +/** + * Consume the first field key, which could be a class name. + * + * @param {String} input The input to parse. + * @return {[String, String]} The collected key, and any remaining input. + */ +function eatFirstKey (input) { + var length = input.length, + collected = '', + isClassName = false, + result, c, i; + + if (input.charAt(0) === '"') { + result = eatString(input.slice(1)); + return [result[0], result[1].slice(1)]; + } + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === '@') { + isClassName = true; + break; + } + else if (c === ':') { + break; + } + else { + collected += c; + } + } + + return [collected, input.slice(i + 1), isClassName]; +} + + +/** + * Consume a field key, which may or may not be quoted. + * + * @param {String} input The input to parse. + * @return {[String, String]} The collected key, and any remaining input. + */ +function eatKey (input) { + var length = input.length, + collected = '', + result, c, i; + + if (input.charAt(0) === '"') { + result = eatString(input.slice(1)); + return [result[0], result[1].slice(1)]; + } + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === ':') { + break; + } + else { + collected += c; + } + } + + return [collected, input.slice(i + 1)]; +} + + + +/** + * Consume a field value. + * + * @param {String} input The input to parse. + * @return {[Mixed, String]} The collected value, and any remaining input. + */ +function eatValue (input) { + var c, n; + c = input.charAt(0); + while (c === ' ' && input.length) { + input = input.slice(1); + c = input.charAt(0); + } + + if (!input.length || c === ',') { + // this is a null field. + return [null, input]; + } + else if (c === '"') { + return eatString(input.slice(1)); + } + else if (c === '#') { + return eatRID(input.slice(1)); + } + else if (c === '[') { + return eatArray(input.slice(1)); + } + else if (c === '<') { + return eatSet(input.slice(1)); + } + else if (c === '{') { + return eatMap(input.slice(1)); + } + else if (c === '(') { + return eatRecord(input.slice(1)); + } + else if (c === '%') { + return eatBag(input.slice(1)); + } + else if (c === '_') { + return eatBinary(input.slice(1)); + } + else if (c === '-' || c === '0' || +c) { + return eatNumber(input); + } + else if (c === 'n' && input.slice(0, 4) === 'null') { + return [null, input.slice(4)]; + } + else if (c === 't' && input.slice(0, 4) === 'true') { + return [true, input.slice(4)]; + } + else if (c === 'f' && input.slice(0, 5) === 'false') { + return [false, input.slice(5)]; + } + else { + return [null, input]; + } +} + +/** + * Consume a string + * + * @param {String} input The input to parse. + * @return {[String, String]} The collected string, and any remaining input. + */ +function eatString (input) { + var length = input.length, + collected = '', + c, i; + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === '\\') { + // escape, skip to the next character + i++; + collected += input.charAt(i); + continue; + } + else if (c === '"') { + break; + } + else { + collected += c; + } + } + + return [collected, input.slice(i + 1)]; +} + +/** + * Consume a number. + * + * If the number has a suffix, consume it also and instantiate the right type, e.g. for dates + * + * @param {String} input The input to parse. + * @return {[Mixed, String]} The collected number, and any remaining input. + */ +function eatNumber (input) { + var length = input.length, + collected = '', + pattern = /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/, + num, c, i; + + num = input.match(pattern); + if (num) { + collected = num[0]; + i = collected.length; + } + + collected = +collected; + input = input.slice(i); + + c = input.charAt(0); + + if (c === 'a' || c === 't') { + collected = new Date(collected); + input = input.slice(1); + } + else if (c === 'b' || c === 's' || c === 'l' || c === 'f' || c == 'd' || c === 'c') { + input = input.slice(1); + } + + return [collected, input]; +} + +/** + * Consume a Record ID. + * + * @param {String} input The input to parse. + * @return {[RID, String]} The collected record id, and any remaining input. + */ +function eatRID (input) { + var length = input.length, + collected = '', + cluster, c, i; + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (cluster === undefined && c === ':') { + cluster = +collected; + collected = ''; + } + else if (c === '0' || +c) { + collected += c; + } + else { + break; + } + } + + return [new RID({cluster: cluster, position: +collected}), input.slice(i)]; +} + + +/** + * Consume an array. + * + * @param {String} input The input to parse. + * @return {[Array, String]} The collected array, and any remaining input. + */ +function eatArray (input) { + var length = input.length, + array = [], + chunk, c; + + while (input.length) { + c = input.charAt(0); + if (c === ',') { + input = input.slice(1); + } + else if (c === ']') { + input = input.slice(1); + break; + } + chunk = eatValue(input); + array.push(chunk[0]); + input = chunk[1]; + } + return [array, input]; +} + + +/** + * Consume a set. + * + * @param {String} input The input to parse. + * @return {[Array, String]} The collected set, and any remaining input. + */ +function eatSet (input) { + var length = input.length, + set = [], + chunk, c; + + while (input.length) { + c = input.charAt(0); + if (c === ',') { + input = input.slice(1); + } + else if (c === '>') { + input = input.slice(1); + break; + } + chunk = eatValue(input); + set.push(chunk[0]); + input = chunk[1]; + } + + return [set, input]; +} + +/** + * Consume a map (object). + * + * @param {String} input The input to parse. + * @return {[Object, String]} The collected map, and any remaining input. + */ +function eatMap (input) { + var length = input.length, + map = {}, + key, value, chunk, c; + + while (input.length) { + c = input.charAt(0); + if (c === ' ') { + input = input.slice(1); + continue; + } + if (c === ',') { + input = input.slice(1); + } + else if (c === '}') { + input = input.slice(1); + break; + } + + chunk = eatKey(input); + key = chunk[0]; + input = chunk[1]; + if (input.length) { + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + map[key] = value; + } + else { + map[key] = null; + } + } + + return [map, input]; +} + +/** + * Consume an embedded record. + * + * @param {String} input The input to parse. + * @return {[Object, String]} The collected record, and any remaining input. + */ +function eatRecord (input) { + var record = {'@type': 'd'}, + chunk, c, key, value; + + while (input.length) { + c = input.charAt(0); + if (c === ' ') { + input = input.slice(1); + continue; + } + else if (c === ')') { + // empty record. + input = input.slice(1); + return [record, input]; + } + else { + break; + } + } + + chunk = eatFirstKey(input); + + if (chunk[2]) { + // this is actually a class name + record['@class'] = chunk[0]; + input = chunk[1]; + chunk = eatKey(input); + while (input.length) { + c = input.charAt(0); + if (c === ' ') { + input = input.slice(1); + continue; + } + else if (c === ')') { + // empty record. + input = input.slice(1); + return [record, input]; + } + else { + break; + } + } + key = chunk[0]; + input = chunk[1]; + } + else { + key = chunk[0]; + input = chunk[1]; + } + + // read the first value. + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + record[key] = value; + + while (input.length) { + c = input.charAt(0); + if (c === ' ') { + input = input.slice(1); + continue; + } + if (c === ',') { + input = input.slice(1); + } + else if (c === ')') { + input = input.slice(1); + break; + } + chunk = eatKey(input); + key = chunk[0]; + input = chunk[1]; + if (input.length) { + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + record[key] = value; + } + else { + record[key] = null; + } + } + + return [record, input]; +} + +/** + * Consume a RID Bag. + * + * @param {String} input The input to parse. + * @return {[Object, String]} The collected bag, and any remaining input. + */ +function eatBag (input) { + var length = input.length, + collected = '', + i, bag, chunk, c; + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === ';') { + break; + } + else { + collected += c; + } + } + input = input.slice(i + 1); + + return [new Bag(collected), input]; +} + + +/** + * Consume a binary buffer. + * + * @param {String} input The input to parse. + * @return {[Object, String]} The collected bag, and any remaining input. + */ +function eatBinary (input) { + var length = input.length, + collected = '', + i, bag, chunk, c; + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === '_' || c === ',' || c === ')' || c === '>' || c === '}' || c === ']') { + break; + } + else { + collected += c; + } + } + input = input.slice(i + 1); + + return [new Buffer(collected, 'base64'), input]; +} + + + +exports.deserialize = deserialize; +exports.eatKey = eatKey; +exports.eatValue = eatValue; +exports.eatString = eatString; +exports.eatNumber = eatNumber; +exports.eatRID = eatRID; +exports.eatArray = eatArray; +exports.eatSet = eatSet; +exports.eatMap = eatMap; +exports.eatRecord = eatRecord; +exports.eatBag = eatBag; +exports.eatBinary = eatBinary; diff --git a/lib/transport/binary/protocol26/index.js b/lib/transport/binary/protocol26/index.js new file mode 100644 index 0000000..f022070 --- /dev/null +++ b/lib/transport/binary/protocol26/index.js @@ -0,0 +1,7 @@ +"use strict"; +exports.Operation = require('./operation'); +exports.OperationQueue = require('./operation-queue'); +exports.constants = require('./constants'); +exports.serializer = require('./serializer'); +exports.deserializer = require('./deserializer'); +exports.operations = require('./operations'); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operation-queue.js b/lib/transport/binary/protocol26/operation-queue.js new file mode 100644 index 0000000..d29b353 --- /dev/null +++ b/lib/transport/binary/protocol26/operation-queue.js @@ -0,0 +1,184 @@ +"use strict"; + +var Promise = require('bluebird'), + Operation = require('./operation'), + operations = require('./operations'), + Emitter = require('events').EventEmitter, + errors = require('../../../errors'), + util = require('util'); + +function OperationQueue (socket) { + this.socket = socket || null; + this.items = []; + this.writes = []; + this.remaining = null; + if (socket) { + this.bindToSocket(); + } + Emitter.call(this); +} + +util.inherits(OperationQueue, Emitter); + +module.exports = OperationQueue; + +/** + * Add an operation to the queue. + * + * @param {String|Operation} op The operation name or instance. + * @param {Object} params The parameters for the operation, if op is a string. + * @promise {Object} The result of the operation. + */ +OperationQueue.prototype.add = function (op, params) { + if (typeof op === 'string') { + op = new operations[op](params || {}); + } + var deferred = Promise.defer(), + buffer; + // define the write operations + op.writer(); + // define the read operations + op.reader(); + buffer = op.buffer(); + if (this.socket) { + this.socket.write(buffer); + } + else { + this.writes.push(buffer); + } + if (op.id === 'REQUEST_DB_CLOSE') { + deferred.resolve({}); + } + else { + this.items.push([op, deferred]); + } + return deferred.promise; +}; + +/** + * Cancel all the operations in the queue. + * + * @param {Error} err The error object, if any. + * @return {OperationQueue} The now empty queue. + */ +OperationQueue.prototype.cancel = function (err) { + var item, op, deferred; + while ((item = this.items.shift())) { + op = item[0]; + deferred = item[1]; + deferred.reject(err); + } + return this; +}; + +/** + * Bind to events on the socket. + */ +OperationQueue.prototype.bindToSocket = function (socket) { + var total, i; + if (socket) { + this.socket = socket; + } + this.socket.on('data', this.handleChunk.bind(this)); + if ((total = this.writes.length)) { + if (this.socket.connected) { + for (i = 0; i < total; i++) { + this.socket.write(this.writes[i]); + } + this.writes = []; + } + else { + this.socket.once('connect', function () { + var total = this.writes.length, + i; + for (i = 0; i < total; i++) { + this.socket.write(this.writes[i]); + } + this.writes = []; + }.bind(this)); + } + } +}; + +/** + * Unbind from socket events. + */ +OperationQueue.prototype.unbindFromSocket = function () { + this.socket.removeAllListeners('data'); + delete this.socket; +}; + +/** + * Handle a chunk of data from the socket and attempt to process it. + * + * @param {Buffer} data The data received from the server. + */ +OperationQueue.prototype.handleChunk = function (data) { + var buffer, result, offset; + if (this.remaining) { + buffer = new Buffer(this.remaining.length + data.length); + this.remaining.copy(buffer); + data.copy(buffer, this.remaining.length); + } + else { + buffer = data; + } + offset = this.process(buffer); + if (buffer.length - offset === 0) { + this.remaining = null; + } + else { + this.remaining = buffer.slice(offset); + } +}; + +/** + * Process the operations in the queue against the given buffer. + * + * + * @param {Buffer} buffer The buffer to process. + * @param {Integer} offset The offset to start processing from, defaults to 0. + * @return {Integer} The offset that was successfully read up to. + */ +OperationQueue.prototype.process = function (buffer, offset) { + var code, parsed, result, status, item, op, deferred, err; + offset = offset || 0; + while ((item = this.items.shift())) { + op = item[0]; + deferred = item[1]; + parsed = op.consume(buffer, offset); + status = parsed[0]; + offset = parsed[1]; + result = parsed[2]; + if (status === Operation.READING) { + // operation is incomplete, buffer does not contain enough data + this.items.unshift(item); + return offset; + } + else if (status === Operation.PUSH_DATA) { + this.emit('update-config', result); + this.items.unshift(item); + return offset; + } + else if (status === Operation.COMPLETE) { + deferred.resolve(result); + } + else if (status === Operation.ERROR) { + if (result.status.error) { + // this is likely a recoverable error + deferred.reject(result.status.error); + } + else { + // cannot recover, reject everything and let the application decide what to do + err = new errors.Protocol('Unknown Error on operation id ' + op.id, result); + deferred.reject(err); + this.cancel(err); + this.emit('error', err); + } + } + else { + deferred.reject(new errors.Protocol('Unsupported operation status: ' + status)); + } + } + return offset; +}; \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operation.js b/lib/transport/binary/protocol26/operation.js new file mode 100644 index 0000000..b55eb1c --- /dev/null +++ b/lib/transport/binary/protocol26/operation.js @@ -0,0 +1,875 @@ +"use strict"; + +var constants = require('./constants'), + utils = require('../../../utils'), + Long = require('../../../long').Long, + errors = require('../../../errors'), + deserializer = require('./deserializer'); + + +/** + * # Operations + * + * The base class for operations, provides a simple DSL for defining + * the steps required to send a command to the server, and then read + * the response. + * + * Each operation should implement the `writer()` and `reader()` methods. + * + * @param {Object} data The data for the operation. + */ +function Operation (data) { + this.status = Operation.PENDING; + this.writeOps = []; + this.readOps = []; + this.stack = [{}]; + this.data = data || {}; +} + +module.exports = Operation; + +// operation statuses + +var statuses = require('../operation-status'); + +Operation.PENDING = statuses.PENDING; +Operation.WRITTEN = statuses.WRITTEN; +Operation.READING = statuses.READING; +Operation.COMPLETE = statuses.COMPLETE; +Operation.ERROR = statuses.ERROR; +Operation.PUSH_DATA = statuses.PUSH_DATA; + +// make it easy to inherit from the base class +Operation.extend = utils.extend; + +/** + * Declares the commands to send to the server. + * Child classes should implement this function. + */ +Operation.prototype.writer = function () { + +}; + +/** + * Declares the steps required to recieve data for the operation. + * Child classes should implement this function. + */ +Operation.prototype.reader = function () { + +}; + +/** + * Prepare the buffer for the operation. + * + * @return {Buffer} The buffer containing the commands to send to the server. + */ +Operation.prototype.buffer = function () { + + if (!this.writeOps.length) { + this.writer(); + } + + var total = this.writeOps.length, + size = 0, + commands = [], + item, i, fn, offset, data, buffer; + + for (i = 0; i < total; i++) { + item = this.writeOps[i]; + offset = size; + commands.push([item[1], offset]); + size += item[0]; + } + + buffer = new Buffer(size); + + for (i = 0; i < total; i++) { + item = commands[i]; + fn = item[0]; + offset = item[1]; + fn(buffer, offset); + } + + return buffer; +}; + +/** + * Write a byte. + * + * @param {Mixed} data The data. + * @return {Operation} The operation instance. + */ +Operation.prototype.writeByte = function (data) { + this.writeOps.push([1, function (buffer, offset) { + buffer[offset] = data; + }]); + return this; +}; + +/** + * Write a boolean. + * + * @param {Mixed} data The data. + * @return {Operation} The operation instance. + */ +Operation.prototype.writeBoolean = function (data) { + this.writeOps.push([1, function (buffer, offset) { + buffer[offset] = data ? 1 : 0; + }]); + return this; +}; + + +/** + * Write a single character. + * + * @param {Mixed} data The data. + * @return {Operation} The operation instance. + */ +Operation.prototype.writeChar = function (data) { + this.writeOps.push([1, function (buffer, offset) { + buffer[offset] = (''+data).charCodeAt(0); + }]); + return this; +}; + +/** + * Parse data to 4 bytes which represents integer value. + * + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +Operation.prototype.writeInt = function (data) { + this.writeOps.push([constants.BYTES_INT, function (buffer, offset) { + buffer.fill(0, offset, offset + constants.BYTES_INT); + buffer.writeInt32BE(data, offset); + }]); + return this; +}; + +/** + * Parse data to 8 bytes which represents a long value. + * + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +Operation.prototype.writeLong = function (data) { + this.writeOps.push([constants.BYTES_LONG, function (buffer, offset) { + data = Long.fromNumber(data); + buffer.fill(0, offset, offset + constants.BYTES_LONG); + buffer.writeInt32BE(data.high_, offset); + buffer.writeInt32BE(data.low_, offset + constants.BYTES_INT); + + }]); + return this; +}; + +/** + * Parse data to 2 bytes which represents short value. + * + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +Operation.prototype.writeShort = function (data) { + this.writeOps.push([constants.BYTES_SHORT, function (buffer, offset) { + buffer.fill(0, offset, offset + constants.BYTES_SHORT); + buffer.writeInt16BE(data, offset); + }]); + return this; +}; + +/** + * Write bytes to a buffer + * @param {Buffer} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +Operation.prototype.writeBytes = function (data) { + this.writeOps.push([constants.BYTES_INT + data.length, function (buffer, offset) { + buffer.writeInt32BE(data.length, offset); + data.copy(buffer, offset + constants.BYTES_INT); + }]); + return this; +}; + +/** + * Parse string data to buffer with UTF-8 encoding. + * + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +Operation.prototype.writeString = function (data) { + if (data == null) { + return this.writeInt(-1); + } + var encoded = encodeString(data), + length = encoded.length; + this.writeOps.push([constants.BYTES_INT + length, function (buffer, offset) { + buffer.writeInt32BE(length, offset); + encoded.copy(buffer, offset + constants.BYTES_INT); + }]); + return this; +}; + +function encodeString (data) { + var length = data.length, + output = new Buffer(length * 3), // worst case, all chars could require 3-byte encodings. + j = 0, // index output + i, c; + + for (i = 0; i < length; i++) { + c = data.charCodeAt(i); + if (c < 0x80) { + // 7-bits done in one byte. + output[j++] = c; + } + else if (c < 0x800) { + // 8-11 bits done in 2 bytes + output[j++] = (0xC0 | c >> 6); + output[j++] = (0x80 | c & 0x3F); + } + else { + // 12-16 bits done in 3 bytes + output[j++] = (0xE0 | c >> 12); + output[j++] = (0x80 | c >> 6 & 0x3F); + output[j++] = (0x80 | c & 0x3F); + } + } + return output.slice(0, j); +} + +// # Read Operations + + +/** + * Read a status from the server response. + * If the status contains an error, that error + * will be read instead of any subsequently queued commands. + * + * @param {String} fieldName The name of the data field to populate. + * @param {Function} reader The function that should be invoked after this value is read. if any. + * @return {Operation} The operation instance. + */ +Operation.prototype.readStatus = function (fieldName, reader) { + var value = {}; + fieldName = fieldName || 'status'; + + this.readOps.push(function (data) { + data[fieldName] = value; + this.stack.push(data[fieldName]); + }); + this.readByte('code'); + this.readInt('sessionId', function (data) { + if (data.code === 1) { + this.readError('error', function () { + if (reader) { + reader.call(this, value, fieldName); + } + this.readOps.push(function () { + this.stack.pop(); + }); + }); + } + else { + if (reader) { + reader.call(this, value, fieldName); + } + this.stack.pop(); + } + }); + return this; +}; + +/** + * Read an error from the server response. + * Any subsequently queued commands will not run. + * + * @param {String} fieldName The name of the data field to populate. + * @param {Function} reader The function that should be invoked after this value is read. if any. + * @return {Operation} The operation instance. + */ +Operation.prototype.readError = function (fieldName, reader) { + this.readOps = [['Error', [fieldName, reader]]]; + return this; +}; + +/** + * Read an object from the server response. + * This is the same as `readString` but deserializes the returned string + * into an object. + * + * @param {String} fieldName The name of the data field to populate. + * @param {Function} reader The function that should be invoked after this value is read. if any. + * @return {Operation} The operation instance. + */ +Operation.prototype.readObject = function (fieldName, reader) { + this.readOps.push(['String', [fieldName, function (data, fieldName) { + data[fieldName] = deserializer.deserialize(data[fieldName]); + if (reader) { + reader.call(this, data, fieldName); + } + }]]); + return this; +}; + + +// Add the `readByte`, `readInt` etc methods. +// these are just shortcuts + +[ + 'Byte', + 'Bytes', + 'Int', + 'Short', + 'Long', + 'String', + 'Array', + 'Record', + 'Char', + 'Boolean', + 'Collection' +] +.forEach(function (name) { + this['read' + name] = function (fieldName, reader) { + this.readOps.push([name, arguments]); + return this; + }; +}, Operation.prototype); + + +/** + * Consume the buffer starting from the given offset. + * Returns an array containing the operation status, the + * new offset and any collected result. + * + * If the buffer doesn't contain enough data for the operation + * to complete, it will process as much as possible and return + * a partial result with a status code of `Operation.READING` meaning + * that the operation is still in the reading state. + * + * If the operation completes successfully, the status code will be + * `Operation.COMPLETE`. + * + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset + * @return {Array} The array containing the status, new offset and result. + */ +Operation.prototype.consume = function (buffer, offset) { + var obj, code, context, item, type, args; + offset = offset || 0; + if (this.status === Operation.PENDING) { + // this is the first time consume has been called for this + // operation. We need to determine whether the response + // we're reading is really for us or whether it's a + // PUSH_DATA command. + if (buffer.length < offset + 1) { + // not enough bytes in the buffer to check. + return [Operation.READING, offset, {}]; + } + + code = buffer.readUInt8(offset); + if (code === 3) { + offset += 5; // ignore the next integer + obj = {}; + offset += this.parsePushedData(buffer, offset, obj, 'data'); + return [Operation.PUSH_DATA, offset, obj.data]; + } + + this.status = Operation.READING; + } + if (this.readOps.length === 0) { + return [Operation.COMPLETE, offset, this.stack[0]]; + } + while ((item = this.readOps.shift())) { + context = this.stack[this.stack.length - 1]; + if (typeof item === 'function') { + // this is a nop, just execute it. + item.call(this, context, buffer, offset); + continue; + } + type = item[0]; + args = item[1]; + if (!this.canRead(type, buffer, offset)) { + // not enough bytes in the buffer, operation is still reading. + this.readOps.unshift(item); + return [Operation.READING, offset, this.stack[0]]; + } + offset += this['parse' + type](buffer, offset, context, args[0], args[1]); + } + if (this.stack[0] && this.stack[0].status && this.stack[0].status.code) { + return [Operation.ERROR, offset, this.stack[0]]; + } + else { + return [Operation.COMPLETE, offset, this.stack[0]]; + } +}; + +/** + * Defetermine whether the operation can read a value of the given + * type from the buffer at the given offset. + * + * @param {String} type The value type. + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading at. + * @return {Boolean} true if the value can be read. + */ +Operation.prototype.canRead = function (type, buffer, offset) { + var length = buffer.length; + if (offset > length) { + return false; + } + switch (type) { + case 'Array': + case 'Error': + return true; + case 'Byte': + case 'Char': + case 'Boolean': + return length >= offset + 1; + case 'Short': + case 'Record': + return length >= offset + constants.BYTES_SHORT; + case 'Long': + return length >= offset + constants.BYTES_LONG; + case 'Int': + case 'Collection': + return length >= offset + constants.BYTES_INT; + case 'Bytes': + case 'String': + if (length <= offset + constants.BYTES_INT) { + return false; + } + else { + return length >= offset + constants.BYTES_INT + buffer.readInt32BE(offset); + } + break; + default: + return false; + } +}; + + +/** + * Parse a byte from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseByte = function (buffer, offset, context, fieldName, reader) { + context[fieldName] = buffer.readUInt8(offset); + if (reader) { + reader.call(this, context, fieldName); + } + return 1; +}; + +/** + * Parse a character from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseChar = function (buffer, offset, context, fieldName, reader) { + context[fieldName] = String.fromCharCode(buffer.readUInt8(offset)); + if (reader) { + reader.call(this, context, fieldName); + } + return 1; +}; + +/** + * Parse a boolean from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseBoolean = function (buffer, offset, context, fieldName, reader) { + context[fieldName] = Boolean(buffer.readUInt8(offset)); + if (reader) { + reader.call(this, context, fieldName); + } + return 1; +}; + + +/** + * Parse a short from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseShort = function (buffer, offset, context, fieldName, reader) { + + context[fieldName] = buffer.readInt16BE(offset); + if (reader) { + reader.call(this, context, fieldName); + } + return constants.BYTES_SHORT; +}; + +/** + * Parse an integer from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseInt = function (buffer, offset, context, fieldName, reader) { + context[fieldName] = buffer.readInt32BE(offset); + if (reader) { + reader.call(this, context, fieldName); + } + return constants.BYTES_INT; +}; + +/** + * Parse a long from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseLong = function (buffer, offset, context, fieldName, reader) { + context[fieldName] = Long + .fromBits( + buffer.readUInt32BE(offset + constants.BYTES_INT), + buffer.readInt32BE(offset) + ) + .toNumber(); + + if (reader) { + reader.call(this, context, fieldName); + } + return constants.BYTES_LONG; +}; + +/** + * Parse some bytes from the given buffer at the given offset and + * insert them into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseBytes = function (buffer, offset, context, fieldName, reader) { + var length = buffer.readInt32BE(offset); + offset += constants.BYTES_INT; + if (length < 0) { + context[fieldName] = null; + } + else { + context[fieldName] = buffer.slice(offset, offset + length); + } + if (reader) { + reader.call(this, context, fieldName); + } + return length > 0 ? length + constants.BYTES_INT : constants.BYTES_INT; +}; + + +/** + * Parse a string from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseString = function (buffer, offset, context, fieldName, reader) { + var length = buffer.readInt32BE(offset); + offset += constants.BYTES_INT; + if (length < 0) { + context[fieldName] = null; + } + else { + context[fieldName] = buffer.toString('utf8', offset, offset + length); + } + if (reader) { + reader.call(this, context, fieldName); + } + return length > 0 ? length + constants.BYTES_INT : constants.BYTES_INT; +}; + +/** + * Parse a record from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseRecord = function (buffer, offset, context, fieldName, reader) { + var remainingOps = this.readOps, + record = {}; + this.readOps = []; + this.stack.push(record); + if (Array.isArray(context[fieldName])) { + context[fieldName].push(record); + } + else { + context[fieldName] = record; + } + this.readShort('classId', function (record, fieldName) { + if (record[fieldName] === -1) { + record.value = new errors.Protocol('No class for record, cannot proceed.'); + this.stack.pop(); + this.readOps.push(function () { + if (reader) { + reader.call(this, context, fieldName); + } + }); + this.readOps.push.apply(this.readOps, remainingOps); + return; + } + else if (record[fieldName] === -2) { + record.value = null; + this.stack.pop(); + this.readOps.push(function () { + if (reader) { + reader.call(this, context, fieldName); + } + }); + this.readOps.push.apply(this.readOps, remainingOps); + return; + } + else if (record[fieldName] === -3) { + record.type = 'd'; + this + .readShort('cluster') + .readLong('position') + .readOps.push(function () { + this.stack.pop(); + this.readOps.push(function () { + if (reader) { + reader.call(this, context, fieldName); + } + }); + this.readOps.push.apply(this.readOps, remainingOps); + }); + } + else if (record[fieldName] > -1) { + this + .readChar('type') + .readShort('cluster') + .readLong('position') + .readInt('version') + .readString('value', function (data, key) { + data[key] = deserializer.deserialize(data[key]); + this.stack.pop(); + this.readOps.push(function () { + if (reader) { + reader.call(this, context, fieldName); + } + }); + this.readOps.push.apply(this.readOps, remainingOps); + }); + } + }); + + + + return 0; +}; + + +/** + * Parse a collection from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseCollection = function (buffer, offset, context, fieldName, reader) { + var remainingOps = this.readOps, + records = [], + total = buffer.readInt32BE(offset), + i; + offset += 4; + this.readOps = []; + context[fieldName] = records; + for (i = 0; i < total; i++) { + this.readRecord(fieldName); + } + if (reader) { + this.readOps.push(function () { + reader.call(this, records); + }); + } + this.readOps.push.apply(this.readOps, remainingOps); + return 4; +}; + + +/** + * Parse an array from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * > Note. this differs from the other `parseXYZ` methods in that `reader` + * is required, and MUST return an array of functions. Each function in the + * array represents a 'scope' for an item in the array and will be invoked in order. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseArray = function (buffer, offset, context, fieldName, reader) { + var items = reader.call(this, context), + remainingOps = this.readOps; + + this.readOps = []; + + context[fieldName] = []; + this.stack.push(context[fieldName]); + + items.map(function (item) { + var childContext = {}; + context[fieldName].push(childContext); + this.readOps.push(function () { + this.stack.push(childContext); + }); + + item.call(this, childContext); + + this.readOps.push(function () { + this.stack.pop(); + }); + }, this); + + + this.readOps.push(function () { + this.stack.pop(); + }); + + this.readOps.push.apply(this.readOps, remainingOps); + return 0; +}; + +/** + * Parse an error from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * > Note: this implementation differs from the others in that + * when an error is encountered, any subsequent `readXYZ()` commands + * that were due to be run will be skipped. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseError = function (buffer, offset, context, fieldName, reader) { + var err = new errors.Request(); + err.previous = []; + // remove any ops we were expecting to run. + this.readOps = []; + + context[fieldName] = err; + this.stack.push(err); + this.readByte('id'); + + + function readItem () { + this.readString('type'); + this.readString('message'); + this.readByte('hasMore', function (data) { + var prev; + if (data.hasMore) { + prev = new errors.Request(); + err.previous.push(prev); + this.stack.pop(); + this.stack.push(prev); + readItem.call(this); + } + else { + this.readBytes('javaStackTrace', function (data) { + this.readOps.push(function (data) { + this.stack.pop(); + }); + }); + } + }); + } + readItem.call(this); + if (reader) { + reader.call(this, context, fieldName); + } + return 0; +}; + + +/** + * Parse any pushed data from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parsePushedData = function (buffer, offset, context, fieldName, reader) { + var length = buffer.readInt32BE(offset), + asString; + offset += constants.BYTES_INT; + asString = buffer.toString('utf8', offset, offset + length); + switch (asString.charAt(0)) { + case 'R': + context[fieldName] = deserializer.deserialize(asString.slice(1)); + break; + default: + console.log('unsupported pushed data format: ' + asString); + } + if (reader) { + reader.call(this, context, fieldName); + } + return length + constants.BYTES_INT; +}; + diff --git a/lib/transport/binary/protocol26/operations/command.js b/lib/transport/binary/protocol26/operations/command.js new file mode 100644 index 0000000..343400a --- /dev/null +++ b/lib/transport/binary/protocol26/operations/command.js @@ -0,0 +1,180 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + serializer = require('../serializer'), + writer = require('../writer'), + RID = require('../../../../recordid'); + +module.exports = Operation.extend({ + id: 'REQUEST_COMMAND', + opCode: 41, + writer: function () { + if (this.data.mode === 'a' && !this.data.class) { + this.data.class = 'com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery'; + } + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId || -1) + .writeChar(this.data.mode || 's') + .writeBytes(this.serializeQuery()); + + }, + serializeQuery: function () { + var buffers = [writer.writeString(this.data.class)]; + + if (this.data.class === 'q' || + this.data.class === 'com.orientechnologies.orient.core.sql.query.OSQLSynchQuery' || + this.data.class === 'com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery') { + buffers.push( + writer.writeString(this.data.query), + writer.writeInt(this.data.limit), + writer.writeString(this.data.fetchPlan || '') + ); + + if (this.data.params) { + buffers.push(writer.writeString(serializeParams(this.data.params))); + } + else { + buffers.push(writer.writeInt(0)); + } + } + else if ( + this.data.class === 's' || + this.data.class === 'com.orientechnologies.orient.core.command.script.OCommandScript') { + buffers.push( + writer.writeString(this.data.language || 'sql'), + writer.writeString(this.data.query) + ); + if (this.data.params && this.data.params.params && Object.keys(this.data.params.params).length) { + buffers.push( + writer.writeBoolean(true), + writer.writeString(serializeParams(this.data.params)) + ); + } + else { + buffers.push(writer.writeBoolean(false)); + } + buffers.push(writer.writeByte(0)); + } + else { + buffers.push(writer.writeString(this.data.query)); + if (this.data.params) { + buffers.push( + writer.writeBoolean(true), + writer.writeString(serializeParams(this.data.params)) + ); + } + else { + buffers.push(writer.writeBoolean(false)); + } + buffers.push(writer.writeBoolean(false)); + } + return Buffer.concat(buffers); + }, + reader: function () { + this + .readStatus('status') + .readCommandResult('results'); + }, + readCommandResult: function (fieldName, reader) { + this.payloads = []; + this.readOps.push(function (data) { + data[fieldName] = this.payloads; + this.stack.push(data[fieldName]); + this.readPayload('payloadStatus', function () { + this.stack.pop(); + }); + }); + return this; + }, + readPayload: function (payloadFieldName, reader) { + + return this.readByte(payloadFieldName, function (data, fieldName) { + var record = {}; + switch (data[fieldName]) { + case 0: + if (reader) { + reader.call(this); + } + break; + case 110: // null + record.type = 'r'; + record.content = null; + this.payloads.push(record); + this.readPayload(payloadFieldName, reader); + break; + case 1: + case 114: + // a record + record.type = 'r'; + this.payloads.push(record); + this.stack.push(record); + this.readRecord('content', function () { + this.stack.pop(); + this.readPayload(payloadFieldName, reader); + }); + break; + case 2: + // prefeteched record + record.type = 'p'; + this.payloads.push(record); + this.stack.push(record); + this.readRecord('content', function (data) { + this.stack.pop(); + this.readPayload(payloadFieldName, reader); + }); + break; + case 97: + // serialized result + record.type = 'f'; + this.payloads.push(record); + this.stack.push(record); + this.readString('content', function () { + this.stack.pop(); + this.readPayload(payloadFieldName, reader); + }); + break; + case 108: + // collection of records + record.type = 'l'; + this.payloads.push(record); + this.stack.push(record); + this.readCollection('content', function (data) { + this.stack.pop(); + this.readPayload(payloadFieldName, reader); + }); + break; + default: + reader.call(this); + } + }); + } +}); + +/** + * Serialize the parameters for a query. + * + * > Note: There is a bug in OrientDB where special kinds of string values + * > need to be twice quoted *in parameters*. Hence the need for this specialist function. + * + * @param {Object} data The data to serialize. + * @return {String} The serialized data. + */ +function serializeParams (data) { + var keys = Object.keys(data.params || {}), + total = keys.length, + c, i, key, value; + + for (i = 0; i < total; i++) { + key = keys[i]; + value = data.params[key]; + if (typeof value === 'string') { + c = value.charAt(0); + if (c === '.' || c === '#' || c === '<' || c === '[' || c === '(' || c === '{' || c === '0' || +c) { + data.params[key] = '"' + value + '"'; + } + } + } + return serializer.serializeDocument(data); +} \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/config-get.js b/lib/transport/binary/protocol26/operations/config-get.js new file mode 100644 index 0000000..a5e3b74 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/config-get.js @@ -0,0 +1,20 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_CONFIG_GET', + opCode: 70, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId || -1) + .writeString(this.data.key); + }, + reader: function () { + this + .readStatus('status') + .readString('value'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/config-list.js b/lib/transport/binary/protocol26/operations/config-list.js new file mode 100644 index 0000000..e4cab31 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/config-list.js @@ -0,0 +1,31 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_CONFIG_LIST', + opCode: 72, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId || -1); + }, + reader: function () { + this + .readStatus('status') + .readShort('total') + .readArray('items', function (data) { + var items = [], + i; + for (i = 0; i < data.total; i++) { + items.push(function () { + this + .readString('key') + .readString('value'); + }); + } + return items; + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/config-set.js b/lib/transport/binary/protocol26/operations/config-set.js new file mode 100644 index 0000000..686dcca --- /dev/null +++ b/lib/transport/binary/protocol26/operations/config-set.js @@ -0,0 +1,23 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_CONFIG_SET', + opCode: 71, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId || -1) + .writeString(this.data.key) + .writeString(this.data.value); + }, + reader: function () { + this + .readStatus('status') + .readOps.push(function (data) { + data.success = true; + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/connect.js b/lib/transport/binary/protocol26/operations/connect.js new file mode 100644 index 0000000..d9b8916 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/connect.js @@ -0,0 +1,27 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + npmPackage = require('../../../../../package.json'); + +module.exports = Operation.extend({ + id: 'REQUEST_CONNECT', + opCode: 2, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId || -1) + .writeString(npmPackage.name) + .writeString(npmPackage.version) + .writeShort(+constants.PROTOCOL_VERSION) + .writeString('') // client id + .writeString('ORecordDocument2csv') // serialization format + .writeString(this.data.username) + .writeString(this.data.password); + }, + reader: function () { + this + .readStatus('status') + .readInt('sessionId'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/datacluster-add.js b/lib/transport/binary/protocol26/operations/datacluster-add.js new file mode 100644 index 0000000..5846cc3 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/datacluster-add.js @@ -0,0 +1,21 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DATACLUSTER_ADD', + opCode: 10, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId) + .writeString(this.data.name) + .writeShort(this.data.id || -1); + }, + reader: function () { + this + .readStatus('status') + .readShort('id'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/datacluster-count.js b/lib/transport/binary/protocol26/operations/datacluster-count.js new file mode 100644 index 0000000..2859a63 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/datacluster-count.js @@ -0,0 +1,35 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DATACLUSTER_COUNT', + opCode: 12, + writer: function () { + var total, item, i; + + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId); + + if (Array.isArray(this.data.id)) { + total = this.data.id.length; + this.writeShort(total); + for (i = 0; i < total; i++) { + this.writeShort(this.data.id[i]); + } + } + else { + this + .writeShort(1) + .writeShort(this.data.id); + } + this.writeByte(this.data.tombstones || false); + }, + reader: function () { + this + .readStatus('status') + .readLong('count'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/datacluster-datarange.js b/lib/transport/binary/protocol26/operations/datacluster-datarange.js new file mode 100644 index 0000000..9caae51 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/datacluster-datarange.js @@ -0,0 +1,23 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DATACLUSTER_DATARANGE', + opCode: 13, + writer: function () { + var total, i; + + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId) + .writeShort(this.data.id); + }, + reader: function () { + this + .readStatus('status') + .readLong('begin') + .readLong('end'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/datacluster-drop.js b/lib/transport/binary/protocol26/operations/datacluster-drop.js new file mode 100644 index 0000000..89b3ea5 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/datacluster-drop.js @@ -0,0 +1,22 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DATACLUSTER_DROP', + opCode: 11, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId) + .writeShort(this.data.id); + }, + reader: function () { + this + .readStatus('status') + .readByte('success', function (data, fieldName) { + data[fieldName] = Boolean(data[fieldName]); + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/db-close.js b/lib/transport/binary/protocol26/operations/db-close.js new file mode 100644 index 0000000..ba291e8 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/db-close.js @@ -0,0 +1,15 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_CLOSE', + opCode: 5, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId || -1); + }, + reader: function () {} +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/db-countrecords.js b/lib/transport/binary/protocol26/operations/db-countrecords.js new file mode 100644 index 0000000..a290a89 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/db-countrecords.js @@ -0,0 +1,19 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_COUNTRECORDS', + opCode: 9, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId); + }, + reader: function () { + this + .readStatus('status') + .readLong('count'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/db-create.js b/lib/transport/binary/protocol26/operations/db-create.js new file mode 100644 index 0000000..3381106 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/db-create.js @@ -0,0 +1,20 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_CREATE', + opCode: 4, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId) + .writeString(this.data.name) + .writeString(this.data.type || 'graph') + .writeString(this.data.storage || 'plocal'); + }, + reader: function () { + this.readStatus('status'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/db-delete.js b/lib/transport/binary/protocol26/operations/db-delete.js new file mode 100644 index 0000000..6772b10 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/db-delete.js @@ -0,0 +1,19 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_DROP', + opCode: 7, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId) + .writeString(this.data.name) + .writeString(this.data.storage || 'plocal'); + }, + reader: function () { + this.readStatus('status'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/db-exists.js b/lib/transport/binary/protocol26/operations/db-exists.js new file mode 100644 index 0000000..29c1e40 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/db-exists.js @@ -0,0 +1,23 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_EXIST', + opCode: 6, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId) + .writeString(this.data.name) + .writeString(this.data.storage || 'local'); + }, + reader: function () { + this + .readStatus('status') + .readByte('exists', function (data) { + data.exists = Boolean(data.exists); + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/db-freeze.js b/lib/transport/binary/protocol26/operations/db-freeze.js new file mode 100644 index 0000000..6c9cba4 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/db-freeze.js @@ -0,0 +1,19 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_FREEZE', + opCode: 94, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId) + .writeString(this.data.name) + .writeString(this.data.storage || 'plocal'); + }, + reader: function () { + this.readStatus('status'); + } +}); diff --git a/lib/transport/binary/protocol26/operations/db-list.js b/lib/transport/binary/protocol26/operations/db-list.js new file mode 100644 index 0000000..8262b5e --- /dev/null +++ b/lib/transport/binary/protocol26/operations/db-list.js @@ -0,0 +1,21 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_LIST', + opCode: 74, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId || -1); + }, + reader: function () { + this + .readStatus('status') + .readObject('databases', function (data, fieldName) { + data[fieldName] = data[fieldName].databases; + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/db-open.js b/lib/transport/binary/protocol26/operations/db-open.js new file mode 100644 index 0000000..8c3eb8f --- /dev/null +++ b/lib/transport/binary/protocol26/operations/db-open.js @@ -0,0 +1,44 @@ +"use strict"; +var Operation = require('../operation'), + constants = require('../constants'), + npmPackage = require('../../../../../package.json'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_OPEN', + opCode: 3, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId || -1) + .writeString(npmPackage.name) + .writeString(npmPackage.version) + .writeShort(+constants.PROTOCOL_VERSION) + .writeString('') // client id + .writeString('ORecordDocument2csv') // serialization format + .writeString(this.data.name) + .writeString(this.data.type) + .writeString(this.data.username) + .writeString(this.data.password); + }, + reader: function () { + this + .readStatus('status') + .readInt('sessionId') + .readShort('totalClusters') + .readArray('clusters', function (data) { + var clusters = [], + total = data.totalClusters, + i; + + for (i = 0; i < total; i++) { + clusters.push(function (data) { + this.readString('name') + .readShort('id'); + }); + } + return clusters; + }) + .readObject('serverCluster') + .readString('release'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/db-release.js b/lib/transport/binary/protocol26/operations/db-release.js new file mode 100644 index 0000000..daeada5 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/db-release.js @@ -0,0 +1,19 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_RELEASE', + opCode: 95, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId) + .writeString(this.data.name) + .writeString(this.data.storage || 'plocal'); + }, + reader: function () { + this.readStatus('status'); + } +}); diff --git a/lib/transport/binary/protocol26/operations/db-reload.js b/lib/transport/binary/protocol26/operations/db-reload.js new file mode 100644 index 0000000..0f01c18 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/db-reload.js @@ -0,0 +1,32 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_RELOAD', + opCode: 73, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId || -1); + }, + reader: function () { + this + .readStatus('status') + .readShort('totalClusters') + .readArray('clusters', function (data) { + var clusters = [], + total = data.totalClusters, + i; + + for (i = 0; i < total; i++) { + clusters.push(function (data) { + this.readString('name') + .readShort('id'); + }); + } + return clusters; + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/db-size.js b/lib/transport/binary/protocol26/operations/db-size.js new file mode 100644 index 0000000..e83b6b3 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/db-size.js @@ -0,0 +1,19 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_SIZE', + opCode: 8, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId); + }, + reader: function () { + this + .readStatus('status') + .readLong('size'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/index.js b/lib/transport/binary/protocol26/operations/index.js new file mode 100644 index 0000000..be581ca --- /dev/null +++ b/lib/transport/binary/protocol26/operations/index.js @@ -0,0 +1,34 @@ +"use strict"; /*jshint sub:true*/ + +exports['connect'] = require('./connect'); +exports['db-open'] = require('./db-open'); +exports['db-create'] = require('./db-create'); +exports['db-exists'] = require('./db-exists'); +exports['db-delete'] = require('./db-delete'); +exports['db-size'] = require('./db-size'); +exports['db-countrecords'] = require('./db-countrecords'); +exports['db-reload'] = require('./db-reload'); +exports['db-list'] = require('./db-list'); +exports['db-freeze'] = require('./db-freeze'); +exports['db-release'] = require('./db-release'); +exports['db-close'] = require('./db-close'); + + +exports['datacluster-add'] = require('./datacluster-add'); +exports['datacluster-count'] = require('./datacluster-count'); +exports['datacluster-datarange'] = require('./datacluster-datarange'); +exports['datacluster-drop'] = require('./datacluster-drop'); + +exports['record-create'] = require('./record-create'); +exports['record-load'] = require('./record-load'); +exports['record-metadata'] = require('./record-metadata'); +exports['record-update'] = require('./record-update'); +exports['record-delete'] = require('./record-delete'); +exports['record-clean-out'] = require('./record-clean-out'); + +exports['command'] = require('./command'); +exports['tx-commit'] = require('./tx-commit'); + +exports['config-list'] = require('./config-list'); +exports['config-get'] = require('./config-get'); +exports['config-set'] = require('./config-set'); diff --git a/lib/transport/binary/protocol26/operations/record-clean-out.js b/lib/transport/binary/protocol26/operations/record-clean-out.js new file mode 100644 index 0000000..a140348 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/record-clean-out.js @@ -0,0 +1,35 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + RID = require('../../../../recordid'), + serializer = require('../serializer'); + +module.exports = Operation.extend({ + id: 'REQUEST_RECORD_CLEAN_OUT', + opCode: 38, + writer: function () { + var rid, cluster, position; + if (this.data.record && this.data.record['@rid']) { + rid = RID.parse(this.data.record['@rid']); + cluster = this.data.cluster || rid.cluster; + position = this.data.position || rid.position; + } + else { + cluster = this.data.cluster; + position = this.data.position; + } + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId) + .writeShort(cluster) + .writeLong(position) + .writeInt(this.data.version || -1) + .writeBoolean(this.data.mode); + }, + reader: function () { + this + .readStatus('status') + .readBoolean('success'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/record-create.js b/lib/transport/binary/protocol26/operations/record-create.js new file mode 100644 index 0000000..b4fc227 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/record-create.js @@ -0,0 +1,51 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + RID = require('../../../../recordid'), + serializer = require('../serializer'); + +module.exports = Operation.extend({ + id: 'REQUEST_RECORD_CREATE', + opCode: 31, + writer: function () { + var rid, cluster; + if (this.data.record['@rid']) { + rid = RID.parse(this.data.record['@rid']); + cluster = this.data.cluster || rid.cluster; + } + else { + cluster = this.data.cluster; + } + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId) + .writeShort(cluster) + .writeBytes(serializer.encodeRecordData(this.data.record)) + .writeByte(constants.RECORD_TYPES[this.data.type || 'd']) + .writeByte(this.data.mode || 0); + }, + reader: function () { + this + .readStatus('status') + .readShort('cluster') + .readLong('position') + .readInt('version') + .readInt('totalChanges') + .readArray('changes', function (data) { + var items = [], + i; + for (i = 0; i < data.totalChanges; i++) { + items.push(function () { + this + .readLong('uuidHigh') + .readLong('uuidLow') + .readLong('fileId') + .readLong('pageIndex') + .readInt('pageOffset'); + }); + } + return items; + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/record-delete.js b/lib/transport/binary/protocol26/operations/record-delete.js new file mode 100644 index 0000000..a246628 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/record-delete.js @@ -0,0 +1,46 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + RID = require('../../../../recordid'), + serializer = require('../serializer'); + +module.exports = Operation.extend({ + id: 'REQUEST_RECORD_DELETE', + opCode: 33, + writer: function () { + var rid, cluster, position, version; + if (this.data.record && this.data.record['@rid']) { + rid = RID.parse(this.data.record['@rid']); + cluster = this.data.cluster || rid.cluster; + position = this.data.position || rid.position; + } + else { + cluster = this.data.cluster; + position = this.data.position; + } + if (this.data.version != null) { + version = this.data.version; + } + else if (this.data.record && this.data.record['@version'] != null) { + version = this.data.record['@version']; + } + else { + version = -1; + } + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId) + .writeShort(cluster) + .writeLong(position) + .writeInt(version) + .writeByte(this.data.mode || 0); + }, + reader: function () { + this + .readStatus('status') + .readByte('success', function (data, fieldName) { + data[fieldName] = Boolean(data[fieldName]); + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/record-load.js b/lib/transport/binary/protocol26/operations/record-load.js new file mode 100644 index 0000000..93123d9 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/record-load.js @@ -0,0 +1,120 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + RID = require('../../../../recordid'), + serializer = require('../serializer'), + deserializer = require('../deserializer'), + errors = require('../../../../errors'); + +module.exports = Operation.extend({ + id: 'REQUEST_RECORD_LOAD', + opCode: 30, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId) + .writeShort(this.data.cluster) + .writeLong(this.data.position) + .writeString(this.data.fetchPlan || '') + .writeByte(this.data.ignoreCache || 0) + .writeByte(this.data.tombstones || 0); + }, + reader: function () { + var records = []; + this.readStatus('status'); + this.readOps.push(function (data) { + data.records = records; + this.stack.push(data.records); + this.readPayload(records, function () { + this.stack.pop(); + data.records = data.records.map(function (record) { + var r; + if (record.type === 'd') { + r = record.content || {}; + r['@rid'] = r['@rid'] ||new RID({ + cluster: record.cluster, + position: record.position + }); + r['@version'] = record.version; + r['@type'] = record.type; + } + else { + r = { + '@rid': new RID({ + cluster: record.cluster, + position: record.position + }), + '@version': record.version, + '@type': record.type, + value: record.content + }; + + } + return r; + }, this); + }); + }); + }, + readPayload: function (records, ender) { + + return this.readByte('payloadStatus', function (data, fieldName) { + var record = {}; + switch (data[fieldName]) { + case 0: + // nothing to do. + if (ender) { + ender.call(this); + } + break; + case 1: + // a record + records.push(record); + this.stack.push(record); + this + .readString('content') + .readInt('version') + .readChar('type', function (data, fieldName) { + data.cluster = this.data.cluster; + data.position = this.data.position; + if (data[fieldName] === 'd') { + data.content = deserializer.deserialize(data.content); + } + this.stack.pop(); + this.readPayload(records, ender); + }); + break; + case 2: + // a sub record + records.push(record); + this.stack.push(record); + this.readShort('classId', function (data, fieldName) { + switch (data[fieldName]) { + case -2: + this.stack.pop(); + this.readPayload(records, ender); + break; + case -3: + throw new errors.Protocol('ClassID ' + data[fieldName] + ' is not supported.'); + default: + this + .readChar('type') + .readShort('cluster') + .readLong('position') + .readInt('version') + .readString('content', function (data, fieldName) { + if (data.type === 'd') { + data.content = deserializer.deserialize(data.content); + } + this.stack.pop(); + this.readPayload(records, ender); + }); + } + }); + break; + default: + this.readPayload(records, ender); + } + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/record-metadata.js b/lib/transport/binary/protocol26/operations/record-metadata.js new file mode 100644 index 0000000..2d0ccb3 --- /dev/null +++ b/lib/transport/binary/protocol26/operations/record-metadata.js @@ -0,0 +1,35 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + RID = require('../../../../recordid'), + serializer = require('../serializer'); + +module.exports = Operation.extend({ + id: 'REQUEST_RECORD_METADATA', + opCode: 29, + writer: function () { + var rid, cluster, position; + if (this.data.record && this.data.record['@rid']) { + rid = RID.parse(this.data.record['@rid']); + cluster = this.data.cluster || rid.cluster; + position = this.data.position || rid.position; + } + else { + cluster = this.data.cluster; + position = this.data.position; + } + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId) + .writeShort(cluster) + .writeLong(position); + }, + reader: function () { + this + .readStatus('status') + .readShort('cluster') + .readLong('position') + .readInt('version'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/record-update.js b/lib/transport/binary/protocol26/operations/record-update.js new file mode 100644 index 0000000..091792e --- /dev/null +++ b/lib/transport/binary/protocol26/operations/record-update.js @@ -0,0 +1,63 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + RID = require('../../../../recordid'), + serializer = require('../serializer'); + +module.exports = Operation.extend({ + id: 'REQUEST_RECORD_UPDATE', + opCode: 32, + writer: function () { + var rid, cluster, position, version; + if (this.data.record['@rid']) { + rid = RID.parse(this.data.record['@rid']); + cluster = this.data.cluster || rid.cluster; + position = this.data.position || rid.position; + } + else { + cluster = this.data.cluster; + position = this.data.position; + } + if (this.data.version != null) { + version = this.data.version; + } + else if (this.data.record['@version'] != null) { + version = this.data.record['@version']; + } + else { + version = -1; + } + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId) + .writeShort(cluster) + .writeLong(position) + .writeBoolean(true) + .writeBytes(serializer.encodeRecordData(this.data.record)) + .writeInt(version) + .writeByte(constants.RECORD_TYPES[this.data.type || 'd']) + .writeByte(this.data.mode || 0); + }, + reader: function () { + this + .readStatus('status') + .readInt('version') + .readInt('totalChanges') + .readArray('changes', function (data) { + var items = [], + i; + for (i = 0; i < data.totalChanges; i++) { + items.push(function () { + this + .readLong('uuidHigh') + .readLong('uuidLow') + .readLong('fileId') + .readLong('pageIndex') + .readInt('pageOffset'); + }); + } + return items; + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/operations/tx-commit.js b/lib/transport/binary/protocol26/operations/tx-commit.js new file mode 100644 index 0000000..b80f39f --- /dev/null +++ b/lib/transport/binary/protocol26/operations/tx-commit.js @@ -0,0 +1,113 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + RID = require('../../../../recordid'), + serializer = require('../serializer'); + +module.exports = Operation.extend({ + id: 'REQUEST_TX_COMMIT', + opCode: 60, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId || -1) + .writeInt(this.data.txId) + .writeByte(this.data.txLog); // use transaction log + + + // creates + var total = this.data.creates.length, + item, i; + + for (i = 0; i < total; i++) { + item = this.data.creates[i]; + this.writeByte(1); // mark the start of an entry. + this.writeByte(3); // create. + this.writeShort(item['@rid'].cluster); + this.writeLong(item['@rid'].position); + this.writeByte(constants.RECORD_TYPES[item['@type'] || 'd'] || 100); // document by default + this.writeBytes(serializer.encodeRecordData(item)); + } + + // updates + total = this.data.updates.length; + + for (i = 0; i < total; i++) { + item = this.data.updates[i]; + this.writeByte(1); // mark the start of an entry. + this.writeByte(1); // update. + this.writeShort(item['@rid'].cluster); + this.writeLong(item['@rid'].position); + this.writeByte(constants.RECORD_TYPES[item['@type'] || 'd'] || 100); // document by default + this.writeInt(item['@version'] || 0); + this.writeBytes(serializer.encodeRecordData(item)); + this.writeBoolean(true); + } + + // deletes + total = this.data.deletes.length; + + for (i = 0; i < total; i++) { + item = this.data.deletes[i]; + this.writeByte(1); // mark the start of an entry. + this.writeByte(2); // delete + this.writeShort(item['@rid'].cluster); + this.writeLong(item['@rid'].position); + this.writeByte(constants.RECORD_TYPES[item['@type'] || 'd'] || 100); // document by default + this.writeInt(item['@version'] || 0); + } + this.writeByte(0); // no more documents + this.writeString(''); + }, + reader: function () { + this + .readStatus('status') + .readInt('totalCreated') + .readArray('created', function (data) { + var items = [], + i; + for (i = 0; i < data.totalCreated; i++) { + items.push(function () { + this + .readShort('tmpCluster') + .readLong('tmpPosition') + .readShort('cluster') + .readLong('position'); + }); + } + return items; + }) + .readInt('totalUpdated') + .readArray('updated', function (data) { + var items = [], + i; + for (i = 0; i < data.totalUpdated; i++) { + items.push(function () { + this + .readShort('cluster') + .readLong('position') + .readInt('version'); + }); + } + return items; + }); + + this.readInt('totalChanges') + .readArray('changes', function (data) { + var items = [], + i; + for (i = 0; i < data.totalChanges; i++) { + items.push(function () { + this + .readLong('uuidHigh') + .readLong('uuidLow') + .readLong('fileId') + .readLong('pageIndex') + .readInt('pageOffset'); + }); + } + return items; + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol26/serializer.js b/lib/transport/binary/protocol26/serializer.js new file mode 100644 index 0000000..1bb1d3e --- /dev/null +++ b/lib/transport/binary/protocol26/serializer.js @@ -0,0 +1,137 @@ +"use strict"; + +var RecordID = require('../../../recordid'); + +/** + * Serialize a record and return it as a buffer. + * + * @param {Object} content The record to serialize. + * @return {Buffer} The buffer containing the content. + */ +function encodeRecordData (content) { + return new Buffer(serializeDocument(content), 'utf8'); +} + +/** + * Serialize a document. + * + * @param {Object} document The document to serialize. + * @param {Boolean} isMap Whether to serialize the document as a map. + * @return {String} The serialized document. + */ +function serializeDocument (document, isMap) { + var result = '', + className = '', + fieldNames = Object.keys(document), + totalFields = fieldNames.length, + fieldWrap, value, field, i; + + for (i = 0; i < totalFields; i++) { + field = fieldNames[i]; + value = document[field]; + if (field === '@class') { + className = value; + } + else if (field.charAt(0) === '@') { + continue; + } + else { + if (isMap) { + fieldWrap = '"'; + } + else { + fieldWrap = ''; + } + result += fieldWrap + field + fieldWrap + ':' + serializeValue(value) + ','; + } + } + + if (className !== '') { + result = className + '@' + result; + } + + if (result[result.length - 1] === ',') { + result = result.slice(0, -1); + } + + return result; +} + +/** + * Serialize a given value according to its type. + * @param {Object} value The value to serialize. + * @return {String} The serialized value. + */ +function serializeValue (value) { + var type = typeof value; + if (type === 'string') { + return '"' + value.replace(/\\/, "\\\\").replace(/"/g, '\\"') + '"'; + } + else if (type === 'number') { + return ~value.toString().indexOf('.') ? value + 'f' : value; + } + else if (type === 'boolean') { + return value ? true : false; + } + else if (Object.prototype.toString.call(value) === '[object Date]') { + return value.getTime() + 't'; + } + else if (Array.isArray(value)) { + return serializeArray(value); + } + else if (value === Object(value)) { + return serializeObject(value); + } + else { + return ''; + } +} + + +/** + * Serialize an array of values. + * @param {Array} value The value to serialize. + * @return {String} The serialized value. + */ +function serializeArray (value) { + var result = '[', i, limit; + for (i = 0, limit = value.length; i < limit; i++) { + if (value[i] === Object(value[i])) { + result += serializeObject(value[i]); + } + else { + result += serializeValue(value[i]); + } + if (i < limit - 1) { + result += ','; + } + } + result += ']'; + return result; +} + +/** + * Serialize an object. + * @param {Object} value The value to serialize. + * @return {String} The serialized value. + */ +function serializeObject (value) { + if (value instanceof RecordID) { + return value.toString(); + } + else if (value['@type'] === 'd') { + return '(' + serializeDocument(value, false) + ')'; + } + else { + return '{' + serializeDocument(value, true) + '}'; + } +} + + + + +// export the public methods + +exports.serializeDocument = serializeDocument; +exports.serializeValue = serializeValue; +exports.encodeRecordData = encodeRecordData; \ No newline at end of file diff --git a/lib/transport/binary/protocol26/writer.js b/lib/transport/binary/protocol26/writer.js new file mode 100644 index 0000000..5c21cd2 --- /dev/null +++ b/lib/transport/binary/protocol26/writer.js @@ -0,0 +1,123 @@ +"use strict"; + +var Long = require('../../../long').Long, + constants = require('./constants'); + +/** + * Parse data to 4 bytes which represents integer value. + * + * @fixme this is a super misleading function name and comment! + * + * @param {Mixed} data The data. + * @return {Buffer} The buffer containing the data. + */ +function writeByte (data) { + return new Buffer([data]); +} + +/** + * Parse data to 4 bytes which represents integer value. + * + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +function writeInt (data) { + var buf = new Buffer(constants.BYTES_INT); + buf.writeInt32BE(data, 0); + return buf; +} + +/** + * Parse data to 8 bytes which represents a long value. + * + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +function writeLong (data) { + var buf = new Buffer(constants.BYTES_LONG), + value = Long.fromNumber(data); + + buf.fill(0); + buf.writeInt32BE(value.high_, 0); + buf.writeInt32BE(value.low_, constants.BYTES_INT); + + return buf; +} + +/** + * Parse data to 2 bytes which represents short value. + * + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +function writeShort (data) { + var buf = new Buffer(constants.BYTES_SHORT); + buf.writeInt16BE(data, 0); + return buf; +} + +/** + * Write bytes to a buffer + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +function writeBytes (data) { + var length = data.length, + buf = new Buffer(constants.BYTES_INT + length); + buf.writeInt32BE(length, 0); + data.copy(buf, constants.BYTES_INT); + return buf; +} + +/** + * Parse string data to buffer with UTF-8 encoding. + * + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +function writeString (data) { + if (data === null) { + return writeInt(-1); + } + var stringBuf = encodeString(data), + length = stringBuf.length, + buf = new Buffer(constants.BYTES_INT + length); + buf.writeInt32BE(length, 0); + stringBuf.copy(buf, constants.BYTES_INT, 0, stringBuf.length); + return buf; +} + +function encodeString (data) { + var length = data.length, + output = new Buffer(length * 3), // worst case, all chars could require 3-byte encodings. + j = 0, // index output + i, c; + + for (i = 0; i < length; i++) { + c = data.charCodeAt(i); + if (c < 0x80) { + // 7-bits done in one byte. + output[j++] = c; + } + else if (c < 0x800) { + // 8-11 bits done in 2 bytes + output[j++] = (0xC0 | c >> 6); + output[j++] = (0x80 | c & 0x3F); + } + else { + // 12-16 bits done in 3 bytes + output[j++] = (0xE0 | c >> 12); + output[j++] = (0x80 | c >> 6 & 0x3F); + output[j++] = (0x80 | c & 0x3F); + } + } + return output.slice(0, j); +} + +exports.writeByte = writeByte; +exports.writeBoolean = writeByte; +exports.writeBytes = writeBytes; +exports.writeShort = writeShort; +exports.writeInt = writeInt; +exports.writeLong = writeLong; +exports.writeString = writeString; \ No newline at end of file From c2755f0072eacc1918487a745841ec866d199429 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 2 Dec 2014 09:33:45 +0000 Subject: [PATCH 159/308] drop support for data segments --- .../binary/protocol19/operations/db-operations-test.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/transport/binary/protocol19/operations/db-operations-test.js b/test/transport/binary/protocol19/operations/db-operations-test.js index ecd0dab..ea49e2d 100644 --- a/test/transport/binary/protocol19/operations/db-operations-test.js +++ b/test/transport/binary/protocol19/operations/db-operations-test.js @@ -294,16 +294,6 @@ describe("Database Operations", function () { }); }); }); - describe('datasegment-add', function () { - it("should add a data segment", function () { - return TEST_SERVER.send('datasegment-add', { - sessionId: dbSessionId, - location: '/tmp', - name: 'test_segment' - }); - }); - }); - describe('db-close', function () { it("should close a database", function () { return TEST_SERVER.send('db-close', { From 038d78460e33edb7ba8e90198086a5de72889c37 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 2 Dec 2014 11:31:23 +0000 Subject: [PATCH 160/308] delete by rid from index no longer supported --- lib/db/index/index.js | 12 ++++++++---- test/db/index-test.js | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/db/index/index.js b/lib/db/index/index.js index 7a0091f..a10d78a 100644 --- a/lib/db/index/index.js +++ b/lib/db/index/index.js @@ -84,13 +84,17 @@ Index.prototype.set = function (key, value) { }; /** - * Delete the given rid from the index. + * Delete the given key from the index. * - * @param {String} rid The rid to delete. + * @param {String} key The key to delete. * @promise {Index} The index object. */ -Index.prototype.delete = function (rid) { - return this.db.query('DELETE FROM index:' + this.name + ' WHERE rid = ' + rid) +Index.prototype.delete = function (key) { + return this.db.query('DELETE FROM index:' + this.name + ' WHERE key = :key', { + params: { + key: key + } + }) .return(this); }; diff --git a/test/db/index-test.js b/test/db/index-test.js index 79d03bb..b85aa96 100644 --- a/test/db/index-test.js +++ b/test/db/index-test.js @@ -159,8 +159,8 @@ describe("Database API - Index", function () { describe('Db::index::delete()', function () { - it('should delete a rid', function () { - return this.index.delete(this.items[4]['@rid']); + it('should delete a key', function () { + return this.index.delete(this.items[4].name); }); }); }); From 609573986225664e3564a2abb9a938482285764e Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 2 Dec 2014 11:51:17 +0000 Subject: [PATCH 161/308] tree based ridbag fixes --- lib/bag.js | 8 ++++---- test/core/bag-test.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/bag.js b/lib/bag.js index 75cbe0d..1c8a23f 100644 --- a/lib/bag.js +++ b/lib/bag.js @@ -86,7 +86,7 @@ Bag.prototype._parse = function () { if (this._type === Bag.BAG_EMBEDDED) { - this._size = buffer.readUInt32BE(this._offset); + this._size = buffer.readInt32BE(this._offset); this._offset += 4; } else { @@ -94,11 +94,11 @@ Bag.prototype._parse = function () { this._offset += 8; this._pageIndex = readLong(buffer, this._offset); this._offset += 8; - this._pageOffset = buffer.readUInt32BE(this._offset); + this._pageOffset = buffer.readInt32BE(this._offset); this._offset += 4; - this._size = buffer.readUInt32BE(this._offset); + this._size = buffer.readInt32BE(this._offset); this._offset += 4; - this._changeSize = buffer.readUInt32BE(this._offset); + this._changeSize = buffer.readInt32BE(this._offset); this._offset += 4; } this._buffer = buffer; diff --git a/test/core/bag-test.js b/test/core/bag-test.js index d2e8297..9b35e01 100644 --- a/test/core/bag-test.js +++ b/test/core/bag-test.js @@ -146,7 +146,7 @@ describe("RID Bag", function () { this.bag.should.be.an.instanceOf(LIB.Bag) this.bag.type.should.equal(LIB.Bag.BAG_TREE); expect(this.bag.uuid).to.equal(null); - this.bag.size.should.equal(120); + this.bag.size.should.equal(-1); // Tree based, so must manually ask the server }); }); }); From 54cd43adbbb4e65ee03ffce0d4633887f0193984 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 2 Dec 2014 11:51:39 +0000 Subject: [PATCH 162/308] add newer odb server config --- ci/orientdb-server-config.xml | 154 ++++++++++++++++++++++++++-------- 1 file changed, 120 insertions(+), 34 deletions(-) diff --git a/ci/orientdb-server-config.xml b/ci/orientdb-server-config.xml index 2924c11..4a9183a 100644 --- a/ci/orientdb-server-config.xml +++ b/ci/orientdb-server-config.xml @@ -1,76 +1,162 @@ + + - - + + + + + + + + + + + + + - - + + + - - - - - - + + + + + + + + + + + - + + - - + + + + + + + + + + + + + + + + + + + + + + + - - + + + - + - + + + + + + + + + - + - - + + + + - + - - - + + - + + - - - - - - - - - + + + + + + + + + + + From 37e1f200de4aac70e17c850117c33a9c340128e1 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 2 Dec 2014 12:07:05 +0000 Subject: [PATCH 163/308] consolidate rest api tests --- .../transport/rest/operations/command-test.js | 26 ----- .../transport/rest/operations/db-open-test.js | 34 ------ .../rest/operations/record-load-test.js | 59 ---------- test/transport/rest/rest-transport-test.js | 101 ++++++++++++++++++ 4 files changed, 101 insertions(+), 119 deletions(-) delete mode 100644 test/transport/rest/operations/command-test.js delete mode 100644 test/transport/rest/operations/db-open-test.js delete mode 100644 test/transport/rest/operations/record-load-test.js diff --git a/test/transport/rest/operations/command-test.js b/test/transport/rest/operations/command-test.js deleted file mode 100644 index 738cf14..0000000 --- a/test/transport/rest/operations/command-test.js +++ /dev/null @@ -1,26 +0,0 @@ -var Promise = require('bluebird'); - -describe('Rest Operations - Command', function () { - before(function () { - return CREATE_TEST_DB(this, 'testdb_rest_command'); - }); - after(function () { - return DELETE_TEST_DB('testdb_rest_command'); - }); - - it('should execute a query', function () { - var config = { - database: 'testdb_rest_command', - class: 'com.orientechnologies.orient.core.sql.query.OSQLSynchQuery', - limit: 2, - query: 'SELECT * FROM OUser', - mode: 's' - }; - return Promise.all([REST_SERVER.send('command', config), this.db.send('command', config)]) - .spread(function (fromRest, fromBinary) { - fromRest.results.length.should.equal(fromBinary.results.length); - fromRest.results[0].content.length.should.equal(fromBinary.results[0].content.length); - }); - }); - -}); diff --git a/test/transport/rest/operations/db-open-test.js b/test/transport/rest/operations/db-open-test.js deleted file mode 100644 index 2ba5e99..0000000 --- a/test/transport/rest/operations/db-open-test.js +++ /dev/null @@ -1,34 +0,0 @@ -var Promise = require('bluebird'); - -describe('Rest Operations - Db Open', function () { - before(function () { - return CREATE_TEST_DB(this, 'testdb_rest_dbopen'); - }); - after(function () { - return DELETE_TEST_DB('testdb_rest_dbopen'); - }); - - it('should open the database', function () { - var params = { - sessionId: -1, - name: 'testdb_rest_dbopen', - type: 'graph', - username: 'admin', - password: 'admin' - }; - return Promise.all([REST_SERVER.send('db-open', params), TEST_SERVER.send('db-open', params)]) - .spread(function (fromRest, fromBinary) { - fromRest.release.should.equal(fromBinary.release); - fromRest.totalClusters.should.equal(fromBinary.totalClusters); - fromRest.clusters.length.should.equal(fromBinary.clusters.length); - fromRest.clusters.map(pluck('name')).sort().should.eql(fromBinary.clusters.map(pluck('name')).sort()); - }); - }) -}); - - -function pluck (key) { - return function (item) { - return item[key]; - } -} \ No newline at end of file diff --git a/test/transport/rest/operations/record-load-test.js b/test/transport/rest/operations/record-load-test.js deleted file mode 100644 index 07b20dc..0000000 --- a/test/transport/rest/operations/record-load-test.js +++ /dev/null @@ -1,59 +0,0 @@ -var Promise = require('bluebird'); - -describe('Rest Operations - Record Load', function () { - before(function () { - return CREATE_TEST_DB(this, 'testdb_rest_record_load'); - }); - after(function () { - return DELETE_TEST_DB('testdb_rest_record_load'); - }); - - it('should load a record', function () { - var params = { - sessionId: -1, - database: 'testdb_rest_record_load', - cluster: 5, - position: 0 - }; - return Promise.all([REST_SERVER.send('record-load', params), this.db.send('record-load', params)]) - .spread(function (fromRest, fromBinary) { - fromRest = fromRest.records[0]; - fromBinary = fromBinary.records[0]; - fromRest['@rid'].should.eql(fromBinary['@rid']); - fromRest['@version'].should.equal(fromBinary['@version']); - fromRest['@type'].should.equal(fromBinary['@type']); - fromRest.name.should.equal(fromBinary.name); - fromRest.status.should.equal(fromBinary.status); - fromRest.roles.length.should.equal(fromBinary.roles.length); - }); - }); - - it('should load a record with a fetch plan', function () { - var params = { - sessionId: -1, - database: 'testdb_rest_record_load', - cluster: 5, - position: 0, - fetchPlan: 'roles:1' - }; - return Promise.all([REST_SERVER.send('record-load', params), this.db.send('record-load', params)]) - .spread(function (fromRest, fromBinary) { - fromRest.records[0]['@rid'].should.eql(fromBinary.records[0]['@rid']); - fromRest.records[0]['@version'].should.equal(fromBinary.records[0]['@version']); - fromRest.records[0]['@type'].should.equal(fromBinary.records[0]['@type']); - fromRest.records[0].name.should.equal(fromBinary.records[0].name); - fromRest.records[0].status.should.equal(fromBinary.records[0].status); - fromRest.records[0].roles.length.should.equal(fromBinary.records[0].roles.length); - fromRest.records[0].roles[0]['@rid'].should.eql(fromBinary.records[0].roles[0]); - fromRest.records[0].roles[0]['@rid'].should.eql(fromBinary.records[1]['@rid']); - fromRest.records[0].roles[0].name.should.eql(fromBinary.records[1].name); - }); - }); -}); - - -function pluck (key) { - return function (item) { - return item[key]; - } -} \ No newline at end of file diff --git a/test/transport/rest/rest-transport-test.js b/test/transport/rest/rest-transport-test.js index 9c745d7..f25b936 100644 --- a/test/transport/rest/rest-transport-test.js +++ b/test/transport/rest/rest-transport-test.js @@ -1,4 +1,6 @@ var errors = LIB.errors; +var Promise = require('bluebird'); + describe("Rest Transport", function () { describe('RestTransport::send()', function () { it("should handle errors correctly", function () { @@ -17,4 +19,103 @@ describe("Rest Transport", function () { }); }) }); + + describe('REST Operations', function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_rest', 'plocal'); + }); + after(function () { + return DELETE_TEST_DB('testdb_rest', 'plocal'); + }); + + + describe('Record Load', function () { + it('should load a record', function () { + var params = { + sessionId: -1, + database: 'testdb_rest', + cluster: 5, + position: 0 + }; + return Promise.all([REST_SERVER.send('record-load', params), this.db.send('record-load', params)]) + .spread(function (fromRest, fromBinary) { + fromRest = fromRest.records[0]; + fromBinary = fromBinary.records[0]; + fromRest['@rid'].should.eql(fromBinary['@rid']); + fromRest['@version'].should.equal(fromBinary['@version']); + fromRest['@type'].should.equal(fromBinary['@type']); + fromRest.name.should.equal(fromBinary.name); + fromRest.status.should.equal(fromBinary.status); + fromRest.roles.length.should.equal(fromBinary.roles.length); + }); + }); + + it('should load a record with a fetch plan', function () { + var params = { + sessionId: -1, + database: 'testdb_rest', + cluster: 5, + position: 0, + fetchPlan: 'roles:1' + }; + return Promise.all([REST_SERVER.send('record-load', params), this.db.send('record-load', params)]) + .spread(function (fromRest, fromBinary) { + fromRest.records[0]['@rid'].should.eql(fromBinary.records[0]['@rid']); + fromRest.records[0]['@version'].should.equal(fromBinary.records[0]['@version']); + fromRest.records[0]['@type'].should.equal(fromBinary.records[0]['@type']); + fromRest.records[0].name.should.equal(fromBinary.records[0].name); + fromRest.records[0].status.should.equal(fromBinary.records[0].status); + fromRest.records[0].roles.length.should.equal(fromBinary.records[0].roles.length); + fromRest.records[0].roles[0]['@rid'].should.eql(fromBinary.records[0].roles[0]); + fromRest.records[0].roles[0]['@rid'].should.eql(fromBinary.records[1]['@rid']); + fromRest.records[0].roles[0].name.should.eql(fromBinary.records[1].name); + }); + }); + }); + + + describe('Db Open', function () { + it('should open the database', function () { + var params = { + sessionId: -1, + name: 'testdb_rest', + type: 'graph', + username: 'admin', + password: 'admin' + }; + return Promise.all([REST_SERVER.send('db-open', params), TEST_SERVER.send('db-open', params)]) + .spread(function (fromRest, fromBinary) { + fromRest.release.should.equal(fromBinary.release); + fromRest.totalClusters.should.equal(fromBinary.totalClusters); + fromRest.clusters.length.should.equal(fromBinary.clusters.length); + fromRest.clusters.map(pluck('name')).sort().should.eql(fromBinary.clusters.map(pluck('name')).sort()); + }); + }) + }); + + + describe('Command', function () { + it('should execute a query', function () { + var config = { + database: 'testdb_rest', + class: 'com.orientechnologies.orient.core.sql.query.OSQLSynchQuery', + limit: 2, + query: 'SELECT * FROM OUser', + mode: 's' + }; + return Promise.all([REST_SERVER.send('command', config), this.db.send('command', config)]) + .spread(function (fromRest, fromBinary) { + fromRest.results.length.should.equal(fromBinary.results.length); + fromRest.results[0].content.length.should.equal(fromBinary.results[0].content.length); + }); + }); + }); + + + function pluck (key) { + return function (item) { + return item[key]; + } + } + }); }); \ No newline at end of file From 8e207a012b659d031d8cfc65a73a56f3978ed20e Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 2 Dec 2014 12:07:55 +0000 Subject: [PATCH 164/308] use plocal for tests because of orientdb issue 3118 --- test/bugs/27-slow.js | 4 ++-- test/index.js | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/test/bugs/27-slow.js b/test/bugs/27-slow.js index 6fc870a..68485cf 100644 --- a/test/bugs/27-slow.js +++ b/test/bugs/27-slow.js @@ -4,7 +4,7 @@ describe("Bug #27: Slow compared to Restful API", function () { this.timeout(10 * 10000); var LIMIT = 5000; before(function () { - return CREATE_TEST_DB(this, 'testdb_bug_27_slow') + return CREATE_TEST_DB(this, 'testdb_bug_27_slow', 'plocal') .bind(this) .then(function () { return this.db.class.create('School', 'V'); @@ -41,7 +41,7 @@ describe("Bug #27: Slow compared to Restful API", function () { }); }); after(function () { - return DELETE_TEST_DB('testdb_bug_27_slow'); + return DELETE_TEST_DB('testdb_bug_27_slow', 'plocal'); }); it('should load a lot of records quickly, using the binary raw command interface', function () { diff --git a/test/index.js b/test/index.js index b85384e..5cc2d67 100644 --- a/test/index.js +++ b/test/index.js @@ -43,8 +43,6 @@ global.DELETE_TEST_DB = deleteTestDb.bind(null, TEST_SERVER); global.CREATE_REST_DB = createTestDb.bind(null, REST_SERVER); global.DELETE_REST_DB = deleteTestDb.bind(null, REST_SERVER); - - function createTestDb(server, context, name, type) { type = type || 'memory'; return server.exists(name, type) From 5726b9f56ad581d370cf6918b91cb14e993ee8fb Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 2 Dec 2014 12:12:34 +0000 Subject: [PATCH 165/308] update travis to latest 1.7.10 and add 2.0-SNAPSHOT --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b3055d2..ee97a27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,5 @@ node_js: before_script: - ./ci/initialize-ci.sh $ORIENTDB_VERSION env: - - ORIENTDB_VERSION=1.7.8 + - ORIENTDB_VERSION=1.7.10 + - ORIENTDB_VERSION=2.0-SNAPSHOT From 6def2a228cf59201c8e3cded537fd3da0596943b Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 2 Dec 2014 12:15:54 +0000 Subject: [PATCH 166/308] fix behavior change test --- test/core/bag-test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/core/bag-test.js b/test/core/bag-test.js index 9b35e01..947e5d8 100644 --- a/test/core/bag-test.js +++ b/test/core/bag-test.js @@ -146,7 +146,10 @@ describe("RID Bag", function () { this.bag.should.be.an.instanceOf(LIB.Bag) this.bag.type.should.equal(LIB.Bag.BAG_TREE); expect(this.bag.uuid).to.equal(null); - this.bag.size.should.equal(-1); // Tree based, so must manually ask the server + // > note: following behavior changes since protocol 19 + // old versions return the number of records, newer ones don't. + // newer versions must ask orient + expect(this.bag.size === -1 || this.bag.size === 120).to.be.true; }); }); }); From c7abdee4f04dc4332e3d9f3a9d818af2e853c9db Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 2 Dec 2014 12:21:48 +0000 Subject: [PATCH 167/308] rest api bodge --- test/bugs/27-slow.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/bugs/27-slow.js b/test/bugs/27-slow.js index 68485cf..823aab4 100644 --- a/test/bugs/27-slow.js +++ b/test/bugs/27-slow.js @@ -108,7 +108,8 @@ describe("Bug #27: Slow compared to Restful API", function () { }) }); - it('should load a lot of records, one at a time, using rest', function () { + // skip the following because orientdb hangs up the socket. + it.skip('should load a lot of records, one at a time, using rest', function () { var start = Date.now(); var cluster = this.class.defaultClusterId, promises = [], From 037efada641fc81327d91b00ebaaa2fee08be108 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 2 Dec 2014 12:28:23 +0000 Subject: [PATCH 168/308] temporarily downgrade travis to orientdb 1.7.8 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ee97a27..843cc40 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,5 +4,5 @@ node_js: before_script: - ./ci/initialize-ci.sh $ORIENTDB_VERSION env: - - ORIENTDB_VERSION=1.7.10 + - ORIENTDB_VERSION=1.7.8 - ORIENTDB_VERSION=2.0-SNAPSHOT From 4dd1db184fc0bbf2ca020b8b2bd1ad9d3a278877 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 2 Dec 2014 12:36:06 +0000 Subject: [PATCH 169/308] use a different config file for 2.0 --- .travis.yml | 2 +- ci/initialize-ci.sh | 6 ++- ci/orientdb-server-config-1.7.xml | 76 +++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 ci/orientdb-server-config-1.7.xml diff --git a/.travis.yml b/.travis.yml index 843cc40..ee97a27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,5 +4,5 @@ node_js: before_script: - ./ci/initialize-ci.sh $ORIENTDB_VERSION env: - - ORIENTDB_VERSION=1.7.8 + - ORIENTDB_VERSION=1.7.10 - ORIENTDB_VERSION=2.0-SNAPSHOT diff --git a/ci/initialize-ci.sh b/ci/initialize-ci.sh index f836425..9294b6a 100755 --- a/ci/initialize-ci.sh +++ b/ci/initialize-ci.sh @@ -22,7 +22,11 @@ if [ ! -d "$ODB_DIR" ]; then echo "--- Setting up OrientDB ---" chmod +x $ODB_LAUNCHER chmod -R +rw "${ODB_DIR}/config/" - cp $PARENT_DIR/ci/orientdb-server-config.xml "${ODB_DIR}/config/" + if [[ $ODB_VERSION == *"1.7"* ]]; then + cp $PARENT_DIR/ci/orientdb-server-config-1.7.xml "${ODB_DIR}/config/orientdb-server-config.xml" + else + cp $PARENT_DIR/ci/orientdb-server-config.xml "${ODB_DIR}/config/" + fi cp $PARENT_DIR/ci/orientdb-server-log.properties "${ODB_DIR}/config/" else echo "!!! Found OrientDB v${ODB_VERSION} in ${ODB_DIR} !!!" diff --git a/ci/orientdb-server-config-1.7.xml b/ci/orientdb-server-config-1.7.xml new file mode 100644 index 0000000..2924c11 --- /dev/null +++ b/ci/orientdb-server-config-1.7.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From bae009c98c2b4645677cc50f02f8991ea29ba51a Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 2 Dec 2014 12:47:05 +0000 Subject: [PATCH 170/308] prepare 0.6.0 [ci skip] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4299b26..557fbc8 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node", "node.js" ], - "version": "0.5.2", + "version": "0.6.0", "author": { "name": "Charles Pick", "email": "charles@codemix.com" From 6608121afa8af67c752f63bf772b504a91f9d349 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 2 Dec 2014 14:08:26 +0000 Subject: [PATCH 171/308] add selective disabling of ridbags --- lib/transport/binary/connection.js | 4 ++++ lib/transport/binary/index.js | 4 ++++ lib/transport/binary/protocol19/deserializer.js | 9 +++++++-- lib/transport/binary/protocol26/deserializer.js | 13 +++++++++---- test/core/bag-test.js | 12 ++++++++++++ 5 files changed, 36 insertions(+), 6 deletions(-) diff --git a/lib/transport/binary/connection.js b/lib/transport/binary/connection.js index 23a9d61..75b6499 100644 --- a/lib/transport/binary/connection.js +++ b/lib/transport/binary/connection.js @@ -17,6 +17,9 @@ function Connection (config) { this.logger = config.logger || {debug: function () {}}; this.setMaxListeners(Infinity); + + this.enableRIDBags = config.enableRIDBags || true; + this.protocol = null; this.queue = []; this.writes = []; @@ -195,6 +198,7 @@ Connection.prototype.negotiateProtocol = function () { else { this.protocol = require('./protocol19'); } + this.protocol.deserializer.enableRIDBags = this.enableRIDBags; resolve(this); this.connecting = false; this.bindToSocket(); diff --git a/lib/transport/binary/index.js b/lib/transport/binary/index.js index e59da6a..d0a48de 100644 --- a/lib/transport/binary/index.js +++ b/lib/transport/binary/index.js @@ -46,6 +46,8 @@ BinaryTransport.prototype.configure = function (config) { this.username = config.username || 'root'; this.password = config.password || ''; + this.enableRIDBags = config.enableRIDBags || true; + this.sessionId = -1; this.configureLogger(config.logger || {}); if (config.pool) { @@ -81,6 +83,7 @@ BinaryTransport.prototype.configureConnection = function () { this.connection = new Connection({ host: this.host, port: this.port, + enableRIDBags: this.enableRIDBags, logger: this.logger }); this.connection.on('update-config', function (config) { @@ -109,6 +112,7 @@ BinaryTransport.prototype.configurePool = function (config) { this.pool = new ConnectionPool({ host: this.host, port: this.port, + enableRIDBags: this.enableRIDBags, logger: this.logger, max: config.max }); diff --git a/lib/transport/binary/protocol19/deserializer.js b/lib/transport/binary/protocol19/deserializer.js index 68b3bc0..52e918a 100644 --- a/lib/transport/binary/protocol19/deserializer.js +++ b/lib/transport/binary/protocol19/deserializer.js @@ -491,7 +491,12 @@ function eatBag (input) { } input = input.slice(i + 1); - return [new Bag(collected), input]; + if (exports.enableRIDBags) { + return [new Bag(collected), input]; + } + else { + return [new Bag(collected).all(), input]; + } } @@ -521,7 +526,7 @@ function eatBinary (input) { } - +exports.enableRIDBags = true; exports.deserialize = deserialize; exports.eatKey = eatKey; exports.eatValue = eatValue; diff --git a/lib/transport/binary/protocol26/deserializer.js b/lib/transport/binary/protocol26/deserializer.js index 68b3bc0..665ab39 100644 --- a/lib/transport/binary/protocol26/deserializer.js +++ b/lib/transport/binary/protocol26/deserializer.js @@ -230,8 +230,8 @@ function eatNumber (input) { num = input.match(pattern); if (num) { - collected = num[0]; - i = collected.length; + collected = num[0]; + i = collected.length; } collected = +collected; @@ -491,7 +491,12 @@ function eatBag (input) { } input = input.slice(i + 1); - return [new Bag(collected), input]; + if (exports.enableRIDBags) { + return [new Bag(collected), input]; + } + else { + return [new Bag(collected).all(), input]; + } } @@ -521,7 +526,7 @@ function eatBinary (input) { } - +exports.enableRIDBags = true; exports.deserialize = deserialize; exports.eatKey = eatKey; exports.eatValue = eatValue; diff --git a/test/core/bag-test.js b/test/core/bag-test.js index 947e5d8..b3622df 100644 --- a/test/core/bag-test.js +++ b/test/core/bag-test.js @@ -86,6 +86,18 @@ describe("RID Bag", function () { item.should.have.property('@rid'); }); }); + + describe('Optional RIDBags', function () { + before(function () { + this.db.server.transport.connection.protocol.deserializer.enableRIDBags = false; + }); + after(function () { + this.db.server.transport.connection.protocol.deserializer.enableRIDBags = true; + }); + it('should optionally disable RIDBags', function () { + Array.isArray(this.bag).should.be.true; + }); + }); }); describe('Tree Bag', function () { From 95eb77c1b0712f37294911e4cff69bd48830ea7a Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 2 Dec 2014 19:57:28 +0000 Subject: [PATCH 172/308] fix silly bug --- lib/transport/binary/connection.js | 2 +- lib/transport/binary/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/transport/binary/connection.js b/lib/transport/binary/connection.js index 75b6499..c3b64f6 100644 --- a/lib/transport/binary/connection.js +++ b/lib/transport/binary/connection.js @@ -18,7 +18,7 @@ function Connection (config) { this.setMaxListeners(Infinity); - this.enableRIDBags = config.enableRIDBags || true; + this.enableRIDBags = config.enableRIDBags == null ? true : config.enableRIDBags; this.protocol = null; this.queue = []; diff --git a/lib/transport/binary/index.js b/lib/transport/binary/index.js index d0a48de..ebacedd 100644 --- a/lib/transport/binary/index.js +++ b/lib/transport/binary/index.js @@ -46,7 +46,7 @@ BinaryTransport.prototype.configure = function (config) { this.username = config.username || 'root'; this.password = config.password || ''; - this.enableRIDBags = config.enableRIDBags || true; + this.enableRIDBags = config.enableRIDBags == null ? true : config.enableRIDBags; this.sessionId = -1; this.configureLogger(config.logger || {}); From 99ba9df36b92d706f8da22c543d5589c06188654 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 4 Dec 2014 22:17:06 +0000 Subject: [PATCH 173/308] fix utils.escape() --- lib/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 7c06567..1624b77 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -125,7 +125,7 @@ exports.clone = function (item) { * @return {String} The escaped input. */ exports.escape = function (input) { - return ('' + input).replace(/([^'\\]*(?:\\.[^'\\]*)*)'/g, '$1\\"'); + return ('' + input).replace(/([^'\\]*(?:\\.[^'\\]*)*)'/g, "$1\\'").replace(/([^"\\]*(?:\\.[^"\\]*)*)"/g, '$1\\"'); }; /** From d8985dd6c85f11404385d71d27b091ec43320fdb Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 4 Dec 2014 22:24:10 +0000 Subject: [PATCH 174/308] resolve nested references --- lib/db/index.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/db/index.js b/lib/db/index.js index db36579..55c1e95 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -239,6 +239,11 @@ Db.prototype.query = function (command, options) { }) .spread(function (results, preloaded) { if (preloaded && preloaded.length) { + preloaded.forEach(function (result) { + if (result && result['@rid']) { + this.record.resolveReferences(result, preloaded); + } + }, this); return results.map(function (result) { if (result && result['@rid']) { return this.record.resolveReferences(result, preloaded); From 7034c9c5d988b832f351c6f4a46b1fcec7e5a2e4 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 5 Dec 2014 11:18:24 +0000 Subject: [PATCH 175/308] disprove #155 --- test/bugs/155-param-substitution.js | 65 +++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 test/bugs/155-param-substitution.js diff --git a/test/bugs/155-param-substitution.js b/test/bugs/155-param-substitution.js new file mode 100644 index 0000000..92cbc70 --- /dev/null +++ b/test/bugs/155-param-substitution.js @@ -0,0 +1,65 @@ +var Statement = require('../../lib/db/statement'); + +describe("Bug #155: db.query() parameters substitution fails for string represents number", function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_155') + .bind(this) + .then(function () { + return this.db.class.create('SomeClass'); + }) + .then(function (item) { + this.class = item; + return this.class.property.create({ + name: 'val', + type: 'string' + }); + }) + .then(function () { + return this.db.insert().into('SomeClass').set({ + val: 123456 + }) + .one(); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_155'); + }); + it('should find by integer value, using query builder', function () { + return this.db.select().from('SomeClass').where({ + val: 123456 + }) + .one() + .then(function (doc) { + doc.val.should.equal('123456'); + }); + }); + it('should find by string value, using query builder', function () { + return this.db.select().from('SomeClass').where({ + val: '123456' + }) + .one() + .then(function (doc) { + doc.val.should.equal('123456'); + }); + }); + it('should find by integer value, using bound params', function () { + return this.db.query('SELECT FROM SomeClass WHERE val = :val', { + params: { + val: 123456 + } + }) + .spread(function (doc) { + doc.val.should.equal('123456'); + }); + }); + it('should find by string value, using bound params', function () { + return this.db.query('SELECT FROM SomeClass WHERE val = :val', { + params: { + val: '123456' + } + }) + .spread(function (doc) { + doc.val.should.equal('123456'); + }); + }); +}); \ No newline at end of file From b24f48b6dfc56440028c2035adc8282e5e9f60f1 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 8 Dec 2014 00:30:06 +0000 Subject: [PATCH 176/308] fix resolveReferences() --- lib/db/index.js | 20 ++---------------- lib/db/record.js | 53 ++++++++++++++++++++++++------------------------ 2 files changed, 29 insertions(+), 44 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index 55c1e95..51fc9fa 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -238,24 +238,8 @@ Db.prototype.query = function (command, options) { }, [[], []]); }) .spread(function (results, preloaded) { - if (preloaded && preloaded.length) { - preloaded.forEach(function (result) { - if (result && result['@rid']) { - this.record.resolveReferences(result, preloaded); - } - }, this); - return results.map(function (result) { - if (result && result['@rid']) { - return this.record.resolveReferences(result, preloaded); - } - else { - return result; - } - }, this); - } - else { - return results; - } + this.record.resolveReferences(results.concat(preloaded)); + return results; }); }; diff --git a/lib/db/record.js b/lib/db/record.js index 5bebcf5..55792b3 100644 --- a/lib/db/record.js +++ b/lib/db/record.js @@ -102,61 +102,62 @@ exports.get = function (record, options) { return response.records[0]; } else { - return this.record.resolveReferences(response.records[0], response.records.slice(1)); + this.record.resolveReferences(response.records); + return response.records[0]; } }); }; /** - * Resolve references to child records for the given record. + * Resolve all references within the given collection of records. * - * @param {Object} record The primary record. - * @param {Object[]} children The child records. - * @return {Object} The primary record with references replaced. + * @param {Object[]} records The records to resolve. + * @return {Object} The records with references replaced. */ -exports.resolveReferences = function (record, children) { - var total = children.length, - indexed = {}, - replaceRecordIds = recordIdResolver(), - child, i; +exports.resolveReferences = function (records) { + var total = records.length, + collated = {}, + i; for (i = 0; i < total; i++) { - child = children[i]; - indexed[child['@rid']] = child; + if (records[i]) { + collated[records[i]['@rid']] = records[i]; + } } + var replaceRecordIds = ridReplacer(collated); + for (i = 0; i < total; i++) { - child = children[i]; - replaceRecordIds(indexed, child); + replaceRecordIds(records[i]); } - return replaceRecordIds(indexed, record); + return records; }; -function recordIdResolver () { + +function ridReplacer (collated) { var seen = {}; return replaceRecordIds; /** * Replace references to records with their instances, where possible. * - * @param {Object} records The map of record ids to record instances. * @param {Object} obj The object to replace references within * @return {Object} The object with references replaced. */ - function replaceRecordIds (records, obj) { + function replaceRecordIds (obj) { if (!obj || typeof obj !== 'object') { return obj; } else if (Array.isArray(obj)) { /*jshint validthis:true */ - return obj.map(replaceRecordIds.bind(this, records)); + return obj.map(replaceRecordIds); } else if (obj instanceof RIDBag) { - obj._prefetchedRecords = records; + obj._prefetchedRecords = collated; return obj; } - else if (obj instanceof RID && records[obj]) { - return records[obj]; + else if (obj instanceof RID && collated[obj]) { + return collated[obj]; } if (obj['@rid']) { if (seen[obj['@rid']]) { @@ -177,15 +178,15 @@ function recordIdResolver () { continue; } if (value instanceof RID) { - if (records[value]) { - obj[key] = records[value]; + if (collated[value]) { + obj[key] = collated[value]; } } else if (Array.isArray(value)) { - obj[key] = value.map(replaceRecordIds.bind(this, records)); + obj[key] = value.map(replaceRecordIds); } else { - obj[key] = replaceRecordIds(records, value); + obj[key] = replaceRecordIds(value); } } return obj; From 11debeb249e2b41d84292644169f698dc6687a96 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 8 Dec 2014 00:30:27 +0000 Subject: [PATCH 177/308] add safe JSON.stringify() substitute --- lib/index.js | 2 ++ lib/utils.js | 29 ++++++++++++++++++++++++++++- test/core/bag-test.js | 4 +++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/index.js b/lib/index.js index 1080c4e..732b8f7 100755 --- a/lib/index.js +++ b/lib/index.js @@ -12,6 +12,8 @@ Oriento.transport = require('./transport'); Oriento.errors = require('./errors'); Oriento.Migration = require('./migration'); Oriento.CLI = require('./cli'); +Oriento.utils = require('./utils'); +Oriento.jsonify = Oriento.utils.jsonify; /** * A list of orientdb data types, indexed by their type id. diff --git a/lib/utils.js b/lib/utils.js index 1624b77..68623ba 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,6 +1,7 @@ "use strict"; -var RID = require('./recordid'); +var RID = require('./recordid'), + Bag = require('./bag'); /** * Make it easy to extend classes. @@ -178,4 +179,30 @@ exports.encode = function (value) { else { return JSON.stringify(value); } +}; + + +/** + * Safely encode a value as JSON, allowing circular references. + * When a record is encountered more than once, subsequent references + * will embed the record's RID rather than the record itself. + * + * @param {Mixed} value The value to JSON stringify. + * @param {Integer} indentlevel The indentation level, if specified the JSON will be pretty printed. + * @return {String} The JSON string. + */ +exports.jsonify = function (value, indentlevel) { + var seen = []; + return JSON.stringify(value, function (key, value) { + if (value && typeof value === 'object') { + if (~seen.indexOf(value)) { + if (value['@rid']) { + return value['@rid'].toJSON(); + } + return; + } + seen.push(value); + } + return value; + }, indentlevel); }; \ No newline at end of file diff --git a/test/core/bag-test.js b/test/core/bag-test.js index b3622df..af0cca0 100644 --- a/test/core/bag-test.js +++ b/test/core/bag-test.js @@ -1,3 +1,5 @@ +var utils = require('../../lib/utils'); + describe("RID Bag", function () { describe('Embedded Bag', function () { before(function () { @@ -79,7 +81,7 @@ describe("RID Bag", function () { }); it('should return the right JSON representation', function () { - var json = JSON.stringify(this.bag) + var json = utils.jsonify(this.bag) decoded = JSON.parse(json); decoded.length.should.equal(10); decoded.forEach(function (item) { From c0b4c8618ab7d7ad2cef0a07b31c8579df2d98ab Mon Sep 17 00:00:00 2001 From: Garrett Gottlieb Date: Mon, 8 Dec 2014 20:04:31 -0500 Subject: [PATCH 178/308] fix for empty results from query --- lib/db/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index 51fc9fa..b6d98f3 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -220,8 +220,8 @@ Db.prototype.query = function (command, options) { return this.exec(command, options) .bind(this) .then(function (response) { - if (response.results.length === 0) { - return []; + if (!response.results || response.results.length === 0) { + return [[], []]; } return response.results .map(this.normalizeResult, this) @@ -440,4 +440,4 @@ function flatten (list, item) { list.push(item); } return list; -} \ No newline at end of file +} From f6beb0825453103da3fd4ad776e1464de0c5aa16 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 10 Dec 2014 15:01:16 +0000 Subject: [PATCH 179/308] preliminary JWT support for protocol v28 --- ci/orientdb-server-config.xml | 143 +-- lib/db/index.js | 7 +- lib/db/statement.js | 18 + lib/transport/binary/connection.js | 5 +- lib/transport/binary/index.js | 13 +- lib/transport/binary/protocol28/constants.js | 20 + .../binary/protocol28/deserializer.js | 541 +++++++++++ lib/transport/binary/protocol28/index.js | 7 + .../binary/protocol28/operation-queue.js | 185 ++++ lib/transport/binary/protocol28/operation.js | 900 ++++++++++++++++++ .../binary/protocol28/operations/command.js | 179 ++++ .../protocol28/operations/config-get.js | 19 + .../protocol28/operations/config-list.js | 29 + .../protocol28/operations/config-set.js | 22 + .../binary/protocol28/operations/connect.js | 33 + .../protocol28/operations/datacluster-add.js | 20 + .../operations/datacluster-count.js | 33 + .../operations/datacluster-datarange.js | 22 + .../protocol28/operations/datacluster-drop.js | 21 + .../binary/protocol28/operations/db-close.js | 13 + .../protocol28/operations/db-countrecords.js | 17 + .../binary/protocol28/operations/db-create.js | 19 + .../binary/protocol28/operations/db-delete.js | 18 + .../binary/protocol28/operations/db-exists.js | 22 + .../binary/protocol28/operations/db-freeze.js | 18 + .../binary/protocol28/operations/db-list.js | 19 + .../binary/protocol28/operations/db-open.js | 51 + .../protocol28/operations/db-release.js | 18 + .../binary/protocol28/operations/db-reload.js | 30 + .../binary/protocol28/operations/db-size.js | 17 + .../binary/protocol28/operations/index.js | 34 + .../protocol28/operations/record-clean-out.js | 34 + .../protocol28/operations/record-create.js | 50 + .../protocol28/operations/record-delete.js | 45 + .../protocol28/operations/record-load.js | 119 +++ .../protocol28/operations/record-metadata.js | 34 + .../protocol28/operations/record-update.js | 62 ++ .../binary/protocol28/operations/tx-commit.js | 112 +++ lib/transport/binary/protocol28/serializer.js | 137 +++ lib/transport/binary/protocol28/writer.js | 123 +++ test/core/jwt.js | 25 + test/server/server-test.js | 127 +-- 42 files changed, 3198 insertions(+), 163 deletions(-) create mode 100644 lib/transport/binary/protocol28/constants.js create mode 100644 lib/transport/binary/protocol28/deserializer.js create mode 100644 lib/transport/binary/protocol28/index.js create mode 100644 lib/transport/binary/protocol28/operation-queue.js create mode 100644 lib/transport/binary/protocol28/operation.js create mode 100644 lib/transport/binary/protocol28/operations/command.js create mode 100644 lib/transport/binary/protocol28/operations/config-get.js create mode 100644 lib/transport/binary/protocol28/operations/config-list.js create mode 100644 lib/transport/binary/protocol28/operations/config-set.js create mode 100644 lib/transport/binary/protocol28/operations/connect.js create mode 100644 lib/transport/binary/protocol28/operations/datacluster-add.js create mode 100644 lib/transport/binary/protocol28/operations/datacluster-count.js create mode 100644 lib/transport/binary/protocol28/operations/datacluster-datarange.js create mode 100644 lib/transport/binary/protocol28/operations/datacluster-drop.js create mode 100644 lib/transport/binary/protocol28/operations/db-close.js create mode 100644 lib/transport/binary/protocol28/operations/db-countrecords.js create mode 100644 lib/transport/binary/protocol28/operations/db-create.js create mode 100644 lib/transport/binary/protocol28/operations/db-delete.js create mode 100644 lib/transport/binary/protocol28/operations/db-exists.js create mode 100644 lib/transport/binary/protocol28/operations/db-freeze.js create mode 100644 lib/transport/binary/protocol28/operations/db-list.js create mode 100644 lib/transport/binary/protocol28/operations/db-open.js create mode 100644 lib/transport/binary/protocol28/operations/db-release.js create mode 100644 lib/transport/binary/protocol28/operations/db-reload.js create mode 100644 lib/transport/binary/protocol28/operations/db-size.js create mode 100644 lib/transport/binary/protocol28/operations/index.js create mode 100644 lib/transport/binary/protocol28/operations/record-clean-out.js create mode 100644 lib/transport/binary/protocol28/operations/record-create.js create mode 100644 lib/transport/binary/protocol28/operations/record-delete.js create mode 100644 lib/transport/binary/protocol28/operations/record-load.js create mode 100644 lib/transport/binary/protocol28/operations/record-metadata.js create mode 100644 lib/transport/binary/protocol28/operations/record-update.js create mode 100644 lib/transport/binary/protocol28/operations/tx-commit.js create mode 100644 lib/transport/binary/protocol28/serializer.js create mode 100644 lib/transport/binary/protocol28/writer.js create mode 100644 test/core/jwt.js diff --git a/ci/orientdb-server-config.xml b/ci/orientdb-server-config.xml index 4a9183a..df603d2 100644 --- a/ci/orientdb-server-config.xml +++ b/ci/orientdb-server-config.xml @@ -1,78 +1,56 @@ - - - - + + - - - - - - + + + + - - - + + - - - - - - - - - - - - + + + + + + + + - - + + + + + + + + - + + + + + + - @@ -96,67 +74,42 @@ - - - + + - + - - - - - - - - - + - + - - - - + + - + + + + - - - - + - - - - - - - - - - - + + + + + + diff --git a/lib/db/index.js b/lib/db/index.js index b6d98f3..1289625 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -47,7 +47,7 @@ Db.prototype.configure = function (config) { this.type = (config.type === 'document' ? 'document' : 'graph'); this.storage = ((config.storage === 'plocal' || config.storage === 'memory') ? config.storage : 'plocal'); - + this.token = null; this.username = config.username || 'admin'; this.password = config.password || 'admin'; this.dataSegments = []; @@ -77,7 +77,8 @@ Db.prototype.open = function () { name: this.name, type: this.type, username: this.username, - password: this.password + password: this.password, + useToken: this.server.useToken }) .bind(this) .then(function (response) { @@ -85,6 +86,7 @@ Db.prototype.open = function () { this.sessionId = response.sessionId; this.cluster.cacheData(response.clusters); this.serverCluster = response.serverCluster; + this.token = response.token; this.server.once('error', function () { this.sessionId = null; }.bind(this)); @@ -119,6 +121,7 @@ Db.prototype.send = function (operation, data) { .bind(this) .then(function () { data = data || {}; + data.token = data.token || this.token; data.sessionId = this.sessionId; data.database = this.name; this.server.logger.debug('sending operation ' + operation + ' for database ' + this.name); diff --git a/lib/db/statement.js b/lib/db/statement.js index 5f48d07..e4a825a 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -274,6 +274,21 @@ Statement.prototype.addParams = function (params) { }; +/** + * Use a particular Auth Token for this statement. + * + * @param {String} token The token to use + * @return {Statement} The statement object. + */ +Statement.prototype.token = function (token) { + if (typeof token === 'string') { + token = new Buffer(token, 'base64'); + } + this._state.token = token || false; + return this; +}; + + /** * Build the statement. * @return {String} The SQL statement. @@ -556,6 +571,9 @@ Statement.prototype.buildOptions = function () { if (this._state.commit !== undefined) { opts.class = 's'; } + if (this._state.token !== undefined) { + opts.token = this._state.token; + } return opts; }; diff --git a/lib/transport/binary/connection.js b/lib/transport/binary/connection.js index c3b64f6..41a356b 100644 --- a/lib/transport/binary/connection.js +++ b/lib/transport/binary/connection.js @@ -192,7 +192,10 @@ Connection.prototype.negotiateProtocol = function () { this.socket.removeAllListeners('error'); this.protocolVersion = data.readUInt16BE(0); this.logger.debug('server protocol: ' + this.protocolVersion); - if (this.protocolVersion >= 26) { + if (this.protocolVersion >= 28) { + this.protocol = require('./protocol28'); + } + else if (this.protocolVersion === 26) { this.protocol = require('./protocol26'); } else { diff --git a/lib/transport/binary/index.js b/lib/transport/binary/index.js index ebacedd..113ba98 100644 --- a/lib/transport/binary/index.js +++ b/lib/transport/binary/index.js @@ -47,6 +47,8 @@ BinaryTransport.prototype.configure = function (config) { this.password = config.password || ''; this.enableRIDBags = config.enableRIDBags == null ? true : config.enableRIDBags; + this.useToken = config.useToken || false; + this.token = config.token || null; this.sessionId = -1; this.configureLogger(config.logger || {}); @@ -84,7 +86,8 @@ BinaryTransport.prototype.configureConnection = function () { host: this.host, port: this.port, enableRIDBags: this.enableRIDBags, - logger: this.logger + logger: this.logger, + useToken: this.useToken }); this.connection.on('update-config', function (config) { this.logger.debug('updating config...'); @@ -141,12 +144,14 @@ BinaryTransport.prototype.connect = function () { this.connecting = (this.pool || this.connection).send('connect', { username: this.username, - password: this.password + password: this.password, + useToken: this.useToken }) .bind(this) .then(function (response) { this.logger.debug('got session id: ' + response.sessionId); this.sessionId = response.sessionId; + this.token = response.token; this.retries = 0; return this; }); @@ -163,6 +168,9 @@ BinaryTransport.prototype.connect = function () { */ BinaryTransport.prototype.send = function (operation, options) { options = options || {}; + if (!options.token && this.useToken && this.token) { + options.token = this.token; + } if (~this.sessionId || options.sessionId != null) { options.sessionId = options.sessionId != null ? options.sessionId : this.sessionId; return (this.pool || this.connection).send(operation, options); @@ -170,6 +178,7 @@ BinaryTransport.prototype.send = function (operation, options) { else { return this.connect() .then(function (server) { + options.token = this.token; options.sessionId = options.sessionId != null ? options.sessionId : this.sessionId; return (server.pool || server.connection).send(operation, options); }); diff --git a/lib/transport/binary/protocol28/constants.js b/lib/transport/binary/protocol28/constants.js new file mode 100644 index 0000000..01d0314 --- /dev/null +++ b/lib/transport/binary/protocol28/constants.js @@ -0,0 +1,20 @@ +"use strict"; + +exports.PROTOCOL_VERSION = 28; + +exports.BYTES_LONG = 8; +exports.BYTES_INT = 4; +exports.BYTES_SHORT = 2; +exports.BYTES_BYTE = 1; + +exports.RECORD_TYPES = { + 'd': 100, + 'b': 98, + 'f': 102, + + // duplicated as upper case for fast lookup + + 'D': 100, + 'B': 98, + 'F': 102, +}; \ No newline at end of file diff --git a/lib/transport/binary/protocol28/deserializer.js b/lib/transport/binary/protocol28/deserializer.js new file mode 100644 index 0000000..665ab39 --- /dev/null +++ b/lib/transport/binary/protocol28/deserializer.js @@ -0,0 +1,541 @@ +"use strict"; + +var RID = require('../../../recordid'), + Bag = require('../../../bag'); + +/** + * Deserialize the given record and return an object containing the values. + * + * @param {String} input The serialized record. + * @return {Object} The deserialized record. + */ +function deserialize (input) { + var record = {'@type': 'd'}, + chunk, key, value; + if (!input) { + return null; + } + chunk = eatFirstKey(input); + if (chunk[2]) { + // this is actually a class name + record['@class'] = chunk[0]; + input = chunk[1]; + chunk = eatKey(input); + key = chunk[0]; + input = chunk[1]; + } + else { + key = chunk[0]; + input = chunk[1]; + } + + // read the first value. + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + record[key] = value; + + while (input.length) { + if (input.charAt(0) === ',') { + input = input.slice(1); + } + else { + break; + } + chunk = eatKey(input); + key = chunk[0]; + input = chunk[1]; + if (input.length) { + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + record[key] = value; + } + else { + record[key] = null; + } + } + + return record; +} + +/** + * Consume the first field key, which could be a class name. + * + * @param {String} input The input to parse. + * @return {[String, String]} The collected key, and any remaining input. + */ +function eatFirstKey (input) { + var length = input.length, + collected = '', + isClassName = false, + result, c, i; + + if (input.charAt(0) === '"') { + result = eatString(input.slice(1)); + return [result[0], result[1].slice(1)]; + } + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === '@') { + isClassName = true; + break; + } + else if (c === ':') { + break; + } + else { + collected += c; + } + } + + return [collected, input.slice(i + 1), isClassName]; +} + + +/** + * Consume a field key, which may or may not be quoted. + * + * @param {String} input The input to parse. + * @return {[String, String]} The collected key, and any remaining input. + */ +function eatKey (input) { + var length = input.length, + collected = '', + result, c, i; + + if (input.charAt(0) === '"') { + result = eatString(input.slice(1)); + return [result[0], result[1].slice(1)]; + } + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === ':') { + break; + } + else { + collected += c; + } + } + + return [collected, input.slice(i + 1)]; +} + + + +/** + * Consume a field value. + * + * @param {String} input The input to parse. + * @return {[Mixed, String]} The collected value, and any remaining input. + */ +function eatValue (input) { + var c, n; + c = input.charAt(0); + while (c === ' ' && input.length) { + input = input.slice(1); + c = input.charAt(0); + } + + if (!input.length || c === ',') { + // this is a null field. + return [null, input]; + } + else if (c === '"') { + return eatString(input.slice(1)); + } + else if (c === '#') { + return eatRID(input.slice(1)); + } + else if (c === '[') { + return eatArray(input.slice(1)); + } + else if (c === '<') { + return eatSet(input.slice(1)); + } + else if (c === '{') { + return eatMap(input.slice(1)); + } + else if (c === '(') { + return eatRecord(input.slice(1)); + } + else if (c === '%') { + return eatBag(input.slice(1)); + } + else if (c === '_') { + return eatBinary(input.slice(1)); + } + else if (c === '-' || c === '0' || +c) { + return eatNumber(input); + } + else if (c === 'n' && input.slice(0, 4) === 'null') { + return [null, input.slice(4)]; + } + else if (c === 't' && input.slice(0, 4) === 'true') { + return [true, input.slice(4)]; + } + else if (c === 'f' && input.slice(0, 5) === 'false') { + return [false, input.slice(5)]; + } + else { + return [null, input]; + } +} + +/** + * Consume a string + * + * @param {String} input The input to parse. + * @return {[String, String]} The collected string, and any remaining input. + */ +function eatString (input) { + var length = input.length, + collected = '', + c, i; + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === '\\') { + // escape, skip to the next character + i++; + collected += input.charAt(i); + continue; + } + else if (c === '"') { + break; + } + else { + collected += c; + } + } + + return [collected, input.slice(i + 1)]; +} + +/** + * Consume a number. + * + * If the number has a suffix, consume it also and instantiate the right type, e.g. for dates + * + * @param {String} input The input to parse. + * @return {[Mixed, String]} The collected number, and any remaining input. + */ +function eatNumber (input) { + var length = input.length, + collected = '', + pattern = /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/, + num, c, i; + + num = input.match(pattern); + if (num) { + collected = num[0]; + i = collected.length; + } + + collected = +collected; + input = input.slice(i); + + c = input.charAt(0); + + if (c === 'a' || c === 't') { + collected = new Date(collected); + input = input.slice(1); + } + else if (c === 'b' || c === 's' || c === 'l' || c === 'f' || c == 'd' || c === 'c') { + input = input.slice(1); + } + + return [collected, input]; +} + +/** + * Consume a Record ID. + * + * @param {String} input The input to parse. + * @return {[RID, String]} The collected record id, and any remaining input. + */ +function eatRID (input) { + var length = input.length, + collected = '', + cluster, c, i; + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (cluster === undefined && c === ':') { + cluster = +collected; + collected = ''; + } + else if (c === '0' || +c) { + collected += c; + } + else { + break; + } + } + + return [new RID({cluster: cluster, position: +collected}), input.slice(i)]; +} + + +/** + * Consume an array. + * + * @param {String} input The input to parse. + * @return {[Array, String]} The collected array, and any remaining input. + */ +function eatArray (input) { + var length = input.length, + array = [], + chunk, c; + + while (input.length) { + c = input.charAt(0); + if (c === ',') { + input = input.slice(1); + } + else if (c === ']') { + input = input.slice(1); + break; + } + chunk = eatValue(input); + array.push(chunk[0]); + input = chunk[1]; + } + return [array, input]; +} + + +/** + * Consume a set. + * + * @param {String} input The input to parse. + * @return {[Array, String]} The collected set, and any remaining input. + */ +function eatSet (input) { + var length = input.length, + set = [], + chunk, c; + + while (input.length) { + c = input.charAt(0); + if (c === ',') { + input = input.slice(1); + } + else if (c === '>') { + input = input.slice(1); + break; + } + chunk = eatValue(input); + set.push(chunk[0]); + input = chunk[1]; + } + + return [set, input]; +} + +/** + * Consume a map (object). + * + * @param {String} input The input to parse. + * @return {[Object, String]} The collected map, and any remaining input. + */ +function eatMap (input) { + var length = input.length, + map = {}, + key, value, chunk, c; + + while (input.length) { + c = input.charAt(0); + if (c === ' ') { + input = input.slice(1); + continue; + } + if (c === ',') { + input = input.slice(1); + } + else if (c === '}') { + input = input.slice(1); + break; + } + + chunk = eatKey(input); + key = chunk[0]; + input = chunk[1]; + if (input.length) { + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + map[key] = value; + } + else { + map[key] = null; + } + } + + return [map, input]; +} + +/** + * Consume an embedded record. + * + * @param {String} input The input to parse. + * @return {[Object, String]} The collected record, and any remaining input. + */ +function eatRecord (input) { + var record = {'@type': 'd'}, + chunk, c, key, value; + + while (input.length) { + c = input.charAt(0); + if (c === ' ') { + input = input.slice(1); + continue; + } + else if (c === ')') { + // empty record. + input = input.slice(1); + return [record, input]; + } + else { + break; + } + } + + chunk = eatFirstKey(input); + + if (chunk[2]) { + // this is actually a class name + record['@class'] = chunk[0]; + input = chunk[1]; + chunk = eatKey(input); + while (input.length) { + c = input.charAt(0); + if (c === ' ') { + input = input.slice(1); + continue; + } + else if (c === ')') { + // empty record. + input = input.slice(1); + return [record, input]; + } + else { + break; + } + } + key = chunk[0]; + input = chunk[1]; + } + else { + key = chunk[0]; + input = chunk[1]; + } + + // read the first value. + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + record[key] = value; + + while (input.length) { + c = input.charAt(0); + if (c === ' ') { + input = input.slice(1); + continue; + } + if (c === ',') { + input = input.slice(1); + } + else if (c === ')') { + input = input.slice(1); + break; + } + chunk = eatKey(input); + key = chunk[0]; + input = chunk[1]; + if (input.length) { + chunk = eatValue(input); + value = chunk[0]; + input = chunk[1]; + record[key] = value; + } + else { + record[key] = null; + } + } + + return [record, input]; +} + +/** + * Consume a RID Bag. + * + * @param {String} input The input to parse. + * @return {[Object, String]} The collected bag, and any remaining input. + */ +function eatBag (input) { + var length = input.length, + collected = '', + i, bag, chunk, c; + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === ';') { + break; + } + else { + collected += c; + } + } + input = input.slice(i + 1); + + if (exports.enableRIDBags) { + return [new Bag(collected), input]; + } + else { + return [new Bag(collected).all(), input]; + } +} + + +/** + * Consume a binary buffer. + * + * @param {String} input The input to parse. + * @return {[Object, String]} The collected bag, and any remaining input. + */ +function eatBinary (input) { + var length = input.length, + collected = '', + i, bag, chunk, c; + + for (i = 0; i < length; i++) { + c = input.charAt(i); + if (c === '_' || c === ',' || c === ')' || c === '>' || c === '}' || c === ']') { + break; + } + else { + collected += c; + } + } + input = input.slice(i + 1); + + return [new Buffer(collected, 'base64'), input]; +} + + +exports.enableRIDBags = true; +exports.deserialize = deserialize; +exports.eatKey = eatKey; +exports.eatValue = eatValue; +exports.eatString = eatString; +exports.eatNumber = eatNumber; +exports.eatRID = eatRID; +exports.eatArray = eatArray; +exports.eatSet = eatSet; +exports.eatMap = eatMap; +exports.eatRecord = eatRecord; +exports.eatBag = eatBag; +exports.eatBinary = eatBinary; diff --git a/lib/transport/binary/protocol28/index.js b/lib/transport/binary/protocol28/index.js new file mode 100644 index 0000000..f022070 --- /dev/null +++ b/lib/transport/binary/protocol28/index.js @@ -0,0 +1,7 @@ +"use strict"; +exports.Operation = require('./operation'); +exports.OperationQueue = require('./operation-queue'); +exports.constants = require('./constants'); +exports.serializer = require('./serializer'); +exports.deserializer = require('./deserializer'); +exports.operations = require('./operations'); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operation-queue.js b/lib/transport/binary/protocol28/operation-queue.js new file mode 100644 index 0000000..d2d28a1 --- /dev/null +++ b/lib/transport/binary/protocol28/operation-queue.js @@ -0,0 +1,185 @@ +"use strict"; + +var Promise = require('bluebird'), + Operation = require('./operation'), + operations = require('./operations'), + Emitter = require('events').EventEmitter, + errors = require('../../../errors'), + util = require('util'); + +function OperationQueue (socket) { + this.socket = socket || null; + this.items = []; + this.writes = []; + this.remaining = null; + if (socket) { + this.bindToSocket(); + } + Emitter.call(this); +} + +util.inherits(OperationQueue, Emitter); + +module.exports = OperationQueue; + +/** + * Add an operation to the queue. + * + * @param {String|Operation} op The operation name or instance. + * @param {Object} params The parameters for the operation, if op is a string. + * @promise {Object} The result of the operation. + */ +OperationQueue.prototype.add = function (op, params) { + if (typeof op === 'string') { + op = new operations[op](params || {}); + } + var deferred = Promise.defer(), + buffer; + // define the write operations + op.writer(); + // define the read operations + op.reader(); + buffer = op.buffer(); + if (this.socket) { + this.socket.write(buffer); + } + else { + this.writes.push(buffer); + } + if (op.id === 'REQUEST_DB_CLOSE') { + deferred.resolve({}); + } + else { + this.items.push([op, deferred]); + } + return deferred.promise; +}; + +/** + * Cancel all the operations in the queue. + * + * @param {Error} err The error object, if any. + * @return {OperationQueue} The now empty queue. + */ +OperationQueue.prototype.cancel = function (err) { + var item, op, deferred; + while ((item = this.items.shift())) { + op = item[0]; + deferred = item[1]; + deferred.reject(err); + } + return this; +}; + +/** + * Bind to events on the socket. + */ +OperationQueue.prototype.bindToSocket = function (socket) { + var total, i; + if (socket) { + this.socket = socket; + } + this.socket.on('data', this.handleChunk.bind(this)); + if ((total = this.writes.length)) { + if (this.socket.connected) { + for (i = 0; i < total; i++) { + this.socket.write(this.writes[i]); + } + this.writes = []; + } + else { + this.socket.once('connect', function () { + var total = this.writes.length, + i; + for (i = 0; i < total; i++) { + this.socket.write(this.writes[i]); + } + this.writes = []; + }.bind(this)); + } + } +}; + +/** + * Unbind from socket events. + */ +OperationQueue.prototype.unbindFromSocket = function () { + this.socket.removeAllListeners('data'); + delete this.socket; +}; + +/** + * Handle a chunk of data from the socket and attempt to process it. + * + * @param {Buffer} data The data received from the server. + */ +OperationQueue.prototype.handleChunk = function (data) { + var buffer, result, offset; + if (this.remaining) { + buffer = new Buffer(this.remaining.length + data.length); + this.remaining.copy(buffer); + data.copy(buffer, this.remaining.length); + } + else { + buffer = data; + } + offset = this.process(buffer); + if (buffer.length - offset === 0) { + this.remaining = null; + } + else { + this.remaining = buffer.slice(offset); + console.log("remaining", this.remaining); + } +}; + +/** + * Process the operations in the queue against the given buffer. + * + * + * @param {Buffer} buffer The buffer to process. + * @param {Integer} offset The offset to start processing from, defaults to 0. + * @return {Integer} The offset that was successfully read up to. + */ +OperationQueue.prototype.process = function (buffer, offset) { + var code, parsed, result, status, item, op, deferred, err; + offset = offset || 0; + while ((item = this.items.shift())) { + op = item[0]; + deferred = item[1]; + parsed = op.consume(buffer, offset); + status = parsed[0]; + offset = parsed[1]; + result = parsed[2]; + if (status === Operation.READING) { + // operation is incomplete, buffer does not contain enough data + this.items.unshift(item); + return offset; + } + else if (status === Operation.PUSH_DATA) { + this.emit('update-config', result); + this.items.unshift(item); + return offset; + } + else if (status === Operation.COMPLETE) { + deferred.resolve(result); + } + else if (status === Operation.ERROR) { + if (result.status.error) { + // this is likely a recoverable error + deferred.reject(result.status.error); + } + else { + // cannot recover, reject everything and let the application decide what to do + err = new errors.Protocol('Unknown Error on operation id ' + op.id, result); + deferred.reject(err); + this.cancel(err); + this.emit('error', err); + } + } + else { + deferred.reject(new errors.Protocol('Unsupported operation status: ' + status)); + } + } + return offset; +}; \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operation.js b/lib/transport/binary/protocol28/operation.js new file mode 100644 index 0000000..b1bcbda --- /dev/null +++ b/lib/transport/binary/protocol28/operation.js @@ -0,0 +1,900 @@ +"use strict"; + +var constants = require('./constants'), + utils = require('../../../utils'), + Long = require('../../../long').Long, + errors = require('../../../errors'), + deserializer = require('./deserializer'); + + +/** + * # Operations + * + * The base class for operations, provides a simple DSL for defining + * the steps required to send a command to the server, and then read + * the response. + * + * Each operation should implement the `writer()` and `reader()` methods. + * + * @param {Object} data The data for the operation. + */ +function Operation (data) { + this.status = Operation.PENDING; + this.writeOps = []; + this.readOps = []; + this.stack = [{}]; + this.data = data || {}; +} + +module.exports = Operation; + +// operation statuses + +var statuses = require('../operation-status'); + +Operation.PENDING = statuses.PENDING; +Operation.WRITTEN = statuses.WRITTEN; +Operation.READING = statuses.READING; +Operation.COMPLETE = statuses.COMPLETE; +Operation.ERROR = statuses.ERROR; +Operation.PUSH_DATA = statuses.PUSH_DATA; + +// make it easy to inherit from the base class +Operation.extend = utils.extend; + +/** + * Declares the commands to send to the server. + * Child classes should implement this function. + */ +Operation.prototype.writer = function () { + +}; + +/** + * Declares the steps required to recieve data for the operation. + * Child classes should implement this function. + */ +Operation.prototype.reader = function () { + +}; + +/** + * Prepare the buffer for the operation. + * + * @return {Buffer} The buffer containing the commands to send to the server. + */ +Operation.prototype.buffer = function () { + + if (!this.writeOps.length) { + this.writer(); + } + + var total = this.writeOps.length, + size = 0, + commands = [], + item, i, fn, offset, data, buffer; + + for (i = 0; i < total; i++) { + item = this.writeOps[i]; + offset = size; + commands.push([item[1], offset]); + size += item[0]; + } + + buffer = new Buffer(size); + + for (i = 0; i < total; i++) { + item = commands[i]; + fn = item[0]; + offset = item[1]; + fn(buffer, offset); + } + + return buffer; +}; + +/** + * Write a request header. + * + * @param {Integer} opCode The operation code. + * @param {Integer} sessionId The session ID. + * @param {Buffer} token The token, if any. + * @return {Operation} The operation instance. + */ +Operation.prototype.writeHeader = function (opCode, sessionId, token) { + this + .writeByte(opCode) + .writeInt(sessionId || -1); + if (token && token.length) { + this.writeBytes(token); + } + return this; +}; + +/** + * Write a byte. + * + * @param {Mixed} data The data. + * @return {Operation} The operation instance. + */ +Operation.prototype.writeByte = function (data) { + this.writeOps.push([1, function (buffer, offset) { + buffer[offset] = data; + }]); + return this; +}; + +/** + * Write a boolean. + * + * @param {Mixed} data The data. + * @return {Operation} The operation instance. + */ +Operation.prototype.writeBoolean = function (data) { + this.writeOps.push([1, function (buffer, offset) { + buffer[offset] = data ? 1 : 0; + }]); + return this; +}; + + +/** + * Write a single character. + * + * @param {Mixed} data The data. + * @return {Operation} The operation instance. + */ +Operation.prototype.writeChar = function (data) { + this.writeOps.push([1, function (buffer, offset) { + buffer[offset] = (''+data).charCodeAt(0); + }]); + return this; +}; + +/** + * Parse data to 4 bytes which represents integer value. + * + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +Operation.prototype.writeInt = function (data) { + this.writeOps.push([constants.BYTES_INT, function (buffer, offset) { + buffer.fill(0, offset, offset + constants.BYTES_INT); + buffer.writeInt32BE(data, offset); + }]); + return this; +}; + +/** + * Parse data to 8 bytes which represents a long value. + * + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +Operation.prototype.writeLong = function (data) { + this.writeOps.push([constants.BYTES_LONG, function (buffer, offset) { + data = Long.fromNumber(data); + buffer.fill(0, offset, offset + constants.BYTES_LONG); + buffer.writeInt32BE(data.high_, offset); + buffer.writeInt32BE(data.low_, offset + constants.BYTES_INT); + + }]); + return this; +}; + +/** + * Parse data to 2 bytes which represents short value. + * + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +Operation.prototype.writeShort = function (data) { + this.writeOps.push([constants.BYTES_SHORT, function (buffer, offset) { + buffer.fill(0, offset, offset + constants.BYTES_SHORT); + buffer.writeInt16BE(data, offset); + }]); + return this; +}; + +/** + * Write bytes to a buffer + * @param {Buffer} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +Operation.prototype.writeBytes = function (data) { + this.writeOps.push([constants.BYTES_INT + data.length, function (buffer, offset) { + buffer.writeInt32BE(data.length, offset); + data.copy(buffer, offset + constants.BYTES_INT); + }]); + return this; +}; + +/** + * Parse string data to buffer with UTF-8 encoding. + * + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +Operation.prototype.writeString = function (data) { + if (data == null) { + return this.writeInt(-1); + } + var encoded = encodeString(data), + length = encoded.length; + this.writeOps.push([constants.BYTES_INT + length, function (buffer, offset) { + buffer.writeInt32BE(length, offset); + encoded.copy(buffer, offset + constants.BYTES_INT); + }]); + return this; +}; + +function encodeString (data) { + var length = data.length, + output = new Buffer(length * 3), // worst case, all chars could require 3-byte encodings. + j = 0, // index output + i, c; + + for (i = 0; i < length; i++) { + c = data.charCodeAt(i); + if (c < 0x80) { + // 7-bits done in one byte. + output[j++] = c; + } + else if (c < 0x800) { + // 8-11 bits done in 2 bytes + output[j++] = (0xC0 | c >> 6); + output[j++] = (0x80 | c & 0x3F); + } + else { + // 12-16 bits done in 3 bytes + output[j++] = (0xE0 | c >> 12); + output[j++] = (0x80 | c >> 6 & 0x3F); + output[j++] = (0x80 | c & 0x3F); + } + } + return output.slice(0, j); +} + +// # Read Operations + + +/** + * Read a status from the server response. + * If the status contains an error, that error + * will be read instead of any subsequently queued commands. + * + * @param {String} fieldName The name of the data field to populate. + * @param {Function} reader The function that should be invoked after this value is read. if any. + * @return {Operation} The operation instance. + */ +Operation.prototype.readStatus = function (fieldName, reader) { + var value = {}; + fieldName = fieldName || 'status'; + + this.readOps.push(function (data) { + data[fieldName] = value; + this.stack.push(data[fieldName]); + }); + this.readByte('code'); + if (this.opCode !== 2 && this.opCode !== 3) { + this.readInt('sessionId'); + this.readBytes('token', next); + } + else { + this.readInt('sessionId', next); + } + return this; + function next (data) { + if (data.code === 1) { + this.readError('error', function () { + if (reader) { + reader.call(this, value, fieldName); + } + this.readOps.push(function () { + this.stack.pop(); + }); + }); + } + else { + if (reader) { + reader.call(this, value, fieldName); + } + this.stack.pop(); + } + } +}; + +/** + * Read an error from the server response. + * Any subsequently queued commands will not run. + * + * @param {String} fieldName The name of the data field to populate. + * @param {Function} reader The function that should be invoked after this value is read. if any. + * @return {Operation} The operation instance. + */ +Operation.prototype.readError = function (fieldName, reader) { + this.readOps = [['Error', [fieldName, reader]]]; + return this; +}; + +/** + * Read an object from the server response. + * This is the same as `readString` but deserializes the returned string + * into an object. + * + * @param {String} fieldName The name of the data field to populate. + * @param {Function} reader The function that should be invoked after this value is read. if any. + * @return {Operation} The operation instance. + */ +Operation.prototype.readObject = function (fieldName, reader) { + this.readOps.push(['String', [fieldName, function (data, fieldName) { + data[fieldName] = deserializer.deserialize(data[fieldName]); + if (reader) { + reader.call(this, data, fieldName); + } + }]]); + return this; +}; + + +// Add the `readByte`, `readInt` etc methods. +// these are just shortcuts + +[ + 'Byte', + 'Bytes', + 'Int', + 'Short', + 'Long', + 'String', + 'Array', + 'Record', + 'Char', + 'Boolean', + 'Collection' +] +.forEach(function (name) { + this['read' + name] = function (fieldName, reader) { + this.readOps.push([name, arguments]); + return this; + }; +}, Operation.prototype); + + +/** + * Consume the buffer starting from the given offset. + * Returns an array containing the operation status, the + * new offset and any collected result. + * + * If the buffer doesn't contain enough data for the operation + * to complete, it will process as much as possible and return + * a partial result with a status code of `Operation.READING` meaning + * that the operation is still in the reading state. + * + * If the operation completes successfully, the status code will be + * `Operation.COMPLETE`. + * + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset + * @return {Array} The array containing the status, new offset and result. + */ +Operation.prototype.consume = function (buffer, offset) { + var obj, code, context, item, type, args; + offset = offset || 0; + if (this.status === Operation.PENDING) { + // this is the first time consume has been called for this + // operation. We need to determine whether the response + // we're reading is really for us or whether it's a + // PUSH_DATA command. + if (buffer.length < offset + 1) { + // not enough bytes in the buffer to check. + return [Operation.READING, offset, {}]; + } + + code = buffer.readUInt8(offset); + if (code === 3) { + offset += 5; // ignore the next integer + obj = {}; + offset += this.parsePushedData(buffer, offset, obj, 'data'); + return [Operation.PUSH_DATA, offset, obj.data]; + } + + this.status = Operation.READING; + } + if (this.readOps.length === 0) { + return [Operation.COMPLETE, offset, this.stack[0]]; + } + while ((item = this.readOps.shift())) { + context = this.stack[this.stack.length - 1]; + if (typeof item === 'function') { + // this is a nop, just execute it. + item.call(this, context, buffer, offset); + continue; + } + type = item[0]; + args = item[1]; + if (!this.canRead(type, buffer, offset)) { + // not enough bytes in the buffer, operation is still reading. + this.readOps.unshift(item); + return [Operation.READING, offset, this.stack[0]]; + } + offset += this['parse' + type](buffer, offset, context, args[0], args[1]); + } + if (this.stack[0] && this.stack[0].status && this.stack[0].status.code) { + return [Operation.ERROR, offset, this.stack[0]]; + } + else { + return [Operation.COMPLETE, offset, this.stack[0]]; + } +}; + +/** + * Defetermine whether the operation can read a value of the given + * type from the buffer at the given offset. + * + * @param {String} type The value type. + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading at. + * @return {Boolean} true if the value can be read. + */ +Operation.prototype.canRead = function (type, buffer, offset) { + var length = buffer.length; + if (offset > length) { + return false; + } + switch (type) { + case 'Array': + case 'Error': + return true; + case 'Byte': + case 'Char': + case 'Boolean': + return length >= offset + 1; + case 'Short': + case 'Record': + return length >= offset + constants.BYTES_SHORT; + case 'Long': + return length >= offset + constants.BYTES_LONG; + case 'Int': + case 'Collection': + return length >= offset + constants.BYTES_INT; + case 'Bytes': + case 'String': + if (length < offset + constants.BYTES_INT) { + return false; + } + else { + return length >= offset + constants.BYTES_INT + buffer.readInt32BE(offset); + } + break; + default: + return false; + } +}; + + +/** + * Parse a byte from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseByte = function (buffer, offset, context, fieldName, reader) { + context[fieldName] = buffer.readUInt8(offset); + if (reader) { + reader.call(this, context, fieldName); + } + return 1; +}; + +/** + * Parse a character from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseChar = function (buffer, offset, context, fieldName, reader) { + context[fieldName] = String.fromCharCode(buffer.readUInt8(offset)); + if (reader) { + reader.call(this, context, fieldName); + } + return 1; +}; + +/** + * Parse a boolean from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseBoolean = function (buffer, offset, context, fieldName, reader) { + context[fieldName] = Boolean(buffer.readUInt8(offset)); + if (reader) { + reader.call(this, context, fieldName); + } + return 1; +}; + + +/** + * Parse a short from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseShort = function (buffer, offset, context, fieldName, reader) { + + context[fieldName] = buffer.readInt16BE(offset); + if (reader) { + reader.call(this, context, fieldName); + } + return constants.BYTES_SHORT; +}; + +/** + * Parse an integer from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseInt = function (buffer, offset, context, fieldName, reader) { + context[fieldName] = buffer.readInt32BE(offset); + if (reader) { + reader.call(this, context, fieldName); + } + return constants.BYTES_INT; +}; + +/** + * Parse a long from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseLong = function (buffer, offset, context, fieldName, reader) { + context[fieldName] = Long + .fromBits( + buffer.readUInt32BE(offset + constants.BYTES_INT), + buffer.readInt32BE(offset) + ) + .toNumber(); + + if (reader) { + reader.call(this, context, fieldName); + } + return constants.BYTES_LONG; +}; + +/** + * Parse some bytes from the given buffer at the given offset and + * insert them into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseBytes = function (buffer, offset, context, fieldName, reader) { + var length = buffer.readInt32BE(offset); + offset += constants.BYTES_INT; + if (length < 0) { + context[fieldName] = null; + } + else { + context[fieldName] = buffer.slice(offset, offset + length); + } + if (reader) { + reader.call(this, context, fieldName); + } + return length > 0 ? length + constants.BYTES_INT : constants.BYTES_INT; +}; + + +/** + * Parse a string from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseString = function (buffer, offset, context, fieldName, reader) { + var length = buffer.readInt32BE(offset); + offset += constants.BYTES_INT; + if (length < 0) { + context[fieldName] = null; + } + else { + context[fieldName] = buffer.toString('utf8', offset, offset + length); + } + if (reader) { + reader.call(this, context, fieldName); + } + return length > 0 ? length + constants.BYTES_INT : constants.BYTES_INT; +}; + +/** + * Parse a record from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseRecord = function (buffer, offset, context, fieldName, reader) { + var remainingOps = this.readOps, + record = {}; + this.readOps = []; + this.stack.push(record); + if (Array.isArray(context[fieldName])) { + context[fieldName].push(record); + } + else { + context[fieldName] = record; + } + this.readShort('classId', function (record, fieldName) { + if (record[fieldName] === -1) { + record.value = new errors.Protocol('No class for record, cannot proceed.'); + this.stack.pop(); + this.readOps.push(function () { + if (reader) { + reader.call(this, context, fieldName); + } + }); + this.readOps.push.apply(this.readOps, remainingOps); + return; + } + else if (record[fieldName] === -2) { + record.value = null; + this.stack.pop(); + this.readOps.push(function () { + if (reader) { + reader.call(this, context, fieldName); + } + }); + this.readOps.push.apply(this.readOps, remainingOps); + return; + } + else if (record[fieldName] === -3) { + record.type = 'd'; + this + .readShort('cluster') + .readLong('position') + .readOps.push(function () { + this.stack.pop(); + this.readOps.push(function () { + if (reader) { + reader.call(this, context, fieldName); + } + }); + this.readOps.push.apply(this.readOps, remainingOps); + }); + } + else if (record[fieldName] > -1) { + this + .readChar('type') + .readShort('cluster') + .readLong('position') + .readInt('version') + .readString('value', function (data, key) { + data[key] = deserializer.deserialize(data[key]); + this.stack.pop(); + this.readOps.push(function () { + if (reader) { + reader.call(this, context, fieldName); + } + }); + this.readOps.push.apply(this.readOps, remainingOps); + }); + } + }); + + + + return 0; +}; + + +/** + * Parse a collection from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseCollection = function (buffer, offset, context, fieldName, reader) { + var remainingOps = this.readOps, + records = [], + total = buffer.readInt32BE(offset), + i; + offset += 4; + this.readOps = []; + context[fieldName] = records; + for (i = 0; i < total; i++) { + this.readRecord(fieldName); + } + if (reader) { + this.readOps.push(function () { + reader.call(this, records); + }); + } + this.readOps.push.apply(this.readOps, remainingOps); + return 4; +}; + + +/** + * Parse an array from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * > Note. this differs from the other `parseXYZ` methods in that `reader` + * is required, and MUST return an array of functions. Each function in the + * array represents a 'scope' for an item in the array and will be invoked in order. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseArray = function (buffer, offset, context, fieldName, reader) { + var items = reader.call(this, context), + remainingOps = this.readOps; + + this.readOps = []; + + context[fieldName] = []; + this.stack.push(context[fieldName]); + + items.map(function (item) { + var childContext = {}; + context[fieldName].push(childContext); + this.readOps.push(function () { + this.stack.push(childContext); + }); + + item.call(this, childContext); + + this.readOps.push(function () { + this.stack.pop(); + }); + }, this); + + + this.readOps.push(function () { + this.stack.pop(); + }); + + this.readOps.push.apply(this.readOps, remainingOps); + return 0; +}; + +/** + * Parse an error from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * > Note: this implementation differs from the others in that + * when an error is encountered, any subsequent `readXYZ()` commands + * that were due to be run will be skipped. + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parseError = function (buffer, offset, context, fieldName, reader) { + var err = new errors.Request(); + err.previous = []; + // remove any ops we were expecting to run. + this.readOps = []; + + context[fieldName] = err; + this.stack.push(err); + this.readByte('id'); + + + function readItem () { + this.readString('type'); + this.readString('message'); + this.readByte('hasMore', function (data) { + var prev; + if (data.hasMore) { + prev = new errors.Request(); + err.previous.push(prev); + this.stack.pop(); + this.stack.push(prev); + readItem.call(this); + } + else { + this.readBytes('javaStackTrace', function (data) { + this.readOps.push(function (data) { + this.stack.pop(); + }); + }); + } + }); + } + readItem.call(this); + if (reader) { + reader.call(this, context, fieldName); + } + return 0; +}; + + +/** + * Parse any pushed data from the given buffer at the given offset and + * insert it into the context under the given field name. + * + * + * @param {Buffer} buffer The buffer to read from. + * @param {Integer} offset The offset to start reading from. + * @param {Object} context The context to add the value to. + * @param {String} fieldName The name of the field in the context. + * @param {Function} reader The function that should be invoked after the value is read, if any. + * @return {Integer} The number of bytes read. + */ +Operation.prototype.parsePushedData = function (buffer, offset, context, fieldName, reader) { + var length = buffer.readInt32BE(offset), + asString; + offset += constants.BYTES_INT; + asString = buffer.toString('utf8', offset, offset + length); + switch (asString.charAt(0)) { + case 'R': + context[fieldName] = deserializer.deserialize(asString.slice(1)); + break; + default: + console.log('unsupported pushed data format: ' + asString); + } + if (reader) { + reader.call(this, context, fieldName); + } + return length + constants.BYTES_INT; +}; + diff --git a/lib/transport/binary/protocol28/operations/command.js b/lib/transport/binary/protocol28/operations/command.js new file mode 100644 index 0000000..fe5ad6f --- /dev/null +++ b/lib/transport/binary/protocol28/operations/command.js @@ -0,0 +1,179 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + serializer = require('../serializer'), + writer = require('../writer'), + RID = require('../../../../recordid'); + +module.exports = Operation.extend({ + id: 'REQUEST_COMMAND', + opCode: 41, + writer: function () { + if (this.data.mode === 'a' && !this.data.class) { + this.data.class = 'com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery'; + } + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeChar(this.data.mode || 's') + .writeBytes(this.serializeQuery()); + + }, + serializeQuery: function () { + var buffers = [writer.writeString(this.data.class)]; + + if (this.data.class === 'q' || + this.data.class === 'com.orientechnologies.orient.core.sql.query.OSQLSynchQuery' || + this.data.class === 'com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery') { + buffers.push( + writer.writeString(this.data.query), + writer.writeInt(this.data.limit), + writer.writeString(this.data.fetchPlan || '') + ); + + if (this.data.params) { + buffers.push(writer.writeString(serializeParams(this.data.params))); + } + else { + buffers.push(writer.writeInt(0)); + } + } + else if ( + this.data.class === 's' || + this.data.class === 'com.orientechnologies.orient.core.command.script.OCommandScript') { + buffers.push( + writer.writeString(this.data.language || 'sql'), + writer.writeString(this.data.query) + ); + if (this.data.params && this.data.params.params && Object.keys(this.data.params.params).length) { + buffers.push( + writer.writeBoolean(true), + writer.writeString(serializeParams(this.data.params)) + ); + } + else { + buffers.push(writer.writeBoolean(false)); + } + buffers.push(writer.writeByte(0)); + } + else { + buffers.push(writer.writeString(this.data.query)); + if (this.data.params) { + buffers.push( + writer.writeBoolean(true), + writer.writeString(serializeParams(this.data.params)) + ); + } + else { + buffers.push(writer.writeBoolean(false)); + } + buffers.push(writer.writeBoolean(false)); + } + return Buffer.concat(buffers); + }, + reader: function () { + this + .readStatus('status') + .readCommandResult('results'); + }, + readCommandResult: function (fieldName, reader) { + this.payloads = []; + this.readOps.push(function (data) { + data[fieldName] = this.payloads; + this.stack.push(data[fieldName]); + this.readPayload('payloadStatus', function () { + this.stack.pop(); + }); + }); + return this; + }, + readPayload: function (payloadFieldName, reader) { + + return this.readByte(payloadFieldName, function (data, fieldName) { + var record = {}; + switch (data[fieldName]) { + case 0: + if (reader) { + reader.call(this); + } + break; + case 110: // null + record.type = 'r'; + record.content = null; + this.payloads.push(record); + this.readPayload(payloadFieldName, reader); + break; + case 1: + case 114: + // a record + record.type = 'r'; + this.payloads.push(record); + this.stack.push(record); + this.readRecord('content', function () { + this.stack.pop(); + this.readPayload(payloadFieldName, reader); + }); + break; + case 2: + // prefeteched record + record.type = 'p'; + this.payloads.push(record); + this.stack.push(record); + this.readRecord('content', function (data) { + this.stack.pop(); + this.readPayload(payloadFieldName, reader); + }); + break; + case 97: + // serialized result + record.type = 'f'; + this.payloads.push(record); + this.stack.push(record); + this.readString('content', function () { + this.stack.pop(); + this.readPayload(payloadFieldName, reader); + }); + break; + case 108: + // collection of records + record.type = 'l'; + this.payloads.push(record); + this.stack.push(record); + this.readCollection('content', function (data) { + this.stack.pop(); + this.readPayload(payloadFieldName, reader); + }); + break; + default: + reader.call(this); + } + }); + } +}); + +/** + * Serialize the parameters for a query. + * + * > Note: There is a bug in OrientDB where special kinds of string values + * > need to be twice quoted *in parameters*. Hence the need for this specialist function. + * + * @param {Object} data The data to serialize. + * @return {String} The serialized data. + */ +function serializeParams (data) { + var keys = Object.keys(data.params || {}), + total = keys.length, + c, i, key, value; + + for (i = 0; i < total; i++) { + key = keys[i]; + value = data.params[key]; + if (typeof value === 'string') { + c = value.charAt(0); + if (c === '.' || c === '#' || c === '<' || c === '[' || c === '(' || c === '{' || c === '0' || +c) { + data.params[key] = '"' + value + '"'; + } + } + } + return serializer.serializeDocument(data); +} \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/config-get.js b/lib/transport/binary/protocol28/operations/config-get.js new file mode 100644 index 0000000..8a61829 --- /dev/null +++ b/lib/transport/binary/protocol28/operations/config-get.js @@ -0,0 +1,19 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_CONFIG_GET', + opCode: 70, + writer: function () { + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeString(this.data.key); + }, + reader: function () { + this + .readStatus('status') + .readString('value'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/config-list.js b/lib/transport/binary/protocol28/operations/config-list.js new file mode 100644 index 0000000..ff7ba23 --- /dev/null +++ b/lib/transport/binary/protocol28/operations/config-list.js @@ -0,0 +1,29 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_CONFIG_LIST', + opCode: 72, + writer: function () { + this.writeHeader(this.opCode, this.data.sessionId, this.data.token); + }, + reader: function () { + this + .readStatus('status') + .readShort('total') + .readArray('items', function (data) { + var items = [], + i; + for (i = 0; i < data.total; i++) { + items.push(function () { + this + .readString('key') + .readString('value'); + }); + } + return items; + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/config-set.js b/lib/transport/binary/protocol28/operations/config-set.js new file mode 100644 index 0000000..5ff03ad --- /dev/null +++ b/lib/transport/binary/protocol28/operations/config-set.js @@ -0,0 +1,22 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_CONFIG_SET', + opCode: 71, + writer: function () { + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeString(this.data.key) + .writeString(this.data.value); + }, + reader: function () { + this + .readStatus('status') + .readOps.push(function (data) { + data.success = true; + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/connect.js b/lib/transport/binary/protocol28/operations/connect.js new file mode 100644 index 0000000..330c86a --- /dev/null +++ b/lib/transport/binary/protocol28/operations/connect.js @@ -0,0 +1,33 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + npmPackage = require('../../../../../package.json'); + +module.exports = Operation.extend({ + id: 'REQUEST_CONNECT', + opCode: 2, + writer: function () { + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId || -1) + .writeString(npmPackage.name) + .writeString(npmPackage.version) + .writeShort(+constants.PROTOCOL_VERSION) + .writeString('') // client id + .writeString('ORecordDocument2csv') // serialization format + .writeBoolean(this.data.useToken) // use JWT? + .writeString(this.data.username) + .writeString(this.data.password); + }, + reader: function () { + this + .readStatus('status') + .readInt('sessionId') + .readBytes('token', function (data) { + console.log('SERVER TOKEN', data); + }); + + + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/datacluster-add.js b/lib/transport/binary/protocol28/operations/datacluster-add.js new file mode 100644 index 0000000..31143d1 --- /dev/null +++ b/lib/transport/binary/protocol28/operations/datacluster-add.js @@ -0,0 +1,20 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DATACLUSTER_ADD', + opCode: 10, + writer: function () { + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeString(this.data.name) + .writeShort(this.data.id || -1); + }, + reader: function () { + this + .readStatus('status') + .readShort('id'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/datacluster-count.js b/lib/transport/binary/protocol28/operations/datacluster-count.js new file mode 100644 index 0000000..4ec8334 --- /dev/null +++ b/lib/transport/binary/protocol28/operations/datacluster-count.js @@ -0,0 +1,33 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DATACLUSTER_COUNT', + opCode: 12, + writer: function () { + var total, item, i; + + this.writeHeader(this.opCode, this.data.sessionId, this.data.token); + + if (Array.isArray(this.data.id)) { + total = this.data.id.length; + this.writeShort(total); + for (i = 0; i < total; i++) { + this.writeShort(this.data.id[i]); + } + } + else { + this + .writeShort(1) + .writeShort(this.data.id); + } + this.writeByte(this.data.tombstones || false); + }, + reader: function () { + this + .readStatus('status') + .readLong('count'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/datacluster-datarange.js b/lib/transport/binary/protocol28/operations/datacluster-datarange.js new file mode 100644 index 0000000..7f18fef --- /dev/null +++ b/lib/transport/binary/protocol28/operations/datacluster-datarange.js @@ -0,0 +1,22 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DATACLUSTER_DATARANGE', + opCode: 13, + writer: function () { + var total, i; + + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeShort(this.data.id); + }, + reader: function () { + this + .readStatus('status') + .readLong('begin') + .readLong('end'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/datacluster-drop.js b/lib/transport/binary/protocol28/operations/datacluster-drop.js new file mode 100644 index 0000000..3349759 --- /dev/null +++ b/lib/transport/binary/protocol28/operations/datacluster-drop.js @@ -0,0 +1,21 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DATACLUSTER_DROP', + opCode: 11, + writer: function () { + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeShort(this.data.id); + }, + reader: function () { + this + .readStatus('status') + .readByte('success', function (data, fieldName) { + data[fieldName] = Boolean(data[fieldName]); + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/db-close.js b/lib/transport/binary/protocol28/operations/db-close.js new file mode 100644 index 0000000..b76bf21 --- /dev/null +++ b/lib/transport/binary/protocol28/operations/db-close.js @@ -0,0 +1,13 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_CLOSE', + opCode: 5, + writer: function () { + this.writeHeader(this.opCode, this.data.sessionId, this.data.token); + }, + reader: function () {} +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/db-countrecords.js b/lib/transport/binary/protocol28/operations/db-countrecords.js new file mode 100644 index 0000000..2034665 --- /dev/null +++ b/lib/transport/binary/protocol28/operations/db-countrecords.js @@ -0,0 +1,17 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_COUNTRECORDS', + opCode: 9, + writer: function () { + this.writeHeader(this.opCode, this.data.sessionId, this.data.token); + }, + reader: function () { + this + .readStatus('status') + .readLong('count'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/db-create.js b/lib/transport/binary/protocol28/operations/db-create.js new file mode 100644 index 0000000..19e5215 --- /dev/null +++ b/lib/transport/binary/protocol28/operations/db-create.js @@ -0,0 +1,19 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_CREATE', + opCode: 4, + writer: function () { + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeString(this.data.name) + .writeString(this.data.type || 'graph') + .writeString(this.data.storage || 'plocal'); + }, + reader: function () { + this.readStatus('status'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/db-delete.js b/lib/transport/binary/protocol28/operations/db-delete.js new file mode 100644 index 0000000..60cfaff --- /dev/null +++ b/lib/transport/binary/protocol28/operations/db-delete.js @@ -0,0 +1,18 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_DROP', + opCode: 7, + writer: function () { + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeString(this.data.name) + .writeString(this.data.storage || 'plocal'); + }, + reader: function () { + this.readStatus('status'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/db-exists.js b/lib/transport/binary/protocol28/operations/db-exists.js new file mode 100644 index 0000000..c8dfc4d --- /dev/null +++ b/lib/transport/binary/protocol28/operations/db-exists.js @@ -0,0 +1,22 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_EXIST', + opCode: 6, + writer: function () { + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeString(this.data.name) + .writeString(this.data.storage || 'local'); + }, + reader: function () { + this + .readStatus('status') + .readByte('exists', function (data) { + data.exists = Boolean(data.exists); + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/db-freeze.js b/lib/transport/binary/protocol28/operations/db-freeze.js new file mode 100644 index 0000000..cac9f79 --- /dev/null +++ b/lib/transport/binary/protocol28/operations/db-freeze.js @@ -0,0 +1,18 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_FREEZE', + opCode: 94, + writer: function () { + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeString(this.data.name) + .writeString(this.data.storage || 'plocal'); + }, + reader: function () { + this.readStatus('status'); + } +}); diff --git a/lib/transport/binary/protocol28/operations/db-list.js b/lib/transport/binary/protocol28/operations/db-list.js new file mode 100644 index 0000000..54a68df --- /dev/null +++ b/lib/transport/binary/protocol28/operations/db-list.js @@ -0,0 +1,19 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_LIST', + opCode: 74, + writer: function () { + this.writeHeader(this.opCode, this.data.sessionId, this.data.token); + }, + reader: function () { + this + .readStatus('status') + .readObject('databases', function (data, fieldName) { + data[fieldName] = data[fieldName].databases; + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/db-open.js b/lib/transport/binary/protocol28/operations/db-open.js new file mode 100644 index 0000000..122d88a --- /dev/null +++ b/lib/transport/binary/protocol28/operations/db-open.js @@ -0,0 +1,51 @@ +"use strict"; +var Operation = require('../operation'), + constants = require('../constants'), + npmPackage = require('../../../../../package.json'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_OPEN', + opCode: 3, + writer: function () { + console.log('dbop', this.data); + this + .writeByte(this.opCode) + .writeInt(this.data.sessionId || -1) + .writeString(npmPackage.name) + .writeString(npmPackage.version) + .writeShort(+constants.PROTOCOL_VERSION) + .writeString('') // client id + .writeString('ORecordDocument2csv') // serialization format + .writeBoolean(this.data.useToken) // tokens please! + .writeString(this.data.name) + .writeString(this.data.type) + .writeString(this.data.username) + .writeString(this.data.password); + }, + reader: function () { + this + .readStatus('status') + .readInt('sessionId') + .readBytes('token', function (data) { + console.log('dbToken', data.token); + }); + + this + .readShort('totalClusters') + .readArray('clusters', function (data) { + var clusters = [], + total = data.totalClusters, + i; + + for (i = 0; i < total; i++) { + clusters.push(function (data) { + this.readString('name') + .readShort('id'); + }); + } + return clusters; + }) + .readObject('serverCluster') + .readString('release'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/db-release.js b/lib/transport/binary/protocol28/operations/db-release.js new file mode 100644 index 0000000..43e2dc0 --- /dev/null +++ b/lib/transport/binary/protocol28/operations/db-release.js @@ -0,0 +1,18 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_RELEASE', + opCode: 95, + writer: function () { + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeString(this.data.name) + .writeString(this.data.storage || 'plocal'); + }, + reader: function () { + this.readStatus('status'); + } +}); diff --git a/lib/transport/binary/protocol28/operations/db-reload.js b/lib/transport/binary/protocol28/operations/db-reload.js new file mode 100644 index 0000000..cd5ed58 --- /dev/null +++ b/lib/transport/binary/protocol28/operations/db-reload.js @@ -0,0 +1,30 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_RELOAD', + opCode: 73, + writer: function () { + this.writeHeader(this.opCode, this.data.sessionId, this.data.token); + }, + reader: function () { + this + .readStatus('status') + .readShort('totalClusters') + .readArray('clusters', function (data) { + var clusters = [], + total = data.totalClusters, + i; + + for (i = 0; i < total; i++) { + clusters.push(function (data) { + this.readString('name') + .readShort('id'); + }); + } + return clusters; + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/db-size.js b/lib/transport/binary/protocol28/operations/db-size.js new file mode 100644 index 0000000..c3b5f9e --- /dev/null +++ b/lib/transport/binary/protocol28/operations/db-size.js @@ -0,0 +1,17 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'); + +module.exports = Operation.extend({ + id: 'REQUEST_DB_SIZE', + opCode: 8, + writer: function () { + this.writeHeader(this.opCode, this.data.sessionId, this.data.token); + }, + reader: function () { + this + .readStatus('status') + .readLong('size'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/index.js b/lib/transport/binary/protocol28/operations/index.js new file mode 100644 index 0000000..be581ca --- /dev/null +++ b/lib/transport/binary/protocol28/operations/index.js @@ -0,0 +1,34 @@ +"use strict"; /*jshint sub:true*/ + +exports['connect'] = require('./connect'); +exports['db-open'] = require('./db-open'); +exports['db-create'] = require('./db-create'); +exports['db-exists'] = require('./db-exists'); +exports['db-delete'] = require('./db-delete'); +exports['db-size'] = require('./db-size'); +exports['db-countrecords'] = require('./db-countrecords'); +exports['db-reload'] = require('./db-reload'); +exports['db-list'] = require('./db-list'); +exports['db-freeze'] = require('./db-freeze'); +exports['db-release'] = require('./db-release'); +exports['db-close'] = require('./db-close'); + + +exports['datacluster-add'] = require('./datacluster-add'); +exports['datacluster-count'] = require('./datacluster-count'); +exports['datacluster-datarange'] = require('./datacluster-datarange'); +exports['datacluster-drop'] = require('./datacluster-drop'); + +exports['record-create'] = require('./record-create'); +exports['record-load'] = require('./record-load'); +exports['record-metadata'] = require('./record-metadata'); +exports['record-update'] = require('./record-update'); +exports['record-delete'] = require('./record-delete'); +exports['record-clean-out'] = require('./record-clean-out'); + +exports['command'] = require('./command'); +exports['tx-commit'] = require('./tx-commit'); + +exports['config-list'] = require('./config-list'); +exports['config-get'] = require('./config-get'); +exports['config-set'] = require('./config-set'); diff --git a/lib/transport/binary/protocol28/operations/record-clean-out.js b/lib/transport/binary/protocol28/operations/record-clean-out.js new file mode 100644 index 0000000..b8dbbf6 --- /dev/null +++ b/lib/transport/binary/protocol28/operations/record-clean-out.js @@ -0,0 +1,34 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + RID = require('../../../../recordid'), + serializer = require('../serializer'); + +module.exports = Operation.extend({ + id: 'REQUEST_RECORD_CLEAN_OUT', + opCode: 38, + writer: function () { + var rid, cluster, position; + if (this.data.record && this.data.record['@rid']) { + rid = RID.parse(this.data.record['@rid']); + cluster = this.data.cluster || rid.cluster; + position = this.data.position || rid.position; + } + else { + cluster = this.data.cluster; + position = this.data.position; + } + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeShort(cluster) + .writeLong(position) + .writeInt(this.data.version || -1) + .writeBoolean(this.data.mode); + }, + reader: function () { + this + .readStatus('status') + .readBoolean('success'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/record-create.js b/lib/transport/binary/protocol28/operations/record-create.js new file mode 100644 index 0000000..abb0499 --- /dev/null +++ b/lib/transport/binary/protocol28/operations/record-create.js @@ -0,0 +1,50 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + RID = require('../../../../recordid'), + serializer = require('../serializer'); + +module.exports = Operation.extend({ + id: 'REQUEST_RECORD_CREATE', + opCode: 31, + writer: function () { + var rid, cluster; + if (this.data.record['@rid']) { + rid = RID.parse(this.data.record['@rid']); + cluster = this.data.cluster || rid.cluster; + } + else { + cluster = this.data.cluster; + } + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeShort(cluster) + .writeBytes(serializer.encodeRecordData(this.data.record)) + .writeByte(constants.RECORD_TYPES[this.data.type || 'd']) + .writeByte(this.data.mode || 0); + }, + reader: function () { + this + .readStatus('status') + .readShort('cluster') + .readLong('position') + .readInt('version') + .readInt('totalChanges') + .readArray('changes', function (data) { + var items = [], + i; + for (i = 0; i < data.totalChanges; i++) { + items.push(function () { + this + .readLong('uuidHigh') + .readLong('uuidLow') + .readLong('fileId') + .readLong('pageIndex') + .readInt('pageOffset'); + }); + } + return items; + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/record-delete.js b/lib/transport/binary/protocol28/operations/record-delete.js new file mode 100644 index 0000000..420995a --- /dev/null +++ b/lib/transport/binary/protocol28/operations/record-delete.js @@ -0,0 +1,45 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + RID = require('../../../../recordid'), + serializer = require('../serializer'); + +module.exports = Operation.extend({ + id: 'REQUEST_RECORD_DELETE', + opCode: 33, + writer: function () { + var rid, cluster, position, version; + if (this.data.record && this.data.record['@rid']) { + rid = RID.parse(this.data.record['@rid']); + cluster = this.data.cluster || rid.cluster; + position = this.data.position || rid.position; + } + else { + cluster = this.data.cluster; + position = this.data.position; + } + if (this.data.version != null) { + version = this.data.version; + } + else if (this.data.record && this.data.record['@version'] != null) { + version = this.data.record['@version']; + } + else { + version = -1; + } + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeShort(cluster) + .writeLong(position) + .writeInt(version) + .writeByte(this.data.mode || 0); + }, + reader: function () { + this + .readStatus('status') + .readByte('success', function (data, fieldName) { + data[fieldName] = Boolean(data[fieldName]); + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/record-load.js b/lib/transport/binary/protocol28/operations/record-load.js new file mode 100644 index 0000000..ea6834c --- /dev/null +++ b/lib/transport/binary/protocol28/operations/record-load.js @@ -0,0 +1,119 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + RID = require('../../../../recordid'), + serializer = require('../serializer'), + deserializer = require('../deserializer'), + errors = require('../../../../errors'); + +module.exports = Operation.extend({ + id: 'REQUEST_RECORD_LOAD', + opCode: 30, + writer: function () { + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeShort(this.data.cluster) + .writeLong(this.data.position) + .writeString(this.data.fetchPlan || '') + .writeByte(this.data.ignoreCache || 0) + .writeByte(this.data.tombstones || 0); + }, + reader: function () { + var records = []; + this.readStatus('status'); + this.readOps.push(function (data) { + data.records = records; + this.stack.push(data.records); + this.readPayload(records, function () { + this.stack.pop(); + data.records = data.records.map(function (record) { + var r; + if (record.type === 'd') { + r = record.content || {}; + r['@rid'] = r['@rid'] ||new RID({ + cluster: record.cluster, + position: record.position + }); + r['@version'] = record.version; + r['@type'] = record.type; + } + else { + r = { + '@rid': new RID({ + cluster: record.cluster, + position: record.position + }), + '@version': record.version, + '@type': record.type, + value: record.content + }; + + } + return r; + }, this); + }); + }); + }, + readPayload: function (records, ender) { + + return this.readByte('payloadStatus', function (data, fieldName) { + var record = {}; + switch (data[fieldName]) { + case 0: + // nothing to do. + if (ender) { + ender.call(this); + } + break; + case 1: + // a record + records.push(record); + this.stack.push(record); + this + .readString('content') + .readInt('version') + .readChar('type', function (data, fieldName) { + data.cluster = this.data.cluster; + data.position = this.data.position; + if (data[fieldName] === 'd') { + data.content = deserializer.deserialize(data.content); + } + this.stack.pop(); + this.readPayload(records, ender); + }); + break; + case 2: + // a sub record + records.push(record); + this.stack.push(record); + this.readShort('classId', function (data, fieldName) { + switch (data[fieldName]) { + case -2: + this.stack.pop(); + this.readPayload(records, ender); + break; + case -3: + throw new errors.Protocol('ClassID ' + data[fieldName] + ' is not supported.'); + default: + this + .readChar('type') + .readShort('cluster') + .readLong('position') + .readInt('version') + .readString('content', function (data, fieldName) { + if (data.type === 'd') { + data.content = deserializer.deserialize(data.content); + } + this.stack.pop(); + this.readPayload(records, ender); + }); + } + }); + break; + default: + this.readPayload(records, ender); + } + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/record-metadata.js b/lib/transport/binary/protocol28/operations/record-metadata.js new file mode 100644 index 0000000..d5b1814 --- /dev/null +++ b/lib/transport/binary/protocol28/operations/record-metadata.js @@ -0,0 +1,34 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + RID = require('../../../../recordid'), + serializer = require('../serializer'); + +module.exports = Operation.extend({ + id: 'REQUEST_RECORD_METADATA', + opCode: 29, + writer: function () { + var rid, cluster, position; + if (this.data.record && this.data.record['@rid']) { + rid = RID.parse(this.data.record['@rid']); + cluster = this.data.cluster || rid.cluster; + position = this.data.position || rid.position; + } + else { + cluster = this.data.cluster; + position = this.data.position; + } + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeShort(cluster) + .writeLong(position); + }, + reader: function () { + this + .readStatus('status') + .readShort('cluster') + .readLong('position') + .readInt('version'); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/record-update.js b/lib/transport/binary/protocol28/operations/record-update.js new file mode 100644 index 0000000..124dfe4 --- /dev/null +++ b/lib/transport/binary/protocol28/operations/record-update.js @@ -0,0 +1,62 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + RID = require('../../../../recordid'), + serializer = require('../serializer'); + +module.exports = Operation.extend({ + id: 'REQUEST_RECORD_UPDATE', + opCode: 32, + writer: function () { + var rid, cluster, position, version; + if (this.data.record['@rid']) { + rid = RID.parse(this.data.record['@rid']); + cluster = this.data.cluster || rid.cluster; + position = this.data.position || rid.position; + } + else { + cluster = this.data.cluster; + position = this.data.position; + } + if (this.data.version != null) { + version = this.data.version; + } + else if (this.data.record['@version'] != null) { + version = this.data.record['@version']; + } + else { + version = -1; + } + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeShort(cluster) + .writeLong(position) + .writeBoolean(true) + .writeBytes(serializer.encodeRecordData(this.data.record)) + .writeInt(version) + .writeByte(constants.RECORD_TYPES[this.data.type || 'd']) + .writeByte(this.data.mode || 0); + }, + reader: function () { + this + .readStatus('status') + .readInt('version') + .readInt('totalChanges') + .readArray('changes', function (data) { + var items = [], + i; + for (i = 0; i < data.totalChanges; i++) { + items.push(function () { + this + .readLong('uuidHigh') + .readLong('uuidLow') + .readLong('fileId') + .readLong('pageIndex') + .readInt('pageOffset'); + }); + } + return items; + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/tx-commit.js b/lib/transport/binary/protocol28/operations/tx-commit.js new file mode 100644 index 0000000..d224fdd --- /dev/null +++ b/lib/transport/binary/protocol28/operations/tx-commit.js @@ -0,0 +1,112 @@ +"use strict"; + +var Operation = require('../operation'), + constants = require('../constants'), + RID = require('../../../../recordid'), + serializer = require('../serializer'); + +module.exports = Operation.extend({ + id: 'REQUEST_TX_COMMIT', + opCode: 60, + writer: function () { + this + .writeHeader(this.opCode, this.data.sessionId, this.data.token) + .writeInt(this.data.txId) + .writeByte(this.data.txLog); // use transaction log + + + // creates + var total = this.data.creates.length, + item, i; + + for (i = 0; i < total; i++) { + item = this.data.creates[i]; + this.writeByte(1); // mark the start of an entry. + this.writeByte(3); // create. + this.writeShort(item['@rid'].cluster); + this.writeLong(item['@rid'].position); + this.writeByte(constants.RECORD_TYPES[item['@type'] || 'd'] || 100); // document by default + this.writeBytes(serializer.encodeRecordData(item)); + } + + // updates + total = this.data.updates.length; + + for (i = 0; i < total; i++) { + item = this.data.updates[i]; + this.writeByte(1); // mark the start of an entry. + this.writeByte(1); // update. + this.writeShort(item['@rid'].cluster); + this.writeLong(item['@rid'].position); + this.writeByte(constants.RECORD_TYPES[item['@type'] || 'd'] || 100); // document by default + this.writeInt(item['@version'] || 0); + this.writeBytes(serializer.encodeRecordData(item)); + this.writeBoolean(true); + } + + // deletes + total = this.data.deletes.length; + + for (i = 0; i < total; i++) { + item = this.data.deletes[i]; + this.writeByte(1); // mark the start of an entry. + this.writeByte(2); // delete + this.writeShort(item['@rid'].cluster); + this.writeLong(item['@rid'].position); + this.writeByte(constants.RECORD_TYPES[item['@type'] || 'd'] || 100); // document by default + this.writeInt(item['@version'] || 0); + } + this.writeByte(0); // no more documents + this.writeString(''); + }, + reader: function () { + this + .readStatus('status') + .readInt('totalCreated') + .readArray('created', function (data) { + var items = [], + i; + for (i = 0; i < data.totalCreated; i++) { + items.push(function () { + this + .readShort('tmpCluster') + .readLong('tmpPosition') + .readShort('cluster') + .readLong('position'); + }); + } + return items; + }) + .readInt('totalUpdated') + .readArray('updated', function (data) { + var items = [], + i; + for (i = 0; i < data.totalUpdated; i++) { + items.push(function () { + this + .readShort('cluster') + .readLong('position') + .readInt('version'); + }); + } + return items; + }); + + this.readInt('totalChanges') + .readArray('changes', function (data) { + var items = [], + i; + for (i = 0; i < data.totalChanges; i++) { + items.push(function () { + this + .readLong('uuidHigh') + .readLong('uuidLow') + .readLong('fileId') + .readLong('pageIndex') + .readInt('pageOffset'); + }); + } + return items; + }); + } +}); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/serializer.js b/lib/transport/binary/protocol28/serializer.js new file mode 100644 index 0000000..1bb1d3e --- /dev/null +++ b/lib/transport/binary/protocol28/serializer.js @@ -0,0 +1,137 @@ +"use strict"; + +var RecordID = require('../../../recordid'); + +/** + * Serialize a record and return it as a buffer. + * + * @param {Object} content The record to serialize. + * @return {Buffer} The buffer containing the content. + */ +function encodeRecordData (content) { + return new Buffer(serializeDocument(content), 'utf8'); +} + +/** + * Serialize a document. + * + * @param {Object} document The document to serialize. + * @param {Boolean} isMap Whether to serialize the document as a map. + * @return {String} The serialized document. + */ +function serializeDocument (document, isMap) { + var result = '', + className = '', + fieldNames = Object.keys(document), + totalFields = fieldNames.length, + fieldWrap, value, field, i; + + for (i = 0; i < totalFields; i++) { + field = fieldNames[i]; + value = document[field]; + if (field === '@class') { + className = value; + } + else if (field.charAt(0) === '@') { + continue; + } + else { + if (isMap) { + fieldWrap = '"'; + } + else { + fieldWrap = ''; + } + result += fieldWrap + field + fieldWrap + ':' + serializeValue(value) + ','; + } + } + + if (className !== '') { + result = className + '@' + result; + } + + if (result[result.length - 1] === ',') { + result = result.slice(0, -1); + } + + return result; +} + +/** + * Serialize a given value according to its type. + * @param {Object} value The value to serialize. + * @return {String} The serialized value. + */ +function serializeValue (value) { + var type = typeof value; + if (type === 'string') { + return '"' + value.replace(/\\/, "\\\\").replace(/"/g, '\\"') + '"'; + } + else if (type === 'number') { + return ~value.toString().indexOf('.') ? value + 'f' : value; + } + else if (type === 'boolean') { + return value ? true : false; + } + else if (Object.prototype.toString.call(value) === '[object Date]') { + return value.getTime() + 't'; + } + else if (Array.isArray(value)) { + return serializeArray(value); + } + else if (value === Object(value)) { + return serializeObject(value); + } + else { + return ''; + } +} + + +/** + * Serialize an array of values. + * @param {Array} value The value to serialize. + * @return {String} The serialized value. + */ +function serializeArray (value) { + var result = '[', i, limit; + for (i = 0, limit = value.length; i < limit; i++) { + if (value[i] === Object(value[i])) { + result += serializeObject(value[i]); + } + else { + result += serializeValue(value[i]); + } + if (i < limit - 1) { + result += ','; + } + } + result += ']'; + return result; +} + +/** + * Serialize an object. + * @param {Object} value The value to serialize. + * @return {String} The serialized value. + */ +function serializeObject (value) { + if (value instanceof RecordID) { + return value.toString(); + } + else if (value['@type'] === 'd') { + return '(' + serializeDocument(value, false) + ')'; + } + else { + return '{' + serializeDocument(value, true) + '}'; + } +} + + + + +// export the public methods + +exports.serializeDocument = serializeDocument; +exports.serializeValue = serializeValue; +exports.encodeRecordData = encodeRecordData; \ No newline at end of file diff --git a/lib/transport/binary/protocol28/writer.js b/lib/transport/binary/protocol28/writer.js new file mode 100644 index 0000000..5c21cd2 --- /dev/null +++ b/lib/transport/binary/protocol28/writer.js @@ -0,0 +1,123 @@ +"use strict"; + +var Long = require('../../../long').Long, + constants = require('./constants'); + +/** + * Parse data to 4 bytes which represents integer value. + * + * @fixme this is a super misleading function name and comment! + * + * @param {Mixed} data The data. + * @return {Buffer} The buffer containing the data. + */ +function writeByte (data) { + return new Buffer([data]); +} + +/** + * Parse data to 4 bytes which represents integer value. + * + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +function writeInt (data) { + var buf = new Buffer(constants.BYTES_INT); + buf.writeInt32BE(data, 0); + return buf; +} + +/** + * Parse data to 8 bytes which represents a long value. + * + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +function writeLong (data) { + var buf = new Buffer(constants.BYTES_LONG), + value = Long.fromNumber(data); + + buf.fill(0); + buf.writeInt32BE(value.high_, 0); + buf.writeInt32BE(value.low_, constants.BYTES_INT); + + return buf; +} + +/** + * Parse data to 2 bytes which represents short value. + * + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +function writeShort (data) { + var buf = new Buffer(constants.BYTES_SHORT); + buf.writeInt16BE(data, 0); + return buf; +} + +/** + * Write bytes to a buffer + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +function writeBytes (data) { + var length = data.length, + buf = new Buffer(constants.BYTES_INT + length); + buf.writeInt32BE(length, 0); + data.copy(buf, constants.BYTES_INT); + return buf; +} + +/** + * Parse string data to buffer with UTF-8 encoding. + * + * @param {Mixed} data The data to write. + * @return {Buffer} The buffer containing the data. + */ +function writeString (data) { + if (data === null) { + return writeInt(-1); + } + var stringBuf = encodeString(data), + length = stringBuf.length, + buf = new Buffer(constants.BYTES_INT + length); + buf.writeInt32BE(length, 0); + stringBuf.copy(buf, constants.BYTES_INT, 0, stringBuf.length); + return buf; +} + +function encodeString (data) { + var length = data.length, + output = new Buffer(length * 3), // worst case, all chars could require 3-byte encodings. + j = 0, // index output + i, c; + + for (i = 0; i < length; i++) { + c = data.charCodeAt(i); + if (c < 0x80) { + // 7-bits done in one byte. + output[j++] = c; + } + else if (c < 0x800) { + // 8-11 bits done in 2 bytes + output[j++] = (0xC0 | c >> 6); + output[j++] = (0x80 | c & 0x3F); + } + else { + // 12-16 bits done in 3 bytes + output[j++] = (0xE0 | c >> 12); + output[j++] = (0x80 | c >> 6 & 0x3F); + output[j++] = (0x80 | c & 0x3F); + } + } + return output.slice(0, j); +} + +exports.writeByte = writeByte; +exports.writeBoolean = writeByte; +exports.writeBytes = writeBytes; +exports.writeShort = writeShort; +exports.writeInt = writeInt; +exports.writeLong = writeLong; +exports.writeString = writeString; \ No newline at end of file diff --git a/test/core/jwt.js b/test/core/jwt.js new file mode 100644 index 0000000..41d9f4d --- /dev/null +++ b/test/core/jwt.js @@ -0,0 +1,25 @@ +describe.only('JWT', function () { + var server; + before(function () { + server = new LIB.Server({ + host: TEST_SERVER_CONFIG.host, + port: TEST_SERVER_CONFIG.port, + username: TEST_SERVER_CONFIG.username, + password: TEST_SERVER_CONFIG.password, + transport: 'binary', + useToken: true + }); + }) + describe('Server::connect()', function () { + it('should connect to the server using a JWT', function () { + return server.list() + .then(function (result) { + var db = server.use('testdb_bug_110'); + return db.select().from('OUser').token('12345678').all(); + }) + .then(function (result) { + console.log(result); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/server/server-test.js b/test/server/server-test.js index d41fa29..28873ec 100644 --- a/test/server/server-test.js +++ b/test/server/server-test.js @@ -1,84 +1,85 @@ var errors = LIB.errors; - -describe('Server::create()', function () { - it("should create a new database", function () { - return TEST_SERVER.create({ - name: 'testdb_server', - type: 'graph', - storage: 'memory' - }) - .then(function (db) { - db.name.should.equal('testdb_server'); +describe("Server", function () { + describe('Server::create()', function () { + it("should create a new database", function () { + return TEST_SERVER.create({ + name: 'testdb_server', + type: 'graph', + storage: 'memory' + }) + .then(function (db) { + db.name.should.equal('testdb_server'); + }); }); }); -}); -describe('Server::freeze()', function () { - it("should freeze", function () { - return TEST_SERVER.freeze("testdb_server") - .then(function (response) { - response.should.be.true; + describe('Server::freeze()', function () { + it("should freeze", function () { + return TEST_SERVER.freeze("testdb_server") + .then(function (response) { + response.should.be.true; + }); }); }); -}); -describe('Server::release()', function () { - it("should release", function () { - return TEST_SERVER.release("testdb_server") - .then(function (response) { - response.should.be.true; + describe('Server::release()', function () { + it("should release", function () { + return TEST_SERVER.release("testdb_server") + .then(function (response) { + response.should.be.true; + }); }); }); -}); -describe('Server::list()', function () { - it("should list the existing databases", function () { - return TEST_SERVER.list() - .then(function (dbs) { - dbs.length.should.be.above(0); - dbs.forEach(function (db) { - db.should.be.an.instanceOf(LIB.Db); + describe('Server::list()', function () { + it("should list the existing databases", function () { + return TEST_SERVER.list() + .then(function (dbs) { + dbs.length.should.be.above(0); + dbs.forEach(function (db) { + db.should.be.an.instanceOf(LIB.Db); + }); }); }); }); -}); -describe('Server::exists()', function () { - it("should confirm an existing database exists", function () { - return TEST_SERVER.exists('testdb_server') - .then(function (exists) { - exists.should.be.true; + describe('Server::exists()', function () { + it("should confirm an existing database exists", function () { + return TEST_SERVER.exists('testdb_server') + .then(function (exists) { + exists.should.be.true; + }); }); - }); - it("should confirm a missing database does not exist", function () { - return TEST_SERVER.exists('a_missing_database') - .then(function (exists) { - exists.should.be.false; + it("should confirm a missing database does not exist", function () { + return TEST_SERVER.exists('a_missing_database') + .then(function (exists) { + exists.should.be.false; + }); }); }); -}); -describe('Server::delete()', function () { - it("should delete a database", function () { - return TEST_SERVER.drop({ - name: 'testdb_server', - type: 'graph', - storage: 'memory' - }) - .then(function (response) { - response.should.be.true; + describe('Server::delete()', function () { + it("should delete a database", function () { + return TEST_SERVER.drop({ + name: 'testdb_server', + type: 'graph', + storage: 'memory' + }) + .then(function (response) { + response.should.be.true; + }); }); }); -}); -describe('Server::config.list', function () { - it("should list the server config", function () { - return TEST_SERVER.config.list() - .then(function (config) { - config.should.have.property('db.pool.min'); + describe('Server::config.list', function () { + it("should list the server config", function () { + return TEST_SERVER.config.list() + .then(function (config) { + config.should.have.property('db.pool.min'); + }); }); }); -}); -describe('Server::config.get', function () { - it("should get a server config key", function () { - return TEST_SERVER.config.get('db.pool.min') - .then(function (value) { - value.should.have.type('string'); + describe('Server::config.get', function () { + it("should get a server config key", function () { + return TEST_SERVER.config.get('db.pool.min') + .then(function (value) { + value.should.have.type('string'); + }); }); }); }); From 37e4e61a883076562ff888808e4bd0dc5667f60f Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 15 Dec 2014 19:33:42 +0000 Subject: [PATCH 180/308] frustration --- lib/db/index.js | 3 ++- lib/recordid.js | 3 +++ lib/server/index.js | 5 +++++ lib/transport/binary/protocol28/operation-queue.js | 1 - lib/transport/binary/protocol28/operation.js | 2 +- lib/transport/binary/protocol28/operations/connect.js | 4 +--- lib/transport/binary/protocol28/operations/db-open.js | 5 +---- .../binary/protocol28/operations/record-load.js | 4 ++-- test/core/jwt.js | 11 ++++++++--- test/index.js | 3 ++- 10 files changed, 25 insertions(+), 16 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index 1289625..2e8d9e4 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -48,6 +48,7 @@ Db.prototype.configure = function (config) { this.storage = ((config.storage === 'plocal' || config.storage === 'memory') ? config.storage : 'plocal'); this.token = null; + this.useToken = config.useToken != null ? config.useToken : this.server.useToken; this.username = config.username || 'admin'; this.password = config.password || 'admin'; this.dataSegments = []; @@ -78,7 +79,7 @@ Db.prototype.open = function () { type: this.type, username: this.username, password: this.password, - useToken: this.server.useToken + useToken: this.useToken }) .bind(this) .then(function (response) { diff --git a/lib/recordid.js b/lib/recordid.js index 3d2e449..10242fd 100644 --- a/lib/recordid.js +++ b/lib/recordid.js @@ -87,6 +87,9 @@ RecordID.prototype.equals = function (rid) { else if (typeof rid === 'string') { return this.toString() === rid; } + else if (rid && rid['@rid']) { + return this.equals(rid['@rid']); + } else if (rid instanceof RecordID) { return rid.cluster === this.cluster && rid.position === this.position; } diff --git a/lib/server/index.js b/lib/server/index.js index 59941d6..85d9624 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -41,6 +41,7 @@ module.exports = Server; * @return {Server} The configured server object. */ Server.prototype.configure = function (config) { + this.useToken = config.useToken || false; this.configureLogger(config.logger || {}); this.configureTransport(config); }; @@ -132,6 +133,10 @@ Server.prototype.use = function (config) { throw new errors.Config('Cannot use a database without a name.'); } + if (config.useToken == null) { + config.useToken = this.useToken; + } + return new Db(config); }; diff --git a/lib/transport/binary/protocol28/operation-queue.js b/lib/transport/binary/protocol28/operation-queue.js index d2d28a1..d29b353 100644 --- a/lib/transport/binary/protocol28/operation-queue.js +++ b/lib/transport/binary/protocol28/operation-queue.js @@ -129,7 +129,6 @@ OperationQueue.prototype.handleChunk = function (data) { } else { this.remaining = buffer.slice(offset); - console.log("remaining", this.remaining); } }; diff --git a/lib/transport/binary/protocol28/operation.js b/lib/transport/binary/protocol28/operation.js index b1bcbda..3c9027f 100644 --- a/lib/transport/binary/protocol28/operation.js +++ b/lib/transport/binary/protocol28/operation.js @@ -276,7 +276,7 @@ Operation.prototype.readStatus = function (fieldName, reader) { this.stack.push(data[fieldName]); }); this.readByte('code'); - if (this.opCode !== 2 && this.opCode !== 3) { + if (this.opCode !== 2 && this.opCode !== 3 && this.data.token && this.data.token.length) { this.readInt('sessionId'); this.readBytes('token', next); } diff --git a/lib/transport/binary/protocol28/operations/connect.js b/lib/transport/binary/protocol28/operations/connect.js index 330c86a..6649682 100644 --- a/lib/transport/binary/protocol28/operations/connect.js +++ b/lib/transport/binary/protocol28/operations/connect.js @@ -24,9 +24,7 @@ module.exports = Operation.extend({ this .readStatus('status') .readInt('sessionId') - .readBytes('token', function (data) { - console.log('SERVER TOKEN', data); - }); + .readBytes('token'); } diff --git a/lib/transport/binary/protocol28/operations/db-open.js b/lib/transport/binary/protocol28/operations/db-open.js index 122d88a..35fa081 100644 --- a/lib/transport/binary/protocol28/operations/db-open.js +++ b/lib/transport/binary/protocol28/operations/db-open.js @@ -7,7 +7,6 @@ module.exports = Operation.extend({ id: 'REQUEST_DB_OPEN', opCode: 3, writer: function () { - console.log('dbop', this.data); this .writeByte(this.opCode) .writeInt(this.data.sessionId || -1) @@ -26,9 +25,7 @@ module.exports = Operation.extend({ this .readStatus('status') .readInt('sessionId') - .readBytes('token', function (data) { - console.log('dbToken', data.token); - }); + .readBytes('token'); this .readShort('totalClusters') diff --git a/lib/transport/binary/protocol28/operations/record-load.js b/lib/transport/binary/protocol28/operations/record-load.js index ea6834c..151bbc8 100644 --- a/lib/transport/binary/protocol28/operations/record-load.js +++ b/lib/transport/binary/protocol28/operations/record-load.js @@ -71,9 +71,9 @@ module.exports = Operation.extend({ records.push(record); this.stack.push(record); this - .readString('content') + .readChar('type') .readInt('version') - .readChar('type', function (data, fieldName) { + .readString('content', function (data, fieldName) { data.cluster = this.data.cluster; data.position = this.data.position; if (data[fieldName] === 'd') { diff --git a/test/core/jwt.js b/test/core/jwt.js index 41d9f4d..a6bcfae 100644 --- a/test/core/jwt.js +++ b/test/core/jwt.js @@ -1,4 +1,4 @@ -describe.only('JWT', function () { +describe.skip('JWT', function () { var server; before(function () { server = new LIB.Server({ @@ -10,11 +10,16 @@ describe.only('JWT', function () { useToken: true }); }) - describe('Server::connect()', function () { + describe.skip('JWT Server::connect()', function () { it('should connect to the server using a JWT', function () { + var db; return server.list() .then(function (result) { - var db = server.use('testdb_bug_110'); + db = server.use('jwt_test'); + return db.open(); + }) + .then(function (result) { + console.log(result.token); return db.select().from('OUser').token('12345678').all(); }) .then(function (result) { diff --git a/test/index.js b/test/index.js index 5cc2d67..810c714 100644 --- a/test/index.js +++ b/test/index.js @@ -33,7 +33,7 @@ global.REST_SERVER = new LIB.Server({ }); // Uncomment the following lines to enable debug logging -// global.TEST_SERVER.logger.debug = console.log.bind(console, '[ORIENTDB]'); +global.TEST_SERVER.logger.debug = console.log.bind(console, '[ORIENTDB]'); // global.REST_SERVER.logger.debug = console.log.bind(console, '[ORIENTDB]'); @@ -47,6 +47,7 @@ function createTestDb(server, context, name, type) { type = type || 'memory'; return server.exists(name, type) .then(function (exists) { + console.log('here'); if (exists) { return server.drop({ name: name, From 62644e7ab3921fe6781d922e83723280f7e90b21 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 17 Dec 2014 00:41:58 +0000 Subject: [PATCH 181/308] enable javascript in server config --- ci/orientdb-server-config.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/orientdb-server-config.xml b/ci/orientdb-server-config.xml index df603d2..c5ed932 100644 --- a/ci/orientdb-server-config.xml +++ b/ci/orientdb-server-config.xml @@ -36,7 +36,7 @@ - + From a3f9a617cde4e37ad1d98f6c2a1f33bf5c1063c9 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 17 Dec 2014 00:42:19 +0000 Subject: [PATCH 182/308] update mocha --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 557fbc8..16112d3 100755 --- a/package.json +++ b/package.json @@ -45,11 +45,11 @@ }, "dependencies": { "bluebird": "~1.2.3", - "yargs": "~1.2.1", - "request": "~2.34.0" + "request": "~2.34.0", + "yargs": "~1.2.1" }, "devDependencies": { - "mocha": "~1.18.2", + "mocha": "^2.0.1", "should": "~3.3.1", "expect.js": "~0.3.1", "istanbul": "~0.2.7", From 671f2e04ab9dcb8cb4e278212a15df274ac8db46 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 17 Dec 2014 00:42:47 +0000 Subject: [PATCH 183/308] fix JWT / protocol 28 support --- lib/db/index.js | 9 +- lib/server/index.js | 17 +++ lib/transport/binary/connection.js | 25 ++-- lib/transport/binary/index.js | 16 ++- .../binary/protocol28/operations/connect.js | 2 - .../protocol28/operations/record-load.js | 2 +- test/bugs/110-connection-lifecycle.js | 58 ++++++-- test/core/jwt.js | 129 ++++++++++++++++-- test/db/vertex-test.js | 9 +- test/index.js | 3 +- test/transport/rest/rest-transport-test.js | 4 +- 11 files changed, 223 insertions(+), 51 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index 2e8d9e4..77bec3a 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -43,6 +43,9 @@ Db.prototype.configure = function (config) { this.name = config.name; this.server = config.server; + this.server.on('reset', function () { + this.sessionId = null; + }.bind(this)); this.type = (config.type === 'document' ? 'document' : 'graph'); @@ -70,7 +73,7 @@ Db.prototype.init = function () { * @promise {Db} The open db instance. */ Db.prototype.open = function () { - if (this.sessionId !== -1) { + if (this.sessionId != null && this.sessionId !== -1) { return Promise.resolve(this); } this.server.logger.debug('opening database connection to ' + this.name); @@ -104,7 +107,7 @@ Db.prototype.close = function () { return this.server.send('db-close') .bind(this) .then(function () { - this.sessionId = -1; + this.sessionId = null; return this; }); }; @@ -125,6 +128,7 @@ Db.prototype.send = function (operation, data) { data.token = data.token || this.token; data.sessionId = this.sessionId; data.database = this.name; + data.db = this; this.server.logger.debug('sending operation ' + operation + ' for database ' + this.name); return this.server.send(operation, data); }); @@ -174,6 +178,7 @@ Db.prototype.exec = function (query, options) { mode: options.mode || 's', fetchPlan: '', limit: -1, + token: options.token, language: options.language, class: options.class || 'com.orientechnologies.orient.core.sql.OCommandSQL' }; diff --git a/lib/server/index.js b/lib/server/index.js index 85d9624..ea5bfc1 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -34,6 +34,18 @@ Server.prototype.augment = utils.augment; module.exports = Server; +Object.defineProperty(Server.prototype, 'token', { + get: function () { + return this.transport.token; + }, + set: function (token) { + if (typeof token === 'string') { + token = new Buffer(token, 'base64'); + } + this.transport.token = token; + } +}); + /** * Configure the server instance. * @@ -46,6 +58,7 @@ Server.prototype.configure = function (config) { this.configureTransport(config); }; + /** * Configure the transport for the server. * @@ -59,6 +72,9 @@ Server.prototype.configureTransport = function (config) { else { this.transport = new BinaryTransport(config); } + this.transport.on('reset', function () { + this.emit('reset'); + }.bind(this)); return this; }; @@ -222,6 +238,7 @@ Server.prototype.drop = function (config) { */ Server.prototype.list = function () { return this.send('db-list') + .bind(this) .then(function (results) { var names = Object.keys(results.databases), total = names.length, diff --git a/lib/transport/binary/connection.js b/lib/transport/binary/connection.js index 41a356b..ce69149 100644 --- a/lib/transport/binary/connection.js +++ b/lib/transport/binary/connection.js @@ -19,7 +19,8 @@ function Connection (config) { this.enableRIDBags = config.enableRIDBags == null ? true : config.enableRIDBags; - + this.closing = false; + this.reconnectNow = false; this.protocol = null; this.queue = []; this.writes = []; @@ -85,20 +86,19 @@ Connection.prototype._sendOp = function (op, params) { op.reader(); var buffer = op.buffer(); - - if (self.socket) { + if (op.id === 'REQUEST_DB_CLOSE') { + self.reconnectNow = true; + self.socket.write(buffer, resolve); + return; + } + else if (self.socket) { self.socket.write(buffer); } else { self.writes.push(buffer); } - if (op.id === 'REQUEST_DB_CLOSE') { - resolve({}); - } - else { - self.queue.push([op, {resolve: resolve, reject: reject}]); - } + self.queue.push([op, {resolve: resolve, reject: reject}]); }); }; @@ -303,9 +303,16 @@ Connection.prototype.handleSocketError = function (err) { * @param {Error} err The error object, if any. */ Connection.prototype.handleSocketEnd = function (err) { + if (this.reconnectNow) { + this.reconnectNow = false; + this.destroySocket(); + this.emit('reconnectNow'); + return; + } if (this.closing) { this.closing = false; this.destroySocket(); + this.emit('close'); return; } err = new errors.Connection(1, err || 'Remote server closed the connection.'); diff --git a/lib/transport/binary/index.js b/lib/transport/binary/index.js index 113ba98..6fb82d5 100644 --- a/lib/transport/binary/index.js +++ b/lib/transport/binary/index.js @@ -20,6 +20,7 @@ function BinaryTransport (config) { EventEmitter.call(this); this.setMaxListeners(Infinity); this.configure(config || {}); + this.closing = false; } util.inherits(BinaryTransport, EventEmitter); @@ -93,13 +94,25 @@ BinaryTransport.prototype.configureConnection = function () { this.logger.debug('updating config...'); this.transportCluster = config; }.bind(this)); + this.connection.on('reconnectNow', function () { + this.sessionId = -1; + this.connecting = false; + this.connection.removeAllListeners(); + this.connection.cancel(new Error('Connection closed.')); + this.connection = false; + this.emit('reset'); + this.configureConnection(); + }.bind(this)); this.connection.on('error', function (err) { if (this.retries++ > this.maxRetries) { return this.emit('error', err); } this.sessionId = -1; this.connecting = false; - + this.connection.removeAllListeners(); + this.connection.cancel(err); + this.connection = false; + this.emit('reset'); this.configureConnection(); }.bind(this)); return this; @@ -192,6 +205,7 @@ BinaryTransport.prototype.send = function (operation, options) { * @return {BinaryTransport} the disconnected transport instance */ BinaryTransport.prototype.close = function () { + this.closing = true; (this.pool || this.connection).close(); return this; }; \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/connect.js b/lib/transport/binary/protocol28/operations/connect.js index 6649682..d0b823a 100644 --- a/lib/transport/binary/protocol28/operations/connect.js +++ b/lib/transport/binary/protocol28/operations/connect.js @@ -25,7 +25,5 @@ module.exports = Operation.extend({ .readStatus('status') .readInt('sessionId') .readBytes('token'); - - } }); \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operations/record-load.js b/lib/transport/binary/protocol28/operations/record-load.js index 151bbc8..577c588 100644 --- a/lib/transport/binary/protocol28/operations/record-load.js +++ b/lib/transport/binary/protocol28/operations/record-load.js @@ -76,7 +76,7 @@ module.exports = Operation.extend({ .readString('content', function (data, fieldName) { data.cluster = this.data.cluster; data.position = this.data.position; - if (data[fieldName] === 'd') { + if (data.type === 'd') { data.content = deserializer.deserialize(data.content); } this.stack.pop(); diff --git a/test/bugs/110-connection-lifecycle.js b/test/bugs/110-connection-lifecycle.js index 1022248..b05c96f 100644 --- a/test/bugs/110-connection-lifecycle.js +++ b/test/bugs/110-connection-lifecycle.js @@ -1,38 +1,68 @@ var Statement = require('../../lib/db/statement'); describe("Bug #110: Connection lifecycle", function () { + var server, db; before(function () { - return CREATE_TEST_DB(this, 'testdb_bug_110'); + server = new LIB.Server({ + host: TEST_SERVER_CONFIG.host, + port: TEST_SERVER_CONFIG.port, + username: TEST_SERVER_CONFIG.username, + password: TEST_SERVER_CONFIG.password, + transport: 'binary', + useToken: false + }); + db = server.use('testdb_bug_110'); + return CREATE_TEST_DB(this, 'testdb_bug_110') + .then(function () { + return db.select().from('OUser').limit(1).one(); // ensure the connection is established. + }); }); after(function () { return DELETE_TEST_DB('testdb_bug_110'); }); it('should not crash if the connection is interrupted', function () { - var promise = this.db.class.list(); - this.db.server.transport.connection.socket.emit('error', {errnum: 100, message: 'ENETDOWN'}); + var promise = db.class.list(); + db.server.transport.connection.socket.emit('error', {errnum: 100, message: 'ENETDOWN'}); + var counter = 0; return promise + .then(function (results) { + throw new Error('Should never happen!'); + }) + .error(function (err) { + counter++; + }) .bind(this) - .then(function (classes) { - return this.db.record.get('#5:0'); + .then(function () { + counter++; + return db.record.get('#5:0'); }) .then(function (rec) { - var promise = this.db.record.get('#5:1'); - this.db.server.transport.connection.socket.emit('error', {errnum: 104, message: 'ECONNRESET'}); + counter++; + var promise = db.record.get('#5:1'); + db.server.transport.connection.socket.emit('error', {errnum: 104, message: 'ECONNRESET'}); return promise; }) - .then(function (rec) { - (rec['@rid']+'').should.equal('#5:1'); + .then(function () { + throw new Error('Should never happen!'); + }) + .error(function (err) { + counter++; + }) + .finally(function () { + counter.should.equal(4); }); }); it('should close the database connection', function () { - return this.db.close() + return db.close() .bind(this) - .then(function (db) { - db.should.equal(this.db); + .then(function () { return db.record.get('#5:0'); }) - .catch(function (err) { - return this.db.record.get('#5:0'); + .then(function () { + throw new Error("Should never happen."); + }) + .error(function (err) { + return db.record.get('#5:0'); }) .then(function (rec) { (''+rec['@rid']).should.equal('#5:0'); diff --git a/test/core/jwt.js b/test/core/jwt.js index a6bcfae..83e3e25 100644 --- a/test/core/jwt.js +++ b/test/core/jwt.js @@ -1,4 +1,6 @@ -describe.skip('JWT', function () { +var Bluebird = require('bluebird'); + +describe('JWT', function () { var server; before(function () { server = new LIB.Server({ @@ -9,21 +11,120 @@ describe.skip('JWT', function () { transport: 'binary', useToken: true }); - }) - describe.skip('JWT Server::connect()', function () { - it('should connect to the server using a JWT', function () { - var db; + return CREATE_TEST_DB(this, 'testdb_jwt'); + }); + after(function () { + return DELETE_TEST_DB('testdb_jwt'); + }); + describe('JWT Server::connect()', function () { + var dbs; + before(function () { return server.list() - .then(function (result) { - db = server.use('jwt_test'); - return db.open(); - }) - .then(function (result) { - console.log(result.token); - return db.select().from('OUser').token('12345678').all(); + .then(function (items) { + dbs = items; + }); + }); + it('should connect to the server and get a token', function () { + server.token.should.be.an.instanceOf(Buffer); + server.token.length.should.be.above(0); + }); + it('should retrieve a list of databases', function () { + dbs.length.should.be.above(0); + }); + }); + describe('JWT Server::use()', function () { + var db; + before(function () { + db = server.use({ + name: 'testdb_jwt', + username: 'admin', + password: 'admin' + }); + return db.open(); + }) + it('should open a database and get a token', function () { + db.token.should.be.an.instanceOf(Buffer); + db.token.length.should.be.above(0); + }); + it('should return a different token from the server token', function () { + db.token.toString().should.not.equal(server.token.toString()); + }); + it('should execute commands using the token', function () { + return db.select().from('OUser').all() + .then(function (users) { + users.length.should.be.above(0); + }); + }); + }); + describe('JWT Database::query()', function () { + var db, admin, reader, writer; + before(function () { + db = server.use('testdb_jwt'); + return Bluebird.all([ + server.use({name: 'testdb_jwt', username: 'admin', password: 'admin'}).open(), + server.use({name: 'testdb_jwt', username: 'reader', password: 'reader'}).open(), + server.use({name: 'testdb_jwt', username: 'writer', password: 'writer'}).open() + ]) + .then(function (items) { + admin = items[0].token; + reader = items[1].token; + writer = items[2].token; + + admin.toString().should.not.equal(reader.toString()); + admin.toString().should.not.equal(writer.toString()); + writer.toString().should.not.equal(reader.toString()); + }); + }); + it('should not allow the reader to create a vertex', function () { + return db.create('VERTEX', 'V').set({foo: 'bar'}).token(reader).one() + .then(function () { + throw new Error('No, this should not happen'); }) - .then(function (result) { - console.log(result); + .catch(LIB.errors.RequestError, function (err) { + /permission/i.test(err.message).should.be.true; + }); + }); + it('should allow the reader to read from a class', function () { + return db.select().from('OUser').token(reader).all() + .then(function (users) { + users.length.should.be.above(0); + }); + }); + it('should allow the writer to create a vertex', function () { + return db.create('VERTEX', 'V').set({foo: 'bar'}).token(writer).one() + .then(function (item) { + item.foo.should.equal('bar'); + }); + }); + it('should allow the writer to read from a class', function () { + return db.select().from('OUser').token(writer).all() + .then(function (users) { + users.length.should.be.above(0); + }); + }); + it('should allow the admin to create a vertex', function () { + return db.create('VERTEX', 'V').set({foo: 'bar'}).token(admin).one() + .then(function (item) { + item.foo.should.equal('bar'); + }); + }); + it('should allow the admin to read from a class', function () { + return db.select().from('OUser').token(admin).all() + .then(function (users) { + users.length.should.be.above(0); + }); + }); + + it('should allow the default user to create a vertex', function () { + return db.create('VERTEX', 'V').set({foo: 'bar'}).one() + .then(function (item) { + item.foo.should.equal('bar'); + }); + }); + it('should allow the default user to read from a class', function () { + return db.select().from('OUser').all() + .then(function (users) { + users.length.should.be.above(0); }); }); }); diff --git a/test/db/vertex-test.js b/test/db/vertex-test.js index a1e2e36..1a2a46b 100644 --- a/test/db/vertex-test.js +++ b/test/db/vertex-test.js @@ -1,6 +1,7 @@ var Class = require('../../lib/db/class'); describe("Database API - Vertex", function () { + var created1, created2; before(function () { return CREATE_TEST_DB(this, 'testdb_dbapi_vertex'); }); @@ -14,7 +15,7 @@ describe("Database API - Vertex", function () { .bind(this) .then(function (vertex) { vertex['@rid'].should.be.an.instanceOf(LIB.RID); - this.created1 = vertex; + created1 = vertex; }); }); it('should create a vertex with some attributes', function () { @@ -28,20 +29,20 @@ describe("Database API - Vertex", function () { vertex['@rid'].should.be.an.instanceOf(LIB.RID); vertex.key1.should.equal('val1'); vertex.key2.should.equal('val2'); - this.created2 = vertex; + created2 = vertex; }); }); }); describe("Db::vertex.delete()", function () { it('should delete a vertex', function () { - return this.db.vertex.delete(this.created1) + return this.db.vertex.delete(created1) .bind(this) .then(function (count) { count.should.equal(1); }); }); it('should delete a vertex with properties', function () { - return this.db.vertex.delete(this.created2) + return this.db.vertex.delete(created2) .bind(this) .then(function (count) { count.should.equal(1); diff --git a/test/index.js b/test/index.js index 810c714..5cc2d67 100644 --- a/test/index.js +++ b/test/index.js @@ -33,7 +33,7 @@ global.REST_SERVER = new LIB.Server({ }); // Uncomment the following lines to enable debug logging -global.TEST_SERVER.logger.debug = console.log.bind(console, '[ORIENTDB]'); +// global.TEST_SERVER.logger.debug = console.log.bind(console, '[ORIENTDB]'); // global.REST_SERVER.logger.debug = console.log.bind(console, '[ORIENTDB]'); @@ -47,7 +47,6 @@ function createTestDb(server, context, name, type) { type = type || 'memory'; return server.exists(name, type) .then(function (exists) { - console.log('here'); if (exists) { return server.drop({ name: name, diff --git a/test/transport/rest/rest-transport-test.js b/test/transport/rest/rest-transport-test.js index f25b936..a7bd76f 100644 --- a/test/transport/rest/rest-transport-test.js +++ b/test/transport/rest/rest-transport-test.js @@ -22,10 +22,10 @@ describe("Rest Transport", function () { describe('REST Operations', function () { before(function () { - return CREATE_TEST_DB(this, 'testdb_rest', 'plocal'); + return CREATE_TEST_DB(this, 'testdb_rest'); }); after(function () { - return DELETE_TEST_DB('testdb_rest', 'plocal'); + return DELETE_TEST_DB('testdb_rest'); }); From fef3482d413b5e71ef1a2968f229bb1dca5848a3 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 17 Dec 2014 00:55:10 +0000 Subject: [PATCH 184/308] disable JWT tests on protocol versions prior to 28 --- test/core/jwt.js | 73 ++++++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/test/core/jwt.js b/test/core/jwt.js index 83e3e25..2057272 100644 --- a/test/core/jwt.js +++ b/test/core/jwt.js @@ -1,7 +1,14 @@ var Bluebird = require('bluebird'); - describe('JWT', function () { - var server; + var hasProtocolSupport = false, + server = null; + function ifSupportedIt (text, fn) { + it(text, function () { + if (hasProtocolSupport) { + return fn(); + } + }); + } before(function () { server = new LIB.Server({ host: TEST_SERVER_CONFIG.host, @@ -11,7 +18,11 @@ describe('JWT', function () { transport: 'binary', useToken: true }); - return CREATE_TEST_DB(this, 'testdb_jwt'); + return CREATE_TEST_DB(this, 'testdb_jwt') + .bind(this) + .then(function () { + hasProtocolSupport = this.db.server.transport.connection.protocolVersion >= 28; + }); }); after(function () { return DELETE_TEST_DB('testdb_jwt'); @@ -24,11 +35,11 @@ describe('JWT', function () { dbs = items; }); }); - it('should connect to the server and get a token', function () { + ifSupportedIt('should connect to the server and get a token', function () { server.token.should.be.an.instanceOf(Buffer); server.token.length.should.be.above(0); }); - it('should retrieve a list of databases', function () { + ifSupportedIt('should retrieve a list of databases', function () { dbs.length.should.be.above(0); }); }); @@ -42,14 +53,14 @@ describe('JWT', function () { }); return db.open(); }) - it('should open a database and get a token', function () { + ifSupportedIt('should open a database and get a token', function () { db.token.should.be.an.instanceOf(Buffer); db.token.length.should.be.above(0); }); - it('should return a different token from the server token', function () { + ifSupportedIt('should return a different token from the server token', function () { db.token.toString().should.not.equal(server.token.toString()); }); - it('should execute commands using the token', function () { + ifSupportedIt('should execute commands using the token', function () { return db.select().from('OUser').all() .then(function (users) { users.length.should.be.above(0); @@ -59,23 +70,25 @@ describe('JWT', function () { describe('JWT Database::query()', function () { var db, admin, reader, writer; before(function () { - db = server.use('testdb_jwt'); - return Bluebird.all([ - server.use({name: 'testdb_jwt', username: 'admin', password: 'admin'}).open(), - server.use({name: 'testdb_jwt', username: 'reader', password: 'reader'}).open(), - server.use({name: 'testdb_jwt', username: 'writer', password: 'writer'}).open() - ]) - .then(function (items) { - admin = items[0].token; - reader = items[1].token; - writer = items[2].token; + if (hasProtocolSupport) { + db = server.use('testdb_jwt'); + return Bluebird.all([ + server.use({name: 'testdb_jwt', username: 'admin', password: 'admin'}).open(), + server.use({name: 'testdb_jwt', username: 'reader', password: 'reader'}).open(), + server.use({name: 'testdb_jwt', username: 'writer', password: 'writer'}).open() + ]) + .then(function (items) { + admin = items[0].token; + reader = items[1].token; + writer = items[2].token; - admin.toString().should.not.equal(reader.toString()); - admin.toString().should.not.equal(writer.toString()); - writer.toString().should.not.equal(reader.toString()); - }); + admin.toString().should.not.equal(reader.toString()); + admin.toString().should.not.equal(writer.toString()); + writer.toString().should.not.equal(reader.toString()); + }); + } }); - it('should not allow the reader to create a vertex', function () { + ifSupportedIt('should not allow the reader to create a vertex', function () { return db.create('VERTEX', 'V').set({foo: 'bar'}).token(reader).one() .then(function () { throw new Error('No, this should not happen'); @@ -84,44 +97,44 @@ describe('JWT', function () { /permission/i.test(err.message).should.be.true; }); }); - it('should allow the reader to read from a class', function () { + ifSupportedIt('should allow the reader to read from a class', function () { return db.select().from('OUser').token(reader).all() .then(function (users) { users.length.should.be.above(0); }); }); - it('should allow the writer to create a vertex', function () { + ifSupportedIt('should allow the writer to create a vertex', function () { return db.create('VERTEX', 'V').set({foo: 'bar'}).token(writer).one() .then(function (item) { item.foo.should.equal('bar'); }); }); - it('should allow the writer to read from a class', function () { + ifSupportedIt('should allow the writer to read from a class', function () { return db.select().from('OUser').token(writer).all() .then(function (users) { users.length.should.be.above(0); }); }); - it('should allow the admin to create a vertex', function () { + ifSupportedIt('should allow the admin to create a vertex', function () { return db.create('VERTEX', 'V').set({foo: 'bar'}).token(admin).one() .then(function (item) { item.foo.should.equal('bar'); }); }); - it('should allow the admin to read from a class', function () { + ifSupportedIt('should allow the admin to read from a class', function () { return db.select().from('OUser').token(admin).all() .then(function (users) { users.length.should.be.above(0); }); }); - it('should allow the default user to create a vertex', function () { + ifSupportedIt('should allow the default user to create a vertex', function () { return db.create('VERTEX', 'V').set({foo: 'bar'}).one() .then(function (item) { item.foo.should.equal('bar'); }); }); - it('should allow the default user to read from a class', function () { + ifSupportedIt('should allow the default user to read from a class', function () { return db.select().from('OUser').all() .then(function (users) { users.length.should.be.above(0); From faff966ea7f0181fbb67f1a69eb7c3e0d4ffbc3f Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 17 Dec 2014 22:10:21 +0000 Subject: [PATCH 185/308] add Db.prototype.createUserContext(token) --- lib/db/index.js | 46 ++++++++++++++++++++++++++++++++++++++++++++++ test/core/jwt.js | 22 ++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/lib/db/index.js b/lib/db/index.js index 77bec3a..f9d396a 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -438,6 +438,52 @@ Db.prototype.let = function () { */ Db.prototype.escape = utils.escape; + +/** + * Create a context for a user, using their authentication token. + * The context includes the query builder methods, which will be executed + * on behalf of the user. + * + * @param {Buffer|String} token The authentication token. + * @return {Object} The object containing the query builder methods. + */ +Db.prototype.createUserContext = function (token) { + var db = this; + return { + /** + * Create a query instance for this database. + * + * @return {Query} The query instance. + */ + createQuery: function () { + return db.createQuery().token(token); + }, + create: function () { + return db.create.apply(db, arguments).token(token); + }, + select: function () { + return db.select.apply(db, arguments).token(token); + }, + traverse: function () { + return db.traverse.apply(db, arguments).token(token); + }, + insert: function () { + return db.select.apply(db, arguments).token(token); + }, + update: function () { + return db.update.apply(db, arguments).token(token); + }, + delete: function () { + return db.delete.apply(db, arguments).token(token); + }, + let: function () { + return db.let.apply(db, arguments).token(token); + }, + escape: utils.escape + }; +}; + + /** * Flatten an array of arrays */ diff --git a/test/core/jwt.js b/test/core/jwt.js index 2057272..c526132 100644 --- a/test/core/jwt.js +++ b/test/core/jwt.js @@ -140,5 +140,27 @@ describe('JWT', function () { users.length.should.be.above(0); }); }); + + describe('Db::createUserContext()', function () { + var context; + before(function () { + context = db.createUserContext(reader); + }); + ifSupportedIt('should create a user context', function () { + return context.select().from('OUser').all() + .then(function (users) { + users.length.should.be.above(1); + }); + }); + ifSupportedIt('should ensure that the token is used correctly', function () { + return context.create('VERTEX', 'V').set({greeting: 'hello world'}).one() + .then(function () { + throw new Error('No, this should not happen'); + }) + .catch(LIB.errors.RequestError, function (err) { + /permission/i.test(err.message).should.be.true; + }); + }); + }); }); }); \ No newline at end of file From 78826f9f2823499fdd39c5fce6812b6927816287 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 17 Dec 2014 23:08:04 +0000 Subject: [PATCH 186/308] fix tests on 1.7.10 --- test/core/jwt.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/core/jwt.js b/test/core/jwt.js index c526132..1344197 100644 --- a/test/core/jwt.js +++ b/test/core/jwt.js @@ -144,7 +144,9 @@ describe('JWT', function () { describe('Db::createUserContext()', function () { var context; before(function () { - context = db.createUserContext(reader); + if (hasProtocolSupport) { + context = db.createUserContext(reader); + } }); ifSupportedIt('should create a user context', function () { return context.select().from('OUser').all() From 277a733064e60a7b88837343c3cea2e95d381347 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 19 Dec 2014 09:50:33 +0000 Subject: [PATCH 187/308] add further perf tests for deserializer [ci skip] --- .../binary/protocol19/deserializer-test.js | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/test/transport/binary/protocol19/deserializer-test.js b/test/transport/binary/protocol19/deserializer-test.js index 359f162..728cefd 100644 --- a/test/transport/binary/protocol19/deserializer-test.js +++ b/test/transport/binary/protocol19/deserializer-test.js @@ -1,4 +1,5 @@ var deserializer = require(LIB_ROOT + '/transport/binary/protocol19/deserializer'); +var serializer = require(LIB_ROOT + '/transport/binary/protocol19/serializer'); describe("Deserializer", function () { it('should go fast!', function () { @@ -17,6 +18,60 @@ describe("Deserializer", function () { console.log('Done in ' + total + 's, ', (limit / total).toFixed(3), 'documents / sec', (((size / total) / 1024) / 1024).toFixed(3), ' Mb / sec') }); + it('should go fast, using simple string keys', function () { + var record = {}; + for (var k = 0; k < 15; k++) { + record["name" + k] = "Luca" + k; + } + + var limit = 100000, + input = serializer.serializeDocument(record), + size = input.length * limit, + start = Date.now(); + + for (var i = 0; i < limit; i++) { + deserializer.deserialize(input); + } + + var stop = Date.now(), + total = (stop - start) / 1000; + + console.log('Done in ' + total + 's, ', (limit / total).toFixed(3), 'documents / sec', (((size / total) / 1024) / 1024).toFixed(3), ' Mb / sec') + }); + + + it('should go fast, using more complex keys', function () { + var record = {}; + record["name"] = "john"; + record["surname"] = "wood"; + record["age"] = 20; + record["born"] = new Date(); + record["money"] = 10.0; + record["address"] = "somewhere bla bla"; + record["active"] = true; + record["timestamp"] = 20; + record["other1"] = "other1"; + record["other2"] = 2; + record["other3"] = 3; + + var limit = 100000, + input = serializer.serializeDocument(record), + size = input.length * limit, + start = Date.now(); + + for (var i = 0; i < limit; i++) { + deserializer.deserialize(input); + } + + var stop = Date.now(), + total = (stop - start) / 1000; + + console.log('Done in ' + total + 's, ', (limit / total).toFixed(3), 'documents / sec', (((size / total) / 1024) / 1024).toFixed(3), ' Mb / sec') + }); + + + + describe('deserialize()', function () { it('should parse a very simple record', function () { var input = 'OUser@foo:123,baz:"bazx\\"za",int:1234,true:true,false:false,null:null,date:123456a,rid:#12:10,array:[1,2,3,4,5],twice:"\\"127.0.0.1\\""'; From 6b8c40e7f1f195b591b510884a8e05c11b53f724 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 19 Dec 2014 20:25:54 +0000 Subject: [PATCH 188/308] add ridbag warning --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ac0006..ab31481 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,11 @@ Official [orientdb](http://www.orientechnologies.com/orientdb/) driver for node. Oriento aims to work with version 1.7.1 of orientdb and later. While it may work with earlier versions, they are not currently supported, [pull requests are welcome!](./CONTRIBUTING.md) - +> **IMPORTANT**: Oriento does not currently support OrientDB's Tree Based [RIDBag](https://github.com/orientechnologies/orientdb/wiki/RidBag) feature because it relies on making additional network requests. +> This means that by default, the result of e.g. `JSON.stringify(record)` for a record with up to 119 edges will be very different from a record with 120+ edges. +> This can lead to very nasty surprises which may not manifest themselves during development but could appear at any time in production. +> There is an [open issue](https://github.com/orientechnologies/orientdb/issues/2315) for this in OrientDB, until that gets fixed, it is **strongly recommended** that you set `RID_BAG_EMBEDDED_TO_SBTREEBONSAI_THRESHOLD` to a very large value, e.g. 2147483647. +> Please see the [relevant section in the OrientDB manual](http://www.orientechnologies.com/docs/2.0/orientdb.wiki/RidBag.html#configuration) for more information. # Installation From 43bd66b96e897ddd6f78e72751fcb5bc7db8ee9a Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 19 Dec 2014 20:43:20 +0000 Subject: [PATCH 189/308] deprecate db.edge and db.vertex --- README.md | 44 ++++++++++++++++++++------------------------ lib/db/index.js | 8 ++++++-- lib/utils.js | 28 ++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index ab31481..c78748a 100644 --- a/README.md +++ b/README.md @@ -311,22 +311,6 @@ db.record.delete('#1:1') }); ``` -### Transactions - -```js -db.begin() -.create({'@class': 'MyClass', name: 'me'}) -.create({'@class': 'MyOtherClass', name: 'wat?'}) -.update(myRecord) -.delete(someOtherRecord) -.commit() -.then(function (results) { - console.log('Created ', results.created); - console.log('Updated ', results.updated); - console.log('Deleted ', results.deleted); -}) -``` - ### Listing all the classes in the database ```js @@ -438,7 +422,7 @@ db.index.get('MyClass.myProp') ### Creating a new, empty vertex ```js -db.vertex.create('V') +db.create('VERTEX', 'V').one() .then(function (vertex) { console.log('created vertex', vertex); }); @@ -447,11 +431,12 @@ db.vertex.create('V') ### Creating a new vertex with some properties ```js -db.vertex.create({ - '@class': 'V', +db.create('VERTEX', 'V') +.set({ key: 'value', foo: 'bar' }) +.one() .then(function (vertex) { console.log('created vertex', vertex); }); @@ -459,7 +444,9 @@ db.vertex.create({ ### Deleting a vertex ```js -db.vertex.delete('#12:12') +db.delete('VERTEX') +.where('@rid = #12:12') +.one() .then(function (count) { console.log('deleted ' + count + ' vertices'); }); @@ -468,7 +455,10 @@ db.vertex.delete('#12:12') ### Creating a simple edge between vertices ```js -db.edge.from('#12:12').to('#12:13').create('E') +db.create('EDGE', 'E') +.from('#12:12') +.to('#12:13') +.one() .then(function (edge) { console.log('created edge:', edge); }); @@ -478,11 +468,14 @@ db.edge.from('#12:12').to('#12:13').create('E') ### Creating an edge with properties ```js -db.edge.from('#12:12').to('#12:13').create({ - '@class': 'E', +db.create('EDGE', 'E') +.from('#12:12') +.to('#12:13') +.set({ key: 'value', foo: 'bar' }) +.one() .then(function (edge) { console.log('created edge:', edge); }); @@ -491,7 +484,10 @@ db.edge.from('#12:12').to('#12:13').create({ ### Deleting an edge between vertices ```js -db.edge.from('#12:12').to('#12:13').delete({ +db.delete('EDGE', 'E') +.from('#12:12') +.to('#12:13') +.scalar() .then(function (count) { console.log('deleted ' + count + ' edges'); }); diff --git a/lib/db/index.js b/lib/db/index.js index f9d396a..0e938b3 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -22,8 +22,12 @@ function Db (config) { this.augment('cluster', require('./cluster')); this.augment('class', require('./class')); this.augment('record', require('./record')); - this.augment('vertex', require('./vertex')); - this.augment('edge', require('./edge')); + utils.deprecate(this, 'vertex', 'db.vertex.* is deprecated, use the query builder instead!', function () { + this.augment('vertex', require('./vertex')); + }); + utils.deprecate(this, 'edge', 'db.edge.* is deprecated, use the query builder instead!', function () { + this.augment('edge', require('./edge')); + }); this.augment('index', require('./index/index')); } diff --git a/lib/utils.js b/lib/utils.js index 68623ba..3a34e21 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -205,4 +205,32 @@ exports.jsonify = function (value, indentlevel) { } return value; }, indentlevel); +}; + + +/** + * Define a deprecated method or property. + * + * A warning message will be displayed the first time the method is called, regardless of the object. + * + * @param {Object} context The context for the method. + * @param {String} name The name of the deprecated method. + * @param {String} message The message to display. + * @param {Function} fn The function to call, it should restore the real property. + */ +exports.deprecate = function (context, name, message, fn) { + var shown = false; + Object.defineProperty(context, name, { + configurable: true, + enumerable: true, + get: function () { + if (!shown) { + console.warn(message); + shown = true; + } + delete this[name]; + fn.call(this, name); + return this[name]; + } + }); }; \ No newline at end of file From 81bcc710eebb521c39f23c3193d05cce7261d115 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 19 Dec 2014 20:43:52 +0000 Subject: [PATCH 190/308] update package.json for 0.7.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 16112d3..e359f1e 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node", "node.js" ], - "version": "0.6.0", + "version": "0.7.0", "author": { "name": "Charles Pick", "email": "charles@codemix.com" From 8da445ecd1a8cad3ed09b9b89430aaf122951591 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 23 Dec 2014 21:17:16 +0000 Subject: [PATCH 191/308] resolve #175 --- test/bugs/175-fetchplan-depth.js | 198 +++++++++++++++++++++++++++++++ test/core/jwt.js | 2 +- 2 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 test/bugs/175-fetchplan-depth.js diff --git a/test/bugs/175-fetchplan-depth.js b/test/bugs/175-fetchplan-depth.js new file mode 100644 index 0000000..d3a986d --- /dev/null +++ b/test/bugs/175-fetchplan-depth.js @@ -0,0 +1,198 @@ +var Promise = require('bluebird'); + +describe("Bug #175: Fetchplan depth", function () { + var hasProtocolSupport; + function ifSupportedIt (text, fn) { + it(text, function () { + if (hasProtocolSupport) { + return fn.call(this); + } + }); + } + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_175') + .bind(this) + .then(function () { + hasProtocolSupport = this.db.server.transport.connection.protocolVersion >= 28; + return Promise.all([ + this.db.class.create('SomeVertex', 'V'), + this.db.class.create('SomeEdge', 'E'), + this.db.class.create('OtherEdge', 'E') + ]); + }) + .spread(function (vertex, edge1, edge2) { + return Promise.all([ + vertex.property.create([ + { + name: 'owner', + type: 'link', + linkedClass: 'OUser' + }, + { + name: 'val', + type: 'string' + } + ]), + edge1.property.create([ + { + name: 'foo', + type: 'string' + } + ]), + edge2.property.create([ + { + name: 'greeting', + type: 'string' + } + ]) + ]); + }) + .then(function () { + return this.db + .let('thing1', function (s) { + s + .create('VERTEX', 'SomeVertex') + .set({ + owner: new LIB.RID('#5:0'), + val: 'a' + }); + }) + .let('thing2', function (s) { + s + .create('VERTEX', 'SomeVertex') + .set({ + owner: new LIB.RID('#5:1'), + val: 'b' + }); + }) + .let('edge1', function (s) { + s + .create('EDGE', 'OtherEdge') + .set({ + greeting: 'Hello World' + }) + .from('$thing2') + .to('$thing1'); + }) + .create('EDGE', 'SomeEdge') + .set({ + foo: 'bar' + }) + .from('$thing1') + .to('$thing2') + .commit() + .all(); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_175'); + }); + ifSupportedIt('should return records using a fetchplan', function () { + return this.db + .select() + .from('SomeVertex') + .fetch({'*': 1}) + .limit(1) + .one() + .then(function (doc) { + doc.should.have.property('owner'); + doc.owner.should.have.property('name'); + doc.should.have.property('out_SomeEdge'); + doc.should.have.property('in_OtherEdge'); + + // allow for difference between 1.7 and 2.0 + if (doc.out_SomeEdge instanceof LIB.Bag) { + doc.out_SomeEdge = doc.out_SomeEdge.all(); + } + else if (!Array.isArray(doc.out_SomeEdge)) { + doc.out_SomeEdge = [doc.out_SomeEdge]; + } + if (doc.in_OtherEdge instanceof LIB.Bag) { + doc.in_OtherEdge = doc.in_OtherEdge.all(); + } + else if (!Array.isArray(doc.in_OtherEdge)) { + doc.in_OtherEdge = [doc.in_OtherEdge]; + } + + + doc.out_SomeEdge.forEach(function (item) { + item.should.not.be.an.instanceOf(LIB.RID); + }); + doc.in_OtherEdge.forEach(function (item) { + item.should.not.be.an.instanceOf(LIB.RID); + }); + }); + }); + ifSupportedIt('should return records, excluding edges using a fetchplan', function () { + return this.db.query('SELECT FROM SomeVertex LIMIT 1', { + fetchPlan: '*:1 in_*:-2 out_*:-2' + }) + .spread(function (doc) { + doc.should.have.property('owner'); + doc.owner.should.have.property('name'); + doc.should.have.property('out_SomeEdge'); + doc.should.have.property('in_OtherEdge'); + + // allow for difference between 1.7 and 2.0 + if (doc.out_SomeEdge instanceof LIB.Bag) { + doc.out_SomeEdge = doc.out_SomeEdge.all(); + } + else if (!Array.isArray(doc.out_SomeEdge)) { + doc.out_SomeEdge = [doc.out_SomeEdge]; + } + if (doc.in_OtherEdge instanceof LIB.Bag) { + doc.in_OtherEdge = doc.in_OtherEdge.all(); + } + else if (!Array.isArray(doc.in_OtherEdge)) { + doc.in_OtherEdge = [doc.in_OtherEdge]; + } + + doc.out_SomeEdge.forEach(function (item) { + item.should.be.an.instanceOf(LIB.RID); + }); + doc.in_OtherEdge.forEach(function (item) { + item.should.be.an.instanceOf(LIB.RID); + }); + }); + }); + ifSupportedIt('should return records, excluding edges using a fetchplan via the query builder', function () { + return this.db + .select() + .from('SomeVertex') + .fetch({ + '*': 1, + 'in_*':-2, + 'out_*':-2 + }) + .limit(1) + .one() + .then(function (doc) { + doc.should.have.property('owner'); + doc.owner.should.have.property('name'); + doc.should.have.property('out_SomeEdge'); + doc.should.have.property('in_OtherEdge'); + + // allow for difference between 1.7 and 2.0 + if (doc.out_SomeEdge instanceof LIB.Bag) { + doc.out_SomeEdge = doc.out_SomeEdge.all(); + } + else if (!Array.isArray(doc.out_SomeEdge)) { + doc.out_SomeEdge = [doc.out_SomeEdge]; + } + if (doc.in_OtherEdge instanceof LIB.Bag) { + doc.in_OtherEdge = doc.in_OtherEdge.all(); + } + else if (!Array.isArray(doc.in_OtherEdge)) { + doc.in_OtherEdge = [doc.in_OtherEdge]; + } + + + doc.out_SomeEdge.forEach(function (item) { + item.should.be.an.instanceOf(LIB.RID); + }); + doc.in_OtherEdge.forEach(function (item) { + item.should.be.an.instanceOf(LIB.RID); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/core/jwt.js b/test/core/jwt.js index 1344197..3a3cb71 100644 --- a/test/core/jwt.js +++ b/test/core/jwt.js @@ -5,7 +5,7 @@ describe('JWT', function () { function ifSupportedIt (text, fn) { it(text, function () { if (hasProtocolSupport) { - return fn(); + return fn.call(this); } }); } From 31f34534ea4cdc6b49ef5c84743725ba1a250f57 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 23 Dec 2014 21:17:26 +0000 Subject: [PATCH 192/308] add .npmignore --- .npmignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .npmignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..8e92e97 --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +ci/environment +coverage + From ddad3f479ea41c0459509e6da1fb4441bde92b8a Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 5 Jan 2015 03:36:54 +0000 Subject: [PATCH 193/308] to() should support functions --- lib/db/statement.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index e4a825a..ab4b52e 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -368,7 +368,16 @@ Statement.prototype.buildStatement = function () { if (state.to && state.to.length) { statement.push('TO'); statement.push(state.to.map(function (item) { - if (typeof item === 'string') { + if (typeof item === 'function') { + var child = new Statement(self.db); + child._state.paramIndex = self._state.paramIndex; + item(child); + return '(' + child.toString() + ')'; + } + else if (item instanceof Statement) { + return '(' + item.toString() + ')'; + } + else if (typeof item === 'string') { if (/(\s+)/.test(item)) { return '(' + item + ')'; } From bae757d366c5cbaca88cd7c3a32de771d7a72df7 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 5 Jan 2015 03:37:25 +0000 Subject: [PATCH 194/308] prepare 0.7.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e359f1e..606b89d 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node", "node.js" ], - "version": "0.7.0", + "version": "0.7.1", "author": { "name": "Charles Pick", "email": "charles@codemix.com" From 332a2fd1f070156c08d832442dd15cad8e612d9e Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 9 Jan 2015 11:15:17 +0000 Subject: [PATCH 195/308] Issue #186: Duplicates test using exec() --- test/bugs/186-resolveReferences-duplicates.js | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 test/bugs/186-resolveReferences-duplicates.js diff --git a/test/bugs/186-resolveReferences-duplicates.js b/test/bugs/186-resolveReferences-duplicates.js new file mode 100644 index 0000000..9a975ad --- /dev/null +++ b/test/bugs/186-resolveReferences-duplicates.js @@ -0,0 +1,50 @@ +var Promise = require('bluebird'); + +describe.only("Bug #186: resolveReferences fail with duplicates present", function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_186') + .bind(this) + .then(function () { + return Promise.map([ + 'create class Person extends V', + 'create class Restaurant extends V', + 'create class Eat extends E', + + 'create vertex Person set name = "Luca"', + 'create vertex Person set name = "Heisenberg"', + 'create vertex Restaurant set name = "Dante", type = "Pizza"', + 'create edge Eat from (select from Person where name = "Luca") to (select from Restaurant where name = "Dante") SET someProperty="something"', + 'create edge Eat from (select from Person where name = "Heisenberg") to (select from Restaurant where name = "Dante") SET someProperty="something"', + ], this.db.query.bind(this.db)); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_186'); + }); + + describe('Query a graph with fetchplan', function () { + + it('should not return duplicates', function (done) { + // OrientBD returns duplicates: #13:0 and #13:1 (edges) + this.db.exec('select from Person', { fetchPlan: 'out_Eat:1 out_Eat.in:2' }).then(function (resultset) { + //console.log('results: ' + require('util').inspect(resultset.results) + '\n'); + + var seen = {}; + + try { + resultset.results.forEach(function(record){ + var rid = '#' + record.content.cluster + ':' + record.content.position; + seen.should.not.have.property(rid); + seen[rid] = record.content; + }); + done(); + } + catch (err) { + done(err); + } + + }); + }); + + }); +}); From b0c5cef9a0eca2238b4ddc1b8b933ef58f4770a7 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 9 Jan 2015 11:36:49 +0000 Subject: [PATCH 196/308] Issue #186: "soft" fix for duplicates returned with fetch plan depth 2 --- lib/db/record.js | 4 +- test/bugs/186-resolveReferences-duplicates.js | 143 ++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 test/bugs/186-resolveReferences-duplicates.js diff --git a/lib/db/record.js b/lib/db/record.js index 55792b3..2c05557 100644 --- a/lib/db/record.js +++ b/lib/db/record.js @@ -121,7 +121,9 @@ exports.resolveReferences = function (records) { for (i = 0; i < total; i++) { if (records[i]) { - collated[records[i]['@rid']] = records[i]; + if(!collated[records[i]['@rid']]){ + collated[records[i]['@rid']] = records[i]; + } } } diff --git a/test/bugs/186-resolveReferences-duplicates.js b/test/bugs/186-resolveReferences-duplicates.js new file mode 100644 index 0000000..589d12d --- /dev/null +++ b/test/bugs/186-resolveReferences-duplicates.js @@ -0,0 +1,143 @@ +var Promise = require('bluebird'); + +describe("Bug #186: resolveReferences fail with duplicates present", function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_186') + .bind(this) + .then(function () { + return Promise.map([ + 'create class Person extends V', + 'create class Restaurant extends V', + 'create class Eat extends E', + + 'create vertex Person set name = "Luca"', + 'create vertex Person set name = "Heisenberg"', + 'create vertex Restaurant set name = "Dante", type = "Pizza"', + 'create edge Eat from (select from Person where name = "Luca") to (select from Restaurant where name = "Dante") SET someProperty="something"', + 'create edge Eat from (select from Person where name = "Heisenberg") to (select from Restaurant where name = "Dante") SET someProperty="something"', + ], this.db.query.bind(this.db)); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_186'); + }); + + describe('Query a graph with fetchplan', function () { + function Person (data) { + if (!(this instanceof Person)) { + return new Person(data); + } + var keys = Object.keys(data), + length = keys.length, + key, i; + for (i = 0; i < length; i++) { + key = keys[i]; + this[key] = data[key]; + } + } + + function Restaurant (data) { + if (!(this instanceof Restaurant)) { + return new Restaurant(data); + } + var keys = Object.keys(data), + length = keys.length, + key, i; + for (i = 0; i < length; i++) { + key = keys[i]; + this[key] = data[key]; + } + } + + function Eat (data) { + if (!(this instanceof Eat)) { + return new Eat(data); + } + var keys = Object.keys(data), + length = keys.length, + key, i; + for (i = 0; i < length; i++) { + key = keys[i]; + this[key] = data[key]; + } + } + + function testPerson(person, verbose){ + (person.name === 'Luca' || person.name === 'Heisenberg').should.be.ok; + person.should.have.property('out_Eat'); + var edge = person.out_Eat; + if(edge instanceof LIB.Bag){ + edge = edge.all()[0]; + } + else if(edge instanceof Array){ + edge = edge[0]; // OrientDB 2.0 @this.toJSON() returns Array + } + if(verbose){ + console.log('edge (' + person.name + '): ' + require('util').inspect(edge, {depth: 3})); + } + edge.should.have.property('in'); + edge.in.should.have.property('name'); // breaks for depth 2: no name, as 'in' is a RID + edge.in.name.should.equal('Dante'); + } + + before(function () { + this.db.registerTransformer('Person', Person); + this.db.registerTransformer('Restaurant', Restaurant); + this.db.registerTransformer('Eat', Eat); + }); + + // Control tests + it('should return linked vertices when using @this.toJSON(fetchPlan) with depth 2', function () { + return this.db.query('SELECT @this.toJSON("fetchPlan:out_Eat:1 out_Eat.in:2") from Person').all() + .then(function (result) { + var people = []; + people[0] = JSON.parse(result[0].this); + people[1] = JSON.parse(result[1].this); + //console.log('people: ' + require('util').inspect(people, {depth: 3})); + people.length.should.be.equal(2); + people.forEach(function (person) { + testPerson(person); + }); + }); + }); + + it('should return linked vertices when using .fetch() with depth 1', function () { + return this.db.select().from('Person').fetch('out_Eat:1 out_Eat.in:1').all() + .then(function (people) { + //console.log('people: ' + require('util').inspect(people, {depth: 3})); + people.length.should.be.equal(2); + people.forEach(function (person) { + testPerson(person); + }); + }); + }); + + it('should return one linked vertex when using .fetch() with depth 2', function () { + return this.db.select().from('Person') + .limit(1) + .fetch('out_Eat:1 out_Eat.in:2').one() + .then(function (person) { + //console.log('\nperson: ' + require('util').inspect(person, {depth: 3})); + testPerson(person); + }); + }); + + // Relevant test + it('should return linked vertices when using .fetch() with depth 2', function () { + // OrientBD returns duplicates: #13:0 and #13:1 (edges) + // this.db.exec('select from Person', { fetchPlan: 'out_Eat:1 out_Eat.in:2' }).then(function (resultset) { + // console.log('results: ' + require('util').inspect(resultset.results) + '\n'); + // }); + + return this.db.select().from('Person').fetch('out_Eat:1 out_Eat.in:2').all() + .then(function (people) { + //console.log('\npeople: ' + require('util').inspect(people, {depth: 4})); + people.length.should.be.equal(2); + people.forEach(function (person) { + testPerson(person, false); + }); + }); + }); + + }); +}); From 79637f652f41ef06f6f221226e28a8e93612a1fd Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 11 Jan 2015 20:54:39 +0000 Subject: [PATCH 197/308] better query serialization, avoid JSON.stringify(). fixes #189 --- lib/transport/binary/protocol28/serializer.js | 2 +- lib/utils.js | 21 ++- test/bugs/189-embedded-document.js | 166 ++++++++++++++++++ 3 files changed, 184 insertions(+), 5 deletions(-) create mode 100644 test/bugs/189-embedded-document.js diff --git a/lib/transport/binary/protocol28/serializer.js b/lib/transport/binary/protocol28/serializer.js index 1bb1d3e..3794567 100644 --- a/lib/transport/binary/protocol28/serializer.js +++ b/lib/transport/binary/protocol28/serializer.js @@ -119,7 +119,7 @@ function serializeObject (value) { if (value instanceof RecordID) { return value.toString(); } - else if (value['@type'] === 'd') { + else if (value['@type'] && value['@type'].charAt(0) === 'd') { return '(' + serializeDocument(value, false) + ')'; } else { diff --git a/lib/utils.js b/lib/utils.js index 3a34e21..0ceefbb 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -126,7 +126,12 @@ exports.clone = function (item) { * @return {String} The escaped input. */ exports.escape = function (input) { - return ('' + input).replace(/([^'\\]*(?:\\.[^'\\]*)*)'/g, "$1\\'").replace(/([^"\\]*(?:\\.[^"\\]*)*)"/g, '$1\\"'); + return ('' + input) + .replace(/\r/g, '\\r') + .replace(/\n/g, '\\n') + .replace(/(?![\\])\//g, "\\/") + .replace(/([^'\\]*(?:\\.[^'\\]*)*)'/g, "$1\\'") + .replace(/([^"\\]*(?:\\.[^"\\]*)*)"/g, '$1\\"'); }; /** @@ -157,7 +162,7 @@ exports.prepare = function (query, params) { * @param {Mixed} value The value to encode. * @return {Mixed} The encoded value. */ -exports.encode = function (value) { +exports.encode = function encode (value) { if (value == null) { return 'null'; } @@ -174,10 +179,18 @@ exports.encode = function (value) { return value.toString(); } else if (Array.isArray(value)) { - return '[' + value.map(exports.encode) + ']'; + return '[' + value.map(encode) + ']'; } else { - return JSON.stringify(value); + var keys = Object.keys(value), + length = keys.length, + parts = new Array(length), + key, i; + for (i = 0; i < length; i++) { + key = keys[i]; + parts[i] = '"' + exports.escape(key) + '":'+encode(value[key]); + } + return '{'+parts.join(',')+'}'; } }; diff --git a/test/bugs/189-embedded-document.js b/test/bugs/189-embedded-document.js new file mode 100644 index 0000000..86f158f --- /dev/null +++ b/test/bugs/189-embedded-document.js @@ -0,0 +1,166 @@ +"use strict"; +var Bluebird = require('bluebird'); + +describe("Bug #189: Error inserting new document with embedded document containing link type field", function () { + var rid; + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_189') + .bind(this) + .then(function () { + return Bluebird.all([ + this.db.class.create('Person', 'V'), + this.db.class.create('Address') + ]) + .spread(function (Person, Address) { + return Bluebird.all([ + Person.property.create([ + { + name: 'name', + type: 'string' + }, + { + name: 'referrer', + type: 'link' + }, + { + name: 'primaryAddress', + type: 'embedded', + linkedType: 'Address' + }, + { + name: 'addresses', + type: 'embeddedset', + linkedType: 'Address' + } + ]), + Address.property.create([ + { + name: 'link', + type: 'link' + }, + { + name: 'city', + type: 'string' + } + ]) + ]); + }); + }) + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_189'); + }); + + it('should insert a person with a primary address', function () { + var query = this.db + .create('VERTEX', 'Person') + .set({ + name: 'Bob', + referrer: new LIB.RID('#5:0'), + primaryAddress: { + '@type': 'document', + '@class': 'Address', + city: 'London', + link: new LIB.RID('#5:0') + } + }) + return query.one() + .then(function (result) { + result.primaryAddress.city.should.equal('London'); + }); + }); + + + it('should insert a person with a primary address and some other addresses', function () { + var query = this.db + .create('VERTEX', 'Person') + .set({ + name: 'Alice', + referrer: new LIB.RID('#5:0'), + primaryAddress: { + '@type': 'document', + '@class': 'Address', + city: 'London' + }, + addresses: [ + { + '@type': 'document', + '@class': 'Address', + city: 'London', + link: new LIB.RID('#5:0') + }, + { + '@type': 'document', + '@class': 'Address', + city: 'Paris', + link: new LIB.RID('#5:1') + } + ] + }) + return query.one() + .then(function (result) { + result.primaryAddress.city.should.equal('London'); + result.addresses.length.should.equal(2); + result.addresses.forEach(function (address) { + (address.city === "London" || address.city === "Paris").should.be.true; + }); + }); + }); + + it('should insert some people using sql batch', function () { + var query = this.db + .let('bob', function (s) { + s + .create('VERTEX', 'Person') + .set({ + name: 'Bob', + referrer: new LIB.RID('#5:0'), + primaryAddress: { + '@type': 'document', + '@class': 'Address', + city: 'London', + link: new LIB.RID('#5:0') + } + }); + }) + .let('alice', function (s) { + s + .create('VERTEX', 'Person') + .set({ + name: 'Alice', + referrer: new LIB.RID('#5:0'), + primaryAddress: { + '@type': 'document', + '@class': 'Address', + city: 'London' + }, + addresses: [ + { + '@type': 'document', + '@class': 'Address', + city: 'London', + link: new LIB.RID('#5:0') + }, + { + '@type': 'document', + '@class': 'Address', + city: 'Paris', + link: new LIB.RID('#5:1') + } + ] + }); + }) + .return('[$bob,$alice]') + .commit(); + + return query.all() + .spread(function (bob, alice) { + bob.primaryAddress.city.should.equal('London'); + alice.primaryAddress.city.should.equal('London'); + alice.addresses.length.should.equal(2); + alice.addresses.forEach(function (address) { + (address.city === "London" || address.city === "Paris").should.be.true; + }); + }); + }); +}); From 73b6d9cce8b218d094f4737a4ede8ea44c582799 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 11 Jan 2015 21:37:49 +0000 Subject: [PATCH 198/308] fix escaping issues, always util.prepare() for older ODB versions --- lib/db/index.js | 5 ++- .../binary/protocol19/operations/command.js | 42 +++++++------------ lib/utils.js | 39 +++++++++++++++-- 3 files changed, 52 insertions(+), 34 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index 0e938b3..a546761 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -5,7 +5,8 @@ var utils = require('../utils'), Promise = require('bluebird'), RID = require('../recordid'), Query = require('./query'), - Transaction = require('./transaction'); + Transaction = require('./transaction'), + ArrayLike = utils.ArrayLike; /** @@ -207,7 +208,7 @@ Db.prototype.exec = function (query, options) { params: options.params.reduce(function (params, param, i) { params[i] = param; return params; - }, {}) + }, new ArrayLike()) }; } else if (typeof options.params === 'object') { diff --git a/lib/transport/binary/protocol19/operations/command.js b/lib/transport/binary/protocol19/operations/command.js index 343400a..624c8a9 100644 --- a/lib/transport/binary/protocol19/operations/command.js +++ b/lib/transport/binary/protocol19/operations/command.js @@ -4,7 +4,8 @@ var Operation = require('../operation'), constants = require('../constants'), serializer = require('../serializer'), writer = require('../writer'), - RID = require('../../../../recordid'); + RID = require('../../../../recordid'), + utils = require('../../../../utils'); module.exports = Operation.extend({ id: 'REQUEST_COMMAND', @@ -23,51 +24,36 @@ module.exports = Operation.extend({ serializeQuery: function () { var buffers = [writer.writeString(this.data.class)]; + var text = this.data.query; + // if there are bound parameters, force prepare them, OrientDB's support is limited in this version. + if (this.data.params && this.data.params.params) { + text = utils.prepare(text, this.data.params.params); + } + if (this.data.class === 'q' || this.data.class === 'com.orientechnologies.orient.core.sql.query.OSQLSynchQuery' || this.data.class === 'com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery') { buffers.push( - writer.writeString(this.data.query), + writer.writeString(text), writer.writeInt(this.data.limit), writer.writeString(this.data.fetchPlan || '') ); - if (this.data.params) { - buffers.push(writer.writeString(serializeParams(this.data.params))); - } - else { - buffers.push(writer.writeInt(0)); - } + buffers.push(writer.writeInt(0)); } else if ( this.data.class === 's' || this.data.class === 'com.orientechnologies.orient.core.command.script.OCommandScript') { buffers.push( writer.writeString(this.data.language || 'sql'), - writer.writeString(this.data.query) + writer.writeString(text) ); - if (this.data.params && this.data.params.params && Object.keys(this.data.params.params).length) { - buffers.push( - writer.writeBoolean(true), - writer.writeString(serializeParams(this.data.params)) - ); - } - else { - buffers.push(writer.writeBoolean(false)); - } + buffers.push(writer.writeBoolean(false)); buffers.push(writer.writeByte(0)); } else { - buffers.push(writer.writeString(this.data.query)); - if (this.data.params) { - buffers.push( - writer.writeBoolean(true), - writer.writeString(serializeParams(this.data.params)) - ); - } - else { - buffers.push(writer.writeBoolean(false)); - } + buffers.push(writer.writeString(text)); + buffers.push(writer.writeBoolean(false)); buffers.push(writer.writeBoolean(false)); } return Buffer.concat(buffers); diff --git a/lib/utils.js b/lib/utils.js index 0ceefbb..b95f033 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -145,16 +145,39 @@ exports.prepare = function (query, params) { if (!params) { return query; } - var pattern = /"(\\[\s\S]|[^"])*"|'(\\[\s\S]|[^'])*'|\s:([A-Za-z][A-Za-z0-9_-]*|\/\*[\s\S]*?\*\/)/g; + else if (params instanceof ArrayLike || Array.isArray(params)) { + return prepareArray(query, params); + } + else { + return prepareObject(query, params); + } +}; + + +function prepareArray (query, params) { + var pattern = /"(\\[\s\S]|[^"])*"|'(\\[\s\S]|[^'])*'|(\?)/g; + var n = 0; return query.replace(pattern, function (all, double, single, param) { if (param) { - return ' ' + exports.encode(params[param]); + return exports.encode(params[n++]); } else { return all; } }); -}; +} + +function prepareObject (query, params) { + var pattern = /"(\\[\s\S]|[^"])*"|'(\\[\s\S]|[^'])*'|([^A-Za-z0-9]:([A-Za-z][A-Za-z0-9_-]*))/g; + return query.replace(pattern, function (all, double, single, char, param) { + if (param) { + return char.charAt(0) + exports.encode(params[param]); + } + else { + return all; + } + }); +} /** * Encode a value for use in a query, escaping and quoting it if required. @@ -246,4 +269,12 @@ exports.deprecate = function (context, name, message, fn) { return this[name]; } }); -}; \ No newline at end of file +}; + +/** + * A class used solely to indicate that an object is an "array like" + * It should never be used directly and is a temporary hack around some poor design decisions. + */ +function ArrayLike () {} + +exports.ArrayLike = ArrayLike; \ No newline at end of file From 3342ca0b92f2eafb461989d2e02c5a0150a9f8ee Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 11 Jan 2015 21:47:39 +0000 Subject: [PATCH 199/308] fail to replicate #180 --- test/bugs/82-emojis.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/bugs/82-emojis.js b/test/bugs/82-emojis.js index 920cd73..88312fd 100644 --- a/test/bugs/82-emojis.js +++ b/test/bugs/82-emojis.js @@ -1,4 +1,4 @@ -describe("Bug #82: db.query errors when parsing emojis ", function () { +describe.only("Bug #82: db.query errors when parsing emojis ", function () { var rid; before(function () { return CREATE_TEST_DB(this, 'testdb_bug_82') @@ -37,4 +37,13 @@ describe("Bug #82: db.query errors when parsing emojis ", function () { result.bio.should.equal("😢😂"); }); }); + + describe('Bug #180: Emoji characters are not saved correctly', function () { + it('should insert some emojis', function () { + return this.db.insert().into('Emoji').set({value: "testing emoji 💪💦👌"}).one() + .then(function (result) { + result.value.should.equal("testing emoji 💪💦👌"); + }); + }); + }); }); From 818f8ecc605e11472d5c25cb5bc664209e3fe9be Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 11 Jan 2015 21:48:02 +0000 Subject: [PATCH 200/308] remove .only() --- test/bugs/82-emojis.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bugs/82-emojis.js b/test/bugs/82-emojis.js index 88312fd..e822aef 100644 --- a/test/bugs/82-emojis.js +++ b/test/bugs/82-emojis.js @@ -1,4 +1,4 @@ -describe.only("Bug #82: db.query errors when parsing emojis ", function () { +describe("Bug #82: db.query errors when parsing emojis ", function () { var rid; before(function () { return CREATE_TEST_DB(this, 'testdb_bug_82') From 10fd8a1f549b0cb04b7a2b9955cd50dd75bf2bfc Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 11 Jan 2015 22:00:53 +0000 Subject: [PATCH 201/308] add Statement::retry() and Statement::wait() --- lib/db/statement.js | 33 +++++++++++++++++++++++++++++++++ test/db/statement-test.js | 14 ++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/lib/db/statement.js b/lib/db/statement.js index ab4b52e..031b518 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -231,6 +231,29 @@ Statement.prototype.commit = function (retryLimit) { return this; }; + +/** + * Specify the number of times to retry a command. + * + * @param {Integer} retryLimit The maximum number of times to retry, defaults to 1. + * @return {Statement} The statement object. + */ +Statement.prototype.retry = function (retryLimit) { + this._state.retry = retryLimit === undefined ? 1 : retryLimit; + return this; +}; + +/** + * Specify the number of milliseconds to wait between retrying a command. + * + * @param {Integer} waitLimit The number of ms to wait. + * @return {Statement} The statement object. + */ +Statement.prototype.wait = function (waitLimit) { + this._state.wait = waitLimit === undefined ? 1 : waitLimit; + return this; +}; + /** * Return a certain variable if there is a let clause. * For update, insert or delete statements it will add a RETURN clause before @@ -529,8 +552,18 @@ Statement.prototype.buildStatement = function () { if (state.commit) { statement.push('RETRY ' + (+state.commit)); } + else if (state.retry) { + statement.push('RETRY ' + (+state.retry)); + } statement.push('\n'); } + else if (state.retry) { + statement.push('RETRY ' + (+state.retry)); + } + + if (state.wait) { + statement.push('WAIT ' + (+state.wait)); + } if (!(state.update || state.insert || state.delete) && state.return) { statement.push('RETURN ' + state.return); diff --git a/test/db/statement-test.js b/test/db/statement-test.js index 368acb4..8231f44 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -213,6 +213,20 @@ COMMIT \n\ }); }); + describe('Statement::retry()', function () { + it('should create an edge with retry', function () { + this.statement.create('EDGE', 'E').from('#5:0').to('#5:1').retry(5); + this.statement.buildStatement().should.equal('CREATE EDGE E FROM #5:0 TO #5:1 RETRY 5'); + }); + }); + + describe('Statement::wait()', function () { + it('should create an edge with retry and wait', function () { + this.statement.create('EDGE', 'E').from('#5:0').to('#5:1').retry(5).wait(100); + this.statement.buildStatement().should.equal('CREATE EDGE E FROM #5:0 TO #5:1 RETRY 5 WAIT 100'); + }); + }); + describe('Statement::return()', function () { it('should build a return clause', function () { this.statement.update('#1:1').set({foo: 'bar', greeting: 'hello world'}).return('AFTER'); From bea4586be1ed0f1a8db914e74db01b05ba956ee7 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 11 Jan 2015 23:09:30 +0000 Subject: [PATCH 202/308] fix infinite loop in deserialize() --- lib/transport/binary/protocol19/deserializer.js | 2 +- lib/transport/binary/protocol26/deserializer.js | 2 +- lib/transport/binary/protocol28/deserializer.js | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/transport/binary/protocol19/deserializer.js b/lib/transport/binary/protocol19/deserializer.js index 52e918a..4d02688 100644 --- a/lib/transport/binary/protocol19/deserializer.js +++ b/lib/transport/binary/protocol19/deserializer.js @@ -267,7 +267,7 @@ function eatRID (input) { cluster = +collected; collected = ''; } - else if (c === '0' || +c) { + else if (c === '-' || c === '0' || +c) { collected += c; } else { diff --git a/lib/transport/binary/protocol26/deserializer.js b/lib/transport/binary/protocol26/deserializer.js index 665ab39..3d9ab5c 100644 --- a/lib/transport/binary/protocol26/deserializer.js +++ b/lib/transport/binary/protocol26/deserializer.js @@ -267,7 +267,7 @@ function eatRID (input) { cluster = +collected; collected = ''; } - else if (c === '0' || +c) { + else if (c === '-' || c === '0' || +c) { collected += c; } else { diff --git a/lib/transport/binary/protocol28/deserializer.js b/lib/transport/binary/protocol28/deserializer.js index 665ab39..b568c34 100644 --- a/lib/transport/binary/protocol28/deserializer.js +++ b/lib/transport/binary/protocol28/deserializer.js @@ -28,7 +28,6 @@ function deserialize (input) { key = chunk[0]; input = chunk[1]; } - // read the first value. chunk = eatValue(input); value = chunk[0]; @@ -89,7 +88,6 @@ function eatFirstKey (input) { collected += c; } } - return [collected, input.slice(i + 1), isClassName]; } @@ -267,7 +265,7 @@ function eatRID (input) { cluster = +collected; collected = ''; } - else if (c === '0' || +c) { + else if (c === '-' || c === '0' || +c) { collected += c; } else { From 8cbab08ea5ee76a38b4b175f7f87d86a375e4c62 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 11 Jan 2015 23:11:12 +0000 Subject: [PATCH 203/308] investigate & sort of fix #188 --- test/bugs/188-subquery-with-vars.js | 112 ++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 test/bugs/188-subquery-with-vars.js diff --git a/test/bugs/188-subquery-with-vars.js b/test/bugs/188-subquery-with-vars.js new file mode 100644 index 0000000..6d92df3 --- /dev/null +++ b/test/bugs/188-subquery-with-vars.js @@ -0,0 +1,112 @@ +"use strict"; +var Bluebird = require('bluebird'); + +describe("Test sub-query + $parent.$current", function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_188') + .bind(this) + .then(function () { + return this.db.class.create('VertexOne','V'); + }) + .then(function (vOneClass) { + return vOneClass.property.create({ + name: 'uuid', + type: 'Integer' + }); + }) + .then(function () { + return this.db.class.create('VertexTwo','V'); + }) + .then(function (vTwoClass) { + return vTwoClass.property.create({ + name: 'uuid', + type: 'Integer' + }); + }) + .then(function () { + return this.db.class.create('HAS_EDGE','E'); + }) + .then(function () { + return this.db + .create('VERTEX', 'VertexOne') + .set({ + uuid: 1 + }) + .one(); + }) + .then(function () { + return this.db + .create('VERTEX', 'VertexTwo') + .set({ + uuid: 2 + }) + .one(); + }) + .then(function () { + return this.db + .create('VERTEX', 'VertexTwo') + .set({ + uuid: 3 + }) + .one(); + }) + .then(function () { + return this.db + .create('EDGE', 'HAS_EDGE') + .from(function (s) { + s + .select() + .from('VertexOne') + .where({uuid: 1}); + }) + .to(function (s) { + s + .select() + .from('VertexTwo') + .where({uuid: 2}); + }) + .one(); + }) + .then(function () { + return this.db + .create('EDGE', 'HAS_EDGE') + .from(function (s) { + s + .select() + .from('VertexOne') + .where({uuid: 1}); + }) + .to(function (s) { + s + .select() + .from('VertexTwo') + .where({uuid: 3}); + }) + .one(); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_188'); + }); + + it('should deserialize a value properly', function () { + var deserializer = this.db.server.transport.connection.protocol.deserializer; // ugh! + deserializer.deserialize('$VTwo:[#-2:1,#-2:2]').should.eql({ + '@type': 'd', + '$VTwo': [ + new LIB.RID('#-2:1'), + new LIB.RID('#-2:2') + ] + }); + }); + + it("should test if request return a value", function () { + return this.db.query("SELECT expand($VTwo) FROM VertexOne LET $VTwo = (SELECT uuid FROM (SELECT expand(out('HAS_EDGE')) FROM $parent.$current)) WHERE uuid = 1") + .then(function (result) { + result.length.should.equal(2); + result.forEach(function (item) { + (item.uuid === 2 || item.uuid === 3).should.be.true; + }); + }); + }); +}); \ No newline at end of file From de8934176db39ca219cea42f3a2d11f8ec53a1d2 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 11 Jan 2015 23:17:36 +0000 Subject: [PATCH 204/308] rev version number [ci skip] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 606b89d..896709d 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node", "node.js" ], - "version": "0.7.1", + "version": "1.0.0", "author": { "name": "Charles Pick", "email": "charles@codemix.com" From aa91f91fe8f9749121f297d2a4351049a78d0e77 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 14 Jan 2015 22:18:45 +0000 Subject: [PATCH 205/308] add test for escaping URLs --- test/bugs/189-embedded-document.js | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/test/bugs/189-embedded-document.js b/test/bugs/189-embedded-document.js index 86f158f..e06b178 100644 --- a/test/bugs/189-embedded-document.js +++ b/test/bugs/189-embedded-document.js @@ -22,6 +22,10 @@ describe("Bug #189: Error inserting new document with embedded document containi name: 'referrer', type: 'link' }, + { + name: 'url', + type: 'string' + }, { name: 'primaryAddress', type: 'embedded', @@ -38,6 +42,10 @@ describe("Bug #189: Error inserting new document with embedded document containi name: 'link', type: 'link' }, + { + name: 'url', + type: 'string' + }, { name: 'city', type: 'string' @@ -57,16 +65,20 @@ describe("Bug #189: Error inserting new document with embedded document containi .set({ name: 'Bob', referrer: new LIB.RID('#5:0'), + url: 'http://example.com/', primaryAddress: { '@type': 'document', '@class': 'Address', city: 'London', - link: new LIB.RID('#5:0') + link: new LIB.RID('#5:0'), + url: 'http://example.com/' } }) return query.one() .then(function (result) { result.primaryAddress.city.should.equal('London'); + result.url.should.equal('http://example.com/'); + result.primaryAddress.url.should.equal('http://example.com/'); }); }); @@ -77,23 +89,27 @@ describe("Bug #189: Error inserting new document with embedded document containi .set({ name: 'Alice', referrer: new LIB.RID('#5:0'), + url: 'http://example.com/', primaryAddress: { '@type': 'document', '@class': 'Address', - city: 'London' + city: 'London', + url: 'http://example.com/' }, addresses: [ { '@type': 'document', '@class': 'Address', city: 'London', - link: new LIB.RID('#5:0') + link: new LIB.RID('#5:0'), + url: 'http://example.com/' }, { '@type': 'document', '@class': 'Address', city: 'Paris', - link: new LIB.RID('#5:1') + link: new LIB.RID('#5:1'), + url: 'http://example.com/' } ] }) From b5d54bbac89dc744240b0b0a4927adc4b014a4c9 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 15 Jan 2015 13:28:50 +0000 Subject: [PATCH 206/308] fix #195 --- lib/transport/binary/index.js | 42 ++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/lib/transport/binary/index.js b/lib/transport/binary/index.js index 6fb82d5..52774f3 100644 --- a/lib/transport/binary/index.js +++ b/lib/transport/binary/index.js @@ -95,25 +95,13 @@ BinaryTransport.prototype.configureConnection = function () { this.transportCluster = config; }.bind(this)); this.connection.on('reconnectNow', function () { - this.sessionId = -1; - this.connecting = false; - this.connection.removeAllListeners(); - this.connection.cancel(new Error('Connection closed.')); - this.connection = false; - this.emit('reset'); - this.configureConnection(); + reconnectTransport(this); }.bind(this)); this.connection.on('error', function (err) { if (this.retries++ > this.maxRetries) { return this.emit('error', err); } - this.sessionId = -1; - this.connecting = false; - this.connection.removeAllListeners(); - this.connection.cancel(err); - this.connection = false; - this.emit('reset'); - this.configureConnection(); + reconnectTransport(this, err); }.bind(this)); return this; }; @@ -152,7 +140,17 @@ BinaryTransport.prototype.connect = function () { } if (this.connecting) { - return this.connecting; + if (this.connecting.isRejected()) { + return new Promise(function (resolve, reject) { + this.once('reset', function () { + this.connect().then(resolve, reject); + }.bind(this)); + reconnectTransport(this); + }.bind(this)); + } + else { + return this.connecting; + } } this.connecting = (this.pool || this.connection).send('connect', { @@ -208,4 +206,16 @@ BinaryTransport.prototype.close = function () { this.closing = true; (this.pool || this.connection).close(); return this; -}; \ No newline at end of file +}; + + +function reconnectTransport (transport, cancellationError) { + cancellationError = cancellationError || new Error('Connection closed.'); + transport.sessionId = -1; + transport.connecting = false; + transport.connection.removeAllListeners(); + transport.connection.cancel(cancellationError); + transport.connection = false; + transport.configureConnection(); + transport.emit('reset'); +} \ No newline at end of file From b7dda7f62aecf35c6a86744b951333b7c90289b0 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 15 Jan 2015 13:29:19 +0000 Subject: [PATCH 207/308] add test for 195, disabled because it requires a manual restart of OrientDB --- test/bugs/195-connection-reset.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 test/bugs/195-connection-reset.js diff --git a/test/bugs/195-connection-reset.js b/test/bugs/195-connection-reset.js new file mode 100644 index 0000000..be44332 --- /dev/null +++ b/test/bugs/195-connection-reset.js @@ -0,0 +1,29 @@ +var Bluebird = require('bluebird'); + +describe.skip("Bug #195: hang on connection reset", function () { + var rid; + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_195', 'plocal'); + }); + after(function () { + // return DELETE_TEST_DB('testdb_bug_195', 'plocal'); + }); + + it('should not hang on connection reset', function () { + var self = this; + this.timeout(10000); + return this.db.select().from('OUser').all() + .then(function () { + console.log('Quick, go restart orientdb'); + return Bluebird.delay(4000); + }) + .then(function () { + console.log('Assuming orientdb was restarted, trying to query again.'); + return self.db.select().from('OUser').all(); + }) + .then(function (users) { + console.log('end', users); + users.length.should.be.above(1); + }); + }); +}); From b5dc758ea3b49ccbdcae4bafec2425aa5022d3ed Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 15 Jan 2015 13:48:29 +0000 Subject: [PATCH 208/308] make parens optional in from and to clauses, fix #198 --- lib/db/statement.js | 9 +++++++-- test/db/statement-test.js | 18 +++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index 031b518..62260fe 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -380,7 +380,12 @@ Statement.prototype.buildStatement = function () { return '(' + item.toString() + ')'; } else if (typeof item === 'string') { - return item; + if (/^[^\(](.*)\s/.test(item)) { + return '(' + item + ')'; + } + else { + return item; + } } else { return ''+item; @@ -401,7 +406,7 @@ Statement.prototype.buildStatement = function () { return '(' + item.toString() + ')'; } else if (typeof item === 'string') { - if (/(\s+)/.test(item)) { + if (/^[^\(](.*)\s/.test(item)) { return '(' + item + ')'; } else { diff --git a/test/db/statement-test.js b/test/db/statement-test.js index 8231f44..4826646 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -186,10 +186,14 @@ COMMIT \n\ this.statement.select().from(new LIB.RID('#4:4')); this.statement.buildStatement().should.equal('SELECT * FROM #4:4'); }); - it('should select from a subexpression', function () { + it('should select from a subexpression with parentheses', function () { this.statement.select().from('(SELECT * FROM OUser)'); this.statement.buildStatement().should.equal('SELECT * FROM (SELECT * FROM OUser)'); }); + it('should select from a subexpression without parentheses', function () { + this.statement.select().from('SELECT * FROM OUser'); + this.statement.buildStatement().should.equal('SELECT * FROM (SELECT * FROM OUser)'); + }); it('should select from a subquery', function () { this.statement.select().from((new Statement(this.db).select().from('OUser'))); this.statement.buildStatement().should.equal('SELECT * FROM (SELECT * FROM OUser)'); @@ -211,6 +215,18 @@ COMMIT \n\ this.statement.create('EDGE', 'E').from(LIB.RID('#5:0')).to(LIB.RID('#22:310540')); this.statement.buildStatement().should.equal('CREATE EDGE E FROM #5:0 TO #22:310540'); }); + it('should create an edge using a subexpression with parentheses', function () { + this.statement.create('EDGE', 'E').to('(SELECT * FROM OUser)').from(LIB.RID('#1:23')); + this.statement.buildStatement().should.equal('CREATE EDGE E FROM #1:23 TO (SELECT * FROM OUser)'); + }); + it('should create an edge using a subexpression without parentheses', function () { + this.statement.create('EDGE', 'E').to('SELECT * FROM OUser').from(LIB.RID('#1:23')); + this.statement.buildStatement().should.equal('CREATE EDGE E FROM #1:23 TO (SELECT * FROM OUser)'); + }); + it('should create an edge using a subquery', function () { + this.statement.create('EDGE', 'E').to((new Statement(this.db).select().from('OUser'))).from(LIB.RID('#1:23')); + this.statement.buildStatement().should.equal('CREATE EDGE E FROM #1:23 TO (SELECT * FROM OUser)'); + }); }); describe('Statement::retry()', function () { From cb657e762ec725e92e9bf526ffda2e768e970f50 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 15 Jan 2015 13:58:50 +0000 Subject: [PATCH 209/308] better fix for #189 --- lib/utils.js | 1 - test/bugs/189-embedded-document.js | 23 +++++++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index b95f033..74e806c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -129,7 +129,6 @@ exports.escape = function (input) { return ('' + input) .replace(/\r/g, '\\r') .replace(/\n/g, '\\n') - .replace(/(?![\\])\//g, "\\/") .replace(/([^'\\]*(?:\\.[^'\\]*)*)'/g, "$1\\'") .replace(/([^"\\]*(?:\\.[^"\\]*)*)"/g, '$1\\"'); }; diff --git a/test/bugs/189-embedded-document.js b/test/bugs/189-embedded-document.js index e06b178..5a0f3c5 100644 --- a/test/bugs/189-embedded-document.js +++ b/test/bugs/189-embedded-document.js @@ -115,9 +115,12 @@ describe("Bug #189: Error inserting new document with embedded document containi }) return query.one() .then(function (result) { + result.url.should.equal('http://example.com/'); + result.primaryAddress.url.should.equal('http://example.com/'); result.primaryAddress.city.should.equal('London'); result.addresses.length.should.equal(2); result.addresses.forEach(function (address) { + address.url.should.equal('http://example.com/'); (address.city === "London" || address.city === "Paris").should.be.true; }); }); @@ -131,11 +134,13 @@ describe("Bug #189: Error inserting new document with embedded document containi .set({ name: 'Bob', referrer: new LIB.RID('#5:0'), + url: 'http://example.com/', primaryAddress: { '@type': 'document', '@class': 'Address', city: 'London', - link: new LIB.RID('#5:0') + link: new LIB.RID('#5:0'), + url: 'http://example.com/' } }); }) @@ -145,23 +150,27 @@ describe("Bug #189: Error inserting new document with embedded document containi .set({ name: 'Alice', referrer: new LIB.RID('#5:0'), + url: 'http://example.com/', primaryAddress: { '@type': 'document', '@class': 'Address', - city: 'London' + city: 'London', + url: 'http://example.com/' }, addresses: [ { '@type': 'document', '@class': 'Address', city: 'London', - link: new LIB.RID('#5:0') + link: new LIB.RID('#5:0'), + url: 'http://example.com/' }, { '@type': 'document', '@class': 'Address', city: 'Paris', - link: new LIB.RID('#5:1') + link: new LIB.RID('#5:1'), + url: 'http://example.com/' } ] }); @@ -171,10 +180,16 @@ describe("Bug #189: Error inserting new document with embedded document containi return query.all() .spread(function (bob, alice) { + + bob.url.should.equal('http://example.com/'); + bob.primaryAddress.url.should.equal('http://example.com/'); bob.primaryAddress.city.should.equal('London'); alice.primaryAddress.city.should.equal('London'); + alice.url.should.equal('http://example.com/'); + alice.primaryAddress.url.should.equal('http://example.com/'); alice.addresses.length.should.equal(2); alice.addresses.forEach(function (address) { + address.url.should.equal('http://example.com/'); (address.city === "London" || address.city === "Paris").should.be.true; }); }); From 7a060f9fbfb5a5d335f7320cfa4df4fbb8124489 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 15 Jan 2015 23:46:56 +0000 Subject: [PATCH 210/308] fix double escaping issue, at the unfortunate expense of quote agnosticism --- lib/utils.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 74e806c..8d57b33 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -122,6 +122,9 @@ exports.clone = function (item) { /** * Escape the given input for use in a query. * + * > NOTE: Because of a fun quirk in OrientDB's parser, this function can only be safely + * used on SQL segments that are enclosed in DOUBLE QUOTES (") not single quotes ('). + * * @param {String} input The input to escape. * @return {String} The escaped input. */ @@ -129,7 +132,6 @@ exports.escape = function (input) { return ('' + input) .replace(/\r/g, '\\r') .replace(/\n/g, '\\n') - .replace(/([^'\\]*(?:\\.[^'\\]*)*)'/g, "$1\\'") .replace(/([^"\\]*(?:\\.[^"\\]*)*)"/g, '$1\\"'); }; From 49849d98599bd628b4db6573585aa7fa119b3eda Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 16 Jan 2015 00:57:17 +0000 Subject: [PATCH 211/308] add lucene fulltext / spatial methods to query builder --- lib/db/statement.js | 99 +++++++++++++++++++++++++++++++++++++++ test/db/statement-test.js | 53 +++++++++++++++++++++ 2 files changed, 152 insertions(+) diff --git a/lib/db/statement.js b/lib/db/statement.js index 62260fe..f2abe3d 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -267,6 +267,105 @@ Statement.prototype.return = function (value) { return this; }; +/** + * Specify a lucene full text query. + * + * > NOTE: You must have installed the lucene query plugin in OrientDB for this to work. + * + * @param {String|Object} ...property The names of the properties to include in the query. + * @param {String} luceneQuery The lucene query, using lucene QueryParser syntax. + * @return {Statement} The statement object. + */ +Statement.prototype.lucene = function (property, luceneQuery) { + if (typeof property === 'object') { + var keys = Object.keys(property), + length = keys.length, + key, i; + for (i = 0; i < length; i++) { + key = keys[i]; + this.lucene(key, property[key]); + } + return this; + } + var properties; + if (arguments.length > 2) { + var totalArguments = arguments.length; + properties = new Array(totalArguments - 1); + for (var a = 0; a < totalArguments - 1; a++) { + properties[a] = arguments[a]; + } + luceneQuery = arguments[totalArguments - 1]; + } + else { + properties = Array.isArray(property) ? property : [property]; + } + + return this.where( + (properties.length === 1 ? properties[0] : '[' + properties.join(',') + ']') + + ' LUCENE ' + JSON.stringify(luceneQuery) + ); +}; + +/** + * Specify a lucene spatial query, find items near the given longitude / latitude. + * + * > NOTE: You must have installed the lucene query plugin in OrientDB for this to work. + * + * @param {String|Object} longitudeProperty Either the name of the longitude property, + * or a map of field names to values. + * @param {String|Number} latitudeProperty Either the name of the latitude property, + * or the optional `maxDistanceInKms`. + * @param {Number} longitude The longitude value to compare against. + * @param {Number} latitude The latitude value to compare against. + * @param {Number} maxDistanceInKms The maximum distance in kilometers. + * @return {Statement} The statement object. + */ +Statement.prototype.near = function (longitudeProperty, latitudeProperty, longitude, latitude, maxDistanceInKms) { + var properties = [], + values = []; + if (typeof longitudeProperty === 'object') { + var keys = Object.keys(longitudeProperty), + length = keys.length, + key, i; + for (i = 0; i < length; i++) { + key = keys[i]; + properties.push(key); + values.push(longitudeProperty[key]); + } + maxDistanceInKms = latitudeProperty; + } + else { + properties.push(longitudeProperty, latitudeProperty); + values.push(longitude, latitude); + } + if (maxDistanceInKms) { + properties.push('$spatial'); + values.push(JSON.stringify({maxDistance: maxDistanceInKms})); + } + return this.where( + '['+properties.join(',')+'] NEAR ['+values.join(',')+']' + ); +}; + + +/** + * Specify a lucene spatial query, find items within the given bounding box. + * + * > NOTE: You must have installed the lucene query plugin in OrientDB for this to work. + * + * @param {String|Object} longitudeProperty The name of the longitude property. + * @param {String|Number} latitudeProperty The name of the latitude property. + * @param {Array} box An array of coordinates. + * @return {Statement} The statement object. + */ +Statement.prototype.within = function (longitudeProperty, latitudeProperty, box) { + return this.where( + '['+longitudeProperty+','+latitudeProperty+'] WITHIN '+JSON.stringify(box) + ); +}; + + + /** * Add the given parameter to the query. * diff --git a/test/db/statement-test.js b/test/db/statement-test.js index 4826646..39ec90f 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -323,4 +323,57 @@ COMMIT \n\ this.statement.buildStatement().should.equal("UPDATE OUser SET foo = 'bar' UPSERT WHERE 1 = 1"); }); }); + + + describe('Statement::lucene()', function () { + it('should accept a string query', function () { + this.statement.select().from('OUser').lucene('name', '(name:"admin")'); + this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE name LUCENE "(name:\\"admin\\")"'); + }); + + it('should accept a naked string query', function () { + this.statement.select().from('OUser').lucene('name', 'admin'); + this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE name LUCENE "admin"'); + }); + + it('should accept a query object', function () { + this.statement.select().from('OUser').lucene({ + name: 'admin', + status: 'ACTIVE' + }); + this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE name LUCENE "admin" AND status LUCENE "ACTIVE"'); + }); + + it('should accept multiple parameters', function () { + this.statement.select().from('OUser').lucene('name', 'status', '(name:"admin" AND status:"ACTIVE")'); + this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE [name,status] LUCENE "(name:\\"admin\\" AND status:\\"ACTIVE\\")"'); + }); + }); + + + describe('Statement::near()', function () { + it('should accept plain values', function () { + this.statement.select().from('OUser').near('longitude', 'latitude', 1, 2); + this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE [longitude,latitude] NEAR [1,2]'); + }); + it('should accept plain values with a max distance', function () { + this.statement.select().from('OUser').near('longitude', 'latitude', 1, 2, 100); + this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE [longitude,latitude,$spatial] NEAR [1,2,{"maxDistance":100}]'); + }); + it('should accept an object of values', function () { + this.statement.select().from('OUser').near({longitude: 1, latitude: 2}); + this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE [longitude,latitude] NEAR [1,2]'); + }); + it('should accept an object of values, with a max distance', function () { + this.statement.select().from('OUser').near({longitude: 1, latitude: 2}, 100); + this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE [longitude,latitude,$spatial] NEAR [1,2,{"maxDistance":100}]'); + }); + }); + + describe('Statement::within()', function () { + it('should build a within query', function () { + this.statement.select().from('OUser').within('longitude', 'latitude', [[1, 2], [3, 4]]); + this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE [longitude,latitude] WITHIN [[1,2],[3,4]]'); + }); + }); }); \ No newline at end of file From 34b617e41275bfb6381139304634bceda5eba644 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 16 Jan 2015 03:17:22 +0000 Subject: [PATCH 212/308] add support for LET in select query --- lib/db/statement.js | 62 ++++++++++++++++++++++++--------------- test/db/statement-test.js | 38 ++++++++++++++++-------- 2 files changed, 63 insertions(+), 37 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index f2abe3d..33bfab4 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -311,31 +311,31 @@ Statement.prototype.lucene = function (property, luceneQuery) { * * > NOTE: You must have installed the lucene query plugin in OrientDB for this to work. * - * @param {String|Object} longitudeProperty Either the name of the longitude property, + * @param {String|Object} latitudeProperty Either the name of the longitude property, * or a map of field names to values. - * @param {String|Number} latitudeProperty Either the name of the latitude property, + * @param {String|Number} longitudeProperty Either the name of the latitude property, * or the optional `maxDistanceInKms`. * @param {Number} longitude The longitude value to compare against. * @param {Number} latitude The latitude value to compare against. * @param {Number} maxDistanceInKms The maximum distance in kilometers. * @return {Statement} The statement object. */ -Statement.prototype.near = function (longitudeProperty, latitudeProperty, longitude, latitude, maxDistanceInKms) { +Statement.prototype.near = function (latitudeProperty, longitudeProperty, longitude, latitude, maxDistanceInKms) { var properties = [], values = []; - if (typeof longitudeProperty === 'object') { - var keys = Object.keys(longitudeProperty), + if (typeof latitudeProperty === 'object') { + var keys = Object.keys(latitudeProperty), length = keys.length, key, i; for (i = 0; i < length; i++) { key = keys[i]; properties.push(key); - values.push(longitudeProperty[key]); + values.push(latitudeProperty[key]); } - maxDistanceInKms = latitudeProperty; + maxDistanceInKms = longitudeProperty; } else { - properties.push(longitudeProperty, latitudeProperty); + properties.push(latitudeProperty, longitudeProperty); values.push(longitude, latitude); } if (maxDistanceInKms) { @@ -353,14 +353,14 @@ Statement.prototype.near = function (longitudeProperty, latitudeProperty, longit * * > NOTE: You must have installed the lucene query plugin in OrientDB for this to work. * - * @param {String|Object} longitudeProperty The name of the longitude property. * @param {String|Number} latitudeProperty The name of the latitude property. + * @param {String|Object} longitudeProperty The name of the longitude property. * @param {Array} box An array of coordinates. * @return {Statement} The statement object. */ -Statement.prototype.within = function (longitudeProperty, latitudeProperty, box) { +Statement.prototype.within = function (latitudeProperty, longitudeProperty, box) { return this.where( - '['+longitudeProperty+','+latitudeProperty+'] WITHIN '+JSON.stringify(box) + '['+latitudeProperty+','+longitudeProperty+'] WITHIN '+JSON.stringify(box) ); }; @@ -423,21 +423,21 @@ Statement.prototype.buildStatement = function () { if (state.commit !== undefined) { statement.push('BEGIN\n'); + if (state.let && state.let.length) { + statement.push(state.let.map(function (item) { + if (typeof item[1] === 'function') { + var child = new Statement(self.db); + child._state.paramIndex = self._state.paramIndex; + item[1](child); + return 'LET ' + item[0] + ' = ' + child + "\n"; + } + else { + return "LET " + item[0] + ' = ' + item[1] + "\n"; + } + }).join(' ')); + } } - if (state.let && state.let.length) { - statement.push(state.let.map(function (item) { - if (typeof item[1] === 'function') { - var child = new Statement(self.db); - child._state.paramIndex = self._state.paramIndex; - item[1](child); - return 'LET ' + item[0] + ' = ' + child + "\n"; - } - else { - return "LET " + item[0] + ' = ' + item[1] + "\n"; - } - }).join(' ')); - } if (state.create && state.create.length) { statement.push('CREATE ' + state.create.join(' ')); @@ -492,6 +492,20 @@ Statement.prototype.buildStatement = function () { }).join(', ')); } + if (state.commit === undefined && state.let && state.let.length) { + statement.push(state.let.map(function (item) { + if (typeof item[1] === 'function') { + var child = new Statement(self.db); + child._state.paramIndex = self._state.paramIndex; + item[1](child); + return 'LET ' + item[0] + ' = ' + child; + } + else { + return "LET " + item[0] + ' = ' + item[1]; + } + }).join('\n')); + } + if (state.to && state.to.length) { statement.push('TO'); statement.push(state.to.map(function (item) { diff --git a/test/db/statement-test.js b/test/db/statement-test.js index 39ec90f..e33052a 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -13,13 +13,25 @@ describe("Database API - Statement", function () { }); describe('Statement::let()', function () { + + it('should let a variable in a select() query', function () { + this.statement + .select('$thing') + .from('OUser') + .let('thing', '$current.thing') + .where('1=1') + .buildStatement() + .should + .equal('SELECT $thing FROM OUser LET thing = $current.thing WHERE 1=1'); + }); + it('should let a variable equal a subexpression', function () { var sub = (new Statement(this.db)).select('name').from('OUser').where({status: 'ACTIVE'}); this.statement .let('names', sub) .buildStatement() .should - .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"\n'); + .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"'); }); it('should let a variable equal a subexpression, more than once', function () { var sub1 = (new Statement(this.db)).select('name').from('OUser').where({status: 'ACTIVE'}), @@ -29,7 +41,7 @@ describe("Database API - Statement", function () { .let('statuses', sub2) .buildStatement() .should - .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"\n LET statuses = SELECT status FROM OUser\n'); + .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"\nLET statuses = SELECT status FROM OUser'); }); it('should let a variable equal a subexpression, more than once, using locks', function () { var sub1 = (new Statement(this.db)).select('name').from('OUser').where({status: 'ACTIVE'}), @@ -39,7 +51,7 @@ describe("Database API - Statement", function () { .let('statuses', sub2) .buildStatement() .should - .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"\n LET statuses = SELECT status FROM OUser LOCK record\n'); + .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"\nLET statuses = SELECT status FROM OUser LOCK record'); }); it('should allow RIDs in LET expressions', function () { @@ -353,27 +365,27 @@ COMMIT \n\ describe('Statement::near()', function () { it('should accept plain values', function () { - this.statement.select().from('OUser').near('longitude', 'latitude', 1, 2); - this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE [longitude,latitude] NEAR [1,2]'); + this.statement.select().from('OUser').near('latitude', 'longitude', 1, 2); + this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE [latitude,longitude] NEAR [1,2]'); }); it('should accept plain values with a max distance', function () { - this.statement.select().from('OUser').near('longitude', 'latitude', 1, 2, 100); - this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE [longitude,latitude,$spatial] NEAR [1,2,{"maxDistance":100}]'); + this.statement.select().from('OUser').near('latitude', 'longitude', 1, 2, 100); + this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE [latitude,longitude,$spatial] NEAR [1,2,{"maxDistance":100}]'); }); it('should accept an object of values', function () { - this.statement.select().from('OUser').near({longitude: 1, latitude: 2}); - this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE [longitude,latitude] NEAR [1,2]'); + this.statement.select().from('OUser').near({latitude: 1, longitude: 2}); + this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE [latitude,longitude] NEAR [1,2]'); }); it('should accept an object of values, with a max distance', function () { - this.statement.select().from('OUser').near({longitude: 1, latitude: 2}, 100); - this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE [longitude,latitude,$spatial] NEAR [1,2,{"maxDistance":100}]'); + this.statement.select().from('OUser').near({latitude: 1, longitude: 2}, 100); + this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE [latitude,longitude,$spatial] NEAR [1,2,{"maxDistance":100}]'); }); }); describe('Statement::within()', function () { it('should build a within query', function () { - this.statement.select().from('OUser').within('longitude', 'latitude', [[1, 2], [3, 4]]); - this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE [longitude,latitude] WITHIN [[1,2],[3,4]]'); + this.statement.select().from('OUser').within('latitude', 'longitude', [[1, 2], [3, 4]]); + this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE [latitude,longitude] WITHIN [[1,2],[3,4]]'); }); }); }); \ No newline at end of file From 5e76a16937033feaa87225c6d920ee18d975a2a4 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Sat, 17 Jan 2015 00:56:19 +0000 Subject: [PATCH 213/308] Issue #203, query serialisation: add support for date --- lib/utils.js | 3 +++ test/core/utils.js | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/lib/utils.js b/lib/utils.js index 8d57b33..ef03a25 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -199,6 +199,9 @@ exports.encode = function encode (value) { else if (typeof value === 'string') { return '"' + exports.escape(value) + '"'; } + else if (Object.prototype.toString.call(value) === '[object Date]') { + return value.getTime(); + } else if (value instanceof RID) { return value.toString(); } diff --git a/test/core/utils.js b/test/core/utils.js index f30c7c7..9eca546 100644 --- a/test/core/utils.js +++ b/test/core/utils.js @@ -9,4 +9,8 @@ describe('utils.prepare', function () { it("should prepare SQL statements with parameters", function () { utils.prepare("select from index:foo where key = :key", {key: 123}).should.equal("select from index:foo where key = 123"); }); + it("should prepare SQL statements with date parameters", function () { + var date = new Date(); + utils.prepare("select from index:foo where date = :date", {date: date}).should.equal("select from index:foo where date = " + date.getTime()); + }); }); \ No newline at end of file From 719f733347917ddd8d3539ead3fd31eb1074e29b Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Sat, 17 Jan 2015 19:12:00 +0000 Subject: [PATCH 214/308] Issue #203, utils.encode(): using instanceof instead of Object.prototype.toString.call() as per @phpnode code review --- lib/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index ef03a25..ecb819a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -199,7 +199,7 @@ exports.encode = function encode (value) { else if (typeof value === 'string') { return '"' + exports.escape(value) + '"'; } - else if (Object.prototype.toString.call(value) === '[object Date]') { + else if (value instanceof Date) { return value.getTime(); } else if (value instanceof RID) { From b5ec97781d922d0e933fe429ca2a6217c9a866bf Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sat, 17 Jan 2015 19:52:19 +0000 Subject: [PATCH 215/308] fix support for multiple let expressions in non-batch queries --- lib/db/statement.js | 8 ++++---- test/db/statement-test.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index 33bfab4..e0c4f3f 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -493,17 +493,17 @@ Statement.prototype.buildStatement = function () { } if (state.commit === undefined && state.let && state.let.length) { - statement.push(state.let.map(function (item) { + statement.push('LET ' + state.let.map(function (item) { if (typeof item[1] === 'function') { var child = new Statement(self.db); child._state.paramIndex = self._state.paramIndex; item[1](child); - return 'LET ' + item[0] + ' = ' + child; + return item[0] + ' = ' + child; } else { - return "LET " + item[0] + ' = ' + item[1]; + return item[0] + ' = ' + item[1]; } - }).join('\n')); + }).join(',')); } if (state.to && state.to.length) { diff --git a/test/db/statement-test.js b/test/db/statement-test.js index e33052a..e4b6e79 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -41,7 +41,7 @@ describe("Database API - Statement", function () { .let('statuses', sub2) .buildStatement() .should - .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"\nLET statuses = SELECT status FROM OUser'); + .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE",statuses = SELECT status FROM OUser'); }); it('should let a variable equal a subexpression, more than once, using locks', function () { var sub1 = (new Statement(this.db)).select('name').from('OUser').where({status: 'ACTIVE'}), @@ -51,7 +51,7 @@ describe("Database API - Statement", function () { .let('statuses', sub2) .buildStatement() .should - .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"\nLET statuses = SELECT status FROM OUser LOCK record'); + .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE",statuses = SELECT status FROM OUser LOCK record'); }); it('should allow RIDs in LET expressions', function () { From cfb239024a0318a69e8ed0e08d0f3a5e7f1945c9 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Sun, 18 Jan 2015 00:48:40 +0000 Subject: [PATCH 216/308] Issue #203, utils.encode(), using SQL function 'date()' when encoding dates; - added tests --- lib/utils.js | 25 +++++++++++++++++++++- test/bugs/203-param-date.js | 41 +++++++++++++++++++++++++++++++++++++ test/core/utils.js | 4 ++-- 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 test/bugs/203-param-date.js diff --git a/lib/utils.js b/lib/utils.js index ecb819a..293b026 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -200,7 +200,7 @@ exports.encode = function encode (value) { return '"' + exports.escape(value) + '"'; } else if (value instanceof Date) { - return value.getTime(); + return 'date("' + getOrientDbUTCDate(value) + '", "yyyy-MM-dd HH:mm:ss", "UTC")'; } else if (value instanceof RID) { return value.toString(); @@ -275,6 +275,29 @@ exports.deprecate = function (context, name, message, fn) { }); }; + +/** + * Converts a date object to string observing OrientDB's default format + * + * @param {Date} The value to convert + * @return {String} The string formatted as 'yyyy-MM-dd HH:mm:ss' + */ +function getOrientDbUTCDate(date){ + var yyyy = pad(date.getUTCFullYear(), 4); + var MM = pad(date.getUTCMonth() + 1, 2); + var dd = pad(date.getUTCDate(), 2); + var HH = pad(date.getUTCHours(), 2); + var mm = pad(date.getUTCMinutes(), 2); + var ss = pad(date.getUTCSeconds(), 2); + + return yyyy + '-' + MM + '-' + dd + ' ' + HH + ':' + mm + ':' + ss; +} + +function pad (number, size){ + return (1e4 + number + "").slice(-size); +} + + /** * A class used solely to indicate that an object is an "array like" * It should never be used directly and is a temporary hack around some poor design decisions. diff --git a/test/bugs/203-param-date.js b/test/bugs/203-param-date.js new file mode 100644 index 0000000..b736d42 --- /dev/null +++ b/test/bugs/203-param-date.js @@ -0,0 +1,41 @@ +describe("Issue #203: support dates in parameterised queries", function () { + var date = new Date(Date.UTC(2015, 0, 17, 22, 5, 12)); + before(function () { + return CREATE_TEST_DB(this, 'testdb_issue_203') + .bind(this) + .then(function () { + return this.db.query('alter database timezone cet'); // something not UTC to avoid coincidences + }) + .then(function () { + return this.db.class.create('dates'); + }) + .then(function (item) { + this.class = item; + return this.class.property.create({ + name: 'schemaDate', + type: 'DateTime' + }); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_issue_203'); + }); + + it('should insert a date in schemaful mode', function () { + return this.db.insert().into('dates').set({ schemaDate: date }).one() + .then(function (result) { + result.should.have.property('@rid'); + result.should.have.property('schemaDate'); + result.schemaDate.should.eql(date); + }); + }); + + it('should insert a date in schemaless mode', function () { + return this.db.insert().into('dates').set({ newProp: date }).one() + .then(function (result) { + result.should.have.property('@rid'); + result.should.have.property('newProp'); + result.newProp.should.eql(date); + }); + }); +}); diff --git a/test/core/utils.js b/test/core/utils.js index 9eca546..a86c30f 100644 --- a/test/core/utils.js +++ b/test/core/utils.js @@ -10,7 +10,7 @@ describe('utils.prepare', function () { utils.prepare("select from index:foo where key = :key", {key: 123}).should.equal("select from index:foo where key = 123"); }); it("should prepare SQL statements with date parameters", function () { - var date = new Date(); - utils.prepare("select from index:foo where date = :date", {date: date}).should.equal("select from index:foo where date = " + date.getTime()); + var date = new Date(Date.UTC(2015, 0, 5, 22, 7, 5)); + utils.prepare("select from index:foo where date = :date", {date: date}).should.equal("select from index:foo where date = date(\"2015-01-05 22:07:05\", \"yyyy-MM-dd HH:mm:ss\", \"UTC\")"); }); }); \ No newline at end of file From f0ec552b4ccc5baa620837efe5e62ecd89550e15 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 23 Jan 2015 00:43:31 +0000 Subject: [PATCH 217/308] support IS NULL and IS NOT NULL, fix #207 --- lib/db/statement.js | 16 +++++++++++++--- test/db/statement-test.js | 7 +++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index e0c4f3f..b9835c9 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -746,9 +746,19 @@ Statement.prototype._objectToCondition = function (obj, operator) { operator = operator || '='; for (i = 0; i < total; i++) { key = keys[i]; - paramName = 'param' + paramify(key) + (this._state.paramIndex++); - conditions.push(key + ' ' + operator + ' :' + paramName); - this.addParam(paramName, obj[key]); + if (obj[key] === null) { + if (operator === '!=' || operator === '<>' || operator === 'NOT') { + conditions.push(key + ' IS NOT NULL'); + } + else { + conditions.push(key + ' IS NULL'); + } + } + else { + paramName = 'param' + paramify(key) + (this._state.paramIndex++); + conditions.push(key + ' ' + operator + ' :' + paramName); + this.addParam(paramName, obj[key]); + } } if (conditions.length === 0) { diff --git a/test/db/statement-test.js b/test/db/statement-test.js index e4b6e79..a150eb2 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -282,6 +282,13 @@ COMMIT \n\ }); this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE (name = :paramname0 AND foo = :paramfoo1)'); }); + it('should generate IS NULL in a where clause', function () { + this.statement.select().from('OUser').where({ + name: 'root', + foo: null + }); + this.statement.toString().should.equal('SELECT * FROM OUser WHERE (name = "root" AND foo IS NULL)'); + }); it('should build a chained where clause', function () { this.statement.select().from('OUser').where('1=1').where('2=2'); this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE 1=1 AND 2=2'); From 699f08e0c9586b6421bff4d73927b31156257380 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 23 Jan 2015 00:48:24 +0000 Subject: [PATCH 218/308] close #206 - failed to replicate --- test/bugs/206-insert-plus.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 test/bugs/206-insert-plus.js diff --git a/test/bugs/206-insert-plus.js b/test/bugs/206-insert-plus.js new file mode 100644 index 0000000..36a842f --- /dev/null +++ b/test/bugs/206-insert-plus.js @@ -0,0 +1,32 @@ +describe("Bug #206: Inserting string value '+' produces error ", function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_206') + .bind(this) + .then(function () { + return this.db.class.create('SomeClass', 'V'); + }) + .then(function (item) { + this.class = item; + return this.class.property.create({ + name: 'val', + type: 'String' + }); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_206'); + }); + it('should insert a "+" sign at the start of a field', function () { + return this.db.insert().into('SomeClass').set({val: '+hello'}).one() + .then(function (result) { + result.val.should.equal('+hello'); + }); + }); + + it('should insert a "+" sign as the value of a field', function () { + return this.db.insert().into('SomeClass').set({val: '+'}).one() + .then(function (result) { + result.val.should.equal('+'); + }); + }); +}); \ No newline at end of file From 353cd732c780c09ec40aa649fe8a345705f4c6be Mon Sep 17 00:00:00 2001 From: orangemug Date: Mon, 26 Jan 2015 13:43:00 +0000 Subject: [PATCH 219/308] Bumped the bluebird version which fixes #211 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 896709d..403ea90 100755 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "url": "http://github.com/codemix/oriento.git" }, "dependencies": { - "bluebird": "~1.2.3", + "bluebird": "~2.9.2", "request": "~2.34.0", "yargs": "~1.2.1" }, From 72924cf198d360ab25117b44fbf37f51bbec8835 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 26 Jan 2015 14:09:35 +0000 Subject: [PATCH 220/308] prepare 1.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 403ea90..def27ed 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node", "node.js" ], - "version": "1.0.0", + "version": "1.0.1", "author": { "name": "Charles Pick", "email": "charles@codemix.com" From b831141ef25c0cbb6b7388b28350d21bbc1cf76d Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 27 Jan 2015 12:35:58 +0000 Subject: [PATCH 221/308] add 2.0.1-SNAPSHOT and 2.1-SNAPSHOT to .travis.yml --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ee97a27..9395283 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,5 @@ before_script: - ./ci/initialize-ci.sh $ORIENTDB_VERSION env: - ORIENTDB_VERSION=1.7.10 - - ORIENTDB_VERSION=2.0-SNAPSHOT + - ORIENTDB_VERSION=2.0.1-SNAPSHOT + - ORIENTDB_VERSION=2.1-SNAPSHOT From be0140b049cc58e4e613c574c8ec20b7f0f01688 Mon Sep 17 00:00:00 2001 From: orangemug Date: Wed, 28 Jan 2015 19:33:49 +0000 Subject: [PATCH 222/308] Updated should dep to latest. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 896709d..8bc2243 100755 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ }, "devDependencies": { "mocha": "^2.0.1", - "should": "~3.3.1", + "should": "~4.6.2", "expect.js": "~0.3.1", "istanbul": "~0.2.7", "jshint": "~2.5.0" From fd9cc70c7ca2a580539667bb8db59ce9a9e9e504 Mon Sep 17 00:00:00 2001 From: orangemug Date: Wed, 28 Jan 2015 19:36:54 +0000 Subject: [PATCH 223/308] Added tests for new query events. --- test/db/db-test.js | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/test/db/db-test.js b/test/db/db-test.js index 6df7dbb..74fd178 100644 --- a/test/db/db-test.js +++ b/test/db/db-test.js @@ -153,4 +153,36 @@ describe("Database API", function () { }); }); }); -}); \ No newline at end of file + + describe('Db::on()', function () { + it('should emit a beginQuery event', function () { + var emitedObject; + this.db.on("beginQuery", function(obj) { + emitedObject = obj; + }); + + return this.db.select('name, status').from('OUser').limit(1).one() + .then(function () { + emitedObject.should.have.propertyByPath("query"); + emitedObject.should.have.propertyByPath("mode"); + emitedObject.should.have.propertyByPath("fetchPlan"); + emitedObject.should.have.propertyByPath("limit"); + emitedObject.should.have.propertyByPath("params"); + emitedObject.query.should.equal("SELECT name, status FROM OUser LIMIT 1"); + }); + }); + + it('should emit a endQuery event', function () { + var emitedObject; + this.db.on("endQuery", function(obj) { + emitedObject = obj; + }); + + return this.db.select('name, status').from('OUser').limit(1).one() + .then(function () { + emitedObject.should.have.propertyByPath("perf", "query"); + emitedObject.perf.query.should.be.above(0); + }); + }); + }); +}); From 6a7b09ad3f857d7b4b6efcf5c26fb038b288621d Mon Sep 17 00:00:00 2001 From: orangemug Date: Wed, 28 Jan 2015 19:38:43 +0000 Subject: [PATCH 224/308] Added beginQuery/endQuery events. --- lib/db/index.js | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index a546761..49145c8 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -6,7 +6,10 @@ var utils = require('../utils'), RID = require('../recordid'), Query = require('./query'), Transaction = require('./transaction'), - ArrayLike = utils.ArrayLike; + ArrayLike = utils.ArrayLike, + inherits = require("util").inherits, + EventEmitter = require('events').EventEmitter; + /** @@ -32,6 +35,8 @@ function Db (config) { this.augment('index', require('./index/index')); } +inherits(Db, EventEmitter); + Db.prototype.augment = utils.augment; Db.extend = utils.extend; @@ -220,7 +225,24 @@ Db.prototype.exec = function (query, options) { this.server.logger.debug('executing query against db ' + this.name + ': ' + query); - return this.send('command', data); + this.emit("beginQuery", { + query: data.query, + mode: data.mode, + fetchPlan: data.fetchPlan, + limit: data.limit, + params: data.params + }); + + var s = Date.now(); + return this.send('command', data).then(function(res) { + var e = Date.now(); + this.emit("endQuery", { + perf: { + query: e-s + } + }); + return res; + }.bind(this)); }; /** From a8f7f99d95d1f2a39e64b10ef3f76d5bf96d2772 Mon Sep 17 00:00:00 2001 From: orangemug Date: Wed, 28 Jan 2015 19:49:35 +0000 Subject: [PATCH 225/308] Fixed tests post should.js dep upgrade. --- test/bugs/25-property-create.js | 7 +++++-- test/bugs/82-emojis.js | 2 +- test/db/property-test.js | 6 +++--- test/db/query-test.js | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/test/bugs/25-property-create.js b/test/bugs/25-property-create.js index fa93588..29c084d 100644 --- a/test/bugs/25-property-create.js +++ b/test/bugs/25-property-create.js @@ -22,7 +22,10 @@ describe("Bug #25: Create undefined in Myclass.property.create", function () { }; return this.class.property.create(values) .then(function (prop) { - prop.should.have.properties(values); + prop.should.have.property('name'); + prop.should.have.property('type'); + prop.should.have.property('mandatory'); + prop.should.have.property('max'); }) }); -}); \ No newline at end of file +}); diff --git a/test/bugs/82-emojis.js b/test/bugs/82-emojis.js index e822aef..a9dce62 100644 --- a/test/bugs/82-emojis.js +++ b/test/bugs/82-emojis.js @@ -30,7 +30,7 @@ describe("Bug #82: db.query errors when parsing emojis ", function () { return this.db.query(query) .bind(this) .spread(function (result) { - result.should.eql(1); + result.should.eql('1'); return this.db.query('SELECT * FROM #5:0'); }) .spread(function (result) { diff --git a/test/db/property-test.js b/test/db/property-test.js index 025b7d1..1213828 100644 --- a/test/db/property-test.js +++ b/test/db/property-test.js @@ -40,7 +40,7 @@ describe("Database API - Class - Property", function () { }) .then(function (item) { item.name.should.equal('customprop'); - item.max.should.eql(20); + item.max.should.eql('20'); }); }); it('should create an array of properties', function () { @@ -91,7 +91,7 @@ describe("Database API - Class - Property", function () { }) .then(function (item) { item.name.should.equal('myprop2'); - item.max.should.eql(20); + item.max.should.eql('20'); }); }); }); @@ -110,4 +110,4 @@ describe("Database API - Class - Property", function () { }); -}); \ No newline at end of file +}); diff --git a/test/db/query-test.js b/test/db/query-test.js index 43151fe..291c87f 100644 --- a/test/db/query-test.js +++ b/test/db/query-test.js @@ -307,7 +307,7 @@ describe("Database API - Query", function () { it('should update a user', function () { return this.db.update('OUser').set({foo: 'bar'}).where({name: 'reader'}).limit(1).scalar() .then(function (count) { - count.should.eql(1); + count.should.eql('1'); }); }); }); @@ -368,4 +368,4 @@ describe("Database API - Query", function () { }); }); }); -}); \ No newline at end of file +}); From 87abd9e08e21c41ef8e7d45a8b7d7c212bd368d0 Mon Sep 17 00:00:00 2001 From: orangemug Date: Wed, 28 Jan 2015 19:54:11 +0000 Subject: [PATCH 226/308] Change to use "propertyByPath" rather than "property" --- test/db/db-test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/db/db-test.js b/test/db/db-test.js index 74fd178..526ec2b 100644 --- a/test/db/db-test.js +++ b/test/db/db-test.js @@ -163,11 +163,11 @@ describe("Database API", function () { return this.db.select('name, status').from('OUser').limit(1).one() .then(function () { - emitedObject.should.have.propertyByPath("query"); - emitedObject.should.have.propertyByPath("mode"); - emitedObject.should.have.propertyByPath("fetchPlan"); - emitedObject.should.have.propertyByPath("limit"); - emitedObject.should.have.propertyByPath("params"); + emitedObject.should.have.property("query"); + emitedObject.should.have.property("mode"); + emitedObject.should.have.property("fetchPlan"); + emitedObject.should.have.property("limit"); + emitedObject.should.have.property("params"); emitedObject.query.should.equal("SELECT name, status FROM OUser LIMIT 1"); }); }); From 4d0eef10afb54988383f325239199ef1c5103689 Mon Sep 17 00:00:00 2001 From: orangemug Date: Wed, 28 Jan 2015 20:04:02 +0000 Subject: [PATCH 227/308] Switched to using bluebird bind. --- lib/db/index.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index 49145c8..f36a69a 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -234,15 +234,17 @@ Db.prototype.exec = function (query, options) { }); var s = Date.now(); - return this.send('command', data).then(function(res) { - var e = Date.now(); - this.emit("endQuery", { - perf: { - query: e-s - } + return this.send('command', data) + .bind(this) + .then(function(res) { + var e = Date.now(); + this.emit("endQuery", { + perf: { + query: e-s + } + }); + return res; }); - return res; - }.bind(this)); }; /** From 371d7dc5ca49b19a81c3e6b460ab378878f25161 Mon Sep 17 00:00:00 2001 From: orangemug Date: Wed, 28 Jan 2015 20:19:28 +0000 Subject: [PATCH 228/308] Added event listener count. --- lib/db/index.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index f36a69a..24df7cf 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -225,26 +225,31 @@ Db.prototype.exec = function (query, options) { this.server.logger.debug('executing query against db ' + this.name + ': ' + query); - this.emit("beginQuery", { - query: data.query, - mode: data.mode, - fetchPlan: data.fetchPlan, - limit: data.limit, - params: data.params - }); + if(this.listeners('beginQuery').length > 0) { + this.emit("beginQuery", { + query: data.query, + mode: data.mode, + fetchPlan: data.fetchPlan, + limit: data.limit, + params: data.params + }); + } - var s = Date.now(); - return this.send('command', data) - .bind(this) - .then(function(res) { - var e = Date.now(); + var promise = this.send('command', data); + + if(this.listeners('endQuery').length > 0) { + var e, s = Date.now(); + promise.bind(this).tap(function() { + e = Date.now(); this.emit("endQuery", { perf: { query: e-s } }); - return res; }); + } + + return promise; }; /** From 44c8481e4eaae7d6098cb6fb44f5403e87bb98c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Marques?= Date: Thu, 29 Jan 2015 00:38:13 -0200 Subject: [PATCH 229/308] Added "put" clause support --- lib/db/statement.js | 27 +++++++++++++++++++++++++++ test/db/statement-test.js | 19 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/lib/db/statement.js b/lib/db/statement.js index b9835c9..d97f14b 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -97,6 +97,19 @@ Statement.prototype.to = clause('to'); */ Statement.prototype.set = clause('set'); +/** + * A `put` clause for embeddedmap properties. + * + * @param {String} property The property name to put the key to. + * @param {Object} keysValues The keys and values to add in the embeddedmap property. + * @return {Statement} The statement object. + */ +Statement.prototype.put = function (property, keysValues) { + this._state.put = this._state.put || []; + this._state.put.push([property, keysValues]); + return this; +}; + /** * Upsert the records to avoid multiple queries. * @@ -562,6 +575,20 @@ Statement.prototype.buildStatement = function () { }, this).filter(function (item) { return item; }).join(', ')); } + if (state.put && state.put.length) { + statement.push('PUT'); + statement.push(state.put.map(function (item) { + var objectToAdd = item[1]; + var keys = Object.keys(objectToAdd); + var propertyName = item[0]; + return keys.map(function (key) { + var paramName = 'param' + paramify(propertyName + key) + (this._state.paramIndex++); + this.addParam(paramName, objectToAdd[key]); + return propertyName + ' = \'' + key + '\', :' + paramName; + }, this).join(', '); + }, this).join(', ')); + } + if (state.upsert) { statement.push('UPSERT'); } diff --git a/test/db/statement-test.js b/test/db/statement-test.js index a150eb2..31bca0b 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -395,4 +395,23 @@ COMMIT \n\ this.statement.buildStatement().should.equal('SELECT * FROM OUser WHERE [latitude,longitude] WITHIN [[1,2],[3,4]]'); }); }); + + describe('Statement::put()', function () { + it('should build a put query', function () { + this.statement.update('#1:1') + .put('fooMap', { + foo: 'fooVal', + greeting: 'hello world' + }) + .put('barMap', { + bar: 'barVal', + name: 'mario' + }); + this.statement.buildStatement().should.equal('UPDATE #1:1 PUT ' + + 'fooMap = \'foo\', :paramfooMapfoo0, ' + + 'fooMap = \'greeting\', :paramfooMapgreeting1, ' + + 'barMap = \'bar\', :parambarMapbar2, ' + + 'barMap = \'name\', :parambarMapname3'); + }); + }); }); \ No newline at end of file From ee5ddf71dba4fa4b7e0b37e4c4017239d4c3195a Mon Sep 17 00:00:00 2001 From: orangemug Date: Thu, 29 Jan 2015 14:59:48 +0000 Subject: [PATCH 230/308] Fixed README property.delete -> property.drop. Fixes #218 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c78748a..0d31978 100644 --- a/README.md +++ b/README.md @@ -371,7 +371,7 @@ MyClass.property.create({ ### Deleting a property from a class ```js -MyClass.property.delete('myprop') +MyClass.property.drop('myprop') .then(function () { console.log('Property deleted.'); }); From 970a234c0739e49064c81b860b49eb2dc1be9d61 Mon Sep 17 00:00:00 2001 From: orangemug Date: Mon, 2 Feb 2015 10:11:44 +0000 Subject: [PATCH 231/308] Added 'property.rename' to docs. Fixes #222 --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index c78748a..e727ee2 100644 --- a/README.md +++ b/README.md @@ -377,6 +377,15 @@ MyClass.property.delete('myprop') }); ``` +### Renaming a property on a class + +```js +MyClass.property.rename('myprop', 'mypropchanged'); +.then(function () { + console.log('Property renamed.'); +}); +``` + ### Creating a record for a class ```js From b452589ccf9d9b09d85741df6179ad62dfe77cc6 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 2 Feb 2015 18:16:48 +0000 Subject: [PATCH 232/308] don't paramify undefined values --- lib/db/statement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index b9835c9..ba728ed 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -784,7 +784,7 @@ Statement.prototype._objectToSet = function (obj) { if (value instanceof RID) { expressions.push(key + ' = ' + value); } - else { + else if (value !== undefined) { paramName = 'param' + paramify(key) + (this._state.paramIndex++); expressions.push(key + ' = :' + paramName); this.addParam(paramName, value); From 0ce41fb35075389631804501be9970d7a5cab137 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 2 Feb 2015 18:17:57 +0000 Subject: [PATCH 233/308] add toOrient() method / interface --- lib/transport/binary/protocol28/serializer.js | 6 +++++- lib/utils.js | 9 ++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/transport/binary/protocol28/serializer.js b/lib/transport/binary/protocol28/serializer.js index 3794567..b2e6933 100644 --- a/lib/transport/binary/protocol28/serializer.js +++ b/lib/transport/binary/protocol28/serializer.js @@ -20,6 +20,10 @@ function encodeRecordData (content) { * @return {String} The serialized document. */ function serializeDocument (document, isMap) { + if (typeof document.toOrient === 'function') { + document = document.toOrient(); + } + var result = '', className = '', fieldNames = Object.keys(document), @@ -32,7 +36,7 @@ function serializeDocument (document, isMap) { if (field === '@class') { className = value; } - else if (field.charAt(0) === '@') { + else if (field.charAt(0) === '@' || value === undefined) { continue; } else { diff --git a/lib/utils.js b/lib/utils.js index 293b026..6de71fe 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -171,7 +171,7 @@ function prepareArray (query, params) { function prepareObject (query, params) { var pattern = /"(\\[\s\S]|[^"])*"|'(\\[\s\S]|[^'])*'|([^A-Za-z0-9]:([A-Za-z][A-Za-z0-9_-]*))/g; return query.replace(pattern, function (all, double, single, char, param) { - if (param) { + if (param && params[param] !== undefined) { return char.charAt(0) + exports.encode(params[param]); } else { @@ -205,6 +205,9 @@ exports.encode = function encode (value) { else if (value instanceof RID) { return value.toString(); } + else if (typeof value.toOrient === 'function') { + return encode(value.toOrient()); + } else if (Array.isArray(value)) { return '[' + value.map(encode) + ']'; } @@ -278,7 +281,7 @@ exports.deprecate = function (context, name, message, fn) { /** * Converts a date object to string observing OrientDB's default format - * + * * @param {Date} The value to convert * @return {String} The string formatted as 'yyyy-MM-dd HH:mm:ss' */ @@ -289,7 +292,7 @@ function getOrientDbUTCDate(date){ var HH = pad(date.getUTCHours(), 2); var mm = pad(date.getUTCMinutes(), 2); var ss = pad(date.getUTCSeconds(), 2); - + return yyyy + '-' + MM + '-' + dd + ' ' + HH + ':' + mm + ':' + ss; } From c13fc2df929d4d25491f6f006c3e6f4d34a2e628 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 2 Feb 2015 18:23:00 +0000 Subject: [PATCH 234/308] add forcePrepare() to avoid problems with bound parameters in OrientDB (enabled by default) --- lib/db/index.js | 1 + .../binary/protocol28/operations/command.js | 29 ++++++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index a546761..d845b44 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -45,6 +45,7 @@ module.exports = Db; */ Db.prototype.configure = function (config) { this.sessionId = config.sessionId != null ? config.sessionId : -1; + this.forcePrepare = config.forcePrepare != null ? config.forcePrepare : true; this.name = config.name; this.server = config.server; diff --git a/lib/transport/binary/protocol28/operations/command.js b/lib/transport/binary/protocol28/operations/command.js index fe5ad6f..abf5024 100644 --- a/lib/transport/binary/protocol28/operations/command.js +++ b/lib/transport/binary/protocol28/operations/command.js @@ -4,7 +4,8 @@ var Operation = require('../operation'), constants = require('../constants'), serializer = require('../serializer'), writer = require('../writer'), - RID = require('../../../../recordid'); + RID = require('../../../../recordid'), + utils = require('../../../../utils'); module.exports = Operation.extend({ id: 'REQUEST_COMMAND', @@ -22,17 +23,25 @@ module.exports = Operation.extend({ serializeQuery: function () { var buffers = [writer.writeString(this.data.class)]; + var text = this.data.query; + var params = this.data.params; + // if there are bound parameters, force prepare them, OrientDB's support is limited in this version. + if (this.data.db && this.data.db.forcePrepare && params && params.params) { + text = utils.prepare(text, params.params); + params = undefined; + } + if (this.data.class === 'q' || this.data.class === 'com.orientechnologies.orient.core.sql.query.OSQLSynchQuery' || this.data.class === 'com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery') { buffers.push( - writer.writeString(this.data.query), + writer.writeString(text), writer.writeInt(this.data.limit), writer.writeString(this.data.fetchPlan || '') ); - if (this.data.params) { - buffers.push(writer.writeString(serializeParams(this.data.params))); + if (params) { + buffers.push(writer.writeString(serializeParams(params))); } else { buffers.push(writer.writeInt(0)); @@ -43,12 +52,12 @@ module.exports = Operation.extend({ this.data.class === 'com.orientechnologies.orient.core.command.script.OCommandScript') { buffers.push( writer.writeString(this.data.language || 'sql'), - writer.writeString(this.data.query) + writer.writeString(text) ); - if (this.data.params && this.data.params.params && Object.keys(this.data.params.params).length) { + if (params && params.params && Object.keys(params.params).length) { buffers.push( writer.writeBoolean(true), - writer.writeString(serializeParams(this.data.params)) + writer.writeString(serializeParams(params)) ); } else { @@ -57,11 +66,11 @@ module.exports = Operation.extend({ buffers.push(writer.writeByte(0)); } else { - buffers.push(writer.writeString(this.data.query)); - if (this.data.params) { + buffers.push(writer.writeString(text)); + if (params) { buffers.push( writer.writeBoolean(true), - writer.writeString(serializeParams(this.data.params)) + writer.writeString(serializeParams(params)) ); } else { From d44fc8d2e6dcd1f4b5ec0815f4c95abcde487cee Mon Sep 17 00:00:00 2001 From: orangemug Date: Mon, 2 Feb 2015 18:32:21 +0000 Subject: [PATCH 235/308] Added err to endQuery event. --- test/db/db-test.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/test/db/db-test.js b/test/db/db-test.js index 526ec2b..15e71ec 100644 --- a/test/db/db-test.js +++ b/test/db/db-test.js @@ -172,7 +172,7 @@ describe("Database API", function () { }); }); - it('should emit a endQuery event', function () { + it('should emit a endQuery event with success', function () { var emitedObject; this.db.on("endQuery", function(obj) { emitedObject = obj; @@ -181,8 +181,26 @@ describe("Database API", function () { return this.db.select('name, status').from('OUser').limit(1).one() .then(function () { emitedObject.should.have.propertyByPath("perf", "query"); + emitedObject.should.have.property("err"); emitedObject.perf.query.should.be.above(0); + (isNaN(emitedObject.err)).should.be.true; }); }); + + it('should emit a endQuery event with error', function () { + var emitedObject; + this.db.on("endQuery", function(obj) { + emitedObject = obj; + }); + + return this.db.select('name, status').from('Invalid').limit(1).one() + .catch(function (err) { + emitedObject.should.have.propertyByPath("perf", "query"); + emitedObject.should.have.property("err"); + emitedObject.perf.query.should.be.above(0); + emitedObject.err.should.be.ok; + }); + }); + }); }); From 9f51c6bc84913a74b7822f42d493aff0612a5831 Mon Sep 17 00:00:00 2001 From: orangemug Date: Mon, 2 Feb 2015 18:43:26 +0000 Subject: [PATCH 236/308] Added 'err' to 'endEvent' object --- lib/db/index.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index 24df7cf..a405bf8 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -238,14 +238,20 @@ Db.prototype.exec = function (query, options) { var promise = this.send('command', data); if(this.listeners('endQuery').length > 0) { - var e, s = Date.now(); - promise.bind(this).tap(function() { - e = Date.now(); - this.emit("endQuery", { - perf: { - query: e-s - } - }); + var err, e, s = Date.now(); + promise + .bind(this) + .catch(function(_err) { + err = _err; + }) + .tap(function() { + e = Date.now(); + this.emit("endQuery", { + err: err, + perf: { + query: e-s + } + }); }); } From 1bf5cdbb0639fc84a93be54198d2911ff508f58f Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 2 Feb 2015 22:25:20 +0000 Subject: [PATCH 237/308] let clauses are now wrapped in parens, fixes #226 --- lib/db/statement.js | 8 +++++++- test/db/db-test.js | 1 + test/db/statement-test.js | 6 +++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index ba728ed..f6ba789 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -498,7 +498,13 @@ Statement.prototype.buildStatement = function () { var child = new Statement(self.db); child._state.paramIndex = self._state.paramIndex; item[1](child); - return item[0] + ' = ' + child; + return item[0] + ' = (' + child + ')'; + } + else if (item[1] instanceof Statement) { + return item[0] + ' = (' + item[1].toString() + ')'; + } + else if (/\s/.test(item[1])) { + return item[0] + ' = (' + item[1] + ')'; } else { return item[0] + ' = ' + item[1]; diff --git a/test/db/db-test.js b/test/db/db-test.js index 526ec2b..e18bfea 100644 --- a/test/db/db-test.js +++ b/test/db/db-test.js @@ -179,6 +179,7 @@ describe("Database API", function () { }); return this.db.select('name, status').from('OUser').limit(1).one() + .delay(10) // solves a strange race condition which happens about 1/20th of the time, needs further investigation. .then(function () { emitedObject.should.have.propertyByPath("perf", "query"); emitedObject.perf.query.should.be.above(0); diff --git a/test/db/statement-test.js b/test/db/statement-test.js index a150eb2..de9ad98 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -31,7 +31,7 @@ describe("Database API - Statement", function () { .let('names', sub) .buildStatement() .should - .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE"'); + .equal('LET names = (SELECT name FROM OUser WHERE status = "ACTIVE")'); }); it('should let a variable equal a subexpression, more than once', function () { var sub1 = (new Statement(this.db)).select('name').from('OUser').where({status: 'ACTIVE'}), @@ -41,7 +41,7 @@ describe("Database API - Statement", function () { .let('statuses', sub2) .buildStatement() .should - .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE",statuses = SELECT status FROM OUser'); + .equal('LET names = (SELECT name FROM OUser WHERE status = "ACTIVE"),statuses = (SELECT status FROM OUser)'); }); it('should let a variable equal a subexpression, more than once, using locks', function () { var sub1 = (new Statement(this.db)).select('name').from('OUser').where({status: 'ACTIVE'}), @@ -51,7 +51,7 @@ describe("Database API - Statement", function () { .let('statuses', sub2) .buildStatement() .should - .equal('LET names = SELECT name FROM OUser WHERE status = "ACTIVE",statuses = SELECT status FROM OUser LOCK record'); + .equal('LET names = (SELECT name FROM OUser WHERE status = "ACTIVE"),statuses = (SELECT status FROM OUser LOCK record)'); }); it('should allow RIDs in LET expressions', function () { From 7362912dcdf4191e5acfbe194ebcd2833a9bf9f4 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 2 Feb 2015 23:44:52 +0000 Subject: [PATCH 238/308] allow passing a Statement or Query instance directly to .exec() and .query() --- lib/db/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/db/index.js b/lib/db/index.js index 708e036..106ab74 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -4,6 +4,7 @@ var utils = require('../utils'), errors = require('../errors'), Promise = require('bluebird'), RID = require('../recordid'), + Statement = require('./statement'), Query = require('./query'), Transaction = require('./transaction'), ArrayLike = utils.ArrayLike, @@ -183,7 +184,13 @@ Db.prototype.begin = function () { * @promise {Mixed} The results of the query / command. */ Db.prototype.exec = function (query, options) { - options = options || {}; + if (query instanceof Statement) { + options = query.buildOptions(); + query = query.toString(); + } + else if (!options) { + options = {}; + } var data = { query: query, mode: options.mode || 's', From 6070785dca8cb35fb89b42e8f37c95f6ab6efe7a Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 2 Feb 2015 23:51:14 +0000 Subject: [PATCH 239/308] investigate #224 --- test/bugs/224-rest-vs-binary-protocol.js | 82 ++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 test/bugs/224-rest-vs-binary-protocol.js diff --git a/test/bugs/224-rest-vs-binary-protocol.js b/test/bugs/224-rest-vs-binary-protocol.js new file mode 100644 index 0000000..9e7ffb8 --- /dev/null +++ b/test/bugs/224-rest-vs-binary-protocol.js @@ -0,0 +1,82 @@ +var Bluebird = require('bluebird'); + +describe("Bug #224: REST vs BINARY protocol", function () { + var rid; + before(function () { + var self = this; + return CREATE_TEST_DB(this, 'testdb_bug_224') + .then(function () { + return Bluebird.map( + [ + 'CREATE CLASS User extends V', + 'CREATE PROPERTY User.name STRING', + + 'CREATE CLASS Post extends V', + 'CREATE PROPERTY Post.message STRING', + 'CREATE PROPERTY Post.score INTEGER', + + 'CREATE CLASS WROTES extends E', + + + "CREATE VERTEX User SET name='Monica'", + + "CREATE VERTEX Post SET message='My 1st post', score = 1", + "CREATE VERTEX Post SET message='My 2nd post', score = 2", + + "CREATE EDGE WROTES FROM (SELECT FROM User) TO (SELECT FROM Post)", + ], + function (text) { + return self.db.query(text); + } + ); + }) + .then(function () { + return self.db.select('@rid').from('User').scalar(); + }) + .then(function (result) { + rid = result; + }); + }); + after(function () { + //return DELETE_TEST_DB('testdb_bug_224'); + }); + + it('should retrieve some records', function () { + return this.db + .select() + .from('User') + .all() + .then(function (results) { + results.length.should.be.above(0); + }); + }); + + it('should work correctly', function () { + var query = this.db + .select('name') + .select('$DescendingPosts AS Posts') + .from(rid.toString()) + .let('DescendingPosts', function (s) { + s + .select('@rid, message, score') + .from(function (s) { + s + .select('expand(out("WROTES"))') + .from('$parent.$current'); + }) + .order({ + "score": 'DESC' + }); + }) + .fetch({Posts: 1}); + + return query + .one() + .then(function (data) { + data.name.should.equal('Monica'); + data.Posts.length.should.equal(2); + data.Posts[0].message.should.equal('My 2nd post'); + data.Posts[1].message.should.equal('My 1st post'); + }) + }); +}); From fd1592c4128d97c58d3990a4edf7118581a7a3bf Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 3 Feb 2015 01:21:38 +0000 Subject: [PATCH 240/308] do class transforms at deserialize --- lib/db/index.js | 13 ++-- .../binary/protocol19/deserializer.js | 65 +++++++++++-------- lib/transport/binary/protocol19/operation.js | 6 +- .../protocol19/operations/record-load.js | 4 +- .../binary/protocol26/deserializer.js | 61 ++++++++++------- lib/transport/binary/protocol26/operation.js | 6 +- .../protocol26/operations/record-load.js | 4 +- .../binary/protocol28/deserializer.js | 59 ++++++++++------- lib/transport/binary/protocol28/operation.js | 6 +- .../protocol28/operations/record-load.js | 4 +- package.json | 1 + 11 files changed, 137 insertions(+), 92 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index 106ab74..c08b3bf 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -9,7 +9,8 @@ var utils = require('../utils'), Transaction = require('./transaction'), ArrayLike = utils.ArrayLike, inherits = require("util").inherits, - EventEmitter = require('events').EventEmitter; + EventEmitter = require('events').EventEmitter, + fast = require('fast.js'); @@ -69,6 +70,7 @@ Db.prototype.configure = function (config) { this.dataSegments = []; this.transactionId = 0; this.transformers = config.transformers || {}; + this.transformerFunctions = {}; return this; }; @@ -141,6 +143,7 @@ Db.prototype.send = function (operation, data) { data.sessionId = this.sessionId; data.database = this.name; data.db = this; + data.transformerFunctions = this.transformerFunctions; this.server.logger.debug('sending operation ' + operation + ' for database ' + this.name); return this.server.send(operation, data); }); @@ -343,9 +346,6 @@ Db.prototype.normalizeResultContent = function (content) { cluster: content.cluster, position: content.position }); - if (value['@class']) { - return this.transformDocument(value); - } return value; } else { @@ -363,7 +363,10 @@ Db.prototype.normalizeResultContent = function (content) { * @return {Db} The database instance. */ Db.prototype.registerTransformer = function (className, transformer) { - this.transformers[className] = this.transformers[className] || []; + if (!this.transformers[className]) { + this.transformers[className] = []; + this.transformerFunctions[className] = fast.bind(this.transformDocument, this); + } this.transformers[className].push(transformer); return this; }; diff --git a/lib/transport/binary/protocol19/deserializer.js b/lib/transport/binary/protocol19/deserializer.js index 4d02688..2d00933 100644 --- a/lib/transport/binary/protocol19/deserializer.js +++ b/lib/transport/binary/protocol19/deserializer.js @@ -6,10 +6,11 @@ var RID = require('../../../recordid'), /** * Deserialize the given record and return an object containing the values. * - * @param {String} input The serialized record. - * @return {Object} The deserialized record. + * @param {String} input The serialized record. + * @param {Object} classes The optional map of class names to transformers. + * @return {Object} The deserialized record. */ -function deserialize (input) { +function deserialize (input, classes) { var record = {'@type': 'd'}, chunk, key, value; if (!input) { @@ -28,9 +29,8 @@ function deserialize (input) { key = chunk[0]; input = chunk[1]; } - // read the first value. - chunk = eatValue(input); + chunk = eatValue(input, classes); value = chunk[0]; input = chunk[1]; record[key] = value; @@ -46,7 +46,7 @@ function deserialize (input) { key = chunk[0]; input = chunk[1]; if (input.length) { - chunk = eatValue(input); + chunk = eatValue(input, classes); value = chunk[0]; input = chunk[1]; record[key] = value; @@ -56,7 +56,12 @@ function deserialize (input) { } } - return record; + if (classes && record['@class'] && classes[record['@class']]) { + return classes[record['@class']](record); + } + else { + return record; + } } /** @@ -89,7 +94,6 @@ function eatFirstKey (input) { collected += c; } } - return [collected, input.slice(i + 1), isClassName]; } @@ -128,10 +132,11 @@ function eatKey (input) { /** * Consume a field value. * - * @param {String} input The input to parse. - * @return {[Mixed, String]} The collected value, and any remaining input. + * @param {String} input The input to parse. + * @param {Object} classes The optional map of class names to transformers. + * @return {[Mixed, String]} The collected value, and any remaining input. */ -function eatValue (input) { +function eatValue (input, classes) { var c, n; c = input.charAt(0); while (c === ' ' && input.length) { @@ -150,16 +155,16 @@ function eatValue (input) { return eatRID(input.slice(1)); } else if (c === '[') { - return eatArray(input.slice(1)); + return eatArray(input.slice(1), classes); } else if (c === '<') { - return eatSet(input.slice(1)); + return eatSet(input.slice(1), classes); } else if (c === '{') { - return eatMap(input.slice(1)); + return eatMap(input.slice(1), classes); } else if (c === '(') { - return eatRecord(input.slice(1)); + return eatRecord(input.slice(1), classes); } else if (c === '%') { return eatBag(input.slice(1)); @@ -230,8 +235,8 @@ function eatNumber (input) { num = input.match(pattern); if (num) { - collected = num[0]; - i = collected.length; + collected = num[0]; + i = collected.length; } collected = +collected; @@ -283,9 +288,10 @@ function eatRID (input) { * Consume an array. * * @param {String} input The input to parse. + * @param {Object} classes The optional map of class names to transformers. * @return {[Array, String]} The collected array, and any remaining input. */ -function eatArray (input) { +function eatArray (input, classes) { var length = input.length, array = [], chunk, c; @@ -299,7 +305,7 @@ function eatArray (input) { input = input.slice(1); break; } - chunk = eatValue(input); + chunk = eatValue(input, classes); array.push(chunk[0]); input = chunk[1]; } @@ -311,9 +317,10 @@ function eatArray (input) { * Consume a set. * * @param {String} input The input to parse. + * @param {Object} classes The optional map of class names to transformers. * @return {[Array, String]} The collected set, and any remaining input. */ -function eatSet (input) { +function eatSet (input, classes) { var length = input.length, set = [], chunk, c; @@ -327,7 +334,7 @@ function eatSet (input) { input = input.slice(1); break; } - chunk = eatValue(input); + chunk = eatValue(input, classes); set.push(chunk[0]); input = chunk[1]; } @@ -339,9 +346,10 @@ function eatSet (input) { * Consume a map (object). * * @param {String} input The input to parse. + * @param {Object} classes The optional map of class names to transformers. * @return {[Object, String]} The collected map, and any remaining input. */ -function eatMap (input) { +function eatMap (input, classes) { var length = input.length, map = {}, key, value, chunk, c; @@ -364,7 +372,7 @@ function eatMap (input) { key = chunk[0]; input = chunk[1]; if (input.length) { - chunk = eatValue(input); + chunk = eatValue(input, classes); value = chunk[0]; input = chunk[1]; map[key] = value; @@ -381,9 +389,10 @@ function eatMap (input) { * Consume an embedded record. * * @param {String} input The input to parse. + * @param {Object} classes The optional map of class names to transformers. * @return {[Object, String]} The collected record, and any remaining input. */ -function eatRecord (input) { +function eatRecord (input, classes) { var record = {'@type': 'd'}, chunk, c, key, value; @@ -434,7 +443,7 @@ function eatRecord (input) { } // read the first value. - chunk = eatValue(input); + chunk = eatValue(input, classes); value = chunk[0]; input = chunk[1]; record[key] = value; @@ -456,7 +465,7 @@ function eatRecord (input) { key = chunk[0]; input = chunk[1]; if (input.length) { - chunk = eatValue(input); + chunk = eatValue(input, classes); value = chunk[0]; input = chunk[1]; record[key] = value; @@ -466,6 +475,10 @@ function eatRecord (input) { } } + if (classes && record['@class'] && classes[record['@class']]) { + record = classes[record['@class']](record); + } + return [record, input]; } diff --git a/lib/transport/binary/protocol19/operation.js b/lib/transport/binary/protocol19/operation.js index b55eb1c..33a74e5 100644 --- a/lib/transport/binary/protocol19/operation.js +++ b/lib/transport/binary/protocol19/operation.js @@ -303,7 +303,7 @@ Operation.prototype.readError = function (fieldName, reader) { */ Operation.prototype.readObject = function (fieldName, reader) { this.readOps.push(['String', [fieldName, function (data, fieldName) { - data[fieldName] = deserializer.deserialize(data[fieldName]); + data[fieldName] = deserializer.deserialize(data[fieldName], this.data.transformerFunctions); if (reader) { reader.call(this, data, fieldName); } @@ -691,7 +691,7 @@ Operation.prototype.parseRecord = function (buffer, offset, context, fieldName, .readLong('position') .readInt('version') .readString('value', function (data, key) { - data[key] = deserializer.deserialize(data[key]); + data[key] = deserializer.deserialize(data[key], this.data.transformerFunctions); this.stack.pop(); this.readOps.push(function () { if (reader) { @@ -862,7 +862,7 @@ Operation.prototype.parsePushedData = function (buffer, offset, context, fieldNa asString = buffer.toString('utf8', offset, offset + length); switch (asString.charAt(0)) { case 'R': - context[fieldName] = deserializer.deserialize(asString.slice(1)); + context[fieldName] = deserializer.deserialize(asString.slice(1), this.data.transformerFunctions); break; default: console.log('unsupported pushed data format: ' + asString); diff --git a/lib/transport/binary/protocol19/operations/record-load.js b/lib/transport/binary/protocol19/operations/record-load.js index 93123d9..6c2134f 100644 --- a/lib/transport/binary/protocol19/operations/record-load.js +++ b/lib/transport/binary/protocol19/operations/record-load.js @@ -78,7 +78,7 @@ module.exports = Operation.extend({ data.cluster = this.data.cluster; data.position = this.data.position; if (data[fieldName] === 'd') { - data.content = deserializer.deserialize(data.content); + data.content = deserializer.deserialize(data.content, this.data.transformerFunctions); } this.stack.pop(); this.readPayload(records, ender); @@ -104,7 +104,7 @@ module.exports = Operation.extend({ .readInt('version') .readString('content', function (data, fieldName) { if (data.type === 'd') { - data.content = deserializer.deserialize(data.content); + data.content = deserializer.deserialize(data.content, this.data.transformerFunctions); } this.stack.pop(); this.readPayload(records, ender); diff --git a/lib/transport/binary/protocol26/deserializer.js b/lib/transport/binary/protocol26/deserializer.js index 3d9ab5c..2d00933 100644 --- a/lib/transport/binary/protocol26/deserializer.js +++ b/lib/transport/binary/protocol26/deserializer.js @@ -6,10 +6,11 @@ var RID = require('../../../recordid'), /** * Deserialize the given record and return an object containing the values. * - * @param {String} input The serialized record. - * @return {Object} The deserialized record. + * @param {String} input The serialized record. + * @param {Object} classes The optional map of class names to transformers. + * @return {Object} The deserialized record. */ -function deserialize (input) { +function deserialize (input, classes) { var record = {'@type': 'd'}, chunk, key, value; if (!input) { @@ -28,9 +29,8 @@ function deserialize (input) { key = chunk[0]; input = chunk[1]; } - // read the first value. - chunk = eatValue(input); + chunk = eatValue(input, classes); value = chunk[0]; input = chunk[1]; record[key] = value; @@ -46,7 +46,7 @@ function deserialize (input) { key = chunk[0]; input = chunk[1]; if (input.length) { - chunk = eatValue(input); + chunk = eatValue(input, classes); value = chunk[0]; input = chunk[1]; record[key] = value; @@ -56,7 +56,12 @@ function deserialize (input) { } } - return record; + if (classes && record['@class'] && classes[record['@class']]) { + return classes[record['@class']](record); + } + else { + return record; + } } /** @@ -89,7 +94,6 @@ function eatFirstKey (input) { collected += c; } } - return [collected, input.slice(i + 1), isClassName]; } @@ -128,10 +132,11 @@ function eatKey (input) { /** * Consume a field value. * - * @param {String} input The input to parse. - * @return {[Mixed, String]} The collected value, and any remaining input. + * @param {String} input The input to parse. + * @param {Object} classes The optional map of class names to transformers. + * @return {[Mixed, String]} The collected value, and any remaining input. */ -function eatValue (input) { +function eatValue (input, classes) { var c, n; c = input.charAt(0); while (c === ' ' && input.length) { @@ -150,16 +155,16 @@ function eatValue (input) { return eatRID(input.slice(1)); } else if (c === '[') { - return eatArray(input.slice(1)); + return eatArray(input.slice(1), classes); } else if (c === '<') { - return eatSet(input.slice(1)); + return eatSet(input.slice(1), classes); } else if (c === '{') { - return eatMap(input.slice(1)); + return eatMap(input.slice(1), classes); } else if (c === '(') { - return eatRecord(input.slice(1)); + return eatRecord(input.slice(1), classes); } else if (c === '%') { return eatBag(input.slice(1)); @@ -283,9 +288,10 @@ function eatRID (input) { * Consume an array. * * @param {String} input The input to parse. + * @param {Object} classes The optional map of class names to transformers. * @return {[Array, String]} The collected array, and any remaining input. */ -function eatArray (input) { +function eatArray (input, classes) { var length = input.length, array = [], chunk, c; @@ -299,7 +305,7 @@ function eatArray (input) { input = input.slice(1); break; } - chunk = eatValue(input); + chunk = eatValue(input, classes); array.push(chunk[0]); input = chunk[1]; } @@ -311,9 +317,10 @@ function eatArray (input) { * Consume a set. * * @param {String} input The input to parse. + * @param {Object} classes The optional map of class names to transformers. * @return {[Array, String]} The collected set, and any remaining input. */ -function eatSet (input) { +function eatSet (input, classes) { var length = input.length, set = [], chunk, c; @@ -327,7 +334,7 @@ function eatSet (input) { input = input.slice(1); break; } - chunk = eatValue(input); + chunk = eatValue(input, classes); set.push(chunk[0]); input = chunk[1]; } @@ -339,9 +346,10 @@ function eatSet (input) { * Consume a map (object). * * @param {String} input The input to parse. + * @param {Object} classes The optional map of class names to transformers. * @return {[Object, String]} The collected map, and any remaining input. */ -function eatMap (input) { +function eatMap (input, classes) { var length = input.length, map = {}, key, value, chunk, c; @@ -364,7 +372,7 @@ function eatMap (input) { key = chunk[0]; input = chunk[1]; if (input.length) { - chunk = eatValue(input); + chunk = eatValue(input, classes); value = chunk[0]; input = chunk[1]; map[key] = value; @@ -381,9 +389,10 @@ function eatMap (input) { * Consume an embedded record. * * @param {String} input The input to parse. + * @param {Object} classes The optional map of class names to transformers. * @return {[Object, String]} The collected record, and any remaining input. */ -function eatRecord (input) { +function eatRecord (input, classes) { var record = {'@type': 'd'}, chunk, c, key, value; @@ -434,7 +443,7 @@ function eatRecord (input) { } // read the first value. - chunk = eatValue(input); + chunk = eatValue(input, classes); value = chunk[0]; input = chunk[1]; record[key] = value; @@ -456,7 +465,7 @@ function eatRecord (input) { key = chunk[0]; input = chunk[1]; if (input.length) { - chunk = eatValue(input); + chunk = eatValue(input, classes); value = chunk[0]; input = chunk[1]; record[key] = value; @@ -466,6 +475,10 @@ function eatRecord (input) { } } + if (classes && record['@class'] && classes[record['@class']]) { + record = classes[record['@class']](record); + } + return [record, input]; } diff --git a/lib/transport/binary/protocol26/operation.js b/lib/transport/binary/protocol26/operation.js index b55eb1c..33a74e5 100644 --- a/lib/transport/binary/protocol26/operation.js +++ b/lib/transport/binary/protocol26/operation.js @@ -303,7 +303,7 @@ Operation.prototype.readError = function (fieldName, reader) { */ Operation.prototype.readObject = function (fieldName, reader) { this.readOps.push(['String', [fieldName, function (data, fieldName) { - data[fieldName] = deserializer.deserialize(data[fieldName]); + data[fieldName] = deserializer.deserialize(data[fieldName], this.data.transformerFunctions); if (reader) { reader.call(this, data, fieldName); } @@ -691,7 +691,7 @@ Operation.prototype.parseRecord = function (buffer, offset, context, fieldName, .readLong('position') .readInt('version') .readString('value', function (data, key) { - data[key] = deserializer.deserialize(data[key]); + data[key] = deserializer.deserialize(data[key], this.data.transformerFunctions); this.stack.pop(); this.readOps.push(function () { if (reader) { @@ -862,7 +862,7 @@ Operation.prototype.parsePushedData = function (buffer, offset, context, fieldNa asString = buffer.toString('utf8', offset, offset + length); switch (asString.charAt(0)) { case 'R': - context[fieldName] = deserializer.deserialize(asString.slice(1)); + context[fieldName] = deserializer.deserialize(asString.slice(1), this.data.transformerFunctions); break; default: console.log('unsupported pushed data format: ' + asString); diff --git a/lib/transport/binary/protocol26/operations/record-load.js b/lib/transport/binary/protocol26/operations/record-load.js index 93123d9..6c2134f 100644 --- a/lib/transport/binary/protocol26/operations/record-load.js +++ b/lib/transport/binary/protocol26/operations/record-load.js @@ -78,7 +78,7 @@ module.exports = Operation.extend({ data.cluster = this.data.cluster; data.position = this.data.position; if (data[fieldName] === 'd') { - data.content = deserializer.deserialize(data.content); + data.content = deserializer.deserialize(data.content, this.data.transformerFunctions); } this.stack.pop(); this.readPayload(records, ender); @@ -104,7 +104,7 @@ module.exports = Operation.extend({ .readInt('version') .readString('content', function (data, fieldName) { if (data.type === 'd') { - data.content = deserializer.deserialize(data.content); + data.content = deserializer.deserialize(data.content, this.data.transformerFunctions); } this.stack.pop(); this.readPayload(records, ender); diff --git a/lib/transport/binary/protocol28/deserializer.js b/lib/transport/binary/protocol28/deserializer.js index b568c34..2d00933 100644 --- a/lib/transport/binary/protocol28/deserializer.js +++ b/lib/transport/binary/protocol28/deserializer.js @@ -6,10 +6,11 @@ var RID = require('../../../recordid'), /** * Deserialize the given record and return an object containing the values. * - * @param {String} input The serialized record. - * @return {Object} The deserialized record. + * @param {String} input The serialized record. + * @param {Object} classes The optional map of class names to transformers. + * @return {Object} The deserialized record. */ -function deserialize (input) { +function deserialize (input, classes) { var record = {'@type': 'd'}, chunk, key, value; if (!input) { @@ -29,7 +30,7 @@ function deserialize (input) { input = chunk[1]; } // read the first value. - chunk = eatValue(input); + chunk = eatValue(input, classes); value = chunk[0]; input = chunk[1]; record[key] = value; @@ -45,7 +46,7 @@ function deserialize (input) { key = chunk[0]; input = chunk[1]; if (input.length) { - chunk = eatValue(input); + chunk = eatValue(input, classes); value = chunk[0]; input = chunk[1]; record[key] = value; @@ -55,7 +56,12 @@ function deserialize (input) { } } - return record; + if (classes && record['@class'] && classes[record['@class']]) { + return classes[record['@class']](record); + } + else { + return record; + } } /** @@ -126,10 +132,11 @@ function eatKey (input) { /** * Consume a field value. * - * @param {String} input The input to parse. - * @return {[Mixed, String]} The collected value, and any remaining input. + * @param {String} input The input to parse. + * @param {Object} classes The optional map of class names to transformers. + * @return {[Mixed, String]} The collected value, and any remaining input. */ -function eatValue (input) { +function eatValue (input, classes) { var c, n; c = input.charAt(0); while (c === ' ' && input.length) { @@ -148,16 +155,16 @@ function eatValue (input) { return eatRID(input.slice(1)); } else if (c === '[') { - return eatArray(input.slice(1)); + return eatArray(input.slice(1), classes); } else if (c === '<') { - return eatSet(input.slice(1)); + return eatSet(input.slice(1), classes); } else if (c === '{') { - return eatMap(input.slice(1)); + return eatMap(input.slice(1), classes); } else if (c === '(') { - return eatRecord(input.slice(1)); + return eatRecord(input.slice(1), classes); } else if (c === '%') { return eatBag(input.slice(1)); @@ -281,9 +288,10 @@ function eatRID (input) { * Consume an array. * * @param {String} input The input to parse. + * @param {Object} classes The optional map of class names to transformers. * @return {[Array, String]} The collected array, and any remaining input. */ -function eatArray (input) { +function eatArray (input, classes) { var length = input.length, array = [], chunk, c; @@ -297,7 +305,7 @@ function eatArray (input) { input = input.slice(1); break; } - chunk = eatValue(input); + chunk = eatValue(input, classes); array.push(chunk[0]); input = chunk[1]; } @@ -309,9 +317,10 @@ function eatArray (input) { * Consume a set. * * @param {String} input The input to parse. + * @param {Object} classes The optional map of class names to transformers. * @return {[Array, String]} The collected set, and any remaining input. */ -function eatSet (input) { +function eatSet (input, classes) { var length = input.length, set = [], chunk, c; @@ -325,7 +334,7 @@ function eatSet (input) { input = input.slice(1); break; } - chunk = eatValue(input); + chunk = eatValue(input, classes); set.push(chunk[0]); input = chunk[1]; } @@ -337,9 +346,10 @@ function eatSet (input) { * Consume a map (object). * * @param {String} input The input to parse. + * @param {Object} classes The optional map of class names to transformers. * @return {[Object, String]} The collected map, and any remaining input. */ -function eatMap (input) { +function eatMap (input, classes) { var length = input.length, map = {}, key, value, chunk, c; @@ -362,7 +372,7 @@ function eatMap (input) { key = chunk[0]; input = chunk[1]; if (input.length) { - chunk = eatValue(input); + chunk = eatValue(input, classes); value = chunk[0]; input = chunk[1]; map[key] = value; @@ -379,9 +389,10 @@ function eatMap (input) { * Consume an embedded record. * * @param {String} input The input to parse. + * @param {Object} classes The optional map of class names to transformers. * @return {[Object, String]} The collected record, and any remaining input. */ -function eatRecord (input) { +function eatRecord (input, classes) { var record = {'@type': 'd'}, chunk, c, key, value; @@ -432,7 +443,7 @@ function eatRecord (input) { } // read the first value. - chunk = eatValue(input); + chunk = eatValue(input, classes); value = chunk[0]; input = chunk[1]; record[key] = value; @@ -454,7 +465,7 @@ function eatRecord (input) { key = chunk[0]; input = chunk[1]; if (input.length) { - chunk = eatValue(input); + chunk = eatValue(input, classes); value = chunk[0]; input = chunk[1]; record[key] = value; @@ -464,6 +475,10 @@ function eatRecord (input) { } } + if (classes && record['@class'] && classes[record['@class']]) { + record = classes[record['@class']](record); + } + return [record, input]; } diff --git a/lib/transport/binary/protocol28/operation.js b/lib/transport/binary/protocol28/operation.js index 3c9027f..76209b3 100644 --- a/lib/transport/binary/protocol28/operation.js +++ b/lib/transport/binary/protocol28/operation.js @@ -328,7 +328,7 @@ Operation.prototype.readError = function (fieldName, reader) { */ Operation.prototype.readObject = function (fieldName, reader) { this.readOps.push(['String', [fieldName, function (data, fieldName) { - data[fieldName] = deserializer.deserialize(data[fieldName]); + data[fieldName] = deserializer.deserialize(data[fieldName], this.data.transformerFunctions); if (reader) { reader.call(this, data, fieldName); } @@ -716,7 +716,7 @@ Operation.prototype.parseRecord = function (buffer, offset, context, fieldName, .readLong('position') .readInt('version') .readString('value', function (data, key) { - data[key] = deserializer.deserialize(data[key]); + data[key] = deserializer.deserialize(data[key], this.data.transformerFunctions); this.stack.pop(); this.readOps.push(function () { if (reader) { @@ -887,7 +887,7 @@ Operation.prototype.parsePushedData = function (buffer, offset, context, fieldNa asString = buffer.toString('utf8', offset, offset + length); switch (asString.charAt(0)) { case 'R': - context[fieldName] = deserializer.deserialize(asString.slice(1)); + context[fieldName] = deserializer.deserialize(asString.slice(1), this.data.transformerFunctions); break; default: console.log('unsupported pushed data format: ' + asString); diff --git a/lib/transport/binary/protocol28/operations/record-load.js b/lib/transport/binary/protocol28/operations/record-load.js index 577c588..6a356fc 100644 --- a/lib/transport/binary/protocol28/operations/record-load.js +++ b/lib/transport/binary/protocol28/operations/record-load.js @@ -77,7 +77,7 @@ module.exports = Operation.extend({ data.cluster = this.data.cluster; data.position = this.data.position; if (data.type === 'd') { - data.content = deserializer.deserialize(data.content); + data.content = deserializer.deserialize(data.content, this.data.transformerFunctions); } this.stack.pop(); this.readPayload(records, ender); @@ -103,7 +103,7 @@ module.exports = Operation.extend({ .readInt('version') .readString('content', function (data, fieldName) { if (data.type === 'd') { - data.content = deserializer.deserialize(data.content); + data.content = deserializer.deserialize(data.content, this.data.transformerFunctions); } this.stack.pop(); this.readPayload(records, ender); diff --git a/package.json b/package.json index 57a1321..e2f6ecd 100755 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ }, "dependencies": { "bluebird": "~2.9.2", + "fast.js": "^0.1.1", "request": "~2.34.0", "yargs": "~1.2.1" }, From 18ba122a98f5161b82f1a17e0c0a89955cf8e15d Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 3 Feb 2015 01:40:21 +0000 Subject: [PATCH 241/308] fix failing test in OrientDB 1.7.X --- test/bugs/224-rest-vs-binary-protocol.js | 19 ++++++++++++++++--- test/core/jwt.js | 3 +++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/test/bugs/224-rest-vs-binary-protocol.js b/test/bugs/224-rest-vs-binary-protocol.js index 9e7ffb8..d3d7425 100644 --- a/test/bugs/224-rest-vs-binary-protocol.js +++ b/test/bugs/224-rest-vs-binary-protocol.js @@ -1,7 +1,19 @@ var Bluebird = require('bluebird'); describe("Bug #224: REST vs BINARY protocol", function () { - var rid; + var rid, + hasProtocolSupport = false; + + function ifSupportedIt (text, fn) { + it(text, function () { + if (hasProtocolSupport) { + return fn.call(this); + } + else { + console.log(' skipping, "'+text+'": operation not supported by OrientDB version'); + } + }); + } before(function () { var self = this; return CREATE_TEST_DB(this, 'testdb_bug_224') @@ -35,10 +47,11 @@ describe("Bug #224: REST vs BINARY protocol", function () { }) .then(function (result) { rid = result; + hasProtocolSupport = self.db.server.transport.connection.protocolVersion >= 28; }); }); after(function () { - //return DELETE_TEST_DB('testdb_bug_224'); + return DELETE_TEST_DB('testdb_bug_224'); }); it('should retrieve some records', function () { @@ -51,7 +64,7 @@ describe("Bug #224: REST vs BINARY protocol", function () { }); }); - it('should work correctly', function () { + ifSupportedIt('should work correctly', function () { var query = this.db .select('name') .select('$DescendingPosts AS Posts') diff --git a/test/core/jwt.js b/test/core/jwt.js index 3a3cb71..1a3683b 100644 --- a/test/core/jwt.js +++ b/test/core/jwt.js @@ -7,6 +7,9 @@ describe('JWT', function () { if (hasProtocolSupport) { return fn.call(this); } + else { + console.log(' skipping, "'+text+'": operation not supported by OrientDB version'); + } }); } before(function () { From 8b60e45d9235916fab506c6287bdb88569a91c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Marques?= Date: Tue, 3 Feb 2015 09:53:11 -0200 Subject: [PATCH 242/308] Added 'Query Builder: Put a map entry into a map' to docs --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 29541fd..bf422f5 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,22 @@ db ``` +### Query Builder: Put a map entry into a map + +```js +db +.update('#1:1') +.put('mapProperty', { + key: 'value', + foo: 'bar' +}) +.scalar() +.then(function (total) { + console.log('updated', total, 'records'); +}); +``` + + ### Loading a record by RID. ```js From 3c0e17cab251df6245149f6d583e1957678bc933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Marques?= Date: Tue, 3 Feb 2015 11:00:59 -0200 Subject: [PATCH 243/308] use double quotes and wrap key with utils.escape() --- lib/db/statement.js | 3 ++- test/db/statement-test.js | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index 3fcdf9f..9fafa7c 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -588,9 +588,10 @@ Statement.prototype.buildStatement = function () { var keys = Object.keys(objectToAdd); var propertyName = item[0]; return keys.map(function (key) { + key = utils.escape(key); var paramName = 'param' + paramify(propertyName + key) + (this._state.paramIndex++); this.addParam(paramName, objectToAdd[key]); - return propertyName + ' = \'' + key + '\', :' + paramName; + return propertyName + ' = "' + key + '", :' + paramName; }, this).join(', '); }, this).join(', ')); } diff --git a/test/db/statement-test.js b/test/db/statement-test.js index a93f7c5..a34fe86 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -408,10 +408,10 @@ COMMIT \n\ name: 'mario' }); this.statement.buildStatement().should.equal('UPDATE #1:1 PUT ' + - 'fooMap = \'foo\', :paramfooMapfoo0, ' + - 'fooMap = \'greeting\', :paramfooMapgreeting1, ' + - 'barMap = \'bar\', :parambarMapbar2, ' + - 'barMap = \'name\', :parambarMapname3'); + 'fooMap = "foo", :paramfooMapfoo0, ' + + 'fooMap = "greeting", :paramfooMapgreeting1, ' + + 'barMap = "bar", :parambarMapbar2, ' + + 'barMap = "name", :parambarMapname3'); }); }); }); \ No newline at end of file From 0b462b7aff120b5f86aab6b4763811d98ffcc2c4 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 3 Feb 2015 20:44:07 +0000 Subject: [PATCH 244/308] add while keyword to query builder --- lib/db/statement.js | 18 +++++++++++++++++- test/db/statement-test.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index f6ba789..ed985f8 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -125,6 +125,17 @@ Statement.prototype.upsert = function (condition, params, comparisonOperator) { */ Statement.prototype.where = whereClause('and'); +/** + * Specify the while clause for the statement. + * + * > Note: This is actually just an alias of `WHERE`. + * + * @param {String|String[]} args The while clause + * @return {Statement} The statement object. + */ +Statement.prototype.while = Statement.prototype.where; + + /** * Specifiy a where clause, using the `CONTAINSTEXT` comparison operator. * @@ -577,7 +588,12 @@ Statement.prototype.buildStatement = function () { } if (state.where) { - statement.push('WHERE'); + if (state.traverse && state.traverse.length) { + statement.push('WHILE'); + } + else { + statement.push('WHERE'); + } statement.push(state.where.reduce(function (accumulator, item) { var op = item[0], condition = item[1], diff --git a/test/db/statement-test.js b/test/db/statement-test.js index de9ad98..c73ef65 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -163,6 +163,35 @@ COMMIT \n\ }); }); + describe('Statement::traverse()', function () { + it('should traverse all the edges by default', function () { + this.statement.traverse(); + this.statement.buildStatement().should.equal('TRAVERSE *'); + }); + + it('should traverse a single edge type', function () { + this.statement.traverse('out("Thing")'); + this.statement.buildStatement().should.equal('TRAVERSE out("Thing")'); + }); + + it('should traverse multiple edge types', function () { + this.statement.traverse('in("Thing")', 'out("Thing")'); + this.statement.buildStatement().should.equal('TRAVERSE in("Thing"), out("Thing")'); + }); + }); + + describe('Statement::while()', function () { + it('should add a while clause to traverses', function () { + this.statement.traverse().from('OUser').while('$depth < 1'); + this.statement.buildStatement().should.equal("TRAVERSE * FROM OUser WHILE $depth < 1"); + }); + + it('should add multiple while clauses to traverses', function () { + this.statement.traverse().from('OUser').while('$depth < 1').and('1=1'); + this.statement.buildStatement().should.equal("TRAVERSE * FROM OUser WHILE $depth < 1 AND 1=1"); + }); + }); + describe('Statement::insert()', function () { it('should insert a record', function () { this.statement.insert().into('OUser').set({foo: 'bar', greeting: 'hello world'}); From c90546d1956ba46d217cbd7db07423b74d55b105 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 3 Feb 2015 20:44:24 +0000 Subject: [PATCH 245/308] disable test #224 due to bug in OrientDB --- test/bugs/224-rest-vs-binary-protocol.js | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/test/bugs/224-rest-vs-binary-protocol.js b/test/bugs/224-rest-vs-binary-protocol.js index d3d7425..8001b05 100644 --- a/test/bugs/224-rest-vs-binary-protocol.js +++ b/test/bugs/224-rest-vs-binary-protocol.js @@ -1,19 +1,7 @@ var Bluebird = require('bluebird'); describe("Bug #224: REST vs BINARY protocol", function () { - var rid, - hasProtocolSupport = false; - - function ifSupportedIt (text, fn) { - it(text, function () { - if (hasProtocolSupport) { - return fn.call(this); - } - else { - console.log(' skipping, "'+text+'": operation not supported by OrientDB version'); - } - }); - } + var rid; before(function () { var self = this; return CREATE_TEST_DB(this, 'testdb_bug_224') @@ -47,7 +35,6 @@ describe("Bug #224: REST vs BINARY protocol", function () { }) .then(function (result) { rid = result; - hasProtocolSupport = self.db.server.transport.connection.protocolVersion >= 28; }); }); after(function () { @@ -64,7 +51,8 @@ describe("Bug #224: REST vs BINARY protocol", function () { }); }); - ifSupportedIt('should work correctly', function () { + // skipping due to bug in versions of OrientDB <= 2.1-SNAPSHOT + it.skip('should work correctly', function () { var query = this.db .select('name') .select('$DescendingPosts AS Posts') From 781c51df629b91ed4cffa8260e0d69c6a118d881 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 3 Feb 2015 21:12:10 +0000 Subject: [PATCH 246/308] closes #231 unable to replicate --- test/bugs/231-field-filtering.js | 84 ++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 test/bugs/231-field-filtering.js diff --git a/test/bugs/231-field-filtering.js b/test/bugs/231-field-filtering.js new file mode 100644 index 0000000..a3684bc --- /dev/null +++ b/test/bugs/231-field-filtering.js @@ -0,0 +1,84 @@ +var Bluebird = require('bluebird'); +var Statement = require('../../lib/db/statement'); + +describe("Bug #231: Field filtering does not handle substitutions", function () { + var rid; + before(function () { + var self = this; + return CREATE_TEST_DB(this, 'testdb_bug_231') + .then(function () { + return Bluebird.map( + [ + 'CREATE CLASS Person EXTENDS V', + 'CREATE CLASS Car EXTENDS V', + 'CREATE CLASS Drives EXTENDS E', + + "CREATE VERTEX Person SET name='Fred'", + + "CREATE VERTEX Car SET make='Volvo', vin = '1234'", + "CREATE VERTEX Car SET make='Tesla', vin = '5678'", + + "CREATE EDGE Drives FROM (SELECT FROM Person) TO (SELECT FROM Car)" + ], + function (text) { + return self.db.query(text); + } + ); + }) + .then(function () { + return self.db.select('@rid').from('Person').scalar(); + }) + .then(function (result) { + rid = result; + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_231'); + }); + + it('should substitute parameters in filters', function () { + var s = new Statement(); + s.select('out(:edge)[val=:value]').from('Person').addParams({edge: "E", value: 123}).toString().should.equal( + 'SELECT out("E")[val=123] FROM Person' + ); + }); + + + it('should select using a normal query', function () { + return this.db.query("SELECT expand(out(\"Drives\")[vin=\"1234\"]) FROM Person WHERE name = \"Fred\"") + .spread(function (data) { + data.vin.should.equal('1234'); + }); + }); + + it('should select using a prepared query', function () { + return this.db.query("select expand( out( :d )[vin=:v] ) from Person WHERE name = :name", { + params: { + d: 'Drives', + v: '1234', + name: "Fred" + } + }) + .spread(function (data) { + data.vin.should.equal('1234'); + }); + }); + + + it('should select using the query builder', function () { + var query = this.db + .select("expand(out(:d)[vin=:v])") + .from('Person') + .where({name: 'Fred'}) + .addParams({ + d: 'Drives', + v: '1234' + }); + + return query + .one() + .then(function (data) { + data.vin.should.equal('1234'); + }); + }); +}); \ No newline at end of file From c8fd93b54c977128548fd12a0e862eaeabf74370 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 3 Feb 2015 21:22:34 +0000 Subject: [PATCH 247/308] disable test for #231 on OrientDB 1.7.X --- test/bugs/231-field-filtering.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/test/bugs/231-field-filtering.js b/test/bugs/231-field-filtering.js index a3684bc..5924caa 100644 --- a/test/bugs/231-field-filtering.js +++ b/test/bugs/231-field-filtering.js @@ -2,7 +2,19 @@ var Bluebird = require('bluebird'); var Statement = require('../../lib/db/statement'); describe("Bug #231: Field filtering does not handle substitutions", function () { - var rid; + var rid, hasProtocolSupport = false; + + function ifSupportedIt (text, fn) { + it(text, function () { + if (hasProtocolSupport) { + return fn.call(this); + } + else { + console.log(' skipping, "'+text+'": operation not supported by OrientDB version'); + } + }); + } + before(function () { var self = this; return CREATE_TEST_DB(this, 'testdb_bug_231') @@ -30,10 +42,12 @@ describe("Bug #231: Field filtering does not handle substitutions", function () }) .then(function (result) { rid = result; + hasProtocolSupport = self.db.server.transport.connection.protocolVersion >= 28; }); }); + after(function () { - return DELETE_TEST_DB('testdb_bug_231'); + // return DELETE_TEST_DB('testdb_bug_231'); }); it('should substitute parameters in filters', function () { @@ -44,14 +58,14 @@ describe("Bug #231: Field filtering does not handle substitutions", function () }); - it('should select using a normal query', function () { + ifSupportedIt('should select using a normal query', function () { return this.db.query("SELECT expand(out(\"Drives\")[vin=\"1234\"]) FROM Person WHERE name = \"Fred\"") .spread(function (data) { data.vin.should.equal('1234'); }); }); - it('should select using a prepared query', function () { + ifSupportedIt('should select using a prepared query', function () { return this.db.query("select expand( out( :d )[vin=:v] ) from Person WHERE name = :name", { params: { d: 'Drives', @@ -65,7 +79,7 @@ describe("Bug #231: Field filtering does not handle substitutions", function () }); - it('should select using the query builder', function () { + ifSupportedIt('should select using the query builder', function () { var query = this.db .select("expand(out(:d)[vin=:v])") .from('Person') From c68344a0f204255a61fefacf77009f014244e545 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 3 Feb 2015 21:28:26 +0000 Subject: [PATCH 248/308] drop db after test --- test/bugs/231-field-filtering.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bugs/231-field-filtering.js b/test/bugs/231-field-filtering.js index 5924caa..42c2b01 100644 --- a/test/bugs/231-field-filtering.js +++ b/test/bugs/231-field-filtering.js @@ -47,7 +47,7 @@ describe("Bug #231: Field filtering does not handle substitutions", function () }); after(function () { - // return DELETE_TEST_DB('testdb_bug_231'); + return DELETE_TEST_DB('testdb_bug_231'); }); it('should substitute parameters in filters', function () { From 565ec8e055d6cd763d46ad25c9e376c2c2e17e2e Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 3 Feb 2015 21:49:19 +0000 Subject: [PATCH 249/308] add .skip() to query builder, fix #234 --- lib/db/statement.js | 18 ++++++++++++++---- test/db/statement-test.js | 17 +++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index 992edef..5de3bb4 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -195,11 +195,21 @@ Statement.prototype.order = clause('order'); * @param {Integer} value The offset. * @return {Statement} The statement object. */ -Statement.prototype.offset = function (value) { - this._state.offset = +value; +Statement.prototype.skip = function (value) { + this._state.skip = +value; return this; }; +/** + * Set the offset to start returning results from. + * + * > Note: This is just an alias of `.skip()`. + * + * @param {Integer} value The offset. + * @return {Statement} The statement object. + */ +Statement.prototype.offset = Statement.prototype.skip; + /** * Set the maximum number of results to return. * @@ -707,8 +717,8 @@ Statement.prototype.buildStatement = function () { if (state.limit) { statement.push('LIMIT ' + (+state.limit)); } - if (state.offset) { - statement.push('OFFSET ' + (+state.offset)); + if (state.skip) { + statement.push('SKIP ' + (+state.skip)); } if (state.lock) { diff --git a/test/db/statement-test.js b/test/db/statement-test.js index d663dc3..460a3f1 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -299,6 +299,23 @@ COMMIT \n\ }); }); + describe('Statement::skip() and Statement::limit()', function () { + it('should build a statement with a skip clause', function () { + this.statement.select().from('OUser').skip(2); + this.statement.buildStatement().should.equal('SELECT * FROM OUser SKIP 2'); + }); + + it('should build a statement with a limit clause', function () { + this.statement.select().from('OUser').limit(2); + this.statement.buildStatement().should.equal('SELECT * FROM OUser LIMIT 2'); + }); + + it('should build a statement with skip and limit clauses', function () { + this.statement.select().from('OUser').skip(1).limit(2); + this.statement.buildStatement().should.equal('SELECT * FROM OUser LIMIT 2 SKIP 1'); + }); + }); + describe('Statement::where(), Statement::and(), Statement::or()', function () { it('should build a where clause with an expression', function () { this.statement.select().from('OUser').where('1=1'); From 8ac39ebf631188661b417ddfe98a237483906c7d Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 3 Feb 2015 22:01:18 +0000 Subject: [PATCH 250/308] allow object params to .return() in query builder, fixes #221 --- lib/db/statement.js | 34 ++++++++++++++++++++++++++++++++-- test/db/statement-test.js | 8 ++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index 5de3bb4..d710c31 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -622,7 +622,16 @@ Statement.prototype.buildStatement = function () { } if ((state.update || state.insert || state.delete) && state.return) { - statement.push('RETURN ' + state.return); + statement.push('RETURN'); + if (Array.isArray(state.return)) { + statement.push('['+state.return.join(',')+']'); + } + else if (typeof state.return === 'object') { + statement.push(encodeReturnObject(state.return)); + } + else { + statement.push(state.return); + } } if (state.where) { @@ -744,7 +753,16 @@ Statement.prototype.buildStatement = function () { } if (!(state.update || state.insert || state.delete) && state.return) { - statement.push('RETURN ' + state.return); + statement.push('RETURN'); + if (Array.isArray(state.return)) { + statement.push('['+state.return.join(',')+']'); + } + else if (typeof state.return === 'object') { + statement.push(encodeReturnObject(state.return)); + } + else { + statement.push(state.return); + } } return statement.join(' '); }; @@ -890,3 +908,15 @@ function whereClause (operator, comparisonOperator) { }; } + +function encodeReturnObject (obj) { + var keys = Object.keys(obj), + length = keys.length, + parts = new Array(length), + key, i; + for (i = 0; i < length; i++) { + key = keys[i]; + parts[i] = utils.encode(key) + ":" + obj[key]; + } + return '{'+parts.join(',')+'}'; +} \ No newline at end of file diff --git a/test/db/statement-test.js b/test/db/statement-test.js index 460a3f1..e3466e5 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -289,6 +289,14 @@ COMMIT \n\ this.statement.update('#1:1').set({foo: 'bar', greeting: 'hello world'}).return('AFTER'); this.statement.buildStatement().should.equal('UPDATE #1:1 SET foo = :paramfoo0, greeting = :paramgreeting1 RETURN AFTER'); }); + it('should build a return clause with object parameters', function () { + this.statement.update('#1:1').set({foo: 'bar', greeting: 'hello world'}).return({rid: '@rid'}); + this.statement.buildStatement().should.equal('UPDATE #1:1 SET foo = :paramfoo0, greeting = :paramgreeting1 RETURN {"rid":@rid}'); + }); + it('should build a return clause with array parameters', function () { + this.statement.update('#1:1').set({foo: 'bar', greeting: 'hello world'}).return(['@rid', '@class']); + this.statement.buildStatement().should.equal('UPDATE #1:1 SET foo = :paramfoo0, greeting = :paramgreeting1 RETURN [@rid,@class]'); + }); it('should build a return clause before the where clause', function () { this.statement.delete().from('OUser').return('BEFORE').where({foo: 'bar', greeting: 'hello world'}); this.statement.buildStatement().should.equal('DELETE FROM OUser RETURN BEFORE WHERE (foo = :paramfoo0 AND greeting = :paramgreeting1)'); From 4f36b99e6327c281440d1a402df174b7569077e8 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 3 Feb 2015 22:01:52 +0000 Subject: [PATCH 251/308] attempt to resolve build failure --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9395283..0029675 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,5 +5,5 @@ before_script: - ./ci/initialize-ci.sh $ORIENTDB_VERSION env: - ORIENTDB_VERSION=1.7.10 - - ORIENTDB_VERSION=2.0.1-SNAPSHOT + - ORIENTDB_VERSION=2.0-SNAPSHOT - ORIENTDB_VERSION=2.1-SNAPSHOT From ed66b23b6e5f813e5b1155d99d04941404bdcb4f Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Tue, 3 Feb 2015 22:16:46 +0000 Subject: [PATCH 252/308] prepare 1.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e2f6ecd..2b0960d 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node", "node.js" ], - "version": "1.0.1", + "version": "1.1.0", "author": { "name": "Charles Pick", "email": "charles@codemix.com" From ecc95488b71081780b1a485358c0958944b76c26 Mon Sep 17 00:00:00 2001 From: orangemug Date: Wed, 4 Feb 2015 08:47:25 +0000 Subject: [PATCH 253/308] Added event docs. --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index 29541fd..a1f2a8c 100644 --- a/README.md +++ b/README.md @@ -627,6 +627,40 @@ oriento migrate down oriento migrate down 1 ``` +## Events +You can also bind to the following events + +### `beginQuery` +Given the query + + db.select('name, status').from('OUser').where({"status": "active"}).limit(1).fetch({"role": 1}).one(); + +The following event will be triggered + + db.on("beginQuery", function(obj) { + // => { + // query: 'SELECT name, status FROM OUser WHERE status = :paramstatus0 LIMIT 1', + // mode: 'a', + // fetchPlan: 'role:1', + // limit: -1, + // params: { params: { paramstatus0: 'active' } } + // } + }); + + +### `endQuery` +After a query has been run, you'll get the the following event emitted + + db.on("endQuery", function(obj) { + // => { + // "err": errObj, + // "result": resultObj, + // "perf": { + // "query": timeInMs + // } + // } + }); + # History From 4a5ab5764a391030a6e686ed0248bab460ba46dc Mon Sep 17 00:00:00 2001 From: orangemug Date: Wed, 4 Feb 2015 08:53:33 +0000 Subject: [PATCH 254/308] Added failing testcase for result key in endQuery event object. --- test/db/db-test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/db/db-test.js b/test/db/db-test.js index 15e71ec..4584491 100644 --- a/test/db/db-test.js +++ b/test/db/db-test.js @@ -182,8 +182,10 @@ describe("Database API", function () { .then(function () { emitedObject.should.have.propertyByPath("perf", "query"); emitedObject.should.have.property("err"); + emitedObject.should.have.property("result"); emitedObject.perf.query.should.be.above(0); (isNaN(emitedObject.err)).should.be.true; + emitedObject.result.should.be.ok; }); }); @@ -197,8 +199,10 @@ describe("Database API", function () { .catch(function (err) { emitedObject.should.have.propertyByPath("perf", "query"); emitedObject.should.have.property("err"); + emitedObject.should.have.property("result"); emitedObject.perf.query.should.be.above(0); emitedObject.err.should.be.ok; + (isNaN(emitedObject.result)).should.be.true; }); }); From 942733020b099413e6a673b591b4f9af066cc9ff Mon Sep 17 00:00:00 2001 From: orangemug Date: Wed, 4 Feb 2015 09:00:11 +0000 Subject: [PATCH 255/308] Added result to endQuery event object. --- lib/db/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/db/index.js b/lib/db/index.js index a405bf8..2a36600 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -244,10 +244,11 @@ Db.prototype.exec = function (query, options) { .catch(function(_err) { err = _err; }) - .tap(function() { + .tap(function(res) { e = Date.now(); this.emit("endQuery", { err: err, + result: res, perf: { query: e-s } From cee136d87e98d1c8569885a14b95a9c29a6edea2 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 4 Feb 2015 20:06:41 +0000 Subject: [PATCH 256/308] close #238 - failed to replicate --- test/bugs/238-hang-on-invalid-credentials.js | 62 ++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 test/bugs/238-hang-on-invalid-credentials.js diff --git a/test/bugs/238-hang-on-invalid-credentials.js b/test/bugs/238-hang-on-invalid-credentials.js new file mode 100644 index 0000000..c582ff1 --- /dev/null +++ b/test/bugs/238-hang-on-invalid-credentials.js @@ -0,0 +1,62 @@ +describe("Bug #238: Request hangs when attempting connection with invalid credentials", function () { + var serverValid, serverInvalid; + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_238') + .bind(this) + .then(function () { + serverValid = new LIB.Server({ + host: TEST_SERVER_CONFIG.host, + port: TEST_SERVER_CONFIG.port, + username: TEST_SERVER_CONFIG.username, + password: TEST_SERVER_CONFIG.password, + transport: 'binary' + }); + + serverInvalid = new LIB.Server({ + host: TEST_SERVER_CONFIG.host, + port: TEST_SERVER_CONFIG.port, + username: 'nonononono', + password: 'nopenopenopenopenope', + transport: 'binary' + }); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_238'); + }); + + it('should connect to the database with valid credentials', function () { + return serverValid.use('testdb_bug_238').open() + .then(function (db) { + db.name.should.equal('testdb_bug_238'); + }); + }); + + it('should fail to open a database with invalid server credentials', function () { + var db = serverInvalid.use('testdb_bug_238'); + + return db.open() + .then(function (data) { + throw new Error('should never happen.'); + }) + .catch(LIB.errors.RequestError, function (err) { + err.message.should.match(/password/i); + }); + }); + + it('should fail to open a database with invalid database credentials', function () { + var db = serverValid.use({ + name: 'testdb_bug_238', + username: 'nonononono', + password: 'nopenopenopenopenope' + }); + + return db.open() + .then(function (data) { + throw new Error('should never happen.'); + }) + .catch(LIB.errors.RequestError, function (err) { + err.message.should.match(/password/i); + }); + }); +}); \ No newline at end of file From 380728113c8a6b85bc40412a48b992aa6ca521eb Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 4 Feb 2015 20:13:07 +0000 Subject: [PATCH 257/308] fix #239 --- lib/db/index.js | 2 +- test/core/jwt.js | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index 0b67dcc..b060fa9 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -520,7 +520,7 @@ Db.prototype.createUserContext = function (token) { return db.traverse.apply(db, arguments).token(token); }, insert: function () { - return db.select.apply(db, arguments).token(token); + return db.insert.apply(db, arguments).token(token); }, update: function () { return db.update.apply(db, arguments).token(token); diff --git a/test/core/jwt.js b/test/core/jwt.js index 1a3683b..c2fb64c 100644 --- a/test/core/jwt.js +++ b/test/core/jwt.js @@ -145,20 +145,21 @@ describe('JWT', function () { }); describe('Db::createUserContext()', function () { - var context; + var readerContext, adminContext; before(function () { if (hasProtocolSupport) { - context = db.createUserContext(reader); + readerContext = db.createUserContext(reader); + adminContext = db.createUserContext(admin); } }); ifSupportedIt('should create a user context', function () { - return context.select().from('OUser').all() + return readerContext.select().from('OUser').all() .then(function (users) { users.length.should.be.above(1); }); }); ifSupportedIt('should ensure that the token is used correctly', function () { - return context.create('VERTEX', 'V').set({greeting: 'hello world'}).one() + return readerContext.create('VERTEX', 'V').set({greeting: 'hello world'}).one() .then(function () { throw new Error('No, this should not happen'); }) @@ -166,6 +167,12 @@ describe('JWT', function () { /permission/i.test(err.message).should.be.true; }); }); + ifSupportedIt('should insert a row using the admin context', function () { + return adminContext.insert().into('OUser').set({name: 'foo', password: 'bar', status: 'active'}).one() + .then(function (data) { + data['@class'].should.equal('OUser'); + }); + }); }); }); }); \ No newline at end of file From d34c21096aff87a6dca082f78ce6ae223eb21d3b Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 4 Feb 2015 20:51:06 +0000 Subject: [PATCH 258/308] add .increment(), .add(), .put() to query builder, fixes #215 --- lib/db/statement.js | 91 ++++++++++++++++++++++++++++++++++++++- test/db/statement-test.js | 52 ++++++++++++++++++++++ 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index d710c31..6d85a81 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -98,10 +98,63 @@ Statement.prototype.to = clause('to'); Statement.prototype.set = clause('set'); /** - * A `put` clause for embeddedmap properties. + * An `increment` clause. + * + * @param {String} property The property name to put the key to. + * @param {Object} value The value to increment by, defaults to 1 + * @return {Statement} The statement object. + */ +Statement.prototype.increment = function (property, value) { + this._state.increment = this._state.increment || []; + this._state.increment.push([property, value || 1]); + return this; +}; + + +/** + * An `add` clause for set / list properties. + * + * @param {String} property The property name to put the key to. + * @param {Object} ...values The values to add in the set / list property. + * @return {Statement} The statement object. + */ +Statement.prototype.add = function (property/*, ...values*/) { + var totalArguments = arguments.length, + values = new Array(totalArguments - 1), + a; + for (a = 1; a < totalArguments; a++) { + values[a - 1] = arguments[a]; + } + this._state.add = this._state.add || []; + this._state.add.push([property, values]); + return this; +}; + + +/** + * A `remove` clause for map / set / list properties. + * + * @param {String} property The property name to put the key to. + * @param {Object} ...values The keys / values to remove from the map / set / list property. + * @return {Statement} The statement object. + */ +Statement.prototype.remove = function (property/*, ...values*/) { + var totalArguments = arguments.length, + values = new Array(totalArguments - 1), + a; + for (a = 1; a < totalArguments; a++) { + values[a - 1] = arguments[a]; + } + this._state.remove = this._state.remove || []; + this._state.remove.push([property, values]); + return this; +}; + +/** + * A `put` clause for map properties. * * @param {String} property The property name to put the key to. - * @param {Object} keysValues The keys and values to add in the embeddedmap property. + * @param {Object} keysValues The keys and values to add in the map property. * @return {Statement} The statement object. */ Statement.prototype.put = function (property, keysValues) { @@ -110,6 +163,7 @@ Statement.prototype.put = function (property, keysValues) { return this; }; + /** * Upsert the records to avoid multiple queries. * @@ -602,6 +656,39 @@ Statement.prototype.buildStatement = function () { }, this).filter(function (item) { return item; }).join(', ')); } + if (state.increment && state.increment.length) { + statement.push('INCREMENT'); + statement.push(state.increment.map(function (item) { + return utils.escape(item[0]) + ' = ' + (+item[1]); + }).join(', ')); + } + + if (state.add && state.add.length) { + statement.push('ADD'); + statement.push(state.add.map(function (item) { + var field = item[0], + values = item[1]; + return values.map(function (value) { + var paramName = 'param' + paramify(field) + (this._state.paramIndex++); + this.addParam(paramName, value); + return field + ' = :' + paramName; + }, this).join(', '); + }, this).join(', ')); + } + + if (state.remove && state.remove.length) { + statement.push('REMOVE'); + statement.push(state.remove.map(function (item) { + var field = item[0], + values = item[1]; + return values.map(function (value) { + var paramName = 'param' + paramify(field) + (this._state.paramIndex++); + this.addParam(paramName, value); + return field + ' = :' + paramName; + }, this).join(', '); + }, this).join(', ')); + } + if (state.put && state.put.length) { statement.push('PUT'); statement.push(state.put.map(function (item) { diff --git a/test/db/statement-test.js b/test/db/statement-test.js index e3466e5..1212f3f 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -450,6 +450,58 @@ COMMIT \n\ }); }); + describe('Statement::increment()', function () { + it('should increment a field using the default value', function () { + this.statement.update('#1:1').increment('foo'); + this.statement.toString().should.equal('UPDATE #1:1 INCREMENT foo = 1'); + }); + + it('should increment a field using the specified positive value', function () { + this.statement.update('#1:1').increment('foo', 100); + this.statement.toString().should.equal('UPDATE #1:1 INCREMENT foo = 100'); + }); + + + it('should increment a field using the specified negative value', function () { + this.statement.update('#1:1').increment('foo', -100); + this.statement.toString().should.equal('UPDATE #1:1 INCREMENT foo = -100'); + }); + }); + + describe('Statement::add()', function () { + it('should add a string value to a property', function () { + this.statement.update('#1:1').add('foo', 'bar'); + this.statement.toString().should.equal('UPDATE #1:1 ADD foo = "bar"'); + }); + + it('should add a numerical value to a property', function () { + this.statement.update('#1:1').add('foo', 123); + this.statement.toString().should.equal('UPDATE #1:1 ADD foo = 123'); + }); + + it('should add multiple values to a property', function () { + this.statement.update('#1:1').add('foo', 123, 'bar'); + this.statement.toString().should.equal('UPDATE #1:1 ADD foo = 123, foo = "bar"'); + }); + }); + + describe('Statement::remove()', function () { + it('should remove a string value from a property', function () { + this.statement.update('#1:1').remove('foo', 'bar'); + this.statement.toString().should.equal('UPDATE #1:1 REMOVE foo = "bar"'); + }); + + it('should remove a numerical value from a property', function () { + this.statement.update('#1:1').remove('foo', 123); + this.statement.toString().should.equal('UPDATE #1:1 REMOVE foo = 123'); + }); + + it('should remove multiple values from a property', function () { + this.statement.update('#1:1').remove('foo', 123, 'bar'); + this.statement.toString().should.equal('UPDATE #1:1 REMOVE foo = 123, foo = "bar"'); + }); + }); + describe('Statement::put()', function () { it('should build a put query', function () { this.statement.update('#1:1') From bae40664331ebc3fa0aa4e86c86cdfbc05121358 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 4 Feb 2015 20:51:33 +0000 Subject: [PATCH 259/308] temporarily disable travis test for 2.0-SNAPSHOT due to repeated failures --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0029675..87f3574 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,5 +5,4 @@ before_script: - ./ci/initialize-ci.sh $ORIENTDB_VERSION env: - ORIENTDB_VERSION=1.7.10 - - ORIENTDB_VERSION=2.0-SNAPSHOT - ORIENTDB_VERSION=2.1-SNAPSHOT From f75f996b96ba92283101ef6a42a52f5bfb710cc8 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Wed, 4 Feb 2015 21:18:02 +0000 Subject: [PATCH 260/308] prepare 1.1.1 [ci skip] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2b0960d..2a1ce31 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node", "node.js" ], - "version": "1.1.0", + "version": "1.1.1", "author": { "name": "Charles Pick", "email": "charles@codemix.com" From 8ad5de18686cf3a11ad4412f6f3922e3c1297739 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 5 Feb 2015 00:10:44 +0000 Subject: [PATCH 261/308] further investigate #238 (no luck) --- test/bugs/238-hang-on-invalid-credentials.js | 40 +++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/test/bugs/238-hang-on-invalid-credentials.js b/test/bugs/238-hang-on-invalid-credentials.js index c582ff1..bff64e7 100644 --- a/test/bugs/238-hang-on-invalid-credentials.js +++ b/test/bugs/238-hang-on-invalid-credentials.js @@ -1,5 +1,16 @@ describe("Bug #238: Request hangs when attempting connection with invalid credentials", function () { - var serverValid, serverInvalid; + var hasProtocolSupport = false, + serverValid, serverInvalid; + function ifSupportedIt (text, fn) { + it(text, function () { + if (hasProtocolSupport) { + return fn.call(this); + } + else { + console.log(' skipping, "'+text+'": operation not supported by OrientDB version'); + } + }); + } before(function () { return CREATE_TEST_DB(this, 'testdb_bug_238') .bind(this) @@ -9,7 +20,8 @@ describe("Bug #238: Request hangs when attempting connection with invalid creden port: TEST_SERVER_CONFIG.port, username: TEST_SERVER_CONFIG.username, password: TEST_SERVER_CONFIG.password, - transport: 'binary' + transport: 'binary', + useToken: true }); serverInvalid = new LIB.Server({ @@ -17,22 +29,25 @@ describe("Bug #238: Request hangs when attempting connection with invalid creden port: TEST_SERVER_CONFIG.port, username: 'nonononono', password: 'nopenopenopenopenope', - transport: 'binary' + transport: 'binary', + useToken: true }); + + hasProtocolSupport = this.db.server.transport.connection.protocolVersion >= 28; }); }); after(function () { return DELETE_TEST_DB('testdb_bug_238'); }); - it('should connect to the database with valid credentials', function () { + ifSupportedIt('should connect to the database with valid credentials', function () { return serverValid.use('testdb_bug_238').open() .then(function (db) { db.name.should.equal('testdb_bug_238'); }); }); - it('should fail to open a database with invalid server credentials', function () { + ifSupportedIt('should fail to open a database with invalid server credentials', function () { var db = serverInvalid.use('testdb_bug_238'); return db.open() @@ -44,7 +59,20 @@ describe("Bug #238: Request hangs when attempting connection with invalid creden }); }); - it('should fail to open a database with invalid database credentials', function () { + ifSupportedIt('should open a database with valid database credentials', function () { + var db = serverValid.use({ + name: 'testdb_bug_238', + username: 'reader', + password: 'reader' + }); + + return db.open() + .then(function (db) { + db.token.length.should.be.above(0); + }); + }); + + ifSupportedIt('should fail to open a database with invalid database credentials', function () { var db = serverValid.use({ name: 'testdb_bug_238', username: 'nonononono', From f13dc662b6bf425cb781fcdd8ae7e9a0c26e7d68 Mon Sep 17 00:00:00 2001 From: kgreenpea Date: Thu, 5 Feb 2015 14:50:42 +0000 Subject: [PATCH 262/308] Update 238-hang-on-invalid-credentials.js To demonstrate issue when reusing original server instance. --- test/bugs/238-hang-on-invalid-credentials.js | 238 ++++++++++++------- 1 file changed, 149 insertions(+), 89 deletions(-) diff --git a/test/bugs/238-hang-on-invalid-credentials.js b/test/bugs/238-hang-on-invalid-credentials.js index bff64e7..7e2c5dd 100644 --- a/test/bugs/238-hang-on-invalid-credentials.js +++ b/test/bugs/238-hang-on-invalid-credentials.js @@ -1,90 +1,150 @@ describe("Bug #238: Request hangs when attempting connection with invalid credentials", function () { - var hasProtocolSupport = false, - serverValid, serverInvalid; - function ifSupportedIt (text, fn) { - it(text, function () { - if (hasProtocolSupport) { - return fn.call(this); - } - else { - console.log(' skipping, "'+text+'": operation not supported by OrientDB version'); - } - }); - } - before(function () { - return CREATE_TEST_DB(this, 'testdb_bug_238') - .bind(this) - .then(function () { - serverValid = new LIB.Server({ - host: TEST_SERVER_CONFIG.host, - port: TEST_SERVER_CONFIG.port, - username: TEST_SERVER_CONFIG.username, - password: TEST_SERVER_CONFIG.password, - transport: 'binary', - useToken: true - }); - - serverInvalid = new LIB.Server({ - host: TEST_SERVER_CONFIG.host, - port: TEST_SERVER_CONFIG.port, - username: 'nonononono', - password: 'nopenopenopenopenope', - transport: 'binary', - useToken: true - }); - - hasProtocolSupport = this.db.server.transport.connection.protocolVersion >= 28; - }); - }); - after(function () { - return DELETE_TEST_DB('testdb_bug_238'); - }); - - ifSupportedIt('should connect to the database with valid credentials', function () { - return serverValid.use('testdb_bug_238').open() - .then(function (db) { - db.name.should.equal('testdb_bug_238'); - }); - }); - - ifSupportedIt('should fail to open a database with invalid server credentials', function () { - var db = serverInvalid.use('testdb_bug_238'); - - return db.open() - .then(function (data) { - throw new Error('should never happen.'); - }) - .catch(LIB.errors.RequestError, function (err) { - err.message.should.match(/password/i); - }); - }); - - ifSupportedIt('should open a database with valid database credentials', function () { - var db = serverValid.use({ - name: 'testdb_bug_238', - username: 'reader', - password: 'reader' - }); - - return db.open() - .then(function (db) { - db.token.length.should.be.above(0); - }); - }); - - ifSupportedIt('should fail to open a database with invalid database credentials', function () { - var db = serverValid.use({ - name: 'testdb_bug_238', - username: 'nonononono', - password: 'nopenopenopenopenope' - }); - - return db.open() - .then(function (data) { - throw new Error('should never happen.'); - }) - .catch(LIB.errors.RequestError, function (err) { - err.message.should.match(/password/i); - }); - }); -}); \ No newline at end of file + var hasProtocolSupport = false, + self = this, + serverValid, serverInvalid; + + //////////////////////////////////////////// + function ifSupportedIt(text, fn) { + it(text, function () { + if (hasProtocolSupport) { + return fn.call(this); + } + else { + console.log(' skipping, "' + text + '": operation not supported by OrientDB version'); + } + }); + } + + function createTestDb(server, name, type) { + type = type || 'memory'; + return server.exists(name, type) + .then(function (exists) { + if (exists) { + return server.drop({ + name: name, + storage: type + }); + } + else { + return false; + } + }) + .then(function () { + return server.create({ + name: name, + type: 'graph', + storage: type + }); + }) + .then(function (db) { + self.db = db; + }); + } + + function deleteTestDb(server, name, type) { + type = type || 'memory'; + return server.exists(name, type) + .then(function (exists) { + if (exists) { + return server.drop({ + name: name, + storage: type + }); + } + else { + return undefined; + } + }) + .then(function () { + return undefined; + }); + } + + function newValidServer() { + return new LIB.Server({ + host: TEST_SERVER_CONFIG.host, + port: TEST_SERVER_CONFIG.port, + username: TEST_SERVER_CONFIG.username, + password: TEST_SERVER_CONFIG.password, + transport: 'binary', + useToken: true + }); + } + + //////////////////////////////////////////// + + before(function () { + serverValid = newValidServer(); + + return createTestDb(serverValid, 'testdb_bug_238') + .then(function () { + + // Uncomment following to make test pass + //serverValid = newValidServer(); + + serverInvalid = new LIB.Server({ + host: TEST_SERVER_CONFIG.host, + port: TEST_SERVER_CONFIG.port, + username: 'nonononono', + password: 'nopenopenopenopenope', + transport: 'binary', + useToken: true + }); + + hasProtocolSupport = self.db.server.transport.connection.protocolVersion >= 28; + }); + }); + after(function () { + return deleteTestDb(serverValid, 'testdb_bug_238'); + }); + + ifSupportedIt('should connect to the database with valid credentials', function () { + return serverValid.use('testdb_bug_238').open() + .then(function (db) { + db.name.should.equal('testdb_bug_238'); + db.token.should.be.an.instanceOf(Buffer); + db.token.length.should.be.above(0); + }); + }); + + ifSupportedIt('should fail to open a database with invalid server credentials', function () { + var db = serverInvalid.use('testdb_bug_238'); + + return db.open() + .then(function (data) { + throw new Error('should never happen.'); + }) + .catch(LIB.errors.RequestError, function (err) { + err.message.should.match(/password/i); + }); + }); + + ifSupportedIt('should open a database with valid database credentials', function () { + var db = serverValid.use({ + name: 'testdb_bug_238', + username: 'reader', + password: 'reader' + }); + + return db.open() + .then(function (db) { + db.token.length.should.be.above(0); + }); + }); + + ifSupportedIt('should fail to open a database with invalid database credentials', function () { + var db = serverValid.use({ + name: 'testdb_bug_238', + username: 'nonononono', + password: 'nopenopenopenopenope' + }); + + return db.open() + .then(function (data) { + throw new Error('should never happen.'); + }) + .catch(LIB.errors.RequestError, function (err) { + err.message.should.match(/password/i); + }); + }); +}); From 32b607563e51be32a6fb944cc2d6d8065ecb42fa Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 5 Feb 2015 23:39:26 +0000 Subject: [PATCH 263/308] export Statement and Query classes --- lib/db/index.js | 3 +++ lib/index.js | 2 ++ 2 files changed, 5 insertions(+) diff --git a/lib/db/index.js b/lib/db/index.js index b060fa9..cbb38c9 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -43,6 +43,9 @@ inherits(Db, EventEmitter); Db.prototype.augment = utils.augment; Db.extend = utils.extend; +Db.Statement = Statement; +Db.Query = Query; + module.exports = Db; /** diff --git a/lib/index.js b/lib/index.js index 732b8f7..5f6b31d 100755 --- a/lib/index.js +++ b/lib/index.js @@ -8,6 +8,8 @@ Oriento.RecordID = Oriento.RecordId = Oriento.RID = require('./recordid'); Oriento.RIDBag = Oriento.Bag = require('./bag'); Oriento.Server = require('./server'); Oriento.Db = require('./db'); +Oriento.Statement = Oriento.Db.Statement; +Oriento.Query = Oriento.Db.Query; Oriento.transport = require('./transport'); Oriento.errors = require('./errors'); Oriento.Migration = require('./migration'); From e317ab85e9abfaec5eb06ca8bf00201d4f913e61 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 5 Feb 2015 23:40:02 +0000 Subject: [PATCH 264/308] add @type to embedded documents --- lib/utils.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 6de71fe..e9d7c14 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -212,12 +212,22 @@ exports.encode = function encode (value) { return '[' + value.map(encode) + ']'; } else { + var keys = Object.keys(value), - length = keys.length, - parts = new Array(length), + offset = 0, + length = keys.length; + + if (length && !value['@type']) { + offset = 1; + } + + var parts = new Array(length + offset), key, i; - for (i = 0; i < length; i++) { - key = keys[i]; + if (offset) { + parts[0] = '"@type":"d"'; + } + for (i = offset; i < length + offset; i++) { + key = keys[i - offset]; parts[i] = '"' + exports.escape(key) + '":'+encode(value[key]); } return '{'+parts.join(',')+'}'; From 9b95c39c3fe4cc0e46268b6e05bc6b3063a9fce5 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 5 Feb 2015 23:40:30 +0000 Subject: [PATCH 265/308] allow embedded statements in set clauses --- lib/db/statement.js | 11 ++++++++++- test/db/statement-test.js | 9 +++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index 6d85a81..1394f55 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -946,7 +946,16 @@ Statement.prototype._objectToSet = function (obj) { for (i = 0; i < total; i++) { key = keys[i]; value = obj[key]; - if (value instanceof RID) { + if (typeof value === 'function') { + var child = new Statement(this.db); + child._state.paramIndex = this._state.paramIndex; + value(child); + expressions.push(key + ' = (' + child.toString() + ')'); + } + else if (value instanceof Statement) { + expressions.push(key + ' = (' + value.toString() + ')'); + } + else if (value instanceof RID) { expressions.push(key + ' = ' + value); } else if (value !== undefined) { diff --git a/test/db/statement-test.js b/test/db/statement-test.js index 1212f3f..3b4e09e 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -204,6 +204,15 @@ COMMIT \n\ this.statement.update('#1:1').set({foo: 'bar', greeting: 'hello world'}); this.statement.buildStatement().should.equal('UPDATE #1:1 SET foo = :paramfoo0, greeting = :paramgreeting1'); }); + + it('should update a record with a nested statement', function () { + this.statement.update('#1:1').set({ + foo: function (s) { + s.select().from('OUser'); + } + }); + this.statement.buildStatement().should.equal('UPDATE #1:1 SET foo = (SELECT * FROM OUser)'); + }); }); describe('Statement::delete()', function () { From 56b823242ce43bf119cf3aca0427d437946d4324 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 5 Feb 2015 23:41:11 +0000 Subject: [PATCH 266/308] prepare 1.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2a1ce31..ed0b368 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node", "node.js" ], - "version": "1.1.1", + "version": "1.1.2", "author": { "name": "Charles Pick", "email": "charles@codemix.com" From 75f1b0f321d3ca7f7dc4c117a9d24ef339a140d5 Mon Sep 17 00:00:00 2001 From: orangemug Date: Fri, 6 Feb 2015 08:53:47 +0000 Subject: [PATCH 267/308] 'beginQuery' now returns the full data object, fixes #248. --- lib/db/index.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index 0b67dcc..8f36b08 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -237,13 +237,7 @@ Db.prototype.exec = function (query, options) { this.server.logger.debug('executing query against db ' + this.name + ': ' + query); if(this.listeners('beginQuery').length > 0) { - this.emit("beginQuery", { - query: data.query, - mode: data.mode, - fetchPlan: data.fetchPlan, - limit: data.limit, - params: data.params - }); + this.emit("beginQuery", data); } var promise = this.send('command', data); From e42cd91b75a9944da44e54172704cc9fc1248b73 Mon Sep 17 00:00:00 2001 From: orangemug Date: Fri, 6 Feb 2015 09:41:38 +0000 Subject: [PATCH 268/308] Added new Db#createFn API, fixes #245. --- README.md | 23 +++++++++++++++++++++++ lib/db/index.js | 33 ++++++++++++++++++++++++++++++++- package.json | 1 + test/db/db-test.js | 24 ++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0505524..e480cae 100644 --- a/README.md +++ b/README.md @@ -518,6 +518,29 @@ db.delete('EDGE', 'E') }); ``` +### Creating a function +You can create a function by supplying a plain javascript function. Please note that the method stringifies the `function` passed so you can't use any varaibles outside the function closure. + +```js +db.createFn("nameOfFunction", function(arg1, arg2) { + return arg1 + arg2; +}) +.then(function (count) { + // Function created! +}); +``` + +You can also omit the name and it'll default to the `Function#name` + +```js +db.createFn(function nameOfFunction(arg1, arg2) { + return arg1 + arg2; +}) +.then(function (count) { + // Function created! +}); +``` + # CLI diff --git a/lib/db/index.js b/lib/db/index.js index 0b67dcc..48fe297 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -10,7 +10,8 @@ var utils = require('../utils'), ArrayLike = utils.ArrayLike, inherits = require("util").inherits, EventEmitter = require('events').EventEmitter, - fast = require('fast.js'); + fast = require('fast.js'), + parseFn = require("parse-function"); @@ -535,6 +536,36 @@ Db.prototype.createUserContext = function (token) { }; }; +/** + * Create a orient function from a plain Javascript function + * + * @param {String} name The name of the function + * @param {Object} fn Plain Javascript function to stringify + * @param {Object} options Not currently used but will be used for 'IDEMPOTENT' arg + * @promise {Mixed} The results of the query / command. + */ +Db.prototype.createFn = function (name, fn, options) { + if(typeof(name) === "function") { + options = fn; + fn = name; + name = fn.name; + } + + var fnDef = parseFn(fn); + var params = ""; + var body = fnDef.body + .replace(/\'/g, "\\'") + .replace(/\"/g, '\\"') + .trim(); + + // NOTE: We can't do `PARAMETERS []` because else orientdb throws an error + if(fnDef.arguments.length > 0) { + params = 'PARAMETERS ['+fnDef.params+']'; + } + + return this.query('CREATE FUNCTION '+name+' "'+body+'" '+params+' LANGUAGE Javascript'); +}; + /** * Flatten an array of arrays diff --git a/package.json b/package.json index 2b0960d..774326f 100755 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "dependencies": { "bluebird": "~2.9.2", "fast.js": "^0.1.1", + "parse-function": "^2.0.0", "request": "~2.34.0", "yargs": "~1.2.1" }, diff --git a/test/db/db-test.js b/test/db/db-test.js index 4eca830..7a8f336 100644 --- a/test/db/db-test.js +++ b/test/db/db-test.js @@ -207,5 +207,29 @@ describe("Database API", function () { }); }); + it('should create a runnable function with name arg as function name', function () { + var db = this.db; + + return db.createFn("runme1", function(str) { + return "this "+str+" work"; + }).then(function() { + return db.select('runme1("does") as testresult').from('OUser').limit(1).one(); + }).then(function(res) { + res.testresult.should.be.equal("this does work"); + }); + }); + + it('should create runnable function with function name as name', function () { + var db = this.db; + + return db.createFn(function runme2(str) { + return "this "+str+" work"; + }).then(function() { + return db.select('runme2("does") as testresult').from('OUser').limit(1).one(); + }).then(function(res) { + res.testresult.should.be.equal("this does work"); + }); + }); + }); }); From d065c4ba9f56a1d5ccab4d167eacb153732b94eb Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 6 Feb 2015 20:52:21 +0000 Subject: [PATCH 269/308] fix #238 - hang on invalid credentials when using tokens --- lib/transport/binary/protocol28/operation.js | 5 + test/bugs/238-hang-on-invalid-credentials.js | 294 +++++++++---------- 2 files changed, 151 insertions(+), 148 deletions(-) diff --git a/lib/transport/binary/protocol28/operation.js b/lib/transport/binary/protocol28/operation.js index 76209b3..ee959c1 100644 --- a/lib/transport/binary/protocol28/operation.js +++ b/lib/transport/binary/protocol28/operation.js @@ -836,6 +836,11 @@ Operation.prototype.parseError = function (buffer, offset, context, fieldName, r context[fieldName] = err; this.stack.push(err); + + if (this.opCode === 3 && this.data.token) { + this.readBytes('token'); + } + this.readByte('id'); diff --git a/test/bugs/238-hang-on-invalid-credentials.js b/test/bugs/238-hang-on-invalid-credentials.js index 7e2c5dd..6e04626 100644 --- a/test/bugs/238-hang-on-invalid-credentials.js +++ b/test/bugs/238-hang-on-invalid-credentials.js @@ -1,150 +1,148 @@ describe("Bug #238: Request hangs when attempting connection with invalid credentials", function () { - var hasProtocolSupport = false, - self = this, - serverValid, serverInvalid; - - //////////////////////////////////////////// - function ifSupportedIt(text, fn) { - it(text, function () { - if (hasProtocolSupport) { - return fn.call(this); - } - else { - console.log(' skipping, "' + text + '": operation not supported by OrientDB version'); - } - }); - } - - function createTestDb(server, name, type) { - type = type || 'memory'; - return server.exists(name, type) - .then(function (exists) { - if (exists) { - return server.drop({ - name: name, - storage: type - }); - } - else { - return false; - } - }) - .then(function () { - return server.create({ - name: name, - type: 'graph', - storage: type - }); - }) - .then(function (db) { - self.db = db; - }); - } - - function deleteTestDb(server, name, type) { - type = type || 'memory'; - return server.exists(name, type) - .then(function (exists) { - if (exists) { - return server.drop({ - name: name, - storage: type - }); - } - else { - return undefined; - } - }) - .then(function () { - return undefined; - }); - } - - function newValidServer() { - return new LIB.Server({ - host: TEST_SERVER_CONFIG.host, - port: TEST_SERVER_CONFIG.port, - username: TEST_SERVER_CONFIG.username, - password: TEST_SERVER_CONFIG.password, - transport: 'binary', - useToken: true - }); - } - - //////////////////////////////////////////// - - before(function () { - serverValid = newValidServer(); - - return createTestDb(serverValid, 'testdb_bug_238') - .then(function () { - - // Uncomment following to make test pass - //serverValid = newValidServer(); - - serverInvalid = new LIB.Server({ - host: TEST_SERVER_CONFIG.host, - port: TEST_SERVER_CONFIG.port, - username: 'nonononono', - password: 'nopenopenopenopenope', - transport: 'binary', - useToken: true - }); - - hasProtocolSupport = self.db.server.transport.connection.protocolVersion >= 28; - }); - }); - after(function () { - return deleteTestDb(serverValid, 'testdb_bug_238'); - }); - - ifSupportedIt('should connect to the database with valid credentials', function () { - return serverValid.use('testdb_bug_238').open() - .then(function (db) { - db.name.should.equal('testdb_bug_238'); - db.token.should.be.an.instanceOf(Buffer); - db.token.length.should.be.above(0); - }); - }); - - ifSupportedIt('should fail to open a database with invalid server credentials', function () { - var db = serverInvalid.use('testdb_bug_238'); - - return db.open() - .then(function (data) { - throw new Error('should never happen.'); - }) - .catch(LIB.errors.RequestError, function (err) { - err.message.should.match(/password/i); - }); - }); - - ifSupportedIt('should open a database with valid database credentials', function () { - var db = serverValid.use({ - name: 'testdb_bug_238', - username: 'reader', - password: 'reader' - }); - - return db.open() - .then(function (db) { - db.token.length.should.be.above(0); - }); - }); - - ifSupportedIt('should fail to open a database with invalid database credentials', function () { - var db = serverValid.use({ - name: 'testdb_bug_238', - username: 'nonononono', - password: 'nopenopenopenopenope' - }); - - return db.open() - .then(function (data) { - throw new Error('should never happen.'); - }) - .catch(LIB.errors.RequestError, function (err) { - err.message.should.match(/password/i); - }); - }); + var hasProtocolSupport = false, + self = this, + serverValid, serverInvalid; + + //////////////////////////////////////////// + function ifSupportedIt(text, fn) { + it(text, function () { + if (hasProtocolSupport) { + return fn.call(this); + } + else { + console.log(' skipping, "' + text + '": operation not supported by OrientDB version'); + } + }); + } + + function createTestDb(server, name, type) { + type = type || 'memory'; + return server.exists(name, type) + .then(function (exists) { + if (exists) { + return server.drop({ + name: name, + storage: type + }); + } + else { + return false; + } + }) + .then(function () { + return server.create({ + name: name, + type: 'graph', + storage: type + }); + }) + .then(function (db) { + self.db = db; + }); + } + + function deleteTestDb(server, name, type) { + type = type || 'memory'; + return server.exists(name, type) + .then(function (exists) { + if (exists) { + return server.drop({ + name: name, + storage: type + }); + } + else { + return undefined; + } + }) + .then(function () { + return undefined; + }); + } + + function newValidServer() { + return new LIB.Server({ + host: TEST_SERVER_CONFIG.host, + port: TEST_SERVER_CONFIG.port, + username: TEST_SERVER_CONFIG.username, + password: TEST_SERVER_CONFIG.password, + transport: 'binary', + useToken: true + }); + } + + //////////////////////////////////////////// + + before(function () { + serverValid = newValidServer(); + + return createTestDb(serverValid, 'testdb_bug_238') + .then(function () { + + serverInvalid = new LIB.Server({ + host: TEST_SERVER_CONFIG.host, + port: TEST_SERVER_CONFIG.port, + username: 'nonononono', + password: 'nopenopenopenopenope', + transport: 'binary', + useToken: true + }); + + hasProtocolSupport = self.db.server.transport.connection.protocolVersion >= 28; + }); + }); + after(function () { + return deleteTestDb(serverValid, 'testdb_bug_238'); + }); + + ifSupportedIt('should connect to the database with valid credentials', function () { + return serverValid.use('testdb_bug_238').open() + .then(function (db) { + db.name.should.equal('testdb_bug_238'); + db.token.should.be.an.instanceOf(Buffer); + db.token.length.should.be.above(0); + }); + }); + + ifSupportedIt('should fail to open a database with invalid server credentials', function () { + var db = serverInvalid.use('testdb_bug_238'); + + return db.open() + .then(function (data) { + throw new Error('should never happen.'); + }) + .catch(LIB.errors.RequestError, function (err) { + err.message.should.match(/password/i); + }); + }); + + ifSupportedIt('should open a database with valid database credentials', function () { + var db = serverValid.use({ + name: 'testdb_bug_238', + username: 'reader', + password: 'reader' + }); + + return db.open() + .then(function (db) { + db.token.length.should.be.above(0); + }); + }); + + ifSupportedIt('should fail to open a database with invalid database credentials', function () { + + var db = serverValid.use({ + name: 'testdb_bug_238', + username: 'nonononono', + password: 'nopenopenopenopenope' + }); + + return db.open() + .then(function (data) { + throw new Error('should never happen.'); + }) + .catch(LIB.errors.RequestError, function (err) { + err.message.should.match(/password/i); + }); + }); }); From 1654040c1f2a79c2bb45e6453bc17d03bb0aaaac Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 13 Feb 2015 11:35:03 +0000 Subject: [PATCH 270/308] fix #252 and #255 --- lib/utils.js | 14 ++---- test/bugs/252-save-embedded-map.js | 68 ++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 test/bugs/252-save-embedded-map.js diff --git a/lib/utils.js b/lib/utils.js index e9d7c14..817c422 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -214,20 +214,12 @@ exports.encode = function encode (value) { else { var keys = Object.keys(value), - offset = 0, length = keys.length; - if (length && !value['@type']) { - offset = 1; - } - - var parts = new Array(length + offset), + var parts = new Array(length), key, i; - if (offset) { - parts[0] = '"@type":"d"'; - } - for (i = offset; i < length + offset; i++) { - key = keys[i - offset]; + for (i = 0; i < length; i++) { + key = keys[i]; parts[i] = '"' + exports.escape(key) + '":'+encode(value[key]); } return '{'+parts.join(',')+'}'; diff --git a/test/bugs/252-save-embedded-map.js b/test/bugs/252-save-embedded-map.js new file mode 100644 index 0000000..7ea1134 --- /dev/null +++ b/test/bugs/252-save-embedded-map.js @@ -0,0 +1,68 @@ +describe("Bug #252: Unable to save plain EmbededMap", function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_252') + .bind(this) + .then(function () { + return this.db.class.create('TestEmbeddedMap'); + }) + .then(function (TestEmbeddedMap) { + return TestEmbeddedMap.property.create([ + { + name: 'name', + type: 'string' + }, + { + name: 'map', + type: 'embeddedmap', + linkedType: 'string' + }, + { + name: 'list', + type: 'embeddedlist' + } + ]) + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_252'); + }); + + it('should insert a map into the database', function () { + return this.db + .insert() + .into('TestEmbeddedMap') + .set({ + name: 'abc', + map: { + k1: 'v1', + k2: 'v2' + } + }) + .one() + .then(function (res) { + res.map.k1.should.equal('v1'); + }); + }); + + describe('Bug #255: param is not working with embeddedlist', function () { + it('should allow params in embedded list', function () { + return this.db.query('INSERT INTO TestEmbeddedMap SET name = :name, list = :list', { + params: { + name: 'def', + list: [ + { + controller: 'home' + } + ] + } + }) + .bind(this) + .then(function () { + return this.db.select().from('TestEmbeddedMap').where({name: 'def'}).one(); + }) + .then(function (row) { + row.list[0].controller.should.equal('home'); + }); + }); + }); +}); From 265d4b26c58a2cee2b2c56d60f22878f9ee3b765 Mon Sep 17 00:00:00 2001 From: Richard Ayotte Date: Mon, 16 Feb 2015 00:20:51 -0500 Subject: [PATCH 271/308] Update oriento.opts --- test/fixtures/oriento.opts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fixtures/oriento.opts b/test/fixtures/oriento.opts index 79a3afd..f0b16d7 100644 --- a/test/fixtures/oriento.opts +++ b/test/fixtures/oriento.opts @@ -1,3 +1,3 @@ ---server=localhost +--host=localhost --port=2424 --password=root From 91318c765fd7d60a327827ac082cf6a6fc89eecf Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 16 Feb 2015 20:24:43 +0000 Subject: [PATCH 272/308] ensure db username / password is respected when migrating, also, don't revert all migrations by default, just one --- lib/cli/command.js | 6 +++++- lib/cli/commands/migrate.js | 8 ++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/cli/command.js b/lib/cli/command.js index 59caa9a..d6b4279 100644 --- a/lib/cli/command.js +++ b/lib/cli/command.js @@ -15,7 +15,11 @@ function Command (server, options) { Object.defineProperty(this, 'db', { get: function () { if (!this._db && this.options.dbname) { - this._db = this.server.use(this.options.dbname); + this._db = this.server.use({ + name: this.options.dbname, + username: this.options.dbuser, + password: this.options.dbpassword + }); } return this._db; }, diff --git a/lib/cli/commands/migrate.js b/lib/cli/commands/migrate.js index 22f11a5..01d2292 100644 --- a/lib/cli/commands/migrate.js +++ b/lib/cli/commands/migrate.js @@ -88,12 +88,8 @@ exports.up = function (limit) { * Migrate down. */ exports.down = function (limit) { - if (limit) { - console.log('Reverting a maximum of ' + limit + ' migration(s)...'); - } - else { - console.log('Reverting all available migrations...'); - } + limit = limit || 1; + console.log('Reverting a maximum of ' + limit + ' migration(s)...'); return this.manager().down(limit) .then(function (results) { console.log('Reverted ' + results.length + ' migration(s):'); From c8eed10b89c94158c578a1597283f2dc17010769 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 20 Feb 2015 20:01:51 +0000 Subject: [PATCH 273/308] use a state machine rather than a RegExp for escaping strings --- lib/utils.js | 28 ++++++++++++++++++++++++---- test/core/utils.js | 6 ++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 817c422..0246a38 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -129,10 +129,30 @@ exports.clone = function (item) { * @return {String} The escaped input. */ exports.escape = function (input) { - return ('' + input) - .replace(/\r/g, '\\r') - .replace(/\n/g, '\\n') - .replace(/([^"\\]*(?:\\.[^"\\]*)*)"/g, '$1\\"'); + var text = ''+input; + var chars = new Array(text.length); + for (var i = 0; i < text.length; i++) { + var char = text.charAt(i); + if (char === '\r') { + chars[i] = '\\r'; + } + else if (char === '\n') { + chars[i] = '\\n'; + } + else if (char === '"') { + chars[i] = '\\"'; + } + else if (char === '\\') { + chars[i] = '\\\\'; + } + else if (char === '/' && i === 0) { + chars[i] = '\\/'; + } + else { + chars[i] = char; + } + } + return chars.join(''); }; /** diff --git a/test/core/utils.js b/test/core/utils.js index a86c30f..64ef66f 100644 --- a/test/core/utils.js +++ b/test/core/utils.js @@ -9,6 +9,12 @@ describe('utils.prepare', function () { it("should prepare SQL statements with parameters", function () { utils.prepare("select from index:foo where key = :key", {key: 123}).should.equal("select from index:foo where key = 123"); }); + it('should prepare SQL statements with parameters with tricky values', function () { + var text = utils.prepare("SELECT * FROM OUser WHERE foo = :foo", { + foo: '/// TE /// ST \\\\\\' + }); + text.should.equal('SELECT * FROM OUser WHERE foo = "\\/// TE /// ST \\\\\\\\\\\\"'); + }); it("should prepare SQL statements with date parameters", function () { var date = new Date(Date.UTC(2015, 0, 5, 22, 7, 5)); utils.prepare("select from index:foo where date = :date", {date: date}).should.equal("select from index:foo where date = date(\"2015-01-05 22:07:05\", \"yyyy-MM-dd HH:mm:ss\", \"UTC\")"); From b87b49403e874e9f782d7ffa54602b642458f63b Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 20 Feb 2015 20:40:52 +0000 Subject: [PATCH 274/308] investigate create link on edge bug --- test/bugs/xxx-link-when-create-edge.js | 81 ++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 test/bugs/xxx-link-when-create-edge.js diff --git a/test/bugs/xxx-link-when-create-edge.js b/test/bugs/xxx-link-when-create-edge.js new file mode 100644 index 0000000..490b92d --- /dev/null +++ b/test/bugs/xxx-link-when-create-edge.js @@ -0,0 +1,81 @@ +var Bluebird = require('bluebird'); + +describe("Bug: Should create a link while inserting an edge", function () { + var first, second, third; + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_edge_link') + .bind(this) + .then(function () { + return Bluebird.all([ + this.db.class.create('Thing', 'V'), + this.db.class.create('Knows', 'E') + ]); + }) + .spread(function (Thing, Knows) { + return Bluebird.all([ + Thing.property.create([ + { + name: 'name', + type: 'string' + } + ]), + Knows.property.create([ + { + name: 'referrer', + type: 'link' + } + ]) + ]); + }) + .then(function () { + return this.db + .let('first', "CREATE VERTEX Thing SET name = 'first'") + .let('second', "CREATE VERTEX Thing SET name = 'second'") + .let('third', "CREATE VERTEX Thing SET name = 'third'") + .return(['$first', '$second', '$third']) + .commit() + .all() + .then(function (results) { + first = results[0]; + second = results[1]; + third = results[2]; + }); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_edge_link'); + }); + it('should create a link whilst creating an edge', function () { + return this.db + .create('EDGE', 'Knows') + .from(first['@rid']) + .to(second['@rid']) + .set({ + referrer: third['@rid'] + }) + .one() + .then(function (result) { + result.referrer.equals(third['@rid']).should.be.true; + }) + }); + + it('should create a link whilst creating an edge in a transaction', function () { + return this.db + .let('fourth', "CREATE VERTEX Thing SET name = 'fourth'") + .let('fifth', "CREATE VERTEX Thing SET name = 'fifth'") + .let('sixth', "CREATE VERTEX Thing SET name = 'sixth'") + .let('knows', function (s) { + s + .create('EDGE', 'Knows') + .from('$fourth') + .to('$fifth') + .set('referrer = first($sixth)') + }) + .return(['$sixth', '$knows']) + .commit() + .all() + .spread(function (referrer, edge) { + edge.referrer.should.equal(referrer); + }); + }); +}); \ No newline at end of file From f698cfd1f1b6778a7ee1dffd00db6230b5f0207b Mon Sep 17 00:00:00 2001 From: Ryan Schmukler Date: Fri, 20 Feb 2015 16:17:59 -0500 Subject: [PATCH 275/308] allow database connection to not require a server-wide connection closes #169 --- lib/server/index.js | 8 ++++++++ lib/transport/binary/index.js | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/server/index.js b/lib/server/index.js index ea5bfc1..33995fa 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -143,6 +143,14 @@ Server.prototype.use = function (config) { } else { config.server = this; + if (config.username) { + this.transport.username = config.username; + this.transport.skipServerConnect = true; + } + if (config.password) { + this.transport.password = config.password; + this.transport.skipServerConnect = true; + } } if (!config.name) { diff --git a/lib/transport/binary/index.js b/lib/transport/binary/index.js index 52774f3..03d2be6 100644 --- a/lib/transport/binary/index.js +++ b/lib/transport/binary/index.js @@ -139,6 +139,10 @@ BinaryTransport.prototype.connect = function () { return Promise.resolve(this); } + if (this.skipServerConnect) { + return Promise.resolve(this).bind(this); + } + if (this.connecting) { if (this.connecting.isRejected()) { return new Promise(function (resolve, reject) { @@ -218,4 +222,4 @@ function reconnectTransport (transport, cancellationError) { transport.connection = false; transport.configureConnection(); transport.emit('reset'); -} \ No newline at end of file +} From cce410a08413b808dfbe14c3d84c95c7d6de4b72 Mon Sep 17 00:00:00 2001 From: Ryan Schmukler Date: Fri, 20 Feb 2015 20:50:47 -0500 Subject: [PATCH 276/308] add test for 169 --- test/bugs/169-connect-directly-to-database.js | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 test/bugs/169-connect-directly-to-database.js diff --git a/test/bugs/169-connect-directly-to-database.js b/test/bugs/169-connect-directly-to-database.js new file mode 100644 index 0000000..c3c94bf --- /dev/null +++ b/test/bugs/169-connect-directly-to-database.js @@ -0,0 +1,30 @@ +describe("Bug #169: Connect to db without server credentials", function () { + var server, db; + before(function () { + server = new LIB.Server({ + host: TEST_SERVER_CONFIG.host, + port: TEST_SERVER_CONFIG.port, + username: 'nope', + password: 'nope', + transport: 'binary', + useToken: false + }); + return CREATE_TEST_DB(this, 'testdb_bug_169') + .then(function () { + db = server.use({ + name: 'testdb_bug_169', + username: 'admin', + password: 'admin' + }); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_169'); + }); + it('should connect to the database directly', function () { + return db.select().from('OUser').all() + .then(function (results) { + results.length.should.be.above(0); + }); + }); +}); From 2e2a07671cfcf5499a5018245037e82ecc0504ec Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sat, 21 Feb 2015 10:55:16 +0000 Subject: [PATCH 277/308] prepare 1.1.3 --- .travis.yml | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 87f3574..f3cf06e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,5 @@ before_script: - ./ci/initialize-ci.sh $ORIENTDB_VERSION env: - ORIENTDB_VERSION=1.7.10 + - ORIENTDB_VERSION=2.0.2 - ORIENTDB_VERSION=2.1-SNAPSHOT diff --git a/package.json b/package.json index 898f7c1..06e504e 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node", "node.js" ], - "version": "1.1.2", + "version": "1.1.3", "author": { "name": "Charles Pick", "email": "charles@codemix.com" From 0f8ae017560f61687927c7d6659b6e4180a0e3f1 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Mon, 23 Feb 2015 16:22:17 +0000 Subject: [PATCH 278/308] add helper that determines whether the given statement requires wrapping in parentheses or not --- lib/db/statement.js | 12 ++++++------ lib/utils.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ test/core/utils.js | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 6 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index 1394f55..1e53a53 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -567,7 +567,7 @@ Statement.prototype.buildStatement = function () { return '(' + item.toString() + ')'; } else if (typeof item === 'string') { - if (/^[^\(](.*)\s/.test(item)) { + if (utils.requiresParens(item)) { return '(' + item + ')'; } else { @@ -591,7 +591,7 @@ Statement.prototype.buildStatement = function () { else if (item[1] instanceof Statement) { return item[0] + ' = (' + item[1].toString() + ')'; } - else if (/\s/.test(item[1])) { + else if (utils.requiresParens(item[1])) { return item[0] + ' = (' + item[1] + ')'; } else { @@ -613,7 +613,7 @@ Statement.prototype.buildStatement = function () { return '(' + item.toString() + ')'; } else if (typeof item === 'string') { - if (/^[^\(](.*)\s/.test(item)) { + if (utils.requiresParens(item)) { return '(' + item + ')'; } else { @@ -630,7 +630,7 @@ Statement.prototype.buildStatement = function () { statement.push('INTO'); statement.push(state.into.map(function (item) { if (typeof item === 'string') { - if (/(\s+)/.test(item)) { + if (utils.requiresParens(item)) { return '(' + item + ')'; } else { @@ -769,7 +769,7 @@ Statement.prototype.buildStatement = function () { statement.push('GROUP BY'); statement.push(state.group.map(function (item) { if (typeof item === 'string') { - if (/(\s+)/.test(item)) { + if (utils.requiresParens(item)) { return '(' + item + ')'; } else { @@ -786,7 +786,7 @@ Statement.prototype.buildStatement = function () { statement.push('ORDER BY'); statement.push(state.order.map(function (item) { if (typeof item === 'string') { - if (/(\s+)/.test(item)) { + if (utils.requiresParens(item)) { return '(' + item + ')'; } else { diff --git a/lib/utils.js b/lib/utils.js index 0246a38..35efdd7 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -322,6 +322,50 @@ function pad (number, size){ return (1e4 + number + "").slice(-size); } +/** + * Determine whether the given expression needs to be wrapped in parentheses or not. + * @param {String} input The string to check. + * @return {Boolean} `true` if parentheses are required, otherwise false. + */ +exports.requiresParens = function (input) { + if (typeof input !== 'string') { + return false; + } + var text = input.trim(); + var exprCount = 0; + var inParens = 0; + var inQuotes = false; + for (var i = 0; i < text.length; i++) { + var char = text.charAt(i); + if (inQuotes) { + if (char === '\\') { + i += 1; + } + else if (char === inQuotes) { + inQuotes = false; + } + } + else if (char === '"' || char === "'") { + inQuotes = char; + } + else if (char === '(') { + if (inParens === 0) { + exprCount++; + } + inParens++; + } + else if (char === ')') { + inParens--; + } + else if (char === "\t" || char === "\r" || char === "\n" || char === " ") { + if (inParens === 0) { + return true; + } + } + } + return false; +}; + /** * A class used solely to indicate that an object is an "array like" diff --git a/test/core/utils.js b/test/core/utils.js index 64ef66f..d274100 100644 --- a/test/core/utils.js +++ b/test/core/utils.js @@ -19,4 +19,39 @@ describe('utils.prepare', function () { var date = new Date(Date.UTC(2015, 0, 5, 22, 7, 5)); utils.prepare("select from index:foo where date = :date", {date: date}).should.equal("select from index:foo where date = date(\"2015-01-05 22:07:05\", \"yyyy-MM-dd HH:mm:ss\", \"UTC\")"); }); +}); + + +describe('utils.requiresParens()', function () { + it('should not require parentheses for string values', function () { + utils.requiresParens('"foo"').should.be.false; + }); + it('should not require parentheses for integer values', function () { + utils.requiresParens('2134').should.be.false; + }); + it('should not require parentheses for function calls', function () { + utils.requiresParens('foo("hello", "world")').should.be.false; + }); + it('should require parentheses for compound expressions with strings', function () { + utils.requiresParens('"foo" AND "bar"').should.be.true; + }); + it('should not require parentheses for pre-wrapped compound expressions with strings', function () { + utils.requiresParens('("foo" AND "bar")').should.be.false; + }); + it('should require parentheses for compound call expressions', function () { + utils.requiresParens('foo("hello", "world") AND wat').should.be.true; + utils.requiresParens('foo("hello", "world") AND bar("wat")').should.be.true; + }); + it('should not require parentheses for deeply nested function calls', function () { + utils.requiresParens("foo(1,2,3, bar(4,5,6))").should.be.false; + }); + it('should require parentheses for separately parenthesized expressions', function () { + utils.requiresParens("(foo(1,2,3) AND test) AND (a > 2)").should.be.true; + }); + it('should not require parentheses for strings with parenthesized expressions', function () { + utils.requiresParens("'foo (a b c) wat'").should.be.false; + }); + it('should ignore leading and trailing whitespace', function () { + utils.requiresParens(' ("foo" AND "bar") ').should.be.false; + }); }); \ No newline at end of file From a5ab53dd7884053991b5e8e767dab11cb027ba39 Mon Sep 17 00:00:00 2001 From: mamobyz Date: Thu, 26 Feb 2015 14:28:13 +0100 Subject: [PATCH 279/308] Fixed the issue #268 Refer to https://github.com/codemix/oriento/issues/268 --- lib/server/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/server/index.js b/lib/server/index.js index 33995fa..41fa3d4 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -283,7 +283,7 @@ Server.prototype.exists = function (name, storageType) { } storageType = storageType || 'plocal'; return this.send('db-exists', { - name: (''+name).toLowerCase(), + name: ''+name, storage: storageType.toLowerCase() }) .then(function (response) { From 7d00ede0eacca59b1e5cc01d6c6d6b3b4dd44027 Mon Sep 17 00:00:00 2001 From: Ryan Schmukler Date: Thu, 26 Feb 2015 15:34:52 -0500 Subject: [PATCH 280/308] add migrate command for direct database connections --- lib/cli/commands/migrate.js | 4 ++-- lib/cli/index.js | 14 +++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/cli/commands/migrate.js b/lib/cli/commands/migrate.js index 01d2292..dd18b65 100644 --- a/lib/cli/commands/migrate.js +++ b/lib/cli/commands/migrate.js @@ -7,7 +7,7 @@ var path = require('path'), * The required CLI arguments for the command. * @type {Array} */ -exports.requiredArgv = ['dbname', 'password']; +exports.requiredArgv = ['dbname', 'password|dbpassword']; /** * Get the migration manager instance @@ -97,4 +97,4 @@ exports.down = function (limit) { console.log('\t\t' + item); }); }); -}; \ No newline at end of file +}; diff --git a/lib/cli/index.js b/lib/cli/index.js index 8dd250a..bb48e57 100644 --- a/lib/cli/index.js +++ b/lib/cli/index.js @@ -33,8 +33,15 @@ CLI.prototype.run = function (argv) { command = this.createCommand(command, argv); total = command.requiredArgv.length; for (i = 0; i < total; i++) { - if (argv[command.requiredArgv[i]] === undefined) { - yargs.demand(command.requiredArgv[i]); + var possibleOpts = command.requiredArgv[i].split('|'); + var found = false; + for (var j = 0; j < possibleOpts.length; ++j) { + if (argv[possibleOpts[j]] !== undefined) { + found = true; + break; + } + } + if (!found) { return command.help(); } } @@ -55,6 +62,7 @@ CLI.prototype.createCommand = function (name, argv) { var Constructor = Command.extend(require(filename)), server = this.createServer(argv); + return new Constructor(server, argv); } catch (e) { @@ -192,4 +200,4 @@ CLI.prototype.list = function () { .map(function (filename) { return filename.slice(0, -3); }); -}; \ No newline at end of file +}; From db75a12118bdd6a92d9e2709c382bd7a120d8ee4 Mon Sep 17 00:00:00 2001 From: mamobyz Date: Thu, 5 Mar 2015 15:13:35 +0100 Subject: [PATCH 281/308] Added strategy clause required for traverse queries which fixes the issue #279 --- lib/db/statement.js | 54 +++++++++++++++++++++++++-------------- test/db/statement-test.js | 20 +++++++++++++++ 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index 1e53a53..c213c16 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -1,9 +1,9 @@ "use strict"; -var RID = require('../recordid'), +var RID = require('../recordid'), utils = require('../utils'); -function Statement (db) { +function Statement(db) { this.db = db; this._state = { params: {}, @@ -31,6 +31,16 @@ Statement.prototype.select = clause('select', '*'); */ Statement.prototype.traverse = clause('traverse', '*'); +/** + * A 'strategy' clause for traverse query + * @param {String} args The strategy how traverse should go in deep, either 'DEPTH_FIRST'|'BREADTH_FIRST', the first one is default + * @return {Statement} The statement object + */ +Statement.prototype.strategy = function (s) { + if (typeof s === 'string' && s.toUpperCase() === 'DEPTH_FIRST' || s.toUpperCase() === 'BREADTH_FIRST') + this._state.strategy = s.toUpperCase(); + return this; +}; /** * Insert expression. @@ -431,7 +441,7 @@ Statement.prototype.near = function (latitudeProperty, longitudeProperty, longit values.push(JSON.stringify({maxDistance: maxDistanceInKms})); } return this.where( - '['+properties.join(',')+'] NEAR ['+values.join(',')+']' + '[' + properties.join(',') + '] NEAR [' + values.join(',') + ']' ); }; @@ -448,12 +458,11 @@ Statement.prototype.near = function (latitudeProperty, longitudeProperty, longit */ Statement.prototype.within = function (latitudeProperty, longitudeProperty, box) { return this.where( - '['+latitudeProperty+','+longitudeProperty+'] WITHIN '+JSON.stringify(box) + '[' + latitudeProperty + ',' + longitudeProperty + '] WITHIN ' + JSON.stringify(box) ); }; - /** * Add the given parameter to the query. * @@ -575,7 +584,7 @@ Statement.prototype.buildStatement = function () { } } else { - return ''+item; + return '' + item; } }).join(', ')); } @@ -621,7 +630,7 @@ Statement.prototype.buildStatement = function () { } } else { - return ''+item; + return '' + item; } }).join(', ')); } @@ -638,7 +647,7 @@ Statement.prototype.buildStatement = function () { } } else { - return ''+item; + return '' + item; } }).join(', ')); } @@ -653,7 +662,9 @@ Statement.prototype.buildStatement = function () { else { return this._objectToSet(item); } - }, this).filter(function (item) { return item; }).join(', ')); + }, this).filter(function (item) { + return item; + }).join(', ')); } if (state.increment && state.increment.length) { @@ -711,7 +722,7 @@ Statement.prototype.buildStatement = function () { if ((state.update || state.insert || state.delete) && state.return) { statement.push('RETURN'); if (Array.isArray(state.return)) { - statement.push('['+state.return.join(',')+']'); + statement.push('[' + state.return.join(',') + ']'); } else if (typeof state.return === 'object') { statement.push(encodeReturnObject(state.return)); @@ -777,7 +788,7 @@ Statement.prototype.buildStatement = function () { } } else { - return ''+item; + return '' + item; } }).join(', ')); } @@ -805,7 +816,7 @@ Statement.prototype.buildStatement = function () { return parts.join(' '); } else { - return ''+item; + return '' + item; } }).join(', ')); } @@ -813,6 +824,11 @@ Statement.prototype.buildStatement = function () { if (state.limit) { statement.push('LIMIT ' + (+state.limit)); } + + if (state.strategy && state.traverse) { + statement.push('STRATEGY ' + state.strategy) + } + if (state.skip) { statement.push('SKIP ' + (+state.skip)); } @@ -836,13 +852,13 @@ Statement.prototype.buildStatement = function () { } if (state.wait) { - statement.push('WAIT ' + (+state.wait)); + statement.push('WAIT ' + (+state.wait)); } if (!(state.update || state.insert || state.delete) && state.return) { statement.push('RETURN'); if (Array.isArray(state.return)) { - statement.push('['+state.return.join(',')+']'); + statement.push('[' + state.return.join(',') + ']'); } else if (typeof state.return === 'object') { statement.push(encodeReturnObject(state.return)); @@ -973,11 +989,11 @@ Statement.prototype._objectToSet = function (obj) { } }; -function paramify (key) { +function paramify(key) { return key.replace(/([^A-Za-z0-9])/g, ''); } -function clause (name) { +function clause(name) { var defaults = Array.prototype.slice.call(arguments, 1); return function (args) { if (args === undefined) { @@ -992,7 +1008,7 @@ function clause (name) { }; } -function whereClause (operator, comparisonOperator) { +function whereClause(operator, comparisonOperator) { comparisonOperator = comparisonOperator || '='; return function (condition, params) { this._state.where = this._state.where || []; @@ -1005,7 +1021,7 @@ function whereClause (operator, comparisonOperator) { } -function encodeReturnObject (obj) { +function encodeReturnObject(obj) { var keys = Object.keys(obj), length = keys.length, parts = new Array(length), @@ -1014,5 +1030,5 @@ function encodeReturnObject (obj) { key = keys[i]; parts[i] = utils.encode(key) + ":" + obj[key]; } - return '{'+parts.join(',')+'}'; + return '{' + parts.join(',') + '}'; } \ No newline at end of file diff --git a/test/db/statement-test.js b/test/db/statement-test.js index 3b4e09e..9c20088 100644 --- a/test/db/statement-test.js +++ b/test/db/statement-test.js @@ -178,6 +178,26 @@ COMMIT \n\ this.statement.traverse('in("Thing")', 'out("Thing")'); this.statement.buildStatement().should.equal('TRAVERSE in("Thing"), out("Thing")'); }); + + it('should traverse in depth first', function () { + this.statement.traverse().strategy('DEPTH_FIRST').from('Abc'); + this.statement.buildStatement().should.equal('TRAVERSE * FROM Abc STRATEGY DEPTH_FIRST'); + }); + + it('should traverse in breadth first', function () { + this.statement.traverse().strategy('BREADTH_FIRST').from('#23:4'); + this.statement.buildStatement().should.equal('TRAVERSE * FROM #23:4 STRATEGY BREADTH_FIRST'); + }); + + it('should traverse with no strategy spec', function () { + this.statement.traverse().strategy('XYZ'); + this.statement.buildStatement().should.equal('TRAVERSE *'); + }); + + it('should traverse in breadth first and with limit', function () { + this.statement.traverse().strategy('BREADTH_FIRST').limit(2).from('Xyz'); + this.statement.buildStatement().should.equal('TRAVERSE * FROM Xyz LIMIT 2 STRATEGY BREADTH_FIRST'); + }); }); describe('Statement::while()', function () { From fa8b2228f9d5fa5804e229d883008342f109e8de Mon Sep 17 00:00:00 2001 From: mamobyz Date: Thu, 5 Mar 2015 15:13:35 +0100 Subject: [PATCH 282/308] Added strategy clause required for traverse queries which fixes the issue #279 Style improvements according to JSLint --- lib/db/statement.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index c213c16..6bff1ea 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -33,12 +33,14 @@ Statement.prototype.traverse = clause('traverse', '*'); /** * A 'strategy' clause for traverse query - * @param {String} args The strategy how traverse should go in deep, either 'DEPTH_FIRST'|'BREADTH_FIRST', the first one is default - * @return {Statement} The statement object + * @param {String} args The strategy how traverse should go in deep, + * either 'DEPTH_FIRST'|'BREADTH_FIRST', the first one is default + * @return {Statement} The statement object */ Statement.prototype.strategy = function (s) { - if (typeof s === 'string' && s.toUpperCase() === 'DEPTH_FIRST' || s.toUpperCase() === 'BREADTH_FIRST') + if (typeof s === 'string' && s.toUpperCase() === 'DEPTH_FIRST' || s.toUpperCase() === 'BREADTH_FIRST') { this._state.strategy = s.toUpperCase(); + } return this; }; @@ -826,7 +828,7 @@ Statement.prototype.buildStatement = function () { } if (state.strategy && state.traverse) { - statement.push('STRATEGY ' + state.strategy) + statement.push('STRATEGY ' + state.strategy); } if (state.skip) { From 315cec8ef626ee5069c321b7967f403f7b621ef8 Mon Sep 17 00:00:00 2001 From: Zlatko Fedor Date: Sun, 15 Mar 2015 15:55:08 +1300 Subject: [PATCH 283/308] added support for abstract classes --- lib/db/class/index.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/db/class/index.js b/lib/db/class/index.js index e42d89d..25d86db 100644 --- a/lib/db/class/index.js +++ b/lib/db/class/index.js @@ -204,9 +204,10 @@ exports.list = function (refresh) { * @param {String} name The name of the class to create. * @param {String} parentName The name of the parent to extend, if any. * @param {String|Integer} cluster The cluster name or id. + * @param {Boolean} isAbstract The flag for the abstract class * @promise {Object} The created class object */ -exports.create = function (name, parentName, cluster) { +exports.create = function (name, parentName, cluster, isAbstract) { var query = 'CREATE CLASS ' + name; if (parentName) { @@ -216,6 +217,10 @@ exports.create = function (name, parentName, cluster) { if (cluster) { query += ' CLUSTER ' + cluster; } + + if(isAbstract) { + query += ' ABSTRACT'; + } return this.query(query) .bind(this) @@ -296,4 +301,4 @@ exports.cacheData = function (classes) { } return this; -}; \ No newline at end of file +}; From 47159e00f308e650925ffd32f6fb632eb481fd72 Mon Sep 17 00:00:00 2001 From: Zlatko Fedor Date: Sun, 15 Mar 2015 16:00:43 +1300 Subject: [PATCH 284/308] Bad default config for class properties Your current implementation for default value will replace valid number 0 to null because 0 || null => null --- lib/db/class/property.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/db/class/property.js b/lib/db/class/property.js index 9db2bcf..b512d56 100644 --- a/lib/db/class/property.js +++ b/lib/db/class/property.js @@ -35,8 +35,8 @@ Property.prototype.configure = function (config) { this.readonly = config.readonly || false; this.notNull = config.notNull || false; this.collate = config.collate || 'default'; - this.min = config.min || null; - this.max = config.max || null; + this.min = typeof config.min !== 'undefined' ? config.min : null; + this.max = typeof config.max !== 'undefined' ? config.max : null; this.regexp = config.regexp || null; this.linkedClass = config.linkedClass || null; if (config.custom && config.custom.fields) { From 2e8b27ae4a04bdb4792803e2e9d472746e7b16f2 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 15 Mar 2015 20:00:14 +0000 Subject: [PATCH 285/308] disable 2.1-SNAPSHOT tests in travis for now --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f3cf06e..7b188f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,4 +6,3 @@ before_script: env: - ORIENTDB_VERSION=1.7.10 - ORIENTDB_VERSION=2.0.2 - - ORIENTDB_VERSION=2.1-SNAPSHOT From 1fce765123111c1dba042cea0df90f646c0113ca Mon Sep 17 00:00:00 2001 From: Zlatko Fedor Date: Sun, 15 Mar 2015 15:55:08 +1300 Subject: [PATCH 286/308] added support for abstract classes --- lib/db/class/index.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/db/class/index.js b/lib/db/class/index.js index e42d89d..25d86db 100644 --- a/lib/db/class/index.js +++ b/lib/db/class/index.js @@ -204,9 +204,10 @@ exports.list = function (refresh) { * @param {String} name The name of the class to create. * @param {String} parentName The name of the parent to extend, if any. * @param {String|Integer} cluster The cluster name or id. + * @param {Boolean} isAbstract The flag for the abstract class * @promise {Object} The created class object */ -exports.create = function (name, parentName, cluster) { +exports.create = function (name, parentName, cluster, isAbstract) { var query = 'CREATE CLASS ' + name; if (parentName) { @@ -216,6 +217,10 @@ exports.create = function (name, parentName, cluster) { if (cluster) { query += ' CLUSTER ' + cluster; } + + if(isAbstract) { + query += ' ABSTRACT'; + } return this.query(query) .bind(this) @@ -296,4 +301,4 @@ exports.cacheData = function (classes) { } return this; -}; \ No newline at end of file +}; From f054e1035cb417675c1f94e8b3c31ae3e0ecc56d Mon Sep 17 00:00:00 2001 From: Zlatko Fedor Date: Sun, 15 Mar 2015 16:00:43 +1300 Subject: [PATCH 287/308] Bad default config for class properties Your current implementation for default value will replace valid number 0 to null because 0 || null => null --- lib/db/class/property.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/db/class/property.js b/lib/db/class/property.js index 9db2bcf..b512d56 100644 --- a/lib/db/class/property.js +++ b/lib/db/class/property.js @@ -35,8 +35,8 @@ Property.prototype.configure = function (config) { this.readonly = config.readonly || false; this.notNull = config.notNull || false; this.collate = config.collate || 'default'; - this.min = config.min || null; - this.max = config.max || null; + this.min = typeof config.min !== 'undefined' ? config.min : null; + this.max = typeof config.max !== 'undefined' ? config.max : null; this.regexp = config.regexp || null; this.linkedClass = config.linkedClass || null; if (config.custom && config.custom.fields) { From 7e403cac52aed8b409709076e57f2e9ed949aeb1 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Sun, 15 Mar 2015 20:00:14 +0000 Subject: [PATCH 288/308] disable 2.1-SNAPSHOT tests in travis for now --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f3cf06e..7b188f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,4 +6,3 @@ before_script: env: - ORIENTDB_VERSION=1.7.10 - ORIENTDB_VERSION=2.0.2 - - ORIENTDB_VERSION=2.1-SNAPSHOT From dd9aeee8fdff6aaba127b44b1fab2edc90c155ef Mon Sep 17 00:00:00 2001 From: mamobyz Date: Mon, 16 Mar 2015 08:45:25 +0100 Subject: [PATCH 289/308] Undo formatting caused by the last changes --- lib/db/statement.js | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/lib/db/statement.js b/lib/db/statement.js index 6bff1ea..a76697e 100644 --- a/lib/db/statement.js +++ b/lib/db/statement.js @@ -1,9 +1,9 @@ "use strict"; -var RID = require('../recordid'), +var RID = require('../recordid'), utils = require('../utils'); -function Statement(db) { +function Statement (db) { this.db = db; this._state = { params: {}, @@ -443,7 +443,7 @@ Statement.prototype.near = function (latitudeProperty, longitudeProperty, longit values.push(JSON.stringify({maxDistance: maxDistanceInKms})); } return this.where( - '[' + properties.join(',') + '] NEAR [' + values.join(',') + ']' + '['+properties.join(',')+'] NEAR ['+values.join(',')+']' ); }; @@ -460,11 +460,12 @@ Statement.prototype.near = function (latitudeProperty, longitudeProperty, longit */ Statement.prototype.within = function (latitudeProperty, longitudeProperty, box) { return this.where( - '[' + latitudeProperty + ',' + longitudeProperty + '] WITHIN ' + JSON.stringify(box) + '['+latitudeProperty+','+longitudeProperty+'] WITHIN '+JSON.stringify(box) ); }; + /** * Add the given parameter to the query. * @@ -586,7 +587,7 @@ Statement.prototype.buildStatement = function () { } } else { - return '' + item; + return ''+item; } }).join(', ')); } @@ -632,7 +633,7 @@ Statement.prototype.buildStatement = function () { } } else { - return '' + item; + return ''+item; } }).join(', ')); } @@ -649,7 +650,7 @@ Statement.prototype.buildStatement = function () { } } else { - return '' + item; + return ''+item; } }).join(', ')); } @@ -664,9 +665,7 @@ Statement.prototype.buildStatement = function () { else { return this._objectToSet(item); } - }, this).filter(function (item) { - return item; - }).join(', ')); + }, this).filter(function (item) { return item; }).join(', ')); } if (state.increment && state.increment.length) { @@ -724,7 +723,7 @@ Statement.prototype.buildStatement = function () { if ((state.update || state.insert || state.delete) && state.return) { statement.push('RETURN'); if (Array.isArray(state.return)) { - statement.push('[' + state.return.join(',') + ']'); + statement.push('['+state.return.join(',')+']'); } else if (typeof state.return === 'object') { statement.push(encodeReturnObject(state.return)); @@ -790,7 +789,7 @@ Statement.prototype.buildStatement = function () { } } else { - return '' + item; + return ''+item; } }).join(', ')); } @@ -818,7 +817,7 @@ Statement.prototype.buildStatement = function () { return parts.join(' '); } else { - return '' + item; + return ''+item; } }).join(', ')); } @@ -854,13 +853,13 @@ Statement.prototype.buildStatement = function () { } if (state.wait) { - statement.push('WAIT ' + (+state.wait)); + statement.push('WAIT ' + (+state.wait)); } if (!(state.update || state.insert || state.delete) && state.return) { statement.push('RETURN'); if (Array.isArray(state.return)) { - statement.push('[' + state.return.join(',') + ']'); + statement.push('['+state.return.join(',')+']'); } else if (typeof state.return === 'object') { statement.push(encodeReturnObject(state.return)); @@ -991,11 +990,11 @@ Statement.prototype._objectToSet = function (obj) { } }; -function paramify(key) { +function paramify (key) { return key.replace(/([^A-Za-z0-9])/g, ''); } -function clause(name) { +function clause (name) { var defaults = Array.prototype.slice.call(arguments, 1); return function (args) { if (args === undefined) { @@ -1010,7 +1009,7 @@ function clause(name) { }; } -function whereClause(operator, comparisonOperator) { +function whereClause (operator, comparisonOperator) { comparisonOperator = comparisonOperator || '='; return function (condition, params) { this._state.where = this._state.where || []; @@ -1023,7 +1022,7 @@ function whereClause(operator, comparisonOperator) { } -function encodeReturnObject(obj) { +function encodeReturnObject (obj) { var keys = Object.keys(obj), length = keys.length, parts = new Array(length), @@ -1032,5 +1031,5 @@ function encodeReturnObject(obj) { key = keys[i]; parts[i] = utils.encode(key) + ":" + obj[key]; } - return '{' + parts.join(',') + '}'; -} \ No newline at end of file + return '{'+parts.join(',')+'}'; +} From 5b2142e409c0667fcf642d0645ce87cc32e7d8d4 Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Thu, 26 Mar 2015 16:26:44 +0100 Subject: [PATCH 290/308] supporting live query --- lib/db/index.js | 61 +++++++++++++++++++- lib/transport/binary/connection.js | 24 +++++++- lib/transport/binary/operation-status.js | 3 +- lib/transport/binary/protocol28/operation.js | 47 +++++++++++++-- 4 files changed, 127 insertions(+), 8 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index e7482a4..ab68fca 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -302,6 +302,66 @@ Db.prototype.query = function (command, options) { }); }; +/** + * Execute a live query against the database + * + * @param {String} query The query or command to execute. + * @param {Object} options The options for the query / command. + * @promise {Mixed} The token of the live query. + */ +Db.prototype.liveQuery = function (command, options) { + options = options || {}; + options.mode = 'l'; + options.class='q'; + this.exec(command, options) + .bind(this) + .then(function (response) { + if (!response.results || response.results.length === 0) { + return [[], []]; + } + return response.results + .map(this.normalizeResult, this) + .reduce(flatten, []) + .reduce(function (list, item) { + if (item && item['@preloaded']) { + delete item['@preloaded']; + list[1].push(item); + } + else { + list[0].push(item); + } + return list; + }, [[], []]); + }) + .spread(function (results, preloaded) { + this.record.resolveReferences(results.concat(preloaded)); + return results; + }) + .then(function (response){ + console.log(response); + if(response.length > 0){ + var iToken = response[0].token; + var parentDb = this; + var wrapperCallback = function(currentToken, operation, result){ + if(currentToken == iToken) { + if (operation === 1) { + parentDb.emit("live-update", result); + } else if (operation === 2) { + parentDb.emit("live-delete", result); + } else if (operation === 3) { + parentDb.emit("live-insert", result); + parentDb.emit("live-create", result); + } + } + } + this.server.transport.connection.on("live-query-result", wrapperCallback); + + } + }) + return this; +}; + + /** * Normalize a result, where possible. * @param {Object} result The result to normalize. @@ -563,7 +623,6 @@ Db.prototype.createFn = function (name, fn, options) { return this.query('CREATE FUNCTION '+name+' "'+body+'" '+params+' LANGUAGE Javascript'); }; - /** * Flatten an array of arrays */ diff --git a/lib/transport/binary/connection.js b/lib/transport/binary/connection.js index ce69149..86ea949 100644 --- a/lib/transport/binary/connection.js +++ b/lib/transport/binary/connection.js @@ -6,7 +6,9 @@ var net = require('net'), errors = require('../../errors'), OperationStatus = require('./operation-status'), EventEmitter = require('events').EventEmitter, - Promise = require('bluebird'); + Promise = require('bluebird'), + Operation = require('./protocol28/operation'); //TODO refactor this!!! + function Connection (config) { EventEmitter.call(this); @@ -352,8 +354,26 @@ Connection.prototype.destroySocket = function () { * @return {Integer} The offset that was successfully read up to. */ Connection.prototype.process = function (buffer, offset) { - var code, parsed, result, status, item, op, deferred, err; + var code, parsed, result, status, item, op, deferred, err, token, operation; offset = offset || 0; + if(this.queue.length === 0){ + op = new Operation();//TODO refactor this! + parsed = op.consume(buffer, offset); + status = parsed[0]; + if (status === OperationStatus.PUSH_DATA) { + offset = parsed[1]; + result = parsed[2]; + this.emit('update-config', result); + return offset; + }else if(status === OperationStatus.LIVE_RESULT){ + token = parsed[1]; + operation = parsed[2]; + result = parsed[3]; + offset = parsed[4]; + this.emit('live-query-result', token, operation, result); + return offset; + } + } while ((item = this.queue.shift())) { op = item[0]; deferred = item[1]; diff --git a/lib/transport/binary/operation-status.js b/lib/transport/binary/operation-status.js index ec971bf..c701e67 100644 --- a/lib/transport/binary/operation-status.js +++ b/lib/transport/binary/operation-status.js @@ -5,4 +5,5 @@ exports.WRITTEN = 1; exports.READING = 2; exports.COMPLETE = 3; exports.ERROR = 4; -exports.PUSH_DATA = 5; \ No newline at end of file +exports.PUSH_DATA = 5; +exports.LIVE_RESULT = 6; \ No newline at end of file diff --git a/lib/transport/binary/protocol28/operation.js b/lib/transport/binary/protocol28/operation.js index ee959c1..1d88952 100644 --- a/lib/transport/binary/protocol28/operation.js +++ b/lib/transport/binary/protocol28/operation.js @@ -394,10 +394,49 @@ Operation.prototype.consume = function (buffer, offset) { code = buffer.readUInt8(offset); if (code === 3) { - offset += 5; // ignore the next integer - obj = {}; - offset += this.parsePushedData(buffer, offset, obj, 'data'); - return [Operation.PUSH_DATA, offset, obj.data]; + var sessionId = buffer.readInt32BE(offset+1); + var pushType = buffer.readUInt8(offset + 5); + + if (pushType === 81) { + offset += 6; // ignore the next integer + var length = buffer.readInt32BE(offset); + offset+=4; + var operation = buffer.readUInt8(offset); + offset+=1; + var token = buffer.readInt32BE(offset); + offset+=4; + var recordType = buffer.readUInt8(offset); + offset+=1; + var version = buffer.readInt32BE(offset); + offset+=4; + var clusterId = buffer.readInt16BE(offset); + offset+=2; + var clusterPosition = Long.fromBits( + buffer.readUInt32BE(offset + 4), + buffer.readInt32BE(offset) + ).toNumber(); + offset+=8; + var contentLenght = buffer.readInt32BE(offset); + offset+=4; + + var asString = buffer.toString('utf8', offset, offset + contentLenght); + offset += contentLenght; + var content = deserializer.deserialize(asString, this.data.transformerFunctions); + + var obj = {}; + obj.content = content; + obj.type = 'd'; + obj.cluster = clusterId; + obj.position = clusterPosition; + obj.version = version; + + return [Operation.LIVE_RESULT, token, operation, obj, offset]; + } else { + offset += 5; // ignore the next integer + obj = {}; + offset += this.parsePushedData(buffer, offset, obj, 'data'); + return [Operation.PUSH_DATA, offset, obj.data]; + } } this.status = Operation.READING; From 28059fa978107247e23bc3ef422c099967d94126 Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Thu, 26 Mar 2015 16:36:14 +0100 Subject: [PATCH 291/308] fixed live query --- lib/transport/binary/protocol28/operation.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/transport/binary/protocol28/operation.js b/lib/transport/binary/protocol28/operation.js index 1d88952..d339ce7 100644 --- a/lib/transport/binary/protocol28/operation.js +++ b/lib/transport/binary/protocol28/operation.js @@ -38,6 +38,8 @@ Operation.READING = statuses.READING; Operation.COMPLETE = statuses.COMPLETE; Operation.ERROR = statuses.ERROR; Operation.PUSH_DATA = statuses.PUSH_DATA; +Operation.LIVE_RESULT = statuses.LIVE_RESULT; + // make it easy to inherit from the base class Operation.extend = utils.extend; From 57f9f32acd2151cc4c3113db47a0bcb1ff595ccd Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Thu, 26 Mar 2015 16:53:08 +0100 Subject: [PATCH 292/308] removed useless console log --- lib/db/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/db/index.js b/lib/db/index.js index ab68fca..1230a4c 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -338,7 +338,6 @@ Db.prototype.liveQuery = function (command, options) { return results; }) .then(function (response){ - console.log(response); if(response.length > 0){ var iToken = response[0].token; var parentDb = this; From f4ba2b1bbd16770fb51c63145bb574cc92940fba Mon Sep 17 00:00:00 2001 From: orgos Date: Mon, 20 Apr 2015 18:30:24 -0400 Subject: [PATCH 293/308] Added milliseconds to the date conversion. --- lib/utils.js | 5 +++-- test/core/utils.js | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 35efdd7..2cee612 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -220,7 +220,7 @@ exports.encode = function encode (value) { return '"' + exports.escape(value) + '"'; } else if (value instanceof Date) { - return 'date("' + getOrientDbUTCDate(value) + '", "yyyy-MM-dd HH:mm:ss", "UTC")'; + return 'date("' + getOrientDbUTCDate(value) + '", "yyyy-MM-dd HH:mm:ss.SSS", "UTC")'; } else if (value instanceof RID) { return value.toString(); @@ -314,8 +314,9 @@ function getOrientDbUTCDate(date){ var HH = pad(date.getUTCHours(), 2); var mm = pad(date.getUTCMinutes(), 2); var ss = pad(date.getUTCSeconds(), 2); + var SSS = pad(date.getUTCMilliseconds(), 3); - return yyyy + '-' + MM + '-' + dd + ' ' + HH + ':' + mm + ':' + ss; + return yyyy + '-' + MM + '-' + dd + ' ' + HH + ':' + mm + ':' + ss + '.' + SSS; } function pad (number, size){ diff --git a/test/core/utils.js b/test/core/utils.js index d274100..16ce5c9 100644 --- a/test/core/utils.js +++ b/test/core/utils.js @@ -16,8 +16,8 @@ describe('utils.prepare', function () { text.should.equal('SELECT * FROM OUser WHERE foo = "\\/// TE /// ST \\\\\\\\\\\\"'); }); it("should prepare SQL statements with date parameters", function () { - var date = new Date(Date.UTC(2015, 0, 5, 22, 7, 5)); - utils.prepare("select from index:foo where date = :date", {date: date}).should.equal("select from index:foo where date = date(\"2015-01-05 22:07:05\", \"yyyy-MM-dd HH:mm:ss\", \"UTC\")"); + var date = new Date(Date.UTC(2015, 0, 5, 22, 7, 5, 435)); + utils.prepare("select from index:foo where date = :date", {date: date}).should.equal("select from index:foo where date = date(\"2015-01-05 22:07:05.435\", \"yyyy-MM-dd HH:mm:ss.SSS\", \"UTC\")"); }); }); From 0c48f0c6f705e3fa56422d20ebe7a0f1aa524ea8 Mon Sep 17 00:00:00 2001 From: Gabri Di Stefano Date: Wed, 6 May 2015 21:28:30 +0200 Subject: [PATCH 294/308] Missing `readonly` on Property update --- lib/db/class/property.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/db/class/property.js b/lib/db/class/property.js index b512d56..e631b7a 100644 --- a/lib/db/class/property.js +++ b/lib/db/class/property.js @@ -214,6 +214,9 @@ Property.update = function (property, reload) { if (property.type !== undefined) { promises.push(this.db.exec(prefix + 'TYPE ' + property.type)); } + if (property.readonly !== undefined) { + promises.push(this.db.exec(prefix + 'READONLY ' + (property.readonly ? 'true' : 'false'))); + } if (property.mandatory !== undefined) { promises.push(this.db.exec(prefix + 'MANDATORY ' + (property.mandatory ? 'true' : 'false'))); } From f7b20ea99bd5d18478213b7c3b378884ef28e5a4 Mon Sep 17 00:00:00 2001 From: Gabriele Di Stefano Date: Sun, 10 May 2015 10:17:59 +0200 Subject: [PATCH 295/308] init --- lib/db/class/index.js | 28 ++++++++++++++++++++++++++-- test/db/class-test.js | 24 +++++++++++++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/lib/db/class/index.js b/lib/db/class/index.js index 25d86db..0bcec77 100644 --- a/lib/db/class/index.js +++ b/lib/db/class/index.js @@ -217,7 +217,7 @@ exports.create = function (name, parentName, cluster, isAbstract) { if (cluster) { query += ' CLUSTER ' + cluster; } - + if(isAbstract) { query += ' ABSTRACT'; } @@ -232,6 +232,31 @@ exports.create = function (name, parentName, cluster, isAbstract) { }); }; +/** + * Update the given class. + * + * @param {Object} class The class settings. + * @param {Boolean} reload Whether to reload the class, default to true. + * @promise {Object} The updated class. + */ +exports.update = function (cls, reload) { + var promises = [], + prefix = 'ALTER CLASS ' + cls.name + ' '; + + if (reload == null) { + reload = true; + } + + if (cls.superClass !== undefined) { + promises.push(this.exec(prefix + 'SUPERCLASS ' + cls.superClass)); + } + + return Promise.all(promises) + .bind(this) + .then(function () { + return this.class.get(cls.name, reload); + }); +}; /** * Delete a class. @@ -250,7 +275,6 @@ exports.drop = function (name) { }); }; - /** * Get a class by name. * diff --git a/test/db/class-test.js b/test/db/class-test.js index ac08ed9..ce472a2 100644 --- a/test/db/class-test.js +++ b/test/db/class-test.js @@ -33,6 +33,29 @@ describe("Database API - Class", function () { return this.db.class.create('TestClass') .then(function (item) { item.name.should.equal('TestClass'); + item.should.have.property('superClass', null); + item.should.be.an.instanceOf(Class); + }); + }); + it('should create a class with the given name and a super class', function () { + return this.db.class.create('TestClassExtended', 'V') + .then(function (item) { + item.name.should.equal('TestClassExtended'); + item.should.have.property('superClass', 'V'); + item.should.be.an.instanceOf(Class); + }); + }); + }); + + describe('Db::class.update()', function () { + it('should update a class with the given superClass', function () { + return this.db.class.update({ + name: 'TestClass', + superClass: 'V' + }) + .then(function (item) { + item.name.should.equal('TestClass'); + item.should.have.property('superClass', 'V'); item.should.be.an.instanceOf(Class); }); }); @@ -44,7 +67,6 @@ describe("Database API - Class", function () { }); }); - describe('Instance functions', function () { before(function () { return this.db.class.get('OUser') From 5a2870a7b5f2d00184b357d557d3543eabe2b09e Mon Sep 17 00:00:00 2001 From: Gabriele Di Stefano Date: Sun, 10 May 2015 10:20:35 +0200 Subject: [PATCH 296/308] rename superClass --- test/db/class-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/db/class-test.js b/test/db/class-test.js index ce472a2..f482f0c 100644 --- a/test/db/class-test.js +++ b/test/db/class-test.js @@ -37,7 +37,7 @@ describe("Database API - Class", function () { item.should.be.an.instanceOf(Class); }); }); - it('should create a class with the given name and a super class', function () { + it('should create a class with the given name and a superClass', function () { return this.db.class.create('TestClassExtended', 'V') .then(function (item) { item.name.should.equal('TestClassExtended'); From 53f491591003d182beb7489dd51ed0388841af38 Mon Sep 17 00:00:00 2001 From: Gabriele Di Stefano Date: Sun, 10 May 2015 10:28:14 +0200 Subject: [PATCH 297/308] update Readme --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index e480cae..38a0b55 100644 --- a/README.md +++ b/README.md @@ -363,6 +363,18 @@ db.class.get('MyClass') }); ``` +### Updating an existing class + +```js +db.class.update({ + name: 'MyClass', + superClass: 'V' +}) +.then(function (MyClass) { + console.log('Updated class: ' + MyClass.name + ' that extends ' + MyClass.superClass); +}); +``` + ### Listing properties in a class ```js From 1ff1ded9c7172a65d099904c97125e0e220b9396 Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Tue, 19 May 2015 08:26:30 +0200 Subject: [PATCH 298/308] live query - code cleanup (duplicate variable and some semicolins) --- lib/db/index.js | 4 ++-- lib/transport/binary/protocol28/operation.js | 22 +++++++++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/db/index.js b/lib/db/index.js index 1230a4c..e9203ac 100644 --- a/lib/db/index.js +++ b/lib/db/index.js @@ -352,11 +352,11 @@ Db.prototype.liveQuery = function (command, options) { parentDb.emit("live-create", result); } } - } + }; this.server.transport.connection.on("live-query-result", wrapperCallback); } - }) + }); return this; }; diff --git a/lib/transport/binary/protocol28/operation.js b/lib/transport/binary/protocol28/operation.js index d339ce7..da9f66a 100644 --- a/lib/transport/binary/protocol28/operation.js +++ b/lib/transport/binary/protocol28/operation.js @@ -425,14 +425,20 @@ Operation.prototype.consume = function (buffer, offset) { offset += contentLenght; var content = deserializer.deserialize(asString, this.data.transformerFunctions); - var obj = {}; - obj.content = content; - obj.type = 'd'; - obj.cluster = clusterId; - obj.position = clusterPosition; - obj.version = version; - - return [Operation.LIVE_RESULT, token, operation, obj, offset]; + return [ + Operation.LIVE_RESULT, + token, + operation, + { + content: content, + type: 'd', + cluster: clusterId, + position: clusterPosition, + version: version + }, + offset + ]; + } else { offset += 5; // ignore the next integer obj = {}; From b657083edd1ae0e25717034382ced2855cb258ba Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Fri, 12 Jun 2015 16:30:33 +0200 Subject: [PATCH 299/308] fixed serializasion issue when a field value contains multiple backslash characters --- lib/transport/binary/protocol28/serializer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/transport/binary/protocol28/serializer.js b/lib/transport/binary/protocol28/serializer.js index b2e6933..5d9b993 100644 --- a/lib/transport/binary/protocol28/serializer.js +++ b/lib/transport/binary/protocol28/serializer.js @@ -69,7 +69,7 @@ function serializeDocument (document, isMap) { function serializeValue (value) { var type = typeof value; if (type === 'string') { - return '"' + value.replace(/\\/, "\\\\").replace(/"/g, '\\"') + '"'; + return '"' + value.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"'; } else if (type === 'number') { return ~value.toString().indexOf('.') ? value + 'f' : value; From ecb6205b9e1e19d7359b70992a118631ac4185f6 Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Fri, 12 Jun 2015 17:54:24 +0200 Subject: [PATCH 300/308] providing test case for #328 --- test/bugs/328-wrong-backslash-quoting.js | 27 ++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 test/bugs/328-wrong-backslash-quoting.js diff --git a/test/bugs/328-wrong-backslash-quoting.js b/test/bugs/328-wrong-backslash-quoting.js new file mode 100644 index 0000000..0c02bff --- /dev/null +++ b/test/bugs/328-wrong-backslash-quoting.js @@ -0,0 +1,27 @@ +describe("Bug #328: wrong serialization of fields with multiple backslash characters", function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_bug_328') + .bind(this) + .then(function () { + return this.db.class.create('TestSerializeBackslash'); + }); + }); + after(function () { + return DELETE_TEST_DB('testdb_bug_328'); + }); + + it('should insert a document with with correct quotes', function () { + return this.db + .insert() + .into('TestSerializeBackslash') + .set({ + foo: 'kratke, , nadherne, proste bozi, chjo som sa ostrihal :\\\\', + bar: '>' + }) + .one() + .then(function (res) { + res.foo.should.equal('kratke, , nadherne, proste bozi, chjo som sa ostrihal :\\\\'); + }); + }); + +}); From 119fd51de4b8c7fb3ee3d715b8d5717619fd8177 Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Fri, 12 Jun 2015 17:56:06 +0200 Subject: [PATCH 301/308] just typos on a test case --- test/bugs/328-wrong-backslash-quoting.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bugs/328-wrong-backslash-quoting.js b/test/bugs/328-wrong-backslash-quoting.js index 0c02bff..3cca293 100644 --- a/test/bugs/328-wrong-backslash-quoting.js +++ b/test/bugs/328-wrong-backslash-quoting.js @@ -10,7 +10,7 @@ describe("Bug #328: wrong serialization of fields with multiple backslash charac return DELETE_TEST_DB('testdb_bug_328'); }); - it('should insert a document with with correct quotes', function () { + it('should insert a document with correct quotes for backslashes', function () { return this.db .insert() .into('TestSerializeBackslash') From 61b9cae1184f9c427d9021a3c136913851a431f9 Mon Sep 17 00:00:00 2001 From: Ziink Date: Fri, 12 Jun 2015 15:35:59 -0700 Subject: [PATCH 302/308] Escaping / (forward slash) causes SQL errors I couldn't figure out if there was a reason that a leading / (forward slash) is being escaped. However, because of this, trying to save a value that begins with a forward slash causes a lexical error in the SQL. (OreintDB 2.1 rc3). --- lib/utils.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 2cee612..5814aa6 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -145,9 +145,6 @@ exports.escape = function (input) { else if (char === '\\') { chars[i] = '\\\\'; } - else if (char === '/' && i === 0) { - chars[i] = '\\/'; - } else { chars[i] = char; } @@ -374,4 +371,4 @@ exports.requiresParens = function (input) { */ function ArrayLike () {} -exports.ArrayLike = ArrayLike; \ No newline at end of file +exports.ArrayLike = ArrayLike; From 63849b50c8794c5254eae67018d4f6f5a8276eda Mon Sep 17 00:00:00 2001 From: Zeph Date: Mon, 15 Jun 2015 15:33:28 -0700 Subject: [PATCH 303/308] Escaping / (forward slash) causes SQL errors Added tests that inserts and reads records with fields starting with a slash. --- test/bugs/329-pullreq-do-not-escape-slash.js | 22 ++++++++++++++++++++ test/core/utils.js | 6 ------ 2 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 test/bugs/329-pullreq-do-not-escape-slash.js diff --git a/test/bugs/329-pullreq-do-not-escape-slash.js b/test/bugs/329-pullreq-do-not-escape-slash.js new file mode 100644 index 0000000..8a2e796 --- /dev/null +++ b/test/bugs/329-pullreq-do-not-escape-slash.js @@ -0,0 +1,22 @@ +describe('Pull Request #329: Not escaping leading forward slash "(/)"', function () { + before(function () { + return CREATE_TEST_DB(this, 'testdb_PR_329'); + }); + after(function () { + return DELETE_TEST_DB('testdb_PR_329'); + }); + it('should still enable inserting record with field like "/// TE /// ST \\\\\\"', function () { + return this.db.insert().into('v').set({val: '/// TE /// ST \\\\\\'}).one() + .then(function (result) { + result.val.should.equal('/// TE /// ST \\\\\\'); + }); + }); + + it('should still enable where clause with field like "/// TE /// ST \\\\\\"', function () { + return this.db.select().from('v').where({val: '/// TE /// ST \\\\\\'}).one() + .then(function (result) { + result.val.should.equal('/// TE /// ST \\\\\\'); + }); + }); + +}); \ No newline at end of file diff --git a/test/core/utils.js b/test/core/utils.js index 16ce5c9..7486033 100644 --- a/test/core/utils.js +++ b/test/core/utils.js @@ -9,12 +9,6 @@ describe('utils.prepare', function () { it("should prepare SQL statements with parameters", function () { utils.prepare("select from index:foo where key = :key", {key: 123}).should.equal("select from index:foo where key = 123"); }); - it('should prepare SQL statements with parameters with tricky values', function () { - var text = utils.prepare("SELECT * FROM OUser WHERE foo = :foo", { - foo: '/// TE /// ST \\\\\\' - }); - text.should.equal('SELECT * FROM OUser WHERE foo = "\\/// TE /// ST \\\\\\\\\\\\"'); - }); it("should prepare SQL statements with date parameters", function () { var date = new Date(Date.UTC(2015, 0, 5, 22, 7, 5, 435)); utils.prepare("select from index:foo where date = :date", {date: date}).should.equal("select from index:foo where date = date(\"2015-01-05 22:07:05.435\", \"yyyy-MM-dd HH:mm:ss.SSS\", \"UTC\")"); From 5cf9595afe1734224fe5048714c261416865f9fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bartovsk=C3=BD?= Date: Tue, 16 Jun 2015 17:03:48 +0200 Subject: [PATCH 304/308] adds descriptions to nested errors, should fix #324 --- lib/transport/binary/protocol19/operation.js | 4 ++++ lib/transport/binary/protocol26/operation.js | 4 ++++ lib/transport/binary/protocol28/operation.js | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/lib/transport/binary/protocol19/operation.js b/lib/transport/binary/protocol19/operation.js index 33a74e5..809038e 100644 --- a/lib/transport/binary/protocol19/operation.js +++ b/lib/transport/binary/protocol19/operation.js @@ -821,12 +821,16 @@ Operation.prototype.parseError = function (buffer, offset, context, fieldName, r var prev; if (data.hasMore) { prev = new errors.Request(); + prev.type = data.type; + prev.message = data.message err.previous.push(prev); this.stack.pop(); this.stack.push(prev); readItem.call(this); } else { + err.type = data.type; + err.message = data.message; this.readBytes('javaStackTrace', function (data) { this.readOps.push(function (data) { this.stack.pop(); diff --git a/lib/transport/binary/protocol26/operation.js b/lib/transport/binary/protocol26/operation.js index 33a74e5..809038e 100644 --- a/lib/transport/binary/protocol26/operation.js +++ b/lib/transport/binary/protocol26/operation.js @@ -821,12 +821,16 @@ Operation.prototype.parseError = function (buffer, offset, context, fieldName, r var prev; if (data.hasMore) { prev = new errors.Request(); + prev.type = data.type; + prev.message = data.message err.previous.push(prev); this.stack.pop(); this.stack.push(prev); readItem.call(this); } else { + err.type = data.type; + err.message = data.message; this.readBytes('javaStackTrace', function (data) { this.readOps.push(function (data) { this.stack.pop(); diff --git a/lib/transport/binary/protocol28/operation.js b/lib/transport/binary/protocol28/operation.js index da9f66a..9a75b4f 100644 --- a/lib/transport/binary/protocol28/operation.js +++ b/lib/transport/binary/protocol28/operation.js @@ -898,12 +898,16 @@ Operation.prototype.parseError = function (buffer, offset, context, fieldName, r var prev; if (data.hasMore) { prev = new errors.Request(); + prev.type = data.type; + prev.message = data.message; err.previous.push(prev); this.stack.pop(); this.stack.push(prev); readItem.call(this); } else { + err.type = data.type; + err.message = data.message; this.readBytes('javaStackTrace', function (data) { this.readOps.push(function (data) { this.stack.pop(); From 34f6efc494138fd512bf93bbd3064934f3f6b025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bartovsk=C3=BD?= Date: Wed, 17 Jun 2015 09:20:55 +0200 Subject: [PATCH 305/308] missing semicolon added --- lib/transport/binary/protocol19/operation.js | 2 +- lib/transport/binary/protocol26/operation.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/transport/binary/protocol19/operation.js b/lib/transport/binary/protocol19/operation.js index 809038e..c109040 100644 --- a/lib/transport/binary/protocol19/operation.js +++ b/lib/transport/binary/protocol19/operation.js @@ -822,7 +822,7 @@ Operation.prototype.parseError = function (buffer, offset, context, fieldName, r if (data.hasMore) { prev = new errors.Request(); prev.type = data.type; - prev.message = data.message + prev.message = data.message; err.previous.push(prev); this.stack.pop(); this.stack.push(prev); diff --git a/lib/transport/binary/protocol26/operation.js b/lib/transport/binary/protocol26/operation.js index 809038e..c109040 100644 --- a/lib/transport/binary/protocol26/operation.js +++ b/lib/transport/binary/protocol26/operation.js @@ -822,7 +822,7 @@ Operation.prototype.parseError = function (buffer, offset, context, fieldName, r if (data.hasMore) { prev = new errors.Request(); prev.type = data.type; - prev.message = data.message + prev.message = data.message; err.previous.push(prev); this.stack.pop(); this.stack.push(prev); From a093acb67cd669b96d828cae44b2094a57509893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1rio?= Date: Thu, 18 Jun 2015 15:49:20 +0100 Subject: [PATCH 306/308] Prepare 1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 06e504e..37d881f 100755 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "node", "node.js" ], - "version": "1.1.3", + "version": "1.2.0", "author": { "name": "Charles Pick", "email": "charles@codemix.com" From ce73d9d5d6f2036089cb5f1260054ff8f6e887e6 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Thu, 18 Jun 2015 21:04:37 +0100 Subject: [PATCH 307/308] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 38a0b55..d5e14d6 100644 --- a/README.md +++ b/README.md @@ -719,7 +719,7 @@ In 2012, [Gabriel Petrovay](https://github.com/gabipetrovay) created the origina In early 2014, [Giraldo Rosales](https://github.com/nitrog7) made a [whole host of improvements](https://github.com/nitrog7/node-orientdb), including support for orientdb 1.7 and switched to a promise based API. -Later in 2014, codemix refactored the library to make it easier to extend and maintain, and introduced an API similar to [nano](https://github.com/dscape/nano). The result is so different from the original codebase that it warranted its own name and npm package. This also gave us the opportunity to switch to semantic versioning. +Later in 2014, [codemix](http://codemix.com/) refactored the library to make it easier to extend and maintain, and introduced an API similar to [nano](https://github.com/dscape/nano). The result is so different from the original codebase that it warranted its own name and npm package. This also gave us the opportunity to switch to semantic versioning. # Notes for contributors From d2143edb8dc4051ab9b049381656b4c1eced2d35 Mon Sep 17 00:00:00 2001 From: Charles Pick Date: Fri, 26 Jun 2015 17:57:28 +0100 Subject: [PATCH 308/308] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index d5e14d6..46a17d8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +# NOTE: Oriento is deprecated, development continues at https://github.com/orientechnologies/orientjs + + # Oriento Official [orientdb](http://www.orientechnologies.com/orientdb/) driver for node.js. Fast, lightweight, uses the binary protocol.