From 9cae1cbc10ce6402cbdf8e272d929a0be5899ff4 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Wed, 3 Jun 2015 02:22:33 +0800 Subject: [PATCH] feat: add get(), list(), insert(), update() --- lib/client.js | 23 ++++-- lib/connection.js | 11 +-- lib/operator.js | 159 ++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + test/ali-rds.test.js | 133 ++++++++++++++++++++++++++++++++++- test/operator.test.js | 44 ++++++++++++ 6 files changed, 362 insertions(+), 9 deletions(-) create mode 100644 lib/operator.js create mode 100644 test/operator.test.js diff --git a/lib/client.js b/lib/client.js index 7b875f3..7fe9cd8 100644 --- a/lib/client.js +++ b/lib/client.js @@ -14,7 +14,10 @@ * Module dependencies. */ +var debug = require('debug')('ali-sdk:ali-rds:client'); var mysql = require('mysql'); +var util = require('util'); +var Operator = require('./operator'); var RDSConnection = require('./connection'); module.exports = RDSClient; @@ -23,17 +26,22 @@ function RDSClient(options) { if (!(this instanceof RDSClient)) { return new RDSClient(options); } + Operator.call(this); + this.pool = mysql.createPool(options); // if needFields = false, return rows result instead of {rows, fields} object result. this._needFields = options.needFields === false ? false : true; } +util.inherits(RDSClient, Operator); + var proto = RDSClient.prototype; proto.query = function (sql, values) { var pool = this.pool; var needFields = this._needFields; return function (callback) { + debug('query %j, needFields: %s', sql, needFields); pool.query(sql, values, function (err, rows, fields) { if (needFields) { callback(err, { rows: rows, fields: fields }); @@ -44,10 +52,6 @@ proto.query = function (sql, values) { }; }; -proto.escape = function (val) { - return this.pool.escape(val); -}; - proto.getConnection = function () { var pool = this.pool; var needFields = this._needFields; @@ -64,3 +68,14 @@ proto.getConnection = function () { }); }; }; + +proto.beginTransaction = function* () { + var conn = yield this.getConnection(); + try { + yield conn.beginTransaction(); + } catch (err) { + conn.release(); + throw err; + } + return conn; +}; diff --git a/lib/connection.js b/lib/connection.js index 45c36dc..33b0015 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -14,12 +14,18 @@ * Module dependencies. */ +var debug = require('debug')('ali-sdk:ali-rds:connection'); +var util = require('util'); +var Operator = require('./operator'); + module.exports = RDSConnection; function RDSConnection(conn, needFields) { + Operator.call(this); this.conn = conn; this._needFields = needFields; } +util.inherits(RDSConnection, Operator); var proto = RDSConnection.prototype; @@ -31,6 +37,7 @@ proto.query = function (sql, values) { var conn = this.conn; var needFields = this._needFields; return function (callback) { + debug('query %j, needFields: %s', sql, needFields); conn.query(sql, values, function (err, rows, fields) { if (needFields) { callback(err, { rows: rows, fields: fields }); @@ -41,10 +48,6 @@ proto.query = function (sql, values) { }; }; -proto.escape = function (val) { - return this.conn.escape(val); -}; - proto.beginTransaction = function () { var conn = this.conn; return function (callback) { diff --git a/lib/operator.js b/lib/operator.js new file mode 100644 index 0000000..301a2e8 --- /dev/null +++ b/lib/operator.js @@ -0,0 +1,159 @@ +/**! + * ali-rds - lib/operator.js + * + * Copyright(c) fengmk2 and other contributors. + * MIT Licensed + * + * Authors: + * fengmk2 (http://fengmk2.com) + */ + +'use strict'; + +/** + * Module dependencies. + */ + +var mysql = require('mysql'); + +module.exports = Operator; + +function Operator() { + +} + +var proto = Operator.prototype; + +proto.escape = function (value, stringifyObjects, timeZone) { + return mysql.escape(value, stringifyObjects, timeZone); +}; + +proto.escapeId = function (value, forbidQualified) { + return mysql.escapeId(value, forbidQualified); +}; + +proto.format = function (sql, values, stringifyObjects, timeZone) { + return mysql.format(sql, values, stringifyObjects, timeZone); +}; + +proto.query = function () { + throw new Error('Not Impl'); +}; + +proto.list = function* (table, obj, keys, columns, orders, limit, offset) { + var sql = this._selectColumns(table, columns) + this._where(obj, keys) + this._orders(orders) + + this._limit(limit, offset) + ';'; + return yield this.query(sql); +}; + +proto.get = function* (table, obj, keys, columns, orders) { + var rows = yield this.list(table, obj, keys, columns, orders, 1); + if (!Array.isArray(rows)) { + rows = rows.rows; + } + return rows && rows[0] || null; +}; + +proto.insert = function* (table, obj, columns) { + if (!columns) { + columns = Object.keys(obj); + } + var values = []; + for (var i = 0; i < columns.length; i++) { + values.push(obj[columns[i]]); + } + var sql = this.format('INSERT INTO ??(??) VALUES(?);', [table, columns, values]); + return yield this.query(sql); +}; + +proto.update = function* (table, obj, keys, columns) { + if (!columns) { + columns = Object.keys(obj); + } + var sets = []; + var values = []; + for (var i = 0; i < columns.length; i++) { + var column = columns[i]; + if (keys.indexOf(column) >= 0) { + continue; + } + sets.push('?? = ?'); + values.push(column); + values.push(obj[column]); + } + var sql = this.format('UPDATE ?? SET ', [table]) + this.format(sets.join(', '), values) + this._where(obj, keys); + return yield this.query(sql); +}; + +proto._where = function (obj, keys) { + if (!keys) { + keys = [ 'id' ]; + } else if (typeof keys === 'string') { + keys = [ keys ]; + } + var wheres = []; + var values = []; + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var value = obj[key]; + if (Array.isArray(value)) { + wheres.push('?? IN (?)'); + } else { + wheres.push('?? = ?'); + } + values.push(key); + values.push(value); + } + return this.format(' WHERE ' + wheres.join(' AND '), values); +}; + +proto._selectColumns = function (table, columns) { + if (!columns) { + columns = '*'; + } + var sql; + if (columns === '*') { + sql = this.format('SELECT * FROM ??', [table]); + } else { + sql = this.format('SELECT ?? FROM ??', [columns, table]); + } + return sql; +}; + +proto._orders = function (orders) { + if (!orders) { + return ''; + } + if (typeof orders === 'string') { + orders = [ orders ]; + } + var values = []; + for (var i = 0; i < orders.length; i++) { + var value = orders[i]; + if (typeof value === 'string') { + values.push(this.escapeId(value)); + } else if (Array.isArray(value)) { + // value format: ['name', 'desc'], ['name'], ['name', 'asc'] + var sort = String(value[1]).toUpperCase(); + if (sort !== 'ASC' && sort !== 'DESC') { + sort = null; + } + if (sort) { + values.push(this.escapeId(value[0]) + ' ' + sort); + } else { + values.push(this.escapeId(value[0])); + } + } + } + return ' ORDER BY ' + values.join(', '); +}; + +proto._limit = function (limit, offset) { + if (!limit || typeof limit !== 'number') { + return ''; + } + if (typeof offset !== 'number') { + offset = 0; + } + return ' LIMIT ' + offset + ', ' + limit; +}; diff --git a/package.json b/package.json index 6243e41..324a4dc 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "cnpm": "npm install --registry=https://registry.npm.taobao.org" }, "dependencies": { + "debug": "~2.2.0", "mysql": "~2.7.0" }, "devDependencies": { diff --git a/test/ali-rds.test.js b/test/ali-rds.test.js index 3d25222..425d1db 100644 --- a/test/ali-rds.test.js +++ b/test/ali-rds.test.js @@ -122,6 +122,32 @@ describe('ali-rds.test.js', function () { assert.equal(data.fields.length, 5); }); + it('should use db.beginTransaction()', function* () { + var conn = yield this.db.beginTransaction(); + try { + yield conn.query('insert into `ali-sdk-test-user`(name, email, gmt_create, gmt_modified) \ + values(?, ?, now(), now())', + [prefix + 'beginTransaction1', prefix + 'm@beginTransaction.com']); + yield conn.query('insert into `ali-sdk-test-user`(name, email, gmt_create, gmt_modified) \ + values(?, ?, now(), now())', + [prefix + 'beginTransaction2', prefix + 'm@beginTransaction.com']); + yield conn.commit(); + } catch (err) { + // error, rollback + yield conn.rollback(); // rollback call won't throw err + throw err; + } finally { + // should release connection whatever + conn.release(); + } + + var data = yield this.db.query('select * from `ali-sdk-test-user` where email=? order by id', [prefix + 'm@beginTransaction.com']); + assert.equal(data.rows.length, 2); + assert.equal(data.rows[0].name, prefix + 'beginTransaction1'); + assert.equal(data.rows[1].name, prefix + 'beginTransaction2'); + assert.equal(data.fields.length, 5); + }); + it('should rollback when query fail', function* () { var conn = yield this.db.getConnection(); try { @@ -154,6 +180,111 @@ describe('ali-rds.test.js', function () { }); }); + describe('get(table, obj, keys, columns, orders), list(table, obj, keys, columns, orders, limit)', function () { + before(function* () { + var result = yield this.db.insert('ali-sdk-test-user', { + name: prefix + 'fengmk2-get', + email: prefix + 'm@fengmk2-get.com' + }); + assert.equal(result.rows.affectedRows, 1); + + var result = yield this.db.insert('ali-sdk-test-user', { + name: prefix + 'fengmk3-get', + email: prefix + 'm@fengmk2-get.com' + }); + assert.equal(result.rows.affectedRows, 1); + }); + + it('should get exists object without columns', function* () { + var user = yield this.db.get('ali-sdk-test-user', {email: prefix + 'm@fengmk2-get.com'}, 'email'); + assert(user); + assert.deepEqual(Object.keys(user), [ 'id', 'gmt_create', 'gmt_modified', 'name', 'email' ]); + assert.equal(user.name, prefix + 'fengmk2-get'); + + var user = yield this.db.get('ali-sdk-test-user', {email: prefix + 'm@fengmk2-get.com'}, 'email', null, [['id', 'desc']]); + assert(user); + assert.deepEqual(Object.keys(user), [ 'id', 'gmt_create', 'gmt_modified', 'name', 'email' ]); + assert.equal(user.name, prefix + 'fengmk3-get'); + + var user = yield this.db.get('ali-sdk-test-user', {email: prefix + 'm@fengmk2-get.com'}, 'email', null, + [['id', 'desc'], 'gmt_modified', ['gmt_create', 'asc']]); + assert(user); + assert.deepEqual(Object.keys(user), [ 'id', 'gmt_create', 'gmt_modified', 'name', 'email' ]); + assert.equal(user.name, prefix + 'fengmk3-get'); + }); + + it('should get exists object with columns', function* () { + var user = yield this.db.get('ali-sdk-test-user', {email: prefix + 'm@fengmk2-get.com'}, [ 'email' ], ['id', 'name']); + assert(user); + assert.deepEqual(Object.keys(user), [ 'id', 'name' ]); + assert.equal(user.name, prefix + 'fengmk2-get'); + }); + + it('should get null when row not exists', function* () { + var user = yield this.db.get('ali-sdk-test-user', {email: prefix + 'm@fengmk2-get-not-exists.com'}, [ 'email' ], ['id', 'name']); + assert.strictEqual(user, null); + }); + + it('should list objects without columns', function* () { + var result = yield this.db.list('ali-sdk-test-user', {email: prefix + 'm@fengmk2-get.com'}, 'email'); + var users = result.rows; + assert(users); + assert.equal(users.length, 2); + assert.deepEqual(Object.keys(users[0]), [ 'id', 'gmt_create', 'gmt_modified', 'name', 'email' ]); + assert.equal(users[0].name, prefix + 'fengmk2-get'); + + var result = yield this.db.list('ali-sdk-test-user', {email: prefix + 'm@fengmk2-get.com'}, + 'email', null, [['id', 'desc']], 1); + var users = result.rows; + assert(users); + assert.equal(users.length, 1); + assert.deepEqual(Object.keys(users[0]), [ 'id', 'gmt_create', 'gmt_modified', 'name', 'email' ]); + assert.equal(users[0].name, prefix + 'fengmk3-get'); + + var result = yield this.db.list('ali-sdk-test-user', {email: prefix + 'm@fengmk2-get.com'}, + 'email', null, [['id', 'desc']], 1, 1); + var users = result.rows; + assert(users); + assert.equal(users.length, 1); + assert.deepEqual(Object.keys(users[0]), [ 'id', 'gmt_create', 'gmt_modified', 'name', 'email' ]); + assert.equal(users[0].name, prefix + 'fengmk2-get'); + + var result = yield this.db.list('ali-sdk-test-user', {email: prefix + 'm@fengmk2-get.com'}, + 'email', null, [['id', 'desc']], 10, 100); + var users = result.rows; + assert(users); + assert.equal(users.length, 0); + }); + }); + + describe('update(table, obj, keys, columns)', function () { + before(function* () { + yield this.db.insert('ali-sdk-test-user', { + name: prefix + 'fengmk2-update', + email: prefix + 'm@fengmk2-update.com' + }); + }); + + it('should update exists row', function* () { + var user = yield this.db.get('ali-sdk-test-user', { + name: prefix + 'fengmk2-update', + }, 'name'); + assert.equal(user.email, prefix + 'm@fengmk2-update.com'); + + var result = yield this.db.update('ali-sdk-test-user', { + name: prefix + 'fengmk2-update', + email: prefix + 'm@fengmk2-update2.com', + gmt_modified: 'now()', + }, 'name'); + assert.equal(result.rows.affectedRows, 1); + + var user = yield this.db.get('ali-sdk-test-user', { + name: prefix + 'fengmk2-update', + }, 'name'); + assert.equal(user.email, prefix + 'm@fengmk2-update2.com'); + }); + }); + describe('getConnection()', function () { it('should throw error when mysql connect fail', function* () { var db = rds({ @@ -164,7 +295,7 @@ describe('ali-rds.test.js', function () { throw new Error('should not run this'); } catch (err) { assert.equal(err.name, 'RDSClientGetConnectionError'); - assert.equal(err.message, 'connect ECONNREFUSED'); + assert.equal(err.message.indexOf('connect ECONNREFUSED'), 0); } }); }); diff --git a/test/operator.test.js b/test/operator.test.js new file mode 100644 index 0000000..f67a1ff --- /dev/null +++ b/test/operator.test.js @@ -0,0 +1,44 @@ +/**! + * ali-rds - test/operator.test.js + * + * Copyright(c) fengmk2 and other contributors. + * MIT Licensed + * + * Authors: + * fengmk2 (http://fengmk2.com) + */ + +'use strict'; + +/** + * Module dependencies. + */ + +var assert = require('assert'); +var Operator = require('../lib/operator'); + +describe('operator.test.js', function () { + describe('_where()', function () { + it('should get where sql', function () { + var op = new Operator(); + assert.equal(op._where({ id: 1, name: 'foo' }), ' WHERE `id` = 1'); + assert.equal(op._where({ id: 1, name: 'foo' }, 'id'), op._where({ id: 1, name: 'foo' })); + assert.equal(op._where({ id: 1, name: 'foo' }, [ 'id' ]), op._where({ id: 1, name: 'foo' })); + + assert.equal(op._where({ id: 1, name: 'foo' }, [ 'id', 'name2' ]), ' WHERE `id` = 1 AND `name2` = NULL'); + + assert.equal(op._where({ 'test.id': 1, name: 'foo' }, 'test.id'), ' WHERE `test`.`id` = 1'); + assert.equal(op._where({ 'id': [1, 2], name: 'foo' }, ['id', 'name']), ' WHERE `id` IN (1, 2) AND `name` = \'foo\''); + assert.equal(op._where({ 'id': [1], name: 'foo' }, ['id', 'name']), ' WHERE `id` IN (1) AND `name` = \'foo\''); + // assert.equal(op.where({ 'test.id': new Date(), name: 'foo' }, 'test.id'), ' WHERE `test`.`id` = 1'); + + assert.equal(op._where({ id: 1, name: 'foo\'\"' }, 'name'), ' WHERE `name` = \'foo\\\'\\\"\''); + assert.equal(op._where({ id: 1, name: 'foo\'\"' }, [ 'name' ]), op._where({ id: 1, name: 'foo\'\"' }, 'name')); + + assert.equal(op._where({ id: 1, name: 'foo\'\"' }, [ 'id', 'name' ]), ' WHERE `id` = 1 AND `name` = \'foo\\\'\\\"\''); + assert.equal(op._where({ id: 1, name: 'foo\'\"' }, [ 'name', 'id' ]), ' WHERE `name` = \'foo\\\'\\\"\' AND `id` = 1'); + assert.equal(op._where({ id: 1, name: 'foo\'\"', user: 'fengmk2' }, [ 'id', 'name', 'user' ]), + ' WHERE `id` = 1 AND `name` = \'foo\\\'\\\"\' AND `user` = \'fengmk2\''); + }); + }); +});