diff --git a/History.md b/History.md index 3da61fc..66212cc 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +2.1.0 / 2017-12-14 +================== + * 大重构, 更加简练, 结构也更加合理 + + 2.0.1 / 2017-05-22 ================== * 优化一处由于js对象引用类型引起的混乱 diff --git a/Readme.md b/Readme.md index c2e63f7..b67e34d 100644 --- a/Readme.md +++ b/Readme.md @@ -1,255 +1,236 @@ ![module info](https://nodei.co/npm/mysqli.png?downloads=true&downloadRank=true&stars=true) + # mysqli -> 本模块基于node-mysql模块二次封装,对基础的增删改查,主从库等按js的特点进行了简化,并对SQL注入进行安全过滤,让没有SQL基础的人,也能顺利使用; -> 当然,一些复杂的查询,以及事务等,这些不在我的服务之内,而且会用到这些功能的童鞋,本身也有一定的SQL基础了; 所以,这类童鞋,请自行使用各自习惯的SQL模块,或手写实现。 +> 本模块基于 node-mysql 模块二次封装,对基础的增删改查,主从库等按 js 的特点进行 +> 了简化,并对 SQL 注入进行安全过滤,让没有 SQL 基础的人,也能顺利使用 ; 当然, +> 一些复杂的查询,以及事务等,这些不在我的服务之内,而且会用到这些功能的童鞋,本 +> 身也有一定的 SQL 基础了 ; 所以,这类童鞋,请自行使用各自习惯的 SQL 模块,或手 +> 写实现。 - -## 使用npm安装 +## 使用 npm 安装 ```bash npm install mysqli ``` - ## 实例化 -> 实例化可以传2种格式的配置,1是json对象,2是数组。 -> 只有一个数据库时,默认是主库; 多于1个数据库服务时,自动以第1个为主库,其他的从库,故实例化时,`注意顺序`。 +> 实例化可以传 2 种格式的配置,1 是 json 对象,2 是数组。只有一个数据库时,默认 +> 是主库 ; 多于 1 个数据库服务时,自动以第 1 个为主库,其他的从库,故实例化时 +> ,`注意顺序`。 ```javascript - let Mysqli = require('mysqli') //传入json let conn = new Mysqli({ - host: '', // IP/域名 - post: 3306, //端口, 默认 3306 - user: '', //用户名 - passwd: '', //密码 - charset: '', // 数据库编码,默认 utf8 【可选】 - db: '', // 可指定数据库,也可以不指定 【可选】 - }) + host: '', // IP/域名 + post: 3306, //端口, 默认 3306 + user: '', //用户名 + passwd: '', //密码 + charset: '', // 数据库编码,默认 utf8 【可选】 + db: '' // 可指定数据库,也可以不指定 【可选】 +}) // 传入数组 let conn = new Mysqli([ - { - host: 'host1', // IP/域名 - post: 3306, //端口, 默认 3306 - user: '', //用户名 - passwd: '', //密码 - charset: '', // 数据库编码,默认 utf8 【可选】 - db: '', // 可指定数据库,也可以不指定 【可选】 - }, - { - host: 'host2', // IP/域名 - post: 3306, //端口, 默认 3306 - user: '', //用户名 - passwd: '', //密码 - charset: '', // 数据库编码,默认 utf8 【可选】 - db: '', // 可指定数据库,也可以不指定 【可选】 - }, - ]) - + { + host: 'host1', // IP/域名 + post: 3306, //端口, 默认 3306 + user: '', //用户名 + passwd: '', //密码 + charset: '', // 数据库编码,默认 utf8 【可选】 + db: '' // 可指定数据库,也可以不指定 【可选】 + }, + { + host: 'host2', // IP/域名 + post: 3306, //端口, 默认 3306 + user: '', //用户名 + passwd: '', //密码 + charset: '', // 数据库编码,默认 utf8 【可选】 + db: '' // 可指定数据库,也可以不指定 【可选】 + } +]) ``` - -## API方法 - +## API 方法 ### 1. escape(val) -> 这是`node-mysql`的内部方法,用于进行SQL安全过滤,这里只是做了搬运工,把它暴露出来给外部调用而已。 - -### 2. listDB() -> 顾名思义,该方法即用于列举当前账号权限范围内的所有的数据库名称,返回值是一个数组; -> -> **注:**`该方法配置await指令可得到纯粹的数据,否则返回的是一个Promise对象` +> 这是`node-mysql`的内部方法,用于进行 SQL 安全过滤,这里只是做了搬运工,把它暴 +> 露出来给外部调用而已。 **这个是静态方法** ```javascript +const Mysqli = require('mysqli') -async function(){ - let db = await conn.listDB() - console.log(db) -} +Mysqli.escape('这是文本') +``` -// 不使用await指令时,返回的是Promise对象 -conn.listDB().then(db => { - console.log(db) +### 2. emit(isSlave, dbName) + +* isSlave `` 可选 +* dbName `` 可选。 如果在实例化连接时 , 已经传入 db, 则这里可不传值。 + +> 触发一个数据库实例 , 可接受 2 个参数 , 第 1 个为 " 是否从库 ", 第 2 个为 " 数 +> 据库名称 " + +```javascript +const Mysqli = require('mysqli') +let conn = new Mysqli({ + /*...*/ }) - - +let db = conn.emit(true, 'test') ``` +### 3. listDb() -### 3. useDB(db[, slave]) -- db `` -- slave `` 可选 - -> 该方法用于切换数据库,仅限于同一台机器上的数据库; 在配置中没有指定数据库的情况下,必须先调用该方法才可以进行后续的增删改查等操作。 -> -> `db`即为要切换的数据库名; `slave`为是否从库查询,默认主库。 +> 顾名思义,该方法即用于列举当前账号权限范围内的所有的数据库名称,返回值是一个数 +> 组 ; +> +> **注:**`方法返回的是一个 Promise 对象` ```javascript +db.listDb().then(list => { + console.log(list) +}) +``` -async function(){ +### 4. listTable() - let docs = await conn.useDB('xx').query(`select * from users limit 10`); - console.log(docs); +> 该方法用于列举当前数据库数据表的集合 -} +```javascript +db.listTable().then(list => { + console.log(list) +}) +``` +### 5. query(sql) + +* sql `` + +> 该方法用于当内置的方法满足不了需求时,可以自行编写`sql 语句`执行 ; 但要注意防 +> 止`sql 注入`,因为该方法是最基础的方法,模块不对传入的`sql 语句`进行任何的安全 +> 过滤。 + +```javascript // 不使用await指令时,返回的是Promise对象 -conn.useDB('xx') - .query(`select * from users limit 10`) - .then(docs => { - console.log(docs) - }) - - +db.query(`select * from users limit 10`).then(row => { + console.log(row) +}) ``` +### 6. filter(condition, select) +* condition ``, 查询条件 +* select ``, 要返回字段 , 默认全部返回 -### 4. query(sql[, slave]) -- sql `` -- slave `` 可选 - -> 该方法用于当内置的方法满足不了需求时,可以自行编写`sql语句`执行; 但要注意防止`sql注入`,因为该方法是最基础的方法,模块不对传入的`sql语句`进行任何的安全过滤。 -> -> `sql`即为要执行的sql语句; `slave`为是否从库查询,默认主库。 +> 该方法用于查询多条数据。无论结果是多少条,返回的都是`数组格式`; 详细请看下面代 +> 码示例: ```javascript - -async function(){ - - let docs = await conn.query(`select * from users limit 10`); - console.log(docs); - -} - -// 不使用await指令时,返回的是Promise对象 -conn.query(`select * from users limit 10`) - .then(docs => { - console.log(docs) - }) - -``` - - - -### 5. find(conf) -- conf `` - -> 该方法用于查询多条数据。无论结果是多少条,返回的都是`数组格式`; 详细请看下面代码示例: - -```javascript - -conn.find({ - table: '', // 要查询的表 - select: ['a', 'b'], //要返回的字段,不传默认返回所有 【可选】 - where: [{ //数组格式,可以组成多个条件,默认查询全表 【可选】 - join: 'OR', //条件关系 AND, OR - op: '>', //关系符,如 =, >, <, <=, >= - key: 'aa', - val: 23 - }], - sort: { //排序, key是要排序的字段,value是排序方式, 1顺序,-1逆序 【可选】 +db + .filter( + { + table: '', // 要查询的表 + where: [ + { + //数组格式,可以组成多个条件,默认查询全表 【可选】 + join: 'OR', //条件关系 AND, OR + op: '>', //关系符,如 =, >, <, <=, >= + key: 'aa', + val: 23 + } + ], + sort: { + //排序, key是要排序的字段,value是排序方式, 1顺序,-1逆序 【可选】 a: 1, b: -1 + }, + limit: [0, 1] // 查询范围,可用于分页 【可选】 }, - limit: { // 查询范围,可用于分页 【可选】 - start: 0, - size: 10 - }, - slave: false // 是否从库 【可选】 -}) - -// 其中,table这一项,还可以联表,但是仅限于 'left join',要使用其他的方式联表,请自行编写sql语句 -// where条件也可以直接使用sql语句,要注意防止注入。 - -conn.find({ - table: { - master: 'xx', - unite: [ - { - table: 'aa', - on: 'xx.id = aa.xid' - }, - //... 可以联多个表, 但都是 left join - ] - }, - where: `xx.id = 123` -}) - + ['a', 'b'] + ) + .then(row => { + console.log(row) + }) ``` +### 7. filterOne(condition) +* condition `` -### 6. findOne(conf) -- conf `` +> 该方法与上面的`filter`方法的使用一致,区别只是该方法只返回一条数据,且为`json +> 格式`。 -> 该方法与上面的`find`方法的使用一致,区别只是该方法只返回一条数据,且为`json格式`。 +### 8. count(condition) +* condition `` -### 7. count(conf) -- conf `` +> 该方法与上面的`filter`方法的使用一致,不过返回的是条目总数 (``) -> 该方法与上面的`find`方法的使用一致,不过返回的是条目总数(``) +### 9. insert(condition, doc) +* condition ``, 插入的条件 +* doc ``, 插入的数据 -### 8. insert(conf) -- conf `` - -> 该方法与上面的`find`方法的使用类似,手于插入一条数据,具体请看下面代码; -> +> 该方法与上面的`filter`方法的使用类似,手于插入一条数据,具体请看下面代码 ; +> > **注:**`该方法一次只能插入一条数据` ```javascript - // 如果主键是自增ID,则结果返回的是 刚插入的数据的自增ID -conn.insert({ - table: 'xx', - data: {}, //要插入的数据 -}) - +db + .insert( + { + table: 'xx' + }, + { xx: 1234 } //要插入的数据 + ) + .then(id => { + console.log(id) + }) ``` +### 10. update(condition, doc) -### 9. update(conf) -- conf `` +* condition ``, 条件 +* doc ``, 数据 -> 该方法与上面的`find`方法的使用类似,用于更新数据,具体请看下面代码; -> `该方法返回的是被修改的条目数量` +> 该方法与上面的`filter`方法的使用类似,用于更新数据,具体请看下面代码 ; `该方法返 +> 回的是被修改的条目数量` ```javascript - // 如果修改成功,则返回被修改的数量 -conn.update({ - table: 'xx', - data: {}, //要修改的数据 - where: `id = 123` -}) - +db + .update( + { + table: 'xx' + }, + { xx: 1234 } //要插入的数据 + ) + .then(num => { + console.log(num) + }) ``` +### 11. remove(condition) -### 10. remove(conf) -- conf `` +* condition `` -> 该方法与上面的`find`方法的使用类似,用于删除指定条件的数据; 具体请看下面代码; -> `该方法返回的是被删除的条目数量` +> 该方法与上面的`update`方法的使用类似,用于删除指定条件的数据 ; 具体请看下面代码 +> ; `该方法返回的是被删除的条目数量` ```javascript - // 如果修改成功,返回的是被删除的条目数量 -conn.update({ - table: 'xx', - data: {}, //要修改的数据 - where: `id = 123` -}) - +db + .remove( + { + table: 'xx' + } + ) + .then(num => { + console.log(num) + }) ``` - - - diff --git a/index.js b/index.js index d7f0623..623d11f 100644 --- a/index.js +++ b/index.js @@ -4,531 +4,79 @@ * @date 2015-11-24 11:31:55 * */ -"use strict"; +'use strict' require('es.shim') -let mysql = require('mysql') -/** - * [parseWhere 格式化where条件] - * @param [array] arr [条件数组] - */ +const mysql = require('mysql') +const Method = require('./lib/method') -function parseWhere(arr){ - - if(typeof arr === 'string' && !!arr){ - return ' WHERE ' + arr - }else if(Array.isArray(arr) && arr.length > 0){ - - let where = '' - for(let it of arr){ - it.join = it.join || 'AND' - it.op = it.op || '=' - - let fixVal = it.val - if(!/(^\(SELECT\s+.*\)$)|^`/.test(it.val) && !['IN', 'BETWEEN'].includes(it.op)){ - - fixVal = mysql.escape(it.val) - } - - where += `${it.join.toUpperCase()} ${it.key} ${it.op} ${fixVal} ` - } - - where = ' WHERE ' + where.trim().replace(/^(AND|OR)/, ' ') + ' ' - return where - - }else{ - return ' ' +if (!Promise.defer) { + Promise.defer = function() { + let obj = {} + obj.promise = new this((yes, no) => { + obj.resolve = yes + obj.reject = no + }) + return obj + } +} +class Mysqli { + /** + * [constructor 构造数据库连接池] + */ + constructor(config) { + if (!Array.isArray(config)) { + config = [config] } - + + //是否有从库 + this.useSlaveDB = config.length > 1 + this.pool = mysql.createPoolCluster() + + config.forEach((item, i) => { + let { host, port, user, charset, passwd: password, db: database } = item + let name = i < 1 ? 'MASTER' : 'SLAVE' + i + + charset = charset || 'utf8' + let collate = + charset + (charset === 'utf8mb4' ? '_unicode_ci' : '_general_ci') + + this.pool.add(name, { + host, + port, + user, + charset, + collate, + password, + database + }) + }) + return this + } + + //对外的escape方法 + static escape(val) { + return mysql.escape(val) + } + + emit(fromSlave = false, db) { + const defer = Promise.defer() + const slave = fromSlave && this.useSlaveDB ? 'SLAVE*' : 'MASTER' + + this.pool.getConnection(slave, (err, conn) => { + if (err) { + return defer.reject(`MySQL connect ${err}`) + } + if (db) { + conn.query('USE ' + db, err => { + if (err) { + return defer.reject('Select DB ' + err) + } + defer.resolve(conn) + }) + } + }) + return new Method(defer.promise) + } } -class Mysqli{ - - /** - * [constructor 构造数据库连接] - */ - constructor(conf){ - if(!Array.isArray(conf)) - conf = [conf] - - //是否有从库 - this.slave = conf.length > 1 - this.conn = null - - this.pool = mysql.createPoolCluster() - let idx = 0 - while(idx < conf.length){ - let cf = conf[idx] - cf.charset = cf.charset || 'utf8' - let name = idx === 0 ? 'MASTER' : ('SLAVE' + idx) - - idx++ - this.pool.add(name, { - host: cf.host, - port: cf.port, - user: cf.user, - password: cf.passwd, - charset: cf.charset, - collate: cf.charset + ((cf.charset === 'utf8mb4') ? '_unicode_ci' : '_general_ci'), - database: cf.db - }) - } - } - - //对外的escape方法 - escape(val){ - return mysql.escape(val) - } - - - //返回数据库列表 - listDB(){ - return new Promise((yes, no) => { - this.pool - .getConnection((err, conn) => { - if(err) - return no(`MySQL connect ${err}`) - - conn.query('SHOW databases', (err, docs) => { - - conn.release() - - if(err) - return no('SHOW databases ' + err) - - let res = [] - for(let it of docs){ - res.push(it.Database) - } - yes(res) - }) - }) - }) - } - - - //选择database - useDB(db, slave){ - slave = (slave && this.slave) ? 'SLAVE*' : 'MASTER' - - this.conn = (async () => { - return await new Promise((yes, no) => { - - this.pool - .getConnection(slave, (err, conn) => { - if(err) - return no(`MySQL connect ${err}`) - - conn.query('USE ' + db, (err) => { - - if(err) - return no('Select DB ' + err) - - yes(conn) - }) - }) - - }) - })() - return this - } - - - /** - * [query sql语句执行] - * @param {[type]} sql [sql语句] - * @param {boolean} slave [是否从库] - */ - query(sql, slave){ - slave = (slave && this.slave) ? 'SLAVE*' : 'MASTER' - - if(typeof sql !== 'string') - return Promise.reject(`query error, argument sql must be string. ${typeof sql} given`) - - return new Promise((yes, no) => { - - if(this.conn){ - this.conn.then(conn => { - - conn.query(sql, (err, res) => { - conn.release() - this.conn = null - - if(err) - return no(`Query ${err}; Last exec SQL: ${sql}`) - - yes(res) - }) - - }).catch(no) - }else{ - this.pool.getConnection(slave, (err, conn) => { - if(err) - return no(`MySQL connect ${err}`) - - conn.query(sql, (err, res) => { - - conn.release() - - if(err) - return no(`Query ${err}; Last exec SQL: ${sql}`) - - yes(res) - - }) - }) - } - - }) - } - - - - /** - * [find 基础的数据查询, 支持简单的联表查询] - * @param {[type]} conf [要查询的信息] - * - * e.g. - * .find({ - * table: '', - * select: ['a', 'b'], - * where: [{ //数组格式,可以组成多个条件,默认查询全表 [可选] - * join: 'OR', //条件关系 AND, OR - * op: '>', //关系符,如 =, >, <, <=, >= - * key: 'aa', - * val: 23 - * }], - * sort: { //排序, key是要排序的字段,value是排序方式, 1顺序,-1逆序 [可选] - * a: 1, - * b: -1 - * }, - * limit: { // 查询范围,可用于分页 [可选] - * start: 0, - * size: 10 - * } - * }) - */ - find(conf){ - conf.slave = (conf.slave && this.slave) ? 'SLAVE*' : 'MASTER' - - if(!conf.table) - return Promise.reject('Find Error: empty table') - - let fields = '' //-------要返回的字段 ---------------- - if(!conf.hasOwnProperty('select') || Object.empty(conf.select)) - fields = '*' - else - fields = conf.select.join(',') - - let sql = 'SELECT ' + fields - - let table = '' //---------要查询的表 ----------------- - if(typeof conf.table === 'string'){ //单表 - table = ' FROM ' + conf.table - }else{ //联表 - table = ' FROM ' + conf.table.master - for(let join of conf.table.unite){ - table += ` LEFT JOIN ${join.table} ON ${join.on} ` - } - } - sql += table - - //查询条件 --------------------------------------- - sql += parseWhere(conf.where) - - let sort = '' //排序 ---------------------------------- - if(conf.sort && typeof conf.sort === 'object'){ - sort = ' ORDER BY ' - for(let i in conf.sort){ - let c = '' - if(conf.sort[i] === -1) - c = 'DESC' - - sort += `${i} ${c},` - } - sort = sort.slice(0, -1) - } - sql += sort - - let limit = '' //--------查询范围 ---------- - if(conf.hasOwnProperty('limit')){ - let start = conf.limit.start || 0 - let size = (conf.limit.size && conf.limit.size > 0) ? conf.limit.size : 1 - limit = ` LIMIT ${start},${size} ` - } - sql += limit - - return new Promise((yes, no) => { - - if(this.conn){ - this.conn.then(conn => { - - conn.query(sql, (err, res) => { - conn.release() - this.conn = null - - if(err) - return no(`Find ${err}; Last exec SQL: ${sql}`) - - yes(res) - }) - - }).catch(no) - }else{ - this.pool.getConnection(conf.slave, (err, conn) => { - if(err) - return no(`MySQL connect ${err}`) - - conn.query(sql, (err, res) => { - - conn.release() - - if(err) - return no(`Find ${err}; Last exec SQL: ${sql}`) - - yes(res) - - }) - }) - } - - }) - } - - /** - * [findOne 查找一条记录, 参数同 find] - */ - findOne(conf){ - let res = (async () => this.find({ - table: conf.table, - select: conf.select || [], - where: conf.where || '', - sort: conf.sort, - slave: conf.slave, - limit: {start: 0, size: 1} - }))() - return new Promise((yes, no) => { - res.then(list => { - yes(list[0] || null) - }, no) - }) - } - - /** - * [count 计算结果总数, 参数同findOne] - */ - count(conf){ - let res = (async () => this.find({ - table: conf.table, - select: ['count(*) AS total'], - slave: conf.slave, - where: conf.where || '' - }))() - return new Promise((yes, no) => { - res.then(list => { - yes(list[0] && list[0].total || 0) - }, no) - }) - } - - /** - * [insert 插入数据,单条] - * @param {[object]} conf [要插入的信息,{table: '', data: {}} ] - * - * eg. - * .insert({ - * table: 'test', - * data: {aa: 123, bb: 456} - * }, function(id){...}) - */ - insert(conf){ - conf.slave = (conf.slave && this.slave) ? 'SLAVE*' : 'MASTER' - - if(!conf.table) - return Promise.reject('Insert Error: empty table') - - let sql = 'INSERT INTO ' + conf.table + ' (' - let keys = [] - let vals = [] - - for(let i in conf.data){ - keys.push(i) - vals.push(mysql.escape(conf.data[i])) - } - sql += `${keys.join(',')}) VALUES (${vals.join(',')})` - - return new Promise((yes, no) => { - - if(this.conn){ - this.conn.then(conn => { - - conn.query(sql, (err, res) => { - conn.release() - this.conn = null - - if(err) - return no(`Insert ${err}; Last exec SQL: ${sql}`) - - yes(res.insertId) - }) - - }).catch(no) - }else{ - this.pool.getConnection(conf.slave, (err, conn) => { - if(err) - return no(`MySQL connect ${err}`) - - conn.query(sql, (err, res) => { - - conn.release() - - if(err) - return no(`Insert ${err}; Last exec SQL: ${sql}`) - - yes(res.insertId) - - }) - }) - } - - }) - } - - /** - * [insert 基础的数据修改] - * @param {[object]} conf [要修改的信息, {table: '', where: [], data: {}}] - * - * eg. - * .update({ - * table: 'test', - * data: {aa: 123, bb: 456}, - * where: [{ //数组格式,可以组成多个条件 - * join: 'OR', //条件关系 AND, OR - * op: '>', //关系符,如 =, >, <, <=, >= - * key: 'aa', - * val: 23 - * }] - * }, function(nums){...}) - */ - update(conf){ - conf.slave = (conf.slave && this.slave) ? 'SLAVE*' : 'MASTER' - - if(!conf.table) - return Promise.reject('Update Error: empty table') - - let sql = 'UPDATE ' + conf.table + ' SET ' - - let fields = [] //要更新的字段 - for(let i in conf.data){ - fields.push(i + ' = ' + mysql.escape(conf.data[i])) - } - sql += fields.join(',') - sql += parseWhere(conf.where) - - return new Promise((yes, no) => { - - if(this.conn){ - this.conn.then(conn => { - - conn.query(sql, (err, res) => { - conn.release() - this.conn = null - - if(err) - return no(`Update ${err}; Last exec SQL: ${sql}`) - - yes(res.affectedRows) - }) - - }).catch(no) - }else{ - this.pool.getConnection(conf.slave, (err, conn) => { - if(err) - return no(`MySQL connect ${err}`) - - conn.query(sql, (err, res) => { - - conn.release() - - if(err) - return no(`Update ${err}; Last exec SQL: ${sql}`) - - yes(res.affectedRows) - - }) - }) - } - - }) - - } - - /** - * [remove 基础的数据删除] - * @param {[type]} conf [要删除的信息, {table: '', where: []}] - * - * eg. - * .update({ - * table: 'test', - * where: [{ //数组格式,可以组成多个条件 - * join: 'OR', //条件关系 AND, OR - * op: '>', //关系符,如 =, >, <, <=, >= - * key: 'aa', - * val: 23 - * }] - * }, function(nums){...}) - */ - remove(conf){ - conf.slave = (conf.slave && this.slave) ? 'SLAVE*' : 'MASTER' - - if(!conf.table) - return Promise.reject('Remove Error: empty table') - - let sql = 'DELETE FROM ' + conf.table - - if(conf.where) - sql += parseWhere(conf.where) - - return new Promise((yes, no) => { - - if(this.conn){ - this.conn.then(conn => { - - conn.query(sql, (err, res) => { - conn.release() - this.conn = null - - if(err) - return no(`Remove ${err}; Last exec SQL: ${sql}`) - - yes(res.affectedRows) - }) - - }).catch(no) - }else{ - this.pool.getConnection(conf.slave, (err, conn) => { - if(err) - return no(`MySQL connect ${err}`) - - conn.query(sql, (err, res) => { - - conn.release() - - if(err) - return no(`Remove ${err}; Last exec SQL: ${sql}`) - - yes(res.affectedRows) - - }) - }) - } - - }) - } - - - -} - - - -module.exports = Mysqli \ No newline at end of file +module.exports = Mysqli diff --git a/lib/method.js b/lib/method.js new file mode 100644 index 0000000..84805f6 --- /dev/null +++ b/lib/method.js @@ -0,0 +1,216 @@ +/** + * + * @authors yutent (yutent@doui.cc) + * @date 2017-12-14 14:01:03 + * @version $Id$ + */ + +const { escape } = require('mysql') +const parser = require('./parser') + +class Method { + constructor(defer) { + this.defer = defer + } + + listDb() { + return this.defer.then(conn => { + const defer = Promise.defer() + + conn.query('SHOW databases', (err, row) => { + if (err) { + return defer.reject('SHOW databases ' + err) + } + defer.resolve(row.map(it => it.Database)) + // conn.release() + }) + + return defer.promise + }) + } + + //返回数据表 + listTable() { + return this.defer.then(conn => { + const defer = Promise.defer() + + conn.query('SHOW tables', (err, row) => { + if (err) { + return defer.reject('SHOW tables ' + err) + } + defer.resolve(row.map(it => Object.values(it)[0])) + // conn.release() + }) + + return defer.promise + }) + } + + /** + * [query sql语句执行] + * @param {[type]} sql [sql语句] + */ + query(sql) { + if (typeof sql !== 'string') + return Promise.reject( + `Query error, argument sql must be string. ${typeof sql} given` + ) + + return this.defer.then(conn => { + const defer = Promise.defer() + + conn.query(sql, (err, result) => { + if (err) { + return defer.reject( + `Query ${err}; \nLast exec SQL:「${this.lastSql}」` + ) + } + defer.resolve(result) + }) + return defer.promise + }) + } + + filter(condition, select) { + const { table, leftJoin, rightJoin, join, where, sort, limit } = condition + + if (!table) { + return Promise.reject('Filter Error: empty table') + } + + let sql = parser.select(select) + sql += `FROM \`${table}\` ` + if (leftJoin) { + sql += parser.leftJoin(leftJoin) + } + if (rightJoin) { + sql += parser.rightJoin(rightJoin) + } + if (join) { + sql += parser.join(join) + } + + if (where) { + sql += parser.where(where) + } + + if (sort) { + sql += parser.sort(sort) + } + + if (limit) { + sql += parser.limit(limit) + } + + return this.defer.then(conn => { + const defer = Promise.defer() + + conn.query(sql, (err, result) => { + if (err) { + return defer.reject( + `Filter ${err}; \nLast exec SQL:「${this.lastSql}」` + ) + } + defer.resolve(result) + }) + return defer.promise + }) + } + + filterOne(condition, select) { + condition.limit = [1] + return this.filter(condition, select).then(row => { + return row[0] || null + }) + } + + count(condition) { + delete condition.limit + return this.filter(condition, ['count(*) AS total']).then(row => { + return (row[0] && row[0].total) || 0 + }) + } + + insert({ table }, doc) { + if (!table) { + return Promise.reject('Insert Error: empty table') + } + + let sql = `INSERT INTO \`${table}\` ` + let keys = [] + let vals = [] + + for (let i in doc) { + keys.push(i) + vals.push(escape(doc[i])) + } + sql += `(${keys.join(',')}) VALUES (${vals.join(',')})` + + return this.defer.then(conn => { + const defer = Promise.defer() + + conn.query(sql, (err, result) => { + if (err) { + return defer.reject(`Insert ${err}; Last exec SQL: ${sql}`) + } + + defer.resolve(result.insertId) + }) + + return defer.promise + }) + } + + update({ table, where }, doc) { + if (!table) { + return Promise.reject('Update Error: empty table') + } + + let sql = `UPDATE \`${table}\` SET ` + + let fields = [] //要更新的字段 + for (let i in doc) { + fields.push(i + ' = ' + escape(doc[i])) + } + sql += fields.join(',') + sql += parser.where(where) + + return this.defer.then(conn => { + const defer = Promise.defer() + conn.query(sql, (err, res) => { + if (err) { + return defer.reject(`Update ${err}; Last exec SQL: ${sql}`) + } + + defer.resolve(res.affectedRows) + }) + return defer.promise + }) + } + + remove({ table, where }) { + if (!table) { + return Promise.reject('Remove Error: empty table') + } + + let sql = `DELETE FROM \`${table}\` ` + + if (where) { + sql += parser.where(where) + } + + return this.defer.then(conn => { + const defer = Promise.defer() + conn.query(sql, (err, res) => { + if (err) { + return defer.reject(`Remove ${err}; Last exec SQL: ${sql}`) + } + + defer.resolve(res.affectedRows) + }) + return defer.promise + }) + } +} + +module.exports = Method diff --git a/lib/parser.js b/lib/parser.js new file mode 100644 index 0000000..3733d95 --- /dev/null +++ b/lib/parser.js @@ -0,0 +1,96 @@ +/** + * + * @authors yutent (yutent@doui.cc) + * @date 2017-12-14 02:41:15 + * @version $Id$ + */ +const { escape } = require('mysql') + +/** + * [parseWhere 格式化where条件] + * @param [array] arr [条件数组] + */ + +function parseWhere(arr) { + if (typeof arr === 'string' && !!arr) { + return ' WHERE ' + arr + } else if (Array.isArray(arr) && arr.length > 0) { + let where = '' + for (let it of arr) { + it.join = it.join || 'AND' + it.op = it.op || '=' + + let fixVal = it.val + if ( + !/(^\(SELECT\s+.*\)$)|^`/.test(it.val) && + !['IN', 'BETWEEN'].includes(it.op) + ) { + fixVal = escape(it.val) + } + + where += `${it.join.toUpperCase()} ${it.key} ${it.op} ${fixVal} ` + } + + where = ' WHERE ' + where.trim().replace(/^(AND|OR)/, ' ') + ' ' + return where + } else { + return ' ' + } +} + +const Parser = { + leftJoin(tables) { + let sql = '' + for (let it of tables) { + sql += ` LEFT JOIN ${it[0]} ON ${it[1]} ` + } + return sql + }, + + rightJoin(tables) { + let sql = '' + for (let it of tables) { + sql += ` RIGHT JOIN ${it[0]} ON ${it[1]} ` + } + return sql + }, + + join(tables) { + let sql = '' + for (let it of tables) { + sql += ` JOIN ${it[0]} ON ${it[1]} ` + } + return sql + }, + + where(where = '') { + return parseWhere(where) + }, + + select(arr = ['*']) { + return `SELECT ${arr.join(',')} ` + }, + + // 排序 ---------------------------------- + sort(obj = {}) { + let sort = '' + for (let i in obj) { + let c = '' + if (obj[i] === -1) { + c = 'DESC' + } + sort += `${i} ${c},` + } + if (sort) { + return ' ORDER BY ' + sort.slice(0, -1) + } else { + return '' + } + }, + + limit(...args) { + return ` LIMIT ${args.join(',')} ` + } +} + +module.exports = Parser