diff --git a/.eslintrc.json b/.eslintrc.json index 3ecd48f8..d99a9226 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -43,7 +43,59 @@ "named": "always", "asyncArrow": "ignore" }], - "space-infix-ops": ["error", { "int32Hint": true }] + "space-infix-ops": ["error", { "int32Hint": true }], + "jsdoc/check-access": 1, + "jsdoc/check-alignment": 1, + "jsdoc/check-line-alignment": 1, + "jsdoc/check-param-names": 1, + "jsdoc/check-property-names": 1, + "jsdoc/check-tag-names": 1, + "jsdoc/check-types": 1, + "jsdoc/check-values": 1, + "jsdoc/empty-tags": 1, + "jsdoc/multiline-blocks": 1, + "jsdoc/newline-after-description": 1, + "jsdoc/no-multi-asterisks": 1, + "jsdoc/require-param": 1, + "jsdoc/require-param-name": 1, + "jsdoc/require-param-type": 1, + "jsdoc/require-property": 1, + "jsdoc/require-property-name": 1, + "jsdoc/require-property-type": 1, + "jsdoc/require-returns-type": 1, + "jsdoc/require-yields": 1, + "jsdoc/require-yields-check": 1, + "jsdoc/tag-lines": 1, + "jsdoc/valid-types": 1, + "jsdoc/sort-tags": [ + "warn", + { + "tagSequence": [ + "global", + "typedef", + "var", + "name", + "namespace", + "constructor", + "callback", + "event", + "function", + "augments", + "lends", + "type", + "prop", + "param", + "throws", + "fires", + "listens", + "ingroup", + "deprecated", + "see", + "todo", + "ignore" + ] + } + ] }, "globals": { "PR": true, @@ -54,5 +106,7 @@ "after": true, "afterEach": true }, - "plugins": [] + "plugins": [ + "jsdoc" + ] } diff --git a/Gruntfile.js b/Gruntfile.js index 9fab42df..d57fc772 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -107,7 +107,7 @@ module.exports = function (grunt) { eslint: { options: { - configFile: '.eslintrc.json', + overrideConfigFile: '.eslintrc.json', format: 'codeframe', fix: false }, diff --git a/app.js b/app.js index a03c5a2c..08f7e451 100644 --- a/app.js +++ b/app.js @@ -65,24 +65,26 @@ program .option('-s, --snapshot ', 'verify snapshot') .parse(process.argv); +var programOpts = program.opts(); + /** * @property {object} - The default list of configuration options. Can be updated by CLI. * @default 'config.json' */ -var appConfig = require('./helpers/config.js')(program.config); -var genesisblock = require(path.resolve(process.cwd(), (program.genesis || 'genesisBlock.json'))); +var appConfig = require('./helpers/config.js')(programOpts.config); +var genesisblock = require(path.resolve(process.cwd(), (programOpts.genesis || 'genesisBlock.json'))); -if (program.port) { - appConfig.port = program.port; +if (programOpts.port) { + appConfig.port = programOpts.port; } -if (program.address) { - appConfig.address = program.address; +if (programOpts.address) { + appConfig.address = programOpts.address; } -if (program.peers) { - if (typeof program.peers === 'string') { - appConfig.peers.list = program.peers.split(',').map(function (peer) { +if (programOpts.peers) { + if (typeof programOpts.peers === 'string') { + appConfig.peers.list = programOpts.peers.split(',').map(function (peer) { peer = peer.split(':'); return { ip: peer.shift(), @@ -94,13 +96,13 @@ if (program.peers) { } } -if (program.log) { - appConfig.consoleLogLevel = program.log; +if (programOpts.log) { + appConfig.consoleLogLevel = programOpts.log; } -if (program.snapshot) { +if (programOpts.snapshot) { appConfig.loading.snapshot = Math.abs( - Math.floor(program.snapshot) + Math.floor(programOpts.snapshot) ); } @@ -309,7 +311,11 @@ d.run(function () { app.options('*', cors()); var server = require('http').createServer(app); - var io = require('socket.io')(server); + + const { Server } = require('socket.io'); + const io = new Server(server, { + allowEIO3: true + }); var privateKey, certificate, https, https_io; @@ -323,7 +329,9 @@ d.run(function () { ciphers: 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:' + 'ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:' + '!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA' }, app); - https_io = require('socket.io')(https); + https_io = new Server(https, { + allowEIO3: true + }); } cb(null, { diff --git a/config.json b/config.json index 1d59e3fd..9dec46ff 100644 --- a/config.json +++ b/config.json @@ -21,9 +21,7 @@ ] }, "redis": { - "host": "127.0.0.1", - "port": 6379, - "db": 0, + "url": "redis://127.0.0.1:6379/0", "password": null }, "api": { @@ -100,8 +98,8 @@ "options": { "port": 443, "address": "0.0.0.0", - "key": "./ssl/lisk.key", - "cert": "./ssl/lisk.crt" + "key": "./ssl/adamant.key", + "cert": "./ssl/adamant.crt" } }, "dapp": { diff --git a/docs/adm-nodes.jpeg b/docs/adm-nodes.jpeg deleted file mode 100644 index c8f3f8e7..00000000 Binary files a/docs/adm-nodes.jpeg and /dev/null differ diff --git a/docs/conf.json b/docs/conf.json deleted file mode 100644 index d33879a0..00000000 --- a/docs/conf.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "tags": { - "allowUnknownTags": true, - "dictionaries": ["jsdoc"] - }, - "source": { - "include": [ - "./docs/typedef/", - "./app.js", - "./api/http/", - "./helpers/", - "./logic/", - "./modules/", - "./schema/" - ], - "exclude": [ "./public", "./node_modules"] - }, - "plugins": ["plugins/markdown"], - "opts": { - "readme": "docs/intro.md", - "encoding": "utf8", - "destination": "./docs/jsdoc", - "recurse": true - }, - "templates": { - "cleverLinks": false - } -} diff --git a/docs/intro.md b/docs/intro.md deleted file mode 100644 index c6061c5a..00000000 --- a/docs/intro.md +++ /dev/null @@ -1,210 +0,0 @@ -![Lisk Logo](https://lisk.io/i/mediakit/logo_1.png) - -# Lisk JSDoc Source code documentation - -This is an ongoing process -## Best Practices -* To indicate the data type for a `@param` or `@return` tag, put the data type in `{}` brackets: `@param {TheType} paramName` or `@return {TheType}`. -For non-object data, use `number`, `string`, `boolean`, `null`, `undefined`, `Object`, `function`, `Array`. -For particular objects, use the constructor name; this could be a built-in JavaScript class (`Date`, `RegExp`) or custom classes. -* This can be a number or a boolean. `{(number|boolean)}` -* A number or null: `{?number}` -* A number, but never null: `{!number}` -* Variable number of that type `@param {...number} num` -* Optional parameter `@param {number} [foo]` or `@param {number=} foo` -* An optional parameter foo with default value 1. `@param {number} [foo=1]` -* [multiple types and repeatable parameters](http://usejsdoc.org/tags-param.html#multiple-types-and-repeatable-parameters) - -* when documenting an object that is not being used as a `namespace` or `class`, use `@prop {type} name` tags to document its properties (these work like `@param` for function parameters). - -* Use `@name` to tell JSDoc the name of what is being documented, if it is not the same as the name in the code. - -* No need to use `@function` in most cases - JSDoc will assume anything declared as a function is a regular function or method. - -#### Tag order -Tags available should be declared in the following order: -``` js -@global - -@typedef -@var -@name -@namespace -@constructor -@callback -@event -@function - -@augments -@lends - -@type -@prop - -@param -@return - -@throws -@fires -@listens - -@ingroup -@deprecated -@see -@todo -@ignore -``` - -## Syntax -### General -`@description `: -Can omit this tag if description is located at the beginning - -### Membership -`@namespace [[{}] ]`: -an object creates a namespace for its members. - -`@memberof `: -identifies a member symbol that belongs to a parent symbol. -`@memberof! `: - forces JSDoc to document a property of an object that is an instance member of a class. -[Examples](http://usejsdoc.org/tags-memberof.html#examples) - -### Relational -`@implements {typeExpression}`: -a symbol implements an interface. - -`@interface []`: -marks a symbol as an interface that other symbols can implement. - -`@external `: -identifies a class, namespace, or module that is defined outside of the current package. -`@see ` to add a link - -### Entities -`@class [ ]`: -marks a function as being a constructor, meant to be called with the new keyword to return an instance. - -`@function []`: -marks an object as being a function, even though it may not appear to be one to the parser - -`@module [[{}] module:]`: -marks the current file as being its own module. All symbols in the file are assumed to be members of the module unless documented otherwise. - -`@static`: -symbol is contained within a parent and can be accessed without instantiating the parent. - -#### Symbols, Parameters, Variables -`@type {typeName}`: -allows you to provide a type expression identifying the type of value that a symbol may contain, or the type of value returned by a function. -[examples](http://usejsdoc.org/tags-type.html) - -`@typedef [] `: -custom types, particularly if you wish to refer to them repeatedly. - -`@private | @public | @protected`: -* public: JSDoc treats all symbols as public -* proceted: a symbol is only available, or should only be used, within the current module. - -`@inner vs @global`: - -### Beavior -`@param {type} name - description`: -name, type, and description of a function parameter. -[examples](http://usejsdoc.org/tags-param.html) - -`@returns`: - -`@callback`: -callback function that can be passed to other functions. - -`@throws {} free-form description`: -an error that a function might throw. - -### Events -`@event #[event:]`: - -`@listens `: - indicates that a symbol listens for the specified event. - -`@fires #[event:]`: - -`@mixin`: -This provides methods used for event handling - -## Examples - -#### document namespaces - -``` js -/** @namespace */ -hgm = {}; - -/** @namespace */ -hgm.cookie = { - /** describe me */ - get: function (name) { }, - - /** describe me */ - set: function (name, value) { }, - - /** describe me */ - remove: function (name) { } -}; -``` - -#### A namespace with defaults and nested default properties -``` js -/** - * @namespace - * @property {object} defaults - The default values for parties. - * @property {number} defaults.players - The default number of players. - * @property {string} defaults.level - The default level for the party. - * @property {object} defaults.treasure - The default treasure. - * @property {number} defaults.treasure.gold - How much gold the party starts with. - */ -var config = { - defaults: { - players: 1, - level: 'beginner', - treasure: { - gold: 0 - } - } -}; -``` - -#### Documenting large apps, group modules into categories - -> This is to represent and explore functional behavior. - -Example: Account module is composed by: -* api -* modules -* logic -* schema -* helpers - -``` js -/**Parent module -* @module package-name -*/ - -/**Child of the parent module -* @namespace firstChild -* @memberof module:package-name -*/ -``` - -#### ToDO - -- [ ] JSDoc template -- [ ] Markdown plugin -- [ ] Use categories tag: `@categories` -- [ ] Patterns examples -- [ ] More Lisk examples: callback, throws, class, nested objects -- [ ] JSDoc tutorials for best practices -- [ ] Callback patterns - * node style: cb(err, data); - `app.js` - * setImmediate style: return setImmediate(cb, null, data); - `modules/blocks.js` - * next style: cb(); - `app.js` \ No newline at end of file diff --git a/docs/typedef/components.js b/docs/typedef/components.js deleted file mode 100644 index ceffce25..00000000 --- a/docs/typedef/components.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Handles module content - * @typedef {Object} modules - * @property {Accounts} accounts - * @property {Blocks} blocks - * @property {Crypto} crypto - * @property {DApps} dapps - * @property {Delegates} delegates - * @property {Loader} loader - * @property {Multisignatures} multisignatures - * @property {Peers} peers - * @property {Rounds} rounds - * @property {Server} server - * @property {Signatures} signatures - * @property {Sql} sql - * @property {System} system - * @property {Transactions} transactions - * @property {transport} Transport - */ diff --git a/docs/typedef/crypto.js b/docs/typedef/crypto.js deleted file mode 100644 index d97a2b00..00000000 --- a/docs/typedef/crypto.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Public Key obtained from sodium.crypto_sign_seed_keypair(hash) - * @typedef {string} publicKey - */ -/** - * Private Key obtained from sodium.crypto_sign_seed_keypair(hash) - * @typedef {string} privateKey - */ -/** - * Address obtained from publicKey using bignum function with 'U' in the beginning. - * @typedef {string} address - */ -/** - * Crypto hash hex - * @typedef {string} hash - */ -/** - * publicKey, privateKey - * @typedef {Object} keypair - */ diff --git a/docs/typedef/modules/accounts.js b/docs/typedef/modules/accounts.js deleted file mode 100644 index cb9f06c4..00000000 --- a/docs/typedef/modules/accounts.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Functional Module `accounts` - * - * @module accounts - */ diff --git a/docs/typedef/modules/blocks.js b/docs/typedef/modules/blocks.js deleted file mode 100644 index 9e76b5f0..00000000 --- a/docs/typedef/modules/blocks.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Functional Module `blocks` - * - * @module blocks - */ diff --git a/docs/typedef/modules/dapps.js b/docs/typedef/modules/dapps.js deleted file mode 100644 index 6e44a3bb..00000000 --- a/docs/typedef/modules/dapps.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Functional Module `dapps` - * - * @module dapps - */ diff --git a/docs/typedef/modules/delegates.js b/docs/typedef/modules/delegates.js deleted file mode 100644 index 092e0dd6..00000000 --- a/docs/typedef/modules/delegates.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Functional Module `delegates` - * - * @module delegates - */ diff --git a/docs/typedef/modules/helpers.js b/docs/typedef/modules/helpers.js deleted file mode 100644 index aaf05480..00000000 --- a/docs/typedef/modules/helpers.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Core Module `helpers` - * - * @module helpers - */ diff --git a/docs/typedef/modules/loader.js b/docs/typedef/modules/loader.js deleted file mode 100644 index d8afa350..00000000 --- a/docs/typedef/modules/loader.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Functional Module `loader` - * - * @module loader - */ diff --git a/docs/typedef/modules/multisignatures.js b/docs/typedef/modules/multisignatures.js deleted file mode 100644 index 2bf920ba..00000000 --- a/docs/typedef/modules/multisignatures.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Functional Module `multisignatures` - * - * @module multisignatures - */ diff --git a/docs/typedef/modules/peers.js b/docs/typedef/modules/peers.js deleted file mode 100644 index aab2edf5..00000000 --- a/docs/typedef/modules/peers.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Functional Module `peers` - * - * @module peers - */ diff --git a/docs/typedef/modules/rounds.js b/docs/typedef/modules/rounds.js deleted file mode 100644 index d997442d..00000000 --- a/docs/typedef/modules/rounds.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Core Module `rounds` - * - * @module rounds - */ diff --git a/docs/typedef/modules/server.js b/docs/typedef/modules/server.js deleted file mode 100644 index c84af8c3..00000000 --- a/docs/typedef/modules/server.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Functional Module `server` - * - * @module server - */ diff --git a/docs/typedef/modules/signatures.js b/docs/typedef/modules/signatures.js deleted file mode 100644 index c0224eee..00000000 --- a/docs/typedef/modules/signatures.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Functional Module `signatures` - * - * @module signatures - */ diff --git a/docs/typedef/modules/transactions.js b/docs/typedef/modules/transactions.js deleted file mode 100644 index 90d356f8..00000000 --- a/docs/typedef/modules/transactions.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Functional Module `transactions` - * - * @module transactions - */ diff --git a/docs/typedef/modules/transport.js b/docs/typedef/modules/transport.js deleted file mode 100644 index 543d205b..00000000 --- a/docs/typedef/modules/transport.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Functional Module `transport` - * - * @module transport - */ diff --git a/docs/typedef/namespace.js b/docs/typedef/namespace.js deleted file mode 100644 index 8f28110c..00000000 --- a/docs/typedef/namespace.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * General object to define private functions in different main functions. - * @namespace __private - * @private - */ diff --git a/helpers/RoundChanges.js b/helpers/RoundChanges.js index ad43c928..fda57ca9 100644 --- a/helpers/RoundChanges.js +++ b/helpers/RoundChanges.js @@ -21,11 +21,16 @@ function RoundChanges (scope) { if (exceptions.rounds[scope.round]) { // Apply rewards factor this.roundRewards.forEach(function (reward, index) { - this.roundRewards[index] = new bignum(reward.toPrecision(15)).times(exceptions.rounds[scope.round].rewards_factor).floor(); + this.roundRewards[index] = new bignum(reward.toPrecision(15)) + .times(exceptions.rounds[scope.round].rewards_factor) + .integerValue(bignum.ROUND_FLOOR); }.bind(this)); // Apply fees factor and bonus - this.roundFees = new bignum(this.roundFees.toPrecision(15)).times(exceptions.rounds[scope.round].fees_factor).plus(exceptions.rounds[scope.round].fees_bonus).floor(); + this.roundFees = new bignum(this.roundFees.toPrecision(15)) + .times(exceptions.rounds[scope.round].fees_factor) + .plus(exceptions.rounds[scope.round].fees_bonus) + .integerValue(bignum.ROUND_FLOOR); } } @@ -39,15 +44,15 @@ function RoundChanges (scope) { * @return {Object} Contains fees, feesRemaining, rewards, balance */ RoundChanges.prototype.at = function (index) { - var fees = new bignum(this.roundFees.toPrecision(15)).dividedBy(slots.delegates).floor(); + var fees = new bignum(this.roundFees.toPrecision(15)).dividedBy(slots.delegates).integerValue(bignum.ROUND_FLOOR); var feesRemaining = new bignum(this.roundFees.toPrecision(15)).minus(fees.times(slots.delegates)); - var rewards = new bignum(this.roundRewards[index].toPrecision(15)).floor() || 0; + var rewards = new bignum(this.roundRewards[index].toPrecision(15)).integerValue(bignum.ROUND_FLOOR) || 0; return { fees: Number(fees.toFixed()), feesRemaining: Number(feesRemaining.toFixed()), rewards: Number(rewards.toFixed()), - balance: Number(fees.add(rewards).toFixed()) + balance: Number(fees.plus(rewards).toFixed()) }; }; diff --git a/helpers/cache.js b/helpers/cache.js index 45c83f16..4f063bea 100644 --- a/helpers/cache.js +++ b/helpers/cache.js @@ -20,23 +20,25 @@ module.exports.connect = function (cacheEnabled, config, logger, cb) { if (config.password === null) { delete config.password; } - var client = redis.createClient(config); - client.on('ready', function () { - logger.info('App connected with redis server'); + var client = redis.createClient(config); - if (!isRedisLoaded) { - isRedisLoaded = true; - return cb(null, { cacheEnabled: cacheEnabled, client: client }); - } - }); + client.connect() + .then(() => { + logger.info('App connected with redis server'); - client.on('error', function (err) { - logger.error('Redis:', err); - // Only throw an error if cache was enabled in config but were unable to load it properly - if (!isRedisLoaded) { - isRedisLoaded = true; - return cb(null, { cacheEnabled: cacheEnabled, client: null }); - } - }); + if (!isRedisLoaded) { + isRedisLoaded = true; + client.ready = isRedisLoaded; + return cb(null, { cacheEnabled: cacheEnabled, client: client }); + } + }) + .catch((err) => { + logger.error('Redis:', err); + // Only throw an error if cache was enabled in config but were unable to load it properly + if (!isRedisLoaded) { + isRedisLoaded = true; + return cb(null, { cacheEnabled: cacheEnabled, client: null }); + } + }); }; diff --git a/helpers/database.js b/helpers/database.js index 9cbe75e3..a56dac68 100644 --- a/helpers/database.js +++ b/helpers/database.js @@ -91,7 +91,7 @@ function Migrator (pgp, db) { (file.id && file.name) && fs.statSync(file.path).isFile() && /\.sql$/.test(file.path) ); }).forEach(function (file) { - if (!lastMigration || file.id.greaterThan(lastMigration.id)) { + if (!lastMigration || file.id.isGreaterThan(lastMigration.id)) { pendingMigrations.push(file); } }); @@ -190,10 +190,10 @@ module.exports.connect = function (config, logger, cb) { monitor.attach(pgOptions, config.logEvents); monitor.setTheme('matrix'); - monitor.log = function (msg, info) { + monitor.setLog(function (msg, info) { logger.log(info.event, info.text); info.display = false; - }; + }); config.user = config.user || process.env.USER; diff --git a/helpers/inserts.js b/helpers/inserts.js index abe04a8b..42517125 100644 --- a/helpers/inserts.js +++ b/helpers/inserts.js @@ -52,9 +52,9 @@ function Inserts (record, values, concat) { return pgp.as.format('INSERT INTO $1~($2^) VALUES $3^', [record.table, fields, values]); }; - this._rawDBType = true; + this.rawType = true; - this.formatDBType = function () { + this.toPostgres = function () { return values.map(function (v) { return '(' + pgp.as.format(self._template, v) + ')'; }).join(','); diff --git a/helpers/request-limiter.js b/helpers/request-limiter.js index f5548677..0932bd5f 100644 --- a/helpers/request-limiter.js +++ b/helpers/request-limiter.js @@ -1,6 +1,7 @@ 'use strict'; -var RateLimit = require('express-rate-limit'); +var rateLimit = require('express-rate-limit'); +var slowDown = require('express-slow-down'); var defaults = { max: 0, // Disabled @@ -54,8 +55,8 @@ module.exports = function (app, config) { }; limits.middleware = { - client: app.use('/api/', new RateLimit(limits.client)), - peer: app.use('/peer/', new RateLimit(limits.peer)) + client: app.use('/api/', rateLimit(limits.client), slowDown(limits.client)), + peer: app.use('/peer/', rateLimit(limits.peer), slowDown(limits.peer)) }; return limits; diff --git a/legacy/json-sql/.gitignore b/legacy/json-sql/.gitignore new file mode 100644 index 00000000..c2658d7d --- /dev/null +++ b/legacy/json-sql/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/legacy/json-sql/.jshintrc b/legacy/json-sql/.jshintrc new file mode 100644 index 00000000..fe7c5b09 --- /dev/null +++ b/legacy/json-sql/.jshintrc @@ -0,0 +1,19 @@ +{ + "node": true, + "freeze": true, + "maxlen": 100, + "smarttabs": true, + "indent": 1, + "quotmark": "single", + "strict": true, + "globalstrict": true, + // use es3 to get 'extra comma' at object literal error + "es3": true, + "undef": true, + "unused": true, + "immed": true, + "eqeqeq": true, + "globals": { + "JSON": false + } +} diff --git a/legacy/json-sql/.npmignore b/legacy/json-sql/.npmignore new file mode 100644 index 00000000..3796175f --- /dev/null +++ b/legacy/json-sql/.npmignore @@ -0,0 +1,2 @@ +.npmignore +test \ No newline at end of file diff --git a/legacy/json-sql/LICENSE b/legacy/json-sql/LICENSE new file mode 100644 index 00000000..2ac98523 --- /dev/null +++ b/legacy/json-sql/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2013-2014 Oleg Korobenko + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/legacy/json-sql/README.md b/legacy/json-sql/README.md new file mode 100644 index 00000000..7891eaed --- /dev/null +++ b/legacy/json-sql/README.md @@ -0,0 +1,145 @@ +# JSON-SQL + +Library for mapping mongo-style query objects to SQL queries. + +## Quick Start + +Install it with NPM or add it to your package.json: + +``` bash +$ npm install json-sql +``` + +Then: + +``` js +var jsonSql = require('json-sql')(); + +var sql = jsonSql.build({ + type: 'select', + table: 'users', + fields: ['name', 'age'], + condition: {name: 'Max', id: 6} +}); + +sql.query +// sql string: +// select name, age from users where name = $p1 && id = 6; + +sql.values +// hash of values: +// { p1: 'Max' } +``` + +## Documentation + +Documentation is available at the [./docs directory](./docs). + +## Examples + +__Select with join:__ + +``` js +var sql = jsonSql.build({ + type: 'select', + table: 'users', + join: { + documents: { + on: {'user.id': 'documents.userId'} + } + } +}); + +sql.query +// select * from users join documents on user.id = documents.userId; + +sql.values +// {} +``` + +__Insert:__ + +``` js +var sql = jsonSql.build({ + type: 'insert', + table: 'users', + values: { + name: 'John', + lastname: 'Snow', + age: 24, + gender: 'male' + } +}); + +sql.query +// insert into users (name, lastname, age, gender) values ($p1, $p2, 24, $p3); + +sql.values +// { p1: 'John', p2: 'Snow', p3: 'male' } +``` + +__Update:__ + +``` js +var sql = jsonSql.build({ + type: 'update', + table: 'users', + condition: { + id: 5 + }, + modifier: { + role: 'admin' + age: 33 + } +}); + +sql.query +// update users set role = $p1, age = 33 where id = 5; + +sql.values +// { p1: 'admin' } +``` + +__Remove:__ + +``` js +var sql = jsonSql.build({ + type: 'remove', + table: 'users', + condition: { + id: 5 + } +}); + +sql.query +// delete from users where id = 5; + +sql.values +// {} +``` + +For more examples, take a look at the [./docs directory](./docs) or [./tests directory](./tests). + +## Tests + +Clone repository from github, `cd` into cloned dir and install dev dependencies: + +``` bash +$ npm install +``` + +Then run tests with command: + +``` bash +$ gulp test +``` + +Or run tests coverage with command: + +``` bash +$ gulp coverage +``` + +## License + +[MIT](./LICENSE) diff --git a/legacy/json-sql/docs/README.md b/legacy/json-sql/docs/README.md new file mode 100644 index 00000000..8752f703 --- /dev/null +++ b/legacy/json-sql/docs/README.md @@ -0,0 +1,1180 @@ +# Documentation + +## Table of contents + +* [API](#api) + - [Initialization](#initialization) + - __[build(query)](#buildquery)__ + - [configure(options)](#configureoptions) + - [setDialect(name)](#setdialectname) +* __[Queries](#queries)__ + - [type: 'create'](#type-create) + - [type: 'select'](#type-select) + - [type: 'insert'](#type-insert) + - [type: 'update'](#type-update) + - [type: 'remove'](#type-remove) + - [type: 'union' | 'intersect' | 'except'](#type-union--intersect--except) +* __[Blocks](#blocks)__ +* __[Condition operators](#condition-operators)__ + +--- + +## API + +### Initialization + +To create new instance of json-sql builder you can use factory function: + +``` js +var jsonSql = require('json-sql')(options); +``` + +or create instance by class constructor: + +``` js +var jsonSql = new (require('json-sql').Builder)(options); +``` + +`options` are similar to [configure method options](#available-options). + +--- + +### build(query) + +Create sql query from mongo-style query object. + +`query` is a json object that has required property `type` and a set of query-specific properties. `type` property determines the type of query. List of available values of `type` property you can see at [Queries section](#queries). + +Returns object with properties: + +| Property | Description | +| -------- | ----------- | +| `query` | SQL query string | +| `value` | Array or object with values.
Exists only if `separatedValues = true`. | +| `prefixValues()` | Method to get values with `valuesPrefix`.
Exists only if `separatedValues = true`. | +| `getValuesArray()` | Method to get values as array.
Exists only if `separatedValues = true`. | +| `getValuesObject()` | Method to get values as object.
Exists only if `separatedValues = true`. | + +--- + +### configure(options) + +Set options of json-sql builder instance. + +#### Available options + +| Option name | Default value | Description | +| ----------- | ------------- | ----------- | +| `separatedValues` | `true` | If `true` - create placeholder for each string value and put it value to result `values`.
If `false` - put string values into sql query without placeholder (potential threat of sql injection). | +| `namedValues` | `true` | If `true` - create hash of values with placeholders p1, p2, ...
If `false` - put all values into array.
Option is used if `separatedValues = true`. | +| `valuesPrefix` | `'$'` | Prefix for values placeholders
Option is used if `namedValues = true`. | +| `dialect` | `'base'` | Active dialect. See setDialect for dialects list. | +| `wrappedIdentifiers` | `true` | If `true` - wrap all identifiers with dialect wrapper (name -> "name"). | + +--- + +### setDialect(name) + +Set active dialect, name can has value `'base'`, `'mssql'`, `'mysql'`, `'postrgresql'` or `'sqlite'`. + +--- + +## Queries + +### type: 'create' + +>[ [tableFields](#tableFields) ]
+>[ [table](#table) ]
+>[ [foreignKeys](#foreignKeys) ] + +__Example:__ + +``` js +var sql = jsonSql.build({ + type: 'create', + table: 'users', + tableFields: [ + { + name: "name", + type: "String", + length: 16, + not_null: true, + unique: true, + default: "empty" + } + ] +}); + +sql.query +// create table if not exists "users"("name" varchar(16) NOT NULL default "empty" UNIQUE); +``` + +--- + +### type: 'select' + +>[ [with](#with-withrecursive) | [withRecursive](#with-withrecursive) ]
+>[ [distinct](#distinct) ]
+>[ [fields](#fields) ]
+>[table](#table) | [query](#query) | [select](#select) | [expression](#expression)
+>[ [alias](#alias) ]
+>[ [join](#join) ]
+>[ [condition](#condition) ]
+>[ [group](#group) ]
+>[ [sort](#sort) ]
+>[ [limit](#limit) ]
+>[ [offset](#offset) ] + +__Example:__ + +``` js +var sql = jsonSql.build({ + type: 'select', + fields: ['a', 'b'] + table: 'table' +}); + +sql.query +// select "a", "b" from "table"; +``` + +If `fields` is not specified in query, result fields is `*` (all columns of the selected rows). + +__Example:__ + +``` js +var sql = jsonSql.build({ + type: 'select', + table: 'table' +}); + +sql.query +// select * from "table"; +``` + +--- + +### type: 'insert' + +>[ [with](#with-withrecursive) | [withRecursive](#with-withrecursive) ]
+>[ [or](#or) ]
+>[table](#table)
+>[values](#values)
+>[ [condition](#condition) ]
+>[ [returning](#returning) ] + +__Example:__ + +``` js +var sql = jsonSql.build({ + type: 'insert', + table: 'table', + values: {a: 4} +}); + +sql.query +// insert into "table" ("a") values (4); +``` + +--- + +### type: 'update' + +>[ [with](#with-withrecursive) | [withRecursive](#with-withrecursive) ]
+>[ [or](#or) ]
+>[table](#table)
+>[modifier](#modifier)
+>[ [condition](#condition) ]
+>[ [returning](#returning) ] + +__Example:__ + +``` js +var sql = jsonSql.build({ + type: 'update', + table: 'table', + modifier: {a: 5} +}); + +sql.query +// update "table" set a = 5; +``` + +--- + +### type: 'remove' + +>[ [with](#with-withrecursive) | [withRecursive](#with-withrecursive) ]
+>[table](#table)
+>[ [condition](#condition) ]
+>[ [returning](#returning) ] + +__Example:__ + +``` js +var sql = jsonSql.build({ + type: 'remove', + table: 'table' +}); + +sql.query +// delete from "table"; +``` + +--- + +### type: 'union' | 'intersect' | 'except' + +>[ [all](#all) ]
+>[ [with](#with-withrecursive) | [withRecursive](#with-withrecursive) ]
+>[queries](#queries)
+>[ [sort](#sort) ]
+>[ [limit](#limit) ]
+>[ [offset](#offset) ] + +__`type: 'union'` example:__ + +``` js +var sql = jsonSql.build({ + type: 'union', + queries: [ + {type: 'select', table: 'table1'}, + {type: 'select', table: 'table2'} + ] +}); + +sql.query +// (select * from "table1") union (select * from "table2"); +``` + +__`type: 'intersect'` example:__ + +``` js +var sql = jsonSql.build({ + type: 'intersect', + queries: [ + {type: 'select', table: 'table1'}, + {type: 'select', table: 'table2'} + ] +}); + +sql.query +// (select * from "table1") intersect (select * from "table2"); +``` + +__`type: 'except'` example:__ + +``` js +var sql = jsonSql.build({ + type: 'except', + queries: [ + {type: 'select', table: 'table1'}, + {type: 'select', table: 'table2'} + ] +}); + +sql.query +// (select * from "table1") except (select * from "table2"); +``` + +--- + +## Blocks + +Blocks are small chunks of query. + +### with, withRecursive + +Should be an `array` or an `object`. + +If value is an `array`, each item of array should be an `object` and should conform the scheme: + +>name
+>[ [fields](#fields) ]
+>[query](#query) | [select](#select) | [expression](#expression) + +__Example:__ + +``` js +var sql = jsonSql.build({ + 'with': [{ + name: 'table', + select: {table: 'withTable'} + }], + table: 'table' +}); + +sql.query +// with "table" as (select * from "withTable") select * from "table"; +``` + +If value is an `object`, keys of object interpret as names and each value should be an `object` and should conform the scheme: + +>[ name ]
+>[ [fields](#fields) ]
+>[query](#query) | [select](#select) | [expression](#expression) + +__Example:__ + +``` js +var sql = jsonSql.build({ + 'with': { + table: { + select: {table: 'withTable'} + } + }, + table: 'table' +}); + +sql.query +// with "table" as (select * from "withTable") select * from "table"; +``` + +--- + +### distinct + +Should be a `boolean`: + +``` +distinct: true +``` + +__Example:__ + +``` js +var sql = jsonSql.build({ + distinct: true, + table: 'table' +}); + +sql.query +// select distinct * from "table"; +``` + +--- + +### tableFields + +Should be an `array` + +Contains array of table`s fields + +--- + +### foreignKeys + +Should be an `array` + +__Example:__ + +``` js +var result = jsonSql.build({ + type: 'create', + table: 'users', + tableFields: [ + { + name: "name", + type: "String", + length: 16, + not_null: true, + primary_key: true + }, + { + name: "age", + type: "Number", + not_null: true + } + ], + foreignKeys: [ + { + field: "name", + table: "person", + table_field: "id" + } + ] +}); + +sql.query +// create table "users"(name varchar(16) NOT NULL PRIMARY KEY,age int NOT NULL, FOREIGN KEY (name) REFERENCES person(id)); +``` + +--- + +### fields + +Should be an `array` or an `object`. + +If value is an `array`, each item interprets as [term block](#term). + +__Example:__ + +``` js +var sql = jsonSql.build({ + fields: [ + 'a', + {b: 'c'}, + {table: 'd', name: 'e', alias: 'f'}, + ['g'] + ], + table: 'table' +}); + +sql.query +// select "a", "b" as "c", "d"."e" as "f", "g" from "table"; +``` + +If value is an `object`, keys of object interpret as field names and each value should be an `object` and should conform the scheme: + +>[ name ]
+>[ [table](#table) ]
+>[ cast ]
+>[ [alias](#alias) ] + +__Example:__ + +``` js +var sql = jsonSql.build({ + fields: { + a: 'b', + d: {table: 'c', alias: 'e'} + }, + table: 'table' +}); + +sql.query +// select "a" as "b", "c"."d" as "e" from "table"; +``` + +--- + +### term + +Should be: +* a `string` - interprets as field name; +* another simple type or an `array` - interprets as value; +* an `object` - should conform the scheme: + +>[query](#query) | [select](#select) | [field](#field) | [value](#value) | [func](#func) | [expression](#expression)
+>[ cast ]
+>[ [alias](#alias) ] + +--- + +### field + +Should be a `string` or an `object`. + +If value is a `string`: + +``` +field: 'fieldName' +``` + +__Example:__ + +``` js +var sql = jsonSql.build({ + fields: [{field: 'a'}], + table: 'table' +}); + +sql.query +// select "a" from "table"; +``` + +If value is an `object` it should conform the scheme: + +>name
+>[ [table](#table) ] + +__Example:__ + +``` js +var sql = jsonSql.build({ + fields: [{field: {name: 'a', table: 'table'}}], + table: 'table' +}); + +sql.query +// select "table"."a" from "table"; +``` + +--- + +### value + +Can have any type. + +__Example:__ + +``` js +var sql = jsonSql.build({ + fields: [ + {value: 5}, + {value: 'test'} + ], + table: 'table' +}); + +sql.query +// select 5, $p1 from "table"; + +sql.values +// {p1: 'test'} +``` + +--- + +### table + +Should be a `string`: + +``` +table: 'tableName' +``` + +__Example:__ + +``` js +var sql = jsonSql.build({ + table: 'table' +}); + +sql.query +// select * from "table"; +``` + +--- + +### query + +Should be an `object`. Value interprets as sub-query and process recursively with [build(query)](#buildquery) method. + +__Example:__ + +``` js +var sql = jsonSql.build({ + query: {type: 'select', table: 'table'} +}); + +sql.query +// select * from (select * from "table"); +``` + +--- + +### select + +Should be an `object`. Value interprets as sub-select and process recursively with [build(query)](#buildquery) method. + +__Example:__ + +``` js +var sql = jsonSql.build({ + select: {table: 'table'} +}); + +sql.query +// select * from (select * from "table"); +``` + +--- + +### func + +Should be a `string` or an `object`. + +If value is a `string`: + +``` +func: 'random' +``` + +__Example:__ + +``` js +var sql = jsonSql.build({ + fields: [{func: 'random'}], + table: 'table' +}); + +sql.query +// select random() from "table"; +``` + +If value is an `object` it should conform the scheme: + +>name
+>[ args ] + +where `name` is a `string` name of function, `args` is an `array` that contains it arguments. + +__Example:__ + +``` js +var sql = jsonSql.build({ + fields: [{ + func: { + name: 'sum', + args: [{field: 'a'}] + } + }], + table: 'table' +}); + +sql.query +// select sum("a") from table; +``` + +--- + +### expression + +Should be a `string` or an `object`. + +If value is a `string`: + +``` +expression: 'random()' +``` + +__Example:__ + +``` js +var sql = jsonSql.build({ + expression: 'generate_series(2, 4)' +}); + +sql.query +// select * from generate_series(2, 4); +``` + +If value is an `object` it should conform the scheme: + +>pattern
+>[ values ] + +where `pattern` is a `string` pattern with placeholders `{placeholderName}`, `values` is a hash that contains values for each `placeholderName`. + +__Example:__ + +``` js +var sql = jsonSql.build({ + expression: { + pattern: 'generate_series({start}, {stop})', + values: {start: 2, stop: 4} + } +}); + +sql.query +// select * from generate_series(2, 4); +``` + +--- + +### alias + +Should be a `string` or an `object`. + +If value is a `string`: + +``` +alias: 'aliasName' +``` + +__Example:__ + +``` js +var sql = jsonSql.build({ + table: 'table', + alias: 'alias' +}); + +sql.query +// select * from "table" as "alias"; +``` + +If value is an `object` it should conform the scheme: + +>name
+>[ columns ] + +__Example:__ + +``` js +var sql = jsonSql.build({ + table: 'table', + alias: {name: 'alias'} +}); + +sql.query +// select * from "table" as "alias"; +``` + +--- + +### join + +Should be an `array` or an `object`. + +If value is an `array`, each item of array should be an `object` and should conform the scheme: + +>[ type ]
+>[table](#table) | [query](#query) | [select](#select) | [expression](#expression)
+>[ [alias](#alias) ]
+>[ on ] + +__Example:__ + +``` js +var sql = jsonSql.build({ + table: 'table', + join: [{ + type: 'right', + table: 'joinTable', + on: {'table.a': 'joinTable.b'} + }] +}); + +sql.query +// select * from "table" right join "joinTable" on "table"."a" = "joinTable"."b"; +``` + +If value is an `object`, keys of object interpret as table names and each value should be an `object` and should conform the scheme: + +>[ type ]
+>[ [table](#table) | [query](#query) | [select](#select) | [expression](#expression) ]
+>[ [alias](#alias) ]
+>[ on ] + +__Example:__ + +``` js +var sql = jsonSql.build({ + table: 'table', + join: { + joinTable: { + type: 'inner', + on: {'table.a': 'joinTable.b'} + } + }] +}); + +sql.query +// select * from "table" inner join "joinTable" on "table"."a" = "joinTable"."b"; +``` + +__Join with sub-select example:__ + +``` js +var sql = jsonSql.build({ + table: 'table', + join: [{ + select: {table: 'joinTable'}, + alias: 'joinTable', + on: {'table.a': 'joinTable.b'} + }] +}); + +sql.query +// select * from "table" join (select * from "joinTable") as "joinTable" on "table"."a" = "joinTable"."b"; +``` + +--- + +### condition + +Should be an `array` or an `object`. + +__`array` example:__ + +``` js +var sql = jsonSql.build({ + table: 'table', + condition: [ + {a: {$gt: 1}}, + {b: {$lt: 10}} + ] +}); + +sql.query +// select * from "table" where "a" > 1 and "b" < 10; +``` + +__`object` example:__ + +``` js +var sql = jsonSql.build({ + table: 'table', + condition: { + a: {$gt: 1}, + b: {$lt: 10} + } +}); + +sql.query +// select * from "table" where "a" > 1 and "b" < 10; +``` + +--- + +### group + +Should be a `string` or an `array`. + +If value is a `string`: + +``` +group: 'fieldName' +``` + +__Example:__ + +``` js +var sql = jsonSql.build({ + table: 'table', + group: 'a' +}); + +sql.query +// select * from "table" group by "a"; +``` + +If value is an `array`: + +``` +group: ['fieldName1', 'fieldName2'] +``` + +__Example:__ + +``` js +var sql = jsonSql.build({ + table: 'table', + group: ['a', 'b'] +}); + +sql.query +// select * from "table" group by "a", "b"; +``` + +--- + +### sort + +Should be a `string`, an `array` or an `object`. + +If value is a `string`: + +``` +sort: 'fieldName' +``` + +__Example:__ + +``` js +var sql = jsonSql.build({ + table: 'table', + sort: 'a' +}); + +sql.query +// select * from "table" order by "a"; +``` + +If value is an `array`: + +``` +sort: ['fieldName1', 'fieldName2'] +``` + +__Example:__ + +``` js +var sql = jsonSql.build({ + table: 'table', + sort: ['a', 'b'] +}); + +sql.query +// select * from "table" order by "a", "b"; +``` + +If value is an `object`: + +``` +sort: { + fieldName1: 1, + fieldName2: -1 +} +``` + +__Example__: + +``` js +var sql = jsonSql.build({ + table: 'table', + sort: {a: 1, b: -1} +}); + +sql.query +// select * from "table" order by "a" asc, "b" desc; +``` + +--- + +### limit + +Should be a `number`. + +``` +limit: limitValue +``` + +__Example:__ + +``` js +var sql = jsonSql.build({ + table: 'table', + limit: 5 +}); + +sql.query +// select * from "table" limit 5; +``` + +--- + +### offset + +Should be a `number`. + +``` +offset: offsetValue +``` + +__Example:__ + +``` js +var sql = jsonSql.build({ + table: 'table', + offset: 5 +}); + +sql.query +// select * from "table" offset 5; +``` + +--- + +### or + +Should be a `string`. + +Available values: 'rollback', 'abort', 'replace', 'fail', 'ignore'. + +``` +or: 'orValue' +``` + +__Example:__ + +``` js +var sql = jsonSql.build({ + type: 'insert', + or: 'replace', + table: 'table', + values: {a: 5} +}); + +sql.query +// insert or replace into "table" ("a") values (5); +``` + +--- + +### values + +Should be an `array` or an `object`. + +If value is an `array`, each item should be an `object` and interprets as single inserted row where keys are field names and corresponding values are field values. + +__Example:__ + +``` js +var sql = jsonSql.build({ + type: 'insert', + table: 'table', + values: [ + {a: 5, b: 'text1'}, + {a: 6, b: 'text2'} + ] +}); + +sql.query +// insert into "table" ("a", "b") values (5, $p1), (6, $p2); + +sql.values +// {p1: 'text1', p2: 'text2'} +``` + +If value is an `object`, it interprets as single inserted row where keys are field names and corresponding values are field values. + +__Example:__ + +``` js +var sql = jsonSql.build({ + type: 'insert', + table: 'table', + values: {a: 5, b: 'text'} +}); + +sql.query +// insert into "table" ("a", "b") values (5, $p1); + +sql.values +// {p1: 'text'} +``` + +Also you can specify fields array. If there no key in value object it value is `null`. + +__Example:__ + +``` js +var sql = jsonSql.build({ + type: 'insert', + table: 'table', + fields: ['a', 'b', 'c'], + values: {c: 'text', b: 5} +}); + +sql.query +// insert into "table" ("a", "b", "c") values (null, 5, $p1); + +sql.values +// {p1: 'text'} +``` + +--- + +### modifier + +Should be an `object`. + +You can specify modifier operator. +Available operators: `$set`, `$inc`, `$dec`, `$mul`, `$div`, `$default`. + +__Example:__ + +``` js +var sql = jsonSql.build({ + type: 'update', + table: 'table', + modifier: { + $set: {a: 5}, + $default: {b: true}, + $inc: {c: 10} + } +}); + +sql.query +// update "table" set "a" = 5, "b" = default, "c" = "c" + 10; +``` + +If modifier operator is not specified it uses default operator `$set`. + +__Example:__ + +``` js +var sql = jsonSql.build({ + type: 'update', + table: 'table', + modifier: {a: 5} +}); + +sql.query +// update "table" set "a" = 5; +``` + +--- + +### returning + +Format is similar to [fields](#fields) block. + +__Example:__ + +``` js +var sql = jsonSql.build({ + type: 'insert', + table: 'table', + values: {a: 5}, + returning: ['a'] +}); + +sql.query +// insert into "table" ("a") values (5) returning "a"; +``` + +--- + +### all + +Should be a `boolean`. + +__Example:__ + +``` js +var sql = jsonSql.build({ + type: 'union', + all: true, + queries: [ + {type: 'select', table: 'table1'}, + {type: 'select', table: 'table2'} + ] +}); + +sql.query +// (select * from "table1") union all (select * from "table2"); +``` + +--- + +### queries + +Should be an `array` with minimum 2 items. Each item interprets as sub-query and process recursively with [build(query)](#buildquery) method. + +__Example:__ + +``` js +var sql = jsonSql.build({ + type: 'union', + queries: [ + {type: 'select', table: 'table1'}, + {type: 'select', table: 'table2'} + ] +}); + +sql.query +// (select * from "table1") union (select * from "table2"); + +//or for sqlite3 +jsonSql.setDialect("sqlite"); +var sql = jsonSql.build({ + type: 'union', + unionqueries: [ + {type: 'select', table: 'table1'}, + {type: 'select', table: 'table2'} + ] +}); + +sql.query +// select * from "table1" union select * from "table2"; +``` + +--- + +## Condition operators + +TODO: write this section \ No newline at end of file diff --git a/legacy/json-sql/gulpfile.js b/legacy/json-sql/gulpfile.js new file mode 100644 index 00000000..c7bbf59b --- /dev/null +++ b/legacy/json-sql/gulpfile.js @@ -0,0 +1,27 @@ +'use strict'; + +var gulp = require('gulp'); +var mocha = require('gulp-mocha'); +var jshint = require('gulp-jshint'); + +gulp.task('test', function() { + return gulp.src(['./tests/*.js'], {read: false}) + .pipe( + mocha({ + reporter: 'spec', + bail: true + }) + ); +}); + +gulp.task('lint', function() { + return gulp.src('./lib/**/*.js') + .pipe(jshint()) + .pipe(jshint.reporter('unix')); +}); + +gulp.task('lintTests', function() { + return gulp.src('./tests/*.js') + .pipe(jshint()) + .pipe(jshint.reporter('unix')); +}); \ No newline at end of file diff --git a/legacy/json-sql/lib/builder.js b/legacy/json-sql/lib/builder.js new file mode 100644 index 00000000..fa0d2a0d --- /dev/null +++ b/legacy/json-sql/lib/builder.js @@ -0,0 +1,233 @@ +'use strict'; + +var _ = require('underscore'); + +var dialects = { + base: require('./dialects/base'), + mssql: require('./dialects/mssql'), + postgresql: require('./dialects/postgresql'), + sqlite: require('./dialects/sqlite') +}; + +var blockRegExp = /\{([a-z0-9]+)\}(.|$)/ig; + +var Builder = module.exports = function (options) { + this.configure(options); +}; + +Builder.prototype._reset = function () { + if (this.options.separatedValues) { + this._placeholderId = 1; + this._values = this.options.namedValues ? {} : []; + } else { + delete this._placeholderId; + delete this._values; + } + + this._query = ''; +}; + +Builder.prototype._getPlaceholder = function () { + return (this.options.namedValues ? 'p' : '') + (this._placeholderId++); +}; + +Builder.prototype._wrapPlaceholder = function (name) { + return this.options.valuesPrefix + '{' + name + '}'; +}; + +Builder.prototype._pushValue = function (value) { + var valueType = typeof value; + + if (valueType === 'undefined') { + return 'null'; + } else if (value === null || valueType === 'number' || + valueType === 'boolean') { + return String(value); + } else if (valueType === 'string') { + if (this.options.separatedValues) { + var placeholder = this._getPlaceholder(); + if (this.options.namedValues) { + this._values[placeholder] = value; + } else { + this._values.push(value); + } + return this._wrapPlaceholder(placeholder); + } else { + return '\'' + value + '\''; + } + } else if (valueType == 'object') { + if (Object.prototype.toString.call(value) == "[object Array]") { + if (value.length > 0) { + return value.join(",") + } else { + return 'null'; + } + } + else if (Buffer.isBuffer(value)) { + var placeholder = this._getPlaceholder(); + this._values[placeholder] = value; + return this._wrapPlaceholder(placeholder); + //return "X'" + value.toString('hex') + "'"; + } else { + var placeholder = this._getPlaceholder(); + this._values[placeholder] = value; + return this._wrapPlaceholder(placeholder); + //return ("'" + JSON.stringify(value).replace(/'/g, "''") + "'"); + } + } else { + throw new Error('Wrong value type "' + valueType + '"'); + } +}; + +Builder.prototype.configure = function (options) { + options = options || {}; + + this.options = _(options).defaults({ + separatedValues: true, + namedValues: true, + valuesPrefix: '$', + dialect: 'base', + wrappedIdentifiers: true + }); + + this.setDialect(this.options.dialect); + + this._reset(); +}; + +Builder.prototype.buildCondition = function (params) { + var self = this; + var result = ''; + + var condition = params.condition; + var logicalOperator = params.logicalOperator || + this.dialect.config.defaultLogicalOperator; + + if (_.isObject(condition) && !_.isEmpty(condition)) { + // if condition is array: [{a: 1}, {b: 2}] + if (_.isArray(condition)) { + result = _(condition).map(function (item) { + return self.buildCondition({ + condition: item, + operator: params.operator + }); + }); + + // if condition is object + } else { + result = _(condition).map(function (value, field) { + // if field name is operator + if (field[0] === '$') { + // if field name is logical operator: {$logicalOperator: {a: 1}} + if (!self.dialect.logicalOperators.has(field)) { + throw new Error('Unknown logical operator "' + field + '"'); + } + + return self.buildCondition({ + condition: value, + logicalOperator: field, + operator: params.operator + }); + + // if condition item is object: {a: {$operator: 4}} + } else if (_.isObject(value)) { + var logicalOperatorFn = self.dialect.logicalOperators + .get(self.dialect.config.defaultLogicalOperator); + + return logicalOperatorFn(_(value).map(function (operatorValue, operator) { + var operatorFn = self.dialect.conditions.get(operator); + if (!operatorFn) { + throw new Error('Unknown operator "' + operator + '"'); + } + return operatorFn(field, operator, operatorValue); + })); + + // if condition item type is simple: {a: 1, b: 2} + } else { + var operatorFn = self.dialect.conditions.get(params.operator); + if (!operatorFn) { + throw new Error('Unknown operator "' + params.operator + '"'); + } + return operatorFn(field, params.operator, value); + } + }); + } + + result = this.dialect.logicalOperators + .get(logicalOperator)(_(result).compact()); + } + + return result; +}; + +Builder.prototype.buildBlock = function (block, params) { + var blockFn = this.dialect.blocks.get(block); + + if (!blockFn) { + throw new Error('Unknown block "' + block + '"'); + } + + return blockFn(params); +}; + +Builder.prototype.buildTemplate = function (type, params) { + var self = this; + + var template = this.dialect.templates.get(type); + if (!template) { + throw new Error('Unknown template type "' + type + '"'); + } + + params = _({}).defaults(params, template.defaults); + + if (template.validate) { + template.validate(type, params); + } + + return template.pattern.replace(blockRegExp, function (fullMatch, block, space) { + if (typeof params[block] !== 'undefined') { + return self.buildBlock(block, params) + space; + } else { + return ''; + } + }).trim(); +}; + +Builder.prototype.build = function (params) { + var builder = this; + + this._reset(); + + this._query = this.buildTemplate('query', {queryBody: params}) + ';'; + + if (this.options.separatedValues) { + return { + query: this._query, + values: this._values, + prefixValues: function () { + var values = {}; + _(this.getValuesObject()).each(function (value, name) { + values[builder._wrapPlaceholder(name)] = value; + }); + return values; + }, + getValuesArray: function () { + return _.isArray(this.values) ? this.values : _(this.values).values(); + }, + getValuesObject: function () { + return _.isArray(this.values) ? _(_.range(1, this.values.length + 1)).object(this.values) : + this.values; + } + }; + } else { + return {query: this._query}; + } +}; + +Builder.prototype.setDialect = function (name) { + if (!dialects[name]) { + throw new Error('Unknown dialect "' + name + '"'); + } + + this.dialect = new (dialects[name])(this); +}; diff --git a/legacy/json-sql/lib/dialects/base/blocks.js b/legacy/json-sql/lib/dialects/base/blocks.js new file mode 100644 index 00000000..809c5fd6 --- /dev/null +++ b/legacy/json-sql/lib/dialects/base/blocks.js @@ -0,0 +1,441 @@ +'use strict'; + +var _ = require('underscore'); +var scheme = require('./scheme.js'); + +function removeTopBrackets(condition) { + if (condition.length && condition[0] === '(' && + condition[condition.length - 1] === ')') { + condition = condition.slice(1, condition.length - 1); + } + + return condition; +} + +module.exports = function(dialect) { + dialect.blocks.add('distinct', function() { + return 'distinct'; + }); + + + dialect.blocks.add('field', function(params) { + var field = params.field || params.$field; + var expression = params.expression || params.$expression; + + if (!field && !expression) { + throw new Error('Neither `field` nor `expression` properties aren\'t set'); + } + + if (field) { + field = this.dialect._wrapIdentifier(field); + + var table = params.table || params.$table; + if (table) { + field = this.buildBlock('table', {table: table}) + '.' + field; + } + + if (expression) { + if (!_.isArray(expression)) expression = [expression]; + var exprSuffix = (new Array(expression.length + 1)).join(')'); + field = expression.join('(') + '(' + field + exprSuffix; + } + } else { + field = expression; + } + + var cast = params.cast || params.$cast; + if (cast) { + field = 'cast(' + field + ' as ' + cast + ')'; + } + + var alias = params.alias || params.$alias; + if (alias) { + field += ' ' + this.buildBlock('alias', {alias: alias}); + } + + return field; + }); + + + dialect.blocks.add('fields', function(params) { + var self = this; + + var fields = params.fields || {}; + var result = ''; + + if (!_.isObject(fields) && !_.isString(fields)) { + throw new Error('Invalid `fields` property type "' + (typeof fields) + '"'); + } + + if (_.isObject(fields)) { + if (_.isEmpty(fields)) { + result = '*'; + } else { + // If fields is array: ['a', {b: 'c'}, {field: '', table: 't', alias: 'r'}] + if (_.isArray(fields)) { + result = _(fields).map(function(item) { + + if (_.isObject(item)) { + // if field is field object: {field: '', table: 't', alias: 'r'} + if (item.field || item.expression) { + return self.buildBlock('field', item); + + // if field is non-field object: {b: 'c'} + } else { + return self.buildBlock('fields', {fields: item}); + } + + // if field is string: 'a' + } else if (_.isString(item)) { + return self.buildBlock('field', {field: item}); + } + }); + + // If fields is object: {a: 'u', b: {table: 't', alias: 'c'}} + } else { + // use keys as field names + result = _(fields).map(function(item, field) { + // b: {table: 't', alias: 'c'} + if (_.isObject(item)) { + if (!item.field) { + item = _.clone(item); + item.field = field; + } + + return self.buildBlock('field', item); + + // a: 'u' + } else if (_.isString(item)) { + return self.buildBlock('field', { + field: field, + alias: item + }); + } + + return ''; + }); + } + + result = result.join(', '); + } + } else { + result = fields; + } + + return result; + }); + + dialect.blocks.add('table', function(params) { + return this.dialect._wrapIdentifier(params.table); + }); + + dialect.blocks.add('expression', function(params) { + return params.expression; + }); + + dialect.blocks.add('name', function(params) { + return this.dialect._wrapIdentifier(params.name); + }); + + dialect.blocks.add('alias', function(params) { + var self = this; + + var result; + + if (_.isObject(params.alias)) { + if (!params.alias.name) { + throw new Error('Alias `name` property is required'); + } + + result = this.dialect._wrapIdentifier(params.alias.name); + + if (_.isArray(params.alias.columns)) { + result += '(' + _(params.alias.columns).map(function(column) { + return self.dialect._wrapIdentifier(column); + }).join(', ') + ')'; + } + } else { + result = this.dialect._wrapIdentifier(params.alias); + } + + return 'as ' + result; + }); + + dialect.blocks.add('condition', function(params) { + var condition = params.condition; + var result = ''; + + if (_.isObject(condition)) { + result = this.buildCondition({ + condition: condition, + operator: '$eq' + }); + } else if (_.isString(condition)) { + result = condition; + } + + if (result) { + result = 'where ' + removeTopBrackets(result); + } + + return result; + }); + + dialect.blocks.add('modifier', function(params) { + var self = this; + + var modifier = params.modifier; + var result = ''; + + // if modifier is object -> call method for each operator + if (_.isObject(modifier)) { + result = _(modifier).map(function(values, field) { + var modifierFn = self.dialect.modifiers.get(field); + var methodParams = values; + + if (!modifierFn) { + modifierFn = self.dialect.modifiers.get(self.dialect.config.defaultModifier); + methodParams = {}; + methodParams[field] = values; + } + + return modifierFn.call(self, methodParams); + }).join(', '); + + // if modifier is string -> not process it + } else if (_.isString(modifier)) { + result = modifier; + } + + if (result) { + result = 'set ' + result; + } + + return result; + }); + + dialect.blocks.add('join', function(params) { + var self = this; + + var join = params.join; + var result = ''; + + // if join is array -> make each joinItem + if (_.isArray(join)) { + result = _(join).map(function(joinItem) { + return self.buildTemplate('joinItem', joinItem); + }).join(' '); + + // if join is object -> set table name from key and make each joinItem + } else if (_.isObject(join)) { + result = _(join).map(function(joinItem, table) { + if (!joinItem.table && !joinItem.query && !joinItem.select) { + joinItem = _.clone(joinItem); + joinItem.table = table; + } + + return self.buildTemplate('joinItem', joinItem); + }).join(' '); + + // if join is string -> not process + } else if (_.isString(join)) { + result = join; + } + + return result; + }); + + dialect.blocks.add('type', function(params) { + return params.type.toLowerCase(); + }); + + + dialect.blocks.add('on', function(params) { + var on = params.on; + var result = ''; + + // `on` block is use `$field` as default compare operator + // because it most used case + if (_.isObject(on)) { + result = this.buildCondition({ + condition: on, + operator: '$field' + }); + } else if (_.isString(on)) { + result = on; + } + + if (result) { + result = 'on ' + removeTopBrackets(result); + } + + return result; + }); + + + dialect.blocks.add('group', function(params) { + var result = ''; + var group = params.group; + + if (_.isArray(group)) { + var self = this; + result = _(group).map(function(field) { + return self.dialect._wrapIdentifier(field); + }).join(', '); + } else if (_.isString(group)) { + result = this.dialect._wrapIdentifier(group); + } else if (_.isObject(group)) { + result = group.expression + (group.having ? " having " + this.buildCondition({ + condition: group.having + }) : ""); + } + + if (result) { + result = 'group by ' + result; + } + + return result; + }); + + + dialect.blocks.add('sort', function(params) { + var result = ''; + var sort = params.sort; + + // if sort is array -> field1, field2, ... + var self = this; + if (_.isArray(sort)) { + result = _(sort).map(function(sortField) { + return self.dialect._wrapIdentifier(sortField); + }).join(', '); + + // if sort is object -> field1 asc, field2 desc, ... + } else if (_.isObject(sort)) { + result = _(sort).map(function(direction, field) { + return self.dialect._wrapIdentifier(field) + ' ' + + (direction > 0 ? 'asc' : 'desc'); + }).join(', '); + + // if sort is string -> not process + } else if (_.isString(sort)) { + result = this.dialect._wrapIdentifier(sort); + } + + if (result) { + result = 'order by ' + result; + } + + return result; + }); + + + dialect.blocks.add('limit', function(params) { + return 'limit ' + this._pushValue(params.limit); + }); + + + dialect.blocks.add('offset', function(params) { + return 'offset ' + this._pushValue(params.offset); + }); + + + dialect.blocks.add('or', function(params) { + return 'or ' + params.or; + }); + + + dialect.blocks.add('values', function(params) { + var self = this; + + var fieldValues = params.values; + + if (!_.isArray(fieldValues)) { + fieldValues = [fieldValues]; + } + + var fields = params.fields || _(fieldValues).chain().map(function(values) { + return _(values).keys(); + }).flatten().uniq().value(); + + fieldValues = _(fieldValues).map(function(values) { + return _(fields).map(function(field) { + return self._pushValue(values[field]); + }); + }); + + return this.buildTemplate('insertValues', { + fields: fields, + fieldValues: fieldValues + }); + }); + + dialect.blocks.add('fieldValues', function(params) { + return _(params.fieldValues).map(function(values) { + return '(' + values.join(', ') + ')'; + }).join(', '); + }); + + dialect.blocks.add('queryBody', function(params) { + var query = params.queryBody || {}; + + return this.buildTemplate(query.type || 'select', query); + }); + + dialect.blocks.add('query', function(params) { + return this.buildTemplate('subQuery', {queryBody: params.query}); + }); + + dialect.blocks.add('select', function(params) { + return this.buildTemplate('subQuery', {queryBody: params.select}); + }); + + dialect.blocks.add('tableFields', function (params) { + return scheme.parse.call(this, params.tableFields, params.foreignKeys); + }); + + dialect.blocks.add('queries', function(params) { + var self = this; + + return _(params.queries).map(function(query) { + return self.buildTemplate('subQuery', {queryBody: query}); + }).join(' ' + params.type + (params.all ? ' all' : '') + ' '); + }); + + dialect.blocks.add('indexOn', function (params) { + return params.indexOn; + }); + + dialect.blocks.add('with', function(params) { + var self = this; + + var withList = params['with']; + var result = ''; + + // if with clause is array -> make each withItem + if (_.isArray(withList)) { + result = _(withList).map(function(withItem) { + return self.buildTemplate('withItem', withItem); + }).join(', '); + + // if with clause is object -> set name from key and make each withItem + } else if (_.isObject(withList)) { + result = _(withList).map(function(withItem, name) { + if (!withItem.name) { + withItem = _.clone(withItem); + withItem.name = name; + } + return self.buildTemplate('withItem', withItem); + }).join(', '); + + // if with clause is string -> not process + } else if (_.isString(withList)) { + result = withList; + } + + return 'with ' + result; + }); + + dialect.blocks.add('returning', function(params) { + return 'returning ' + this.buildBlock('fields', {fields: params.returning}); + }); +}; diff --git a/legacy/json-sql/lib/dialects/base/conditions.js b/legacy/json-sql/lib/dialects/base/conditions.js new file mode 100644 index 00000000..c46be4f3 --- /dev/null +++ b/legacy/json-sql/lib/dialects/base/conditions.js @@ -0,0 +1,122 @@ +'use strict'; + +var _ = require('underscore'); + +// Compare conditions (e.g. $eq, $gt) +function buildCompareCondition(builder, field, operator, value) { + var placeholder; + + // if value is object, than make field block from it + if (value && _.isObject(value)) { + placeholder = builder.buildBlock('field', value); + } else { + // if value is simple - create placeholder for it + placeholder = builder._pushValue(value); + } + + field = builder.dialect._wrapIdentifier(field); + return [field, operator, placeholder].join(' '); +} + +// Contain conditions ($in/$nin) +function buildContainsCondition(builder, field, operator, value) { + var newValue; + + if (_.isArray(value)) { + if (!value.length) value = [null]; + + newValue = '(' + _(value).map(function(item) { + return builder._pushValue(item); + }).join(', ') + ')'; + } else if (_.isObject(value)) { + newValue = builder.buildTemplate('subQuery', {queryBody: value}); + } else { + throw new Error('Invalid `' + operator + '` value type "' + (typeof value) + '"'); + } + + field = builder.dialect._wrapIdentifier(field); + return [field, operator, newValue].join(' '); +} + +module.exports = function(dialect) { + dialect.conditions.add('$eq', function(field, operator, value) { + return buildCompareCondition(this, field, '=', value); + }); + + dialect.conditions.add('$ne', function(field, operator, value) { + return buildCompareCondition(this, field, '!=', value); + }); + + dialect.conditions.add('$gt', function(field, operator, value) { + return buildCompareCondition(this, field, '>', value); + }); + + dialect.conditions.add('$lt', function(field, operator, value) { + return buildCompareCondition(this, field, '<', value); + }); + + dialect.conditions.add('$gte', function(field, operator, value) { + return buildCompareCondition(this, field, '>=', value); + }); + + dialect.conditions.add('$lte', function(field, operator, value) { + return buildCompareCondition(this, field, '<=', value); + }); + + dialect.conditions.add('$is', function(field, operator, value) { + return buildCompareCondition(this, field, 'is', value); + }); + + dialect.conditions.add('$isnot', function(field, operator, value) { + return buildCompareCondition(this, field, 'is not', value); + }); + + dialect.conditions.add('$like', function(field, operator, value) { + return buildCompareCondition(this, field, 'like', value); + }); + + dialect.conditions.add('$null', function(field, operator, value) { + return buildCompareCondition(this, field, 'is' + (value ? '' : ' not'), null); + }); + + dialect.conditions.add('$field', function(field, operator, value) { + var placeholder; + + // if value is object, than make field block from it + if (_.isObject(value)) { + placeholder = this.buildBlock('field', value); + } else { + // $field - special operator, that not make placeholder for value + placeholder = this.dialect._wrapIdentifier(value); + } + + return [this.dialect._wrapIdentifier(field), '=', placeholder].join(' '); + }); + + + dialect.conditions.add('$in', function(field, operator, value) { + return buildContainsCondition(this, field, 'in', value); + }); + + dialect.conditions.add('$nin', function(field, operator, value) { + return buildContainsCondition(this, field, 'not in', value); + }); + + dialect.conditions.add('$between', function(field, operator, value) { + if (!_.isArray(value)) { + throw new Error('Invalid `$between` value type "' + (typeof value) + '"'); + } + + if (value.length < 2) { + throw new Error('`$between` array length should be 2 or greater'); + } + + return [ + this.dialect._wrapIdentifier(field), + 'between', + this._pushValue(value[0]), + 'and', + this._pushValue(value[1]) + ].join(' '); + }); +}; diff --git a/legacy/json-sql/lib/dialects/base/index.js b/legacy/json-sql/lib/dialects/base/index.js new file mode 100644 index 00000000..2a0e2c7a --- /dev/null +++ b/legacy/json-sql/lib/dialects/base/index.js @@ -0,0 +1,58 @@ +'use strict'; + +var _ = require('underscore'); +var ValuesStore = require('../../valuesStore'); + +var templatesInit = require('./templates'); +var blocksInit = require('./blocks'); +var conditionsInit = require('./conditions'); +var logicalOperatorsInit = require('./logicalOperators'); +var modifiersInit = require('./modifiers'); + +var Dialect = module.exports = function(builder) { + this.builder = builder; + + this.blocks = new ValuesStore({context: builder}); + this.modifiers = new ValuesStore({context: builder}); + this.conditions = new ValuesStore({context: builder}); + this.logicalOperators = new ValuesStore({context: builder}); + this.templates = new ValuesStore({context: builder}); + + templatesInit(this); + blocksInit(this); + conditionsInit(this); + modifiersInit(this); + logicalOperatorsInit(this); + + this.identifierPartsRegexp = new RegExp( + '(\\' + this.config.identifierPrefix + '[^\\' + this.config.identifierSuffix + ']*\\' + + this.config.identifierSuffix + '|[^\\.]+)', 'g' + ); + this.wrappedIdentifierPartRegexp = new RegExp( + '^\\' + this.config.identifierPrefix + '.*\\' + this.config.identifierSuffix + '$' + ); +}; + +Dialect.prototype.config = { + identifierPrefix: '"', + identifierSuffix: '"', + defaultLogicalOperator: '$and', + defaultModifier: '$set' +}; + +Dialect.prototype._wrapIdentifier = function(name) { + if (this.builder.options.wrappedIdentifiers) { + var self = this; + var nameParts = name.match(this.identifierPartsRegexp); + + return _(nameParts).map(function(namePart) { + if (namePart !== '*' && !self.wrappedIdentifierPartRegexp.test(namePart)) { + namePart = self.config.identifierPrefix + namePart + self.config.identifierSuffix; + } + + return namePart; + }).join('.'); + } + + return name; +}; diff --git a/legacy/json-sql/lib/dialects/base/logicalOperators.js b/legacy/json-sql/lib/dialects/base/logicalOperators.js new file mode 100644 index 00000000..fb2879c1 --- /dev/null +++ b/legacy/json-sql/lib/dialects/base/logicalOperators.js @@ -0,0 +1,37 @@ +'use strict'; + +var _ = require('underscore'); + +function buildJoinOperator(conditions, operator) { + var isBracketsNeeded = conditions.length > 1; + var result = conditions.join(' ' + operator + ' '); + + if (result && isBracketsNeeded) result = '(' + result + ')'; + + return result; +} + +module.exports = function(dialect) { + dialect.logicalOperators.add('$and', function(conditions) { + return buildJoinOperator(conditions, 'and'); + }); + + dialect.logicalOperators.add('$or', function(conditions) { + return buildJoinOperator(conditions, 'or'); + }); + + dialect.logicalOperators.add('$not', function(conditions) { + var result = ''; + + if (_.isArray(conditions)) { + result = dialect.logicalOperators + .get(dialect.config.defaultLogicalOperator)(conditions); + } else if (_.isString(conditions)) { + result = conditions; + } + + if (result) result = 'not ' + result; + + return result; + }); +}; diff --git a/legacy/json-sql/lib/dialects/base/modifiers.js b/legacy/json-sql/lib/dialects/base/modifiers.js new file mode 100644 index 00000000..3be42aa6 --- /dev/null +++ b/legacy/json-sql/lib/dialects/base/modifiers.js @@ -0,0 +1,37 @@ +'use strict'; + +var _ = require('underscore'); + +module.exports = function(dialect) { + dialect.modifiers.add('$set', function(values) { + var self = this; + + return _(values).map(function(value, field) { + var placeholder = self._pushValue(value); + + return [self.dialect._wrapIdentifier(field), '=', placeholder].join(' '); + }).join(', '); + }); + + dialect.modifiers.add('$inc', function(values) { + var self = this; + + return _(values).map(function(value, field) { + var placeholder = self._pushValue(value); + field = self.dialect._wrapIdentifier(field); + + return [field, '=', field, '+', placeholder].join(' '); + }).join(', '); + }); + + dialect.modifiers.add('$dec', function(values) { + var self = this; + + return _(values).map(function(value, field) { + var placeholder = self._pushValue(value); + field = self.dialect._wrapIdentifier(field); + + return [field, '=', field, '-', placeholder].join(' '); + }).join(', '); + }); +}; diff --git a/legacy/json-sql/lib/dialects/base/scheme.js b/legacy/json-sql/lib/dialects/base/scheme.js new file mode 100644 index 00000000..686e16ed --- /dev/null +++ b/legacy/json-sql/lib/dialects/base/scheme.js @@ -0,0 +1,146 @@ +var SchemeParser = function () { +} + +var types = { + "Number": {syntax: "int"}, + "BigInt": {syntax: "bigint"}, + "SmallInt": {syntax: "smallint"}, + "String": {syntax: "varchar", length: true}, + "Text": {syntax: "text"}, + "Real": {syntax: "real"}, + "Boolean": {syntax: "boolean"}, + "Blob": {syntax: "blob"}, + "Binary": {syntax: "bytea"} +} + +// for future +var onDeleteTrigger = { + "set_null": "SET NULL", + "cascade": "CASCADE" +} + +function getType(field) { + var s = ""; + var type = types[field.type]; + + if (!type) { + throw new Error("Invalid type of field: " + field.type); + } + + s += type.syntax; + + if (type.length) { + if (!field.length || field.length <= 0) { + throw new Error("Field length can't be less or equal 0"); + } + + s += "(" + field.length + ")"; + } else if (type.default_length) { + s += "(" + type.default_length + ")"; + } + + return s; +} + +function foreignkeys(fields, keys) { + if (!keys || keys.length == 0) { + return ""; + } + + var s = ", "; + var names = []; + fields.forEach(function (field) { + names.push(field.name) + }); + + keys.forEach(function (key, i) { + if (!key.field) { + throw new Error("Provide field for foreign key"); + } + + if (names.indexOf(key.field) < 0) { + throw new Error("Not exists field to make foreign key: " + key.field); + } + + if (!key.table || key.table.trim().length == 0) { + throw new Error("Invalid reference table name"); + } + + if (!key.table_field || key.table_field.trim().length == 0) { + throw new Error("Invalid reference table filed"); + } + + s += "FOREIGN KEY (\"" + key.field + "\") REFERENCES " + key.table + "(\"" + key.table_field + "\")" + (key.on_delete ? " ON DELETE " + key.on_delete : ""); + if (i != keys.length - 1) { + s += ","; + } + }); + + return s; +} + +function parse(fields, fkeys) { + var sql_fields = ""; + var checkPrimaryKey = false; + var names = []; + + fields.forEach(function (field, i) { + if (!field.name) { + throw new Error("Name of field most be provided"); + } + + if (field.name.trim().length == 0) { + throw new Error("Name most contains characters"); + } + + if (names.indexOf(field.name) >= 0) { + throw new Error("Two parameters with same name: " + field.name); + } + + var line = this.dialect._wrapIdentifier(field.name) + " " + getType(field); + + if (field.not_null) { + line += " NOT NULL"; + } + + if (field.default !== undefined) { + var _type = typeof field.default; + var _default = field.default; + if (_type === "string") { + _default = "'" + field.default + "'"; + } + line += " default " + _default; + } + + if (field.unique) { + line += " UNIQUE"; + } + + + if (field.primary_key) { + if (checkPrimaryKey) { + throw new Error("Too much primary key '" + field.name + "' in table"); + } else { + checkPrimaryKey = true; + } + + line += " PRIMARY KEY"; + } + + sql_fields += line; + + names.push(field.name); + + if (i != fields.length - 1) { + sql_fields += ","; + } + }.bind(this)); + + sql_fields += foreignkeys(fields, fkeys); + return sql_fields; +} + +module.exports = { + parse: parse, + foreignkeys: foreignkeys +} diff --git a/legacy/json-sql/lib/dialects/base/templates.js b/legacy/json-sql/lib/dialects/base/templates.js new file mode 100644 index 00000000..81c60cdb --- /dev/null +++ b/legacy/json-sql/lib/dialects/base/templates.js @@ -0,0 +1,220 @@ +'use strict'; + +var _ = require('underscore'); + +module.exports = function(dialect) { + var availableSourceProps = ['table', 'query', 'select', 'expression']; + var availableJoinTypes = ['natural', 'cross', 'inner', 'outer', 'left', 'right', 'full', 'self']; + var orRegExp = /^(rollback|abort|replace|fail|ignore)$/i; + + // private templates + + dialect.templates.add('query', { + pattern: '{queryBody}', + validate: function(type, params) { + hasRequiredProp(type, params, 'queryBody'); + hasObjectProp(type, params, 'queryBody'); + } + }); + + + dialect.templates.add('subQuery', { + pattern: '({queryBody})', + validate: function(type, params) { + hasRequiredProp(type, params, 'queryBody'); + hasObjectProp(type, params, 'queryBody'); + } + }); + + dialect.templates.add('create', { + pattern: 'create table if not exists {table}({tableFields})', + validate: function (type, params) { + hasRequiredProp(type, params, 'table'); + hasRequiredProp(type, params, 'tableFields'); + hasArrayProp(type, params, 'tableFields'); + } + }); + + dialect.templates.add('index', { + pattern: 'create index if not exists {name} ON {table}({indexOn}) {condition}', + validate: function (type, params) { + hasRequiredProp(type, params, 'table'); + hasRequiredProp(type, params, 'name') + hasRequiredProp(type, params, 'indexOn'); + hasMinPropLength(type, params, 'name', 1); + hasMinPropLength(type, params, 'indexOn', 1); + } + }) + + + dialect.templates.add('queriesCombination', { + pattern: '{with} {queries} {sort} {limit} {offset}', + validate: function(type, params) { + hasRequiredProp(type, params, 'queries'); + hasArrayProp(type, params, 'queries'); + hasMinPropLength(type, params, 'queries', 2); + } + }); + + + dialect.templates.add('insertValues', { + pattern: '({fields}) values {fieldValues}', + validate: function(type, params) { + hasRequiredProp('values', params, 'fields'); + hasArrayProp('values', params, 'fields'); + hasMinPropLength('values', params, 'fields', 1); + + hasRequiredProp('values', params, 'fieldValues'); + hasArrayProp('values', params, 'fieldValues'); + hasMinPropLength('values', params, 'fieldValues', 1); + } + }); + + + dialect.templates.add('joinItem', { + pattern: '{type} join {table} {query} {select} {expression} {alias} {on}', + validate: function(type, params) { + hasOneOfProps('join', params, availableSourceProps); + + if (params.type) { + hasStringProp('join', params, 'type'); + + var splitType = _(params.type.toLowerCase().split(' ')).compact(); + if (_.difference(splitType, availableJoinTypes).length) { + throw new Error('Invalid `type` property value "' + params.type + '" in `join` clause'); + } + } + } + }); + + + dialect.templates.add('withItem', { + pattern: '{name} {fields} as {query} {select} {expression}', + validate: function(type, params) { + hasRequiredProp('with', params, 'name'); + hasOneOfProps('with', params, ['query', 'select', 'expression']); + } + }); + + + dialect.templates.add('fromItem', { + pattern: '{table} {query} {select} {expression}', + validate: function(type, params) { + hasOneOfProps('from', params, availableSourceProps); + } + }); + + + // public templates + + dialect.templates.add('select', { + pattern: '{with} select {distinct} {fields} ' + + 'from {from} {table} {query} {select} {expression} {alias} ' + + '{join} {condition} {group} {sort} {limit} {offset}', + defaults: { + fields: {} + }, + validate: function(type, params) { + hasOneOfProps(type, params, availableSourceProps); + } + }); + + + dialect.templates.add('insert', { + pattern: '{with} insert {or} into {table} {values} {returning} {condition}', + validate: function(type, params) { + hasRequiredProp(type, params, 'values'); + hasObjectProp(type, params, 'values'); + hasOneOfProps(type, params, availableSourceProps); + if (params.or) { + hasStringProp(type, params, 'or'); + matchesRegExpProp(type, params, 'or', orRegExp); + } + } + }); + + + dialect.templates.add('update', { + pattern: '{with} update {or} {table} {modifier} {condition} {returning}', + validate: function(type, params) { + hasRequiredProp(type, params, 'modifier'); + hasRequiredProp(type, params, 'table'); + if (params.or) { + hasStringProp(type, params, 'or'); + matchesRegExpProp(type, params, 'or', orRegExp); + } + } + }); + + + dialect.templates.add('remove', { + pattern: '{with} delete from {table} {condition} {returning}', + validate: function(type, params) { + hasRequiredProp(type, params, 'table'); + } + }); + + + dialect.templates.add('union', dialect.templates.get('queriesCombination')); + + + dialect.templates.add('intersect', dialect.templates.get('queriesCombination')); + + + dialect.templates.add('except', dialect.templates.get('queriesCombination')); + + + // validation helpers + + function hasRequiredProp(type, params, propName) { + if (!params[propName]) { + throw new Error('`' + propName + '` property is not set in `' + type + '` clause'); + } + } + + function hasObjectProp(type, params, propName) { + if (!_.isObject(params[propName])) { + throw new Error('`' + propName + '` property should be an object in `' + type + '` clause'); + } + } + + function hasArrayProp(type, params, propName) { + if (!_.isArray(params[propName])) { + throw new Error('`' + propName + '` property should be an array in `' + type + '` clause'); + } + } + + function hasStringProp(type, params, propName) { + if (!_.isString(params.type)) { + throw new Error('`' + propName + '` property should be a string in `' + type + '` clause'); + } + } + + function hasMinPropLength(type, params, propName, length) { + if (params[propName].length < length) { + throw new Error('`' + propName + '` property should not have length less than ' + length + + ' in `' + type + '` clause'); + } + } + + function hasOneOfProps(type, params, expectedPropNames) { + var propNames = _(params).chain().keys().intersection(expectedPropNames).value(); + + if (!propNames.length) { + throw new Error('Neither `' + expectedPropNames.join('`, `') + + '` properties are not set in `' + type + '` clause'); + } + + if (propNames.length > 1) { + throw new Error('Wrong using `' + propNames.join('`, `') + '` properties together in `' + + type + '` clause'); + } + } + + function matchesRegExpProp(type, params, propName, regExp) { + if (!params[propName].match(regExp)) { + throw new Error('Invalid `' + propName + '` property value "' + params[propName] + '" in `' + + type + '` clause'); + } + } +}; diff --git a/legacy/json-sql/lib/dialects/mssql/index.js b/legacy/json-sql/lib/dialects/mssql/index.js new file mode 100644 index 00000000..8374184f --- /dev/null +++ b/legacy/json-sql/lib/dialects/mssql/index.js @@ -0,0 +1,13 @@ +'use strict'; + +var BaseDialect = require('../base'); +var _ = require('underscore'); +var util = require('util'); + +var Dialect = module.exports = function(builder) { + BaseDialect.call(this, builder); +}; + +util.inherits(Dialect, BaseDialect); + +Dialect.prototype.config = _({}).extend(BaseDialect.prototype.config, {}); diff --git a/legacy/json-sql/lib/dialects/postgresql/blocks.js b/legacy/json-sql/lib/dialects/postgresql/blocks.js new file mode 100644 index 00000000..e6f0066d --- /dev/null +++ b/legacy/json-sql/lib/dialects/postgresql/blocks.js @@ -0,0 +1,33 @@ +'use strict'; + +var _ = require('underscore'); + +module.exports = function(dialect) { + dialect.blocks.add('offset', function(params) { + var limit = ''; + + if (typeof params.limit === 'undefined') { + limit = this.buildBlock('limit', {limit: -1}) + ' '; + } + + return limit + 'offset ' + this._pushValue(params.offset); + }); + + dialect.blocks.add('unionqueries', function(params) { + var self = this; + + return _(params.unionqueries).map(function(query) { + return self.buildTemplate('subUnionQuery', {queryBody: query}); + }).join(' ' + params.type + (params.all ? ' all' : '') + ' '); + }); + + dialect.blocks.add('conflictFields', function(params) { + var self = this; + + var fields = _(params.conflictFields).map(function(field) { + return self.dialect._wrapIdentifier(field); + }); + + return '(' + fields.join(',') + ')'; + }); +}; diff --git a/legacy/json-sql/lib/dialects/postgresql/conditions.js b/legacy/json-sql/lib/dialects/postgresql/conditions.js new file mode 100644 index 00000000..dddba22a --- /dev/null +++ b/legacy/json-sql/lib/dialects/postgresql/conditions.js @@ -0,0 +1,87 @@ +'use strict'; + +var _ = require('underscore'); + +var buildJsonInCondition = function(builder, field, operator, value) { + field = builder.dialect._wrapIdentifier(field); + + var placeholder; + try { + placeholder = builder.buildBlock('field', value); + } catch (e) { + placeholder = builder._pushValue(JSON.stringify(value)); + } + + return [field, operator, placeholder].join(' '); +}; + +var buildJsonHasCondition = function(builder, field, operator, value) { + field = builder.dialect._wrapIdentifier(field); + + var placeholder; + if (_(value).isArray()) { + placeholder = 'array[' + _(value).map(function(item) { + return builder._pushValue(item); + }).join(', ') + ']'; + } else { + placeholder = builder.buildBlock('field', value); + } + + return [field, operator, placeholder].join(' '); +}; + +module.exports = function(dialect) { + dialect.conditions.add('$jsonContains', function(field, operator, value) { + return buildJsonInCondition(this, field, '@>', value); + }); + + dialect.conditions.add('$jsonIn', function(field, operator, value) { + return buildJsonInCondition(this, field, '<@', value); + }); + + dialect.conditions.add('$jsonHas', function(field, operator, value) { + field = this.dialect._wrapIdentifier(field); + + var placeholder = value; + if (_(placeholder).isObject()) { + placeholder = this.buildBlock('field', placeholder); + } else { + placeholder = this._pushValue(value.toString()); + } + + return [field, '?', placeholder].join(' '); + }); + + dialect.conditions.add('$jsonHasAny', function(field, operator, value) { + return buildJsonHasCondition(this, field, '?|', value); + }); + + dialect.conditions.add('$jsonHasAll', function(field, operator, value) { + return buildJsonHasCondition(this, field, '?&', value); + }); + + dialect.conditions.add('$upper', function(field, operator, value) { + return [ + 'upper(' + this.dialect._wrapIdentifier(field) + ')', + '=', + 'upper(' + this._pushValue(value[1]) + ')' + ].join(' '); + }); + + dialect.conditions.add('$lower', function(field, operator, value) { + return [ + 'lower(' + this.dialect._wrapIdentifier(field) + ')', + '=', + 'lower(' + this._pushValue(value[1]) + ')' + ].join(' '); + }); + + dialect.conditions.add('$decode', function (field, operator, value) { + return [ + this.dialect._wrapIdentifier(field), + '=', + 'decode(' + this._pushValue(value[1]) + ',', + this._pushValue(value[2]) + ')' + ].join(' '); + }); +}; diff --git a/legacy/json-sql/lib/dialects/postgresql/index.js b/legacy/json-sql/lib/dialects/postgresql/index.js new file mode 100644 index 00000000..755dc00d --- /dev/null +++ b/legacy/json-sql/lib/dialects/postgresql/index.js @@ -0,0 +1,39 @@ +'use strict'; + +var BaseDialect = require('../base'); +var _ = require('underscore'); +var util = require('util'); +var blocksInit = require('./blocks'); +var conditionsInit = require('./conditions'); +var templatesInit = require('./templates'); + +var Dialect = module.exports = function (builder) { + BaseDialect.call(this, builder); + blocksInit(this) + conditionsInit(this); + templatesInit(this); +}; + +Dialect.prototype.config = _({}).extend(BaseDialect.prototype.config); + +util.inherits(Dialect, BaseDialect); + +Dialect.prototype.config = _({ + jsonSeparatorRegexp: /->>?/g +}).extend(BaseDialect.prototype.config); + +Dialect.prototype._wrapIdentifier = function(name) { + // split by json separator + var nameParts = name.split(this.config.jsonSeparatorRegexp); + var separators = name.match(this.config.jsonSeparatorRegexp); + + // wrap base identifier + var identifier = BaseDialect.prototype._wrapIdentifier.call(this, nameParts[0]); + + // wrap all json identifier and join them with separators + identifier += _(separators).reduce(function(memo, separator, index) { + return memo + separator + '\'' + nameParts[index + 1] + '\''; + }, ''); + + return identifier; +}; diff --git a/legacy/json-sql/lib/dialects/postgresql/templates.js b/legacy/json-sql/lib/dialects/postgresql/templates.js new file mode 100644 index 00000000..297ed8b0 --- /dev/null +++ b/legacy/json-sql/lib/dialects/postgresql/templates.js @@ -0,0 +1,269 @@ +'use strict'; + +var _ = require('underscore'); + +module.exports = function(dialect) { + var availableSourceProps = ['table', 'query', 'select', 'expression']; + var availableJoinTypes = ['natural', 'cross', 'inner', 'outer', 'left', 'right', 'full', 'self']; + var orRegExp = /^(rollback|abort|replace|fail|ignore)$/i; + + // private templates + + dialect.templates.add('query', { + pattern: '{queryBody}', + validate: function(type, params) { + hasRequiredProp(type, params, 'queryBody'); + hasObjectProp(type, params, 'queryBody'); + } + }); + + dialect.templates.add('subQuery', { + pattern: '({queryBody})', + validate: function(type, params) { + hasRequiredProp(type, params, 'queryBody'); + hasObjectProp(type, params, 'queryBody'); + } + }); + + dialect.templates.add('subUnionQuery', { + pattern: '{queryBody}', + validate: function(type, params) { + hasRequiredProp(type, params, 'queryBody'); + hasObjectProp(type, params, 'queryBody'); + } + }); + + dialect.templates.add('create', { + pattern: 'create table if not exists {table}({tableFields})', + validate: function (type, params) { + hasRequiredProp(type, params, 'table'); + hasRequiredProp(type, params, 'tableFields'); + hasArrayProp(type, params, 'tableFields'); + } + }); + + dialect.templates.add('index', { + pattern: 'create index if not exists {name} ON {table}({indexOn}) {condition}', + validate: function (type, params) { + hasRequiredProp(type, params, 'table'); + hasRequiredProp(type, params, 'name') + hasRequiredProp(type, params, 'indexOn'); + hasMinPropLength(type, params, 'name', 1); + hasMinPropLength(type, params, 'indexOn', 1); + } + }) + + + dialect.templates.add('queriesCombination', { + pattern: '{with} {queries} {sort} {limit} {offset}', + validate: function(type, params) { + hasRequiredProp(type, params, 'queries'); + hasArrayProp(type, params, 'queries'); + hasMinPropLength(type, params, 'queries', 2); + } + }); + + dialect.templates.add('queriesUnionCombination', { + pattern: '{with} {unionqueries} {sort} {limit} {offset}', + validate: function(type, params) { + hasRequiredProp(type, params, 'unionqueries'); + hasArrayProp(type, params, 'unionqueries'); + hasMinPropLength(type, params, 'unionqueries', 2); + } + }); + + + dialect.templates.add('insertValues', { + pattern: '({fields}) values {fieldValues}', + validate: function(type, params) { + hasRequiredProp('values', params, 'fields'); + hasArrayProp('values', params, 'fields'); + hasMinPropLength('values', params, 'fields', 1); + + hasRequiredProp('values', params, 'fieldValues'); + hasArrayProp('values', params, 'fieldValues'); + hasMinPropLength('values', params, 'fieldValues', 1); + } + }); + + + dialect.templates.add('joinItem', { + pattern: '{type} join {table} {query} {select} {expression} {alias} {on}', + validate: function(type, params) { + hasOneOfProps('join', params, availableSourceProps); + + if (params.type) { + hasStringProp('join', params, 'type'); + + var splitType = _(params.type.toLowerCase().split(' ')).compact(); + if (_.difference(splitType, availableJoinTypes).length) { + throw new Error('Invalid `type` property value "' + params.type + '" in `join` clause'); + } + } + } + }); + + + dialect.templates.add('withItem', { + pattern: '{name} {fields} as {query} {select} {expression}', + validate: function(type, params) { + hasRequiredProp('with', params, 'name'); + hasOneOfProps('with', params, ['query', 'select', 'expression']); + } + }); + + + dialect.templates.add('fromItem', { + pattern: '{table} {query} {select} {expression}', + validate: function(type, params) { + hasOneOfProps('from', params, availableSourceProps); + } + }); + + + // public templates + + dialect.templates.add('select', { + pattern: '{with} select {distinct} {fields} ' + + 'from {from} {table} {query} {select} {expression} {alias} ' + + '{join} {condition} {group} {sort} {limit} {offset}', + defaults: { + fields: {} + }, + validate: function(type, params) { + hasOneOfProps(type, params, availableSourceProps); + } + }); + + + dialect.templates.add('insert', { + pattern: '{with} insert {or} into {table} {values} {condition} {returning}', + validate: function(type, params) { + hasRequiredProp(type, params, 'values'); + hasObjectProp(type, params, 'values'); + hasOneOfProps(type, params, availableSourceProps); + if (params.or) { + hasStringProp(type, params, 'or'); + matchesRegExpProp(type, params, 'or', orRegExp); + } + } + }); + + + dialect.templates.add('insertornothing', { + pattern: '{with} insert {or} into {table} {values} on conflict do nothing {returning} {condition}', + validate: function(type, params) { + hasRequiredProp(type, params, 'values'); + hasObjectProp(type, params, 'values'); + hasOneOfProps(type, params, availableSourceProps); + if (params.or) { + hasStringProp(type, params, 'or'); + matchesRegExpProp(type, params, 'or', orRegExp); + } + } + }); + + + dialect.templates.add('insertorupdate', { + pattern: '{with} insert {or} into {table} {values} on conflict {conflictFields} do update {modifier} {condition} {returning}', + validate: function(type, params) { + hasRequiredProp(type, params, 'table'); + hasRequiredProp(type, params, 'values'); + hasObjectProp(type, params, 'values'); + hasRequiredProp('conflictFields', params, 'conflictFields'); + hasArrayProp('conflictFields', params, 'conflictFields'); + hasMinPropLength('conflictFields', params, 'conflictFields', 1); + hasRequiredProp(type, params, 'modifier'); + hasOneOfProps(type, params, availableSourceProps); + if (params.or) { + hasStringProp(type, params, 'or'); + matchesRegExpProp(type, params, 'or', orRegExp); + } + } + }); + + + dialect.templates.add('update', { + pattern: '{with} update {or} {table} {modifier} {condition} {returning}', + validate: function(type, params) { + hasRequiredProp(type, params, 'modifier'); + hasRequiredProp(type, params, 'table'); + if (params.or) { + hasStringProp(type, params, 'or'); + matchesRegExpProp(type, params, 'or', orRegExp); + } + } + }); + + + dialect.templates.add('remove', { + pattern: '{with} delete from {table} {condition} {returning}', + validate: function(type, params) { + hasRequiredProp(type, params, 'table'); + } + }); + + + dialect.templates.add('union', dialect.templates.get('queriesUnionCombination')); + + + dialect.templates.add('intersect', dialect.templates.get('queriesCombination')); + + + dialect.templates.add('except', dialect.templates.get('queriesCombination')); + + + // validation helpers + + function hasRequiredProp(type, params, propName) { + if (!params[propName]) { + throw new Error('`' + propName + '` property is not set in `' + type + '` clause'); + } + } + + function hasObjectProp(type, params, propName) { + if (!_.isObject(params[propName])) { + throw new Error('`' + propName + '` property should be an object in `' + type + '` clause'); + } + } + + function hasArrayProp(type, params, propName) { + if (!_.isArray(params[propName])) { + throw new Error('`' + propName + '` property should be an array in `' + type + '` clause'); + } + } + + function hasStringProp(type, params, propName) { + if (!_.isString(params.type)) { + throw new Error('`' + propName + '` property should be a string in `' + type + '` clause'); + } + } + + function hasMinPropLength(type, params, propName, length) { + if (params[propName].length < length) { + throw new Error('`' + propName + '` property should not have length less than ' + length + + ' in `' + type + '` clause'); + } + } + + function hasOneOfProps(type, params, expectedPropNames) { + var propNames = _(params).chain().keys().intersection(expectedPropNames).value(); + + if (!propNames.length) { + throw new Error('Neither `' + expectedPropNames.join('`, `') + + '` properties are not set in `' + type + '` clause'); + } + + if (propNames.length > 1) { + throw new Error('Wrong using `' + propNames.join('`, `') + '` properties together in `' + + type + '` clause'); + } + } + + function matchesRegExpProp(type, params, propName, regExp) { + if (!params[propName].match(regExp)) { + throw new Error('Invalid `' + propName + '` property value "' + params[propName] + '" in `' + + type + '` clause'); + } + } +}; diff --git a/legacy/json-sql/lib/dialects/sqlite/blocks.js b/legacy/json-sql/lib/dialects/sqlite/blocks.js new file mode 100644 index 00000000..c3531c2a --- /dev/null +++ b/legacy/json-sql/lib/dialects/sqlite/blocks.js @@ -0,0 +1,23 @@ +'use strict'; + +var _ = require('underscore'); + +module.exports = function(dialect) { + dialect.blocks.add('offset', function(params) { + var limit = ''; + + if (typeof params.limit === 'undefined') { + limit = this.buildBlock('limit', {limit: -1}) + ' '; + } + + return limit + 'offset ' + this._pushValue(params.offset); + }); + + dialect.blocks.add('unionqueries', function(params) { + var self = this; + + return _(params.unionqueries).map(function(query) { + return self.buildTemplate('subUnionQuery', {queryBody: query}); + }).join(' ' + params.type + (params.all ? ' all' : '') + ' '); + }); +}; diff --git a/legacy/json-sql/lib/dialects/sqlite/index.js b/legacy/json-sql/lib/dialects/sqlite/index.js new file mode 100644 index 00000000..72549400 --- /dev/null +++ b/legacy/json-sql/lib/dialects/sqlite/index.js @@ -0,0 +1,17 @@ +'use strict'; + +var BaseDialect = require('../base'); +var _ = require('underscore'); +var util = require('util'); +var blocksInit = require('./blocks'); +var templatesInit = require('./templates'); + +var Dialect = module.exports = function (builder) { + BaseDialect.call(this, builder); + blocksInit(this); + templatesInit(this); +}; + +util.inherits(Dialect, BaseDialect); + +Dialect.prototype.config = _({}).extend(BaseDialect.prototype.config); diff --git a/legacy/json-sql/lib/dialects/sqlite/templates.js b/legacy/json-sql/lib/dialects/sqlite/templates.js new file mode 100644 index 00000000..ac6e382f --- /dev/null +++ b/legacy/json-sql/lib/dialects/sqlite/templates.js @@ -0,0 +1,236 @@ +'use strict'; + +var _ = require('underscore'); + +module.exports = function(dialect) { + var availableSourceProps = ['table', 'query', 'select', 'expression']; + var availableJoinTypes = ['natural', 'cross', 'inner', 'outer', 'left', 'right', 'full', 'self']; + var orRegExp = /^(rollback|abort|replace|fail|ignore)$/i; + + // private templates + + dialect.templates.add('query', { + pattern: '{queryBody}', + validate: function(type, params) { + hasRequiredProp(type, params, 'queryBody'); + hasObjectProp(type, params, 'queryBody'); + } + }); + + dialect.templates.add('subQuery', { + pattern: '({queryBody})', + validate: function(type, params) { + hasRequiredProp(type, params, 'queryBody'); + hasObjectProp(type, params, 'queryBody'); + } + }); + + dialect.templates.add('subUnionQuery', { + pattern: '{queryBody}', + validate: function(type, params) { + hasRequiredProp(type, params, 'queryBody'); + hasObjectProp(type, params, 'queryBody'); + } + }); + + dialect.templates.add('create', { + pattern: 'create table if not exists {table}({tableFields})', + validate: function (type, params) { + hasRequiredProp(type, params, 'table'); + hasRequiredProp(type, params, 'tableFields'); + hasArrayProp(type, params, 'tableFields'); + } + }); + + dialect.templates.add('index', { + pattern: 'create index if not exists {name} ON {table}({indexOn}) {condition}', + validate: function (type, params) { + hasRequiredProp(type, params, 'table'); + hasRequiredProp(type, params, 'name') + hasRequiredProp(type, params, 'indexOn'); + hasMinPropLength(type, params, 'name', 1); + hasMinPropLength(type, params, 'indexOn', 1); + } + }) + + + dialect.templates.add('queriesCombination', { + pattern: '{with} {queries} {sort} {limit} {offset}', + validate: function(type, params) { + hasRequiredProp(type, params, 'queries'); + hasArrayProp(type, params, 'queries'); + hasMinPropLength(type, params, 'queries', 2); + } + }); + + dialect.templates.add('queriesUnionCombination', { + pattern: '{with} {unionqueries} {sort} {limit} {offset}', + validate: function(type, params) { + hasRequiredProp(type, params, 'unionqueries'); + hasArrayProp(type, params, 'unionqueries'); + hasMinPropLength(type, params, 'unionqueries', 2); + } + }); + + + dialect.templates.add('insertValues', { + pattern: '({fields}) values {fieldValues}', + validate: function(type, params) { + hasRequiredProp('values', params, 'fields'); + hasArrayProp('values', params, 'fields'); + hasMinPropLength('values', params, 'fields', 1); + + hasRequiredProp('values', params, 'fieldValues'); + hasArrayProp('values', params, 'fieldValues'); + hasMinPropLength('values', params, 'fieldValues', 1); + } + }); + + + dialect.templates.add('joinItem', { + pattern: '{type} join {table} {query} {select} {expression} {alias} {on}', + validate: function(type, params) { + hasOneOfProps('join', params, availableSourceProps); + + if (params.type) { + hasStringProp('join', params, 'type'); + + var splitType = _(params.type.toLowerCase().split(' ')).compact(); + if (_.difference(splitType, availableJoinTypes).length) { + throw new Error('Invalid `type` property value "' + params.type + '" in `join` clause'); + } + } + } + }); + + + dialect.templates.add('withItem', { + pattern: '{name} {fields} as {query} {select} {expression}', + validate: function(type, params) { + hasRequiredProp('with', params, 'name'); + hasOneOfProps('with', params, ['query', 'select', 'expression']); + } + }); + + + dialect.templates.add('fromItem', { + pattern: '{table} {query} {select} {expression}', + validate: function(type, params) { + hasOneOfProps('from', params, availableSourceProps); + } + }); + + + // public templates + + dialect.templates.add('select', { + pattern: '{with} select {distinct} {fields} ' + + 'from {from} {table} {query} {select} {expression} {alias} ' + + '{join} {condition} {group} {sort} {limit} {offset}', + defaults: { + fields: {} + }, + validate: function(type, params) { + hasOneOfProps(type, params, availableSourceProps); + } + }); + + + dialect.templates.add('insert', { + pattern: '{with} insert {or} into {table} {values} {condition} {returning}', + validate: function(type, params) { + hasRequiredProp(type, params, 'values'); + hasObjectProp(type, params, 'values'); + hasOneOfProps(type, params, availableSourceProps); + if (params.or) { + hasStringProp(type, params, 'or'); + matchesRegExpProp(type, params, 'or', orRegExp); + } + } + }); + + + dialect.templates.add('update', { + pattern: '{with} update {or} {table} {modifier} {condition} {returning}', + validate: function(type, params) { + hasRequiredProp(type, params, 'modifier'); + hasRequiredProp(type, params, 'table'); + if (params.or) { + hasStringProp(type, params, 'or'); + matchesRegExpProp(type, params, 'or', orRegExp); + } + } + }); + + + dialect.templates.add('remove', { + pattern: '{with} delete from {table} {condition} {returning}', + validate: function(type, params) { + hasRequiredProp(type, params, 'table'); + } + }); + + + dialect.templates.add('union', dialect.templates.get('queriesUnionCombination')); + + + dialect.templates.add('intersect', dialect.templates.get('queriesCombination')); + + + dialect.templates.add('except', dialect.templates.get('queriesCombination')); + + + // validation helpers + + function hasRequiredProp(type, params, propName) { + if (!params[propName]) { + throw new Error('`' + propName + '` property is not set in `' + type + '` clause'); + } + } + + function hasObjectProp(type, params, propName) { + if (!_.isObject(params[propName])) { + throw new Error('`' + propName + '` property should be an object in `' + type + '` clause'); + } + } + + function hasArrayProp(type, params, propName) { + if (!_.isArray(params[propName])) { + throw new Error('`' + propName + '` property should be an array in `' + type + '` clause'); + } + } + + function hasStringProp(type, params, propName) { + if (!_.isString(params.type)) { + throw new Error('`' + propName + '` property should be a string in `' + type + '` clause'); + } + } + + function hasMinPropLength(type, params, propName, length) { + if (params[propName].length < length) { + throw new Error('`' + propName + '` property should not have length less than ' + length + + ' in `' + type + '` clause'); + } + } + + function hasOneOfProps(type, params, expectedPropNames) { + var propNames = _(params).chain().keys().intersection(expectedPropNames).value(); + + if (!propNames.length) { + throw new Error('Neither `' + expectedPropNames.join('`, `') + + '` properties are not set in `' + type + '` clause'); + } + + if (propNames.length > 1) { + throw new Error('Wrong using `' + propNames.join('`, `') + '` properties together in `' + + type + '` clause'); + } + } + + function matchesRegExpProp(type, params, propName, regExp) { + if (!params[propName].match(regExp)) { + throw new Error('Invalid `' + propName + '` property value "' + params[propName] + '" in `' + + type + '` clause'); + } + } +}; diff --git a/legacy/json-sql/lib/index.js b/legacy/json-sql/lib/index.js new file mode 100644 index 00000000..55bbe02a --- /dev/null +++ b/legacy/json-sql/lib/index.js @@ -0,0 +1,8 @@ +'use strict'; + +var Builder = require('./builder'); + +module.exports = function(params) { + return new Builder(params); +}; +module.exports.Builder = Builder; diff --git a/legacy/json-sql/lib/valuesStore.js b/legacy/json-sql/lib/valuesStore.js new file mode 100644 index 00000000..29b7d395 --- /dev/null +++ b/legacy/json-sql/lib/valuesStore.js @@ -0,0 +1,31 @@ +'use strict'; + +var _ = require('underscore'); + +module.exports = ValuesStore; + +function ValuesStore(options) { + options = options || {}; + this.context = options.context || null; + this._values = options.values || {}; +} + +ValuesStore.prototype.add = ValuesStore.prototype.set = function(name, value) { + if (_.isFunction(value) && this.context) { + value = _(value).bind(this.context); + } + + this._values[name] = value; +}; + +ValuesStore.prototype.get = function(name) { + return this._values[name] || null; +}; + +ValuesStore.prototype.remove = function(name) { + delete this._values[name]; +}; + +ValuesStore.prototype.has = function(name) { + return this._values.hasOwnProperty(name); +}; diff --git a/legacy/json-sql/package.json b/legacy/json-sql/package.json new file mode 100644 index 00000000..73c407e3 --- /dev/null +++ b/legacy/json-sql/package.json @@ -0,0 +1,44 @@ +{ + "name": "json-sql", + "description": "Node.js json to sql query mapper", + "version": "0.2.6", + "author": { + "name": "Artem Zhukov", + "email": "artzhuchka@gmail.com" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/2do2go/json-sql.git" + }, + "keywords": [ + "json-sql", + "json", + "sql", + "odm", + "mapper", + "db", + "database" + ], + "dependencies": { + "underscore": "=1.8.3" + }, + "devDependencies": { + "jshint": "=2.9.2", + "chai": "=3.5.0", + "gulp": "=3.9.1", + "gulp-jshint": "=2.0.0", + "gulp-mocha": "=2.2.0" + }, + "main": "./lib/index", + "readme": "# JSON-SQL\n\nLibrary for mapping mongo-style query objects to SQL queries.\n\n## Quick Start\n\nInstall it with NPM or add it to your package.json:\n\n``` bash\n$ npm install json-sql\n```\n\nThen:\n\n``` js\nvar jsonSql = require('json-sql')();\n\nvar sql = jsonSql.build({\n\ttype: 'select',\n\ttable: 'users',\n\tfields: ['name', 'age'],\n\tcondition: {name: 'Max', id: 6}\n});\n\nsql.query\n// sql string:\n// select name, age from users where name = $p1 && id = 6;\n\nsql.values\n// hash of values:\n// { p1: 'Max' }\n```\n\n## Documentation\n\nDocumentation is available at the [./docs directory](./docs).\n\n## Examples\n\n__Select with join:__\n\n``` js\nvar sql = jsonSql.build({\n\ttype: 'select',\n\ttable: 'users',\n\tjoin: {\n\t\tdocuments: {\n\t\t\ton: {'user.id': 'documents.userId'}\n\t\t}\n\t}\n});\n\nsql.query\n// select * from users join documents on user.id = documents.userId;\n\nsql.values\n// {}\n```\n\n__Insert:__\n\n``` js\nvar sql = jsonSql.build({\n\ttype: 'insert',\n\ttable: 'users',\n\tvalues: {\n\t\tname: 'John',\n\t\tlastname: 'Snow',\n\t\tage: 24,\n\t\tgender: 'male'\n\t}\n});\n\nsql.query\n// insert into users (name, lastname, age, gender) values ($p1, $p2, 24, $p3);\n\nsql.values\n// { p1: 'John', p2: 'Snow', p3: 'male' }\n```\n\n__Update:__\n\n``` js\nvar sql = jsonSql.build({\n\ttype: 'update',\n\ttable: 'users',\n\tcondition: {\n\t\tid: 5\n\t},\n\tmodifier: {\n\t\trole: 'admin'\n\t\tage: 33\n\t}\n});\n\nsql.query\n// update users set role = $p1, age = 33 where id = 5;\n\nsql.values\n// { p1: 'admin' }\n```\n\n__Remove:__\n\n``` js\nvar sql = jsonSql.build({\n\ttype: 'remove',\n\ttable: 'users',\n\tcondition: {\n\t\tid: 5\n\t}\n});\n\nsql.query\n// delete from users where id = 5;\n\nsql.values\n// {}\n```\n\nFor more examples, take a look at the [./docs directory](./docs) or [./tests directory](./tests).\n\n## Tests\n\nClone repository from github, `cd` into cloned dir and install dev dependencies:\n\n``` bash\n$ npm install\n```\n\nThen run tests with command:\n\n``` bash\n$ gulp test\n```\n\nOr run tests coverage with command:\n\n``` bash\n$ gulp coverage\n```\n\n## License\n\n[MIT](./LICENSE)\n", + "readmeFilename": "README.md", + "bugs": { + "url": "https://github.com/2do2go/json-sql/issues" + }, + "homepage": "https://github.com/2do2go/json-sql#readme", + "_id": "json-sql@0.2.4", + "_shasum": "df8c5f345b72f421c6fc7da57116c47cae5b5216", + "_resolved": "https://github.com/LiskHQ/json-sql/tarball/master", + "_from": "https://github.com/LiskHQ/json-sql/tarball/master" +} diff --git a/legacy/json-sql/tests/0_base.js b/legacy/json-sql/tests/0_base.js new file mode 100644 index 00000000..b46774be --- /dev/null +++ b/legacy/json-sql/tests/0_base.js @@ -0,0 +1,304 @@ +'use strict'; + +var jsonSql = require('../lib')(); +var Builder = require('../lib').Builder; +var expect = require('chai').expect; + +describe('Builder', function() { + it('should have fields', function() { + expect(jsonSql).to.be.ok; + expect(jsonSql).to.be.an.instanceof(Builder); + + expect(jsonSql.dialect).to.be.ok; + + expect(jsonSql._query).to.be.equal(''); + expect(jsonSql._values).to.be.eql({}); + + expect(jsonSql.dialect.blocks).to.be.ok; + expect(jsonSql.dialect.templates).to.be.ok; + expect(jsonSql.dialect.conditions).to.be.ok; + expect(jsonSql.dialect.modifiers).to.be.ok; + expect(jsonSql.dialect.logicalOperators).to.be.ok; + }); + + it('should throw error with wrong `type` property', function() { + expect(function() { + jsonSql.build({ + type: 'wrong' + }); + }).to.throw('Unknown template type "wrong"'); + }); + + it('should throw error without `table`, `query` and `select` properties', function() { + expect(function() { + jsonSql.build({}); + }).to.throw('Neither `table`, `query`, `select`, `expression` properties ' + + 'are not set in `select` clause'); + }); + + it('should throw error with both `table` and `select` properties', function() { + expect(function() { + jsonSql.build({ + table: 'users', + select: {table: 'payments'} + }); + }).to.throw('Wrong using `table`, `select` properties together in `select` clause'); + }); + + it('should throw error with both `table` and `query` properties', function() { + expect(function() { + jsonSql.build({ + table: 'users', + query: {table: 'payments'} + }); + }).to.throw('Wrong using `table`, `query` properties together in `select` clause'); + }); + + it('should throw error with both `query` and `select` properties', function() { + expect(function() { + jsonSql.build({ + query: {table: 'payments'}, + select: {table: 'payments'} + }); + }).to.throw('Wrong using `query`, `select` properties together in `select` clause'); + }); + + it('should throw error without `name` property in `with` clause', function() { + expect(function() { + jsonSql.build({ + 'with': [{ + select: { + table: 'payments' + } + }], + table: 'users' + }); + }).to.throw('`name` property is not set in `with` clause'); + }); + + it('should throw error without `query` and `select` properties in `with` clause', function() { + expect(function() { + jsonSql.build({ + 'with': [{ + name: 'payments' + }], + table: 'users' + }); + }).to.throw('Neither `query`, `select`, `expression` properties are not set in `with` clause'); + }); + + it('should throw error with both `query` and `select` properties in `with` clause', function() { + expect(function() { + jsonSql.build({ + 'with': [{ + name: 'payments', + query: {table: 'table1'}, + select: {table: 'table2'} + }], + table: 'users' + }); + }).to.throw('Wrong using `query`, `select` properties together in `with` clause'); + }); + + it('should be ok with array in `with` clause', function() { + var result = jsonSql.build({ + 'with': [{ + name: 'payments', + select: { + table: 'payments' + } + }], + table: 'users' + }); + + expect(result.query).to.be.equal('with "payments" as (select * from "payments") select * from ' + + '"users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object in `with` clause', function() { + var result = jsonSql.build({ + 'with': { + payments: { + select: { + table: 'payments' + } + } + }, + table: 'users' + }); + + expect(result.query).to.be.equal('with "payments" as (select * from "payments") select * from ' + + '"users";'); + expect(result.values).to.be.eql({}); + }); + + it('should create array values with option `namedValues` = false', function() { + jsonSql.configure({ + namedValues: false + }); + + expect(jsonSql._values).to.be.eql([]); + + var result = jsonSql.build({ + table: 'users', + condition: {name: 'John'} + }); + + expect(result.query).to.be.equal('select * from "users" where "name" = ${1};'); + expect(result.values).to.be.eql(['John']); + }); + + it('should use prefix `@` for values with option `valuesPrefix` = @', function() { + jsonSql.configure({ + valuesPrefix: '@' + }); + + var result = jsonSql.build({ + table: 'users', + condition: {name: 'John'} + }); + + expect(result.query).to.be.equal('select * from "users" where "name" = @{p1};'); + expect(result.values).to.be.eql({p1: 'John'}); + }); + + it('should return prefixed values with method `prefixValues`', function() { + var result = jsonSql.build({ + table: 'users', + condition: {name: 'John'} + }); + + expect(result.query).to.be.equal('select * from "users" where "name" = @{p1};'); + expect(result.values).to.be.eql({p1: 'John'}); + expect(result.prefixValues()).to.be.eql({'@{p1}': 'John'}); + }); + + it('should return array values with method `getValuesArray`', function() { + var result = jsonSql.build({ + table: 'users', + condition: {name: 'John'} + }); + + expect(result.query).to.be.equal('select * from "users" where "name" = @{p1};'); + expect(result.values).to.be.eql({p1: 'John'}); + expect(result.getValuesArray()).to.be.eql(['John']); + }); + + it('should return object values with method `getValuesObject`', function() { + jsonSql.configure({ + valuesPrefix: '$', + namedValues: false + }); + + expect(jsonSql._values).to.be.eql([]); + + var result = jsonSql.build({ + table: 'users', + condition: {name: 'John'} + }); + + expect(result.query).to.be.equal('select * from "users" where "name" = ${1};'); + expect(result.values).to.be.eql(['John']); + expect(result.prefixValues()).to.be.eql({'${1}': 'John'}); + expect(result.getValuesObject()).to.be.eql({1: 'John'}); + }); + + it('should create query without values with option `separatedValues` = false', function() { + jsonSql.configure({ + separatedValues: false + }); + + expect(jsonSql._values).to.not.be.ok; + expect(jsonSql._placeholderId).to.not.be.ok; + + var result = jsonSql.build({ + type: 'insert', + table: 'users', + values: {name: 'John', surname: 'Doe'} + }); + + expect(result.query).to.be.equal('insert into "users" ("name", "surname") values ' + + '(\'John\', \'Doe\');'); + expect(result.values).to.not.be.ok; + }); + + it('should create query without wrapping identifiers with option `wrappedIdentifiers` = false', + function() { + jsonSql.configure({ + wrappedIdentifiers: false + }); + + var result = jsonSql.build({ + type: 'insert', + table: 'users', + values: {name: 'John'} + }); + + expect(result.query).to.be.equal('insert into users (name) values (${p1});'); + } + ); + + it('shouldn\'t wrap identifiers that already wrapped', function() { + jsonSql.configure({ + wrappedIdentifiers: true + }); + + var result = jsonSql.build({ + type: 'insert', + table: '"users"', + values: { + '"name"': 'John', + '"users"."age"': 22 + } + }); + + expect(result.query).to.be.equal('insert into "users" ("name", "users"."age") values (${p1}, 22);'); + }); + + it('shouldn\'t split identifiers by dots inside quotes', function() { + jsonSql.configure({ + wrappedIdentifiers: true + }); + + var result = jsonSql.build({ + type: 'insert', + table: '"users"', + values: { + '"users.age"': 22 + } + }); + + expect(result.query).to.be.equal('insert into "users" ("users.age") values (22);'); + }); + + it('shouldn\'t wrap asterisk identifier parts', function() { + jsonSql.configure({ + wrappedIdentifiers: true + }); + + var result = jsonSql.build({ + fields: ['users.*'], + table: '"users"' + }); + + expect(result.query).to.be.equal('select "users".* from "users";'); + }); + + it('should split identifiers by dots and wrap each part', function() { + jsonSql.configure({ + wrappedIdentifiers: true + }); + + var result = jsonSql.build({ + type: 'insert', + table: '"users"', + values: { + 'name': 'John', + 'users.age': 22 + } + }); + + expect(result.query).to.be.equal('insert into "users" ("name", "users"."age") values (${p1}, 22);'); + }); +}); diff --git a/legacy/json-sql/tests/1_select.js b/legacy/json-sql/tests/1_select.js new file mode 100644 index 00000000..087a1afb --- /dev/null +++ b/legacy/json-sql/tests/1_select.js @@ -0,0 +1,1222 @@ +'use strict'; + +var jsonSql = require('../lib')(); +var expect = require('chai').expect; + +describe('Select', function() { + describe('type', function() { + it('should be ok without `type` property', function() { + var result = jsonSql.build({ + table: 'users' + }); + + expect(result.query).to.be.equal('select * from "users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with "select" value', function() { + var result = jsonSql.build({ + type: 'select', + table: 'users' + }); + + expect(result.query).to.be.equal('select * from "users";'); + expect(result.values).to.be.eql({}); + }); + }); + + describe('distinct', function() { + it('should be ok with true value', function() { + var result = jsonSql.build({ + table: 'users', + distinct: true + }); + + expect(result.query).to.be.equal('select distinct * from "users";'); + expect(result.values).to.be.eql({}); + }); + }); + + describe('fields', function() { + it('should be ok with string array', function() { + var result = jsonSql.build({ + table: 'users', + fields: ['name', 'type'] + }); + + expect(result.query).to.be.equal('select "name", "type" from "users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object(`name`: `alias`, ...)', function() { + var result = jsonSql.build({ + table: 'users', + fields: {userAge: 'age', userScore: 'score'} + }); + + expect(result.query).to.be.equal('select "userAge" as "age", "userScore" as "score" from ' + + '"users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with array of objects(`name`: `alias`, ...)', function() { + var result = jsonSql.build({ + table: 'users', + fields: [{userAge: 'age'}] + }); + + expect(result.query).to.be.equal('select "userAge" as "age" from "users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object(`field`) array', function() { + var result = jsonSql.build({ + table: 'users', + fields: [{field: 'address'}] + }); + + expect(result.query).to.be.equal('select "address" from "users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object(`field`, `table`) array', function() { + var result = jsonSql.build({ + table: 'users', + fields: [{field: 'score', table: 'users'}] + }); + + expect(result.query).to.be.equal('select "users"."score" from "users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object(`field`, `alias`) array', function() { + var result = jsonSql.build({ + table: 'users', + fields: [{field: 'zoneName', alias: 'zone'}] + }); + + expect(result.query).to.be.equal('select "zoneName" as "zone" from "users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object(`field`, `table`, `alias`) array', function() { + var result = jsonSql.build({ + table: 'users', + fields: [{field: 'zoneName', table: 'users', alias: 'zone'}] + }); + + expect(result.query).to.be.equal('select "users"."zoneName" as "zone" from "users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object(`table`)', function() { + var result = jsonSql.build({ + table: 'users', + fields: {score: {table: 'users'}} + }); + + expect(result.query).to.be.equal('select "users"."score" from "users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object(`field`, `alias`)', function() { + var result = jsonSql.build({ + table: 'users', + fields: {zone: {field: 'zone_1', alias: 'zone'}} + }); + + expect(result.query).to.be.equal('select "zone_1" as "zone" from "users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object(`table`, `alias`)', function() { + var result = jsonSql.build({ + table: 'users', + fields: {score: {table: 'users', alias: 's'}} + }); + + expect(result.query).to.be.equal('select "users"."score" as "s" from "users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object(`field`, `table`, `alias`)', function() { + var result = jsonSql.build({ + table: 'users', + fields: {name: {field: 'name_1', table: 'users', alias: 'name_2'}} + }); + + expect(result.query).to.be.equal('select "users"."name_1" as "name_2" from "users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object(`expression`)', function() { + var result = jsonSql.build({ + table: 'users', + fields: [{ + expression: 'count(*)' + }] + }); + + expect(result.query).to.be.equal('select count(*) from "users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object(`expression`, `alias`)', function() { + var result = jsonSql.build({ + table: 'users', + fields: [{ + expression: 'count(*)', + alias: 'count' + }] + }); + + expect(result.query).to.be.equal('select count(*) as "count" from "users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object(`expression`, `field`, `alias`)', function() { + var result = jsonSql.build({ + table: 'users', + fields: [{ + expression: 'sum', + field: 'income', + alias: 'sum' + }] + }); + + expect(result.query).to.be.equal('select sum("income") as "sum" from "users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object(`expression`[], `field`, `alias`)', function() { + var result = jsonSql.build({ + table: 'users', + fields: [{ + expression: ['abs', 'sum'], + field: 'income', + alias: 'sum' + }] + }); + + expect(result.query).to.be.equal('select abs(sum("income")) as "sum" from "users";'); + expect(result.values).to.be.eql({}); + }); + }); + + describe('alias', function() { + it('should be ok with string `alias` property', function() { + var result = jsonSql.build({ + table: 'users', + alias: 'u' + }); + + expect(result.query).to.be.equal('select * from "users" as "u";'); + expect(result.values).to.be.eql({}); + }); + + it('should throw error if object `alias` does not have `name` property', function() { + expect(function() { + jsonSql.build({ + table: 'users', + alias: {} + }); + }).to.throw('Alias `name` property is required'); + }); + + it('should be ok with object `alias`(`name`) property', function() { + var result = jsonSql.build({ + table: 'users', + alias: { + name: 'u' + } + }); + + expect(result.query).to.be.equal('select * from "users" as "u";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object `alias`(`name`, `columns`) property', function() { + var result = jsonSql.build({ + table: 'users', + alias: { + name: 'u', + columns: ['a', 'b'] + } + }); + + expect(result.query).to.be.equal('select * from "users" as "u"("a", "b");'); + expect(result.values).to.be.eql({}); + }); + }); + + describe('query', function() { + it('should be ok with `query` property', function() { + var result = jsonSql.build({ + query: { + type: 'select', + table: 't' + } + }); + + expect(result.query).to.be.equal('select * from (select * from "t");'); + expect(result.values).to.be.eql({}); + }); + }); + + describe('select', function() { + it('should be ok with `select` property', function() { + var result = jsonSql.build({ + select: { + table: 't' + } + }); + + expect(result.query).to.be.equal('select * from (select * from "t");'); + expect(result.values).to.be.eql({}); + }); + }); + + describe('expression', function() { + it('should be ok with `expression` property', function() { + var result = jsonSql.build({ + expression: 'function()' + }); + + expect(result.query).to.be.equal('select * from function();'); + expect(result.values).to.be.eql({}); + }); + }); + + describe('join', function() { + it('should throw error without `table`, `query` and `select` properties', + function() { + expect(function() { + jsonSql.build({ + table: 'users', + join: [{}] + }); + }).to.throw('Neither `table`, `query`, `select`, `expression` properties ' + + 'are not set in `join` clause'); + } + ); + + it('should throw error with both `table` and `select` properties', function() { + expect(function() { + jsonSql.build({ + table: 'users', + join: [{ + table: 'a', + select: {table: 'b'} + }] + }); + }).to.throw('Wrong using `table`, `select` properties together in `join` clause'); + }); + + it('should throw error with both `table` and `query` properties', function() { + expect(function() { + jsonSql.build({ + table: 'users', + join: [{ + table: 'a', + query: {table: 'b'} + }] + }); + }).to.throw('Wrong using `table`, `query` properties together in `join` clause'); + }); + + it('should throw error with both `query` and `select` properties', function() { + expect(function() { + jsonSql.build({ + table: 'users', + join: [{ + query: 'a', + select: {table: 'b'} + }] + }); + }).to.throw('Wrong using `query`, `select` properties together in `join` clause'); + }); + + it('should throw error with wrong `type` property', function() { + expect(function() { + jsonSql.build({ + table: 'users', + join: [{ + type: 'wrong', + table: 'payments' + }] + }); + }).to.throw('Invalid `type` property value "wrong" in `join` clause'); + }); + + it('should be ok with correct `type` property', function() { + var result = jsonSql.build({ + table: 'users', + join: [{ + type: 'left outer', + table: 'payments' + }] + }); + + expect(result.query).to.be.equal('select * from "users" left outer join "payments";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with array `join`', function() { + var result = jsonSql.build({ + table: 'users', + join: [{ + table: 'payments' + }] + }); + + expect(result.query).to.be.equal('select * from "users" join "payments";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object `join`', function() { + var result = jsonSql.build({ + table: 'users', + join: { + payments: {} + } + }); + + expect(result.query).to.be.equal('select * from "users" join "payments";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `on` property', function() { + var result = jsonSql.build({ + table: 'users', + join: { + payments: { + on: {'users.name': 'payments.name'} + } + } + }); + + expect(result.query).to.be.equal('select * from "users" join "payments" on "users"."name" = ' + + '"payments"."name";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `query` property', function() { + var result = jsonSql.build({ + table: 'users', + join: [{ + query: { + table: 'payments' + }, + on: {'users.name': 'payments.name'} + }] + }); + + expect(result.query).to.be.equal( + 'select * from "users" ' + + 'join (select * from "payments") ' + + 'on "users"."name" = "payments"."name";' + ); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `select` property', function() { + var result = jsonSql.build({ + table: 'users', + join: [{ + select: { + table: 'payments' + }, + on: {'users.name': 'payments.name'} + }] + }); + + expect(result.query).to.be.equal( + 'select * from "users" ' + + 'join (select * from "payments") ' + + 'on "users"."name" = "payments"."name";' + ); + expect(result.values).to.be.eql({}); + }); + }); + + describe('condition', function() { + describe('compare operators', function() { + it('should throw error with wrong operator', function() { + expect(function() { + jsonSql.build({ + table: 'users', + condition: { + name: {$wrong: 'John'} + } + }); + }).to.throw('Unknown operator "$wrong"'); + }); + + it('should be ok with default operator(=)', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + name: 'John' + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" = ${p1};'); + expect(result.values).to.be.eql({ + p1: 'John' + }); + }); + + it('should be ok with `$eq` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + name: {$eq: 'John'} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" = ${p1};'); + expect(result.values).to.be.eql({ + p1: 'John' + }); + }); + + it('should be ok with `$ne` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + name: {$ne: 'John'} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" != ${p1};'); + expect(result.values).to.be.eql({ + p1: 'John' + }); + }); + + it('should be ok with `$gt` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + name: {$gt: 'John'} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" > ${p1};'); + expect(result.values).to.be.eql({ + p1: 'John' + }); + }); + + it('should be ok with `$lt` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + name: {$lt: 'John'} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" < ${p1};'); + expect(result.values).to.be.eql({ + p1: 'John' + }); + }); + + it('should be ok with `$gte` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + name: {$gte: 'John'} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" >= ${p1};'); + expect(result.values).to.be.eql({ + p1: 'John' + }); + }); + + it('should be ok with `$lte` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + name: {$lte: 'John'} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" <= ${p1};'); + expect(result.values).to.be.eql({ + p1: 'John' + }); + }); + + it('should be ok with `$is` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + name: {$is: null} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" is null;'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `$isnot` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + name: {$isnot: null} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" is not null;'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `$like` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + name: {$like: 'John%'} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" like ${p1};'); + expect(result.values).to.be.eql({ + p1: 'John%' + }); + }); + + it('should be ok with `$null`:true operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + name: {$null: true} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" is null;'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `$null`:false operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + name: {$null: false} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" is not null;'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `$field` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + name: {$field: 'name_2'} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" = "name_2";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object `$field` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + name: {$field: {field: 'name_2'}} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" = "name_2";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `$in` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + age: {$in: [12, 13, 14]} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "age" in (12, 13, 14);'); + expect(result.values).to.be.eql({}); + }); + + it('should add `null` value with empty array in `$in` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + age: {$in: []} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "age" in (null);'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `$nin` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + age: {$nin: [12, 13, 14]} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "age" not in (12, 13, 14);'); + expect(result.values).to.be.eql({}); + }); + + it('should add `null` value with empty array in `$nin` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + age: {$nin: []} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "age" not in (null);'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object subquery in `$in` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + age: {$in: { + table: 'test' + }} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "age" in (select * from ' + + '"test");'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `query` subquery in `$in` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + age: {$in: { + query: { + table: 'test' + } + }} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "age" in (select * from ' + + '(select * from "test"));'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `select` subquery in `$in` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + age: {$in: { + select: { + table: 'test' + } + }} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "age" in (select * from ' + + '(select * from "test"));'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `$between` operator', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + age: {$between: [12, 14]} + } + }); + + expect(result.query).to.be.equal('select * from "users" where "age" between 12 and 14;'); + expect(result.values).to.be.eql({}); + }); + }); + + describe('logical operators', function() { + it('should throw error with wrong logical operator', function() { + expect(function() { + jsonSql.build({ + table: 'users', + condition: { + $wrong: [ + {name: 'John'}, + {age: 12} + ] + } + }); + }).to.throw('Unknown logical operator "$wrong"'); + }); + + it('should be ok with default logical operator(`$and`)', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + name: 'John', + age: 12 + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" = ${p1} and "age" = 12;'); + expect(result.values).to.be.eql({ + p1: 'John' + }); + }); + + it('should be ok with default logical operator(`$and`) for one field', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + age: { + $gt: 5, + $lt: 15, + $ne: 10 + } + } + }); + + expect(result.query).to.be.equal('select * from "users" where "age" > 5 and "age" < 15 and ' + + '"age" != 10;'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with array `$and`', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + $and: [ + {name: 'John'}, + {age: 12} + ] + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" = ${p1} and "age" = 12;'); + expect(result.values).to.be.eql({ + p1: 'John' + }); + }); + + it('should be ok with object `$and`', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + $and: { + name: 'John', + age: 12 + } + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" = ${p1} and "age" = 12;'); + expect(result.values).to.be.eql({ + p1: 'John' + }); + }); + + it('should be ok with array `$or`', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + $or: [ + {name: 'John'}, + {age: 12} + ] + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" = ${p1} or "age" = 12;'); + expect(result.values).to.be.eql({ + p1: 'John' + }); + }); + + it('should be ok with object `$or`', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + $or: { + name: 'John', + age: 12 + } + } + }); + + expect(result.query).to.be.equal('select * from "users" where "name" = ${p1} or "age" = 12;'); + expect(result.values).to.be.eql({ + p1: 'John' + }); + }); + + it('should be ok with array `$not`', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + $not: [ + {name: 'John'}, + {age: 12} + ] + } + }); + + expect(result.query).to.be.equal('select * from "users" where not ("name" = ${p1} and ' + + '"age" = 12);'); + expect(result.values).to.be.eql({ + p1: 'John' + }); + }); + + it('should be ok with object `$not`', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + $not: { + name: 'John', + age: 12 + } + } + }); + + expect(result.query).to.be.equal('select * from "users" where not ("name" = ${p1} and ' + + '"age" = 12);'); + expect(result.values).to.be.eql({ + p1: 'John' + }); + }); + + it('should be ok with object [`$or`, `$or`]', function() { + var result = jsonSql.build({ + table: 'users', + condition: [{ + $or: { + name: 'John', + age: 12 + } + }, { + $or: { + name: 'Mark', + age: 14 + } + }] + }); + + expect(result.query).to.be.equal( + 'select * from "users" ' + + 'where ("name" = ${p1} or "age" = 12) and ' + + '("name" = ${p2} or "age" = 14);' + ); + expect(result.values).to.be.eql({ + p1: 'John', + p2: 'Mark' + }); + }); + + it('should be ok with object `$and`:[`$or`, `$or`]', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + $and: [{ + $or: { + name: 'John', + age: 12 + } + }, { + $or: { + name: 'Mark', + age: 14 + } + }] + } + }); + + expect(result.query).to.be.equal( + 'select * from "users" ' + + 'where ("name" = ${p1} or "age" = 12) and ' + + '("name" = ${p2} or "age" = 14);' + ); + expect(result.values).to.be.eql({ + p1: 'John', + p2: 'Mark' + }); + }); + + it('should be ok with object `$or`:[{},{}]', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + $or: [{ + name: 'John', + age: 12 + }, { + name: 'Mark', + age: 14 + }] + } + }); + + expect(result.query).to.be.equal( + 'select * from "users" ' + + 'where ("name" = ${p1} and "age" = 12) or ' + + '("name" = ${p2} and "age" = 14);' + ); + expect(result.values).to.be.eql({ + p1: 'John', + p2: 'Mark' + }); + }); + + it('should be ok with object `$or`:[`$and`, `$and`]', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + $or: [{ + $and: { + name: 'John', + age: 12 + } + }, { + $and: { + name: 'Mark', + age: 14 + } + }] + } + }); + + expect(result.query).to.be.equal( + 'select * from "users" ' + + 'where ("name" = ${p1} and "age" = 12) or ' + + '("name" = ${p2} and "age" = 14);' + ); + expect(result.values).to.be.eql({ + p1: 'John', + p2: 'Mark' + }); + }); + + it('should be ok with [{}, {}]', function() { + var result = jsonSql.build({ + table: 'users', + condition: [{ + name: 'John', + age: 12 + }, { + name: 'Mark', + age: 14 + }] + }); + + expect(result.query).to.be.equal( + 'select * from "users" ' + + 'where ("name" = ${p1} and "age" = 12) and ' + + '("name" = ${p2} and "age" = 14);'); + expect(result.values).to.be.eql({ + p1: 'John', + p2: 'Mark' + }); + }); + + it('should be ok with `$and`:[{}, {}]', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + $and: [{ + name: 'John', + age: 12 + }, { + name: 'Mark', + age: 14 + }] + } + }); + + expect(result.query).to.be.equal( + 'select * from "users" ' + + 'where ("name" = ${p1} and "age" = 12) and ' + + '("name" = ${p2} and "age" = 14);' + ); + expect(result.values).to.be.eql({ + p1: 'John', + p2: 'Mark' + }); + }); + + it('should be ok with `$and`:[`$and`, `$and`]', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + $and: [{ + $and: { + name: 'John', + age: 12 + } + }, { + $and: { + name: 'Mark', + age: 14 + } + }] + } + }); + + expect(result.query).to.be.equal( + 'select * from "users" ' + + 'where ("name" = ${p1} and "age" = 12) and ' + + '("name" = ${p2} and "age" = 14);' + ); + expect(result.values).to.be.eql({ + p1: 'John', + p2: 'Mark' + }); + }); + + it('should be ok with `$or`:[`$or`, `$or`]', function() { + var result = jsonSql.build({ + table: 'users', + condition: { + $or: [{ + $or: { + name: 'John', + age: 12 + } + }, { + $or: { + name: 'Mark', + age: 14 + } + }] + } + }); + + expect(result.query).to.be.equal( + 'select * from "users" ' + + 'where ("name" = ${p1} or "age" = 12) or ' + + '("name" = ${p2} or "age" = 14);' + ); + expect(result.values).to.be.eql({ + p1: 'John', + p2: 'Mark' + }); + }); + }); + }); + + describe('group', function() { + it('should be ok with string value', function() { + var result = jsonSql.build({ + table: 'users', + group: 'age' + }); + + expect(result.query).to.be.equal( + 'select * from "users" group by "age";' + ); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with array value', function() { + var result = jsonSql.build({ + table: 'users', + group: ['age', 'gender'] + }); + + expect(result.query).to.be.equal( + 'select * from "users" group by "age", "gender";' + ); + expect(result.values).to.be.eql({}); + }); + }); + + describe('sort', function() { + it('should be ok with string value', function() { + var result = jsonSql.build({ + table: 'users', + sort: 'age' + }); + + expect(result.query).to.be.equal( + 'select * from "users" order by "age";' + ); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with array value', function() { + var result = jsonSql.build({ + table: 'users', + sort: ['age', 'gender'] + }); + + expect(result.query).to.be.equal( + 'select * from "users" order by "age", "gender";' + ); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object value', function() { + var result = jsonSql.build({ + table: 'users', + sort: { + age: 1, + gender: -1 + } + }); + + expect(result.query).to.be.equal( + 'select * from "users" order by "age" asc, "gender" desc;' + ); + expect(result.values).to.be.eql({}); + }); + }); + + describe('limit, offset', function() { + it('should be ok with `limit` property', function() { + var result = jsonSql.build({ + table: 'users', + limit: 5 + }); + + expect(result.query).to.be.equal( + 'select * from "users" limit 5;' + ); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `offset` property', function() { + var result = jsonSql.build({ + table: 'users', + offset: 5 + }); + + expect(result.query).to.be.equal( + 'select * from "users" offset 5;' + ); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `limit` and `offset` properties', function() { + var result = jsonSql.build({ + table: 'users', + limit: 10, + offset: 20 + }); + + expect(result.query).to.be.equal( + 'select * from "users" limit 10 offset 20;' + ); + expect(result.values).to.be.eql({}); + }); + }); +}); diff --git a/legacy/json-sql/tests/2_insert.js b/legacy/json-sql/tests/2_insert.js new file mode 100644 index 00000000..fe3f1b9d --- /dev/null +++ b/legacy/json-sql/tests/2_insert.js @@ -0,0 +1,73 @@ +'use strict'; + +var jsonSql = require('../lib')(); +var expect = require('chai').expect; + +describe('Insert', function() { + it('should throw error without `values` property', function() { + expect(function() { + jsonSql.build({ + type: 'insert', + table: 'users' + }); + }).to.throw('`values` property is not set in `insert` clause'); + }); + + it('should be ok with `values` property', function() { + var result = jsonSql.build({ + type: 'insert', + table: 'users', + values: { + name: 'Max' + } + }); + + expect(result.query).to.be.equal('insert into "users" ("name") values (${p1});'); + expect(result.values).to.be.eql({p1: 'Max'}); + }); + + it('should be ok with `with` property', function() { + var result = jsonSql.build({ + 'with': [{ + name: 't_1', + select: { + table: 't_1' + } + }], + type: 'insert', + table: 'users', + values: { + name: 'Max', + age: 17, + lastVisit: null, + active: true + } + }); + + expect(result.query).to.be.equal( + 'with "t_1" as (select * from "t_1") insert into "users" ' + + '("name", "age", "lastVisit", "active") values (${p1}, 17, null, true);' + ); + expect(result.values).to.be.eql({p1: 'Max'}); + }); + + it('should be ok with `returning` property', function() { + var result = jsonSql.build({ + type: 'insert', + table: 'users', + values: { + name: 'Max', + age: 17, + lastVisit: null, + active: true + }, + returning: ['users.*'] + }); + + expect(result.query).to.be.equal( + 'insert into "users" ("name", "age", "lastVisit", "active") ' + + 'values (${p1}, 17, null, true) returning "users".*;' + ); + expect(result.values).to.be.eql({p1: 'Max'}); + }); +}); diff --git a/legacy/json-sql/tests/3_update.js b/legacy/json-sql/tests/3_update.js new file mode 100644 index 00000000..688b4c1d --- /dev/null +++ b/legacy/json-sql/tests/3_update.js @@ -0,0 +1,123 @@ +'use strict'; + +var jsonSql = require('../lib')(); +var expect = require('chai').expect; + +describe('Update', function() { + describe('modifier', function() { + it('should throw error without `modifier` property', function() { + expect(function() { + jsonSql.build({ + type: 'update', + table: 'users' + }); + }).to.throw('`modifier` property is not set in `update` clause'); + }); + + it('should be ok with default(`$set`)', function() { + var result = jsonSql.build({ + type: 'update', + table: 'users', + modifier: { + name: 'Max', + age: 16, + lastVisit: null, + active: false + } + }); + + expect(result.query).to.be.equal('update "users" set "name" = ${p1}, "age" = 16, ' + + '"lastVisit" = null, "active" = false;'); + expect(result.values).to.be.eql({p1: 'Max'}); + }); + + it('should be ok with `$set`', function() { + var result = jsonSql.build({ + type: 'update', + table: 'users', + modifier: { + $set: { + name: 'Max' + } + } + }); + + expect(result.query).to.be.equal('update "users" set "name" = ${p1};'); + expect(result.values).to.be.eql({p1: 'Max'}); + }); + + it('should be ok with `$inc`', function() { + var result = jsonSql.build({ + type: 'update', + table: 'users', + modifier: { + $inc: { + age: 4 + } + } + }); + + expect(result.query).to.be.equal('update "users" set "age" = "age" + 4;'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `$dec`', function() { + var result = jsonSql.build({ + type: 'update', + table: 'users', + modifier: { + $dec: { + age: 2 + } + } + }); + + expect(result.query).to.be.equal('update "users" set "age" = "age" - 2;'); + expect(result.values).to.be.eql({}); + }); + }); + + describe('with', function() { + it('should be ok', function() { + var result = jsonSql.build({ + 'with': [{ + name: 't_1', + select: { + table: 't_1' + } + }], + type: 'update', + table: 'users', + modifier: { + $dec: { + age: 3 + } + } + }); + + expect(result.query).to.be.equal('with "t_1" as (select * from "t_1") update "users" ' + + 'set "age" = "age" - 3;'); + expect(result.values).to.be.eql({}); + }); + }); + + describe('returning', function() { + it('should be ok', function() { + var result = jsonSql.build({ + type: 'update', + table: 'users', + modifier: { + $dec: { + age: 3 + } + }, + returning: ['users.*'] + }); + + expect(result.query).to.be.equal( + 'update "users" set "age" = "age" - 3 returning "users".*;' + ); + expect(result.values).to.be.eql({}); + }); + }); +}); diff --git a/legacy/json-sql/tests/4_delete.js b/legacy/json-sql/tests/4_delete.js new file mode 100644 index 00000000..c90f19f2 --- /dev/null +++ b/legacy/json-sql/tests/4_delete.js @@ -0,0 +1,58 @@ +'use strict'; + +var jsonSql = require('../lib')(); +var expect = require('chai').expect; + +describe('Delete', function() { + it('should be ok without `condition` property', function() { + var result = jsonSql.build({ + type: 'remove', + table: 'users' + }); + + expect(result.query).to.be.equal('delete from "users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `condition` property', function() { + var result = jsonSql.build({ + type: 'remove', + table: 'users', + condition: { + a: 5 + } + }); + + expect(result.query).to.be.equal('delete from "users" where "a" = 5;'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `with` property', function() { + var result = jsonSql.build({ + 'with': [{ + name: 't_1', + select: { + table: 't_1' + } + }], + type: 'remove', + table: 'users' + }); + + expect(result.query).to.be.equal('with "t_1" as (select * from "t_1") delete from "users";'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `returning` property', function() { + var result = jsonSql.build({ + type: 'remove', + table: 'users', + returning: ['users.*'] + }); + + expect(result.query).to.be.equal( + 'delete from "users" returning "users".*;' + ); + expect(result.values).to.be.eql({}); + }); +}); diff --git a/legacy/json-sql/tests/5_union.js b/legacy/json-sql/tests/5_union.js new file mode 100644 index 00000000..a1e79fcf --- /dev/null +++ b/legacy/json-sql/tests/5_union.js @@ -0,0 +1,256 @@ +'use strict'; + +var jsonSql = require('../lib')(); +var expect = require('chai').expect; + +describe('Union, except, intersect', function() { + describe('queries', function() { + it('should throw error without `queries` property', function() { + expect(function() { + jsonSql.build({ + type: 'union' + }); + }).to.throw('`queries` property is not set in `union` clause'); + }); + + it('should throw error with non-array value', function() { + expect(function() { + jsonSql.build({ + type: 'union', + queries: 'wrong' + }); + }).to.throw('`queries` property should be an array in `union` clause'); + }); + + it('should throw error with value length < 2', function() { + expect(function() { + jsonSql.build({ + type: 'union', + queries: [{ + table: 'users' + }] + }); + }).to.throw('`queries` property should not have length less than 2 in `union` clause'); + }); + + it('should be ok with value length = 2', function() { + var result = jsonSql.build({ + type: 'union', + queries: [{ + table: 'users' + }, { + table: 'vipUsers' + }] + }); + + expect(result.query).to.be.equal('(select * from "users") union (select * from "vipUsers");'); + expect(result.values).to.be.eql({}); + }); + }); + + describe('type & all combinations', function() { + it('should be ok with `type` = "union", `all` = true', function() { + var result = jsonSql.build({ + type: 'union', + all: true, + queries: [{ + table: 'users' + }, { + table: 'vipUsers' + }] + }); + + expect(result.query).to.be.equal('(select * from "users") union all (select * from ' + + '"vipUsers");'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `type` = "except"', function() { + var result = jsonSql.build({ + type: 'except', + queries: [{ + table: 'users' + }, { + table: 'vipUsers' + }] + }); + + expect(result.query).to.be.equal('(select * from "users") except (select * from "vipUsers");'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `type` = "except", `all` = true', function() { + var result = jsonSql.build({ + type: 'except', + all: true, + queries: [{ + table: 'users' + }, { + table: 'vipUsers' + }] + }); + + expect(result.query).to.be.equal('(select * from "users") except all (select * from ' + + '"vipUsers");'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `type` = "intersect"', function() { + var result = jsonSql.build({ + type: 'intersect', + queries: [{ + table: 'users' + }, { + table: 'vipUsers' + }] + }); + + expect(result.query).to.be.equal('(select * from "users") intersect (select * from ' + + '"vipUsers");'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `type` = "intersect", `all` = true', function() { + var result = jsonSql.build({ + type: 'intersect', + all: true, + queries: [{ + table: 'users' + }, { + table: 'vipUsers' + }] + }); + + expect(result.query).to.be.equal('(select * from "users") intersect all (select * from ' + + '"vipUsers");'); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `type` = "union" subquery', function() { + var result = jsonSql.build({ + query: { + type: 'union', + queries: [{ + table: 'users' + }, { + table: 'vipUsers' + }] + } + }); + + expect(result.query).to.be.equal('select * from ((select * from "users") union (select * ' + + 'from "vipUsers"));'); + expect(result.values).to.be.eql({}); + }); + }); + + describe('sort', function() { + it('should be ok with string value', function() { + var result = jsonSql.build({ + type: 'union', + queries: [{ + table: 'users' + }, { + table: 'vipUsers' + }], + sort: 'age' + }); + + expect(result.query).to.be.equal( + '(select * from "users") union (select * from "vipUsers") order by "age";' + ); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with array value', function() { + var result = jsonSql.build({ + type: 'union', + queries: [{ + table: 'users' + }, { + table: 'vipUsers' + }], + sort: ['age', 'gender'] + }); + + expect(result.query).to.be.equal( + '(select * from "users") union (select * from "vipUsers") order by "age", "gender";' + ); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with object value', function() { + var result = jsonSql.build({ + type: 'union', + queries: [{ + table: 'users' + }, { + table: 'vipUsers' + }], + sort: { + age: 1, + gender: -1 + } + }); + + expect(result.query).to.be.equal( + '(select * from "users") union (select * from "vipUsers") order by "age" asc, "gender" desc;' + ); + expect(result.values).to.be.eql({}); + }); + }); + + describe('limit, offset', function() { + it('should be ok with `limit` property', function() { + var result = jsonSql.build({ + type: 'union', + queries: [{ + table: 'users' + }, { + table: 'vipUsers' + }], + limit: 5 + }); + + expect(result.query).to.be.equal( + '(select * from "users") union (select * from "vipUsers") limit 5;' + ); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `offset` property', function() { + var result = jsonSql.build({ + type: 'union', + queries: [{ + table: 'users' + }, { + table: 'vipUsers' + }], + offset: 5 + }); + + expect(result.query).to.be.equal( + '(select * from "users") union (select * from "vipUsers") offset 5;' + ); + expect(result.values).to.be.eql({}); + }); + + it('should be ok with `limit` and `offset` properties', function() { + var result = jsonSql.build({ + type: 'union', + queries: [{ + table: 'users' + }, { + table: 'vipUsers' + }], + limit: 10, + offset: 20 + }); + + expect(result.query).to.be.equal( + '(select * from "users") union (select * from "vipUsers") limit 10 offset 20;' + ); + expect(result.values).to.be.eql({}); + }); + }); +}); diff --git a/legacy/json-sql/tests/6_postgresDialect.js b/legacy/json-sql/tests/6_postgresDialect.js new file mode 100644 index 00000000..790c0ec2 --- /dev/null +++ b/legacy/json-sql/tests/6_postgresDialect.js @@ -0,0 +1,140 @@ +'use strict'; + +var jsonSql = require('../lib')({ + dialect: 'postgresql', + namedValues: false +}); +var expect = require('chai').expect; + +describe('PostgreSQL dialect', function() { + describe('json', function() { + it('should correctly wrap each part of json path', function() { + var result = jsonSql.build({ + table: 'test', + fields: ['params->a->>b'], + condition: { + 'params->>c': {$like: '7%'} + } + }); + + expect(result.query).to.be.equal( + 'select "params"->\'a\'->>\'b\' from "test" ' + + 'where "params"->>\'c\' like ${1};' + ); + }); + + it('should be ok with `$jsonContains` conditional operator', function() { + var result = jsonSql.build({ + table: 'test', + condition: { + 'params->a': { + $jsonContains: {b: 1} + } + } + }); + + expect(result.query).to.be.equal( + 'select * from "test" where "params"->\'a\' @> ${1};' + ); + expect(result.values).to.be.eql(['{"b":1}']); + }); + + it('should be ok with `$jsonIn` conditional operator', function() { + var result = jsonSql.build({ + table: 'test', + condition: { + 'params->a': { + $jsonIn: {$field: 'data->b'} + } + } + }); + + expect(result.query).to.be.equal( + 'select * from "test" where "params"->\'a\' <@ "data"->\'b\';' + ); + expect(result.values).to.be.eql([]); + }); + + it('should be ok with `$jsonHas` conditional operator', function() { + var result = jsonSql.build({ + table: 'test', + condition: { + params: {$jsonHas: 'account'} + } + }); + + expect(result.query).to.be.equal('select * from "test" where "params" ? ${1};'); + expect(result.values).to.be.eql(['account']); + }); + + it('should be ok with `$jsonHasAny` conditional operator', function() { + var result = jsonSql.build({ + table: 'test', + condition: { + params: {$jsonHasAny: ['a', 'b']} + } + }); + + expect(result.query).to.be.equal( + 'select * from "test" where "params" ?| array[${1}, ${2}];' + ); + expect(result.values).to.be.eql(['a', 'b']); + }); + + it('should be ok with `$jsonHasAll` conditional operator', function() { + var result = jsonSql.build({ + table: 'test', + condition: { + params: {$jsonHasAll: ['a', 'b']} + } + }); + + expect(result.query).to.be.equal( + 'select * from "test" where "params" ?& array[${1}, ${2}];' + ); + expect(result.values).to.be.eql(['a', 'b']); + }); + + it('should be ok with `$upper` conditional operator', function() { + var result = jsonSql.build({ + table: 'test', + condition: { + params: {$upper: ['params', '3498862814541110459l']} + } + }); + + expect(result.query).to.be.equal( + 'select * from "test" where upper("params") = upper(${1});' + ); + expect(result.values).to.be.eql(['3498862814541110459l']); + }); + + it('should be ok with `$lower` conditional operator', function() { + var result = jsonSql.build({ + table: 'test', + condition: { + params: {$lower: ['params', '3498862814541110459L']} + } + }); + + expect(result.query).to.be.equal( + 'select * from "test" where lower("params") = lower(${1});' + ); + expect(result.values).to.be.eql(['3498862814541110459L']); + }); + + it('should be ok with `$decode` conditional operator', function() { + var result = jsonSql.build({ + table: 'test', + condition: { + params: {$decode: ['params', '3498862814541110459L', 'hex']} + } + }); + + expect(result.query).to.be.equal( + 'select * from "test" where "params" = decode(${1}, ${2});' + ); + expect(result.values).to.be.eql(['3498862814541110459L', 'hex']); + }); + }); +}); diff --git a/legacy/json-sql/tests/7_create.js b/legacy/json-sql/tests/7_create.js new file mode 100644 index 00000000..a93ca702 --- /dev/null +++ b/legacy/json-sql/tests/7_create.js @@ -0,0 +1,279 @@ +'use strict'; + +var jsonSql = require('../lib')(); +var expect = require('chai').expect; + +describe('Create', function () { + it('should throw error without `tableFields` property', function () { + expect(function () { + jsonSql.build({ + type: 'create', + table: 'users' + }); + }).to.throw('`tableFields` property is not set in `create` clause'); + }); + + it('should throw error with incorrect field type', function () { + expect(function () { + jsonSql.build({ + type: 'create', + table: 'users', + tableFields: [ + { + name: "age", + type: "NotNumber" + } + ] + }); + }).to.throw('Invalid type of field: NotNumber'); + }); + + it('should be ok with `tableFields` property', function () { + var result = jsonSql.build({ + type: 'create', + table: 'users', + tableFields: [ + { + name: "age", + type: "Number" + } + ] + }); + + + expect(result.query).to.be.equal('create table if not exists "users"("age" int);'); + }); + + it('should throw error when length property for string field not provided', function () { + expect(function () { + jsonSql.build({ + type: 'create', + table: 'users', + tableFields: [ + { + name: "name", + type: "String" + } + ] + }); + }).to.throw('Field length can\'t be less or equal 0'); + }); + + it('should throw error with empty name', function () { + expect(function () { + jsonSql.build({ + type: 'create', + table: 'users', + tableFields: [ + { + name: " ", + type: "String" + } + ] + }); + }).to.throw('Name most contains characters'); + }); + + it('should be ok with string field and length', function () { + var result = jsonSql.build({ + type: 'create', + table: 'users', + tableFields: [ + { + name: "name", + type: "String", + length: 16 + } + ] + }); + + expect(result.query).to.be.equal('create table if not exists "users"("name" varchar(16));'); + }); + + it('should be ok with string field not null', function () { + var result = jsonSql.build({ + type: 'create', + table: 'users', + tableFields: [ + { + name: "name", + type: "String", + length: 16, + not_null: true + } + ] + }); + + expect(result.query).to.be.equal('create table if not exists "users"("name" varchar(16) NOT NULL);'); + }); + + it('should be ok with string field not null primary key', function () { + var result = jsonSql.build({ + type: 'create', + table: 'users', + tableFields: [ + { + name: "name", + type: "String", + length: 16, + not_null: true, + primary_key: true + } + ] + }); + + expect(result.query).to.be.equal('create table if not exists "users"("name" varchar(16) NOT NULL PRIMARY KEY);'); + }); + + + it('should be ok with string field not null unique', function () { + var result = jsonSql.build({ + type: 'create', + table: 'users', + tableFields: [ + { + name: "name", + type: "String", + length: 16, + not_null: true, + unique: true + } + ] + }); + + expect(result.query).to.be.equal('create table if not exists "users"("name" varchar(16) NOT NULL UNIQUE);'); + }); + + + it('should be allow only one primary key field', function () { + expect(function () { + jsonSql.build({ + type: 'create', + table: 'users', + tableFields: [ + { + name: "name", + type: "String", + length: 16, + not_null: true, + primary_key: true + }, + { + name: "secondname", + type: "String", + length: 16, + not_null: true, + primary_key: true + } + ] + }) + }).to.throw("Too much primary key 'secondname' in table"); + }); + + it('should be allow only unique field name', function () { + expect(function () { + jsonSql.build({ + type: 'create', + table: 'users', + tableFields: [ + { + name: "name", + type: "String", + length: 16, + not_null: true, + primary_key: true + }, + { + name: "name", + type: "String", + length: 16, + not_null: true + } + ] + }) + }).to.throw("Two parameters with same name: name"); + }); + + it("should allow few fields", function () { + var result = jsonSql.build({ + type: 'create', + table: 'users', + tableFields: [ + { + name: "name", + type: "String", + length: 16, + not_null: true, + primary_key: true + }, + { + name: "age", + type: "Number", + not_null: true + } + ] + }); + + expect(result.query).to.be.equal('create table if not exists "users"("name" varchar(16) NOT NULL PRIMARY KEY,"age" int NOT NULL);'); + }); + + it("should allow few fields", function () { + var result = jsonSql.build({ + type: 'create', + table: 'users', + tableFields: [ + { + name: "name", + type: "String", + length: 16, + not_null: true, + primary_key: true + }, + { + name: "age", + type: "Number", + not_null: true + } + ], + foreignKeys: [ + { + field: "name", + table: "person", + table_field: "id" + } + ] + }); + + expect(result.query).to.be.equal('create table if not exists "users"("name" varchar(16) NOT NULL PRIMARY KEY,"age" int NOT NULL, FOREIGN KEY ("name") REFERENCES person("id"));'); + }); + + it("should allow few fields", function () { + var result = jsonSql.build({ + type: 'create', + table: 'users', + tableFields: [ + { + name: "name", + type: "String", + length: 16, + not_null: true, + primary_key: true + }, + { + name: "age", + type: "Number", + not_null: true + } + ], + foreignKeys: [ + { + field: "name", + table: "person", + table_field: "id" + } + ] + }); + + expect(result.query).to.be.equal('create table if not exists "users"("name" varchar(16) NOT NULL PRIMARY KEY,"age" int NOT NULL, FOREIGN KEY ("name") REFERENCES person("id"));'); + }); +}); diff --git a/legacy/json-sql/tests/8_index.js b/legacy/json-sql/tests/8_index.js new file mode 100644 index 00000000..464a85b3 --- /dev/null +++ b/legacy/json-sql/tests/8_index.js @@ -0,0 +1,45 @@ +'use strict'; + +var jsonSql = require('../lib')(); +var expect = require('chai').expect; + +/* + jsonSql.build({ + type: 'index', + table: 'users', + index: { + name: "user_id", + field: "id" + } + }); + */ + +describe('Index', function() { + it('should throw error without name property', function() { + expect(function() { + jsonSql.build({ + type: 'index', + table: 'users' + }); + }).to.throw('`name` property is not set in `index` clause'); + }); + + it('should throw error without indexOn property', function() { + expect(function() { + jsonSql.build({ + type: 'index', + table: 'users', + name: 'index_id' + }); + }).to.throw('`indexOn` property is not set in `index` clause'); + }); + + it('should be ok with name and field property', function () { + var result = jsonSql.build({ + type: "index", + table: "users", + name: "index_id", + indexOn: "id" + }); + }); +}); diff --git a/legacy/lisk-sandbox.js b/legacy/lisk-sandbox.js new file mode 100644 index 00000000..defbc643 --- /dev/null +++ b/legacy/lisk-sandbox.js @@ -0,0 +1,231 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 Lisk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +// https://github.com/LiskArchive/lisk-sandbox + +var EventEmitter = require('events').EventEmitter, + util = require('util'), + spawn = require('child_process').spawn, + path = require('path'), + querystring = require('querystring'), + async = require('async'); + +var callbacks = {}; +var magic = "=%5a$ng*a8="; +var magicData = ""; +var debugPort = 5859; + +function Sandbox(file, id, params, apiHandler, debug) { + EventEmitter.call(this); + + if (typeof file !== "string" || file === undefined || file === null) { + throw new Error("First argument should be a path to file to launch in vm"); + } + + if (typeof id !== "string" || id === undefined || id === null) { + throw new Error("Second argument should be a id of dapp"); + } + + if (typeof apiHandler !== "function" || apiHandler === undefined || apiHandler === null) { + throw new Error("Third argument should be a api hanlder callback"); + } + + this.params = params; + this.file = file; + this.id = id; + this.apiHandler = apiHandler; + this.child = null; + this.queue = null; + this.debug = debug || false; +} + +util.inherits(Sandbox, EventEmitter); + +Sandbox.prototype._parse = function (data) { + try { + var json = JSON.parse(data); + } catch (e) { + return this._onError(new Error("Can't parse JSON response from DApp: \n" + data + "\n" + e.toString())); + } + + if (json.callback_id === null || json.callback_id === undefined) { + return this._onError(new Error("Incorrect response from vm, missed callback id field")); + } + + try { + var callback_id = parseInt(json.callback_id); + } catch (e) { + return this._onError(new Error("Incorrect callback_id field, callback_id should be a number")); + } + + if (isNaN(callback_id)) { + return this._onError(new Error("Incorrect callback_id field, callback_id should be a number")); + } + + if (json.type == "dapp_response") { + var callback = callbacks[callback_id]; + + if (!callback) { + return this._onError(new Error("Lisk can't find callback_id from vm")); + } + + var error = json.error; + var response = json.response; + + setImmediate(callback, error, response); + } else if (json.type == "dapp_call") { + var message = json.message; + + if (message === null || message === undefined) { + return this._onError(new Error("Lisk can't find message for request from vm")); + } + + message.dappid = this.id; + + this.apiHandler(message, function (err, response) { + var responseObj = { + type: "lisk_response", + callback_id: callback_id, + error: err, + response: response || {} + }; + + try { + var responseString = JSON.stringify(responseObj); + } catch (e) { + return this._onError(new Error("Can't make response: " + e.toString())); + } + + this.queue.push({message: responseString + magic}); + }.bind(this)); + } else { + this._onError(new Error("Incorrect response type from vm")); + } +} + +Sandbox.prototype.run = function () { + var params = [this.file].concat(this.params); + if (this.debug) { + params.unshift("--debug=" + debugPort); + console.log("DebugPort " + params[1] + " : " + debugPort++); + } + this.child = spawn(path.join(__dirname, "../../nodejs/node"), params, { + stdio: ['pipe', 'pipe', 'pipe', 'pipe', 'pipe'] + }); + + var self = this; + + this.queue = async.queue(function (task, callback) { + try { + var size = Buffer.byteLength(task.message, 'utf8'); + if (size > 16000) { + console.log("incoming " + (size) + " bytes"); + } + self.child.stdio[3].write(task.message); + } catch (e) { + console.log(e.toString()) + } finally { + setTimeout(callback, 10); + } + }, 1); + + // Catch errors... + this.child.on('error', this._onError.bind(this)); + this.child.stdio[0].on('error', this._onError.bind(this)); + this.child.stdio[1].on('error', this._onError.bind(this)); + this.child.stdio[2].on('error', this._onError.bind(this)); + this.child.stdio[3].on('error', this._onError.bind(this)); + this.child.stdio[4].on('error', this._onError.bind(this)); + + this.child.stdio[4].on('data', this._listen.bind(this)); + + if (this.debug) { + this.child.stdio[1].on('data', this._debug.bind(this)); + } + + this.child.stdio[2].on('data', this._debug.bind(this)); +} + +Sandbox.prototype.setApi = function (apiHanlder) { + if (typeof apiHanlder != "function" || apiHanlder === null || apiHanlder === undefined) { + throw new Error("First argument should be a function"); + } + this.apiHandler = apiHanlder; +} + +Sandbox.prototype.sendMessage = function (message, callback) { + var callback_id = Object.keys(callbacks).length + 1; + + var messageObj = { + callback_id: callback_id, + type: "lisk_call", + message: message + }; + + try { + var messageString = JSON.stringify(messageObj); + } catch (e) { + return setImmediate(callback, "Can't stringify message: " + e.toString()); + } + + this.queue.push({message: messageString + magic}); + + callbacks[callback_id] = callback; +} + +Sandbox.prototype.exit = function () { + if (this.child) { + this.child.kill(); + this.emit("exit"); + } +} + +Sandbox.prototype._debug = function (data) { + console.log("Debug " + this.file + ": \n"); + console.log(data.toString('utf8')); +} + +Sandbox.prototype._onError = function (err) { + console.log(err.stack) + this.exit(); + this.emit("error", err); +} + +Sandbox.prototype._listen = function (dataraw) { + var data = querystring.unescape(dataraw.toString('utf8')); + magicData += data; + if (data.substring(data.length - 11) == magic) { + var fullMessage = magicData; + magicData = ""; + + var parts = fullMessage.split(magic); + parts.pop(); + parts.forEach(function (jsonmessage) { + this._parse(jsonmessage); + }.bind(this)); + + } +} + +module.exports = Sandbox; \ No newline at end of file diff --git a/logic/transaction.js b/logic/transaction.js index a6e6c3a6..6df61c1a 100644 --- a/logic/transaction.js +++ b/logic/transaction.js @@ -378,7 +378,7 @@ Transaction.prototype.checkConfirmed = function (trs, cb) { * @return {Object} With exceeded boolean and error: address, balance */ Transaction.prototype.checkBalance = function (amount, balance, trs, sender) { - var exceededBalance = new bignum(sender[balance].toString()).lessThan(amount); + var exceededBalance = new bignum(sender[balance].toString()).isLessThan(amount); var exceeded = (trs.blockId !== this.scope.genesisblock.block.id && exceededBalance); return { diff --git a/modules/blocks/verify.js b/modules/blocks/verify.js index ee7f42e6..12420e63 100644 --- a/modules/blocks/verify.js +++ b/modules/blocks/verify.js @@ -476,7 +476,7 @@ Verify.prototype.processBlock = function (block, broadcast, cb, saveBlock) { } else { // The block and the transactions are OK i.e: // * Block and transactions have valid values (signatures, block slots, etc...) - // * The check against database state passed (for instance sender has enough LSK, votes are under 101, etc...) + // * The check against database state passed (for instance sender has enough ADM, votes are under 101, etc...) // We thus update the database with the transactions values, save the block and tick it modules.blocks.chain.applyBlock(block, broadcast, cb, saveBlock); } diff --git a/modules/cache.js b/modules/cache.js index c9d6eaee..0ed99575 100644 --- a/modules/cache.js +++ b/modules/cache.js @@ -48,13 +48,14 @@ Cache.prototype.getJsonForKey = function (key, cb) { if (!self.isConnected()) { return cb(errorCacheDisabled); } - client.get(key, function (err, value) { - if (err) { - return cb(err, value); - } - // parsing string to json - return cb(null, JSON.parse(value)); - }); + client.get(key) + .then((value) => { + // parsing string to json + return cb(null, JSON.parse(value)); + }) + .catch((err) => { + return cb(err, key); + }); }; /** @@ -67,8 +68,15 @@ Cache.prototype.setJsonForKey = function (key, value, cb) { if (!self.isConnected()) { return cb(errorCacheDisabled); } + // redis calls toString on objects, which converts it to object [object] so calling stringify before saving - client.set(key, JSON.stringify(value), cb); + client.set(key, JSON.stringify(value)) + .then((res) => { + cb(null, res); + }) + .catch((err) => { + cb(err, value); + }); }; /** @@ -79,7 +87,10 @@ Cache.prototype.deleteJsonForKey = function (key, cb) { if (!self.isConnected()) { return cb(errorCacheDisabled); } - client.del(key, cb); + + client.del(key) + .then((res) => cb(null, res)) + .catch((err) => cb(err, key)); }; /** @@ -93,21 +104,23 @@ Cache.prototype.removeByPattern = function (pattern, cb) { } var keys, cursor = 0; async.doWhilst(function iteratee (whilstCb) { - client.scan(cursor, 'MATCH', pattern, function (err, res) { - if (err) { - return whilstCb(err); - } else { - cursor = Number(res.shift()); - keys = res.shift(); - if (keys.length > 0) { - client.del(keys, whilstCb); - } else { - return whilstCb(); - } - } - }); - }, function test () { - return cursor > 0; + client.scan(cursor, { MATCH: pattern }) + .then((res) => { + cursor = res.cursor; + keys = res.keys; + if (keys.length > 0) { + client.del(keys) + .then((res) => whilstCb(null, res)) + .catch((err) => whilstCb(err)); + } else { + return whilstCb(); + } + }) + .catch((err) => { + return whilstCb(err); + }); + }, function test (...args) { + return args[args.length - 1](null, cursor > 0); }, cb); }; @@ -119,7 +132,10 @@ Cache.prototype.flushDb = function (cb) { if (!self.isConnected()) { return cb(errorCacheDisabled); } - client.flushdb(cb); + + client.flushDb() + .then((res) => cb(null, res)) + .catch((err) => cb(err)); }; /** @@ -139,7 +155,10 @@ Cache.prototype.quit = function (cb) { // because connection isn't established in the first place. return cb(); } - client.quit(cb); + + client.quit() + .then((res) => cb(null, res)) + .catch((err) => cb(err)); }; /** diff --git a/modules/chats.js b/modules/chats.js index 76777e90..318a00d8 100644 --- a/modules/chats.js +++ b/modules/chats.js @@ -5,10 +5,8 @@ var crypto = require('crypto'); var Chat = require('../logic/chat.js'); var extend = require('extend'); var ip = require('ip'); -var npm = require('npm'); var OrderBy = require('../helpers/orderBy.js'); var path = require('path'); -var popsicle = require('popsicle'); var Router = require('../helpers/router.js'); var schema = require('../schema/chats.js'); var sql = require('../sql/chats.js'); diff --git a/modules/clientWs.js b/modules/clientWs.js index 66183ec8..585f76d4 100644 --- a/modules/clientWs.js +++ b/modules/clientWs.js @@ -1,10 +1,15 @@ +const { Server } = require('socket.io'); + class ClientWs { constructor (config, logger, cb) { if (!config || !config.enabled) { return false; } const port = config.portWS; - const io = require('socket.io')(port); + const io = new Server(port, { + allowEIO3: true + }); + this.describes = {}; this.logger = logger; io.sockets.on('connection', (socket) => { diff --git a/modules/dapps.js b/modules/dapps.js index cb3f74ba..ab5854df 100644 --- a/modules/dapps.js +++ b/modules/dapps.js @@ -5,19 +5,19 @@ var crypto = require('crypto'); var DApp = require('../logic/dapp.js'); var dappCategories = require('../helpers/dappCategories.js'); var dappTypes = require('../helpers/dappTypes.js'); -var DecompressZip = require('decompress-zip'); +var unzipper = require('unzipper'); var extend = require('extend'); var fs = require('fs'); var ip = require('ip'); var InTransfer = require('../logic/inTransfer.js'); -var npm = require('npm'); +var execa = require('execa'); var OrderBy = require('../helpers/orderBy.js'); var OutTransfer = require('../logic/outTransfer.js'); var path = require('path'); -var popsicle = require('popsicle'); +var axios = require('axios').default; var rmdir = require('rimraf'); var Router = require('../helpers/router.js'); -var Sandbox = require('lisk-sandbox'); +var Sandbox = require('../legacy/lisk-sandbox.js'); var sandboxHelper = require('../helpers/sandbox.js'); var schema = require('../schema/dapps.js'); var sql = require('../sql/dapps.js'); @@ -296,27 +296,13 @@ __private.createBasePaths = function (cb) { __private.installDependencies = function (dapp, cb) { var dappPath = path.join(__private.dappsPath, dapp.transactionId); - var packageJson = path.join(dappPath, 'package.json'); - var config = null; - - try { - config = JSON.parse(fs.readFileSync(packageJson)); - } catch (e) { - return setImmediate(cb, 'Failed to open package.json file'); - } - - npm.load(config, function (err) { - if (err) { - return setImmediate(cb, err); - } - - npm.root = path.join(dappPath, 'node_modules'); - npm.prefix = dappPath; - - npm.commands.install(function (err, data) { - return setImmediate(cb, null); - }); - }); + execa('npm', ['install', `-g --prefix ${dappPath}`]) + .then(function () { + return setImmediate(cb, null); + }) + .catch(function (err) { + return setImmediate(cb, err); + }); }; /** @@ -393,8 +379,8 @@ __private.removeDApp = function (dapp, cb) { /** * Creates a temp dir, downloads the dapp as stream and decompress it. * @private - * @implements {popsicle} - * @implements {DecompressZip} + * @implements {axios} + * @implements {unzipper} * @param {dapp} dapp * @param {string} dappPath * @param {function} cb @@ -432,59 +418,81 @@ __private.downloadLink = function (dapp, dappPath, cb) { }); } - var request = popsicle.get({ - url: dapp.link, - transport: popsicle.createTransport({ type: 'stream' }) - }); - - request.then(function (res) { - if (res.status !== 200) { - return setImmediate(serialCb, ['Received bad response code', res.status].join(' ')); - } - - var stream = fs.createWriteStream(tmpPath); + var stream = fs.createWriteStream(tmpPath); - stream.on('error', cleanup); + axios({ + method: 'get', + url: dapp.link, + responseType: 'stream' + }) + .then((res) => { + if (res.status !== 200) { + return setImmediate(serialCb, ['Received bad response code', res.status].join(' ')); + } - stream.on('finish', function () { - library.logger.info(dapp.transactionId, 'Finished downloading'); - stream.close(serialCb); - }); + stream.on('error', cleanup); - res.body.pipe(stream); - }); + stream.on('finish', function () { + library.logger.info(dapp.transactionId, 'Finished downloading'); + stream.close(serialCb); + }); - request.catch(cleanup); + res.data.pipe(writer); + }) + .catch(cleanup); }, decompressZip: function (serialCb) { - var unzipper = new DecompressZip(tmpPath); - library.logger.info(dapp.transactionId, 'Decompressing zip file'); - unzipper.on('error', function (err) { - library.logger.error(dapp.transactionId, 'Decompression failed: ' + err.message); - fs.exists(tmpPath, function (exists) { - if (exists) { fs.unlink(tmpPath); } - return setImmediate(serialCb, 'Failed to decompress zip file'); - }); - }); + fs.createReadStream(tmpPath) + .pipe(unzipper.Parse()) + .on('entry', function (entry) { + const fileName = entry.path; + const strippedPath = fileName + .split(path.sep) + .splice(1) + .join(path.sep); + + if (!strippedPath) { + return entry.autodrain(); + } - unzipper.on('extract', function (log) { - library.logger.info(dapp.transactionId, 'Finished extracting'); - fs.exists(tmpPath, function (exists) { - if (exists) { fs.unlink(tmpPath); } - return setImmediate(serialCb, null); - }); - }); + if (entry.type === 'File') { + const dirName = path.dirname(strippedPath); - unzipper.on('progress', function (fileIndex, fileCount) { - library.logger.info(dapp.transactionId, ['Extracted file', (fileIndex + 1), 'of', fileCount].join(' ')); - }); + if (dirName) { + const dirPath = path.resolve(dappPath, dirName); - unzipper.extract({ - path: dappPath, - strip: 1 - }); + fs.exists(dirPath, function (exists) { + if (!exists) { + fs.mkdirSync(dirPath, { + recursive: true + }); + } + }); + } + + library.logger.info(dapp.transactionId, `Extracted file: ${strippedPath}`); + entry.pipe(fs.createWriteStream(path.resolve(dappPath, strippedPath))); + } else { + entry.autodrain(); + } + }) + .promise() + .then(() => { + library.logger.info(dapp.transactionId, 'Finished extracting'); + fs.exists(tmpPath, function (exists) { + if (exists) { fs.unlink(tmpPath); } + return setImmediate(serialCb, null); + }); + }) + .catch((error) => { + library.logger.error(dapp.transactionId, 'Decompression failed: ' + error); + fs.exists(tmpPath, function (exists) { + if (exists) { fs.unlink(tmpPath); } + return setImmediate(serialCb, 'Failed to decompress zip file'); + }); + }); } }, function (err) { diff --git a/modules/loader.js b/modules/loader.js index c8c35825..95bd8001 100644 --- a/modules/loader.js +++ b/modules/loader.js @@ -342,8 +342,8 @@ __private.loadBlockChain = function () { }, loadBlocksOffset: function (seriesCb) { async.until( - function () { - return count < offset; + function (testCb) { + return testCb(null, count < offset); }, function (cb) { if (count > 1) { library.logger.info('Rebuilding blockchain, current block height: ' + (offset + 1)); @@ -532,8 +532,8 @@ __private.loadBlocksFromNetwork = function (cb) { return setImmediate(cb, err); } else { async.whilst( - function () { - return !loaded && errorCount < 5; + function (testCb) { + return testCb(null, !loaded && errorCount < 5); }, function (next) { var peer = network.peers[Math.floor(Math.random() * network.peers.length)]; diff --git a/modules/peers.js b/modules/peers.js index ba1bd507..988c85ec 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -413,11 +413,14 @@ Peers.prototype.acceptable = function (peers) { return (a.ip + a.port) === (b.ip + b.port); }) .filter(function (peer) { - // Removing peers with private address or nonce equal to self + // Removing peers with private address or nonce equal to self + const isJsAPI = peer.os === 'adm-js-api' || peer.os === 'lisk-js-api'; + if ((process.env['NODE_ENV'] || '').toUpperCase() === 'TEST') { - return peer.nonce !== modules.system.getNonce() && (peer.os !== 'lisk-js-api'); + return peer.nonce !== modules.system.getNonce() && !isJsAPI; } - return !ip.isPrivate(peer.ip) && peer.nonce !== modules.system.getNonce() && (peer.os !== 'lisk-js-api'); + + return !ip.isPrivate(peer.ip) && peer.nonce !== modules.system.getNonce() && !isJsAPI; }).value(); }; @@ -679,7 +682,7 @@ Peers.prototype.shared = { * @return {Object} cb.obj Anonymous object with version info * @return {String} cb.obj.build Build information (if available, otherwise '') * @return {String} cb.obj.commit Hash of last git commit (if available, otherwise '') - * @return {String} cb.obj.version Lisk version from config file + * @return {String} cb.obj.version ADAMANT version from config file */ version: function (req, cb) { return setImmediate(cb, null, { diff --git a/modules/sql.js b/modules/sql.js index 00c8281f..e4a3a957 100644 --- a/modules/sql.js +++ b/modules/sql.js @@ -159,9 +159,9 @@ __private.query = function (action, config, cb) { } else { var batchPack = []; async.until( - function () { + function (testCb) { batchPack = config.values.splice(0, 10); - return batchPack.length === 0; + return testCb(null, batchPack.length === 0); }, function (cb) { var fields = Object.keys(config.fields).map(function (field) { return __private.escape2(config.fields[field]); // Add double quotes to field identifiers diff --git a/modules/states.js b/modules/states.js index e916b111..792769b0 100644 --- a/modules/states.js +++ b/modules/states.js @@ -5,10 +5,8 @@ var crypto = require('crypto'); var State = require('../logic/state.js'); var extend = require('extend'); var ip = require('ip'); -var npm = require('npm'); var OrderBy = require('../helpers/orderBy.js'); var path = require('path'); -var popsicle = require('popsicle'); var Router = require('../helpers/router.js'); var schema = require('../schema/states.js'); var sql = require('../sql/states.js'); diff --git a/modules/transactions.js b/modules/transactions.js index 035b84d8..6f3fbf3f 100644 --- a/modules/transactions.js +++ b/modules/transactions.js @@ -128,7 +128,7 @@ __private.list = function (filter, cb) { // Mutating parameters when unix timestamp is supplied if (_.includes(['fromUnixTime', 'toUnixTime'], field[1])) { - // Lisk epoch is 1464109200 as unix timestamp + // ADAMANT epoch is 1464109200 as unix timestamp value = value - constants.epochTime.getTime() / 1000; field[1] = field[1].replace('UnixTime', 'Timestamp'); } diff --git a/modules/transport.js b/modules/transport.js index 0b36049d..7953a2fd 100644 --- a/modules/transport.js +++ b/modules/transport.js @@ -9,7 +9,7 @@ var constants = require('../helpers/constants.js'); var crypto = require('crypto'); var extend = require('extend'); var ip = require('ip'); -var popsicle = require('popsicle'); +var axios = require('axios').default; var schema = require('../schema/transport.js'); var sandboxHelper = require('../helpers/sandbox.js'); var sql = require('../sql/transport.js'); @@ -330,7 +330,7 @@ Transport.prototype.getFromRandomPeer = function (config, options, cb) { * - same network (response headers nethash) * - compatible version (response headers version) * Removes peer if error, updates peer otherwise - * @requires {popsicle} + * @requires {axios} * @implements {library.logic.peers.create} * @implements {library.schema.validate} * @implements {modules.system.networkCompatible} @@ -362,12 +362,11 @@ Transport.prototype.getFromPeer = function (peer, options, cb) { }; if (options.data) { - req.body = options.data; + req.data = options.data; } - popsicle.request(req) - .use(popsicle.plugins.parse(['json'], false)) - .then(function (res) { + axios(req) + .then((res) => { if (res.status !== 200) { // Remove peer __private.removePeer({ peer: peer, code: 'ERESPONSE ' + res.status }, req.method + ' ' + req.url); @@ -400,9 +399,10 @@ Transport.prototype.getFromPeer = function (peer, options, cb) { modules.peers.update(peer); - return setImmediate(cb, null, { body: res.body, peer: peer }); + return setImmediate(cb, null, { body: res.data, peer: peer }); } - }).catch(function (err) { + }) + .catch((err) => { if (peer) { __private.removePeer({ peer: peer, code: err.code }, req.method + ' ' + req.url); } diff --git a/package.json b/package.json index 6efe68fc..6240dbde 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adamant", - "version": "0.6.5", + "version": "0.7.0", "private": true, "scripts": { "start": "node app.js", @@ -15,7 +15,7 @@ "eslint:fixrule": "npx eslint -f visualstudio --no-eslintrc --fix --env browser,node,es2021,commonjs --parser-options=ecmaVersion:12 --rule", "fetchCoverage": "npx grunt exec:fetchCoverage --verbose", "jsdoc": "npx jsdoc -c docs/conf.json --verbose --pedantic", - "server-docs": "npx jsdoc && http-server docs/jsdoc/" + "server-docs": "npx jsdoc && npx http-server docs/jsdoc/" }, "keywords": [ "adm", @@ -30,69 +30,77 @@ "encryption", "crypto", "cryptocurrency" - ], + ], "author": "ADAMANT Tech Labs , Lisk Foundation , lightcurve GmbH ", "license": "GPL-3.0", "dependencies": { - "async": "=2.1.4", - "bignumber.js": "=4.0.0", - "bitcore-mnemonic": "=1.1.1", - "body-parser": "=1.16.0", + "async": "=3.2.4", + "axios": "^0.27.2", + "bignumber.js": "=9.1.0", + "bitcore-mnemonic": "=8.25.36", + "body-parser": "=1.20.0", "bytebuffer": "=5.0.1", - "change-case": "=3.0.1", - "colors": "=1.1.2", - "commander": "=2.9.0", - "compression": "=1.6.2", - "cors": "=2.8.1", - "decompress-zip": "LiskHQ/decompress-zip#f46b0a3", - "ejs": "=2.5.5", - "express": "=4.14.1", + "change-case": "=4.1.2", + "colors": "=1.4.0", + "commander": "=9.4.0", + "compression": "=1.7.4", + "cors": "=2.8.5", + "ejs": "=3.1.8", + "execa": "^5.1.1", + "express": "=4.18.1", "express-domain-middleware": "=0.1.0", - "express-query-int": "=1.0.1", - "express-rate-limit": "=2.6.0", + "express-query-int": "=3.0.0", + "express-rate-limit": "=6.5.1", + "express-slow-down": "1.4.0", "extend": "=3.0.2", - "ip": "=1.1.5", - "js-nacl": "^1.2.2", - "json-schema": "=0.2.3", - "json-sql": "LiskHQ/json-sql#27e1ed1", - "lisk-sandbox": "LiskHQ/lisk-sandbox#162da38", - "lodash": "=4.17.15", - "method-override": "=2.3.10", - "npm": "=6.13.4", - "pg-monitor": "=0.7.1", - "pg-native": "=1.10.0", - "pg-promise": "=5.5.6", - "popsicle": "=9.0.0", - "randomstring": "=1.1.5", - "redis": "=2.7.1", - "rimraf": "=2.5.4", - "semver": "=5.3.0", - "socket.io": "^1.7.2", + "ip": "=1.1.8", + "js-nacl": "^1.4.0", + "json-schema": "=0.4.0", + "json-sql": "./legacy/json-sql", + "lodash": "=4.17.21", + "method-override": "=3.0.0", + "npm": "=8.17.0", + "pg-monitor": "=1.4.1", + "pg-native": "=3.0.0", + "pg-promise": "=10.11.1", + "randomstring": "=1.2.2", + "redis": "=4.2.0", + "rimraf": "=3.0.2", + "semver": "=7.3.7", + "socket.io": "^4.5.1", "sodium": "^3.0.2", "sodium-browserify-tweetnacl": "*", - "strftime": "=0.10.0", + "strftime": "=0.10.1", + "unzipper": "^0.10.11", "valid-url": "=1.0.9", - "validator": "=6.2.1", - "validator.js": "=2.0.3", - "z-schema": "=3.18.2" + "validator.js": "=2.0.4", + "z-schema": "=5.0.3" }, "devDependencies": { - "chai": "^4.3.4", + "chai": "^4.3.6", "chai-bignumber": "^3.0.0", "eslint-config-google": "^0.14.0", - "grunt": "^1.4.1", + "eslint-formatter-codeframe": "^7.32.1", + "eslint-plugin-jsdoc": "^39.3.4", + "grunt": "^1.5.3", "grunt-cli": "^1.4.3", "grunt-contrib-compress": "^2.0.0", - "grunt-eslint": "^23.0.0", - "grunt-exec": "^3.0.0", "grunt-contrib-obfuscator": "^8.0.0", - "jsdoc": "^3.6.7", - "mocha": "^9.1.1", - "moment": "^2.29.1", + "grunt-eslint": "^24.0.0", + "grunt-exec": "^3.0.0", + "jsdoc": "^3.6.11", + "mocha": "^10.0.0", + "moment": "^2.29.4", +>>>>>>> dev "nyc": "^15.1.0", - "nyc-middleware": "^1.0.2", - "sinon": "^11.1.2", - "supertest": "^6.1.6" + "nyc-middleware": "^1.0.4", + "sinon": "^14.0.0", + "supertest": "^6.2.4" + }, + "overrides": { + "grunt-contrib-obfuscator": { + "javascript-obfuscator": "^4.0.0" + } }, "config": { "minVersion": ">=0.4.0" diff --git a/schema/config.js b/schema/config.js index 4e9c294b..6b050073 100644 --- a/schema/config.js +++ b/schema/config.js @@ -70,25 +70,14 @@ module.exports = { redis: { type: 'object', properties: { - host: { - type: 'string', - format: 'ip' - }, - port: { - type: 'integer', - minimum: 1, - maximum: 65535 - }, - db: { - type: 'integer', - minimum: 0, - maximum: 15 + url: { + type: 'string' }, password: { type: ['string', 'null'] } }, - required: ['host', 'port', 'db', 'password'] + required: ['url', 'password'] }, api: { type: 'object', diff --git a/sql/memoryTables.sql b/sql/memoryTables.sql index 0da9d142..ee80c8b0 100644 --- a/sql/memoryTables.sql +++ b/sql/memoryTables.sql @@ -1,4 +1,4 @@ -/* Lisk Memory Tables +/* ADAMANT Memory Tables * */ diff --git a/test/api/accounts.js b/test/api/accounts.js index 91dc93d7..fd613b93 100644 --- a/test/api/accounts.js +++ b/test/api/accounts.js @@ -119,9 +119,9 @@ describe('GET /api/accounts/getBalance?address=', function () { }); it('using invalid address should fail', function (done) { - getBalance('thisIsNOTALiskAddress', function (err, res) { + getBalance('thisIsNOTAnAdamantAddress', function (err, res) { node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error').to.eql('Object didn\'t pass validation for format address: thisIsNOTALiskAddress'); + node.expect(res.body).to.have.property('error').to.eql('Object didn\'t pass validation for format address: thisIsNOTAnAdamantAddress'); done(); }); }); @@ -158,9 +158,9 @@ describe('GET /api/accounts/getPublicKey?address=', function () { }); it('using invalid address should fail', function (done) { - getPublicKey('thisIsNOTALiskAddress', function (err, res) { + getPublicKey('thisIsNOTAnAdamantAddress', function (err, res) { node.expect(res.body).to.have.property('success').to.be.not.ok; - node.expect(res.body).to.have.property('error').to.contain('Object didn\'t pass validation for format address: thisIsNOTALiskAddress'); + node.expect(res.body).to.have.property('error').to.contain('Object didn\'t pass validation for format address: thisIsNOTAnAdamantAddress'); done(); }); }); @@ -295,10 +295,10 @@ describe('GET /accounts', function () { }); it('using invalid address should fail', function (done) { - getAccounts('address=' + 'thisIsNOTALiskAddress', function (err, res) { + getAccounts('address=' + 'thisIsNOTAnAdamantAddress', function (err, res) { node.expect(res.body).to.have.property('success').to.be.not.ok; node.expect(res.body).to.have.property('error'); - node.expect(res.body.error).to.contain('Object didn\'t pass validation for format address: thisIsNOTALiskAddress'); + node.expect(res.body.error).to.contain('Object didn\'t pass validation for format address: thisIsNOTAnAdamantAddress'); done(); }); }); @@ -346,10 +346,10 @@ describe('GET /accounts', function () { }); it('using invalid publicKey should fail', function (done) { - getAccounts('publicKey=' + 'thisIsNOTALiskAccountPublicKey', function (err, res) { + getAccounts('publicKey=' + 'thisIsNOTAnAdamantAccountPublicKey', function (err, res) { node.expect(res.body).to.have.property('success').to.be.not.ok; node.expect(res.body).to.have.property('error'); - node.expect(res.body.error).to.contain('Object didn\'t pass validation for format publicKey: thisIsNOTALiskAccountPublicKey'); + node.expect(res.body.error).to.contain('Object didn\'t pass validation for format publicKey: thisIsNOTAnAdamantAccountPublicKey'); done(); }); }); diff --git a/test/api/dapps.js b/test/api/dapps.js index 9246ed53..0de4afb3 100644 --- a/test/api/dapps.js +++ b/test/api/dapps.js @@ -43,28 +43,28 @@ before(function (done) { }); before(function (done) { - // Send to LISK to account 1 address + // Send to ADM to account 1 address setTimeout(function () { - var randomLISK = node.randomLISK(); - var expectedFee = node.expectedFee(randomLISK); + var randomADM = node.randomADM(); + var expectedFee = node.expectedFee(randomADM); putTransaction({ secret: node.iAccount.password, - amount: randomLISK, + amount: randomADM, recipientId: account.address }, done); }, 2000); }); before(function (done) { - // Send to LISK to account 2 address + // Send to Adamant to account 2 address setTimeout(function () { - var randomLISK = node.randomLISK(); - var expectedFee = node.expectedFee(randomLISK); + var randomADM = node.randomADM(); + var expectedFee = node.expectedFee(randomADM); putTransaction({ secret: node.iAccount.password, - amount: randomLISK, + amount: randomADM, recipientId: account2.address }, done); }, 2000); diff --git a/test/api/delegates.js b/test/api/delegates.js index f0a74e09..78285b42 100644 --- a/test/api/delegates.js +++ b/test/api/delegates.js @@ -85,7 +85,7 @@ describe('PUT /api/accounts/delegates with funds', function () { before(function (done) { sendADM({ secret: node.iAccount.password, - amount: node.randomLISK(), + amount: node.randomADM(), recipientId: account.address }, function (err, res) { node.expect(res.body).to.have.property('success').to.be.ok; @@ -267,7 +267,7 @@ describe('PUT /api/delegates with funds', function () { beforeEach(function (done) { sendADM({ secret: node.iAccount.password, - amount: node.randomLISK(), + amount: node.randomADM(), recipientId: account.address }, function (err, res) { node.expect(res.body).to.have.property('success').to.be.ok; @@ -785,7 +785,7 @@ describe('GET /api/delegates/voters', function () { before(function (done) { sendADM({ secret: node.iAccount.password, - amount: node.randomLISK(), + amount: node.randomADM(), recipientId: account.address }, function (err, res) { node.expect(res.body).to.have.property('success').to.be.ok; diff --git a/test/api/multisignatures.js b/test/api/multisignatures.js index e0bd5278..9bdca71e 100644 --- a/test/api/multisignatures.js +++ b/test/api/multisignatures.js @@ -25,23 +25,23 @@ var multiSigTx = { txId: '' }; -function sendLISK (account, i, done) { - var randomLISK = node.randomLISK(); +function sendADM (account, i, done) { + var randomADM = node.randomADM(); node.put('/api/transactions/', { secret: node.iAccount.password, - amount: randomLISK, + amount: randomADM, recipientId: account.address }, function (err, res) { node.expect(res.body).to.have.property('success').to.be.ok; if (res.body.success && i != null) { - accounts[i].balance = randomLISK / node.normalizer; + accounts[i].balance = randomADM / node.normalizer; } done(); }); } -function sendLISKFromMultisigAccount (password, amount, recipient, done) { +function sendADMFromMultisigAccount (password, amount, recipient, done) { node.put('/api/transactions/', { secret: password, amount: amount, @@ -57,8 +57,8 @@ function confirmTransaction (transactionId, passphrases, done) { var count = 0; async.until( - function () { - return (count >= passphrases.length); + function (testCb) { + return testCb(null, count >= passphrases.length); }, function (untilCb) { var passphrase = passphrases[count]; @@ -96,7 +96,7 @@ function makeKeysGroup () { before(function (done) { var i = 0; async.eachSeries(accounts, function (account, eachCb) { - sendLISK(account, i, function () { + sendADM(account, i, function () { i++; return eachCb(); }); @@ -106,7 +106,7 @@ before(function (done) { }); before(function (done) { - sendLISK(multisigAccount, null, done); + sendADM(multisigAccount, null, done); }); before(function (done) { @@ -453,7 +453,7 @@ describe('PUT /api/multisignatures', function () { before(function (done) { async.every([multisigAccount1, multisigAccount2, multisigAccount3], function (acc, cb) { - sendLISK(acc, 0, cb); + sendADM(acc, 0, cb); }, done); }); @@ -572,7 +572,7 @@ describe('GET /api/multisignatures/pending', function () { describe('PUT /api/transactions', function () { it('when group transaction is pending should be ok', function (done) { - sendLISKFromMultisigAccount(multisigAccount.password, 100000000, node.iAccount.address, function (err, transactionId) { + sendADMFromMultisigAccount(multisigAccount.password, 100000000, node.iAccount.address, function (err, transactionId) { node.onNewBlock(function (err) { node.get('/api/transactions/get?id=' + transactionId, function (err, res) { node.expect(res.body).to.have.property('success').to.be.ok; @@ -681,7 +681,7 @@ describe('POST /api/multisignatures/sign (transaction)', function () { }); before(function (done) { - sendLISKFromMultisigAccount(multisigAccount.password, 100000000, node.iAccount.address, function (err, transactionId) { + sendADMFromMultisigAccount(multisigAccount.password, 100000000, node.iAccount.address, function (err, transactionId) { multiSigTx.txId = transactionId; node.onNewBlock(function (err) { done(); diff --git a/test/api/peer.transactions.main.js b/test/api/peer.transactions.main.js index 5601334f..c363e495 100644 --- a/test/api/peer.transactions.main.js +++ b/test/api/peer.transactions.main.js @@ -268,8 +268,8 @@ describe('POST /peer/transactions', function () { count++; return next(); }); - }, function () { - return count === 10; + }, function (testCb) { + return testCb(null, count === 10); }, function () { return done(); }); diff --git a/test/api/peer.transactions.multisignatures.js b/test/api/peer.transactions.multisignatures.js index 78785a0f..7e46a6ea 100644 --- a/test/api/peer.transactions.multisignatures.js +++ b/test/api/peer.transactions.multisignatures.js @@ -16,7 +16,7 @@ function postTransaction (transaction, done) { }); } -function sendLISK (params, done) { +function sendADM (params, done) { var transaction = node.createSendTransaction({ keyPair: account.keypair, amount: 100000000, @@ -52,7 +52,7 @@ describe('POST /peer/transactions', function () { describe('when account has funds', function () { before(function (done) { - sendLISK({ + sendADM({ secret: node.iAccount.password, amount: node.fees.multisignatureRegistrationFee * 10, recipientId: multisigAccount.address diff --git a/test/api/peer.transactions.signatures.js b/test/api/peer.transactions.signatures.js index b8786ae1..4aea1164 100644 --- a/test/api/peer.transactions.signatures.js +++ b/test/api/peer.transactions.signatures.js @@ -20,7 +20,7 @@ function postSignatureTransaction (transaction, done) { }); } -function sendLISK (params, done) { +function sendADM (params, done) { var transaction = node.createSendTransaction({ keyPair: node.createKeypairFromPassphrase(params.secret), amount: params.amount, @@ -75,7 +75,7 @@ describe('POST /peer/transactions', function () { describe('when account has funds', function () { before(function (done) { - sendLISK({ + sendADM({ secret: node.iAccount.password, amount: node.fees.secondPasswordFee + 100000000, recipientId: account.address diff --git a/test/api/peer.transactions.stress.js b/test/api/peer.transactions.stress.js index 732a867e..7a0eabc8 100644 --- a/test/api/peer.transactions.stress.js +++ b/test/api/peer.transactions.stress.js @@ -60,8 +60,8 @@ describe('POST /peer/transactions', function () { next(); }); }); - }, function () { - return (count >= maximum); + }, function (testCb) { + return testCb(null, count >= maximum); }, function (err) { done(err); }); @@ -110,8 +110,8 @@ describe('POST /peer/transactions', function () { count++; next(); }); - }, function () { - return (count >= maximum); + }, function (testCb) { + return testCb(null, count >= maximum); }, function (err) { done(err); }); diff --git a/test/api/signatures.js b/test/api/signatures.js index c55f68c3..36bc7205 100644 --- a/test/api/signatures.js +++ b/test/api/signatures.js @@ -18,13 +18,13 @@ function putDelegate (params, done) { node.put('/api/delegates', params, done); } -function sendLISK (account, done) { - var randomLISK = node.randomLISK(); - var expectedFee = node.expectedFee(randomLISK); +function sendADM (account, done) { + var randomADM = node.randomADM(); + var expectedFee = node.expectedFee(randomADM); putTransaction({ secret: node.iAccount.password, - amount: randomLISK, + amount: randomADM, recipientId: account.address }, function (err, res) { node.expect(res.body).to.have.property('success').to.be.ok; @@ -34,13 +34,13 @@ function sendLISK (account, done) { before(function (done) { setTimeout(function () { - sendLISK(account, done); + sendADM(account, done); }, 2000); }); before(function (done) { setTimeout(function () { - sendLISK(account2, done); + sendADM(account2, done); }, 2000); describe('PUT /api/transactions from account with second signature enabled', function () { diff --git a/test/api/transactions.js b/test/api/transactions.js index ebb1263e..dc71324a 100644 --- a/test/api/transactions.js +++ b/test/api/transactions.js @@ -70,13 +70,13 @@ function sendADM2voter (params, done) { before(function (done) { setTimeout(function () { - sendADM(account, node.randomLISK(), done); + sendADM(account, node.randomADM(), done); }, 2000); }); before(function (done) { setTimeout(function () { - sendADM(account2, node.randomLISK(), done); + sendADM(account2, node.randomADM(), done); }, 2000); }); diff --git a/test/config.json b/test/config.json index 49424861..aa833504 100644 --- a/test/config.json +++ b/test/config.json @@ -21,9 +21,7 @@ ] }, "redis": { - "host": "127.0.0.1", - "port": 6379, - "db": 1, + "url": "redis://127.0.0.1:6379/1", "password": null }, "api": { @@ -216,6 +214,6 @@ "wsClient": { "portWS": 36665, "enabled": true - }, + }, "nethash": "38f153a81332dea86751451fd992df26a9249f0834f72f58f84ac31cceb70f43" } diff --git a/test/node.js b/test/node.js index a7943b3f..477ba05b 100644 --- a/test/node.js +++ b/test/node.js @@ -23,7 +23,7 @@ node.accounts = require('../helpers/accounts.js'); node._ = require('lodash'); node.async = require('async'); -node.popsicle = require('popsicle'); +node.axios = require('axios').default; node.expect = require('chai').expect; node.chai = require('chai'); node.chai.config.includeStack = true; @@ -39,7 +39,7 @@ node.baseUrl = 'http://' + node.config.address + ':' + node.config.port; // node.baseUrl = 'http://' + node.config.peers.list[2].ip + ':' + node.config.peers.list[2].port; node.api = node.supertest(node.baseUrl); -node.normalizer = 100000000; // Use this to convert LISK amount to normal value +node.normalizer = 100000000; // Use this to convert ADM amount to normal value node.blockTime = 10000; // Block time in milliseconds node.blockTimePlus = 12000; // Block time + 2 seconds in milliseconds node.version = packageJson.version; // Node version @@ -138,8 +138,8 @@ if (process.env.SILENT === 'true') { node.debug = console.log; } -// Returns random LSK amount -node.randomLISK = function () { +// Returns random ADM amount +node.randomADM = function () { return Math.floor(Math.random() * (10000 * 100000000)) + (1000 * 100000000); }; @@ -468,21 +468,17 @@ node.getId = function (trs) { // Returns current block height node.getHeight = function (cb) { - var request = node.popsicle.get(node.baseUrl + '/api/blocks/getHeight'); - - request.use(node.popsicle.plugins.parse(['json'])); - - request.then(function (res) { - if (res.status !== 200) { - return setImmediate(cb, ['Received bad response code', res.status, res.url].join(' ')); - } else { - return setImmediate(cb, null, res.body.height); - } - }); - - request.catch(function (err) { - return setImmediate(cb, err); - }); + node.axios.get(node.baseUrl + '/api/blocks/getHeight') + .then((res) => { + if (res.status !== 200 || !res.data) { + return setImmediate(cb, ['Received bad response code', res.status, res.config.url].join(' ')); + } else { + return setImmediate(cb, null, res.data.height); + } + }) + .catch((err) => { + return setImmediate(cb, err); + }); }; // Run callback on new round @@ -538,30 +534,26 @@ node.waitForNewBlock = function (height, blocksToWait, cb) { node.async.doWhilst( function (cb) { - var request = node.popsicle.get(node.baseUrl + '/api/blocks/getHeight'); - - request.use(node.popsicle.plugins.parse(['json'])); - - request.then(function (res) { - if (res.status !== 200) { - return cb(['Received bad response code', res.status, res.url].join(' ')); - } - - node.debug('== Waiting for block:'.grey, 'Height:'.grey, res.body.height, 'Target:'.grey, target, 'Second:'.grey, counter++); - - if (res.body.height >= target) { - height = res.body.height; - } - - setTimeout(cb, 1000); - }); - - request.catch(function (err) { - return cb(err); - }); + node.axios.get(node.baseUrl + '/api/blocks/getHeight') + .then((res) => { + if (res.status !== 200) { + return cb(['Received bad response code', res.status, res.config.url].join(' ')); + } + + node.debug('== Waiting for block:'.grey, 'Height:'.grey, res.data.height, 'Target:'.grey, target, 'Second:'.grey, counter++); + + if (res.data.height >= target) { + height = res.data.height; + } + + setTimeout(cb, 1000); + }) + .catch(function (err) { + return cb(err); + }); }, - function () { - return actualHeight === height; + function (testCb) { + return testCb(null, actualHeight === height); }, function (err) { if (err) { @@ -580,13 +572,14 @@ node.addPeers = function (numOfPeers, ip, cb) { var os, version; var i = 0; - node.async.whilst(function () { - return i < numOfPeers; + node.async.whilst(function (testCb) { + return testCb(null, i < numOfPeers); }, function (next) { os = operatingSystems[node.randomizeSelection(operatingSystems.length)]; version = node.version; - var request = node.popsicle.get({ + node.axios({ + method: 'get', url: node.baseUrl + '/peer/height', headers: { broadhash: node.config.nethash, @@ -598,22 +591,18 @@ node.addPeers = function (numOfPeers, ip, cb) { version: version, nonce: 'randomNonce' } - }); - - request.use(node.popsicle.plugins.parse(['json'])); - - request.then(function (res) { - if (res.status !== 200) { - return next(['Received bad response code', res.status, res.url].join(' ')); - } else { - i++; - next(); - } - }); - - request.catch(function (err) { - return next(err); - }); + }) + .then(function (res) { + if (res.status !== 200) { + return next(['Received bad response code', res.status, res.config.url].join(' ')); + } else { + i++; + next(); + } + }) + .catch(function (err) { + return next(err); + }); }, function (err) { // Wait for peer to be swept to db setTimeout(function () { diff --git a/test/unit/logic/peers.js b/test/unit/logic/peers.js index 3bbe0a7a..6f10e015 100644 --- a/test/unit/logic/peers.js +++ b/test/unit/logic/peers.js @@ -100,10 +100,10 @@ describe('peers', function () { removeAll(); }); - it('should not insert new peer with lisk-js-api os', function () { + it('should not insert new peer with adm-js-api os', function () { removeAll(); var modifiedPeer = _.clone(randomPeer); - modifiedPeer.os = 'lisk-js-api'; + modifiedPeer.os = 'adm-js-api'; peers.upsert(modifiedPeer); expect(peers.list().length).equal(0); removeAll(); diff --git a/test/unit/logic/transaction.js b/test/unit/logic/transaction.js index def08c4a..360c1512 100644 --- a/test/unit/logic/transaction.js +++ b/test/unit/logic/transaction.js @@ -905,7 +905,7 @@ describe('transaction', function () { accountModule.getAccount({ publicKey: trs.senderPublicKey }, function (err, accountAfter) { var balanceAfter = new bignum(accountAfter.balance.toString()); - expect(balanceBefore.plus(amount.mul(2)).toString()).to.not.equal(balanceAfter.toString()); + expect(balanceBefore.plus(amount.multipliedBy(2)).toString()).to.not.equal(balanceAfter.toString()); expect(balanceBefore.toString()).to.equal(balanceAfter.toString()); done(); }); diff --git a/test/unit/modules/peers.js b/test/unit/modules/peers.js index 4816a665..1a6007ee 100644 --- a/test/unit/modules/peers.js +++ b/test/unit/modules/peers.js @@ -209,9 +209,9 @@ describe('peers', function () { expect(peers.acceptable([privatePeer])).that.is.an('array').and.to.be.empty; }); - it('should not accept peer with lisk-js-api os', function () { + it('should not accept peer with adm-js-api os', function () { var privatePeer = _.clone(randomPeer); - privatePeer.os = 'lisk-js-api'; + privatePeer.os = 'adm-js-api'; expect(peers.acceptable([privatePeer])).that.is.an('array').and.to.be.empty; }); diff --git a/tools/install_node.sh b/tools/install_node.sh index 6c9432ef..8a86dcf2 100644 --- a/tools/install_node.sh +++ b/tools/install_node.sh @@ -37,7 +37,7 @@ while getopts 'b:n:' OPTION; do done printf "\n" -printf "Welcome to the ADAMANT node installer v2.0.1 for Ubuntu 18, 20. Make sure you got this file from adamant.im website or GitHub.\n" +printf "Welcome to the ADAMANT node installer v2.0.2 for Ubuntu 18, 20, 22. Make sure you got this file from adamant.im website or GitHub.\n" printf "This installer is the easiest way to run ADAMANT node. We still recommend to consult IT specialist if you are not familiar with Linux systems.\n" printf "You can see full installation instructions on https://medium.com/adamant-im/how-to-run-your-adamant-node-on-ubuntu-990e391e8fcc\n" printf "The installer will ask you to set database and user passwords during the installation.\n" @@ -106,10 +106,10 @@ fi #Packages printf "Updating system packages…\n\n" sudo apt update && sudo apt upgrade -y -printf "\n\nInstalling postgresql, python and other prerequisites…\n\n" +printf "\n\nInstalling postgresql and other prerequisites…\n\n" sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" > /etc/apt/sources.list.d/pgdg.list' && wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O - | sudo apt-key add - sudo apt update && sudo DEBIAN_FRONTEND=noninteractive apt-get -yq upgrade -sudo apt install -y python build-essential curl automake autoconf libtool rpl mc git postgresql postgresql-contrib libpq-dev redis-server +sudo apt install -y build-essential curl automake autoconf libtool rpl mc git postgresql postgresql-contrib libpq-dev redis-server #Start postgres. This step is necessary for Windows Subsystem for Linux machines sudo service postgresql start @@ -126,11 +126,11 @@ su - "$username" <