Skip to content

Commit b0458b1

Browse files
committed
Improve handling of limit and offset when using fully qualified column identifier in order by clauses
1 parent 0366a3f commit b0458b1

File tree

1 file changed

+34
-18
lines changed

1 file changed

+34
-18
lines changed

lib/active_record/connection_adapters/sqlserver_adapter.rb

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -469,18 +469,28 @@ def quote_string(string)
469469
string.gsub(/\'/, "''")
470470
end
471471

472+
# If name is on the form "foo.bar.baz"
472473
def quote_table_name(name)
473-
if name.split(".").length == 3
474-
b=name.split(".")
474+
if name.to_s.split(".").length == 3
475+
b = name.to_s.split(".")
475476
"[#{b[0]}].[#{b[1]}].[#{b[2]}]"
476477
else
477478
super(name)
478479
end
479480

480481
end
481482

482-
def quote_column_name(name)
483-
"[#{name}]"
483+
# Quotes the given column identifier.
484+
#
485+
# Examples
486+
#
487+
# quote_column_name('foo') # => '[foo]'
488+
# quote_column_name(:foo) # => '[foo]'
489+
# quote_column_name('foo.bar') # => '[foo].[bar]'
490+
def quote_column_name(identifier)
491+
identifier.to_s.split('.').collect do |name|
492+
"[#{name}]"
493+
end.join(".")
484494
end
485495

486496
def add_limit_offset!(sql, options)
@@ -509,26 +519,32 @@ def add_limit_offset!(sql, options)
509519
if (options[:limit] + options[:offset]) >= total_rows
510520
options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
511521
end
522+
523+
# Wrap the SQL query in a bunch of outer SQL queries that emulate proper LIMIT,OFFSET support.
512524
sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + options[:offset]}")
513525
sql << ") AS tmp1"
526+
514527
if options[:order]
515-
# don't strip the table name, it is needed later on
516-
#options[:order] = options[:order].split(',').map do |field|
517528
order = options[:order].split(',').map do |field|
518-
parts = field.split(" ")
519-
# tc = column_name etc (not direction of sort)
520-
tc = parts[0]
521-
#if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used in query
522-
# tc.gsub!(/\./, '\\.\\[')
523-
# tc << '\\]'
524-
#end
525-
if sql =~ /#{Regexp.escape(tc)} AS (t\d_r\d\d?)/
526-
parts[0] = $1
527-
elsif parts[0] =~ /\w+\.\[?(\w+)\]?/
528-
parts[0] = $1
529+
order_by_column, order_direction = field.split(" ")
530+
order_by_column = quote_column_name(order_by_column)
531+
532+
# Investigate the SQL query to figure out if the order_by_column has been renamed.
533+
if sql =~ /#{Regexp.escape(order_by_column)} AS (t\d_r\d\d?)/
534+
# Fx "[foo].[bar] AS t4_r2" was found in the SQL. Use the column alias (ie 't4_r2') for the subsequent orderings
535+
order_by_column = $1
536+
elsif order_by_column =~ /\w+\.\[?(\w+)\]?/
537+
order_by_column = $1
538+
else
539+
# It doesn't appear that the column name has been renamed as part of the query. Use just the column
540+
# name rather than the full identifier for the outer queries.
541+
order_by_column = order_by_column.split('.').last
529542
end
530-
parts.join(' ')
543+
544+
# Put the column name and eventual direction back together
545+
[order_by_column, order_direction].join(' ').strip
531546
end.join(', ')
547+
532548
sql << " ORDER BY #{change_order_direction(order)}) AS tmp2 ORDER BY #{order}"
533549
else
534550
sql << ") AS tmp2"

0 commit comments

Comments
 (0)