为什么Rails使用带有范围的where子句哈希语法向查询添加“OR 1 = 0”?

我正在研究的项目是在RDS上使用MySQL(特别是mysql2 gem)。

当我使用包含where语句中的范围的条件的哈希时,我的查询有点奇怪。

 User.where(id: [1..5]) 

 User.where(id: [1...5]) 

分别导致以下查询:

 SELECT `users`.* FROM `users` WHERE ((`users`.`id` BETWEEN 1 AND 5 OR 1=0)) SELECT `users`.* FROM `users` WHERE ((`users`.`id` >= 1 AND `users`.`id` < 5 OR 1=0)) 

查询工作完全正常,因为OR FALSE实际上是无操作。 我只是想知道为什么Rails或ARel将这个片段添加到查询中。

编辑

看起来可以解释这一点的行是ActiveRecord::PredicateBuilder第26行 。 仍然不知道哈希是如何empty? 在那一点,但也许别人做。

编辑2

这是有趣的。 我正在调查菲利普的评论,看看为什么他这样做,因为它似乎就像澄清一样,但他是正确的1..5 != [1..5] 。 前者是1到5的包含范围,后者是第一个元素前者的数组。 我尝试将这些放入ARel where调用查看生成的SQL并且OR 1=0不存在!

 User.where(id: 1..5) #=> SELECT "users".* FROM "users" WHERE ("users"."id" BETWEEN 1 AND 5) User.where(id: 1...5) #=> SELECT "users".* FROM "users" WHERE ("users"."id" >= 1 AND "users"."id" < 5) 

虽然我仍然不知道为什么 ARel会添加OR 1=0 ,这将永远是假的,看似没必要。 这可能是由于Array s和Range s的处理方式不同。

基于你发现的事实, [1..5]不是指定范围的正确方法……我已经发现为什么[1..5]表现得像它一样。 为此,我首先发现散列条件中的空数组产生1=0 SQL条件:

 User.where(id: []).to_sql # => "SELECT \"users\".* FROM \"users\" WHERE 1=0" 

并且,如果检查ActiveRecord :: PredicateBuilder :: ArrayHandler代码 ,您将看到数组值始终被分区为范围和其他值。

 ranges, values = values.partition { |v| v.is_a?(Range) } 

这解释了在使用非范围值时您没有看到1=0 。 也就是说,从数组中获得1=0而不包括范围的唯一方法是提供一个空数组,产生1=0条件,如上所示。 当所有数组中都有一个范围时,你将得到范围条件( ranges ),并且分别执行一个空数组条件( values )。 我的猜测是没有一个很好的理由…它只是更容易让这个比避免它(因为结果集是相同的两种方式)。 如果分区代码更聪明一点,那么就不必添加额外的空values数组,并且可以跳过1=0条件。

至于1=0来自哪里……我认为这来自数据库适配器,但我找不到确切的位置。 但是,我会称之为尝试找不到记录。 换句话说, WHERE 1=0永远不会返回任何用户,这对于像WHERE id=null这样的替代SQL是有意义的,它会找到id为null的任何用户(意识到这不是真正正确的SQL语法) 。 这是我在尝试查找id为空集的所有用户时所期望的(即我们不要求nil id或null id或其他)。 所以,在我的脑海里,把一个关于1=0确切位置来自黑盒子就可以了。 至少我们现在可以推断为什么数组内部的范围导致它出现!

UPDATE

我还发现,即使直接使用ARel,你仍然可以获得1=0

 User.arel_table[:id].in([]).to_sql # => "1=0" 

严格来说这是猜测,因为我在自己的项目中做了类似的事情(尽管我使用的是AND 1 )。

无论出于何种原因,在生成查询时,总是有一个包含无操作的WHERE子句比有条件地生成WHERE子句更容易。 也就是说,如果您不包含任何部分,它将最终生成仍然有效的内容。

另一方面,我不确定为什么它采取这种forms:当我这样做时,我使用1 [...]它允许任意链接,但我不知道你是怎么做的重新看到会允许它。 尽管如此,我仍然认为它可能是算法代码生成方案的结果。

检查您是否使用active_record-acts_as。 那是我的问题。

将以下行添加到您的Gemfile:

 gem 'active_record-acts_as', :git => 'https://github.com/hzamani/active_record-acts_as.git' 

这将只是拉出有希望修复的gem的最新版本。 为我工作。

我觉得你亲眼看到ruby的副作用。

我认为做你正在做的事情的更好方法就是

 2.0.0-p481@meri :008 > [*1..5] => [1, 2, 3, 4, 5] User.where(id: [*1..5]).to_sql "SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 2, 3, 4, 5)" 

因为这会创建一个Array vs一个Array,其元素1是Range类。

要么

使用显式Range来触发AREL中的BETWEEN。

 # with end element, ie exclude_end=false 2.0.0-p481@meri :013 > User.where(id: Range.new(1,5)).to_sql => "SELECT `users`.* FROM `users` WHERE (`users`.`id` BETWEEN 1 AND 5)" # without end element, ie exclude_end=true 2.0.0-p481@meri :022 > User.where(id: Range.new(1, 5, true)).to_sql => "SELECT `users`.* FROM `users` WHERE (`users`.`id` >= 1 AND `users`.`id` < 5)" 

如果你关心控制你生成的查询以及SQL语言和数据库function的全部function,那么我建议从ActiveRecord / Arel转到Sequel。

我可以诚实地说,使用ActiveRecord会有更多的怪癖和激动人心的时刻,特别是当你超越简单的查询问题时。 当你开始尝试在愤怒中查询数据时,可能需要在这里和那里加入一些连接表,并意识到你确实需要连接条件或联合所有类型的查询。

它在查询生成和结果处理方面也显着更快,更可靠,并且更容易组合您想要的查询。 它还有真实的文档,你可以实际阅读不像arel。

我只是希望我早点发现它而不是坚持使用rails默认数据访问层。