From 7df4c93a3033cb94b4af0be4b01dd57e6ce96c12 Mon Sep 17 00:00:00 2001 From: oof1lab Date: Thu, 2 Nov 2017 20:13:42 +0000 Subject: [PATCH] Feature addition : WHERE clause version 0.0.8 --- README.md | 58 ++++++++-- lib/util/cmd.helper.js | 2 +- lib/util/whereClause.helper.js | 56 ++++++---- lib/xapi.js | 52 ++++++--- lib/xsql.js | 22 +++- package.json | 2 +- tests/tests.js | 196 +++++++++++++++++++++++---------- 7 files changed, 286 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index 0fef4e171e..3ce1aabbae 100644 --- a/README.md +++ b/README.md @@ -36,18 +36,19 @@ That's it! * Serves APIs irrespective of naming conventions of primary keys, foreign keys, tables etc * CRUD : Usual suspects * Support for composite primary keys -* Pagination -* Sorting -* Column filtering - Fields -* Group By -* Group By, Order By +* Pagination :tada: +* Sorting :tada: +* Column filtering - Fields :tada: +* Row filtering - Where :tada: +* Group By :tada: +* Group By, Order By :tada: * Aggregate functions :tada: -* Relations +* Relations :tada: :tada: * Run dynamic queries * Upload single file * Upload multiple files * Download file -* Row filtering - Where - Work in progress :racehorse: +* Group By, Having - Work in Progress - :racehorse::racehorse: Use HTTP clients like [Postman](https://www.getpostman.com/) or [similar tools](https://chrome.google.com/webstore/search/http%20client?_category=apps) to invoke REST API calls @@ -144,7 +145,48 @@ eg: gets only customerNumber and checkNumber in response of each record eg: gets all fields in table row but not checkNumber ## Row filtering / Where -> Work in progress + +#### Comparison operators + +``` +eq - '=' +ne - '!=' +gt - '>' +gte - '>=' +lt - '<' +lte - '<=' +``` +#### Use of comparison operators +``` +/api/payments?_where=(checkNumber,eq,JM555205) +``` + +#### Logical operators +``` +~or - 'or' +~and - 'and' +~xor - 'xor' +``` + +#### Use of logical operators +``` +/api/payments?_where=(checkNumber,eq,JM555205)~or(checkNumber,eq,OM314933) +``` + +eg: complex parentheses +``` +/api/payments?_where=((checkNumber,eq,JM555205)~or(checkNumber,eq,OM314933))~and(amount,gt,100) +``` + +eg: where with sorting(_sort), pagination(_p), column filtering (_fields) +``` +/api/payments?_where=(amount,gte,1000)&_sort=-amount&p=2&&_fields=customerNumber +``` + +eg: filter of rows using _where is available for relational route URLs too. +``` +/api/offices/1/employees?_where=(jobTitle,eq,Sales%20Rep) +``` ## Group By diff --git a/lib/util/cmd.helper.js b/lib/util/cmd.helper.js index 1b0f905168..e8bddf092f 100644 --- a/lib/util/cmd.helper.js +++ b/lib/util/cmd.helper.js @@ -11,7 +11,7 @@ program.on('--help', () => { }) program - .version('0.0.6') + .version('0.0.8') .option('-h, --host ', 'hostname') .option('-d, --database ', 'database schema name') .option('-u, --user ', 'username of database / root by default') diff --git a/lib/util/whereClause.helper.js b/lib/util/whereClause.helper.js index 0f1efe67f6..f754b6fff0 100644 --- a/lib/util/whereClause.helper.js +++ b/lib/util/whereClause.helper.js @@ -54,13 +54,29 @@ function getComparisonOperator(operator) { return '>=' break; - case 'like': - return ' like ' - break; + // case 'is': + // return ' is ' + // break; - case 'nlike': - return ' not like ' - break; + // case 'isnot': + // return ' is not ' + // break; + + // case 'isnull': + // return ' is NULL ' + // break; + + // case 'isnnull': + // return ' is not NULL ' + // break; + + // case 'like': + // return ' like ' + // break; + + // case 'nlike': + // return ' not like ' + // break; // case 'in': // return ' in ' @@ -79,19 +95,19 @@ function getLogicalOperator(operator) { switch (operator) { - case '+or': + case '~or': return 'or' break; - case '+and': + case '~and': return 'and' break; - case '+not': - return 'not' - break; + // case '~not': + // return 'not' + // break; - case '+xor': + case '~xor': return 'xor' break; @@ -103,17 +119,22 @@ function getLogicalOperator(operator) { } -exports.getWhereClause = function (whereInQueryParams, whereQuery, whereParams) { +exports.getWhereClause = function (whereInQueryParams) { + let whereQuery = ''; + let whereParams = []; let grammarErr = 0; - let numOfConditions = whereInQueryParams.split(/\+or|\+and|\+not|\+xor/); - let logicalOperatorsInClause = whereInQueryParams.match(/(\+or|\+and|\+not|\+xor)/g) + let numOfConditions = whereInQueryParams.split(/~or|~and|~not|~xor/); + let logicalOperatorsInClause = whereInQueryParams.match(/(~or|~and|~not|~xor)/g) if (numOfConditions && logicalOperatorsInClause && numOfConditions.length !== logicalOperatorsInClause.length + 1) { console.log('conditions and logical operators mismatch', numOfConditions.length, logicalOperatorsInClause.length); return } + // console.log('numOfConditions',numOfConditions,whereInQueryParams); + // console.log('logicalOperatorsInClause',logicalOperatorsInClause); + for (var i = 0; i < numOfConditions.length; ++i) { let variable = '' @@ -178,7 +199,7 @@ exports.getWhereClause = function (whereInQueryParams, whereQuery, whereParams) } whereParams.push(variableValue[1]) - // then replace the variableName with ? + // then replace the variableValue with ? temp = replaceWhereParamsToQMarks(false, result[2]) if (!temp) { grammarErr = 1; @@ -220,9 +241,6 @@ exports.getWhereClause = function (whereInQueryParams, whereQuery, whereParams) obj['params'] = whereParams } - - return obj - } diff --git a/lib/xapi.js b/lib/xapi.js index f7b17f2093..2350ee1223 100644 --- a/lib/xapi.js +++ b/lib/xapi.js @@ -212,29 +212,45 @@ class Xapi { async list(req, res) { + let queryParamsObj = {} + queryParamsObj.query = ''; + queryParamsObj.params = []; + let cols = this.mysql.getColumnsForSelectStmt(req.app.locals._tableName, req.query); - let query = 'select ' + cols + ' from ?? '; - let params = []; - params.push(req.app.locals._tableName); - query = query + this.mysql.getOrderByClause(req.query, req.app.locals._tableName); + /**************** tableName ****************/ + queryParamsObj.query = 'select ' + cols + ' from ?? '; + queryParamsObj.params.push(req.app.locals._tableName); - query = query + ' limit ?,? ' - params = params.concat(this.mysql.getLimitClause(req.query)); + /**************** where clause ****************/ + this.mysql.getWhereClause(req.query, req.app.locals._tableName, queryParamsObj,' where '); - let results = await this.mysql.exec(query, params); + /**************** order clause ****************/ + queryParamsObj.query += this.mysql.getOrderByClause(req.query, req.app.locals._tableName); + + /**************** limit clause ****************/ + queryParamsObj.query += ' limit ?,? ' + queryParamsObj.params = queryParamsObj.params.concat(this.mysql.getLimitClause(req.query)); + + //console.log(queryParamsObj.query, queryParamsObj.params); + + let results = await this.mysql.exec(queryParamsObj.query, queryParamsObj.params); res.status(200).json(results); } async nestedList(req, res) { - let cols = this.mysql.getColumnsForSelectStmt(req.app.locals._childTable, req.query); - let query = 'select ' + cols + ' from ?? where '; - let params = []; + let queryParamsObj = {} + queryParamsObj.query = ''; + queryParamsObj.params = []; - params.push(req.app.locals._childTable); + /**************** tableName ****************/ + let cols = this.mysql.getColumnsForSelectStmt(req.app.locals._childTable, req.query); + queryParamsObj.query = 'select ' + cols + ' from ?? where '; + queryParamsObj.params.push(req.app.locals._childTable); + /**************** where foreign key ****************/ let whereClause = this.mysql.getForeignKeyWhereClause(req.app.locals._parentTable, req.params.id, req.app.locals._childTable); @@ -244,15 +260,19 @@ class Xapi { error: "Table is made of composite primary keys - all keys were not in input" }) } + queryParamsObj.query += whereClause; - query += whereClause; + /**************** where conditions in query ****************/ + this.mysql.getWhereClause(req.query, req.app.locals._tableName, queryParamsObj, ' and '); - query = query + this.mysql.getOrderByClause(req.query, req.app.locals._parentTable); + /**************** order clause ****************/ + queryParamsObj.query = queryParamsObj.query + this.mysql.getOrderByClause(req.query, req.app.locals._parentTable); - query = query + ' limit ?,? ' - params = params.concat(this.mysql.getLimitClause(req.query)); + /**************** limit clause ****************/ + queryParamsObj.query = queryParamsObj.query + ' limit ?,? ' + queryParamsObj.params = queryParamsObj.params.concat(this.mysql.getLimitClause(req.query)); - let results = await this.mysql.exec(query, params); + let results = await this.mysql.exec(queryParamsObj.query, queryParamsObj.params); res.status(200).json(results); } diff --git a/lib/xsql.js b/lib/xsql.js index 709a7cd8ee..aaf20c2fc6 100644 --- a/lib/xsql.js +++ b/lib/xsql.js @@ -2,8 +2,10 @@ const mysql = require('mysql'); const dataHelp = require('./util/data.helper.js'); +const whereHelp = require('./util/whereClause.helper.js'); const assert = require('assert') + //define class class Xsql { @@ -171,6 +173,24 @@ class Xsql { } + getWhereClause(queryparams, tableName, queryParamsObj, appendToWhere) { + + let addWhereStr = '' + + if (queryparams && queryparams._where) { + + let whereClauseObj = whereHelp.getWhereClause(queryparams._where) + + if (whereClauseObj.err === 0) { + queryParamsObj.query = queryParamsObj.query + appendToWhere + whereClauseObj.query; + queryParamsObj.params = queryParamsObj.params.concat(whereClauseObj.params) + } + + //console.log('> > > after where clause filling up:', queryParamsObj.query, queryParamsObj.params); + } + + } + getOrderByClause(queryparams, tableName) { //defaults @@ -217,7 +237,7 @@ class Xsql { // get column name in _fields and mark column name which start with '-' for (let i = 0; i < _fieldsInQuery.length; ++i) { - if (_fieldsInQuery[i][0] == '-') { + if (_fieldsInQuery[i][0] === '-') { removeFieldsObj[_fieldsInQuery[i].substring(1, _fieldsInQuery[i].length)] = 1; } else { cols.push(_fieldsInQuery[i]) diff --git a/package.json b/package.json index 3843faf161..8b08d9652a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xmysql", - "version": "0.0.6", + "version": "0.0.8", "description": "One command to generate REST APIs for any MySql database", "main": "index.js", "scripts": { diff --git a/tests/tests.js b/tests/tests.js index 6858903f96..f1805c7810 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -504,6 +504,88 @@ describe('xmysql : tests', function () { }); }); + + + it('GET /api/offices/1/employees?_where=(jobTitle,eq,Sales%20Rep) should PASS', function (done) { + + //post to an url with data + agent.get('/api/offices/1/employees?_where=(jobTitle,eq,Sales%20Rep)') //enter url + .expect(200)//200 for success 4xx for failure + .end(function (err, res) { + // Handle /api/v error + if (err) { + return done(err); + } + + //validate response + res.body.length.should.be.equals(2) + + return done(); + + }); + }); + + + + it('GET /api/payments?_where=(amount,gte,1000)~and(customerNumber,lte,120) should PASS', function (done) { + + //post to an url with data + agent.get('/api/payments?_where=(amount,gte,1000)~and(customerNumber,lte,120)') //enter url + .expect(200)//200 for success 4xx for failure + .end(function (err, res) { + // Handle /api/v error + if (err) { + return done(err); + } + + //validate response + res.body.length.should.be.equals(13) + + return done(); + + }); + }); + + it('GET /api/payments?_where=(amount,gte,1000)&_sort=-amount should PASS', function (done) { + + //post to an url with data + agent.get('/api/payments?_where=(amount,gte,1000)&_sort=-amount') //enter url + .expect(200)//200 for success 4xx for failure + .end(function (err, res) { + // Handle /api/v error + if (err) { + return done(err); + } + + //validate response + res.body[0].amount.should.be.equals(120166.58) + + return done(); + + }); + }); + + http://localhost:3000/api/payments?_where=(checkNumber,eq,JM555205)~or(checkNumber,eq,OM314933) + + it('GET /api/payments?_where=(checkNumber,eq,JM555205)~or(checkNumber,eq,OM314933) should PASS', function (done) { + + //post to an url with data + agent.get('/api/payments?_where=(checkNumber,eq,JM555205)~or(checkNumber,eq,OM314933)') //enter url + .expect(200)//200 for success 4xx for failure + .end(function (err, res) { + // Handle /api/v error + if (err) { + return done(err); + } + + //validate response + res.body.length.should.be.equals(2) + + return done(); + + }); + }); + it('GET /api/employees/1002/employees should PASS', function (done) { //post to an url with data @@ -753,7 +835,7 @@ describe('xmysql : tests', function () { var query = '' var params = [] - var err = whereClause.getWhereClause('(abc,eq,1234)',query,params) + var err = whereClause.getWhereClause('(abc,eq,1234)') err.err.should.be.equal(0) err.query.should.be.equal('(??=?)') @@ -771,7 +853,7 @@ describe('xmysql : tests', function () { var query = '' var params = [] - var err = whereClause.getWhereClause('(abc,ne,1234)',query,params) + var err = whereClause.getWhereClause('(abc,ne,1234)') err.err.should.be.equal(0) err.query.should.be.equal('(??!=?)') @@ -789,7 +871,7 @@ describe('xmysql : tests', function () { var query = '' var params = [] - var err = whereClause.getWhereClause('(abc,lt,1234)',query,params) + var err = whereClause.getWhereClause('(abc,lt,1234)') err.err.should.be.equal(0) err.query.should.be.equal('(???)') @@ -840,7 +922,7 @@ describe('xmysql : tests', function () { var query = '' var params = [] - var err = whereClause.getWhereClause('(abc,gte,1234)',query,params) + var err = whereClause.getWhereClause('(abc,gte,1234)') err.err.should.be.equal(0) err.query.should.be.equal('(??>=?)') @@ -854,46 +936,46 @@ describe('xmysql : tests', function () { }); - it('where clause unit ?_where=(abc,like,1234) should PASS', function (done) { - - var query = '' - var params = [] - var err = whereClause.getWhereClause('(abc,like,1234)',query,params) - - err.err.should.be.equal(0) - err.query.should.be.equal('(?? like ?)') - err.params[0].should.be.equal('abc') - err.params[1].should.be.equal('1234') - - done() - - //console.log(query,params,err); - - }); - - - it('where clause unit ?_where=(abc,nlike,1234) should PASS', function (done) { - - var query = '' - var params = [] - var err = whereClause.getWhereClause('(abc,nlike,1234)',query,params) - - err.err.should.be.equal(0) - err.query.should.be.equal('(?? not like ?)') - err.params[0].should.be.equal('abc') - err.params[1].should.be.equal('1234') - - done() - - //console.log(query,params,err); - - }); + // it('where clause unit ?_where=(abc,like,1234) should PASS', function (done) { + // + // var query = '' + // var params = [] + // var err = whereClause.getWhereClause('(abc,like,1234)') + // + // err.err.should.be.equal(0) + // err.query.should.be.equal('(?? like ?)') + // err.params[0].should.be.equal('abc') + // err.params[1].should.be.equal('1234') + // + // done() + // + // //console.log(query,params,err); + // + // }); + // + // + // it('where clause unit ?_where=(abc,nlike,1234) should PASS', function (done) { + // + // var query = '' + // var params = [] + // var err = whereClause.getWhereClause('(abc,nlike,1234)') + // + // err.err.should.be.equal(0) + // err.query.should.be.equal('(?? not like ?)') + // err.params[0].should.be.equal('abc') + // err.params[1].should.be.equal('1234') + // + // done() + // + // //console.log(query,params,err); + // + // }); it('where clause unit ?_where=abc,eq,1234) should FAIL', function (done) { var query = '' var params = [] - var err = whereClause.getWhereClause('abc,eq,1234)',query,params) + var err = whereClause.getWhereClause('abc,eq,1234)') err.err.should.be.equal(1) err.query.should.be.equal('') @@ -909,7 +991,7 @@ describe('xmysql : tests', function () { var query = '' var params = [] - var err = whereClause.getWhereClause('(abc,eq,1234',query,params) + var err = whereClause.getWhereClause('(abc,eq,1234') err.err.should.be.equal(1) err.query.should.be.equal('') @@ -925,7 +1007,7 @@ describe('xmysql : tests', function () { var query = '' var params = [] - var err = whereClause.getWhereClause('(abc,eq1234)',query,params) + var err = whereClause.getWhereClause('(abc,eq1234)') err.err.should.be.equal(1) err.query.should.be.equal('') @@ -941,7 +1023,7 @@ describe('xmysql : tests', function () { var query = '' var params = [] - var err = whereClause.getWhereClause('(abceq,1234)',query,params) + var err = whereClause.getWhereClause('(abceq,1234)') err.err.should.be.equal(1) err.query.should.be.equal('') @@ -954,11 +1036,11 @@ describe('xmysql : tests', function () { }); - it('where clause unit ?_where=(1,eq,1)(1,eq,2)+or should FAIL', function (done) { + it('where clause unit ?_where=(1,eq,1)(1,eq,2)~or should FAIL', function (done) { var query = '' var params = [] - var err = whereClause.getWhereClause('(1,eq,1)(1,eq,2)+or',query,params) + var err = whereClause.getWhereClause('(1,eq,1)(1,eq,2)~or') err.err.should.be.equal(1) err.query.should.be.equal('') @@ -970,11 +1052,11 @@ describe('xmysql : tests', function () { }); - it('where clause unit ?_where=(1,eq,1)+or+or(1,eq,2)(1,eq,2) should FAIL', function (done) { + it('where clause unit ?_where=(1,eq,1)~or~or(1,eq,2)(1,eq,2) should FAIL', function (done) { var query = '' var params = [] - var err = whereClause.getWhereClause('(1,eq,1)+or+or(1,eq,2)(1,eq,2)',query,params) + var err = whereClause.getWhereClause('(1,eq,1)~or~or(1,eq,2)(1,eq,2)') err.err.should.be.equal(1) err.query.should.be.equal('') @@ -986,11 +1068,11 @@ describe('xmysql : tests', function () { }); - it('where clause unit ?_where=(abc,eq,1)+or(b,eq,2) should PASS', function (done) { + it('where clause unit ?_where=(abc,eq,1)~or(b,eq,2) should PASS', function (done) { var query = '' var params = [] - var err = whereClause.getWhereClause('(abc,eq,1)+or(b,eq,2)',query,params) + var err = whereClause.getWhereClause('(abc,eq,1)~or(b,eq,2)') err.err.should.be.equal(0) err.query.should.be.equal('(??=?)or(??=?)') @@ -1009,11 +1091,11 @@ describe('xmysql : tests', function () { }); - it('where clause unit ?_where=((a,eq,1)+and(b,eq,2))+or(c,eq,3) should PASS', function (done) { + it('where clause unit ?_where=((a,eq,1)~and(b,eq,2))~or(c,eq,3) should PASS', function (done) { var query = '' var params = [] - var err = whereClause.getWhereClause('((abc,eq,1234)+and(b,eq,2))+or(cde,eq,3)',query,params) + var err = whereClause.getWhereClause('((abc,eq,1234)~and(b,eq,2))~or(cde,eq,3)') err.err.should.be.equal(0) err.query.should.be.equal('((??=?)and(??=?))or(??=?)') @@ -1032,11 +1114,11 @@ describe('xmysql : tests', function () { }); - it('where clause unit ?_where=((a,eq,1)+and(b,eq,2))+xor(c,eq,3) should PASS', function (done) { + it('where clause unit ?_where=((a,eq,1)~and(b,eq,2))~xor(c,eq,3) should PASS', function (done) { var query = '' var params = [] - var err = whereClause.getWhereClause('((abc,eq,1234)+and(b,eq,2))+xor(cde,eq,3)',query,params) + var err = whereClause.getWhereClause('((abc,eq,1234)~and(b,eq,2))~xor(cde,eq,3)') err.err.should.be.equal(0) err.query.should.be.equal('((??=?)and(??=?))xor(??=?)') @@ -1055,11 +1137,13 @@ describe('xmysql : tests', function () { }); - it('where clause unit ?_where=(a,eq,1)+and((b,eq,2)+or(c,eq,3)) should PASS', function (done) { + it('where clause unit ?_where=(a,eq,1)~and((b,eq,2)~or(c,eq,3)) should PASS', function (done) { var query = '' var params = [] - var err = whereClause.getWhereClause('(a,eq,1)+and((b,eq,2)+or(c,eq,3))',query,params) + var err = whereClause.getWhereClause('(a,eq,1)~and((b,eq,2)~or(c,eq,3))') + + //console.log(query,params); err.err.should.be.equal(0) err.query.should.be.equal('(??=?)and((??=?)or(??=?))')