Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix the LIMIT for SQL Server 2011 or earlier #32

Closed
abydahana opened this issue Nov 28, 2021 · 1 comment
Closed

Fix the LIMIT for SQL Server 2011 or earlier #32

abydahana opened this issue Nov 28, 2021 · 1 comment
Labels
documentation Improvements or additions to documentation

Comments

@abydahana
Copy link
Owner

abydahana commented Nov 28, 2021

Since my PR on codeigniter4/CodeIgniter4 didn't responded yet, you need to touch the core class to make the limit working with SQL Server 2011 or earlier.

File need changes
vendor/codeigniter4/framework/system/Database/SQLSRV/Builder.php

Find the _limit() protected method and override with following code:

    /**
     * Local implementation of limit
     */
    protected function _limit(string $sql, bool $offsetIgnore = false): string
    {
        if (empty($this->QBOrderBy))
        {
            $this->QBOrderBy    = ' ORDER BY (SELECT NULL) ';
            $sql               .= $this->QBOrderBy;
        }

        if (version_compare($this->db->getVersion(), '11', '>='))
        {
            if ($offsetIgnore)
            {
                $sql .= ' OFFSET 0 ';
            }
            else
            {
                $sql .= is_int($this->QBOffset) ? ' OFFSET ' . $this->QBOffset : ' OFFSET 0 ';
            }

            return $sql .= ' ROWS FETCH NEXT ' . $this->QBLimit . ' ROWS ONLY ';
        }

        $limit = $this->QBOffset + $this->QBLimit;

        // An ORDER BY clause is required for ROW_NUMBER() to work
        if ($this->QBOffset && ! empty($this->QBOrderBy))
        {
            $orderBy = $this->compileOrderBy();

            // We have to strip the ORDER BY clause
            $sql = trim(substr($sql, 0, strrpos($sql, $orderBy)));

            // Get the fields to select from our subquery, so that we can avoid CI_rownum appearing in the actual results
            if (count($this->QBSelect) === 0 OR strpos(implode(',', $this->QBSelect), '*') !== FALSE)
            {
                $select         = '*'; // Inevitable
            }
            else
            {
                // Use only field names and their aliases, everything else is out of our scope.
                $select         = [];
                $fieldRegexp    = ($this->_quoted_identifier ? '("[^\"]+")' : '(\[[^\]]+\])');

                foreach ($this->QBSelect as $i => $singleQBSelect) {
                    $select[]   = (preg_match('/(?:\s|\.)' . $fieldRegexp . '$/i', $singleQBSelect, $m) ? $m[1] : $singleQBSelect);
                }

                $select         = implode(', ', $select);
            }

            return 'SELECT ' . $select . " FROM (\n\n" . preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER(' . trim($orderBy) . ') AS ' . $this->db->escapeIdentifiers('CI_rownum') . ', ', $sql) . "\n\n) " . $this->db->escapeIdentifiers('CI_subquery') . "\nWHERE " . $this->db->escapeIdentifiers('CI_rownum').' BETWEEN ' . ($this->QBOffset + 1) . ' AND ' . $limit;
        }

        return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP ' . $limit . ' ', $sql);
    }
@abydahana abydahana added the documentation Improvements or additions to documentation label Nov 28, 2021
@abydahana abydahana pinned this issue Nov 28, 2021
@abydahana
Copy link
Owner Author

abydahana commented May 2, 2024

For CodeIgniter 4.3.1

/**
 * Local implementation of limit
 */
protected function _limit(string $sql, bool $offsetIgnore = false): string
{
    if (empty($this->QBOrderBy))
    {
        $this->QBOrderBy    = ' ORDER BY (SELECT NULL) ';
        $sql               .= $this->QBOrderBy;
    }

    if (version_compare($this->db->getVersion(), '11', '>='))
    {
        if ($offsetIgnore)
        {
            $sql .= ' OFFSET 0 ';
        }
        else
        {
            $sql .= is_int($this->QBOffset) ? ' OFFSET ' . $this->QBOffset : ' OFFSET 0 ';
        }

        return $sql .= ' ROWS FETCH NEXT ' . $this->QBLimit . ' ROWS ONLY ';
    }

    $limit = $this->QBOffset + $this->QBLimit;

    // An ORDER BY clause is required for ROW_NUMBER() to work
    if ($this->QBOffset && ! empty($this->QBOrderBy))
    {
        $orderBy = $this->compileOrderBy();

        // We have to strip the ORDER BY clause
        $sql = trim(substr($sql, 0, strrpos($sql, $orderBy)));

        // Get the fields to select from our subquery, so that we can avoid CI_rownum appearing in the actual results
        if (count($this->QBSelect) === 0 OR strpos(implode(',', $this->QBSelect), '*') !== FALSE)
        {
            $select         = '*'; // Inevitable
        }
        else
        {
            // Use only field names and their aliases, everything else is out of our scope.
            $select         = [];
            $fieldRegexp    = ($this->_quoted_identifier ? '("[^\"]+")' : '(\[[^\]]+\])');

            foreach ($this->QBSelect as $i => $singleQBSelect) {
                $select[]   = (preg_match('/(?:\s|\.)' . $fieldRegexp . '$/i', $singleQBSelect, $m) ? $m[1] : $singleQBSelect);
            }

            $select         = implode(', ', $select);
        }

        return "SELECT * FROM (\n\n" . preg_replace('/^(SELECT( DISTINCT)?)/i', '\\1 ROW_NUMBER() OVER(' . trim($orderBy) . ') AS ' . $this->db->escapeIdentifiers('CI_rownum') . ', ', $sql) . "\n\n) " . $this->db->escapeIdentifiers('CI_subquery') . "\nWHERE " . $this->db->escapeIdentifiers('CI_rownum').' BETWEEN ' . ($this->QBOffset + 1) . ' AND ' . $limit;
    }

    return preg_replace('/(^\SELECT (DISTINCT)?)/i','\\1 TOP ' . $limit . ' ', $sql);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

1 participant