This project is archived and is in readonly mode.
Queries using limits and punctuation in order clauses generate invalid SQL
Reported by Adam Keys | May 14th, 2010 @ 10:34 PM | in 3.0.2
If one creates a query that uses both limits and order clauses that contain punctuation, column aliases are put in the wrong place and an invalid query is generated.
For example:
# Works
Trip.includes(:spots).offset(0).limit(20).order("LEAST(COS(1)*COS(-1)*COS(RADIANS(spots.lat)))").all
# Fails (note the comma)
Trip.includes(:spots).offset(0).limit(20).order("LEAST(1,COS(1)*COS(-1)*COS(RADIANS(spots.lat)))").all
The correct query looks like this:
SELECT * FROM (SELECT DISTINCT ON ("trips".id) "trips".id, LEAST(COS(1)*COS(-1)*COS(RADIANS(spots.lat))) AS alias_0 FROM "trips" LEFT OUTER JOIN "waypoints" ON "trips"."id" = "waypoints"."trip_id" LEFT OUTER JOIN "spots" ON "spots"."id" = "waypoints"."spot_id") AS id_list ORDER BY id_list.alias_0 LIMIT 20 OFFSET 0
The incorrect query ends up like this (note the placement of the
first AS
):
SELECT * FROM (SELECT DISTINCT ON ("trips".id) "trips".id, LEAST(1 AS alias_0, COS(1)*COS(-1)*COS(RADIANS(spots.lat))) AS alias_1 FROM "trips" LEFT OUTER JOIN "waypoints" ON "trips"."id" = "waypoints"."trip_id" LEFT OUTER JOIN "spots" ON "spots"."id" = "waypoints"."spot_id") AS id_list ORDER BY id_list.alias_0 , id_list.alias_1 LIMIT 20 OFFSET 0
If the limit
is removed, the query is generated
properly, regardless of order
clause.
This script shows a couple working order clauses and one that doesn't.
logger = Logger.new(STDOUT)
ActiveRecord::Base.logger = logger
ActiveRecord::Base.colorize_logging = false
working = ["LEAST(COS(1)*COS(-1)*COS(RADIANS(spots.lat)))",
"LEAST(1,COS(1)*COS(-1)*COS(RADIANS(lat)))"]
not_working = ["LEAST(1,COS(1)*COS(-1)*COS(RADIANS(spots.lat)))"]
(working + not_working).each do |order|
begin
# Spots can belong to multiple trips. Each spot has a lat/lng pair.
Trip.includes(:spots).offset(0).limit(20).order(order).all
logger.info "Good: #{order}"
rescue => e
logger.info "Bad: #{order}"
logger.debug e
end
end
The output of said script is attached.
Comments and changes to this ticket
-
Adam Keys May 15th, 2010 @ 04:02 PM
Sure. Attached is a patch to add the test case and the result I get when run against PostgreSQL. I ran this against a recent clone of master.
-
Santiago Pastorino May 15th, 2010 @ 08:11 PM
- State changed from new to invalid
Adam Keys the wrong thing here is that you are doing ...
Trip.includes(:spots).offset(0).limit(20).order("LEAST(COS(1)COS(-1)COS(RADIANS(spots.lat)))").all
and expecting the order method to add things on the select part of the query
SELECT * FROM (SELECT DISTINCT ON ("trips".id) "trips".id, LEAST(COS(1)COS(-1)COS(RADIANS(spots.lat))) AS alias_0 FROM "trips" LEFT OUTER JOIN "waypoints" ON "trips"."id" = "waypoints"."trip_id" LEFT OUTER JOIN "spots" ON "spots"."id" = "waypoints"."spot_id") AS id_list ORDER BY id_list.alias_0 LIMIT 20 OFFSET 0
-
Santiago Pastorino May 15th, 2010 @ 08:12 PM
Perhaps you misspeled order and tried to do another thing, please fix it if this is the case
-
Santiago Pastorino May 15th, 2010 @ 08:41 PM
- Tag set to bugmash
-
Adam Keys May 15th, 2010 @ 08:49 PM
The reason I believe this is a bug is that it works when there is no
limit
clause. Removing it produces this valid query:SELECT "tags"."id" AS t0_r0, "tags"."name" AS t0_r1, "tags"."taggings_count" AS t0_r2, "taggings"."id" AS t1_r0, "taggings"."tag_id" AS t1_r1, "taggings"."super_tag_id" AS t1_r2, "taggings"."taggable_type" AS t1_r3, "taggings"."taggable_id" AS t1_r4 FROM "tags" LEFT OUTER JOIN "taggings" ON "taggings"."tag_id" = "tags"."id" ORDER BY LEAST(1,COS(1)*COS(-1)*COS(RADIANS(taggings.taggings_count)))
The query I have in the test case isn't exactly right for the test schema, but I'm certain that generating this bit of SQL is not correct:
SELECT DISTINCT ON ("trips".id) "trips".id, LEAST(1 AS alias_0,
The alias inside the function call is how this bug manifests itself.
-
Adam Keys May 15th, 2010 @ 09:09 PM
Here's a better testcase. Remove the limit and the query is fine but the assert fails. Leave it in and the query is invalid.
-
Adam Keys May 18th, 2010 @ 03:54 PM
- Assigned user set to Santiago Pastorino
Santiago, could you please review the most recent testcase attached above and clear the invalid flag on this ticket? I believe this is a bonafide bug.
-
José Valim May 20th, 2010 @ 10:14 PM
- State changed from invalid to open
-
Santiago Pastorino May 21st, 2010 @ 04:53 AM
- Milestone cleared.
- Tag changed from bugmash to activerecord, arel, limit, order, patch, postgresql
First of all sorry for closing the ticket, i hadn't understood it really well.
The issue here is .order(str) and str containing comma inside of expressions when used with limit
as in order("LEAST(1,COS(1)COS(-1)COS(RADIANS(spots.lat)))").limit(1)this comma ^^^
I propose to deprecate/remove the following use order("column1, column2") in favor of order(["column1", "column2"]) which is also a cleaner usage.
If not trying to deal with both cases order("column1, column2") and order("LEAST(1,COS(1)COS(-1)COS(RADIANS(spots.lat)))") the split by comma done in activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb:853 couldn't be and we should manage with a StringScanner taking care about things like ((,)),((,)) in we only want to split by the middle comma.With this API change accepted we have to do a thsi minimal patch for Arel see here http://github.com/spastorino/arel/commit/6e4b421ac394a4c18d8d35d6a6...
And apply this patch on master.
-
Santiago Pastorino May 21st, 2010 @ 11:44 AM
The previous patch makes SQLite fail because it doesn't support LEAST function.
This is the right one. -
Adam Keys May 21st, 2010 @ 08:59 PM
I applied the patch above and got one failure when I ran the PostgreSQL relation tests:
1) Error: test_finding_with_complex_order_and_limit(RelationTest): ActiveRecord::StatementInvalid: PGError: ERROR: column id_list.alias_1 does not exist LINE 1: ...tags"."id") AS id_list ORDER BY id_list.alias_0 , id_list.al... ^ : SELECT * FROM (SELECT DISTINCT ON ("tags".id) "tags".id, LEAST(1,COS(1)*COS(-1)*COS(RADIANS(taggings.super_tag_id))) AS alias_0 FROM "tags" LEFT OUTER JOIN "taggings" ON "taggings"."tag_id" = "tags"."id") AS id_list ORDER BY id_list.alias_0 , id_list.alias_1 LIMIT 1 /Users/adam/Desktop/In/rails/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb:210:in `log' /Users/adam/Desktop/In/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb:464:in `execute_without_query_record' ./test/cases/helper.rb:40:in `execute' /Users/adam/Desktop/In/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb:954:in `select_raw' /Users/adam/Desktop/In/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb:941:in `select' /Users/adam/Desktop/In/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb:7:in `select_all' /Users/adam/Desktop/In/rails/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb:56:in `select_all' /Users/adam/Desktop/In/rails/activerecord/lib/active_record/base.rb:431:in `find_by_sql' /Users/adam/Desktop/In/rails/activerecord/lib/active_record/relation.rb:64:in `to_a' /Users/adam/Desktop/In/rails/activerecord/lib/active_record/relation.rb:12:in `collect' /Users/adam/Desktop/In/rails/activerecord/lib/active_record/relation/finder_methods.rb:221:in `construct_limited_ids_condition' /Users/adam/Desktop/In/rails/activerecord/lib/active_record/relation/finder_methods.rb:208:in `apply_join_dependency' /Users/adam/Desktop/In/rails/activerecord/lib/active_record/relation/finder_methods.rb:197:in `construct_relation_for_association_find' /Users/adam/Desktop/In/rails/activerecord/lib/active_record/relation/finder_methods.rb:182:in `find_with_associations' /Users/adam/Desktop/In/rails/activerecord/lib/active_record/relation.rb:64:in `to_a' ./test/cases/relations_test.rb:102:in `test_finding_with_complex_order_and_limit' /Users/adam/Desktop/In/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:67:in `__send__' /Users/adam/Desktop/In/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:67:in `run' /Users/adam/Desktop/In/rails/activesupport/lib/active_support/callbacks.rb:414:in `_run_setup_callbacks' /Users/adam/Desktop/In/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:65:in `run'
Will give it a look tomorrow.
-
Santiago Pastorino May 22nd, 2010 @ 12:45 AM
- Assigned user changed from Santiago Pastorino to Jeremy Kemper
-
Adam Keys May 23rd, 2010 @ 12:29 AM
+1
I switched my bundle to use your fork of arel and, with your patch, everything is passing.
Thanks!
-
Santiago Pastorino May 23rd, 2010 @ 02:01 AM
I've thought on other approaches less obtrusives than this, but need to talk to Jeremy Kemper about this.
-
Jeremy Kemper May 23rd, 2010 @ 09:20 PM
Passing an array makes sense. What about deprecation in 2.3.7?
-
Santiago Pastorino August 3rd, 2010 @ 04:44 PM
- Assigned user changed from Jeremy Kemper to Santiago Pastorino
-
Santiago Pastorino November 4th, 2010 @ 10:08 PM
- Assigned user changed from Santiago Pastorino to Aaron Patterson
-
Aaron Patterson November 4th, 2010 @ 10:27 PM
@Adam can you try this again against 3-0-stable with ARel 2.0.0? I think this is fixed, but I don't know that the number of return rows is correct.
By "fixed", I mean valid SQL is returned.
-
Aaron Patterson November 4th, 2010 @ 10:27 PM
- State changed from open to needs-more-info
-
Santiago Pastorino November 4th, 2010 @ 11:28 PM
- State changed from needs-more-info to open
I've tried again and is not working. The problem is in the distinct method of postgresql.
We are going to try some things with Aaron and get back to you. -
Repository November 5th, 2010 @ 06:29 PM
- State changed from open to committed
(from [3146aa68fd03ea4392b45f1c8771675a9c850471]) Fixes queries using limits and punctuation in order, removes order("col1, col2") usage in favor of order(["col1", "col2"})
[#4597 state:committed] https://github.com/rails/rails/commit/3146aa68fd03ea4392b45f1c87716...
Create your profile
Help contribute to this project by taking a few moments to create your personal profile. Create your profile »
<h2 style="font-size: 14px">Tickets have moved to Github</h2>
The new ticket tracker is available at <a href="https://github.com/rails/rails/issues">https://github.com/rails/rails/issues</a>
People watching this ticket
Attachments
Referenced by
- 4597 Queries using limits and punctuation in order clauses generate invalid SQL [#4597 state:committed] https://github.com/rails/rails/c...