From 060f55bed4727a951674469080372c786cc10e08 Mon Sep 17 00:00:00 2001 From: a-komarev Date: Sun, 7 Oct 2018 20:52:42 +0300 Subject: [PATCH] Add QueryExplanation --- CHANGELOG.md | 7 ++ README.md | 2 +- ...se_request_db_query_explanations_table.php | 46 ++++++++++ resources/views/queries/index.blade.php | 8 ++ resources/views/queries/view.blade.php | 89 +++++++++++++++++++ routes/web.php | 1 + src/Db/Query/Models/Query.php | 7 ++ .../Models/QueryExplanation.php | 32 +++++++ src/Http/Controllers/Queries/Get/Action.php | 32 +++++++ src/Http/Controllers/Queries/Get/Request.php | 40 +++++++++ src/Http/Controllers/Queries/Get/Response.php | 46 ++++++++++ src/Providers/SenseServiceProvider.php | 28 +++++- 12 files changed, 336 insertions(+), 2 deletions(-) create mode 100644 database/migrations/2018_09_24_003000_create_sense_request_db_query_explanations_table.php create mode 100644 resources/views/queries/view.blade.php create mode 100644 src/Db/QueryExplanation/Models/QueryExplanation.php create mode 100644 src/Http/Controllers/Queries/Get/Action.php create mode 100644 src/Http/Controllers/Queries/Get/Request.php create mode 100644 src/Http/Controllers/Queries/Get/Response.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a65b69..146d6dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to `laravel-sense` will be documented in this file. +## [0.5.0] - 2018-10-07 + +### Added + +- `QueryExplanation` model added ([#19](https://github.com/cybercog/laravel-sense/pull/19)) + ## [0.4.0] - 2018-10-07 ### Added @@ -38,6 +44,7 @@ All notable changes to `laravel-sense` will be documented in this file. - Initial release +[0.5.0]: https://github.com/cybercog/laravel-sense/compare/0.4.0...0.5.0 [0.4.0]: https://github.com/cybercog/laravel-sense/compare/0.3.0...0.4.0 [0.3.0]: https://github.com/cybercog/laravel-sense/compare/0.2.1...0.3.0 [0.2.1]: https://github.com/cybercog/laravel-sense/compare/0.2.0...0.2.1 diff --git a/README.md b/README.md index 3f0b339..db8d459 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ If you discover any security related issues, please email open@cybercog.su inste ## Alternatives - [barryvdh/laravel-debugbar](https://github.com/barryvdh/laravel-debugbar) -- [itsgoingd/clockwork](itsgoingd/clockwork) +- [itsgoingd/clockwork](https://github.com/itsgoingd/clockwork) *Feel free to add more alternatives as Pull Request.* diff --git a/database/migrations/2018_09_24_003000_create_sense_request_db_query_explanations_table.php b/database/migrations/2018_09_24_003000_create_sense_request_db_query_explanations_table.php new file mode 100644 index 0000000..363e362 --- /dev/null +++ b/database/migrations/2018_09_24_003000_create_sense_request_db_query_explanations_table.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +use Illuminate\Support\Facades\Schema; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Migrations\Migration; + +class CreateSenseRequestDbQueryExplanationsTable extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up(): void + { + Schema::create('sense_request_db_query_explanations', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->unsignedBigInteger('query_id'); + $table->json('result'); + $table->timestamps(); + + $table->index('query_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + Schema::dropIfExists('sense_request_db_query_explanations'); + } +} diff --git a/resources/views/queries/index.blade.php b/resources/views/queries/index.blade.php index 82390db..624b7f3 100644 --- a/resources/views/queries/index.blade.php +++ b/resources/views/queries/index.blade.php @@ -19,6 +19,9 @@ Time + + Explain + Created at @@ -45,6 +48,11 @@ {{ $query->time }} + + + EXPLAIN + + {{ $query->created_at }} diff --git a/resources/views/queries/view.blade.php b/resources/views/queries/view.blade.php new file mode 100644 index 0000000..a8beb2c --- /dev/null +++ b/resources/views/queries/view.blade.php @@ -0,0 +1,89 @@ +@extends('sense::app') + +@section('content') +

Query

+ + + + + + + + + + + + + + + + + + + + + + +
+ # + + Correlation ID + + Connection + + SQL + + Time + + Explain + + Created at + + Updated at +
+ {{ $query->id }} + + + {{ $query->request->correlation_id }} + + + {{ $query->connection }} + + {{ $query->sql }} + + {{ $query->time }} + + + EXPLAIN + + + {{ $query->created_at }} + + {{ $query->updated_at }} +
+ +

Query Explain

+ + @if (count($query->explanation) > 0) + @foreach($query->explanation->result as $explain) + + + + + + @foreach ($explain as $key => $value) + + + + + @endforeach +
KeyValue
+ {{ $key }} + + {{ $value ?? '-' }} +
+ @endforeach + @else +

There are no explains for this query.

+ @endif +@endsection diff --git a/routes/web.php b/routes/web.php index 097269e..b41ef3f 100644 --- a/routes/web.php +++ b/routes/web.php @@ -25,5 +25,6 @@ Route::get('/urls/{url}')->uses('Urls\Get\Action'); Route::get('/queries')->uses('Queries\Collect\Action'); +Route::get('/queries/{query}')->uses('Queries\Get\Action'); Route::get('/{view?}', 'AppController')->where('view', '(.*)')->name('sense.index'); diff --git a/src/Db/Query/Models/Query.php b/src/Db/Query/Models/Query.php index d4027b8..dfd18cc 100644 --- a/src/Db/Query/Models/Query.php +++ b/src/Db/Query/Models/Query.php @@ -13,10 +13,12 @@ namespace Cog\Laravel\Sense\Db\Query\Models; +use Cog\Laravel\Sense\Db\QueryExplanation\Models\QueryExplanation; use Cog\Laravel\Sense\Request\Models\Request; use Cog\Laravel\Sense\Statement\Models\Statement; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasOne; class Query extends Model { @@ -45,4 +47,9 @@ public function statement(): BelongsTo { return $this->belongsTo(Statement::class, 'statement_id'); } + + public function explanation(): HasOne + { + return $this->hasOne(QueryExplanation::class, 'query_id'); + } } diff --git a/src/Db/QueryExplanation/Models/QueryExplanation.php b/src/Db/QueryExplanation/Models/QueryExplanation.php new file mode 100644 index 0000000..92aa940 --- /dev/null +++ b/src/Db/QueryExplanation/Models/QueryExplanation.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Cog\Laravel\Sense\Db\QueryExplanation\Models; + +use Illuminate\Database\Eloquent\Model; + +class QueryExplanation extends Model +{ + protected $connection = 'sense'; + + protected $table = 'sense_request_db_query_explanations'; + + protected $fillable = [ + 'query_id', + 'result', + ]; + + protected $casts = [ + 'result' => 'json', + ]; +} diff --git a/src/Http/Controllers/Queries/Get/Action.php b/src/Http/Controllers/Queries/Get/Action.php new file mode 100644 index 0000000..e3ae144 --- /dev/null +++ b/src/Http/Controllers/Queries/Get/Action.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Cog\Laravel\Sense\Http\Controllers\Queries\Get; + +use Cog\Laravel\Sense\Db\Query\Models\Query; +use Cog\Laravel\Sense\Http\Controllers\Controller; + +class Action extends Controller +{ + public function __invoke($query, Request $request) + { + $query = Query::query()->whereKey($query); + + $query->with([ + ]); + + $queryModel = $query->firstOrFail(); + + return new Response($queryModel); + } +} diff --git a/src/Http/Controllers/Queries/Get/Request.php b/src/Http/Controllers/Queries/Get/Request.php new file mode 100644 index 0000000..d081fc8 --- /dev/null +++ b/src/Http/Controllers/Queries/Get/Request.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Cog\Laravel\Sense\Http\Controllers\Queries\Get; + +use Illuminate\Foundation\Http\FormRequest; + +class Request extends FormRequest +{ + /** + * Determine if the user is authorized to make this request. + * + * @return bool + */ + public function authorize(): bool + { + return true; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules(): array + { + return [ + ]; + } +} diff --git a/src/Http/Controllers/Queries/Get/Response.php b/src/Http/Controllers/Queries/Get/Response.php new file mode 100644 index 0000000..dc7d383 --- /dev/null +++ b/src/Http/Controllers/Queries/Get/Response.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Cog\Laravel\Sense\Http\Controllers\Queries\Get; + +use Illuminate\Contracts\Support\Responsable; + +class Response implements Responsable +{ + /** + * @var \Cog\Laravel\Sense\Db\Query\Models\Query + */ + private $query; + + public function __construct($query) + { + $this->query = $query; + } + + public function toResponse($request) + { + return $request->wantsJson() ? $this->toJson() : $this->toHtml(); + } + + private function toHtml() + { + return view('sense::queries.view', [ + 'query' => $this->query, + ]); + } + + private function toJson() + { + return []; + } +} diff --git a/src/Providers/SenseServiceProvider.php b/src/Providers/SenseServiceProvider.php index dd03e10..056106f 100644 --- a/src/Providers/SenseServiceProvider.php +++ b/src/Providers/SenseServiceProvider.php @@ -13,6 +13,7 @@ namespace Cog\Laravel\Sense\Providers; +use Cog\Laravel\Sense\Db\Query\Models\Query; use Cog\Laravel\Sense\Request\Id; use Cog\Laravel\Sense\Request\Models\Request; use Cog\Laravel\Sense\RequestSummary\Models\RequestSummary; @@ -23,6 +24,7 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; +use PDO; class SenseServiceProvider extends ServiceProvider { @@ -147,7 +149,8 @@ private function storeStatement(Request $request, QueryExecuted $query): void 'value' => $query->sql, ]); - $request->dbQueries()->create([ + /** @var \Cog\Laravel\Sense\Db\Query\Models\Query $queryModel */ + $queryModel = $request->dbQueries()->create([ 'statement_id' => $statement->getKey(), 'connection' => $query->connectionName, 'sql' => $this->buildSqlString($query), @@ -155,6 +158,8 @@ private function storeStatement(Request $request, QueryExecuted $query): void 'time' => $query->time, ]); + $this->explainQuery($queryModel, $query); + /** @var \Cog\Laravel\Sense\StatementSummary\Models\StatementSummary $summary */ $summary = $statement->summaries() ->where([ @@ -211,6 +216,27 @@ private function buildSqlString(QueryExecuted $query): string return $sql; } + private function explainQuery(Query $queryModel, QueryExecuted $query): void + { + $explainTypes = [ + 'select', +// 'insert', +// 'update', +// 'delete', + ]; + if (starts_with(strtolower($query->sql), $explainTypes)) { + $pdo = $query->connection->getPdo(); + $bindings = $query->connection->prepareBindings($query->bindings); + $statement = $pdo->prepare('EXPLAIN ' . $query->sql); + $statement->execute($bindings); + $explanation = $statement->fetchAll(PDO::FETCH_CLASS); + + $queryModel->explanation()->create([ + 'result' => $explanation, + ]); + } + } + private function isQueryShouldBeStored(QueryExecuted $query): bool { return $query->connectionName !== 'sense';