From 59757eee13816963b71011365186c85635677297 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 7 Jul 2022 13:37:30 +0600 Subject: [PATCH 01/33] chore: update socket.io version --- app.js | 6 ++++-- modules/clientWs.js | 5 ++++- package.json | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app.js b/app.js index a03c5a2c..eb0d1787 100644 --- a/app.js +++ b/app.js @@ -309,7 +309,9 @@ 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); var privateKey, certificate, https, https_io; @@ -323,7 +325,7 @@ 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); } cb(null, { diff --git a/modules/clientWs.js b/modules/clientWs.js index 66183ec8..ed295421 100644 --- a/modules/clientWs.js +++ b/modules/clientWs.js @@ -1,10 +1,13 @@ +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); + this.describes = {}; this.logger = logger; io.sockets.on('connection', (socket) => { diff --git a/package.json b/package.json index 6efe68fc..67d6fe4f 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "encryption", "crypto", "cryptocurrency" - ], + ], "author": "ADAMANT Tech Labs , Lisk Foundation , lightcurve GmbH ", "license": "GPL-3.0", "dependencies": { @@ -67,7 +67,7 @@ "redis": "=2.7.1", "rimraf": "=2.5.4", "semver": "=5.3.0", - "socket.io": "^1.7.2", + "socket.io": "^4.5.1", "sodium": "^3.0.2", "sodium-browserify-tweetnacl": "*", "strftime": "=0.10.0", From 30c6c7e832861c796d05d32c5a9c9add834f1b23 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 7 Jul 2022 13:46:09 +0600 Subject: [PATCH 02/33] chore: update bignumber.js version --- helpers/RoundChanges.js | 15 ++++++++++----- helpers/database.js | 2 +- logic/transaction.js | 2 +- package.json | 2 +- test/unit/logic/transaction.js | 2 +- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/helpers/RoundChanges.js b/helpers/RoundChanges.js index ad43c928..7ace61e0 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/database.js b/helpers/database.js index 9cbe75e3..1449929d 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); } }); 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/package.json b/package.json index 67d6fe4f..1bf7c313 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "license": "GPL-3.0", "dependencies": { "async": "=2.1.4", - "bignumber.js": "=4.0.0", + "bignumber.js": "=9.0.2", "bitcore-mnemonic": "=1.1.1", "body-parser": "=1.16.0", "bytebuffer": "=5.0.1", 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(); }); From caf28f4364f8fd781023f582cc6ada408f268a8c Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 7 Jul 2022 13:49:09 +0600 Subject: [PATCH 03/33] chore: update redis version --- helpers/cache.js | 34 +++++++++++++++------------- modules/cache.js | 59 ++++++++++++++++++++++++++++++++---------------- package.json | 2 +- schema/config.js | 15 ++---------- test/config.json | 6 ++--- 5 files changed, 62 insertions(+), 54 deletions(-) diff --git a/helpers/cache.js b/helpers/cache.js index 45c83f16..c8e1ed86 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/modules/cache.js b/modules/cache.js index c9d6eaee..3a3d8052 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,19 +104,21 @@ 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(); + client.scan(cursor, { MATCH: pattern }) + .then((res) => { + cursor = res.cursor; + keys = res.keys; if (keys.length > 0) { - client.del(keys, whilstCb); + client.del(keys) + .then((res) => whilstCb(null, res)) + .catch((err) => whilstCb(err)); } else { return whilstCb(); } - } - }); + }) + .catch((err) => { + return whilstCb(err); + }); }, function test () { return 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/package.json b/package.json index 1bf7c313..9d67d642 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "pg-promise": "=5.5.6", "popsicle": "=9.0.0", "randomstring": "=1.1.5", - "redis": "=2.7.1", + "redis": "=4.1.0", "rimraf": "=2.5.4", "semver": "=5.3.0", "socket.io": "^4.5.1", diff --git a/schema/config.js b/schema/config.js index 4e9c294b..b3c2811a 100644 --- a/schema/config.js +++ b/schema/config.js @@ -70,25 +70,14 @@ module.exports = { redis: { type: 'object', properties: { - host: { + url: { type: 'string', - format: 'ip' - }, - port: { - type: 'integer', - minimum: 1, - maximum: 65535 - }, - db: { - type: 'integer', - minimum: 0, - maximum: 15 }, password: { type: ['string', 'null'] } }, - required: ['host', 'port', 'db', 'password'] + required: ['url', 'password'] }, api: { type: 'object', 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" } From a7ea129a1bea7549df2bb47df53ca68b54a8ef79 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 7 Jul 2022 13:59:59 +0600 Subject: [PATCH 04/33] chore: replace old npm api with execa --- modules/chats.js | 1 - modules/dapps.js | 30 ++++++++---------------------- modules/states.js | 1 - package.json | 2 +- 4 files changed, 9 insertions(+), 25 deletions(-) diff --git a/modules/chats.js b/modules/chats.js index 76777e90..efa4d990 100644 --- a/modules/chats.js +++ b/modules/chats.js @@ -5,7 +5,6 @@ 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'); diff --git a/modules/dapps.js b/modules/dapps.js index cb3f74ba..9835c8aa 100644 --- a/modules/dapps.js +++ b/modules/dapps.js @@ -10,7 +10,7 @@ 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'); @@ -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); + }); }; /** diff --git a/modules/states.js b/modules/states.js index e916b111..91ce0724 100644 --- a/modules/states.js +++ b/modules/states.js @@ -5,7 +5,6 @@ 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'); diff --git a/package.json b/package.json index 9d67d642..072f3bd5 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "cors": "=2.8.1", "decompress-zip": "LiskHQ/decompress-zip#f46b0a3", "ejs": "=2.5.5", + "execa": "^5.1.1", "express": "=4.14.1", "express-domain-middleware": "=0.1.0", "express-query-int": "=1.0.1", @@ -58,7 +59,6 @@ "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", From c24e7ff81e76b915e4de9a875dda549ae4140cd9 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 7 Jul 2022 14:04:32 +0600 Subject: [PATCH 05/33] chore: replace popsicle with axios --- modules/chats.js | 1 - modules/dapps.js | 41 +++++++++---------- modules/states.js | 1 - modules/transport.js | 16 ++++---- package.json | 2 +- test/node.js | 96 +++++++++++++++++++------------------------- 6 files changed, 71 insertions(+), 86 deletions(-) diff --git a/modules/chats.js b/modules/chats.js index efa4d990..318a00d8 100644 --- a/modules/chats.js +++ b/modules/chats.js @@ -7,7 +7,6 @@ var extend = require('extend'); var ip = require('ip'); 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/dapps.js b/modules/dapps.js index 9835c8aa..ee4d2b98 100644 --- a/modules/dapps.js +++ b/modules/dapps.js @@ -14,7 +14,7 @@ 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'); @@ -379,7 +379,7 @@ __private.removeDApp = function (dapp, cb) { /** * Creates a temp dir, downloads the dapp as stream and decompress it. * @private - * @implements {popsicle} + * @implements {axios} * @implements {DecompressZip} * @param {dapp} dapp * @param {string} dappPath @@ -418,29 +418,28 @@ __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); diff --git a/modules/states.js b/modules/states.js index 91ce0724..792769b0 100644 --- a/modules/states.js +++ b/modules/states.js @@ -7,7 +7,6 @@ var extend = require('extend'); var ip = require('ip'); 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/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 072f3bd5..46e46426 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "license": "GPL-3.0", "dependencies": { "async": "=2.1.4", + "axios": "^0.27.2", "bignumber.js": "=9.0.2", "bitcore-mnemonic": "=1.1.1", "body-parser": "=1.16.0", @@ -62,7 +63,6 @@ "pg-monitor": "=0.7.1", "pg-native": "=1.10.0", "pg-promise": "=5.5.6", - "popsicle": "=9.0.0", "randomstring": "=1.1.5", "redis": "=4.1.0", "rimraf": "=2.5.4", diff --git a/test/node.js b/test/node.js index a7943b3f..58d50ab1 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; @@ -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,27 +534,23 @@ 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; @@ -586,7 +578,7 @@ node.addPeers = function (numOfPeers, ip, cb) { os = operatingSystems[node.randomizeSelection(operatingSystems.length)]; version = node.version; - var request = node.popsicle.get({ + node.axios.get({ url: node.baseUrl + '/peer/height', headers: { broadhash: node.config.nethash, @@ -598,22 +590,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 () { From bfeb15ae2395e1256994a15eba2ce823a6f523a9 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 7 Jul 2022 14:14:47 +0600 Subject: [PATCH 06/33] chore: update async version --- modules/cache.js | 4 ++-- modules/loader.js | 8 ++++---- modules/sql.js | 4 ++-- package.json | 2 +- test/api/multisignatures.js | 4 ++-- test/api/peer.transactions.main.js | 4 ++-- test/api/peer.transactions.stress.js | 8 ++++---- test/node.js | 8 ++++---- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/modules/cache.js b/modules/cache.js index 3a3d8052..ba346c0f 100644 --- a/modules/cache.js +++ b/modules/cache.js @@ -119,8 +119,8 @@ Cache.prototype.removeByPattern = function (pattern, cb) { .catch((err) => { return whilstCb(err); }); - }, function test () { - return cursor > 0; + }, function test (...args) { + return args[args.length - 1](null, cursor > 0); }, cb); }; 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/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/package.json b/package.json index 46e46426..6c19d32b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "author": "ADAMANT Tech Labs , Lisk Foundation , lightcurve GmbH ", "license": "GPL-3.0", "dependencies": { - "async": "=2.1.4", + "async": "=3.2.4", "axios": "^0.27.2", "bignumber.js": "=9.0.2", "bitcore-mnemonic": "=1.1.1", diff --git a/test/api/multisignatures.js b/test/api/multisignatures.js index e0bd5278..41904196 100644 --- a/test/api/multisignatures.js +++ b/test/api/multisignatures.js @@ -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]; 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.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/node.js b/test/node.js index 58d50ab1..40d06a39 100644 --- a/test/node.js +++ b/test/node.js @@ -552,8 +552,8 @@ node.waitForNewBlock = function (height, blocksToWait, cb) { return cb(err); }); }, - function () { - return actualHeight === height; + function (testCb) { + return testCb(null, actualHeight === height); }, function (err) { if (err) { @@ -572,8 +572,8 @@ 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; From e88f50a7b4ae7bde7197dc25f3c00c8b77cff8f6 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 7 Jul 2022 14:32:09 +0600 Subject: [PATCH 07/33] chore: update pg-related dependencies --- helpers/database.js | 4 ++-- helpers/inserts.js | 4 ++-- package.json | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/helpers/database.js b/helpers/database.js index 1449929d..a56dac68 100644 --- a/helpers/database.js +++ b/helpers/database.js @@ -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/package.json b/package.json index 6c19d32b..f588a956 100644 --- a/package.json +++ b/package.json @@ -60,9 +60,9 @@ "lisk-sandbox": "LiskHQ/lisk-sandbox#162da38", "lodash": "=4.17.15", "method-override": "=2.3.10", - "pg-monitor": "=0.7.1", - "pg-native": "=1.10.0", - "pg-promise": "=5.5.6", + "pg-monitor": "=1.4.1", + "pg-native": "=3.0.0", + "pg-promise": "=10.11.1", "randomstring": "=1.1.5", "redis": "=4.1.0", "rimraf": "=2.5.4", From 1aeff5aea01b257d66fd67b6ac14f5ad8d594c60 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 7 Jul 2022 14:49:09 +0600 Subject: [PATCH 08/33] chore: update commander version --- app.js | 28 +++++++++++++++------------- package.json | 2 +- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app.js b/app.js index eb0d1787..3953d41b 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) ); } diff --git a/package.json b/package.json index f588a956..525f9620 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "bytebuffer": "=5.0.1", "change-case": "=3.0.1", "colors": "=1.1.2", - "commander": "=2.9.0", + "commander": "=9.3.0", "compression": "=1.6.2", "cors": "=2.8.1", "decompress-zip": "LiskHQ/decompress-zip#f46b0a3", From 672207bfa577d3d02b1d664856dbbc717c3e655f Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 7 Jul 2022 15:13:45 +0600 Subject: [PATCH 09/33] chore: update express version --- helpers/request-limiter.js | 7 ++++--- package.json | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) 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/package.json b/package.json index 525f9620..d815c707 100644 --- a/package.json +++ b/package.json @@ -48,10 +48,11 @@ "decompress-zip": "LiskHQ/decompress-zip#f46b0a3", "ejs": "=2.5.5", "execa": "^5.1.1", - "express": "=4.14.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.4.0", + "express-slow-down": "1.4.0", "extend": "=3.0.2", "ip": "=1.1.5", "js-nacl": "^1.2.2", From 3094c425e07d96f1e46eca2faa575a5cf21118ee Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 7 Jul 2022 15:19:28 +0600 Subject: [PATCH 10/33] chore: update dev dependencies --- Gruntfile.js | 2 +- package.json | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) 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/package.json b/package.json index d815c707..44dcf29d 100644 --- a/package.json +++ b/package.json @@ -78,22 +78,23 @@ "z-schema": "=3.18.2" }, "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", + "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.10", + "mocha": "^10.0.0", + "moment": "^2.29.3", "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.3" }, "config": { "minVersion": ">=0.4.0" From 991b66741d65db86b73fbeeec3231efe436e7f86 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 7 Jul 2022 15:21:00 +0600 Subject: [PATCH 11/33] fix: npm script to server jsdoc --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 44dcf29d..8b1b4212 100644 --- a/package.json +++ b/package.json @@ -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", From ec6e8cdb75981da476d44dca538ce11a0f365e4c Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 7 Jul 2022 15:25:43 +0600 Subject: [PATCH 12/33] chore: update non-breakable dependencies and fix some vulnerabilities --- package.json | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 8b1b4212..b38b502e 100644 --- a/package.json +++ b/package.json @@ -37,16 +37,16 @@ "async": "=3.2.4", "axios": "^0.27.2", "bignumber.js": "=9.0.2", - "bitcore-mnemonic": "=1.1.1", - "body-parser": "=1.16.0", + "bitcore-mnemonic": "=8.25.30", + "body-parser": "=1.20.0", "bytebuffer": "=5.0.1", - "change-case": "=3.0.1", - "colors": "=1.1.2", + "change-case": "=4.1.2", + "colors": "=1.4.0", "commander": "=9.3.0", - "compression": "=1.6.2", - "cors": "=2.8.1", + "compression": "=1.7.4", + "cors": "=2.8.5", "decompress-zip": "LiskHQ/decompress-zip#f46b0a3", - "ejs": "=2.5.5", + "ejs": "=3.1.8", "execa": "^5.1.1", "express": "=4.18.1", "express-domain-middleware": "=0.1.0", @@ -54,28 +54,28 @@ "express-rate-limit": "=6.4.0", "express-slow-down": "1.4.0", "extend": "=3.0.2", - "ip": "=1.1.5", - "js-nacl": "^1.2.2", - "json-schema": "=0.2.3", + "ip": "=1.1.8", + "js-nacl": "^1.4.0", + "json-schema": "=0.4.0", "json-sql": "LiskHQ/json-sql#27e1ed1", "lisk-sandbox": "LiskHQ/lisk-sandbox#162da38", - "lodash": "=4.17.15", - "method-override": "=2.3.10", + "lodash": "=4.17.21", + "method-override": "=3.0.0", + "npm": "=8.13.1", "pg-monitor": "=1.4.1", "pg-native": "=3.0.0", "pg-promise": "=10.11.1", - "randomstring": "=1.1.5", + "randomstring": "=1.2.2", "redis": "=4.1.0", - "rimraf": "=2.5.4", - "semver": "=5.3.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", "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.6", @@ -96,6 +96,14 @@ "sinon": "^14.0.0", "supertest": "^6.2.3" }, + "overrides": { + "grunt-contrib-obfuscator": { + "javascript-obfuscator": "^4.0.0" + }, + "json-sql": { + "underscore": "^1.13.4" + } + }, "config": { "minVersion": ">=0.4.0" }, From 0c16df6aa2e508d5d028ca60d4afad6e5afd42e8 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 7 Jul 2022 15:45:34 +0600 Subject: [PATCH 13/33] fix: eslint errors --- config.json | 404 +++++++++++++++++++++++++++++----------- helpers/RoundChanges.js | 10 +- helpers/cache.js | 32 ++-- modules/cache.js | 66 +++---- schema/config.js | 2 +- 5 files changed, 348 insertions(+), 166 deletions(-) diff --git a/config.json b/config.json index 1d59e3fd..6b050073 100644 --- a/config.json +++ b/config.json @@ -1,117 +1,299 @@ -{ - "port": 36666, - "address": "0.0.0.0", - "fileLogLevel": "warn", - "logFileName": "logs/adamant.log", - "consoleLogLevel": "info", - "trustProxy": false, - "topAccounts": false, - "cacheEnabled": false, - "db": { - "host": "localhost", - "port": 5432, - "database": "adamant_main", - "user": "adamant", - "password": "password", - "poolSize": 95, - "poolIdleTimeout": 30000, - "reapIntervalMillis": 1000, - "logEvents": [ - "error" - ] - }, - "redis": { - "host": "127.0.0.1", - "port": 6379, - "db": 0, - "password": null - }, - "api": { - "enabled": true, - "access": { - "public": false, - "whiteList": [ - "127.0.0.1" - ] +'use strict'; + +module.exports = { + config: { + id: 'appCon', + type: 'object', + properties: { + port: { + type: 'integer', + minimum: 1, + maximum: 65535 + }, + address: { + type: 'string', + format: 'ip' + }, + fileLogLevel: { + type: 'string' + }, + logFileName: { + type: 'string' + }, + consoleLogLevel: { + type: 'string' + }, + trustProxy: { + type: 'boolean' + }, + topAccounts: { + type: 'boolean' + }, + cacheEnabled: { + type: 'boolean' + }, + db: { + type: 'object', + properties: { + host: { + type: 'string' + }, + port: { + type: 'integer', + minimum: 1, + maximum: 65535 + }, + database: { + type: 'string' + }, + user: { + type: 'string' + }, + password: { + type: 'string' + }, + poolSize: { + type: 'integer' + }, + poolIdleTimeout: { + type: 'integer' + }, + reapIntervalMillis: { + type: 'integer' + }, + logEvents: { + type: 'array' + } }, - "options": { - "limits": { - "max": 0, - "delayMs": 0, - "delayAfter": 0, - "windowMs": 60000 - } - } - }, - "peers": { - "enabled": true, - "list": [ - { - "ip": "51.15.221.205", - "port": 36666 + required: ['host', 'port', 'database', 'user', 'password', 'poolSize', 'poolIdleTimeout', 'reapIntervalMillis', 'logEvents'] + }, + redis: { + type: 'object', + properties: { + url: { + type: 'string' + }, + password: { + type: ['string', 'null'] + } + }, + required: ['url', 'password'] + }, + api: { + type: 'object', + properties: { + enabled: { + type: 'boolean' + }, + access: { + type: 'object', + properties: { + public: { + type: 'boolean' + }, + whiteList: { + type: 'array' + } }, - { - "ip": "51.15.88.53", - "port": 36666 + required: ['public', 'whiteList'] + }, + options: { + type: 'object', + properties: { + limits: { + type: 'object', + properties: { + max: { + type: 'integer' + }, + delayMs: { + type: 'integer' + }, + delayAfter: { + type: 'integer' + }, + windowMs: { + type: 'integer' + } + }, + required: ['max', 'delayMs', 'delayAfter', 'windowMs'] + } }, - { - "ip": "51.15.217.230", - "port": 36666 - } - ], - "access": { - "blackList": [] + required: ['limits'] + } }, - "options": { - "limits": { - "max": 0, - "delayMs": 0, - "delayAfter": 0, - "windowMs": 60000 + required: ['enabled', 'access', 'options'] + }, + peers: { + type: 'object', + properties: { + enabled: { + type: 'boolean' + }, + list: { + type: 'array' + }, + access: { + type: 'object', + properties: { + blackList: { + type: 'array' + } }, - "timeout": 5000 - } - }, - "broadcasts": { - "broadcastInterval": 1500, - "broadcastLimit": 20, - "parallelLimit": 20, - "releaseLimit": 25, - "relayLimit": 3 - }, - "transactions": { - "maxTxsPerQueue": 1000 - }, - "forging": { - "force": false, - "secret": [], - "access": { - "whiteList": [ - "127.0.0.1" - ] - } - }, - "loading": { - "verifyOnLoading": false, - "loadPerIteration": 5000 - }, - "ssl": { - "enabled": false, - "options": { - "port": 443, - "address": "0.0.0.0", - "key": "./ssl/lisk.key", - "cert": "./ssl/lisk.crt" - } - }, - "dapp": { - "masterrequired": true, - "masterpassword": "", - "autoexec": [] - }, - "wsClient": { - "portWS": 36668, - "enabled": true + required: ['blackList'] + }, + options: { + properties: { + limits: { + type: 'object', + properties: { + max: { + type: 'integer' + }, + delayMs: { + type: 'integer' + }, + delayAfter: { + type: 'integer' + }, + windowMs: { + type: 'integer' + } + }, + required: ['max', 'delayMs', 'delayAfter', 'windowMs'] + }, + timeout: { + type: 'integer' + } + }, + required: ['limits', 'timeout'] + } + }, + required: ['enabled', 'list', 'access', 'options'] + }, + broadcasts: { + type: 'object', + properties: { + broadcastInterval: { + type: 'integer', + minimum: 1000, + maximum: 60000 + }, + broadcastLimit: { + type: 'integer', + minimum: 1, + maximum: 100 + }, + parallelLimit: { + type: 'integer', + minimum: 1, + maximum: 100 + }, + releaseLimit: { + type: 'integer', + minimum: 1, + maximum: 25 + }, + relayLimit: { + type: 'integer', + minimum: 1, + maximum: 100 + } + }, + required: ['broadcastInterval', 'broadcastLimit', 'parallelLimit', 'releaseLimit', 'relayLimit'] + }, + transactions: { + type: 'object', + maxTxsPerQueue: { + type: 'integer', + minimum: 100, + maximum: 5000 + }, + required: ['maxTxsPerQueue'] + }, + forging: { + type: 'object', + properties: { + force: { + type: 'boolean' + }, + secret: { + type: 'array' + }, + access: { + type: 'object', + properties: { + whiteList: { + type: 'array' + } + }, + required: ['whiteList'] + } + }, + required: ['force', 'secret', 'access'] + }, + loading: { + type: 'object', + properties: { + verifyOnLoading: { + type: 'boolean' + }, + loadPerIteration: { + type: 'integer', + minimum: 1, + maximum: 5000 + } + }, + required: ['verifyOnLoading', 'loadPerIteration'] + }, + ssl: { + type: 'object', + properties: { + enabled: { + type: 'boolean' + }, + options: { + type: 'object', + properties: { + port: { + type: 'integer' + }, + address: { + type: 'string', + format: 'ip' + }, + key: { + type: 'string' + }, + cert: { + type: 'string' + } + }, + required: ['port', 'address', 'key', 'cert'] + } + }, + required: ['enabled', 'options'] + }, + dapp: { + type: 'object', + properties: { + masterrequired: { + type: 'boolean' + }, + masterpassword: { + type: 'string' + }, + autoexec: { + type: 'array' + } + }, + required: ['masterrequired', 'masterpassword', 'autoexec'] + }, + nethash: { + type: 'string', + format: 'hex' + } }, - "nethash": "bd330166898377fb28743ceef5e43a5d9d0a3efd9b3451fb7bc53530bb0a6d64" -} + required: ['port', 'address', 'fileLogLevel', 'logFileName', 'consoleLogLevel', 'trustProxy', 'topAccounts', 'db', 'api', 'peers', 'broadcasts', 'transactions', 'forging', 'loading', 'ssl', 'dapp', 'nethash', 'cacheEnabled', 'redis'] + } +}; diff --git a/helpers/RoundChanges.js b/helpers/RoundChanges.js index 7ace61e0..fda57ca9 100644 --- a/helpers/RoundChanges.js +++ b/helpers/RoundChanges.js @@ -22,15 +22,15 @@ function RoundChanges (scope) { // Apply rewards factor this.roundRewards.forEach(function (reward, index) { this.roundRewards[index] = new bignum(reward.toPrecision(15)) - .times(exceptions.rounds[scope.round].rewards_factor) - .integerValue(bignum.ROUND_FLOOR); + .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) - .integerValue(bignum.ROUND_FLOOR); + .times(exceptions.rounds[scope.round].fees_factor) + .plus(exceptions.rounds[scope.round].fees_bonus) + .integerValue(bignum.ROUND_FLOOR); } } diff --git a/helpers/cache.js b/helpers/cache.js index c8e1ed86..4f063bea 100644 --- a/helpers/cache.js +++ b/helpers/cache.js @@ -24,21 +24,21 @@ module.exports.connect = function (cacheEnabled, config, logger, cb) { var client = redis.createClient(config); client.connect() - .then(() => { - logger.info('App connected with redis server'); + .then(() => { + logger.info('App connected with redis server'); - 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 }); - } - }); + 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/modules/cache.js b/modules/cache.js index ba346c0f..94b52d31 100644 --- a/modules/cache.js +++ b/modules/cache.js @@ -49,13 +49,13 @@ Cache.prototype.getJsonForKey = function (key, cb) { return cb(errorCacheDisabled); } client.get(key) - .then((value) => { - // parsing string to json - return cb(null, JSON.parse(value)); - }) - .catch((err) => { - return cb(err, key); - }); + .then((value) => { + // parsing string to json + return cb(null, JSON.parse(value)); + }) + .catch((err) => { + return cb(err, key); + }); }; /** @@ -71,12 +71,12 @@ Cache.prototype.setJsonForKey = function (key, value, cb) { // redis calls toString on objects, which converts it to object [object] so calling stringify before saving client.set(key, JSON.stringify(value)) - .then((res) => { - cb(null, res) - }) - .catch((err) => { - cb(err, value) - }); + .then((res) => { + cb(null, res) + }) + .catch((err) => { + cb(err, value) + }); }; /** @@ -89,8 +89,8 @@ Cache.prototype.deleteJsonForKey = function (key, cb) { } client.del(key) - .then((res) => cb(null, res)) - .catch((err) => cb(err, key)); + .then((res) => cb(null, res)) + .catch((err) => cb(err, key)); }; /** @@ -105,20 +105,20 @@ Cache.prototype.removeByPattern = function (pattern, cb) { var keys, cursor = 0; async.doWhilst(function iteratee (whilstCb) { 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); - }); + .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); @@ -134,8 +134,8 @@ Cache.prototype.flushDb = function (cb) { } client.flushDb() - .then((res) => cb(null, res)) - .catch((err) => cb(err)); + .then((res) => cb(null, res)) + .catch((err) => cb(err)); }; /** @@ -157,8 +157,8 @@ Cache.prototype.quit = function (cb) { } client.quit() - .then((res) => cb(null, res)) - .catch((err) => cb(err)); + .then((res) => cb(null, res)) + .catch((err) => cb(err)); }; /** diff --git a/schema/config.js b/schema/config.js index b3c2811a..6b050073 100644 --- a/schema/config.js +++ b/schema/config.js @@ -71,7 +71,7 @@ module.exports = { type: 'object', properties: { url: { - type: 'string', + type: 'string' }, password: { type: ['string', 'null'] From 56059c2068a1db65399eec474a5eff5442f8aa52 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 7 Jul 2022 15:49:39 +0600 Subject: [PATCH 14/33] fix: undo changes in config.json --- config.json | 404 +++++++++++++++------------------------------------- 1 file changed, 111 insertions(+), 293 deletions(-) diff --git a/config.json b/config.json index 6b050073..1d59e3fd 100644 --- a/config.json +++ b/config.json @@ -1,299 +1,117 @@ -'use strict'; - -module.exports = { - config: { - id: 'appCon', - type: 'object', - properties: { - port: { - type: 'integer', - minimum: 1, - maximum: 65535 - }, - address: { - type: 'string', - format: 'ip' - }, - fileLogLevel: { - type: 'string' - }, - logFileName: { - type: 'string' - }, - consoleLogLevel: { - type: 'string' - }, - trustProxy: { - type: 'boolean' - }, - topAccounts: { - type: 'boolean' - }, - cacheEnabled: { - type: 'boolean' - }, - db: { - type: 'object', - properties: { - host: { - type: 'string' - }, - port: { - type: 'integer', - minimum: 1, - maximum: 65535 - }, - database: { - type: 'string' - }, - user: { - type: 'string' - }, - password: { - type: 'string' - }, - poolSize: { - type: 'integer' - }, - poolIdleTimeout: { - type: 'integer' - }, - reapIntervalMillis: { - type: 'integer' - }, - logEvents: { - type: 'array' - } - }, - required: ['host', 'port', 'database', 'user', 'password', 'poolSize', 'poolIdleTimeout', 'reapIntervalMillis', 'logEvents'] - }, - redis: { - type: 'object', - properties: { - url: { - type: 'string' - }, - password: { - type: ['string', 'null'] - } - }, - required: ['url', 'password'] - }, - api: { - type: 'object', - properties: { - enabled: { - type: 'boolean' - }, - access: { - type: 'object', - properties: { - public: { - type: 'boolean' - }, - whiteList: { - type: 'array' - } - }, - required: ['public', 'whiteList'] - }, - options: { - type: 'object', - properties: { - limits: { - type: 'object', - properties: { - max: { - type: 'integer' - }, - delayMs: { - type: 'integer' - }, - delayAfter: { - type: 'integer' - }, - windowMs: { - type: 'integer' - } - }, - required: ['max', 'delayMs', 'delayAfter', 'windowMs'] - } - }, - required: ['limits'] - } +{ + "port": 36666, + "address": "0.0.0.0", + "fileLogLevel": "warn", + "logFileName": "logs/adamant.log", + "consoleLogLevel": "info", + "trustProxy": false, + "topAccounts": false, + "cacheEnabled": false, + "db": { + "host": "localhost", + "port": 5432, + "database": "adamant_main", + "user": "adamant", + "password": "password", + "poolSize": 95, + "poolIdleTimeout": 30000, + "reapIntervalMillis": 1000, + "logEvents": [ + "error" + ] + }, + "redis": { + "host": "127.0.0.1", + "port": 6379, + "db": 0, + "password": null + }, + "api": { + "enabled": true, + "access": { + "public": false, + "whiteList": [ + "127.0.0.1" + ] }, - required: ['enabled', 'access', 'options'] - }, - peers: { - type: 'object', - properties: { - enabled: { - type: 'boolean' - }, - list: { - type: 'array' - }, - access: { - type: 'object', - properties: { - blackList: { - type: 'array' - } + "options": { + "limits": { + "max": 0, + "delayMs": 0, + "delayAfter": 0, + "windowMs": 60000 + } + } + }, + "peers": { + "enabled": true, + "list": [ + { + "ip": "51.15.221.205", + "port": 36666 }, - required: ['blackList'] - }, - options: { - properties: { - limits: { - type: 'object', - properties: { - max: { - type: 'integer' - }, - delayMs: { - type: 'integer' - }, - delayAfter: { - type: 'integer' - }, - windowMs: { - type: 'integer' - } - }, - required: ['max', 'delayMs', 'delayAfter', 'windowMs'] - }, - timeout: { - type: 'integer' - } + { + "ip": "51.15.88.53", + "port": 36666 }, - required: ['limits', 'timeout'] - } - }, - required: ['enabled', 'list', 'access', 'options'] - }, - broadcasts: { - type: 'object', - properties: { - broadcastInterval: { - type: 'integer', - minimum: 1000, - maximum: 60000 - }, - broadcastLimit: { - type: 'integer', - minimum: 1, - maximum: 100 - }, - parallelLimit: { - type: 'integer', - minimum: 1, - maximum: 100 - }, - releaseLimit: { - type: 'integer', - minimum: 1, - maximum: 25 - }, - relayLimit: { - type: 'integer', - minimum: 1, - maximum: 100 - } - }, - required: ['broadcastInterval', 'broadcastLimit', 'parallelLimit', 'releaseLimit', 'relayLimit'] - }, - transactions: { - type: 'object', - maxTxsPerQueue: { - type: 'integer', - minimum: 100, - maximum: 5000 + { + "ip": "51.15.217.230", + "port": 36666 + } + ], + "access": { + "blackList": [] }, - required: ['maxTxsPerQueue'] - }, - forging: { - type: 'object', - properties: { - force: { - type: 'boolean' - }, - secret: { - type: 'array' - }, - access: { - type: 'object', - properties: { - whiteList: { - type: 'array' - } + "options": { + "limits": { + "max": 0, + "delayMs": 0, + "delayAfter": 0, + "windowMs": 60000 }, - required: ['whiteList'] - } - }, - required: ['force', 'secret', 'access'] - }, - loading: { - type: 'object', - properties: { - verifyOnLoading: { - type: 'boolean' - }, - loadPerIteration: { - type: 'integer', - minimum: 1, - maximum: 5000 - } - }, - required: ['verifyOnLoading', 'loadPerIteration'] - }, - ssl: { - type: 'object', - properties: { - enabled: { - type: 'boolean' - }, - options: { - type: 'object', - properties: { - port: { - type: 'integer' - }, - address: { - type: 'string', - format: 'ip' - }, - key: { - type: 'string' - }, - cert: { - type: 'string' - } - }, - required: ['port', 'address', 'key', 'cert'] - } - }, - required: ['enabled', 'options'] - }, - dapp: { - type: 'object', - properties: { - masterrequired: { - type: 'boolean' - }, - masterpassword: { - type: 'string' - }, - autoexec: { - type: 'array' - } - }, - required: ['masterrequired', 'masterpassword', 'autoexec'] - }, - nethash: { - type: 'string', - format: 'hex' - } + "timeout": 5000 + } + }, + "broadcasts": { + "broadcastInterval": 1500, + "broadcastLimit": 20, + "parallelLimit": 20, + "releaseLimit": 25, + "relayLimit": 3 + }, + "transactions": { + "maxTxsPerQueue": 1000 + }, + "forging": { + "force": false, + "secret": [], + "access": { + "whiteList": [ + "127.0.0.1" + ] + } + }, + "loading": { + "verifyOnLoading": false, + "loadPerIteration": 5000 + }, + "ssl": { + "enabled": false, + "options": { + "port": 443, + "address": "0.0.0.0", + "key": "./ssl/lisk.key", + "cert": "./ssl/lisk.crt" + } + }, + "dapp": { + "masterrequired": true, + "masterpassword": "", + "autoexec": [] + }, + "wsClient": { + "portWS": 36668, + "enabled": true }, - required: ['port', 'address', 'fileLogLevel', 'logFileName', 'consoleLogLevel', 'trustProxy', 'topAccounts', 'db', 'api', 'peers', 'broadcasts', 'transactions', 'forging', 'loading', 'ssl', 'dapp', 'nethash', 'cacheEnabled', 'redis'] - } -}; + "nethash": "bd330166898377fb28743ceef5e43a5d9d0a3efd9b3451fb7bc53530bb0a6d64" +} From 6351e62b7080aaa925d3b7cf41ae014af28ddd96 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 7 Jul 2022 15:51:38 +0600 Subject: [PATCH 15/33] fix: eslint errors --- modules/cache.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/cache.js b/modules/cache.js index 94b52d31..0ed99575 100644 --- a/modules/cache.js +++ b/modules/cache.js @@ -72,10 +72,10 @@ Cache.prototype.setJsonForKey = function (key, value, cb) { // redis calls toString on objects, which converts it to object [object] so calling stringify before saving client.set(key, JSON.stringify(value)) .then((res) => { - cb(null, res) + cb(null, res); }) .catch((err) => { - cb(err, value) + cb(err, value); }); }; @@ -110,8 +110,8 @@ Cache.prototype.removeByPattern = function (pattern, cb) { keys = res.keys; if (keys.length > 0) { client.del(keys) - .then((res) => whilstCb(null, res)) - .catch((err) => whilstCb(err)); + .then((res) => whilstCb(null, res)) + .catch((err) => whilstCb(err)); } else { return whilstCb(); } From c954aad00c7d64703955ad4347f802298fb016f9 Mon Sep 17 00:00:00 2001 From: martiliones Date: Sun, 17 Jul 2022 02:05:21 +0600 Subject: [PATCH 16/33] chore: replace lisk with adamant --- config.json | 4 ++-- modules/blocks/verify.js | 2 +- modules/peers.js | 6 +++--- modules/transactions.js | 2 +- sql/memoryTables.sql | 2 +- test/api/accounts.js | 16 +++++++-------- test/api/dapps.js | 16 +++++++-------- test/api/delegates.js | 6 +++--- test/api/multisignatures.js | 20 +++++++++---------- test/api/peer.transactions.multisignatures.js | 4 ++-- test/api/peer.transactions.signatures.js | 4 ++-- test/api/signatures.js | 12 +++++------ test/api/transactions.js | 4 ++-- test/node.js | 6 +++--- test/unit/logic/peers.js | 4 ++-- test/unit/modules/peers.js | 4 ++-- 16 files changed, 56 insertions(+), 56 deletions(-) diff --git a/config.json b/config.json index 1d59e3fd..37575d18 100644 --- a/config.json +++ b/config.json @@ -100,8 +100,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/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/peers.js b/modules/peers.js index ba1bd507..3eb6e4f1 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -415,9 +415,9 @@ Peers.prototype.acceptable = function (peers) { .filter(function (peer) { // Removing peers with private address or nonce equal to self if ((process.env['NODE_ENV'] || '').toUpperCase() === 'TEST') { - return peer.nonce !== modules.system.getNonce() && (peer.os !== 'lisk-js-api'); + return peer.nonce !== modules.system.getNonce() && (peer.os !== 'adm-js-api'); } - 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() && (peer.os !== 'adm-js-api'); }).value(); }; @@ -679,7 +679,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/transactions.js b/modules/transactions.js index 035b84d8..7d15d215 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/sql/memoryTables.sql b/sql/memoryTables.sql index 0da9d142..2e4850f5 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 41904196..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, @@ -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.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/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/node.js b/test/node.js index 40d06a39..e8474825 100644 --- a/test/node.js +++ b/test/node.js @@ -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); }; 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/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; }); From 17b9ce6a238ae77a16fd36c3b54e31d9dec2af52 Mon Sep 17 00:00:00 2001 From: martiliones Date: Sun, 17 Jul 2022 23:52:32 +0600 Subject: [PATCH 17/33] fix: redis config for mainnet --- config.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/config.json b/config.json index 37575d18..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": { From bce5014e1a70db5737865938830c0856bbc854d9 Mon Sep 17 00:00:00 2001 From: martiliones Date: Mon, 18 Jul 2022 01:37:57 +0600 Subject: [PATCH 18/33] chore: move lisk-sandbox from depends to legacy directory --- legacy/lisk-sandbox.js | 231 +++++++++++++++++++++++++++++++++++++++++ modules/dapps.js | 2 +- package.json | 1 - 3 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 legacy/lisk-sandbox.js 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/modules/dapps.js b/modules/dapps.js index ee4d2b98..aebfaac4 100644 --- a/modules/dapps.js +++ b/modules/dapps.js @@ -17,7 +17,7 @@ var path = require('path'); 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'); diff --git a/package.json b/package.json index b38b502e..2d2158e3 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "js-nacl": "^1.4.0", "json-schema": "=0.4.0", "json-sql": "LiskHQ/json-sql#27e1ed1", - "lisk-sandbox": "LiskHQ/lisk-sandbox#162da38", "lodash": "=4.17.21", "method-override": "=3.0.0", "npm": "=8.13.1", From e2d179b672d37efa874429bc5ba5d8a8f2b4773c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=8C=EF=B8=8F?= Date: Tue, 26 Jul 2022 23:36:47 +0600 Subject: [PATCH 19/33] style: change the name of blockchain to upper case Co-authored-by: adamant-al <33592982+adamant-al@users.noreply.github.com> --- modules/peers.js | 2 +- modules/transactions.js | 2 +- sql/memoryTables.sql | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index 3eb6e4f1..fb5ac5de 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -679,7 +679,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 Adamant 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/transactions.js b/modules/transactions.js index 7d15d215..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])) { - // Adamant 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/sql/memoryTables.sql b/sql/memoryTables.sql index 2e4850f5..ee80c8b0 100644 --- a/sql/memoryTables.sql +++ b/sql/memoryTables.sql @@ -1,4 +1,4 @@ -/* Adamant Memory Tables +/* ADAMANT Memory Tables * */ From 909fb9803b6d8995518d91c200d895741c6efb1f Mon Sep 17 00:00:00 2001 From: martiliones Date: Wed, 27 Jul 2022 00:03:20 +0600 Subject: [PATCH 20/33] feat: enable compatibility with Socket.IO v2 clients --- app.js | 8 ++++++-- modules/clientWs.js | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index 3953d41b..08f7e451 100644 --- a/app.js +++ b/app.js @@ -313,7 +313,9 @@ d.run(function () { var server = require('http').createServer(app); const { Server } = require('socket.io'); - const io = new Server(server); + const io = new Server(server, { + allowEIO3: true + }); var privateKey, certificate, https, https_io; @@ -327,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 = new Server(https); + https_io = new Server(https, { + allowEIO3: true + }); } cb(null, { diff --git a/modules/clientWs.js b/modules/clientWs.js index ed295421..585f76d4 100644 --- a/modules/clientWs.js +++ b/modules/clientWs.js @@ -6,7 +6,9 @@ class ClientWs { return false; } const port = config.portWS; - const io = new Server(port); + const io = new Server(port, { + allowEIO3: true + }); this.describes = {}; this.logger = logger; From 94972606c2bd158c458f7b5ae9fefd487302bc08 Mon Sep 17 00:00:00 2001 From: martiliones Date: Sun, 31 Jul 2022 07:41:21 +0600 Subject: [PATCH 21/33] chore: replace json-sql with knex.js --- logic/account.js | 159 ++++++++++++++++++++----------------- modules/accounts.js | 3 +- modules/delegates.js | 38 +++++++-- modules/multisignatures.js | 8 +- modules/sql.js | 121 ++++++++++++++++++++++------ 5 files changed, 221 insertions(+), 108 deletions(-) diff --git a/logic/account.js b/logic/account.js index 3640730b..bdbc46e3 100644 --- a/logic/account.js +++ b/logic/account.js @@ -3,8 +3,10 @@ var async = require('async'); var pgp = require('pg-promise'); var path = require('path'); -var jsonSql = require('json-sql')(); -jsonSql.setDialect('postgresql'); +var knex = require('knex')({ + client: 'pg' +}); + var constants = require('../helpers/constants.js'); var slots = require('../helpers/slots.js'); @@ -456,14 +458,14 @@ Account.prototype.removeTables = function (cb) { 'mem_accounts2u_delegates', 'mem_accounts2multisignatures', 'mem_accounts2u_multisignatures'].forEach(function (table) { - sql = jsonSql.build({ - type: 'remove', - table: table - }); - sqles.push(sql.query); + const sql = knex.delete() + .from(table) + .toString(); + + sqles.push(sql); }); - this.scope.db.query(sqles.join('')).then(function () { + this.scope.db.query(sqles.join('; ')).then(function () { return setImmediate(cb); }).catch(function (err) { library.logger.error(err.stack); @@ -582,6 +584,8 @@ Account.prototype.getAll = function (filter, fields, cb) { }.bind(this)); var limit, offset, sort; + const rawFilters = filter.raw || []; + delete filter.raw; if (filter.limit > 0) { limit = filter.limit; @@ -599,23 +603,39 @@ Account.prototype.getAll = function (filter, fields, cb) { delete filter.sort; if (typeof filter.address === 'string') { - filter.address = { - $upper: ['address', filter.address] - }; + rawFilters.push( + knex.raw('address = UPPER(?)', filter.address) + ); + delete filter.address; } - var sql = jsonSql.build({ - type: 'select', - table: this.table, - limit: limit, - offset: offset, - sort: sort, - alias: 'a', - condition: filter, - fields: realFields - }); + const sql = knex(this.table) + .select(realFields) + .from({ a: this.table }) + .modify(function (queryBuilder) { + if (limit) { + queryBuilder.limit(limit); + } + + if (offset) { + queryBuilder.offset(offset); + } + + if (sort) { + queryBuilder.orderBy(Array.isArray(sort) ? sort : [sort]); + } + + if (Object.keys(filter).length >= 1) { + builder.where(filter); + } + + if (rawFilters.length >= 1) { + builder.where(rawFilters.join(' and ')); + } + }) + .toString(); - this.scope.db.query(sql.query, sql.values).then(function (rows) { + this.scope.db.query(sql).then(function (rows) { return setImmediate(cb, null, rows); }).catch(function (err) { library.logger.error(err.stack); @@ -638,15 +658,13 @@ Account.prototype.set = function (address, fields, cb) { address = String(address).toUpperCase(); fields.address = address; - var sql = jsonSql.build({ - type: 'insertorupdate', - table: this.table, - conflictFields: ['address'], - values: this.toDB(fields), - modifier: this.toDB(fields) - }); + const sql = knex(this.table) + .insert(this.toDB(fields)) + .onConflict(['address']) + .merge(this.toDB(fields)) + .toString(); - this.scope.db.none(sql.query, sql.values).then(function () { + this.scope.db.none(sql).then(function () { return setImmediate(cb); }).catch(function (err) { library.logger.error(err.stack); @@ -797,16 +815,16 @@ Account.prototype.merge = function (address, diff, cb) { var sqles = []; + const tableName = `${self.table}2${el}`; + if (Object.keys(remove).length) { Object.keys(remove).forEach(function (el) { - var sql = jsonSql.build({ - type: 'remove', - table: self.table + '2' + el, - condition: { - dependentId: { $in: remove[el] }, - accountId: address - } - }); + const sql = knex(tableName) + .whereIn('dependentId', remove[el]) + .andWhere('accountId', address) + .del() + .toString(); + sqles.push(sql); }); } @@ -814,14 +832,13 @@ Account.prototype.merge = function (address, diff, cb) { if (Object.keys(insert).length) { Object.keys(insert).forEach(function (el) { for (var i = 0; i < insert[el].length; i++) { - var sql = jsonSql.build({ - type: 'insert', - table: self.table + '2' + el, - values: { - accountId: address, - dependentId: insert[el][i] - } - }); + const sql = knex(tableName) + .insert({ + accountId: address, + dependentId: insert[el][i] + }) + .toString(); + sqles.push(sql); } }); @@ -830,11 +847,11 @@ Account.prototype.merge = function (address, diff, cb) { if (Object.keys(remove_object).length) { Object.keys(remove_object).forEach(function (el) { remove_object[el].accountId = address; - var sql = jsonSql.build({ - type: 'remove', - table: self.table + '2' + el, - condition: remove_object[el] - }); + const sql = knex(tableName) + .where(remove_object[el]) + .del() + .toString(); + sqles.push(sql); }); } @@ -843,25 +860,21 @@ Account.prototype.merge = function (address, diff, cb) { Object.keys(insert_object).forEach(function (el) { insert_object[el].accountId = address; for (var i = 0; i < insert_object[el].length; i++) { - var sql = jsonSql.build({ - type: 'insert', - table: self.table + '2' + el, - values: insert_object[el] - }); + const sql = knex(tableName) + .insert(insert_object[el]) + .toString(); + sqles.push(sql); } }); } if (Object.keys(update).length) { - var sql = jsonSql.build({ - type: 'update', - table: this.table, - modifier: update, - condition: { - address: address - } - }); + const sql = knex(this.table) + .where({ address }) + .update(update) + .toString(); + sqles.push(sql); } @@ -876,9 +889,9 @@ Account.prototype.merge = function (address, diff, cb) { } } - var queries = sqles.concat(round).map(function (sql) { + var queries = sqles.join(';') + round.map(function (sql) { return pgp.as.format(sql.query, sql.values); - }).join(''); + }).join('').concat(round); if (!cb) { return queries; @@ -903,14 +916,12 @@ Account.prototype.merge = function (address, diff, cb) { * @return {setImmediateCallback} Data with address | Account#remove error. */ Account.prototype.remove = function (address, cb) { - var sql = jsonSql.build({ - type: 'remove', - table: this.table, - condition: { - address: address - } - }); - this.scope.db.none(sql.query, sql.values).then(function () { + const sql = knex(this.table) + .where({ address }) + .del() + .toString(); + + this.scope.db.none(sql).then(function () { return setImmediate(cb, null, address); }).catch(function (err) { library.logger.error(err.stack); diff --git a/modules/accounts.js b/modules/accounts.js index 39d12d1b..59715cbe 100644 --- a/modules/accounts.js +++ b/modules/accounts.js @@ -668,7 +668,8 @@ Accounts.prototype.internal = { top: function (query, cb) { self.getAccounts({ sort: { - balance: -1 + column: 'balance', + order: 'desc' }, offset: query.offset, limit: (query.limit || 100) diff --git a/modules/delegates.js b/modules/delegates.js index 3c801579..c0d0a9c2 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -10,6 +10,9 @@ var jobsQueue = require('../helpers/jobsQueue.js'); var crypto = require('crypto'); var Delegate = require('../logic/delegate.js'); var extend = require('extend'); +var knex = require('knex')({ + client: 'pg' +}); var OrderBy = require('../helpers/orderBy.js'); var sandboxHelper = require('../helpers/sandbox.js'); var schema = require('../schema/delegates.js'); @@ -80,7 +83,13 @@ function Delegates (cb, scope) { __private.getKeysSortByVote = function (cb) { modules.accounts.getAccounts({ isDelegate: 1, - sort: { 'vote': -1, 'publicKey': 1 }, + sort: [{ + column: 'vote', + order: 'desc' + }, { + column: 'publicKey', + order: 'asc' + }], limit: slots.delegates }, ['publicKey'], function (err, rows) { if (err) { @@ -101,7 +110,13 @@ __private.getKeysSortByVote = function (cb) { __private.getKeysSortByVotesWeight = function (cb) { modules.accounts.getAccounts({ isDelegate: 1, - sort: { 'votesWeight': -1, 'publicKey': 1 }, + sort: [{ + column: 'votesWeight', + order: 'desc' + }, { + column: 'publicKey', + order: 'asc' + }], limit: slots.delegates }, ['publicKey'], function (err, rows) { if (err) { @@ -425,10 +440,23 @@ Delegates.prototype.getDelegates = function (query, cb) { if (!query) { throw 'Missing query argument'; } - var sortFilter = { 'vote': -1, 'publicKey': 1 }; + var sortFilter = [{ + column: 'vote', + order: 'desc' + }]; + if (modules.blocks.lastBlock.get().height > constants.fairSystemActivateBlock) { - sortFilter = { 'votesWeight': -1, 'publicKey': 1 }; + sortFilter = [{ + column: 'votesWeight', + order: 'desc' + }]; } + + sortFilter.push({ + column: 'publicKey', + order: 'asc' + }); + modules.accounts.getAccounts({ isDelegate: 1, sort: sortFilter @@ -856,7 +884,7 @@ Delegates.prototype.shared = { var addresses = (row.accountIds) ? row.accountIds : []; modules.accounts.getAccounts({ - address: { $in: addresses }, + raw: knex.raw('?? in (??)', ['address', addresses]).toString(), sort: 'balance' }, ['address', 'balance', 'username', 'publicKey'], function (err, rows) { if (err) { diff --git a/modules/multisignatures.js b/modules/multisignatures.js index 4eb23a64..20cb59a4 100644 --- a/modules/multisignatures.js +++ b/modules/multisignatures.js @@ -9,7 +9,9 @@ var sandboxHelper = require('../helpers/sandbox.js'); var schema = require('../schema/multisignatures.js'); var sql = require('../sql/multisignatures.js'); var transactionTypes = require('../helpers/transactionTypes.js'); - +var knex = require('knex')({ + client: 'pg' +}); // Private fields var modules, library, self, __private = {}, shared = {}; @@ -233,7 +235,7 @@ Multisignatures.prototype.shared = { }, getAccounts: function (seriesCb) { modules.accounts.getAccounts({ - address: { $in: scope.accountIds }, + raw: knex.raw('?? in (??)', ['address', scope.accountIds]).toString(), sort: 'balance' }, ['address', 'balance', 'multisignatures', 'multilifetime', 'multimin'], function (err, accounts) { if (err) { @@ -253,7 +255,7 @@ Multisignatures.prototype.shared = { } modules.accounts.getAccounts({ - address: { $in: addresses } + raw: knex.raw('?? in (??)', ['address', addresses]).toString() }, ['address', 'publicKey', 'balance'], function (err, multisigaccounts) { if (err) { return setImmediate(eachSeriesCb, err); diff --git a/modules/sql.js b/modules/sql.js index e4a3a957..8da60a61 100644 --- a/modules/sql.js +++ b/modules/sql.js @@ -2,8 +2,10 @@ var async = require('async'); var extend = require('extend'); -var jsonSql = require('json-sql')(); -jsonSql.setDialect('postgresql'); +var knex = require('knex')({ + client: 'pg' +}); + var sandboxHelper = require('../helpers/sandbox.js'); // Private fields @@ -118,7 +120,7 @@ __private.pass = function (obj, dappid) { * @implements {jsonSql.build} * @implements {library.db.query} * @implements {async.until} - * @param {string} action + * @param {string} action - should be `batch` or sql query * @param {Object} config * @param {function} cb * @return {setImmediateCallback} cb, err, data @@ -135,22 +137,11 @@ __private.query = function (action, config, cb) { } if (action !== 'batch') { - __private.pass(config, config.dappid); - - var defaultConfig = { - type: action - }; - - try { - sql = jsonSql.build(extend({}, config, defaultConfig)); - library.logger.trace('sql.query:', sql); - } catch (e) { - return done(e); - } + const sql = action; - // console.log(sql.query, sql.values); + library.logger.trace('sql.query:', sql); - library.db.query(sql.query, sql.values).then(function (rows) { + library.db.query(sql).then(function (rows) { return done(null, rows); }).catch(function (err) { library.logger.error(err.stack); @@ -206,6 +197,9 @@ Sql.prototype.createTables = function (dappid, config, cb) { var sqles = []; for (var i = 0; i < config.length; i++) { config[i].table = 'dapp_' + dappid + '_' + config[i].table; + + let sql; + if (config[i].type === 'table') { config[i].type = 'create'; if (config[i].foreignKeys) { @@ -213,17 +207,31 @@ Sql.prototype.createTables = function (dappid, config, cb) { config[i].foreignKeys[n].table = 'dapp_' + dappid + '_' + config[i].foreignKeys[n].table; } } + + try { + sql = knex.schema.createTableIfNotExists('user', function (table) { + for (let j = 0; j < config[i].tableFields.length; j++) { + const field = config[i].tableFields[j]; + + table[field.type](field.name); + } + }).toString(); + } catch (err) { + return setImmediate(cb, 'An error occurred while creating table: ' + err); + } } else if (config[i].type === 'index') { - config[i].type = 'index'; + sql = knex.raw( + `create index if not exists ? ON ?(?)`, + [config[i].name, config[i].table, config[i].indexOn] + ).toString(); } else { return setImmediate(cb, 'Unknown table type: ' + config[i].type); } - var sql = jsonSql.build(config[i]); - sqles.push(sql.query); + sqles.push(sql); } - async.eachSeries(sqles, function (command, cb) { + async.eachSeries(sqles.join('; '), function (command, cb) { library.db.none(command).then(function () { return setImmediate(cb); }).catch(function (err) { @@ -308,7 +316,35 @@ Sql.prototype.onBlockchainReady = function () { */ shared.select = function (req, cb) { var config = extend({}, req.body, { dappid: req.dappid }); - __private.query.call(this, 'select', config, cb); + + __private.pass(config, config.dappid); + + const table = config.alias ? + ({ [config.alias]: config.table }) : + config.table; + + const sql = knex(table) + .select(config.fields) + .modify(function (queryBuilder) { + if (config.limit) { + queryBuilder.limit(config.limit); + } + + if (config.offset) { + queryBuilder.offset(config.offset); + } + + if (config.condition) { + queryBuilder.where(config.condition); + } + + if (config.sort) { + queryBuilder.orderBy(config.sort); + } + }) + .toString(); + + __private.query.call(this, sql, config, cb); }; /** @@ -328,7 +364,19 @@ shared.batch = function (req, cb) { */ shared.insert = function (req, cb) { var config = extend({}, req.body, { dappid: req.dappid }); - __private.query.call(this, 'insert', config, cb); + + __private.pass(config, config.dappid); + + const sql = knex(config.table) + .insert(config.values) + .modify(function (queryBuilder) { + if (config.condition) { + queryBuilder.where(config.condition); + } + }) + .toString(); + + __private.query.call(this, sql, config, cb); }; /** @@ -338,7 +386,19 @@ shared.insert = function (req, cb) { */ shared.update = function (req, cb) { var config = extend({}, req.body, { dappid: req.dappid }); - __private.query.call(this, 'update', config, cb); + + __private.pass(config, config.dappid); + + const sql = knex(config.table) + .update(config.modifier) + .modify(function (queryBuilder) { + if (config.condition) { + queryBuilder.where(config.condition); + } + }) + .toString(); + + __private.query.call(this, sql, config, cb); }; /** @@ -348,7 +408,18 @@ shared.update = function (req, cb) { */ shared.remove = function (req, cb) { var config = extend({}, req.body, { dappid: req.dappid }); - __private.query.call(this, 'remove', config, cb); + __private.pass(config, config.dappid); + + const sql = knex(config.table) + .modify(function (queryBuilder) { + if (config.condition) { + queryBuilder.where(config.condition); + } + }) + .del() + .toString(); + + __private.query.call(this, sql, config, cb); }; // Export From c360227b4b88ef252b2556bf9cb55edb132c3c29 Mon Sep 17 00:00:00 2001 From: martiliones Date: Sun, 31 Jul 2022 07:44:52 +0600 Subject: [PATCH 22/33] fix: add knex and remove json-sql from depends --- package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/package.json b/package.json index 2d2158e3..d3dbb506 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "ip": "=1.1.8", "js-nacl": "^1.4.0", "json-schema": "=0.4.0", - "json-sql": "LiskHQ/json-sql#27e1ed1", + "knex": "^2.2.0", "lodash": "=4.17.21", "method-override": "=3.0.0", "npm": "=8.13.1", @@ -98,9 +98,6 @@ "overrides": { "grunt-contrib-obfuscator": { "javascript-obfuscator": "^4.0.0" - }, - "json-sql": { - "underscore": "^1.13.4" } }, "config": { From 0f46a98be1aa8088bbbd3bb9a54276800d9ffc4f Mon Sep 17 00:00:00 2001 From: martiliones Date: Sun, 31 Jul 2022 07:57:36 +0600 Subject: [PATCH 23/33] chore: replace decompress-zip with unzipper --- modules/dapps.js | 73 +++++++++++++++++++++++++++++++----------------- package.json | 2 +- 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/modules/dapps.js b/modules/dapps.js index aebfaac4..ab5854df 100644 --- a/modules/dapps.js +++ b/modules/dapps.js @@ -5,7 +5,7 @@ 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'); @@ -380,7 +380,7 @@ __private.removeDApp = function (dapp, cb) { * Creates a temp dir, downloads the dapp as stream and decompress it. * @private * @implements {axios} - * @implements {DecompressZip} + * @implements {unzipper} * @param {dapp} dapp * @param {string} dappPath * @param {function} cb @@ -442,34 +442,57 @@ __private.downloadLink = function (dapp, dappPath, cb) { .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/package.json b/package.json index d3dbb506..dae07ecf 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "commander": "=9.3.0", "compression": "=1.7.4", "cors": "=2.8.5", - "decompress-zip": "LiskHQ/decompress-zip#f46b0a3", "ejs": "=3.1.8", "execa": "^5.1.1", "express": "=4.18.1", @@ -72,6 +71,7 @@ "sodium": "^3.0.2", "sodium-browserify-tweetnacl": "*", "strftime": "=0.10.1", + "unzipper": "^0.10.11", "valid-url": "=1.0.9", "validator.js": "=2.0.4", "z-schema": "=5.0.3" From 79f2cda1f24ca95b8087546919e0fa8321fdc396 Mon Sep 17 00:00:00 2001 From: martiliones Date: Sun, 31 Jul 2022 07:59:30 +0600 Subject: [PATCH 24/33] chore: update dependencies --- package.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index dae07ecf..66e424d4 100644 --- a/package.json +++ b/package.json @@ -37,12 +37,12 @@ "async": "=3.2.4", "axios": "^0.27.2", "bignumber.js": "=9.0.2", - "bitcore-mnemonic": "=8.25.30", + "bitcore-mnemonic": "=8.25.34", "body-parser": "=1.20.0", "bytebuffer": "=5.0.1", "change-case": "=4.1.2", "colors": "=1.4.0", - "commander": "=9.3.0", + "commander": "=9.4.0", "compression": "=1.7.4", "cors": "=2.8.5", "ejs": "=3.1.8", @@ -50,7 +50,7 @@ "express": "=4.18.1", "express-domain-middleware": "=0.1.0", "express-query-int": "=3.0.0", - "express-rate-limit": "=6.4.0", + "express-rate-limit": "=6.5.1", "express-slow-down": "1.4.0", "extend": "=3.0.2", "ip": "=1.1.8", @@ -59,12 +59,12 @@ "knex": "^2.2.0", "lodash": "=4.17.21", "method-override": "=3.0.0", - "npm": "=8.13.1", + "npm": "=8.15.1", "pg-monitor": "=1.4.1", "pg-native": "=3.0.0", "pg-promise": "=10.11.1", "randomstring": "=1.2.2", - "redis": "=4.1.0", + "redis": "=4.2.0", "rimraf": "=3.0.2", "semver": "=7.3.7", "socket.io": "^4.5.1", @@ -87,13 +87,13 @@ "grunt-contrib-obfuscator": "^8.0.0", "grunt-eslint": "^24.0.0", "grunt-exec": "^3.0.0", - "jsdoc": "^3.6.10", + "jsdoc": "^3.6.11", "mocha": "^10.0.0", - "moment": "^2.29.3", + "moment": "^2.29.4", "nyc": "^15.1.0", "nyc-middleware": "^1.0.4", "sinon": "^14.0.0", - "supertest": "^6.2.3" + "supertest": "^6.2.4" }, "overrides": { "grunt-contrib-obfuscator": { From 671ea57ba9b765e0edd1ee028537ca9bd3ccace7 Mon Sep 17 00:00:00 2001 From: martiliones Date: Sun, 31 Jul 2022 08:41:24 +0600 Subject: [PATCH 25/33] fix: add backward compatibility for old js api --- modules/peers.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/peers.js b/modules/peers.js index fb5ac5de..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 !== 'adm-js-api'); + return peer.nonce !== modules.system.getNonce() && !isJsAPI; } - return !ip.isPrivate(peer.ip) && peer.nonce !== modules.system.getNonce() && (peer.os !== 'adm-js-api'); + + return !ip.isPrivate(peer.ip) && peer.nonce !== modules.system.getNonce() && !isJsAPI; }).value(); }; From 0b08c73a7e4fdf7002d6f46d4f6fad2727406f9f Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 11 Aug 2022 13:01:36 +0600 Subject: [PATCH 26/33] Revert "fix: add knex and remove json-sql from depends" This reverts commit c360227b4b88ef252b2556bf9cb55edb132c3c29. --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 66e424d4..d0b109d3 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "ip": "=1.1.8", "js-nacl": "^1.4.0", "json-schema": "=0.4.0", - "knex": "^2.2.0", + "json-sql": "LiskHQ/json-sql#27e1ed1", "lodash": "=4.17.21", "method-override": "=3.0.0", "npm": "=8.15.1", @@ -98,6 +98,9 @@ "overrides": { "grunt-contrib-obfuscator": { "javascript-obfuscator": "^4.0.0" + }, + "json-sql": { + "underscore": "^1.13.4" } }, "config": { From e3eb3b35aa0b3a38b48a99e7e2291202326cf882 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 11 Aug 2022 13:03:40 +0600 Subject: [PATCH 27/33] Revert "chore: replace json-sql with knex.js" This reverts commit 94972606c2bd158c458f7b5ae9fefd487302bc08. --- logic/account.js | 159 +++++++++++++++++-------------------- modules/accounts.js | 3 +- modules/delegates.js | 38 ++------- modules/multisignatures.js | 8 +- modules/sql.js | 121 ++++++---------------------- 5 files changed, 108 insertions(+), 221 deletions(-) diff --git a/logic/account.js b/logic/account.js index bdbc46e3..3640730b 100644 --- a/logic/account.js +++ b/logic/account.js @@ -3,10 +3,8 @@ var async = require('async'); var pgp = require('pg-promise'); var path = require('path'); -var knex = require('knex')({ - client: 'pg' -}); - +var jsonSql = require('json-sql')(); +jsonSql.setDialect('postgresql'); var constants = require('../helpers/constants.js'); var slots = require('../helpers/slots.js'); @@ -458,14 +456,14 @@ Account.prototype.removeTables = function (cb) { 'mem_accounts2u_delegates', 'mem_accounts2multisignatures', 'mem_accounts2u_multisignatures'].forEach(function (table) { - const sql = knex.delete() - .from(table) - .toString(); - - sqles.push(sql); + sql = jsonSql.build({ + type: 'remove', + table: table + }); + sqles.push(sql.query); }); - this.scope.db.query(sqles.join('; ')).then(function () { + this.scope.db.query(sqles.join('')).then(function () { return setImmediate(cb); }).catch(function (err) { library.logger.error(err.stack); @@ -584,8 +582,6 @@ Account.prototype.getAll = function (filter, fields, cb) { }.bind(this)); var limit, offset, sort; - const rawFilters = filter.raw || []; - delete filter.raw; if (filter.limit > 0) { limit = filter.limit; @@ -603,39 +599,23 @@ Account.prototype.getAll = function (filter, fields, cb) { delete filter.sort; if (typeof filter.address === 'string') { - rawFilters.push( - knex.raw('address = UPPER(?)', filter.address) - ); - delete filter.address; + filter.address = { + $upper: ['address', filter.address] + }; } - const sql = knex(this.table) - .select(realFields) - .from({ a: this.table }) - .modify(function (queryBuilder) { - if (limit) { - queryBuilder.limit(limit); - } - - if (offset) { - queryBuilder.offset(offset); - } - - if (sort) { - queryBuilder.orderBy(Array.isArray(sort) ? sort : [sort]); - } - - if (Object.keys(filter).length >= 1) { - builder.where(filter); - } - - if (rawFilters.length >= 1) { - builder.where(rawFilters.join(' and ')); - } - }) - .toString(); + var sql = jsonSql.build({ + type: 'select', + table: this.table, + limit: limit, + offset: offset, + sort: sort, + alias: 'a', + condition: filter, + fields: realFields + }); - this.scope.db.query(sql).then(function (rows) { + this.scope.db.query(sql.query, sql.values).then(function (rows) { return setImmediate(cb, null, rows); }).catch(function (err) { library.logger.error(err.stack); @@ -658,13 +638,15 @@ Account.prototype.set = function (address, fields, cb) { address = String(address).toUpperCase(); fields.address = address; - const sql = knex(this.table) - .insert(this.toDB(fields)) - .onConflict(['address']) - .merge(this.toDB(fields)) - .toString(); + var sql = jsonSql.build({ + type: 'insertorupdate', + table: this.table, + conflictFields: ['address'], + values: this.toDB(fields), + modifier: this.toDB(fields) + }); - this.scope.db.none(sql).then(function () { + this.scope.db.none(sql.query, sql.values).then(function () { return setImmediate(cb); }).catch(function (err) { library.logger.error(err.stack); @@ -815,16 +797,16 @@ Account.prototype.merge = function (address, diff, cb) { var sqles = []; - const tableName = `${self.table}2${el}`; - if (Object.keys(remove).length) { Object.keys(remove).forEach(function (el) { - const sql = knex(tableName) - .whereIn('dependentId', remove[el]) - .andWhere('accountId', address) - .del() - .toString(); - + var sql = jsonSql.build({ + type: 'remove', + table: self.table + '2' + el, + condition: { + dependentId: { $in: remove[el] }, + accountId: address + } + }); sqles.push(sql); }); } @@ -832,13 +814,14 @@ Account.prototype.merge = function (address, diff, cb) { if (Object.keys(insert).length) { Object.keys(insert).forEach(function (el) { for (var i = 0; i < insert[el].length; i++) { - const sql = knex(tableName) - .insert({ - accountId: address, - dependentId: insert[el][i] - }) - .toString(); - + var sql = jsonSql.build({ + type: 'insert', + table: self.table + '2' + el, + values: { + accountId: address, + dependentId: insert[el][i] + } + }); sqles.push(sql); } }); @@ -847,11 +830,11 @@ Account.prototype.merge = function (address, diff, cb) { if (Object.keys(remove_object).length) { Object.keys(remove_object).forEach(function (el) { remove_object[el].accountId = address; - const sql = knex(tableName) - .where(remove_object[el]) - .del() - .toString(); - + var sql = jsonSql.build({ + type: 'remove', + table: self.table + '2' + el, + condition: remove_object[el] + }); sqles.push(sql); }); } @@ -860,21 +843,25 @@ Account.prototype.merge = function (address, diff, cb) { Object.keys(insert_object).forEach(function (el) { insert_object[el].accountId = address; for (var i = 0; i < insert_object[el].length; i++) { - const sql = knex(tableName) - .insert(insert_object[el]) - .toString(); - + var sql = jsonSql.build({ + type: 'insert', + table: self.table + '2' + el, + values: insert_object[el] + }); sqles.push(sql); } }); } if (Object.keys(update).length) { - const sql = knex(this.table) - .where({ address }) - .update(update) - .toString(); - + var sql = jsonSql.build({ + type: 'update', + table: this.table, + modifier: update, + condition: { + address: address + } + }); sqles.push(sql); } @@ -889,9 +876,9 @@ Account.prototype.merge = function (address, diff, cb) { } } - var queries = sqles.join(';') + round.map(function (sql) { + var queries = sqles.concat(round).map(function (sql) { return pgp.as.format(sql.query, sql.values); - }).join('').concat(round); + }).join(''); if (!cb) { return queries; @@ -916,12 +903,14 @@ Account.prototype.merge = function (address, diff, cb) { * @return {setImmediateCallback} Data with address | Account#remove error. */ Account.prototype.remove = function (address, cb) { - const sql = knex(this.table) - .where({ address }) - .del() - .toString(); - - this.scope.db.none(sql).then(function () { + var sql = jsonSql.build({ + type: 'remove', + table: this.table, + condition: { + address: address + } + }); + this.scope.db.none(sql.query, sql.values).then(function () { return setImmediate(cb, null, address); }).catch(function (err) { library.logger.error(err.stack); diff --git a/modules/accounts.js b/modules/accounts.js index 59715cbe..39d12d1b 100644 --- a/modules/accounts.js +++ b/modules/accounts.js @@ -668,8 +668,7 @@ Accounts.prototype.internal = { top: function (query, cb) { self.getAccounts({ sort: { - column: 'balance', - order: 'desc' + balance: -1 }, offset: query.offset, limit: (query.limit || 100) diff --git a/modules/delegates.js b/modules/delegates.js index c0d0a9c2..3c801579 100644 --- a/modules/delegates.js +++ b/modules/delegates.js @@ -10,9 +10,6 @@ var jobsQueue = require('../helpers/jobsQueue.js'); var crypto = require('crypto'); var Delegate = require('../logic/delegate.js'); var extend = require('extend'); -var knex = require('knex')({ - client: 'pg' -}); var OrderBy = require('../helpers/orderBy.js'); var sandboxHelper = require('../helpers/sandbox.js'); var schema = require('../schema/delegates.js'); @@ -83,13 +80,7 @@ function Delegates (cb, scope) { __private.getKeysSortByVote = function (cb) { modules.accounts.getAccounts({ isDelegate: 1, - sort: [{ - column: 'vote', - order: 'desc' - }, { - column: 'publicKey', - order: 'asc' - }], + sort: { 'vote': -1, 'publicKey': 1 }, limit: slots.delegates }, ['publicKey'], function (err, rows) { if (err) { @@ -110,13 +101,7 @@ __private.getKeysSortByVote = function (cb) { __private.getKeysSortByVotesWeight = function (cb) { modules.accounts.getAccounts({ isDelegate: 1, - sort: [{ - column: 'votesWeight', - order: 'desc' - }, { - column: 'publicKey', - order: 'asc' - }], + sort: { 'votesWeight': -1, 'publicKey': 1 }, limit: slots.delegates }, ['publicKey'], function (err, rows) { if (err) { @@ -440,23 +425,10 @@ Delegates.prototype.getDelegates = function (query, cb) { if (!query) { throw 'Missing query argument'; } - var sortFilter = [{ - column: 'vote', - order: 'desc' - }]; - + var sortFilter = { 'vote': -1, 'publicKey': 1 }; if (modules.blocks.lastBlock.get().height > constants.fairSystemActivateBlock) { - sortFilter = [{ - column: 'votesWeight', - order: 'desc' - }]; + sortFilter = { 'votesWeight': -1, 'publicKey': 1 }; } - - sortFilter.push({ - column: 'publicKey', - order: 'asc' - }); - modules.accounts.getAccounts({ isDelegate: 1, sort: sortFilter @@ -884,7 +856,7 @@ Delegates.prototype.shared = { var addresses = (row.accountIds) ? row.accountIds : []; modules.accounts.getAccounts({ - raw: knex.raw('?? in (??)', ['address', addresses]).toString(), + address: { $in: addresses }, sort: 'balance' }, ['address', 'balance', 'username', 'publicKey'], function (err, rows) { if (err) { diff --git a/modules/multisignatures.js b/modules/multisignatures.js index 20cb59a4..4eb23a64 100644 --- a/modules/multisignatures.js +++ b/modules/multisignatures.js @@ -9,9 +9,7 @@ var sandboxHelper = require('../helpers/sandbox.js'); var schema = require('../schema/multisignatures.js'); var sql = require('../sql/multisignatures.js'); var transactionTypes = require('../helpers/transactionTypes.js'); -var knex = require('knex')({ - client: 'pg' -}); + // Private fields var modules, library, self, __private = {}, shared = {}; @@ -235,7 +233,7 @@ Multisignatures.prototype.shared = { }, getAccounts: function (seriesCb) { modules.accounts.getAccounts({ - raw: knex.raw('?? in (??)', ['address', scope.accountIds]).toString(), + address: { $in: scope.accountIds }, sort: 'balance' }, ['address', 'balance', 'multisignatures', 'multilifetime', 'multimin'], function (err, accounts) { if (err) { @@ -255,7 +253,7 @@ Multisignatures.prototype.shared = { } modules.accounts.getAccounts({ - raw: knex.raw('?? in (??)', ['address', addresses]).toString() + address: { $in: addresses } }, ['address', 'publicKey', 'balance'], function (err, multisigaccounts) { if (err) { return setImmediate(eachSeriesCb, err); diff --git a/modules/sql.js b/modules/sql.js index 8da60a61..e4a3a957 100644 --- a/modules/sql.js +++ b/modules/sql.js @@ -2,10 +2,8 @@ var async = require('async'); var extend = require('extend'); -var knex = require('knex')({ - client: 'pg' -}); - +var jsonSql = require('json-sql')(); +jsonSql.setDialect('postgresql'); var sandboxHelper = require('../helpers/sandbox.js'); // Private fields @@ -120,7 +118,7 @@ __private.pass = function (obj, dappid) { * @implements {jsonSql.build} * @implements {library.db.query} * @implements {async.until} - * @param {string} action - should be `batch` or sql query + * @param {string} action * @param {Object} config * @param {function} cb * @return {setImmediateCallback} cb, err, data @@ -137,11 +135,22 @@ __private.query = function (action, config, cb) { } if (action !== 'batch') { - const sql = action; + __private.pass(config, config.dappid); + + var defaultConfig = { + type: action + }; + + try { + sql = jsonSql.build(extend({}, config, defaultConfig)); + library.logger.trace('sql.query:', sql); + } catch (e) { + return done(e); + } - library.logger.trace('sql.query:', sql); + // console.log(sql.query, sql.values); - library.db.query(sql).then(function (rows) { + library.db.query(sql.query, sql.values).then(function (rows) { return done(null, rows); }).catch(function (err) { library.logger.error(err.stack); @@ -197,9 +206,6 @@ Sql.prototype.createTables = function (dappid, config, cb) { var sqles = []; for (var i = 0; i < config.length; i++) { config[i].table = 'dapp_' + dappid + '_' + config[i].table; - - let sql; - if (config[i].type === 'table') { config[i].type = 'create'; if (config[i].foreignKeys) { @@ -207,31 +213,17 @@ Sql.prototype.createTables = function (dappid, config, cb) { config[i].foreignKeys[n].table = 'dapp_' + dappid + '_' + config[i].foreignKeys[n].table; } } - - try { - sql = knex.schema.createTableIfNotExists('user', function (table) { - for (let j = 0; j < config[i].tableFields.length; j++) { - const field = config[i].tableFields[j]; - - table[field.type](field.name); - } - }).toString(); - } catch (err) { - return setImmediate(cb, 'An error occurred while creating table: ' + err); - } } else if (config[i].type === 'index') { - sql = knex.raw( - `create index if not exists ? ON ?(?)`, - [config[i].name, config[i].table, config[i].indexOn] - ).toString(); + config[i].type = 'index'; } else { return setImmediate(cb, 'Unknown table type: ' + config[i].type); } - sqles.push(sql); + var sql = jsonSql.build(config[i]); + sqles.push(sql.query); } - async.eachSeries(sqles.join('; '), function (command, cb) { + async.eachSeries(sqles, function (command, cb) { library.db.none(command).then(function () { return setImmediate(cb); }).catch(function (err) { @@ -316,35 +308,7 @@ Sql.prototype.onBlockchainReady = function () { */ shared.select = function (req, cb) { var config = extend({}, req.body, { dappid: req.dappid }); - - __private.pass(config, config.dappid); - - const table = config.alias ? - ({ [config.alias]: config.table }) : - config.table; - - const sql = knex(table) - .select(config.fields) - .modify(function (queryBuilder) { - if (config.limit) { - queryBuilder.limit(config.limit); - } - - if (config.offset) { - queryBuilder.offset(config.offset); - } - - if (config.condition) { - queryBuilder.where(config.condition); - } - - if (config.sort) { - queryBuilder.orderBy(config.sort); - } - }) - .toString(); - - __private.query.call(this, sql, config, cb); + __private.query.call(this, 'select', config, cb); }; /** @@ -364,19 +328,7 @@ shared.batch = function (req, cb) { */ shared.insert = function (req, cb) { var config = extend({}, req.body, { dappid: req.dappid }); - - __private.pass(config, config.dappid); - - const sql = knex(config.table) - .insert(config.values) - .modify(function (queryBuilder) { - if (config.condition) { - queryBuilder.where(config.condition); - } - }) - .toString(); - - __private.query.call(this, sql, config, cb); + __private.query.call(this, 'insert', config, cb); }; /** @@ -386,19 +338,7 @@ shared.insert = function (req, cb) { */ shared.update = function (req, cb) { var config = extend({}, req.body, { dappid: req.dappid }); - - __private.pass(config, config.dappid); - - const sql = knex(config.table) - .update(config.modifier) - .modify(function (queryBuilder) { - if (config.condition) { - queryBuilder.where(config.condition); - } - }) - .toString(); - - __private.query.call(this, sql, config, cb); + __private.query.call(this, 'update', config, cb); }; /** @@ -408,18 +348,7 @@ shared.update = function (req, cb) { */ shared.remove = function (req, cb) { var config = extend({}, req.body, { dappid: req.dappid }); - __private.pass(config, config.dappid); - - const sql = knex(config.table) - .modify(function (queryBuilder) { - if (config.condition) { - queryBuilder.where(config.condition); - } - }) - .del() - .toString(); - - __private.query.call(this, sql, config, cb); + __private.query.call(this, 'remove', config, cb); }; // Export From 03d10afd9afc8d7488d7884d3ed8228f2fa9e7af Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 11 Aug 2022 13:42:16 +0600 Subject: [PATCH 28/33] fix: move json-sql to legacy directory --- legacy/json-sql | 1 + package.json | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) create mode 160000 legacy/json-sql diff --git a/legacy/json-sql b/legacy/json-sql new file mode 160000 index 00000000..6aa525c5 --- /dev/null +++ b/legacy/json-sql @@ -0,0 +1 @@ +Subproject commit 6aa525c580f49c9a00e1814721e76764a4c9e0bb diff --git a/package.json b/package.json index d0b109d3..1fa1e249 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "ip": "=1.1.8", "js-nacl": "^1.4.0", "json-schema": "=0.4.0", - "json-sql": "LiskHQ/json-sql#27e1ed1", + "json-sql": "./legacy/json-sql", "lodash": "=4.17.21", "method-override": "=3.0.0", "npm": "=8.15.1", @@ -98,9 +98,6 @@ "overrides": { "grunt-contrib-obfuscator": { "javascript-obfuscator": "^4.0.0" - }, - "json-sql": { - "underscore": "^1.13.4" } }, "config": { From 2177a984f55dabde9f3b203eb2431be4be7bb325 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 11 Aug 2022 13:43:08 +0600 Subject: [PATCH 29/33] chore: update dependencies with minor changes --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 1fa1e249..5b282bcb 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,8 @@ "dependencies": { "async": "=3.2.4", "axios": "^0.27.2", - "bignumber.js": "=9.0.2", - "bitcore-mnemonic": "=8.25.34", + "bignumber.js": "=9.1.0", + "bitcore-mnemonic": "=8.25.36", "body-parser": "=1.20.0", "bytebuffer": "=5.0.1", "change-case": "=4.1.2", @@ -59,7 +59,7 @@ "json-sql": "./legacy/json-sql", "lodash": "=4.17.21", "method-override": "=3.0.0", - "npm": "=8.15.1", + "npm": "=8.17.0", "pg-monitor": "=1.4.1", "pg-native": "=3.0.0", "pg-promise": "=10.11.1", From c38eff435f0b618a4f81c2d6aa252fc55f1226c4 Mon Sep 17 00:00:00 2001 From: martiliones Date: Wed, 17 Aug 2022 19:53:05 +0600 Subject: [PATCH 30/33] fix(tests): axios syntax error --- test/node.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/node.js b/test/node.js index e8474825..477ba05b 100644 --- a/test/node.js +++ b/test/node.js @@ -578,7 +578,8 @@ node.addPeers = function (numOfPeers, ip, cb) { os = operatingSystems[node.randomizeSelection(operatingSystems.length)]; version = node.version; - node.axios.get({ + node.axios({ + method: 'get', url: node.baseUrl + '/peer/height', headers: { broadhash: node.config.nethash, From 7cc3e0c8cf4f4ea7cf80a36bfa58a1667ab94aeb Mon Sep 17 00:00:00 2001 From: martiliones Date: Wed, 17 Aug 2022 20:52:48 +0600 Subject: [PATCH 31/33] fix: add json-sql to legacy directory --- legacy/json-sql | 1 - legacy/json-sql/.gitignore | 1 + legacy/json-sql/.jshintrc | 19 + legacy/json-sql/.npmignore | 2 + legacy/json-sql/LICENSE | 22 + legacy/json-sql/README.md | 145 ++ legacy/json-sql/docs/README.md | 1180 ++++++++++++++++ legacy/json-sql/gulpfile.js | 27 + legacy/json-sql/lib/builder.js | 233 ++++ legacy/json-sql/lib/dialects/base/blocks.js | 441 ++++++ .../json-sql/lib/dialects/base/conditions.js | 122 ++ legacy/json-sql/lib/dialects/base/index.js | 58 + .../lib/dialects/base/logicalOperators.js | 37 + .../json-sql/lib/dialects/base/modifiers.js | 37 + legacy/json-sql/lib/dialects/base/scheme.js | 146 ++ .../json-sql/lib/dialects/base/templates.js | 220 +++ legacy/json-sql/lib/dialects/mssql/index.js | 13 + .../lib/dialects/postgresql/blocks.js | 33 + .../lib/dialects/postgresql/conditions.js | 87 ++ .../json-sql/lib/dialects/postgresql/index.js | 39 + .../lib/dialects/postgresql/templates.js | 269 ++++ legacy/json-sql/lib/dialects/sqlite/blocks.js | 23 + legacy/json-sql/lib/dialects/sqlite/index.js | 17 + .../json-sql/lib/dialects/sqlite/templates.js | 236 ++++ legacy/json-sql/lib/index.js | 8 + legacy/json-sql/lib/valuesStore.js | 31 + legacy/json-sql/package.json | 44 + legacy/json-sql/tests/0_base.js | 304 ++++ legacy/json-sql/tests/1_select.js | 1222 +++++++++++++++++ legacy/json-sql/tests/2_insert.js | 73 + legacy/json-sql/tests/3_update.js | 123 ++ legacy/json-sql/tests/4_delete.js | 58 + legacy/json-sql/tests/5_union.js | 256 ++++ legacy/json-sql/tests/6_postgresDialect.js | 140 ++ legacy/json-sql/tests/7_create.js | 279 ++++ legacy/json-sql/tests/8_index.js | 45 + 36 files changed, 5990 insertions(+), 1 deletion(-) delete mode 160000 legacy/json-sql create mode 100644 legacy/json-sql/.gitignore create mode 100644 legacy/json-sql/.jshintrc create mode 100644 legacy/json-sql/.npmignore create mode 100644 legacy/json-sql/LICENSE create mode 100644 legacy/json-sql/README.md create mode 100644 legacy/json-sql/docs/README.md create mode 100644 legacy/json-sql/gulpfile.js create mode 100644 legacy/json-sql/lib/builder.js create mode 100644 legacy/json-sql/lib/dialects/base/blocks.js create mode 100644 legacy/json-sql/lib/dialects/base/conditions.js create mode 100644 legacy/json-sql/lib/dialects/base/index.js create mode 100644 legacy/json-sql/lib/dialects/base/logicalOperators.js create mode 100644 legacy/json-sql/lib/dialects/base/modifiers.js create mode 100644 legacy/json-sql/lib/dialects/base/scheme.js create mode 100644 legacy/json-sql/lib/dialects/base/templates.js create mode 100644 legacy/json-sql/lib/dialects/mssql/index.js create mode 100644 legacy/json-sql/lib/dialects/postgresql/blocks.js create mode 100644 legacy/json-sql/lib/dialects/postgresql/conditions.js create mode 100644 legacy/json-sql/lib/dialects/postgresql/index.js create mode 100644 legacy/json-sql/lib/dialects/postgresql/templates.js create mode 100644 legacy/json-sql/lib/dialects/sqlite/blocks.js create mode 100644 legacy/json-sql/lib/dialects/sqlite/index.js create mode 100644 legacy/json-sql/lib/dialects/sqlite/templates.js create mode 100644 legacy/json-sql/lib/index.js create mode 100644 legacy/json-sql/lib/valuesStore.js create mode 100644 legacy/json-sql/package.json create mode 100644 legacy/json-sql/tests/0_base.js create mode 100644 legacy/json-sql/tests/1_select.js create mode 100644 legacy/json-sql/tests/2_insert.js create mode 100644 legacy/json-sql/tests/3_update.js create mode 100644 legacy/json-sql/tests/4_delete.js create mode 100644 legacy/json-sql/tests/5_union.js create mode 100644 legacy/json-sql/tests/6_postgresDialect.js create mode 100644 legacy/json-sql/tests/7_create.js create mode 100644 legacy/json-sql/tests/8_index.js diff --git a/legacy/json-sql b/legacy/json-sql deleted file mode 160000 index 6aa525c5..00000000 --- a/legacy/json-sql +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6aa525c580f49c9a00e1814721e76764a4c9e0bb 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" + }); + }); +}); From d3ef4882abc24cca2321c0d890e5976a9dffa8da Mon Sep 17 00:00:00 2001 From: adamant-al Date: Mon, 5 Sep 2022 10:18:00 +0300 Subject: [PATCH 32/33] chore: update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5b282bcb..9592f59d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adamant", - "version": "0.6.5", + "version": "0.7.7", "private": true, "scripts": { "start": "node app.js", From 088965163f342b5d9d11d872ad79f120a62c7fee Mon Sep 17 00:00:00 2001 From: adamant-al Date: Mon, 5 Sep 2022 10:18:23 +0300 Subject: [PATCH 33/33] fix: update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9592f59d..e46a2b32 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adamant", - "version": "0.7.7", + "version": "0.7.0", "private": true, "scripts": { "start": "node app.js",