Fixing Rails Pagination for SQL Server

MS SQL Server certainly feels like the red headed step child of the Rails connection adapters. The core developers aren't interested in it, and I'd have to guess that most Rails developers deploy to MySQL or PostgreSQL.

A good example of why the SQL Server support needs more love from the Rails community. The pagination code in the connection adapter is horribly ugly. SQL Server 2000 doesn't support a limit or offset, which makes pagination extremely difficult. For kicks, check out the SQL that the Rails connection adapter generates for a limit and offset query in SQL Server. There's enough sub queries and reverse sorts to make your head spin. Not to mention the awful performance killing select count(*) before every query.

SQL Server 2005 makes our life a little easier in that it added row_number() support. With this, it's possible, however not straight forward, to perform pagination that doesn't make you want to puke so much. Unfortunately, Rails hasn't yet split their SQL Server adapters into a SQL Server 2000 and SQL Server 2005 adapters. I strongly encourage this move, as there are many differences between the two.

If you are running SQL Server 2005, and you want to fix many pagination problems that plague the sqlserver_adapter.rb (just look inside the Rails Trac sometime, there's a lot), have I got the monkey patch for you. We've been using this for a little while now, and it seems to do the trick. YMMV but it should hopefully give you an idea of what's possible.

module ActiveRecord
module ConnectionAdapters
class SQLServerAdapter

def add_limit_offset!(sql, options)
if options[:limit] and options[:offset]
options[:order] ||= sql.match('FROM (.*) ')[1] + '.id'
sql.sub!(/ORDER BY.*$/i, '')
"SELECT row_number() over( order by #{options[:order]} ) as row_num, \n")
sql.replace("select top #{options[:limit]} * from (#{sql}) as tmp_table1 \n" +
"where row_num > #{options[:offset]}")

Not only was the built in pagination queries terribly slow (because it always executes a select count(*) before the query itself), but it had problems when doing paginations with included models. This is something that ActiveScaffold does all the time, so if you are using that and SQL Server, you've no doubt felt the pain when you tried to sort a column.

Popular posts from this blog

Lists and arrays in Dart

Converting Array to List in Scala

Null-aware operators in Dart