Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions system/Database/OCI8/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,18 @@ protected function _update(string $table, array $values): string
protected function _limit(string $sql, bool $offsetIgnore = false): string
{
$offset = (int) ($offsetIgnore === false ? $this->QBOffset : 0);
if (version_compare($this->db->getVersion(), '12.1', '>=')) {
// OFFSET-FETCH can be used only with the ORDER BY clause
if (empty($this->QBOrderBy)) {
$sql .= ' ORDER BY 1';
}

// OFFSET-FETCH can be used only with the ORDER BY clause
if (! is_array($this->QBOrderBy) || $this->QBOrderBy === []) {
$sql .= ' ORDER BY 1';
return $sql . ' OFFSET ' . $offset . ' ROWS FETCH NEXT ' . $this->QBLimit . ' ROWS ONLY';
}

return $sql . ' OFFSET ' . $offset . ' ROWS FETCH NEXT ' . $this->QBLimit . ' ROWS ONLY';
$limitTemplateQuery = 'SELECT * FROM (SELECT INNER_QUERY.*, ROWNUM RNUM FROM (%s) INNER_QUERY WHERE ROWNUM < %d)' . ($offset ? ' WHERE RNUM >= %d' : '');

return sprintf($limitTemplateQuery, $sql, $offset + $this->QBLimit + 1, $offset);
}

/**
Expand Down
115 changes: 115 additions & 0 deletions tests/system/Database/Builder/LimitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
namespace CodeIgniter\Database\Builder;

use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\OCI8\Builder as OCI8Builder;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\Mock\MockConnection;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;

/**
Expand Down Expand Up @@ -65,4 +67,117 @@ public function testLimitAndOffsetMethod(): void

$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
}

#[DataProvider('provideOracle11gVersions')]
public function testLimitAloneWithOracle11g(string $version): void
{
$builder = new OCI8Builder('user', $this->createOci8Connection($version));

$builder->limit(5);

$expectedSQL = 'SELECT * FROM (SELECT INNER_QUERY.*, ROWNUM RNUM FROM (SELECT * FROM "user" ) INNER_QUERY WHERE ROWNUM < 6)';

$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
}

#[DataProvider('provideOracle11gVersions')]
public function testLimitAndOffsetWithOracle11g(string $version): void
{
$builder = new OCI8Builder('user', $this->createOci8Connection($version));

$builder->limit(5, 1);

$expectedSQL = 'SELECT * FROM (SELECT INNER_QUERY.*, ROWNUM RNUM FROM (SELECT * FROM "user" ) INNER_QUERY WHERE ROWNUM < 7) WHERE RNUM >= 1';

$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
}

#[DataProvider('provideOracle11gVersions')]
public function testLimitAndOffsetWithOrderByOracle11g(string $version): void
{
$builder = new OCI8Builder('user', $this->createOci8Connection($version));

$builder->orderBy('id', 'ASC')->limit(5, 1);

$expectedSQL = 'SELECT * FROM (SELECT INNER_QUERY.*, ROWNUM RNUM FROM (SELECT * FROM "user" ORDER BY "id" ASC ) INNER_QUERY WHERE ROWNUM < 7) WHERE RNUM >= 1';

$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
}

#[DataProvider('provideOracle12_1AndAboveVersions')]
public function testLimitAloneWithOracle12_1AndAbove(string $version): void
{
$builder = new OCI8Builder('user', $this->createOci8Connection($version));

$builder->limit(5);

$expectedSQL = 'SELECT * FROM "user" ORDER BY 1 OFFSET 0 ROWS FETCH NEXT 5 ROWS ONLY';

$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
}

#[DataProvider('provideOracle12_1AndAboveVersions')]
public function testLimitAndOffsetWithOracle12_1AndAbove(string $version): void
{
$builder = new OCI8Builder('user', $this->createOci8Connection($version));

$builder->limit(5, 1);

$expectedSQL = 'SELECT * FROM "user" ORDER BY 1 OFFSET 1 ROWS FETCH NEXT 5 ROWS ONLY';

$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
}

#[DataProvider('provideOracle12_1AndAboveVersions')]
public function testLimitAndOffsetWithOrderByOracle12_1AndAbove(string $version): void
{
$builder = new OCI8Builder('user', $this->createOci8Connection($version));

$builder->orderBy('id', 'ASC')->limit(5, 1);

$expectedSQL = 'SELECT * FROM "user" ORDER BY "id" ASC OFFSET 1 ROWS FETCH NEXT 5 ROWS ONLY';

$this->assertSame($expectedSQL, str_replace("\n", ' ', $builder->getCompiledSelect()));
}

/**
* @return iterable<string, array{string}>
*/
public static function provideOracle11gVersions(): iterable
{
yield '11.2' => ['11.2.0.4.0'];

yield '11.1' => ['11.1.0.7.0'];
}

/**
* @return iterable<string, array{string}>
*/
public static function provideOracle12_1AndAboveVersions(): iterable
{
yield '12.1' => ['12.1'];

yield '12.1.0.2' => ['12.1.0.2.0'];

yield '19c' => ['19.0.0.0.0'];
}

private function createOci8Connection(string $version): MockConnection
{
return new class (['DBDriver' => 'OCI8'], $version) extends MockConnection {
private readonly string $oracleVersion;

public function __construct(array $params, string $version)
{
parent::__construct($params);

$this->oracleVersion = $version;
}

public function getVersion(): string
{
return $this->oracleVersion;
}
};
}
}