[English](./README.md) | 简体中文 ## 目录 - [介绍](#introduction) - [安装](install) - [快速上手](#quick) - [SqlUtil 方法使用](#sqlUtil-use) - [ssh配置](#ssh-config) - [SqlUtil 功能介绍](#sql-methods-intro) - [错误码](#errorCode) - [License](#license) ## 介绍 SqlUtil 是轻量型数据库工具库,基于 mysql2+ssh2 实现,支持: - 支持 ssh 远程调试,方便本地开发调试 - 操作数据库便捷方法,包括数据查询、创建、删除、修改、事务单等等 - 支持原生 SQL 查询 - 安全防范,避免安全问题,比如 SQL 注入等 - 使用连接池库缓存数据库链接,减少连接创建/释放操作 ## 安装 ``` yarn add node-sql-util -S ``` ``` npm install node-sql-util --save ``` ## 快速上手 ```js // 创建链接 const mySql = new SqlUtil({ dbConfig: { host: "1.2.3.4", port: "1000", database: "xxxx", user: "xxxx", password: "xxxx", connectionLimit: 5, // 默认5 可以不配置 }, returnOriginError: false, // 是否返回原始错误信息 returnOriginSource: false, // 是否返回原始成功执行信息 }); // 使用 let searchRes = await mySql.select({ table: "xxxx", where: { id: 1 } }); ``` ## SqlUtil 方法使用 注意: 1. 所有的方法里,当 asSql 字段为 true 时,返回 sql 语句,否则**默认返回 sql 执行结果**。 2. Where 语句默认对象写法,数组写法见[这里](#where-array)(推荐数组写法,更灵活) 以下展示常用的 sql 语句的用法 - [select 选择](#select) - [find 查找单一数据](#find) - [insert 插入](#insert) - [update 更新](#update) - [delete 删除](#delete) - [count 统计](#count) - [join 多表联查](#join) - [条件选择](#condition) - [事务](#task) - [raw 原生 SQL 操作](#raw) ### select 选择 用法: ```js mysql.select({ fields = [], table = "", where = null, groupby = "", order = "desc", limit = null, asSql = false }) ``` #### 参数 | 字段名 | 默认值 | 说明 | 必需 | | ------- | ------ | --------------- | ---- | | table | "" | 表 | 是 | | fields | [] | 列 | 否 | | where | null | 条件 | 否 | | groupby | "" | 分组 | 否 | | orderby | "" | 排序 | 否 | | order | "desc" | 排序方式 | 否 | | limit | null | 分页 | 否 | | asSql | false | 是否返回sql语句 | 否 | #### 返回示例 ```javascript { code:0, subcode: 0, message:'success', default: 0, data: [{name: 'milu',age: 18}], } ``` 1.选择全部字段 ```sql select * from table1; ``` ```javascript let res = await mySql.select({ table: "table1" }); if (res.code === 0) { res.data; // 返回数组 console.info("成功!"); } else { console.info("错误!"); } ```
2.选择指定列 ```sql select name,age from table1; ``` ```javascript await mySql.select({ fields: ["name", "age"], table: "table1" }); ```
3.条件选择 ```sql select name,age from table1 where age=18 and name="lili"; ``` ```javascript await mySql.select({ fields: ["name", "age"], table: "table1", where: { age: 18, name: "lili" } }); ``` 更多条件请看[《条件选择》章节](#condition) ### find查找单一数据 `find`方法和`select`方法类似,差异在于: - limit 参数为 1,支持覆盖 - 返回结果`res.data`不是数组,而是第一个数据项 ```sql select age,sex from table1 where age=18 limit 1; await mySql.find({ table: "table1", fields: ["age", "sex"], where: { age: { value: 18 } } }); ``` #### 返回示例 ```javascript { code: 0, // 0表示成功,非0表示失败 data: { name: "xxx", age: 1, sex: 2, type: 8 }, message: "xxxxx" } ``` ### insert插入 用法: ```js mysql.insert({ fields = [], table = "", data = [] }) ``` 1.插入单行 ```sql INSERT INTO table_1 SET `age` = 1, `sex` = 1, `type` = 2, `created` = Now(); ``` ```javascript await mySql.insert({ table: "table_1", data: { age: 1, sex: 1, type: 2, created: mySql.raw("Now()") } }); // 或者数组写法 await sqlutil.insert({ fields: ["age", "sex", "type", "created"], table: "table_1", data:[1, 1, 2, mySql.raw("now()")] }); ``` 返回值中提供`insertId`表示新增数据的ID,形如: ```javascript { code: 0, // 0表示成功,非0表示失败 data: { insertId: 20000061, }, message: "xxxxx" } ```
2.插入多行 ```javascript await mySql.insert({ fields: ["fid", "uid", "position", "qq", "nick","insert_time"], table: "table_1", data:[ ["3", "333", "333", "333", "333",sqlutil.raw("CURRENT_TIMESTAMP")], ["4", "444", "444", "444", "444",sqlutil.raw("CURRENT_TIMESTAMP")] ] }); ``` #### 返回示例 ```json { "code": 0, "subcode": 0, "message": "success", "default": 0, "data": { "fieldCount": 0, "affectedRows": 1, "insertId": 17, "info": "", "serverStatus": 2, "warningStatus": 0 } } ``` ### update 更新 用法: ```js mysql.update({ table = "", data = {}, where = null }) ``` > 注意:`插入的字段如果是SQL内置变量或方法`,如 `NOW(),CURRENT_TIMESTAMP`,必须使用`sqlutil.raw()`进行转义,否则会以普通字符串形式插入。 ```sql update table_1 SET `age` = 18, `sex` = 2, `updated` = Now() where `id` = 4; ``` ```javascript await mySql.update({ table: "table_1", data: { age: 18, sex: 2, updated: mySql.raw("Now()") }, where: { id: 4 } }); ``` #### 返回示例 ```json { "code": 0, "subcode": 0, "message": "success", "default": 0, "data": { "fieldCount": 0, "affectedRows": 1, "insertId": 0, "info": "Rows matched: 1 Changed: 0 Warnings: 0", "serverStatus": 2, "warningStatus": 0, "changedRows": 0 } } ``` ### delete 删除数据 用法: ```js mysql.delete({ table = "", where = null, asSql = false }) ``` ```sql delete from `table1` where `id` = 11 ; ``` ```javascript await mySql.delete({ table: "table1", where: { id: 11 } }); ``` #### 返回示例 ```json { "code": 0, "subcode": 0, "message": "success", "default": 0, "data": { "fieldCount": 0, "affectedRows": 1, "insertId": 0, "info": "", "serverStatus": 2, "warningStatus": 0 } } ``` ### count 统计数量 用法: ```js mySql.count({ field = "", table = "", where = null }) ``` ```sql select count(`id`) as total from `table1` where `age` >= 18 ; ``` ```javascript let res = await mySql.count({ table: "table1", field: "id", where: { age: { value: 18, condition: ">=" } } }); ``` 返回值中`total`表示数量,形如: ```javascript { code: 0, // 0表示成功,非0表示失败 data: { total: 14 }, message: "xxxxx" } ``` ### join 多表联查 用法: ```js mysql.join({ leftTable = "", leftFields = [], rightTable = "", rightFields = [], joinCondition = "", where = null, groupby = "", orderby = "", order = "desc", limit = null, total = false, asSql = false }) ``` #### 参数 | 字段名 | 默认值 | 说明 | 必需 | | ------------- | ------ | --------------------------------------------- | ---- | | leftTable | "" | 左表 | 是 | | leftFields | [] | 左边字段 | 是 | | rightTable | "" | 右表 | 是 | | rightFields | [] | 右表字段 | 是 | | joinCondition | "" | 连接条件 | 是 | | where | null | 查询条件 | 否 | | groupby | "" | 分组 | 否 | | orderby | "" | 排序 | 否 | | order | "desc" | 排序方式 | 否 | | limit | null | 分页 | 否 | | total | false | //ture的时候为查询全部数据,limit字段此时失效 | 否 | | asSql | false | 是否返回sql语句 | 否 | 1.指定表字段 ```sql select `table1`.`name` as `name`, `table1`.`age` as `age`, `table1`.`b` as `b`, `table2`.`c` as `c`,`table2`.`d` as `d` from `table1` `table1`,`table2` `table2` where `table1`.`name` = `table2`.`name` and `table2`.`name` >= 11 ; ``` ```javascript await mySql.join({ leftTable: "table1", leftFields: ["name", "age", "b"], rightTable: "table2", rightFields: ["c", "d"], joinCondition: "name", where: { 'table2.age': { // 指定表字段 value: 11, condition: '>=' } }, total: false }); ```
2.表别名 ```javascript await mySql.join({ leftTable: "table1 as extra", leftFields: ["name", "age", "b"], rightTable: "table2", rightFields: ["c", "d"], joinCondition: "name", where: { 'extra.age': { // 表别名 value: 11, condition: '>=' } }, total: false }); ``` 更复杂的查询请结合 sqlutil.format 手动编写 sql 语句 ### 条件选择 #### 分页 ```sql select name,age from table1 limit 30,10; ``` ```javascript await mySql.select({ fields: ["name", "age"], table: "table1", limit: { start: 30, size: 10 } }); ``` #### 模糊选择 ```sql select name,age from table1 where name like "%ju%"; ``` ```javascript await mySql.select({ fields: ["name", "age"], table: "table1", where: { name: { value: "ju", like: true } } }); ``` #### 位置查询 ```sql select name,age from table1 where age=18 and position('milu' in name); ``` ```js await mySql.select({ fields: ["name", "age"], table: "table1", where: { age: 18, name: { value: "milu", position: true } } }); ``` #### 排序 1.分组排序 ```sql select name,age from `table1` where `age` = 18 group by `sex` order by `brithday` desc; ``` ```javascript await mySql.select({ fields: ["name", "age"], table: "table1", where: { age: 18 }, groupby: "sex", orderby: "brithday", order: "desc" }); ``` 2.多列排序 ```sql select name,age from table1 order by age desc, id asc; ``` ```javascript await mySql.select({ fields: ["name", "age"], table: "table1", orders: [{ order: 'desc', by: 'age' },{ order: 'asc', by: 'id' }] }); ``` #### 选择范围 ```sql select name,age from table1 where age in (12,34,1,6,7) ``` ```javascript await mySql.select({ fields: ["name", "age"], table: "table1", where: { age: [12, 34, 1, 6, 7] } }); ``` 1.或大于小于 ```sql select * from table1 where age between 3 and 5; ``` ```javascript await mySql.select({ table: "table1", where: { age: { value: [3, 5], between: true } } }); ```
2.大于小于取反 ```sql select * from table1 where age not between 3 and 5; ``` ```javascript await mySql.select({ table: "table1", where: { age: { value: [3, 5], between: true, not: true } } }); ``` #### 组合条件 1.(=,>,>=,<,<=) where 条件数组写法,支持重复字段,字段名 field 写在对象里面。其他条件跟对象写法一致 ```sql select * from table1 where age > 3 and age <= 5; ``` ```javascript await mySql.select({ table: "table1", where: [ { field: "age", value: 3, condition: ">" }, { field: "age", value: 5, condition: "<=" } ] }); ```
2.(and or) ```sql select name,age from table1 where age=18 or name="milu"; ``` ```javascript await sqlutil.select({ fields: ["name", "age"], table: "table1", where: { age: 18, name: { value: "milu", or: true //必须在第二个条件才生效 } } }); ```
3.优先查询组合条件,combineCondition 可以递归组合条件,`combineCondition.where`就和条件语法完全一致: ```sql select age, name from table1 where age=1 and (type=2 or (name like "luck" and type=8)); ``` ```javascript await mySql.select({ table: "table1", fields: ["age", "name"], where: { age: 1, combineCondition: { where: { type: 2, combineCondition: { where: { name: { value: "luck", like: true }, type: 8 }, or: true } } } } }); ``` ### 事务 `sqlUtil` 提供`runTransaction`方法处理事务,该方法中提供: - 一个新的 `sqlUtil` 实例,用法和上述一致,在事务中**必须使用该新实例**操作数据库 - 通过`rollback()`可以回滚事务执行的所有操作 - 当事务全部完成时,调用`commit()`用于提交事务。主要用于**提前**提交事务完成。 ```javascript let taskRes = await mySql.runTransaction( async ({ sqlUtil: newSql, rollback, commit }) => { // 事务内必须使用新实例newSql进行操作,用法和mySql一致 const modRes = await newSql.find({ table: "xxx", where: { id: "xxx" } }); // 可以将错误结果通过rollback返回,方便内部输入日志 if (modRes.code !== 0) { return rollback(modRes); } const updateRes = await newSql.update({ table: "xxx", data: { name: "xxx" }, where: { id: "xxx" } }); if (updateRes.code !== 0) { return rollback(updateRes); } // commit()是可选的 commit(); } ); if (taskRes.code === 0) { console.info("事务成功!"); } else { console.info("事务失败!"); } ``` `commit()`是可选的,可以不调用,`runTransaction(cb)`监听到 `cb `函数完成时,如果判断没有调用过 `rollback()` 并且代码没有报错,就会自动提交 `commit`。 `commit()`主要使用在**语义化的**地表示**提前完成事务**的场景: ```javascript await mySql.runTransaction( async ({ sqlUtil: newSql, rollback, commit }) => { const userRes = await newSql.select({ table: "xxx", where: { age: 18 } }); if (userRes.code !== 0) { return rollback(modRes); } // 如果人数过长,删除多余数据 if (userRes.data.length >= 20) { await newSql.delete({ table: "xxx", where: { age: 18 } }); // 提前完成事务 return commit(); } // 其他代码操作.... } ); ``` ### raw 原生 SQL 操作 使用`.format()`方法生成 SQL 语句,基本用法: > format 函数基于[sqlstring 官方库 ](https://github.com/mysqljs/sqlstring)实现的,具体说明看官方文档 ```javascript const SqlUtil = require("@tencent/kg-sqlutil"); const mySql = new SqlUtil({ /* ... */ }) // 1. ?表示对插入值进行转义 const sql = mySql.format(`select * from table1 where name = ?;`, ["lulu"]); // 2. 不需要转义的插入值,应该用双问号?? const sql = mySql.format(`insert into ?? set name = ?;`, ["table1", "lulu"]); // 3. 使用raw()方法表示插入值为“原生SQL代码”,注意raw对应一个问号 const sql = mySql.format(`insert into ?? set created = ?;`, [ "table1", mySql.raw("NOW()") ]); ``` 最后使用`.query()`方法执行上述生成的 SQL 语句: ```javascript let res = await mySql.query(sql); if (res.code === 0) { console.info("成功!"); } else { console.info("失败!"); } ``` ## SSH 配置[option feature] ```js // 创建链接 const mySql = new SqlUtil({ dbConfig: { host: "1.2.3.4", port: "1000", database: "xxxx", user: "xxxx", password: "xxxx", connectionLimit: 5 // 默认5 可以不配置 }, // 仅在本地开发时使用ssh ssh: __DEV__ ? { srcHost: "127.0.0.1", srcPort: 8080, host: "1.2.3.4", port: 1000, username: "xxx", password: "xxxx" } : null }); // 使用 let searchRes = await mySql.select({ table: "xxxx", where: { id: 1 } }); ``` - srcHost:本地后台服务启动 ip - srcPort: 本地后台服务启动端口 - host: SSH 服务器 ip - port: SSH 服务器端口 - username: SSH 服务器账户 - password: SSH 服务器密码 **注意**: ssh 只能在本地开发时使用,线上最好不要使用,注意隔离开发和线上环境。 ## SqlUtil 功能介绍 SqlUtil 实例属性和方法 - [`sqlutil.dbConfig` ](#newSqlUtil)db 配置 - [`sqlutil.ssh`](#newSqlUtil)ssh 配置 - [`sqlutil.format()` ](#format)转义 sql 语句,将输入字符转为安全字符串 - [`sqlutil.escape()` ](#escape)转义某个字符串字段 - [`sqlutil.escapeId()`](#escapeId) 转义表字段 - [`sqlutil.query()` ](#query)手动查询 sql 方法 - [`sqlutil.handleRes()` ](#handleRes)返回执行结果 - `sqlutil.setConnection(dbConfig)` 设置 db 连接配置 - `sqlutil.raw()` 转义 sql 内置方法变量 - `sqlutil.select()` 筛选数据方法 - `sqlutil.count()` 统计方法 - `sqlutil.insert()` 插入数据方法 - `sqlutil.update()` 更新数据方法 - `sqlutil.join()`多表查询方法 - `sqlutil.delete()`删除数据方法 - `sqlutil.find()`查找单一数据方法 ### 创建 SqlUtil 连接实例 ```javascript // 创建链接 const mySql = new SqlUtil({ dbConfig: { host: "1.2.3.4", port: "1000", database: "xxxx", user: "xxxx", password: "xxxx", connectionLimit: 5 // 默认5 可以不配置 }, // 仅在本地开发时使用ssh ssh: __DEV__ ? { srcHost: "127.0.0.1", srcPort: 8080, host: "1.2.3.4", port: 1000, username: "xxx", password: "xxxx" } : null }); ``` ### 转义 SQL 语句 `??` 为字段或表名,`?` 为具体字段值,需要转义的字段 1.普通字 `? `段转义 ```javascript const name = 'lili' const sql = sqlutil.format(`select * from table1 where name = ?;`,[name]); console.log(sql);//select * from table1 where name = 'lili'; ``` 2.字段`??`转义 ```javascript const name = 'lili' const field= 'name' const sql = sqlutil.format(`select * from table1 where ?? = ?;`,[field,name]); console.log(sql);//select * from table1 where `name` = 'lili'; ``` 3.数组和对象转义 ```javascript const name = 'milu' const age = 18 const field= ['name','age'] const sql = sqlutil.format(`select ?? from table1 where name = ? and age = ?;`,[field,name,age]); console.log(sql);//select `name`,`age` from table1 where `name` = 'milu' and `age` = 18; ``` ```javascript const name= 'milu'; const condition = { name : 'milu', age : 18 }; const sql = sqlutil.format(`update ?? set ? where name = ?;`,['talble1',condition,name]); console.log(sql);//update `table1` set `name` = 'milu', `age` = 18 where name = 'milu'; ``` 4.内置函数不转义 `sqlutil.raw` ```javascript const name = 'milu' const table = 'table1' const value = { date : sqlutil.raw('NOW()') } const sql = sqlutil.format(`update ?? set ? where name = ?;`,[table,value,name]); console.log(sql);//update `table1` set `date` = NOW() where name = 'milu'; ``` 5.列表转义 ```javascript const value = [['a', 'b'], ['c', 'd']]; const sql = sqlutil.format('?',[value]) console.log(sql);//('a', 'b'), ('c', 'd') ``` 6.表字段转义 `sqlutil.escapeId` ```javascript const sorter = 'posts.date'; const sql = 'SELECT * FROM posts ORDER BY ' + sqlutil.escapeId(sorter); console.log(sql); // SELECT * FROM posts ORDER BY `posts`.`date` // sqlutil.escapeId('date'); -> `date` // sqlutil.escapeId('table.date'); -> `table`.`date` // sqlutil.escapeId('table.date',true); -> `table.date` ``` 7.转义字符串 ```javascript sqlutil.escape('abc\'d'); // -> "'aaa\'a'" ``` ### 手动查询 sql ```javascript await sqlutil.query('select * from table1;'); await sqlutil.query('update table1 set a=1 where id=1;'); await sqlutil.query(`insert into table1 (name,age) values('milu',18)`); ``` ### 返回统一的执行结果 ```javascript return sqlutil.handleRes(-1000, '未登录', { data: 'data', other: 'other info' }); // 返回 { code:-1000, subcode: 0, message:'未登录', default: 0, data: 'data', other:'other info' } ``` ## 错误码 | 类型 | 说明 | | ----- | --------------------- | | 0 | 操作成功 | | -100x | 数据库连接相关的错误 | | -200x | sql语句使用相关的错误 | ### 连接相关 | 类型 | 说明 | | ----- | ------------------------------------ | | 0 | 操作成功 | | -1003 | 1. 连接数据失败
2. 事务启动失败 | | -1004 | 查询数据失败 | | -1005 | 连接数据失败,连接池为空,请检查配置 | | -1006 | 事务提交失败 | | -1007 | 事务执行失败 | ### sql语句相关 #### -2001 | 说明 | | ----------------- | | 未配置数据表table | #### -2002 | 方法 | 说明 | | ------ | ------------------------------------------------------------ | | count | 未指定字段统计 | | insert | 未配置表字段fields | | update | update条件未配置,有可能影响全部数据,请配置where字段。如需绕过请手动编写sql语句。 | | delete | delete条件未配置,有可能影响全部数据,请配置delete字段。如需绕过请手动编写sql语句。 | | join | 1. 未指定字段 leftFields 或 rightFields
2. 未指定连接字段 joinCondition | #### -2003 | 方法 | 说明 | | ------ | ---------------------------- | | insert | 插入字段不能为空 | | join | 连接字段为数组时必须传两个值 | ## License SqlUtil is available under the [MIT license](https://opensource.org/licenses/MIT).