Problem with query range reporting Incorrect syntax near OFFSET

Created on 26 August 2023, about 1 year ago
Updated 30 August 2023, about 1 year ago

Problem/Motivation

When working with this module, I ran into a few issues where the SQLSRV connection class was not handling RANGE correctly for the MSSQL engine version / compatibility level. For whatever reason, the code base was ignoring the possibility the module might interact with an older DB and expected a particular OFFSET approach.

On my particular problem, I was see an error around this format of a select:

SELECT p.[productName] AS [productName], p.[productTypeId] AS [productTypeId], p.[checkInId] AS [checkInId] FROM [Product] [p] ORDER BY (SELECT NULL) OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;

More specifically, the Incorrect syntax near 'OFFSET' error was killing my request.

Proposed resolution

The proposed resolution is to have my range request look something more like this:

SELECT *
FROM (
  SELECT p.[productName] AS [productName], p.[productTypeId] AS [productTypeId], p.[checkInId] AS [checkInId],
         ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS RowNum
  FROM [Product] [p]
) AS sub
WHERE sub.RowNum BETWEEN 11 AND 20;

Or so, this is how the request might be formatted.

What I did was review some of the older code that existed in the code base for version 8.x-2.x:

  public function addRangeToQuery($query, $from, $count)
  {
    if ($from == 0) {
      // Easy case: just use a TOP query if we don't have to skip any rows.
      $query = preg_replace('/^\s*SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP(' . $count . ')', $query);
    } else {
      if ($this->connection->Scheme()->EngineVersionNumber() >= 11) {
        if (strripos($query, 'ORDER BY') === false) {
          $query = "SELECT Q.*, 0 as TempSort FROM ({$query}) as Q ORDER BY TempSort OFFSET {$from} ROWS FETCH NEXT {$count} ROWS ONLY";
        } else {
          $query = "{$query} OFFSET {$from} ROWS FETCH NEXT {$count} ROWS ONLY";
        }
      } else {
        // More complex case: use a TOP query to retrieve $from + $count rows, and
        // filter out the first $from rows using a window function.
        $query = preg_replace('/^\s*SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP(' . ($from + $count) . ') ', $query);
        $query = '
          SELECT * FROM (
            SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.__line2) AS __line3 FROM (
              SELECT sub1.*, 1 AS __line2 FROM (' . $query . ') AS sub1
            ) as sub2
          ) AS sub3
          WHERE __line3 BETWEEN ' . ($from + 1) . ' AND ' . ($from + $count);
      }
    }
    return $query;
  }

This was quite promising, so I've written a patch to fix the current approach and bring this back in.

Feature request
Status

Closed: won't fix

Version

4.3

Component

Code

Created by

🇨🇦Canada wilco

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @wilco
  • @wilco opened merge request.
  • Status changed to Needs review about 1 year ago
  • Status changed to Closed: won't fix about 1 year ago
  • 🇬🇧United Kingdom pstewart

    Hi @wilco, thanks for working on this. OFFSET/FETCH has been support by SQL Server since 11.x / 2012, so only SQL Server 2008 and older versions would need this workaround. Given that such old versions are long since out of support and don't receive security updates from Microsoft, I would strongly recommend migrating to a newer SQL Server version. I think the minimum version targeted by previous maintainers is SQL Server 2016, and that's a hard minimum for Drupal 10 / 4.4.x when using SQLServer for the Drupal database due to the JSON support requirement.

    As such I'm not inclined to bring this workaround back in to current versions of the module, however the patch does look OK otherwise so should serve you well in the interim while you figure out switching to a newer SQL Server version, and anyone else in a similar situation.

Production build 0.71.5 2024