使用Postgres强制转换将动态列名安全地传递给ActiveRecord查询?

chr*_*son 12 postgresql activerecord ruby-on-rails 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')
Run Code Online (Sandbox Code Playgroud)

这正确地返回大于时间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')
Run Code Online (Sandbox Code Playgroud)

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

如何使用强制column_name转换安全地传入查询::time?虽然这不会接受用户输入,但我仍然希望确保考虑SQL注入.

mu *_*ort 20

这是更复杂的比你想象的,在第一,因为标识符(列名,表名,...)和价值('pancakes',6,...),在SQL非常不同的东西有不同的引用规则,甚至引号字符(单引号对于字符串,标准SQL中标识符的双引号,MySQL中标识符的反引号,SQL-Server中标识符的括号,......).如果您考虑像Ruby变量名称这样的标识符,以及类似的文字Ruby值,那么您可以开始看到它们之间的区别.

当你这样说:

where('? > ?', ...)
Run Code Online (Sandbox Code Playgroud)

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

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

> puts ActiveRecord::Base.connection.quote_column_name('pancakes')
"pancakes"
=> nil
Run Code Online (Sandbox Code Playgroud)

所以你可以这样说:

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

这有点令人不快,因为我们在使用字符串插值来构建SQL时会退缩(或至少我们应该).但是,quote_column_name会照顾任何狡猾或不安全的东西,column_name所以这实际上并不危险.

你也可以说:

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

如果你不总是需要将列名转换为a time.甚至:

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

您也可以使用extract使用其他日期/时间函数而不是类型转换,但您仍然会留下引用问题和有点畏缩的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')
Run Code Online (Sandbox Code Playgroud)

只要您没有任何时髦的列名"gotta have some breakfast"或类似的东西总是需要正确引用,这应该没问题.您甚至可以使用Shift.column_namesShift.columns构建白名单.

使用白名单然后quote_column_name可能是最安全的,但该quote_column_name方法应该足够了.

  • 这就是我最终做的事情.对于插值来说,我感觉有点不对劲,即使它是安全的,但最好的解决方案! (2认同)