diff --git a/src/Oci8/Query/Grammars/OracleGrammar.php b/src/Oci8/Query/Grammars/OracleGrammar.php index 76560c17..c8af4918 100644 --- a/src/Oci8/Query/Grammars/OracleGrammar.php +++ b/src/Oci8/Query/Grammars/OracleGrammar.php @@ -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; @@ -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}"; } /** diff --git a/tests/Database/Oci8QueryBuilderTest.php b/tests/Database/Oci8QueryBuilderTest.php index fbebaaff..072b0c37 100644 --- a/tests/Database/Oci8QueryBuilderTest.php +++ b/tests/Database/Oci8QueryBuilderTest.php @@ -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()); @@ -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()); @@ -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() ); } @@ -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() ); } @@ -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() ); } @@ -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()