MySQL 的简单 SQL 转义和格式
安装
$ npm install sqlstring
用法
var SqlString = require('sqlstring');
转义查询值
注意:这些转义值的方法仅在 禁用NO_BACKSLASH_ESCAPES SQL 模式(这是 MySQL 服务器的默认状态)时才有效。
注意该库执行客户端转义,因为这是一个在客户端生成 SQL 字符串的库。诸如此类的函数的语法 SqlString.format
可能看起来类似于准备好的语句,但事实并非如此,并且该模块的转义规则用于生成结果 SQL 字符串。转义输入的目的是避免 SQL 注入攻击。为了支持增强的支持SET
和IN
格式化,该模块将根据传入的 JavaScript 值的形状进行转义,并且生成的转义字符串可能不止一个值。当提供结构化用户输入作为要转义的值时,应注意验证输入的形状以验证输出是否符合预期。
为了避免 SQL 注入攻击,在 SQL 查询中使用用户提供的任何数据之前,您应该始终转义该数据。您可以使用以下方法来执行此 SqlString.escape()
操作:
var userId = 'some user provided value';
var sql = 'SELECT * FROM users WHERE id = ' + SqlString.escape(userId);
console.log(sql); // SELECT * FROM users WHERE id = 'some user provided value'
或者,您可以使用?
字符作为您想要转义的值的占位符,如下所示:
var userId = 1;
var sql = SqlString.format('SELECT * FROM users WHERE id = ?', [userId]);
console.log(sql); // SELECT * FROM users WHERE id = 1
多个占位符按照传递的顺序映射到值。例如,在以下查询中foo
equals a
、bar
equals b
、baz
equalsc
和 id
will be userId
:
var userId = 1;
var sql = SqlString.format('UPDATE users SET foo = ?, bar = ?, baz = ? WHERE id = ?',
['a', 'b', 'c', userId]);
console.log(sql); // UPDATE users SET foo = 'a', bar = 'b', baz = 'c' WHERE id = 1
这看起来类似于 MySQL 中的准备语句,但它实际上只是SqlString.escape()
在内部使用相同的方法。
注意这也与准备好的语句不同,因为所有语句?
都被替换,甚至那些包含在注释和字符串中的语句。
不同的值类型的转义方式不同,具体方法如下:
- 数字保持不变
- 布尔值转换为
true
/false
- 日期对象转换为
'YYYY-mm-dd HH:ii:ss'
字符串 - 缓冲区转换为十六进制字符串,例如
X'0fa5'
- 字符串被安全转义
- 数组变成列表,例如
['a', 'b']
变成'a', 'b'
- 嵌套数组被变成分组列表(用于批量插入),例如
[['a', 'b'], ['c', 'd']]
变成('a', 'b'), ('c', 'd')
- 具有
toSqlString
方法的对象将被.toSqlString()
调用,并且返回值将用作原始 SQL。 key = 'val'
对于对象上的每个可枚举属性,对象都会变成对。如果属性的值是一个函数,则跳过它;如果属性的值是一个对象,则对其调用 toString() 并使用返回值。undefined
/null
转换为NULL
NaN
/Infinity
保持原样。 MySQL 不支持这些,尝试将它们作为值插入将触发 MySQL 错误,直到它们实现支持。
您可能已经注意到,这种转义允许您做如下巧妙的事情:
var post = {id: 1, title: 'Hello MySQL'};
var sql = SqlString.format('INSERT INTO posts SET ?', post);
console.log(sql); // INSERT INTO posts SET `id` = 1, `title` = 'Hello MySQL'
该toSqlString
方法允许您使用函数形成复杂的查询:
var CURRENT_TIMESTAMP = { toSqlString: function() { return 'CURRENT_TIMESTAMP()'; } };
var sql = SqlString.format('UPDATE posts SET modified = ? WHERE id = ?', [CURRENT_TIMESTAMP, 42]);
console.log(sql); // UPDATE posts SET modified = CURRENT_TIMESTAMP() WHERE id = 42
要使用方法生成对象toSqlString
,SqlString.raw()
可以使用该方法。这将创建一个在占位符中使用时保持不变的对象?
,对于将函数用作动态值非常有用:
注意提供的字符串SqlString.raw()
在使用时将跳过所有转义函数,因此在传递未经验证的输入时要小心。
var CURRENT_TIMESTAMP = SqlString.raw('CURRENT_TIMESTAMP()');
var sql = SqlString.format('UPDATE posts SET modified = ? WHERE id = ?', [CURRENT_TIMESTAMP, 42]);
console.log(sql); // UPDATE posts SET modified = CURRENT_TIMESTAMP() WHERE id = 42
如果你觉得需要自己转义查询,也可以直接使用转义函数:
var sql = 'SELECT * FROM posts WHERE title=' + SqlString.escape('Hello MySQL');
console.log(sql); // SELECT * FROM posts WHERE title='Hello MySQL'
转义查询标识符
如果您不能信任 SQL 标识符(数据库/表/列名称),因为它是由用户提供的,您应该SqlString.escapeId(identifier)
像这样转义它:
var sorter = 'date';
var sql = 'SELECT * FROM posts ORDER BY ' + SqlString.escapeId(sorter);
console.log(sql); // SELECT * FROM posts ORDER BY `date`
它还支持添加限定标识符。它将逃脱这两个部分。
var sorter = 'date';
var sql = 'SELECT * FROM posts ORDER BY ' + SqlString.escapeId('posts.' + sorter);
console.log(sql); // SELECT * FROM posts ORDER BY `posts`.`date`
如果您不想将其.
视为限定标识符,TP钱包下载则可以将第二个参数设置为 ,true
以便将字符串保留为文字标识符:
var sorter = 'date.2';
var sql = 'SELECT * FROM posts ORDER BY ' + SqlString.escapeId(sorter, true);
console.log(sql); // SELECT * FROM posts ORDER BY `date.2`
或者,您可以使用??
字符作为您想要转义的标识符的占位符,如下所示:
var userId = 1;
var columns = ['username', 'email'];
var sql = SqlString.format('SELECT ?? FROM ?? WHERE id = ?', [columns, 'users', userId]);
console.log(sql); // SELECT `username`, `email` FROM `users` WHERE id = 1
请注意,最后一个字符序列是实验性的,语法可能会改变
当您将对象传递给.escape()
或 时.format()
,.escapeId()
用于避免对象键中的 SQL 注入。
格式化查询
您可以SqlString.format
使用 id 和值的正确转义来准备具有多个插入点的查询。一个简单的例子如下:
var userId = 1;
var inserts = ['users', 'id', userId];
var sql = SqlString.format('SELECT * FROM ?? WHERE ?? = ?', inserts);
console.log(sql); // SELECT * FROM `users` WHERE `id` = 1
接下来,您将获得一个有效的转义查询,然后您可以将其安全地发送到数据库。如果您希望在实际将查询发送到数据库之前准备好查询,那么这非常有用。您还可以选择(但不是必需)传入stringifyObject
和timeZone
,允许您提供将对象转换为字符串的自定义方法,以及特定于位置/时区的Date
。
这可以进一步与SqlString.raw()
帮助程序结合以生成包含 MySQL 函数作为动态值的 SQL:
var userId = 1;
var data = { email: 'foobar@example.com', modified: SqlString.raw('NOW()') };
var sql = SqlString.format('UPDATE ?? SET ? WHERE `id` = ?', ['users', data, userId]);
console.log(sql); // UPDATE `users` SET `email` = 'foobar@example.com', `modified` = NOW() WHERE `id` = 1