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

feat: improve pagination performance #879

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
23 changes: 22 additions & 1 deletion src/Oci8/Query/Grammars/OracleGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Query\Grammars\Grammar;
use Illuminate\Support\Str;
use Yajra\Oci8\OracleReservedWords;
Expand Down Expand Up @@ -174,7 +175,27 @@ protected function compileTableExpression($sql, $constraint, $query)
return "select * from ({$sql}) where rownum {$constraint}";
}

return "select t2.* from ( select rownum AS \"rn\", t1.* from ({$sql}) t1 ) t2 where t2.\"rn\" {$constraint}";
$table = $this->wrapTable($query->from);
if ($query->from instanceof Expression) {
// Get the last item in the array which represents the table alias
$table = last(explode(' ', $this->wrapTable($query->from)));
}

if (count($query->columns) > 1) {
$query->columns = ['*'];
}

$select = $this->compileColumns($query, $query->columns);

// Apply ROW_NUMBER() for pagination
$orderBy = $this->compileOrders($query, $query->orders);

// If no ORDER BY is specified, use ROWID for ordering
if (empty($orderBy)) {
$orderBy = 'order by ROWID';
}

return "{$select} from (select {$table}.*, row_number() over ({$orderBy}) as rn from ({$sql}) {$table}) {$table} where rn {$constraint}";
}

/**
Expand Down
81 changes: 59 additions & 22 deletions tests/Database/Oci8QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,7 @@ public function testSubSelectWhereIns()
$q->select('id')->from('users')->where('age', '>', 25)->take(3);
});
$this->assertEquals(
'select * from "USERS" where "ID" in (select t2.* from ( select rownum AS "rn", t1.* from (select "ID" from "USERS" where "AGE" > ?) t1 ) t2 where t2."rn" between 1 and 3)',
'select * from "USERS" where "ID" in (select "ID" from (select "USERS".*, row_number() over (order by ROWID) as rn from (select "ID" from "USERS" where "AGE" > ?) "USERS") "USERS" where rn between 1 and 3)',
$builder->toSql()
);
$this->assertEquals([25], $builder->getBindings());
Expand All @@ -873,7 +873,7 @@ public function testSubSelectWhereIns()
$q->select('id')->from('users')->where('age', '>', 25)->take(3);
});
$this->assertEquals(
'select * from "USERS" where "ID" not in (select t2.* from ( select rownum AS "rn", t1.* from (select "ID" from "USERS" where "AGE" > ?) t1 ) t2 where t2."rn" between 1 and 3)',
'select * from "USERS" where "ID" not in (select "ID" from (select "USERS".*, row_number() over (order by ROWID) as rn from (select "ID" from "USERS" where "AGE" > ?) "USERS") "USERS" where rn between 1 and 3)',
$builder->toSql()
);
$this->assertEquals([25], $builder->getBindings());
Expand Down Expand Up @@ -1111,7 +1111,7 @@ public function testOffset()
$builder = $this->getBuilder();
$builder->select('*')->from('users')->offset(10);
$this->assertEquals(
'select t2.* from ( select rownum AS "rn", t1.* from (select * from "USERS") t1 ) t2 where t2."rn" >= 11',
'select * from (select "USERS".*, row_number() over (order by ROWID) as rn from (select * from "USERS") "USERS") "USERS" where rn >= 11',
$builder->toSql()
);
}
Expand All @@ -1121,35 +1121,66 @@ public function testLimitsAndOffsets()
$builder = $this->getBuilder();
$builder->select('*')->from('users')->offset(5)->limit(10);
$this->assertEquals(
'select t2.* from ( select rownum AS "rn", t1.* from (select * from "USERS") t1 ) t2 where t2."rn" between 6 and 15',
'select * from (select "USERS".*, row_number() over (order by ROWID) as rn from (select * from "USERS") "USERS") "USERS" where rn between 6 and 15',
$builder->toSql()
);

$builder = $this->getBuilder();
$builder->select('*')->from('users')->skip(5)->take(10);
$this->assertEquals(
'select t2.* from ( select rownum AS "rn", t1.* from (select * from "USERS") t1 ) t2 where t2."rn" between 6 and 15',
'select * from (select "USERS".*, row_number() over (order by ROWID) as rn from (select * from "USERS") "USERS") "USERS" where rn between 6 and 15',
$builder->toSql()
);

$builder = $this->getBuilder();
$builder->select('*')->from('users')->skip(-5)->take(10);
$this->assertEquals(
'select t2.* from ( select rownum AS "rn", t1.* from (select * from "USERS") t1 ) t2 where t2."rn" between 1 and 10',
'select * from (select "USERS".*, row_number() over (order by ROWID) as rn from (select * from "USERS") "USERS") "USERS" where rn between 1 and 10',
$builder->toSql()
);

$builder = $this->getBuilder();
$builder->select('*')->from('users')->forPage(2, 15);
$this->assertEquals(
'select t2.* from ( select rownum AS "rn", t1.* from (select * from "USERS") t1 ) t2 where t2."rn" between 16 and 30',
'select * from (select "USERS".*, row_number() over (order by ROWID) as rn from (select * from "USERS") "USERS") "USERS" where rn between 16 and 30',
$builder->toSql()
);

$builder = $this->getBuilder();
$builder->select('*')->from('users')->forPage(-2, 15);
$this->assertEquals(
'select t2.* from ( select rownum AS "rn", t1.* from (select * from "USERS") t1 ) t2 where t2."rn" between 1 and 15',
'select * from (select "USERS".*, row_number() over (order by ROWID) as rn from (select * from "USERS") "USERS") "USERS" where rn between 1 and 15',
$builder->toSql()
);

$builder = $this->getBuilder();
$builder->select('users.*')->from('users')->forPage(1, 10);
$this->assertEquals(
'select "USERS".* from (select "USERS".*, row_number() over (order by ROWID) as rn from (select "USERS".* from "USERS") "USERS") "USERS" where rn between 1 and 10',
$builder->toSql()
);
}

public function testLimitAndOffsetFromSub()
{
$builder = $this->getBuilder();
$builder->select('users.*')->fromSub(fn ($query) => $query->from('users'), 'users')->limit(10);
$this->assertEquals(
'select "USERS".* from (select "USERS".*, row_number() over (order by ROWID) as rn from (select "USERS".* from (select * from "USERS") "USERS") "USERS") "USERS" where rn between 1 and 10',
$builder->toSql()
);

$builder = $this->getBuilder();
$builder->select('users.*')->fromSub($this->getBuilder()->from('users'), 'users')->limit(10);
$this->assertEquals(
'select "USERS".* from (select "USERS".*, row_number() over (order by ROWID) as rn from (select "USERS".* from (select * from "USERS") "USERS") "USERS") "USERS" where rn between 1 and 10',
$builder->toSql()
);

$builder = $this->getBuilder();
$builder->select('users.*')->fromSub($this->getBuilder()->from('users'), 'users')->limit(10)->offset(10);
$this->assertEquals(
'select "USERS".* from (select "USERS".*, row_number() over (order by ROWID) as rn from (select "USERS".* from (select * from "USERS") "USERS") "USERS") "USERS" where rn between 11 and 20',
$builder->toSql()
);
}
Expand All @@ -1159,14 +1190,14 @@ public function testLimitAndOffsetToPaginateOne()
$builder = $this->getBuilder();
$builder->select('*')->from('users')->offset(0)->limit(1);
$this->assertEquals(
'select t2.* from ( select rownum AS "rn", t1.* from (select * from "USERS") t1 ) t2 where t2."rn" between 1 and 1',
'select * from (select "USERS".*, row_number() over (order by ROWID) as rn from (select * from "USERS") "USERS") "USERS" where rn between 1 and 1',
$builder->toSql()
);

$builder = $this->getBuilder();
$builder->select('*')->from('users')->offset(1)->limit(1);
$this->assertEquals(
'select t2.* from ( select rownum AS "rn", t1.* from (select * from "USERS") t1 ) t2 where t2."rn" between 2 and 2',
'select * from (select "USERS".*, row_number() over (order by ROWID) as rn from (select * from "USERS") "USERS") "USERS" where rn between 2 and 2',
$builder->toSql()
);
}
Expand All @@ -1176,38 +1207,44 @@ public function testForPage()
$builder = $this->getBuilder();
$builder->select('*')->from('users')->forPage(2, 15);
$this->assertSame(
'select t2.* from ( select rownum AS "rn", t1.* from (select * from "USERS") t1 ) t2 where t2."rn" between 16 and 30',
$builder->toSql());
'select * from (select "USERS".*, row_number() over (order by ROWID) as rn from (select * from "USERS") "USERS") "USERS" where rn between 16 and 30',
$builder->toSql()
);

$builder = $this->getBuilder();
$builder->select('*')->from('users')->forPage(0, 15);
$this->assertSame(
'select t2.* from ( select rownum AS "rn", t1.* from (select * from "USERS") t1 ) t2 where t2."rn" between 1 and 15',
$builder->toSql());
'select * from (select "USERS".*, row_number() over (order by ROWID) as rn from (select * from "USERS") "USERS") "USERS" where rn between 1 and 15',
$builder->toSql()
);

$builder = $this->getBuilder();
$builder->select('*')->from('users')->forPage(-2, 15);
$this->assertSame(
'select t2.* from ( select rownum AS "rn", t1.* from (select * from "USERS") t1 ) t2 where t2."rn" between 1 and 15',
$builder->toSql());
'select * from (select "USERS".*, row_number() over (order by ROWID) as rn from (select * from "USERS") "USERS") "USERS" where rn between 1 and 15',
$builder->toSql()
);

$builder = $this->getBuilder();
$builder->select('*')->from('users')->forPage(2, 0);
$this->assertSame(
'select t2.* from ( select rownum AS "rn", t1.* from (select * from "USERS") t1 ) t2 where t2."rn" between 1 and 0',
$builder->toSql());
'select * from (select "USERS".*, row_number() over (order by ROWID) as rn from (select * from "USERS") "USERS") "USERS" where rn between 1 and 0',
$builder->toSql()
);

$builder = $this->getBuilder();
$builder->select('*')->from('users')->forPage(0, 0);
$this->assertSame(
'select t2.* from ( select rownum AS "rn", t1.* from (select * from "USERS") t1 ) t2 where t2."rn" between 1 and 0',
$builder->toSql());
'select * from (select "USERS".*, row_number() over (order by ROWID) as rn from (select * from "USERS") "USERS") "USERS" where rn between 1 and 0',
$builder->toSql()
);

$builder = $this->getBuilder();
$builder->select('*')->from('users')->forPage(-2, 0);
$this->assertSame(
'select t2.* from ( select rownum AS "rn", t1.* from (select * from "USERS") t1 ) t2 where t2."rn" between 1 and 0',
$builder->toSql());
'select * from (select "USERS".*, row_number() over (order by ROWID) as rn from (select * from "USERS") "USERS") "USERS" where rn between 1 and 0',
$builder->toSql()
);
}

public function testGetCountForPaginationWithBindings()
Expand Down
Loading