From ec6a77bab27984ce05b003af07ac9f42ca410d94 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Mon, 11 Mar 2013 03:35:21 -0400
Subject: [PATCH] Fix LIMIT/OFFSET queries handling on MS SQL Server (#1488984) - require version 2005+

---
 CHANGELOG                                 |    1 
 INSTALL                                   |    4 -
 program/lib/Roundcube/rcube_db_mssql.php  |   30 +++++++-------
 program/lib/Roundcube/rcube_db_sqlsrv.php |   32 +++++++++-------
 4 files changed, 35 insertions(+), 32 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index ef3830a..55360ec 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix LIMIT/OFFSET queries handling on MS SQL Server (#1488984)
 - Fix so task name can really contain all from a-z0-9_- characters (#1488941)
 - Support IMAP MOVE extension [RFC 6851]
 - Fix javascript errors when working in a page opened with taget="_blank"
diff --git a/INSTALL b/INSTALL
index 326ef88..de29441 100644
--- a/INSTALL
+++ b/INSTALL
@@ -34,7 +34,7 @@
    - magic_quotes_runtime disabled
    - magic_quotes_sybase disabled
 * PHP compiled with OpenSSL to connect to IMAPS and to use the spell checker
-* A MySQL (4.0.8 or newer), PostgreSQL, MSSQL database engine
+* A MySQL (4.0.8 or newer), PostgreSQL, MS SQL Server (2005 or newer) database engine
   or SQLite support in PHP
 * One of the above databases with permission to create tables
 * An SMTP server (recommended) or PHP configured for mail delivery
@@ -232,5 +232,3 @@
 
     compress.filetype = ("text/plain", "text/html", "text/javascript", "text/css", "text/xml", "image/gif", "image/png")
 }
-
-
diff --git a/program/lib/Roundcube/rcube_db_mssql.php b/program/lib/Roundcube/rcube_db_mssql.php
index a1ce80a..37a4267 100644
--- a/program/lib/Roundcube/rcube_db_mssql.php
+++ b/program/lib/Roundcube/rcube_db_mssql.php
@@ -100,30 +100,30 @@
     {
         $limit  = intval($limit);
         $offset = intval($offset);
+        $end    = $offset + $limit;
 
-        $orderby = stristr($query, 'ORDER BY');
-        if ($orderby !== false) {
-            $sort  = (stripos($orderby, ' desc') !== false) ? 'desc' : 'asc';
-            $order = str_ireplace('ORDER BY', '', $orderby);
-            $order = trim(preg_replace('/\bASC\b|\bDESC\b/i', '', $order));
-        }
-
-        $query = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . ($limit + $offset) . ' ', $query);
-
+        // query without OFFSET
         if (!$offset) {
+            $query = preg_replace('/^SELECT\s/i', "SELECT TOP $limit ", $query);
             return $query;
         }
 
-        $query = 'SELECT * FROM (SELECT TOP ' . $limit . ' * FROM (' . $query . ') AS inner_tbl';
+        $orderby = stristr($query, 'ORDER BY');
+        $offset += 1;
+
         if ($orderby !== false) {
-            $query .= ' ORDER BY ' . $order . ' ';
-            $query .= (stripos($sort, 'asc') !== false) ? 'DESC' : 'ASC';
+            $query = trim(substr($query, 0, -1 * strlen($orderby)));
         }
-        $query .= ') AS outer_tbl';
-        if ($orderby !== false) {
-            $query .= ' ORDER BY ' . $order . ' ' . $sort;
+        else {
+            // it shouldn't happen, paging without sorting has not much sense
+            // @FIXME: I don't know how to build paging query without ORDER BY
+            $orderby = "ORDER BY 1";
         }
 
+        $query = preg_replace('/^SELECT\s/i', '', $query);
+        $query = "WITH paging AS (SELECT ROW_NUMBER() OVER ($orderby) AS [RowNumber], $query)"
+            . " SELECT * FROM paging WHERE [RowNumber] BETWEEN $offset AND $end ORDER BY [RowNumber]";
+
         return $query;
     }
 
diff --git a/program/lib/Roundcube/rcube_db_sqlsrv.php b/program/lib/Roundcube/rcube_db_sqlsrv.php
index e696780..e5dfb11 100644
--- a/program/lib/Roundcube/rcube_db_sqlsrv.php
+++ b/program/lib/Roundcube/rcube_db_sqlsrv.php
@@ -100,25 +100,29 @@
     {
         $limit  = intval($limit);
         $offset = intval($offset);
+        $end    = $offset + $limit;
+
+        // query without OFFSET
+        if (!$offset) {
+            $query = preg_replace('/^SELECT\s/i', "SELECT TOP $limit ", $query);
+            return $query;
+        }
 
         $orderby = stristr($query, 'ORDER BY');
+        $offset += 1;
+
         if ($orderby !== false) {
-            $sort  = (stripos($orderby, ' desc') !== false) ? 'desc' : 'asc';
-            $order = str_ireplace('ORDER BY', '', $orderby);
-            $order = trim(preg_replace('/\bASC\b|\bDESC\b/i', '', $order));
+            $query = trim(substr($query, 0, -1 * strlen($orderby)));
+        }
+        else {
+            // it shouldn't happen, paging without sorting has not much sense
+            // @FIXME: I don't know how to build paging query without ORDER BY
+            $orderby = "ORDER BY 1";
         }
 
-        $query = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . ($limit + $offset) . ' ', $query);
-
-        $query = 'SELECT * FROM (SELECT TOP ' . $limit . ' * FROM (' . $query . ') AS inner_tbl';
-        if ($orderby !== false) {
-            $query .= ' ORDER BY ' . $order . ' ';
-            $query .= (stripos($sort, 'asc') !== false) ? 'DESC' : 'ASC';
-        }
-        $query .= ') AS outer_tbl';
-        if ($orderby !== false) {
-            $query .= ' ORDER BY ' . $order . ' ' . $sort;
-        }
+        $query = preg_replace('/^SELECT\s/i', '', $query);
+        $query = "WITH paging AS (SELECT ROW_NUMBER() OVER ($orderby) AS [RowNumber], $query)"
+            . " SELECT * FROM paging WHERE [RowNumber] BETWEEN $offset AND $end ORDER BY [RowNumber]";
 
         return $query;
     }

--
Gitblit v1.9.1