一开始你可能会认为这比想象的要复杂得多,因为在SQL中标识符(列名、表名等)和值('pancakes'
、6
等)是非常不同的东西,有不同的引号规则甚至引号字符(在标准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("
这有点不太好,因为我们会感到不舒服(或者至少应该会),使用字符串插值构建SQL。但是,quote_column_name
将处理 column_name
中任何可疑或不安全的内容,所以实际上并不危险。
你也可以这样说:
quoted_column = "#{Shift.connection.quote_column_name(column_name)}::time"
Shift.where("
如果您不需要始终将列名转换为 time
。或者甚至:
clause = "#{Shift.connection.quote_column_name(column_name)}::time > ?"
Shift.where(clause, '20:31:00.00')
你也可以使用
extract
或其他日期/时间函数 来代替类型转换,但你仍将面临引号问题和有点尴尬的
quote_column_name
调用。
另一个选择是将白名单设置为
column_name
,以便只允许特定有效值。然后,你可以将安全的
column_name
直接插入查询中:
if(!in_the_whitelist(column_name))
end
Shift.where("#{column_name} > ?", '20:31:00.00')
只要您没有任何奇怪的列名,例如"gotta have some breakfast"
或类似总是需要正确引用的内容,那么这应该是可以的。 您甚至可以使用Shift.column_names
或Shift.columns
来构建白名单。
同时使用白名单和quote_column_name
可能是最安全的方法,但quote_column_name
方法应该已经足够了。
query_text = "start_at > ?"
,然后使用Shift.where(query_text, '20:31:00.00')
吗? - inyecolumn_name
是'start_at'
,理论上列名可能会以'pancakes house'
的形式出现,因此需要在发送到数据库之前进行引用/转义。 - mu is too shortcolumn_name
时,也许你可以使用一些if else
来分配query_text
。 - inye