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: Optimize countQuery with complex select #3008

Merged
merged 18 commits into from
Jun 27, 2023
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)) {
yajra marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -789,4 +807,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());
yajra marked this conversation as resolved.
Show resolved Hide resolved
$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");
}
}