1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Merge pull request #18928 from bogdan/unreversable-order

Raise AR::IrreversibleOrderError when #reverse_order can not do it's job
This commit is contained in:
Sean Griffin 2016-01-27 23:26:34 -05:00
commit 74bee8353b
4 changed files with 69 additions and 2 deletions

View file

@ -1,3 +1,17 @@
* `ActiveRecord::Relation#reverse_order` throws `ActiveRecord::IrreversibleOrderError`
when the order can not be reversed using current trivial algorithm.
Also raises the same error when `#reverse_order` is called on
relation without any order and table has no primary key:
Topic.order("concat(author_name, title)").reverse_order
# Before: SELECT `topics`.* FROM `topics` ORDER BY concat(author_name DESC, title) DESC
# After: raises ActiveRecord::IrreversibleOrderError
Edge.all.reverse_order
# Before: SELECT `edges`.* FROM `edges` ORDER BY `edges`.`` DESC
# After: raises ActiveRecord::IrreversibleOrderError
*Bogdan Gusiev*
* Improve schema_migrations insertion performance by inserting all versions
in one INSERT SQL.

View file

@ -275,4 +275,9 @@ module ActiveRecord
# The mysql2 and postgresql adapters support setting the transaction isolation level.
class TransactionIsolationError < ActiveRecordError
end
# IrreversibleOrderError is raised when a relation's order is too complex for
# +reverse_order+ to automatically reverse.
class IrreversibleOrderError < ActiveRecordError
end
end

View file

@ -1104,14 +1104,21 @@ module ActiveRecord
end
def reverse_sql_order(order_query)
order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
if order_query.empty?
return [table[primary_key].desc] if primary_key
raise IrreversibleOrderError,
"Relation has no current order and table has no primary key to be used as default order"
end
order_query.flat_map do |o|
case o
when Arel::Nodes::Ordering
o.reverse
when String
o.to_s.split(',').map! do |s|
if does_not_support_reverse?(o)
raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically"
end
o.split(',').map! do |s|
s.strip!
s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
end
@ -1121,6 +1128,13 @@ module ActiveRecord
end
end
def does_not_support_reverse?(order)
#uses sql function with multiple arguments
order =~ /\([^()]*,[^()]*\)/ ||
# uses "nulls first" like construction
order =~ /nulls (first|last)\Z/i
end
def build_order(arel)
orders = order_values.uniq
orders.reject!(&:blank?)

View file

@ -19,6 +19,7 @@ require 'models/aircraft'
require "models/possession"
require "models/reader"
require "models/categorization"
require "models/edge"
class RelationTest < ActiveRecord::TestCase
fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
@ -223,6 +224,39 @@ class RelationTest < ActiveRecord::TestCase
assert_equal topics(:fifth).title, topics.first.title
end
def test_reverse_order_with_function
topics = Topic.order("length(title)").reverse_order
assert_equal topics(:second).title, topics.first.title
end
def test_reverse_order_with_function_other_predicates
topics = Topic.order("author_name, length(title), id").reverse_order
assert_equal topics(:second).title, topics.first.title
topics = Topic.order("length(author_name), id, length(title)").reverse_order
assert_equal topics(:fifth).title, topics.first.title
end
def test_reverse_order_with_multiargument_function
assert_raises(ActiveRecord::IrreversibleOrderError) do
Topic.order("concat(author_name, title)").reverse_order
end
end
def test_reverse_order_with_nulls_first_or_last
assert_raises(ActiveRecord::IrreversibleOrderError) do
Topic.order("title NULLS FIRST").reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
Topic.order("title nulls last").reverse_order
end
end
def test_default_reverse_order_on_table_without_primary_key
assert_raises(ActiveRecord::IrreversibleOrderError) do
Edge.all.reverse_order
end
end
def test_order_with_hash_and_symbol_generates_the_same_sql
assert_equal Topic.order(:id).to_sql, Topic.order(:id => :asc).to_sql
end