我正在尝试将我的代码从ActiveRecord 3升级到ActiveRecord 4,我相信我在ActiveRecord + SQLite3的布尔查询支持中遇到了错误/回归。
以下是运行ActiveRecord 4.0.2的IRB会话的输出,其中SQLite3是数据库后端:
2.0.0p353 :040 > StoreItem.where(item_class: 1, enabled: 1).order(item_order: :desc).count
=> 4
2.0.0p353 :041 > StoreItem.where(item_class: 1, enabled: true).order(item_order: :desc).count
=> 0
作为比较点,当Mysql 5.5是数据库后端时,这是相同的输出:
2.0.0p353 :005 > StoreItem.where(item_class: 1, enabled: 1).order(item_order: :desc).count
=> 4
2.0.0p353 :006 > StoreItem.where(item_class: 1, enabled: true).order(item_order: :desc).count
=> 4
现在,让我们看看使用AR 3.2.14运行时会发生什么:
SQLite3的:
2.0.0p353 :005 > StoreItem.where(item_class: 1, enabled: 1).order(item_order: :desc).count
=> 0
2.0.0p353 :006 > StoreItem.where(item_class: 1, enabled: true).order(item_order: :desc).count
=> 4
Mysql 5.5:
2.0.0p353 :001 > StoreItem.where(item_class: 1, enabled: 1).order(item_order: :desc).count
=> 4
2.0.0p353 :002 > StoreItem.where(item_class: 1, enabled: true).order(item_order: :desc).count
=> 4
正如您所看到的,当出现布尔查询时,ActiveRecord 3.2.14和4.0.2在SQLite3中完全相反。
我刚检查了实际生成的SQL,它是完全相同的。第一个查询如下所示:
SELECT COUNT(*) FROM "store_items" WHERE "store_items"."item_class" = 1 AND "store_items"."enabled" = 1
第二个看起来像这样:
SELECT COUNT(*) FROM "store_items" WHERE "store_items"."item_class" = 1 AND "store_items"."enabled" = 't'
因此,在处理布尔列值时,SQLite3可能从1.3.5变为1.3.8?
这是一个已知的错误,任何人都可以评论原因吗?
答案 0 :(得分:2)
我调试了ActiveRecord 3.2.14和4.0.2的内容。这是从头到尾的错误:
SQLite允许您插入任意字符串/数字作为布尔列的列值。因此,您可以为布尔列值插入1或't'。
当与SQLite交互时,ActiveRecord将布尔值映射到字符串类型't'或'f',而不是1或0.这个决定背后的原因可能来自Postgres,但现在就是这样。
当ActiveRecord第一次在3.2.14 at line 365 in persistence.rb中创建记录时,它会创建所有模型字段的映射并插入所有字段,无论它们是否已被更改。这是创建方法:
def create
attributes_values = arel_attributes_values(!id.nil?)
new_id = self.class.unscoped.insert attributes_values
self.id ||= new_id if self.class.primary_key
IdentityMap.add(self) if IdentityMap.enabled?
@new_record = false
id
end
这会导致ActiveRecord生成如下所示的insert语句(注意是否存在enabled,我们的布尔列):
INSERT INTO "store_items" ("created_at", "enabled", "other_columns....") VALUES (?, ?, ?) [["created_at", 2014-01-11 21:47:24 UTC], ["enabled", true], ["other_colummns", ...]]
在ActiveRecord 4.0.2中,文件dirty.rb, line 78现在调用persistence.rb (at line 507)的create_record方法。创建记录如下所示:
def create_record(attribute_names = @attributes.keys)
attributes_values = arel_attributes_with_values_for_create(attribute_names)
new_id = self.class.unscoped.insert attributes_values
self.id ||= new_id if self.class.primary_key
@new_record = false
id
end
因为create_record现在接受一个只列出已更改的列的参数,所以它会生成一个insert语句,该语句不包含默认值与您插入的列匹配的列。因此,使用与默认值匹配的布尔值的insert语句将如下所示:
INSERT INTO "store_items" ("created_at", "other_columns....") VALUES (?, ?, ?) [["created_at", 2014-01-11 21:47:24 UTC], ["other_colummns", ...]]
请注意,缺少布尔列“enabled”,因为在这种情况下,我们的默认值true / 1与我们第一次创建记录时插入的内容相匹配。
因为ActiveRecord生成的insert语句没有指定enabled,所以SQLite给它的值为1,而不是't'的值,这是ActiveRecord 3.1.14用来给它的。
最终,要解决此错误,请不要在布尔列上包含默认值,或者确保将其更改为非默认值,以强制ActiveRecord将其实际设置为“t”或“f” '创造价值。
因此,改变这个:
class CreateStoreItems < ActiveRecord::Migration
def change
create_table :store_items do |t|
t.boolean :enabled, :null => false, :default => 1
end
end
end
到这个
class CreateStoreItems < ActiveRecord::Migration
def change
create_table :store_items do |t|
t.boolean :enabled, :null => false
end
end
end
或者,如果您'旋转'布尔值而不是依赖默认值,您也可以更正错误。
答案 1 :(得分:0)
ActiveRecord与SQLite的布尔值有混淆的历史。你可以在这里阅读Rails3中的一些问题:
执行摘要是:
't'
和'f'
字符串作为布尔文字。这些实际上是PostgreSQL的本地boolean
类型的字符串表示,SQLite驱动程序似乎意外地从基本驱动程序继承它们,这可能是为PostgreSQL编写的。这意味着,假设enabled
是一个布尔列,enabled: 1
和enabled: true
在MySQL中应该是相同的,但这是代码中的一个错误,它会意外地假设有关底层数据库的内容行为。在Rails3中,enabled: 1
和enabled: true
与SQLite完全不同,无论您使用哪种ActiveRecord版本,它们在PostgreSQL中都是不同的。
在ActiveRecord的Rails4版本中,我猜测(不,我没有追踪执行),有时会在quote
with this kludgery处理SQLite布尔混淆:
case value
#...
when true, false
if column && column.type == :integer
value ? '1' : '0'
else
value ? quoted_true : quoted_false
end
所以如果你发送一个真正的Ruby布尔值,它应该被克服到数据库的正确值,但如果你发送一个整数,所有的赌注都是关闭的。类似的废话出现在type_cast
。
我认为你还有一些工作要做:
1
和true
是相同的,0
和false
也是如此。如果要查询数据库中的布尔值,请使用true
和false
。't'
和'f'
来匹配SQLite驱动程序的混淆。您还需要检查MySQL中所有布尔列的值,以确保,MySQL还可以快速执行规则,因此它可以在您的背后做出奇怪的事情。 MySQL并不像SQLite那么快,但它们都会以友好性的名义弯曲规则。我倾向于认为如果你给它boolean_column: 1
或where('c in (?)', empty_array)
这样的话,ActiveRecord应该抛出异常;告诉我,我是一个白痴会友好,帮助我悄悄弄乱一些东西不是。