我正在阅读Rebuilding Rails一书。在小ORM章节中,它使用sqlite3 gem与sqlite数据库进行通信。 my_table数据库结构
create table my_table (
id INTEGER PRIMARY KEY,
posted INTEGER,
title VARCHAR(30),
body VARCHAR(32000));
并且在.rb文件中插入my_table的代码是:
DB.execute <<-SQL
INSERT INTO #{table} (#{keys.join ","})
VALUES (#{vals.join ","});
SQL
# vals=>["1", "It happend!", "It did!"]
但它转向的sql语句将是:
"INSERT INTO my_table (posted,title,body)\n VALUES (1,It happend!,It did!);\n"
由于 “它发生了!” 和 “它确实!”
我检查文档发现Array#join返回通过将数组的每个元素转换为字符串而创建的字符串。因此,数组中的双引号元素将转换为字符串并丢失双引号。并导致sql语法错误。
如何解决这个问题?
任何帮助将不胜感激! 谢谢!
答案 0 :(得分:3)
这样的事情,你不应该做自己。不幸的是,mysql2
gem(我相信你正在使用)不支持预备语句,这就是你应该这样做的方式;但是您可以使用其他一些宝石来添加功能。
一个是mysql-cs-bind
gem,这很简单,只需将其添加到mysql2
:
client.xquery(<<-SQL, vals)
INSERT INTO #{table} (#{keys.join ","})
VALUES (#{keys.map { "?" }.join(",")});
SQL
另一个是使用像sequel
这样更通用的宝石,它可以在各种数据库中提供很多功能,而不仅仅是MySQL。
你不应该自己做的原因是因为
如果你必须亲自去做:
db.execute <<-SQL
INSERT INTO #{table} (#{keys.join ","})
VALUES (#{
vals.map { |val|
case val
when String
"'#{mysql.escape(val)}'"
when Date, Time, DateTime
"'#{val}'"
else
val
end
}.join(', ')
});
SQL
(不确定MySQL想要什么格式的日期/时间值,因此可能需要进行调整)
编辑:幸运的是SQLite3确实提供了预处理语句和占位符。
DB.execute <<-SQL, *vals
INSERT INTO #{table} (#{keys.join ","})
VALUES (#{keys.map { "?" }.join(",")});
SQL
编辑:对map
感到愚蠢。谢谢,乔丹。
答案 1 :(得分:3)
在构造SQL查询时,应避免使用字符串插值(#{...}
)。除了您正在经历的问题之外,这是打开SQL注入攻击的好方法。
相反,您应该使用参数绑定。这样,数据库本身就可以正确,安全地清理和引用您的值,这样您就不必担心了。
在你的情况下,它看起来像这样:
vals = [ "1", "It happened!", "It did!" ]
query = <<SQL
INSERT INTO my_table (posted, title, body)
VALUES (?, ?, ?);
SQL
DB.execute(query, vals)
你可以稍微缩短一点:
DB.execute <<-SQL, vals
INSERT INTO my_table (posted, title, body)
VALUES (?, ?, ?);
SQL
执行查询时,?
占位符将替换为您提供的数组中的相应值 - 已清理和引用 - 作为第二个参数。
您可能已经注意到这与您的代码不完全相同,因为我已经对表名和列名进行了硬编码。 SQLite的一个限制是你不能对像这样的标识符进行参数绑定。怎么办呢?如果您从不受信任的来源获取列名(例如来自最终用户的Web请求),因此不能仅在查询中对其进行硬编码,那么您将不得不妥协。你可以做的最好的事情可能是白名单:
column_whitelist = %w[ posted title body ]
unless keys.all? {|key| column_whitelist.include?(key) }
raise "Invalid column name '#{key}'!"
end
如果table_name
来自不受信任的来源,你也会想做类似的事情。
一旦筛选了表名和列名,就可以在查询中安全地使用它们:
column_names = keys.join(", ")
placeholders = keys.map { "?" }.join(", ")
DB.execute <<-SQL, vals
INSERT INTO #{table_name} (#{column_names})
VALUES (#{placeholders});
SQL
P.S。如果您的表或列名称包含空格,引号或任何其他特殊字符,则需要转义并引用它们。这意味着通过在前面添加另一个双引号("
变为""
)然后用双引号包围整个事物来转义任何双引号。像这样的小辅助方法就可以做到:
def escape_and_quote_identifier(str)
sprintf('"%s"', str.gsub(/"/, '""'))
end
然后你想将它应用于你的表名和列名:
table_name = escape_and_quote_identifier(table_name)
column_names = keys.map {|key| escape_and_quote_identifier(key) }
.join(", ")
placeholders = keys.map { "?" }.join(", ")
DB.execute <<-SQL, vals
INSERT INTO #{table_name} (#{column_names})
VALUES (#{placeholders});
SQL