Skip to content

Commit

Permalink
feat(query): postgresql specific explain output
Browse files Browse the repository at this point in the history
  • Loading branch information
tpetry committed Dec 16, 2021
1 parent 24ede3b commit be2d643
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.7.0] - 2021-12-16
### Added
* PostgreSQL specific explain output on Query\Builder instances

## [0.6.1] - 2021-10-28
### Fixed
- Zero Downtime Migration support for Laravel 6.x and 7.x
Expand Down
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ composer require tpetry/laravel-postgresql-enhanced
- [Zero Downtime Migration](#zero-downtime-migration)
- [Extensions](#extensions)
- [Views](#views)
- [Explain](#explain)
- [Indexes](#indexes)
- [Partial Indexes](#partial-indexes)
- [Include Columns](#include-columns)
Expand Down Expand Up @@ -161,6 +162,48 @@ Schema::dropView('myview1', 'myview2');
Schema::dropViewIfExists('myview1', 'myview2');
```

### Explain
Laravel has the ability to get the database query plan for any query you are building. Just calling `explain()` on the query will get a collection with the query plan.

This behaviour has been extended to be more PostgreSQL specific. There are multiple (optional) parameters for the [explain statement](https://www.postgresql.org/docs/current/sql-explain.html), different for every version. The enhanced PostgreSQL driver will automatically activate all options available for your PostgreSQL version.

```php
DB::table('migrations')->where('batch', 1)->explain()->dd();

// Output:
// array:1 [
// 0 => """
// Seq Scan on public.migrations (cost=0.00..11.75 rows=1 width=524)\n
// Output: id, migration, batch\n
// Filter: (migrations.batch = 1)\n
// Settings: search_path = 'public'\n
// Planning Time: 0.370 ms
// """
//]
```

Additionally, you can also get the query plan with executing the query. The query plan will be extended by valuable runtime information like per-operation timing and buffer read/write statistics:

```php
DB::table('migrations')->where('batch', 1)->explain(analyze:true)->dd();

// Output:
// array:1 [
// 0 => """
// Seq Scan on public.migrations (cost=0.00..11.75 rows=1 width=524) (actual time=0.014..0.031 rows=1 loops=1)\n
// Output: id, migration, batch\n
// Filter: (migrations.batch = 1)\n
// Buffers: shared hit=1\n
// Settings: search_path = 'public'\n
// Planning:\n
// Buffers: shared hit=61\n
// Planning Time: 0.282 ms\n
// Execution Time: 0.100 ms
// """
//]
```
**NOTE: The PostgreSQL-specific query plan is currently limited to `Query\Builder` instances, for `Eloquent\Builder` instances [PR #40075](https://github.com/laravel/framework/pull/40075) needs to be merged to Laravel or you have to use `$query->toBase()` for transforming an eloquent query builder to a base query builder.**

### Indexes

#### Unique Indexes
Expand Down
15 changes: 12 additions & 3 deletions src/PostgresEnhancedConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
use Illuminate\Database\QueryException;
use Illuminate\Support\Str;
use Throwable;
use Tpetry\PostgresqlEnhanced\Schema\Builder;
use Tpetry\PostgresqlEnhanced\Query\Builder as QueryBuilder;
use Tpetry\PostgresqlEnhanced\Schema\Builder as SchemaBuilder;
use Tpetry\PostgresqlEnhanced\Schema\Grammars\Grammar;
use Tpetry\PostgresqlEnhanced\Support\Helpers\ZeroDowntimeMigrationSupervisor;

Expand All @@ -21,13 +22,21 @@ class PostgresEnhancedConnection extends PostgresConnection
/**
* Get a schema builder instance for the connection.
*/
public function getSchemaBuilder(): Builder
public function getSchemaBuilder(): SchemaBuilder
{
if (null === $this->schemaGrammar) {
$this->useDefaultSchemaGrammar();
}

return new Builder($this);
return new SchemaBuilder($this);
}

/**
* Get a new query builder instance.
*/
public function query(): QueryBuilder
{
return new QueryBuilder($this, $this->getQueryGrammar(), $this->getPostProcessor());
}

/**
Expand Down
34 changes: 34 additions & 0 deletions src/Query/Builder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Tpetry\PostgresqlEnhanced\Query;

use Illuminate\Database\Query\Builder as BaseBuilder;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;

class Builder extends BaseBuilder
{
public function explain(bool $analyze = false): Collection
{
$version = $this->getConnection()->selectOne('SHOW server_version')->server_version;
$options = match (true) {
$analyze && version_compare($version, '13') >= 0 => 'ANALYZE TRUE, BUFFERS TRUE, SETTINGS TRUE, VERBOSE TRUE, WAL TRUE',
$analyze && version_compare($version, '12') >= 0 => 'ANALYZE TRUE, BUFFERS TRUE, SETTINGS TRUE, VERBOSE TRUE',
$analyze => 'ANALYZE TRUE, BUFFERS TRUE, VERBOSE TRUE',
version_compare($version, '12') >= 0 => 'SETTINGS TRUE, SUMMARY TRUE, VERBOSE TRUE',
default => 'SUMMARY TRUE, VERBOSE TRUE',
};

return (new Collection($this->getConnection()->select("EXPLAIN ({$options}) {$this->toSql()}", $this->getBindings())))
->map(fn ($row) => Arr::first($row))
->reduce(function (?Collection $carry, string $item) {
if (null === $carry) {
return new Collection([$item]);
}

return new Collection(["{$carry[0]}\n{$item}"]);
});
}
}

0 comments on commit be2d643

Please sign in to comment.