Skip to content

Commit

Permalink
Merge pull request #3008 from Arkhas/optimize_count_queries
Browse files Browse the repository at this point in the history
feat: Optimize countQuery with complex select
  • Loading branch information
yajra committed Jun 27, 2023
2 parents 1c42c78 + 4f403dd commit 65d89f2
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 5 deletions.
36 changes: 33 additions & 3 deletions src/QueryDataTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Illuminate\Database\Query\Expression;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Yajra\DataTables\Utilities\Helper;

Expand Down Expand Up @@ -48,6 +49,13 @@ class QueryDataTable extends DataTableAbstract
*/
protected bool $keepSelectBindings = false;

/**
* Flag to ignore the selects in count query.
*
* @var bool
*/
protected bool $ignoreSelectInCountQuery = false;

/**
* @param QueryBuilder $builder
*/
Expand Down Expand Up @@ -156,10 +164,20 @@ public function prepareCountQuery(): QueryBuilder
$builder = clone $this->query;

if ($this->isComplexQuery($builder)) {
$builder->select(DB::raw('1'));
if ($this->ignoreSelectInCountQuery || ! $this->isComplexQuery($builder)) {

Check failure on line 168 in src/QueryDataTable.php

View workflow job for this annotation

GitHub Actions / Static Analysis with PHPStan (8.1, prefer-stable)

Negated boolean expression is always false.
return $this->getConnection()
->query()
->fromRaw('('.$builder->toSql().') count_row_table')
->setBindings($builder->getBindings());
}

$builder = clone $this->query;

return $this->getConnection()
->query()
->fromRaw('('.$builder->toSql().') count_row_table')
->setBindings($builder->getBindings());
->query()
->fromRaw('('.$builder->toSql().') count_row_table')
->setBindings($builder->getBindings());
}

$row_count = $this->wrap('row_count');
Expand Down Expand Up @@ -818,4 +836,16 @@ public function getFilteredQuery(): QueryBuilder

return $this->getQuery();
}

/**
* Ignore the selects in count query.
*
* @return $this
*/
public function ignoreSelectsInCountQuery(): static
{
$this->ignoreSelectInCountQuery = true;

return $this;
}
}
87 changes: 85 additions & 2 deletions tests/Unit/QueryDataTableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,69 @@ public function test_complex_query_are_wrapped_and_countable()
);

$this->assertQueryWrapped(true, $dataTable->prepareCountQuery());
$this->assertQueryHasNoSelect(false, $dataTable->prepareCountQuery());
$this->assertEquals(60, $dataTable->count());
}

public function test_complex_query_use_select_in_count()
{
/** @var \Yajra\DataTables\QueryDataTable $dataTable */
$dataTable = app('datatables')->of(
DB::table('users')
->select('users.*')
->addSelect([
'last_post_id' => DB::table('posts')
->whereColumn('posts.user_id', 'users.id')
->orderBy('created_at')
->select('id'),
])
->orderBy(DB::table('posts')->whereColumn('posts.user_id', 'users.id')->orderBy('created_at')->select('created_at')
)
);

$this->assertQueryHasNoSelect(false, $dataTable->prepareCountQuery());
$this->assertEquals(20, $dataTable->count());
}

public function test_complex_query_can_ignore_select_in_count()
{
/** @var \Yajra\DataTables\QueryDataTable $dataTable */
$dataTable = app('datatables')->of(
DB::table('users')
->select('users.*')
->addSelect([
'last_post_id' => DB::table('posts')
->whereColumn('posts.user_id', 'users.id')
->orderBy('created_at')
->select('id'),
])
->orderBy(DB::table('posts')->whereColumn('posts.user_id', 'users.id')->orderBy('created_at')->select('created_at')
)
)->ignoreSelectsInCountQuery();

$this->assertQueryHasNoSelect(true, $dataTable->prepareCountQuery());
$this->assertEquals(20, $dataTable->count());
}

public function test_simple_queries_with_complexe_select_are_wrapped_without_selects()
{
/** @var \Yajra\DataTables\QueryDataTable $dataTable */
$dataTable = app('datatables')->of(
DB::table('users')
->select('users.*')
->addSelect([
'last_post_id' => DB::table('posts')
->whereColumn('posts.user_id', 'users.id')
->orderBy('created_at')
->select('id'),
])
);

$this->assertQueryWrapped(true, $dataTable->prepareCountQuery());
$this->assertQueryHasNoSelect(true, $dataTable->prepareCountQuery());
$this->assertEquals(20, $dataTable->count());
}

public function test_simple_queries_are_not_wrapped_and_countable()
{
/** @var \Yajra\DataTables\QueryDataTable $dataTable */
Expand All @@ -42,9 +102,20 @@ public function test_simple_queries_are_not_wrapped_and_countable()
$this->assertEquals(20, $dataTable->count());
}

public function test_complexe_queries_can_be_wrapped_and_countable()
{
/** @var \Yajra\DataTables\QueryDataTable $dataTable */
$dataTable = app('datatables')->of(
User::with('posts')->select('users.*')
);

$this->assertQueryWrapped(false, $dataTable->prepareCountQuery());
$this->assertEquals(20, $dataTable->count());
}

/**
* @param $expected bool
* @param $query \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
* @param $expected bool
* @param $query \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
* @return void
*/
protected function assertQueryWrapped($expected, $query)
Expand All @@ -53,4 +124,16 @@ protected function assertQueryWrapped($expected, $query)

$this->assertSame($expected, Str::endsWith($sql, 'count_row_table'), "'{$sql}' is not wrapped");
}

/**
* @param $expected bool
* @param $query \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
* @return void
*/
public function assertQueryHasNoSelect($expected, $query)
{
$sql = $query->toSql();

$this->assertSame($expected, Str::startsWith($sql, 'select * from (select 1 from'), "'{$sql}' is not wrapped");
}
}

0 comments on commit 65d89f2

Please sign in to comment.