所以说我有一个带有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作为字符串执行,而不是作为具有时间转换的列.
如何安全地将column_name传入带有:: time cast的查询?虽然这不会接受用户输入,但我仍然希望确保考虑sql注入.
解决方法
当你这样说时:
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')
这应该没问题,只要你没有任何时髦的专栏名称,如“必须吃早餐”或类似的东西,总是需要正确引用.您甚至可以使用Shift.column_names或Shift.columns来构建白名单.
使用白名单和quote_column_name可能是最安全的,但quote_column_name方法应该足够了.