Browse Source

New API : Groups v0.2.2

pull/13/head
oof1lab 7 years ago
parent
commit
e72dee4b93
  1. 43
      README.md
  2. 2
      lib/util/cmd.helper.js
  3. 57
      lib/xapi.js
  4. 1
      lib/xsql.js
  5. 2
      package.json
  6. 85
      tests/tests.js

43
README.md

@ -58,18 +58,22 @@ Powered by popular node packages : ([express](https://github.com/expressjs/expre
# Features # Features
* Generates API for **ANY** MySql database * Rest API Usual Suspects
* Serves APIs irrespective of naming conventions of primary keys, foreign keys, tables etc * CRUD, List, Count, Exists
* CRUD : Usual suspects
* Relations * Relations
* Support for composite primary keys
* Pagination * Pagination
* Sorting * Sorting
* Xmysql Rest API - Start of cool things
* Generates API for **ANY** MySql database
* Serves APIs irrespective of naming conventions of primary keys, foreign keys, tables etc
* Support for composite primary keys
* Column filtering - Fields * Column filtering - Fields
* Row filtering - Where * Row filtering - Where
* Group By, Having (as query params) * Group By, Having (as query params)
* Group By, Having (as a separate route) * Group By, Having (as a separate route)
* Aggregate functions * Aggregate functions
* Union of many group by statements [ AWESOME ALERT ]
* Prototyping (features available with only local MySql server)
* Run dynamic queries * Run dynamic queries
* Upload single file * Upload single file
* Upload multiple files * Upload multiple files
@ -98,13 +102,19 @@ Root URL (localhost:3000/) returns all REST API urls for each table in schema.
* GET       /api/tableName/findOne * GET       /api/tableName/findOne
* GET       /api/tableName/count * GET       /api/tableName/count
* GET       /api/tableName/:id/exists * GET       /api/tableName/:id/exists
* GET       /api/tableName/groupby
* GET       /api/tableName/aggregate
* GET       /api/parentTable/:id/childTable * GET       /api/parentTable/:id/childTable
* DELETE  /api/tableName/:id * DELETE  /api/tableName/:id
* POST     /dynamic
## Other APIS ## Cool features
* GET       /api/tableName/groupby
* GET       /api/tableName/aggregate
* GET       /api/tableName/groups
## Only in Prototyping
* POST     /dynamic
* GET       /upload
* GET       /uploads
* GET       /download
* GET      /api/tableName/describe * GET      /api/tableName/describe
* GET      /api/tables * GET      /api/tables
@ -295,7 +305,7 @@ eg: SELECT country,city,count(*) FROM offices GROUP BY country,city ORDER BY cit
eg: SELECT country,city,count(*) FROM offices GROUP BY country,city ORDER BY city ASC, country DESC eg: SELECT country,city,count(*) FROM offices GROUP BY country,city ORDER BY city ASC, country DESC
## Aggregate functions :jack_o_lantern: :sunglasses: ## Aggregate functions
``` ```
http://localhost:3000/api/payments/aggregate?_fields=amount http://localhost:3000/api/payments/aggregate?_fields=amount
@ -339,6 +349,21 @@ response body
eg: retrieves numeric aggregate can be done for multiple columns too eg: retrieves numeric aggregate can be done for multiple columns too
## Union of many group by statements
Group by multiple columns in one API call using _fields query params
```
http://localhost:3000/api/offices/ugroupby?_fields=country,city,state
response body
{
"country":[{"Australia":1},{"France":1},{"Japan":1},{"UK":1},{"USA":3}],
"state":[{"":3},{"CA":1},{"Chiyoda-Ku":1},{"MA":1},{"NY":1}]
}
```
## Run dynamic queries ## Run dynamic queries

2
lib/util/cmd.helper.js

@ -11,7 +11,7 @@ program.on('--help', () => {
}) })
program program
.version('0.2.1') .version('0.2.2')
.option('-h, --host <n>', 'hostname') .option('-h, --host <n>', 'hostname')
.option('-d, --database <n>', 'database schema name') .option('-d, --database <n>', 'database schema name')
.option('-u, --user <n>', 'username of database / root by default') .option('-u, --user <n>', 'username of database / root by default')

57
lib/xapi.js

@ -185,12 +185,16 @@ class Xapi {
.get(this.asyncMiddleware(this.groupBy.bind(this))); .get(this.asyncMiddleware(this.groupBy.bind(this)));
break; break;
case 'ugroupby':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(this.ugroupby.bind(this)));
break;
case 'aggregate': case 'aggregate':
this.app.route(routes[i]['routeUrl']) this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(this.aggregate.bind(this))); .get(this.asyncMiddleware(this.aggregate.bind(this)));
break; break;
} }
} }
} }
@ -506,7 +510,6 @@ class Xapi {
let results = await this.mysql.exec(query, params); let results = await this.mysql.exec(query, params);
res.status(200).json(results); res.status(200).json(results);
} }
async groupBy(req, res) { async groupBy(req, res) {
@ -546,8 +549,58 @@ class Xapi {
res.status(400).json({message: 'Missing _fields query params eg: /api/tableName/groupby?_fields=column1'}) res.status(400).json({message: 'Missing _fields query params eg: /api/tableName/groupby?_fields=column1'})
} }
}
async ugroupby(req, res) {
if (req.query && req.query._fields) {
let queryParamsObj = {}
queryParamsObj.query = '';
queryParamsObj.params = [];
let uGrpByResults = {}
/**************** add fields with count(*) *****************/
let fields = req.query._fields.split(',')
for (var i = 0; i < fields.length; ++i) {
uGrpByResults[fields[i]] = []
if (i) {
queryParamsObj.query += ' UNION '
} }
queryParamsObj.query += ' SELECT IFNULL(CONCAT(?,?,??),?) as ugroupby, count(*) as _count from ?? GROUP BY ?? '
queryParamsObj.params.push(fields[i])
queryParamsObj.params.push('~')
queryParamsObj.params.push(fields[i])
queryParamsObj.params.push(fields[i] + '~')
queryParamsObj.params.push(req.app.locals._tableName)
queryParamsObj.params.push(fields[i])
}
//console.log(queryParamsObj.query, queryParamsObj.params);
var results = await this.mysql.exec(queryParamsObj.query, queryParamsObj.params);
for (var i = 0; i < results.length; ++i) {
let grpByColName = results[i]['ugroupby'].split('~')[0]
let grpByColValue = results[i]['ugroupby'].split('~')[1]
let obj = {}
obj[grpByColValue] = results[i]['_count'];
uGrpByResults[grpByColName].push(obj)
}
res.status(200).json(uGrpByResults);
} else {
res.status(400).json({message: 'Missing _fields query params eg: /api/tableName/groupby?_fields=column1'})
}
}
async aggregate(req, res) { async aggregate(req, res) {

1
lib/xsql.js

@ -456,6 +456,7 @@ class Xsql {
routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/describe', 'describe')) routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/describe', 'describe'))
routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/count', 'count')) routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/count', 'count'))
routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/groupby', 'groupby')) routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/groupby', 'groupby'))
routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/ugroupby', 'ugroupby'))
routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/aggregate', 'aggregate')) routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/aggregate', 'aggregate'))
routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/findOne', 'findOne')) routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/findOne', 'findOne'))
routes.push(this.prepareRoute(internal, 'post', apiPrefix, tableName, 'create')) routes.push(this.prepareRoute(internal, 'post', apiPrefix, tableName, 'create'))

2
package.json

@ -1,6 +1,6 @@
{ {
"name": "xmysql", "name": "xmysql",
"version": "0.2.1", "version": "0.2.2",
"description": "One command to generate REST APIs for any MySql database", "description": "One command to generate REST APIs for any MySql database",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

85
tests/tests.js

@ -863,6 +863,91 @@ describe('xmysql : tests', function () {
}); });
}); });
it('GET /api/offices/ugroupby?_fields=country should PASS', function (done) {
//post to an url with data
agent.get('/api/offices/ugroupby?_fields=country') //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
Object.keys(res.body).length.should.be.equals(1)
res.body['country'].length.should.be.equals(5)
return done();
});
});
it('GET /api/offices/ugroupby?_fields=country,city,state should PASS', function (done) {
//post to an url with data
agent.get('/api/offices/ugroupby?_fields=country,city,state') //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(3)
Object.keys(res.body).length.should.be.equals(3)
res.body['country'].length.should.be.equals(5)
res.body['city'].length.should.be.equals(7)
res.body['state'].length.should.be.equals(5)
return done();
});
});
it('GET /api/offices/ugroupby?_fields=country,city should PASS', function (done) {
//post to an url with data
agent.get('/api/offices/ugroupby?_fields=country,city') //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
Object.keys(res.body).length.should.be.equals(2)
res.body['country'].length.should.be.equals(5)
res.body['city'].length.should.be.equals(7)
return done();
});
});
it('GET /api/offices/ugroupby?_fields= should PASS', function (done) {
//post to an url with data
agent.get('/api/offices/ugroupby?_fields=') //enter url
.expect(400)//200 for success 4xx for failure
.end(function (err, res) {
// Handle /api/v error
if (err) {
return done(err);
}
Object.keys(res.body).length.should.be.equals(1)
return done();
});
});
it('GET /api/offices/1/employees?_groupby=jobTitle&_having=(_count,gt,1) should PASS', function (done) { it('GET /api/offices/1/employees?_groupby=jobTitle&_having=(_count,gt,1) should PASS', function (done) {
//post to an url with data //post to an url with data

Loading…
Cancel
Save