ruby-on-rails - 使用 Postgres 转换安全地将动态列名传递到 ActiveRecord 查询中?

标签 ruby-on-rails postgresql activerecord rails-activerecord

我在我的应用程序中有很多时间没有日期查询,我想抽象一些查询。

假设我有一个带有 DateTime starts_at 字段的模型:

Shift.where('starts_at::time > ?', '20:31:00.00')
-> SELECT "shifts".* FROM "shifts" WHERE (starts_at::time > '20:31:00.00')

这会正确返回所有大于时间 20:31 的“starts_at”值。

我想将列名动态传递到查询中,所以我可以这样做:

Shift.where('? > ?', "#{column_name}::time", '20:31:00.00').
-> SELECT "shifts".* FROM "shifts" WHERE ('starts_at::time' > '20:31:00.00')

在此示例中,这不起作用,因为搜索将 starts_at::time 作为字符串执行,而不是作为具有 time 强制转换的列。

如何安全地将 column_name 传递到带有 ::time 转换的查询中?虽然这不会接受用户输入,但我仍然想确保考虑到 SQL 注入(inject)。

最佳答案

这比您一开始想象的要复杂,因为标识符(列名、表名...)和值('pancakes'6、. ..) 在 SQL 中是非常不同的事物,它们具有不同的引用规则甚至引号字符(字符串的单引号,标准 SQL 中标识符的双引号,MySQL 中标识符的反引号,SQL-Server 中标识符的括号,......) .如果您想到像 Ruby 变量名称和值这样的标识符,嗯,文字 Ruby 值,那么您就会开始看到它们之间的区别。

当你这样说时:

where('? > ?', ...)

两个占位符都将被视为值(而不是标识符)并被引用。为什么是这样? ActiveRecord 无法知道哪个 ? 应该是标识符(例如 created_at 列名),哪个应该是值(例如 20:31: 00.00).

虽然数据库连接确实有一个专门用于引用列名的方法:

> puts ActiveRecord::Base.connection.quote_column_name('pancakes')
"pancakes"
=> nil

所以你可以这样说:

quoted_column = Shift.connection.quote_column_name(column_name)
Shift.where("#{quoted_name}::time > ?", '20:31:00.00')

这有点不愉快,因为我们对使用字符串插值来构建 SQL 退缩(或者至少我们应该退缩)。但是,quote_column_name 会处理 column_name 中的任何不安全或不安全的事情,因此这实际上并不危险。

你也可以说:

quoted_column = "#{Shift.connection.quote_column_name(column_name)}::time"
Shift.where("#{quoted_name} > ?", '20:31:00.00')

如果您并不总是需要将列名转换为时间。甚至:

clause = "#{Shift.connection.quote_column_name(column_name)}::time > ?"
Shift.where(clause, '20:31:00.00')

你也可以使用 extract or one of the other date/time functions而不是类型转换,但您仍然会遇到引用问题和有点令人畏惧的 quote_column_name 调用。

另一种选择是将 column_name 列入白名单,这样只允许特定的有效值。然后您可以将安全的 column_name 直接放入查询中:

if(!in_the_whitelist(column_name))
  # Throw a tantrum, hissy fit, or complain in your preferred fashion
end
Shift.where("#{column_name} > ?", '20:31:00.00')

只要您没有像 "gotta have some breakfast" 之类的时髦列名称或始终需要正确引用的类似名称,这应该没问题。您甚至可以使用 Shift.column_namesShift.columns 来构建您的白名单。

同时使用白名单和 quote_column_name 可能是最安全的,但 quote_column_name 方法应该足够了。

关于ruby-on-rails - 使用 Postgres 转换安全地将动态列名传递到 ActiveRecord 查询中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39734301/

相关文章:

ruby-on-rails - 输入表单字段的 ActiveAdmin 自定义值

javascript - 将 Rails Ruby 变量传递给 Javascript

ruby-on-rails - ActiveRecord :Module 的未定义方法 `belongs_to'

ruby - 正确的设计 : Including module in ActiveRecord or helper method

ruby-on-rails - ActionMailer 在 Rails 4.2 中使用哪个 ActiveJob 队列?

ruby-on-rails - 如何调试卡在数据库事务中的 ruby​​ 进程?

json - Grails 在第二个数据库上运行查询 postgresql 以生成原始 JSON

ruby-on-rails - Rails 和 ActiveRecord:将方法附加到从 ActiveRecord::Base 继承的模型

ruby-on-rails - Rails 应用程序无法连接到 PostgreSQL

sql - 创建表时的 Postgres 格式