Browse Source

Feature: Bulk insert, delete, read - npm v0.2.5

pull/13/head
oof1lab 7 years ago
parent
commit
fb97051753
  1. 49
      README.md
  2. BIN
      assets/log.gif
  3. 4
      bin/index.js
  4. 2
      lib/util/cmd.helper.js
  5. 141
      lib/xapi.js
  6. 71
      lib/xsql.js
  7. 2
      package.json
  8. 109
      tests/tests.js

49
README.md

@ -92,29 +92,32 @@ if you haven't on your system.
## API Overview
|# | HTTP Type | API URL | Comments |
|--|-----------|----------------------------------|---------------------------------------------------------
|01| GET | / | Gets all REST APIs |
|02| GET | /api/tableName | Lists rows of table |
|03| POST | /api/tableName | Create a new row |
|04| PUT | /api/tableName | Replaces existing row with new row |
|05| GET | /api/tableName/:id | Retrieves a row by primary key |
|06| PATCH | /api/tableName/:id | Updates a row by primary key |
|07| GET | /api/tableName/findOne | Works as list but gets single record matching criteria |
|08| GET | /api/tableName/count | Count number of rows in a table |
|09| GET | /api/tableName/:id/exists | True or false whether a row exists or not |
|10| DELETE | /api/tableName/:id | Delete a row by primary key |
|11| GET | [/api/parentTable/:id/childTable](#relational-tables) | Get list of child table rows with parent table foreign key |
|12| GET :fire:| [/api/tableName/aggregate](#aggregate-functions) | Aggregate results of numeric column(s) |
|13| GET :fire:| [/api/tableName/groupby](#group-by-having-as-api) | Group by results of column(s) |
|14| GET :fire:| [/api/tableName/ugroupby](#union-of-multiple-group-by-statements) | Multiple group by results using one call |
|15| GET :fire:| [/api/tableName/chart](#chart) | Numeric column distribution based on (min,max,step) or(step array) or (automagic)|
|16| GET :fire:| [/dynamic](#run-dynamic-queries) | execute dynamic mysql statements with params |
|17| GET :fire:| [/upload](#upload-single-file) | upload single file |
|18| GET :fire:| [/uploads](#upload-multiple-files) | upload multiple files |
|19| GET :fire:| [/download](#download-file) | download a file |
|20| GET | /api/tableName/describe| describe each table for its columns |
|21| GET | /api/tables| get all tables in database |
| HTTP Type | API URL | Comments |
|-----------|----------------------------------|---------------------------------------------------------
| GET | / | Gets all REST APIs |
| GET | /api/tableName | Lists rows of table |
| POST | /api/tableName | Create a new row |
| PUT | /api/tableName | Replaces existing row with new row |
| POST :fire:| /api/tableName/bulk | Create multiple rows - send object array in request body|
| GET :fire:| /api/tableName/bulk | Lists multiple rows - /api/tableName/bulk?_ids=1,2,3 |
| DELETE :fire:| /api/tableName/bulk | Deletes multiple rows - /api/tableName/bulk?_ids=1,2,3 |
| GET | /api/tableName/:id | Retrieves a row by primary key |
| PATCH | /api/tableName/:id | Updates row element by primary key |
| DELETE | /api/tableName/:id | Delete a row by primary key |
| GET | /api/tableName/findOne | Works as list but gets single record matching criteria |
| GET | /api/tableName/count | Count number of rows in a table |
| GET | /api/tableName/:id/exists | True or false whether a row exists or not |
| GET | [/api/parentTable/:id/childTable](#relational-tables) | Get list of child table rows with parent table foreign key |
| GET :fire:| [/api/tableName/aggregate](#aggregate-functions) | Aggregate results of numeric column(s) |
| 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:| [/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 |
| GET :fire:| [/download](#download-file) | download a file |
| GET | /api/tableName/describe| describe each table for its columns |
| GET | /api/tables| get all tables in database |

BIN
assets/log.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

4
bin/index.js

@ -30,7 +30,9 @@ let mysqlPool = mysql.createPool(sqlConfig);
/**************** START : setup Xapi ****************/
console.log('');
console.log(' REST APIs at the speed of thought.. ');
console.log('');
console.log('');
console.log(' Generating REST APIs at the speed of your thought.. ');
console.log('');
let t = process.hrtime();

2
lib/util/cmd.helper.js

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

141
lib/xapi.js

@ -149,6 +149,21 @@ class Xapi {
.get(this.asyncMiddleware(this.read.bind(this)));
break;
case 'bulkInsert':
this.app.route(routes[i]['routeUrl'])
.post(this.asyncMiddleware(this.bulkInsert.bind(this)));
break;
case 'bulkRead':
this.app.route(routes[i]['routeUrl'])
.get(this.asyncMiddleware(this.bulkRead.bind(this)));
break;
case 'bulkDelete':
this.app.route(routes[i]['routeUrl'])
.delete(this.asyncMiddleware(this.bulkDelete.bind(this)));
break;
case 'patch':
this.app.route(routes[i]['routeUrl'])
.patch(this.asyncMiddleware(this.patch.bind(this)));
@ -230,7 +245,7 @@ class Xapi {
console.log(' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ');
console.log(' ');
console.log(' Database : %s', this.config.database);
console.log(' Number of resources : %s', stat.tables);
console.log(' Number of Tables : %s', stat.tables);
console.log(' ');
console.log(' REST APIs Generated : %s'.green.bold, stat.apis);
console.log(' ');
@ -256,7 +271,7 @@ class Xapi {
* @param req
* @param res
* @param queryParamsObj : {query, params}
* @param listType : 0:list, 1:nested, 2:findOne
* @param listType : 0:list, 1:nested, 2:findOne, 3:bulkRead
*
* Updates query, params for query of type listType
*/
@ -297,6 +312,28 @@ class Xapi {
this.mysql.getWhereClause(req.query._where, req.app.locals._tableName, queryParamsObj, ' and ');
} else if (listType === 3) { //bulkRead
// select * from table where pk in (ids) and whereConditions
queryParamsObj.params.push(req.app.locals._tableName);
queryParamsObj.query += ' where ?? in ';
queryParamsObj.params.push(this.mysql.getPrimaryKeyName(req.app.locals._tableName));
queryParamsObj.query += '('
if (req.query && req.query._ids) {
let ids = req.query._ids.split(',')
for (var i = 0; i < ids.length; ++i) {
if (i) {
queryParamsObj.query += ','
}
queryParamsObj.query += '?'
queryParamsObj.params.push(ids[i])
}
}
queryParamsObj.query += ') '
this.mysql.getWhereClause(req.query._where, req.app.locals._tableName, queryParamsObj, ' and ');
} else {
queryParamsObj.params.push(req.app.locals._tableName);
@ -468,44 +505,6 @@ class Xapi {
}
// async update(req, res) {
//
// let query = 'UPDATE ?? SET ';
// let keys = Object.keys(req.body);
//
// // SET clause
// let updateKeys = '';
// for (let i = 0; i < keys.length; ++i) {
// updateKeys += keys[i] + ' = ? '
// if (i !== keys.length - 1)
// updateKeys += ', '
// }
//
// // where clause
// query += updateKeys + ' where '
// let clause = this.mysql.getPrimaryKeyWhereClause(req.app.locals._tableName,
// req.params.id.split('___'));
//
// if (!clause) {
// return res.status(400).send({
// error: "Table is made of composite primary keys - all keys were not in input"
// })
// }
//
// query += clause;
//
// // params
// let params = [];
// params.push(req.app.locals._tableName);
// params = params.concat(Object.values(req.body));
//
// let results = await this.mysql.exec(query, params);
// res.status(200).json(results);
//
//
// }
async delete(req, res) {
let query = 'DELETE FROM ?? WHERE ';
@ -530,6 +529,68 @@ class Xapi {
}
async bulkInsert(req, res) {
let queryParamsObj = {}
queryParamsObj.query = ''
queryParamsObj.params = []
let results = []
//console.log(req.app.locals._tableName, req.body);
this.mysql.getBulkInsertStatement(req.app.locals._tableName, req.body, queryParamsObj)
results = await this.mysql.exec(queryParamsObj.query, queryParamsObj.params);
res.status(200).json(results);
}
async bulkDelete(req, res) {
let query = 'delete from ?? where ?? in ';
let params = [];
params.push(req.app.locals._tableName);
params.push(this.mysql.getPrimaryKeyName(req.app.locals._tableName));
query += '('
if (req.query && req.query._ids) {
let ids = req.query._ids.split(',')
for (var i = 0; i < ids.length; ++i) {
if (i) {
query += ','
}
query += '?'
params.push(ids[i])
}
}
query += ')'
//console.log(query, params);
var results = await this.mysql.exec(query, params);
res.status(200).json(results);
}
async bulkRead(req, res) {
let queryParamsObj = {}
queryParamsObj.query = ''
queryParamsObj.params = []
this.prepareListQuery(req, res, queryParamsObj, 3);
//console.log(queryParamsObj.query, queryParamsObj.params);
let results = await this.mysql.exec(queryParamsObj.query, queryParamsObj.params);
res.status(200).json(results);
}
async count(req, res) {
let query = 'select count(1) as no_of_rows from ??';

71
lib/xsql.js

@ -173,6 +173,65 @@ class Xsql {
}
getBulkInsertStatement(tableName, objectArray, queryParamsObj) {
if (tableName in this.metaDb.tables && objectArray) {
let insObj = objectArray[0];
// goal => insert into ?? (?,?..?) values ? [tablName, col1,col2...coln,[[ObjValues_1],[ObjValues_2],...[ObjValues_N]]
queryParamsObj.query = ' INSERT INTO ?? ( '
queryParamsObj.params.push(tableName)
let cols = [];
let colPresent = false;
/**************** START : prepare column names to be inserted ****************/
// iterate over all column in table and have only ones existing in objects to be inserted
for (var i = 0; i < this.metaDb.tables[tableName]['columns'].length; ++i) {
let colName = this.metaDb.tables[tableName]['columns'][i]['column_name']
if (colName in insObj) {
if (colPresent) {
queryParamsObj.query += ','
}
queryParamsObj.query += colName
colPresent = true;
}
cols.push(colName)
//console.log('> > ', queryParamsObj.query);
}
queryParamsObj.query += ' ) values ?'
/**************** END : prepare column names to be inserted ****************/
/**************** START : prepare value object in prepared statement ****************/
// iterate over sent object array
let arrOfArr = []
for (var i = 0; i < objectArray.length; ++i) {
let arrValues = []
for (var j = 0; j < cols.length; ++j) {
if (cols[j] in objectArray[i])
arrValues.push(objectArray[i][cols[j]])
}
arrOfArr.push(arrValues);
}
queryParamsObj.params.push(arrOfArr)
/**************** END : prepare value object in prepared statement ****************/
}
}
getGroupByClause(_groupby, tableName, queryParamsObj) {
if (_groupby) {
@ -350,6 +409,14 @@ class Xsql {
}
getPrimaryKeyName(tableName) {
let pk = null
if (tableName in this.metaDb.tables) {
pk = this.metaDb.tables[tableName].primaryKeys[0]['column_name']
}
return pk
}
getPrimaryKeyWhereClause(tableName, pksValues) {
let whereClause = '';
@ -462,6 +529,9 @@ class Xsql {
routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/findOne', 'findOne'))
routes.push(this.prepareRoute(internal, 'post', apiPrefix, tableName, 'create'))
routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName, 'list'))
routes.push(this.prepareRoute(internal, 'post', apiPrefix, tableName + '/bulk', 'bulkInsert'))
routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/bulk', 'bulkRead'))
routes.push(this.prepareRoute(internal, 'delete', apiPrefix, tableName + '/bulk', 'bulkDelete'))
routes.push(this.prepareRoute(internal, 'put', apiPrefix, tableName, 'update'))
routes.push(this.prepareRoute(internal, 'get', apiPrefix, tableName + '/:id', 'read'))
routes.push(this.prepareRoute(internal, 'patch', apiPrefix, tableName + '/:id', 'patch'))
@ -570,7 +640,6 @@ class Xsql {
}
}

2
package.json

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

109
tests/tests.js

@ -457,6 +457,115 @@ describe('xmysql : tests', function () {
});
});
it('POST /api/productlines/bulk should PASS', function (done) {
var objArray = []
var obj = {};
obj['productLine'] = 'Bulletrain'
obj['textDescription'] = 'Japan'
var obj1 = {};
obj1['productLine'] = 'Bulletrain_1'
obj1['textDescription'] = 'China'
objArray.push(obj)
objArray.push(obj1)
//post to an url with data
agent.post('/api/productlines/bulk') //enter url
.send(objArray) //postdata
.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['affectedRows'].should.be.equals(2)
return done();
});
});
it('POST /api/productlines/bulk should PASS', function (done) {
var objArray = []
var obj = {};
obj['productLine'] = 'Bulletrain_2'
var obj1 = {};
obj1['productLine'] = 'Bulletrain_3'
objArray.push(obj)
objArray.push(obj1)
//post to an url with data
agent.post('/api/productlines/bulk') //enter url
.send(objArray) //postdata
.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['affectedRows'].should.be.equals(2)
return done();
});
});
it('GET /api/productlines/bulk should PASS', function (done) {
//post to an url with data
agent.get('/api/productlines/bulk?_ids=Bulletrain,Bulletrain_1,Bulletrain_2,Bulletrain_3') //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(4)
return done();
});
});
it('DELETE /api/productlines/bulk should PASS', function (done) {
//post to an url with data
agent.del('/api/productlines/bulk?_ids=Bulletrain,Bulletrain_1,Bulletrain_2,Bulletrain_3') //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['affectedRows'].should.be.equals(4)
return done();
});
});
it('PUT /api/productlines should PASS', function (done) {
var obj = {};

Loading…
Cancel
Save