-
Notifications
You must be signed in to change notification settings - Fork 21.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Always use SQL limit in Relation#last when limit argument given #16400
Conversation
Could you add a test to specifically check against full relation being loaded in memory? This way the limit won't be just considered an implementation detail that could regress in future. |
@egilburg added an assertion that relation wasn't loaded during after |
to_a.last(limit) | ||
end | ||
result = order_values.empty? && primary_key ? | ||
order(arel_table[primary_key].desc) : reverse_order |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we avoid the ternary here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
d1efac8
to
28f5d8b
Compare
assert_equal 1, query.length | ||
assert_no_match(/LIMIT/, query.first) | ||
assert !relation.loaded? | ||
assert_match(/LIMIT/, query.first) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this assertion, if the intent is to make sure that we don't load the entire relation into memory
So this will break the following case: ActiveRecord::Schema.define do
create_table :foos, primary_key: false do |t|
t.string :name
end
end
class Foo < ActiveRecord::Base
end
10.times { |n| Foo.create!(name: n) }
Foo.last(5) If there's no primary key, and no existing order, we need to load the entire set into memory. |
28f5d8b
to
ccad4b2
Compare
@sgrif added a test and fixed the case you've described also removed unneeded assertion |
instead of loading relation
Always use SQL limit in Relation#last when limit argument given
@bogdan was this supposed to warn if the code is using a relation incorrectly? We have something like this in our app:
and there was no deprecation warning telling me to add |
can not be reversed is deprecated. | ||
Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case. | ||
Please call `to_a.last` if you still want to load the relation. | ||
WARNING |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also print the exception message so people know what they'd see in the future?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see a lot of benefit in that. Can you explain the use case when it is useful?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Saw you raised IrreversibleOrderError
with two different messages in #18928. At least the first one gives you more info than this warning does alone.
On the other hand, you do specify that people should just add to_a
which I guess would be the fix anyway. So I'll backtrack: this is fine as is 👍
@eileencodes I don't understand why you think you should make a change. |
I thought I should make a change because something is not working correctly. We're getting the wrong record for last in our app. I'm going to investigate more today. It doesn't seem there are any tests that test the last record is the expected last record so it's difficult to track down correct behavior for all cases of For our case we have order values so we are moving into |
I take back there being no tests that test the last is the correct last record (https://github.com/rails/rails/blob/master/activerecord/test/cases/relations_test.rb), but I'm unsure why changing |
@eileencodes thanks for spotting out that. Can you also check if |
I don't know what I was thinking when I said
require 'active_record'
require 'minitest/autorun'
ActiveRecord::Base.logger = ActiveSupport::Logger.new($stdout)
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.connection.instance_eval do
create_table "comments", force: true do |t|
t.string "content"
t.integer "post_id"
end
create_table "posts", force: true do |t|
t.string "name"
end
end
class Comment < ActiveRecord::Base
belongs_to :post
end
class Post < ActiveRecord::Base
has_many :comments
end
class BugTest < Minitest::Test
def test_last
(1..5).each do |n|
Post.create!(name: "this is a post #{n}")
end
Post.all.each do |post|
(1..5).each do |i|
x = i == 1 ? 1 : 4
Comment.create!(content: "test content #{i}", post_id: post.id)
end
end
post = Post.find(1)
comments = post.comments
# assert order
assert_equal ["test content 5", "test content 4", "test content 3"], comments.order(id: :desc).limit(3).map(&:content)
# fail
assert_equal "test content 3", comments.order(id: :desc).limit(3).last.content
# pass
assert_equal "test content 3", comments.order(id: :desc).limit(3).to_a.last.content
end
end |
Probably, the following will fix the issue: result = result.limit!(limit || 1).to_a |
I'd go with |
I've temporarily reverted this, as we are looking to release beta2 and there's still concerns with #23377 that need to be addressed. Once the issues with this have been addressed, we can bring this back in for RC1. |
This reverts commit 9f3730a, reversing changes made to 2637fb7. There are additional issues with this commit that need to be addressed before this change is ready (see #23377). This is a temporary revert in order for us to have more time to address the issues with that PR, without blocking the release of beta2.
Using
Relation#last
causes entire relation to be loaded into memory.In most cases it is unacceptable:
We can still use
reverse_order
to let SQL do heavy lifting (AR is quite bad at that).Unfortunately back to 2011 in this commit 5f5527c it wasn't done. And 2 x unfortunately this commit added 2 tests that need to be modified now.
@dmathieu do you still remember something about it?