From 4b7695cd2869f7f205303d9a70854043d71fa859 Mon Sep 17 00:00:00 2001 From: luckydrq Date: Sun, 20 Sep 2015 18:44:52 +0800 Subject: [PATCH] feat: support multiple rows update --- README.md | 39 ++++++++++++++++ lib/operator.js | 101 ++++++++++++++++++++++++++++++++++++++++-- package.json | 2 + test/operator.test.js | 38 ++++++++++++++++ 4 files changed, 176 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ebf8c63..7044dc0 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,45 @@ console.log(result); changedRows: 1 } ``` +- Update multiple rows with each row has a primary key: `id` + +```js +let rows = [{ + id: 123, + name: 'fengmk2', + fieldA: 'fieldA value', +}, { + id: 456, + name: 'luckydrq', + fieldB: 'fieldB value' +}]; +let result = yield db.updateRows('table-name', rows); + +=> UPDATE `table-name` + SET name = CASE id + WHEN 123 THEN 'fengmk2' + WHEN 456 THEN 'luckydrq' + END, + fieldA = CASE id + WHEN 123 THEN 'filedA value' + END, + fieldB = CASE id + WHEN 456 THEN 'fieldB value' + END + WHERE id IN(123, 456) + +console.log(result); +{ + fieldCount: 0, + affectedRows: 2, + insertId: 0, + serverStatus: 2, + warningCount: 0, + message: '', + protocol41: true, + changedRows: 2 } +``` + ### Get - Get a row diff --git a/lib/operator.js b/lib/operator.js index a89e43c..68ce65c 100644 --- a/lib/operator.js +++ b/lib/operator.js @@ -14,7 +14,10 @@ * Module dependencies. */ +const util = require('util'); const debug = require('debug')('ali-rds:operator'); +const toArray = require('json-to-array'); +const merge = require('array-merges'); const SqlString = require('./sqlstring'); const literals = require('./literals'); @@ -63,7 +66,7 @@ proto.count = function* (table, where) { let sql = this.format('SELECT COUNT(*) as count FROM ??', [table]) + this._where(where); debug('count(%j, %j) \n=> %j', table, where, sql); - var rows = yield this.query(sql); + let rows = yield this.query(sql); return rows[0].count; }; @@ -116,8 +119,8 @@ proto.insert = function* (table, rows, options) { let params = [table, options.columns]; let strs = []; for (let i = 0; i < rows.length; i++) { - var values = []; - var row = rows[i]; + let values = []; + let row = rows[i]; for (let j = 0; j < options.columns.length; j++) { values.push(row[options.columns[j]]); } @@ -148,7 +151,7 @@ proto.update = function* (table, row, options) { let sets = []; let values = []; for (let i = 0; i < options.columns.length; i++) { - var column = options.columns[i]; + let column = options.columns[i]; if (column in options.where) { continue; } @@ -163,6 +166,96 @@ proto.update = function* (table, row, options) { return yield this.query(sql); }; +/** + * Update multiple rows from a table + * @see http://stackoverflow.com/questions/2528181/update-multiple-rows-with-one-query + * + * @param {String} table table name + * @param {Array} rows row obj with a primary key `id` + * @return {Object} result + */ +proto.updateRows = function* (table, rows) { + if (!Array.isArray(rows)) { + rows = [rows]; + } + + let escape = this.escape; + let escapeId = this.escapeId; + let ids = []; + let rowArr = rows + .map(function(row) { + // {a: b, c: d} => [[a, b], [c, d]] + if (!row.hasOwnProperty('id')) { + throw new Error('Can\' update rows. Make sure each row has a id'); + } + + // escape first + row.id = escape(row.id); + ids.push(row.id); + return toArray(row, { excepts: ['id'] }); + }) + .reduce(function(prev, curr) { + // merge([[a, b]], [[a, c]]) => [[a, [b, c]]] + prev = merge(prev, curr, { + equal: equal, + onMerge: onMerge + }); + return prev; + }, []); + + function equal(prev, next) { + return prev[0] === next[0]; + } + + function onMerge(prev, next) { + if (Array.isArray(prev[1])) { + prev[1].push(next[1]); + } else { + prev[1] = [prev[1], next[1]]; + } + return [[prev[0], prev[1]]]; + } + + // id 查找规则。多条记录可能拥有相同的key, value。 + // 通过{id}_{key}做key,是唯一的。 + let idMap = {}; + function getId(key, value) { + for (let i = 0; i < rows.length; i++) { + if (rows[i][key] === value) { + let hashKey = rows[i].id + '_' + key; + if (idMap[hashKey]) { + continue; + } else { + idMap[hashKey] = true; + return rows[i].id; + } + } + } + return null; + } + + let clauseTemplate = 'UPDATE %s SET %s WHERE id IN (%s)'; + let subClauseTemplate = '%s = CASE id %s END'; + let loopClauseTemplate = 'WHEN %s THEN %s'; + let clauseArr = []; + table = escapeId(table); + rowArr.forEach(function(tuple) { + let key = tuple[0], values = Array.isArray(tuple[1]) ? tuple[1] : [tuple[1]]; + let loopArr = []; + values.forEach(function(value) { + let id = getId(key, value); + if (id) { + value = escape(value); + loopArr.push(util.format(loopClauseTemplate, id, value)); + } + }); + clauseArr.push(util.format(subClauseTemplate, escapeId(key), loopArr.join(' '))); + }); + let sql = util.format(clauseTemplate, table, clauseArr.join(','), ids.join(',')); + debug('update(%j, %j, %j) \n=> %j', table, JSON.stringify(rows), sql); + return yield this.query(sql); +}; + proto.delete = function* (table, where) { let sql = this.format('DELETE FROM ??', [table]) + this._where(where); diff --git a/package.json b/package.json index 4a698c8..2f9382e 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,9 @@ "cnpm": "npm install --registry=https://registry.npm.taobao.org" }, "dependencies": { + "array-merges": "^0.1.0", "debug": "~2.2.0", + "json-to-array": "^0.1.0", "mysql": "~2.7.0" }, "devDependencies": { diff --git a/test/operator.test.js b/test/operator.test.js index 5f0f997..6492ac9 100644 --- a/test/operator.test.js +++ b/test/operator.test.js @@ -15,9 +15,18 @@ */ const assert = require('assert'); +const rds = require('..'); +const config = require('./config'); const Operator = require('../lib/operator'); describe('operator.test.js', function () { + const prefix = 'prefix-' + process.version + '-'; + const table = 'user'; + before(function* () { + this.db = rds(config); + yield this.db.query('delete from ?? where name like ?', [table, prefix + '%']); + }); + describe('_where(where)', function () { it('should get where sql', function () { let op = new Operator(); @@ -52,4 +61,33 @@ describe('operator.test.js', function () { } }); }); + + describe('updateRows()', function() { + before(function* () { + yield this.db.query('insert into ??(name, email, gmt_create, gmt_modified) \ + values(?, ?, now(), now())', + [table, prefix + 'luckydrq', prefix + 'drqzju@gmail.com']); + yield this.db.query('insert into ??(name, email, gmt_create, gmt_modified) \ + values(?, ?, now(), now())', + [table, prefix + 'luckydrq2', prefix + 'drqzju@gmail.com']); + }); + + it('should update multiple rows', function* () { + let rows = yield this.db.query('select * from ?? where email=? order by id', + [table, prefix + 'drqzju@gmail.com']); + console.log(rows); + assert.equal(rows.length, 2); + assert.equal(rows[0].name, prefix + 'luckydrq'); + assert.equal(rows[1].name, prefix + 'luckydrq2'); + + rows[0].name = prefix + 'drq'; + rows[1].email = prefix + 'drqzju@gmail2.com'; + yield this.db.updateRows(table, rows); + let updatedRows = yield this.db.query('select * from ?? where id in (?, ?)', + [table, rows[0].id, rows[1].id]); + console.log(updatedRows); + assert.equal(updatedRows[0].name, prefix + 'drq'); + assert.equal(updatedRows[1].email, prefix + 'drqzju@gmail2.com'); + }); + }); });