Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

tables_in_string matches literal string containing dot

#3446

When a query string contains dots, such as @@@"x = 'foo.bar'"@@@, the code that tries to understand which tables are mentioned in the query will mistake "foo" for a table name. The consequence is that sometimes, a query with :include will be executed with a join rather than with separate queries. And that can make the behaviour of ActiveRecord depend on user input. For example,

:conditions => ["x = ?", params[:input]]

In rare cases, this can cause a query to fail.

I provide a patch that tests for the problem and fixes it. Although I'm not comfortable with this solution; it seems to me the code in tables_in_string is too naive. This problem is similar to the one reported in ticket #532 references_eager_loaded_tables? matches numbers with decimals, which was fixed by improving the regexp.

Reported by xpmatteo · October 31st, 2009 @ 03:49 PM

State: wontfix
Milestone: none
Assigned to: Pratik Pratik
Importance: none

Activity

  1. xpmatteo
    xpmatteo
    • Assigned user changed from Tarmo Tänav to Pratik

    October 31st, 2009 @ 03:51 PM

  2. xpmatteo
    xpmatteo

    Improved the regexp (and test) to filter out all string literals. (I think...)

    November 5th, 2009 @ 01:29 PM

  3. Graeme Mathieson
    Graeme Mathieson

    I've just run across this issue. I created a test case which shows it off, using searchlogic, because I initially assumed the error was in there:

    http://gist.github.com/439071

    I'll give xpmatteo's patch a try, see if that fixes my test case.

    June 15th, 2010 @ 03:05 PM

  4. Graeme Mathieson
  5. Graeme Mathieson
    Graeme Mathieson

    Dodgy monkey patch for anyone else who stumbles across this:

    ActiveRecord::Associations::ClassMethods.module_eval do
      def tables_in_string(string)
        return [] if string.blank?
        string.gsub(/'(\\'|[^'])*'|"(\\"|[^"])*"/, '').scan(/([a-zA-Z_][\.\w]+).?\./).flatten
      end
    end
    

    June 15th, 2010 @ 03:21 PM

  6. Simon Kaczor
    Simon Kaczor

    The patch creates other issues. tables_in_string needs to return actual tables names:

      def tables_in_string(string)
        return [] if string.blank?
        string.gsub(/'(.*)'/) {|s| s.gsub('.', '_')}.scan(/([\.a-zA-Z_]+).?\./).flatten
      end
    

    June 23rd, 2010 @ 05:16 PM

  7. Andrew Selder
    Andrew Selder
    • Tag set to 2.3.10, 3.0.3, active_record, tables_in_string
    • Importance changed from to

    This is still an open issue in ActiveRecord 3.0.3 and 2.3.10

    bad = "((((((LOWER(events.name) LIKE '%ga.fs%')))) AND (events.delete_flag = 0)) AND (events.date >= curdate()))"

    In AR 3.0.3
    bad.scan(/([a-zA-Z_][.\w]+).?./).flatten.map{ |s| s.downcase }.uniq => ["events", "ga"]

    In AR 2.3.10
    bad.scan(/([.a-zA-Z_]+).?./).flatten => ["events", "ga", "events", "events"]

    December 8th, 2010 @ 11:15 PM

  8. Andrew Selder
    Andrew Selder

    Simon's regex above is not sufficient unfortunately.

    Two problems:

    1. The first gsub's pattern is greedy, so if you have multiple literals table names can get missed

    example
    bad2 = "((((((LOWER(events.name) LIKE '%ga.fs%')))) AND ((((((LOWER(venues.name) LIKE '%ga.fs%')))) AND (events.delete_flag = 0)) AND (events.date >= curdate()))"

    bad2.gsub(/'(.*)'/) {|s| s.gsub('.', '')}.scan(/([.a-zA-Z]+).?./).flatten => ["events", "events", "events"]

    Here it lost the venues table.

    1. This only works for literals enclosed by single quotes. Literals could also be enclosed by double quotes.

    Here is my proposed updated regex:

    string.gsub(/(['"])(.*?)(\1)/) {|s| s.gsub('.', '')}.scan(/([.a-zA-Z]+).?./).flatten

    I'll be ginning up a patch against 2.3.10 and 3.0.3 shortly

    December 8th, 2010 @ 11:36 PM

  9. Andrew Selder
    Andrew Selder

    Here are a couple of patches against master and 2-3-stable.

    It implements the regex I mentioned above.

    December 9th, 2010 @ 12:50 AM

  10. Andrew Selder
    Andrew Selder
    • Tag changed from 2.3.10, 3.0.3, active_record, tables_in_string to 2.3.10, 3.0.3, active_record, patch, tables_in_string

    Let's actually try attaching this time.

    December 9th, 2010 @ 12:55 AM

  11. Ed Ruder
  12. Mark Towfiq
    Mark Towfiq

    Looks good. I'm glad one of the tests included the tablename.columnname case as it looks like the current regexp cares about that.

    December 9th, 2010 @ 01:01 AM

  13. nate b
    nate b

    Good catch, Andrew. Worked perfectly for me.

    December 9th, 2010 @ 01:02 AM

  14. Ed Ruder
    Ed Ruder

    +1 (Adding comment, this time.) My project got bit by this problem--I think this patch addresses the problem better than the previous patch.

    December 9th, 2010 @ 01:03 AM

  15. Ed Ruder
    Ed Ruder
    • Tag changed from 2.3.10, 3.0.3, active_record, patch, tables_in_string to 2.3.10, 3.0.3, active_record, patch, tables_in_string, verified

    December 9th, 2010 @ 01:09 AM

  16. Andrew Selder
    Andrew Selder
    • Tag changed from 2.3.10, 3.0.3, active_record, patch, tables_in_string, verified to 2.3.10, 3.0.3, active_record, patch, tables_in_string

    readding patches with the ticket update keyword stuff in commit comment

    December 9th, 2010 @ 01:15 AM

  17. Andrew Selder
    Andrew Selder
    • Tag changed from 2.3.10, 3.0.3, active_record, patch, tables_in_string to 2.3.10, 3.0.3, active_record, patch, tables_in_string, verified

    December 9th, 2010 @ 01:15 AM

  18. Andrew Selder
  19. Andrew Selder
    Andrew Selder
    • Tag changed from 2.3.10, 3.0.3, active_record, patch, tables_in_string, verified to 2.3.10, 3.0.3, active_record, tables_in_string

    Jeremy Evans found a case where the regex doesn't work (embedded escaped quote in literal).

    I'll have the regex fixed and new patches up shortly.

    December 9th, 2010 @ 02:42 AM

  20. Andrew Selder
    Andrew Selder

    After talking to some of the guys over on the core mailing list, this tables_in string was a temporary hack in the move to preloading. It will probably be coming out altogether in

    In Rails 3, use .preload(:bars) rather than include.

    In Rails 2, we'll just have to stick with the monkey patch method.

    Here's what I've come up with that seems to work with all the stuff people have mentioned.

    ActiveRecord::Associations::ClassMethods.module_eval do
      def tables_in_string(string)
        return [] if string.blank?
        literals_without_dots(string).scan(/([\.a-zA-Z_]+).?\./).flatten
      end
    
      def literals_without_dots(string) 
        string.gsub(/(['"])(([^\\]|(\\.))*?)(\1)/) {|s| s.gsub('.', '_')} 
      end
    end
    

    December 9th, 2010 @ 04:25 AM

  21. Michael Koziarski
    Michael Koziarski

    As Andrew says, I'm loathe to climb further down the rabbit hole of trying to parse SQL with regexps. It's destined to fail.

    If you're bitten by this in 2-3-stable apps you can either monkey patch in an initializer, or use will's patch in #3683 Add :preload option to find

    December 9th, 2010 @ 07:36 PM

  22. Michael Koziarski
    Michael Koziarski
    • State changed from new to wontfix

    December 9th, 2010 @ 08:04 PM