diff --git a/README.md b/README.md index 82ca2f1281..98132c4ba9 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ Powered by popular node packages : ([express](https://github.com/expressjs/expre * Group By, Having (as a separate API) :fire::fire: * Multiple group by in one API :fire::fire::fire::fire: * Chart API for numeric column :fire::fire::fire::fire::fire::fire: +* Auto Chart API - (Must see : a gift to lazy while prototyping) :fire::fire::fire::fire::fire::fire: * Supports views * Prototyping (features available when using local MySql server only) * Run dynamic queries :fire::fire::fire: @@ -116,6 +117,7 @@ if you haven't on your system. | GET :fire:| [/api/tableName/groupby](#group-by-having-as-api) | Group by results of column(s) | | GET :fire:| [/api/tableName/ugroupby](#union-of-multiple-group-by-statements) | Multiple group by results using one call | | GET :fire:| [/api/tableName/chart](#chart) | Numeric column distribution based on (min,max,step) or(step array) or (automagic)| +| GET :fire:| [/api/tableName/autochart](#autochart) | Same as Chart but identifies which are numeric column automatically - gift for lazy while prototyping| | GET :fire:| [/dynamic](#run-dynamic-queries) | execute dynamic mysql statements with params | | GET :fire:| [/upload](#upload-single-file) | upload single file | | GET :fire:| [/uploads](#upload-multiple-files) | upload multiple files | @@ -637,6 +639,50 @@ Response Please Note: _fields in Chart API can only take numeric column as its argument. +## Autochart + +Identifies numeric columns in a table which are not any sort of key and applies chart API as before - +feels like magic when there are multiple numeric columns in table while hacking/prototyping and you invoke this API. + +``` +http://localhost:3000/api/payments/autochart + +[ + { + "column": "amount", + "chart": [ + { + "amount": "-9860 to 11100", + "_count": 45 + }, + { + "amount": "11101 to 32060", + "_count": 91 + }, + { + "amount": "32061 to 53020", + "_count": 109 + }, + { + "amount": "53021 to 73980", + "_count": 16 + }, + { + "amount": "73981 to 94940", + "_count": 7 + }, + { + "amount": "94941 to 115900", + "_count": 3 + }, + { + "amount": "115901 to 130650", + "_count": 2 + } + ] + } +] +``` ## Run dynamic queries [:arrow_heading_up:](#api-overview) diff --git a/lib/util/cmd.helper.js b/lib/util/cmd.helper.js index e1f9de846a..2bb80aece5 100644 --- a/lib/util/cmd.helper.js +++ b/lib/util/cmd.helper.js @@ -11,7 +11,7 @@ program.on('--help', () => { }) program - .version('0.3.3') + .version('0.3.4') .option('-h, --host ', 'hostname / localhost by default') .option('-u, --user ', 'username of database / root by default') .option('-p, --password ', 'password of database / empty by default') diff --git a/lib/xapi.js b/lib/xapi.js index 912c262ee3..1cb49d6800 100644 --- a/lib/xapi.js +++ b/lib/xapi.js @@ -219,6 +219,11 @@ class Xapi { .get(this.asyncMiddleware(this.chart.bind(this))); break; + case 'autoChart': + this.app.route(routes[i]['routeUrl']) + .get(this.asyncMiddleware(this.autoChart.bind(this))); + break; + case 'aggregate': this.app.route(routes[i]['routeUrl']) .get(this.asyncMiddleware(this.aggregate.bind(this))); @@ -866,6 +871,70 @@ class Xapi { } + async autoChart(req, res) { + + let query = 'describe ??' + let params = [req.app.locals._tableName] + let obj = {} + let results = [] + + let isRange = false + if (req.query.range) { + isRange = true + } + + + let describeResults = await this.mysql.exec(query, params) + + //console.log(describeResults); + + for (var i = 0; i < describeResults.length; ++i) { + + //console.log('is this numeric column', describeResults[i]['Type']); + + if (describeResults[i]['Key'] !== 'PRI' && this.mysql.isTypeOfColumnNumber(describeResults[i]['Type'])) { + + query = 'select min(??) as min,max(??) as max,stddev(??) as stddev,avg(??) as avg from ??'; + params = []; + + params.push(describeResults[i]['Field']); + params.push(describeResults[i]['Field']); + params.push(describeResults[i]['Field']); + params.push(describeResults[i]['Field']); + params.push(req.app.locals._tableName); + + let _this = this; + + let minMaxResults = await _this.mysql.exec(query, params); + + //console.log(minMaxResults, minMaxResults['max'], req.params); + + query = '' + params = [] + + obj = _this.mysql.getChartQueryAndParamsFromMinMaxStddev(req.app.locals._tableName, + describeResults[i]['Field'], + minMaxResults[0]['min'], + minMaxResults[0]['max'], + minMaxResults[0]['stddev'], + isRange + ) + + let r = await this.mysql.exec(obj.query, obj.params); + + let resultObj = {} + resultObj['column'] = describeResults[i]['Field'] + resultObj['chart'] = r + + results.push(resultObj); + + } + } + + res.status(200).json(results); + } + + /**************** START : files related ****************/ downloadFile(req, res) { let file = path.join(process.cwd(), req.query.name); diff --git a/lib/xsql.js b/lib/xsql.js index 472c6e294b..be2b1fa9a9 100644 --- a/lib/xsql.js +++ b/lib/xsql.js @@ -154,6 +154,34 @@ class Xsql { } + typeOfColumn(Type) { + + //TODO: Im sure there are more types to handle here + const strTypes = ['varchar', 'text', 'char', 'tinytext', 'mediumtext', 'longtext', 'blob', 'mediumblob', 'longblob']; + const intTypes = ['int', 'long', 'smallint', 'mediumint', 'bigint', 'tinyint']; + const flatTypes = ['float', 'double', 'decimal']; + const dateTypes = ['date', 'datetime', 'timestamp', 'time', 'year']; + + if (getType(Type, strTypes)) { + return "string" + } else if (getType(Type, intTypes)) { + return "int" + } else if (getType(Type, flatTypes)) { + return "float" + } else if (getType(Type, dateTypes)) { + return "date" + } else { + return "unknown" + } + + } + + isTypeOfColumnNumber(Type) { + + //console.log(Type, this.typeOfColumn(Type)); + return ('int' === this.typeOfColumn(Type) || 'float' === this.typeOfColumn(Type)) + } + getLimitClause(reqParams) { //defaults @@ -532,6 +560,7 @@ class Xsql { routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/chart', 'chart')) 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 + '/autoChart', 'autoChart')) if (!isView) { routes.push(this.prepareRoute(internal, 'post', apiPrefix, tableName, 'create')) @@ -712,3 +741,15 @@ function getColumnType(column) { } +function getType(colType, typesArr) { + for (let i = 0; i < typesArr.length; ++i) { + // if (typesArr[i].indexOf(colType) !== -1) { + // return 1; + // } + + if (colType.indexOf(typesArr[i]) !== -1) { + return 1; + } + } + return 0; +} diff --git a/package.json b/package.json index 1b26d947eb..76dbbc7787 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xmysql", - "version": "0.3.3", + "version": "0.3.4", "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 f21cc81c48..b6d86e5276 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -858,7 +858,6 @@ describe('xmysql : tests', function () { res.body.length.should.be.equals(1) - return done(); }); @@ -879,7 +878,6 @@ describe('xmysql : tests', function () { res.body.length.should.be.equals(4) - return done(); }); @@ -1223,6 +1221,25 @@ describe('xmysql : tests', function () { }); }) + it('GET /api/payments/autochart should PASS', function (done) { + + //post to an url with data + agent.get('/api/payments/autochart') //enter url + .expect(200)//200 for success 4xx for failure + .end(function (err, res) { + // Handle /api/v error + if (err) { + return done(err); + } + + res.body[0]['chart'].length.should.be.equals(7) + res.body[0]['chart'][0]['_count'].should.be.equals(45) + res.body[0]['chart'][6]['_count'].should.be.equals(2) + + return done(); + + }); + }) it('GET /api/payments?_where=(amount,bw,1000,5000) should PASS', function (done) {