diff --git a/README.md b/README.md index 171b2b8..a88c03d 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,14 @@ Adjust the configuration file to suite your application. // i.e Console command or Request ], ], + 'ignorable_tables' => [ + 'jobs' // Do you want to capture queries on specific tables? + // If you are utilizing the database queue driver, you need to + // ignore the jobs table or you'll get infinite capture loops. + ], + 'ignorable_statements' => [ + 'create' // Do you want to ignore specific SQL statements? + ] ], 'listener' => [ // Channel notifications are queued 'connection' => 'sync', // Define what connection to use. diff --git a/config/querywatcher.php b/config/querywatcher.php index 6f65163..274994e 100644 --- a/config/querywatcher.php +++ b/config/querywatcher.php @@ -17,6 +17,12 @@ 'enabled' => true, ], ], + 'ignorable_tables' => [ + 'jobs', + ], + 'ignorable_statements' => [ + 'create', + ], ], 'listener' => [ 'connection' => 'sync', diff --git a/src/QueryWatcher.php b/src/QueryWatcher.php index 03ece6e..6209f47 100644 --- a/src/QueryWatcher.php +++ b/src/QueryWatcher.php @@ -2,6 +2,7 @@ namespace YorCreative\QueryWatcher; +use Illuminate\Database\Events\QueryExecuted; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\DB; use YorCreative\QueryWatcher\Events\QueryEvent; @@ -18,7 +19,14 @@ public static function listen(): void $time_exceeds_ms_enabled = self::timeExceedsMsEnabled(); if ($time_exceeds_ms_enabled && self::getTimeExceedsMs() < $query->time - || ! $time_exceeds_ms_enabled) { + || ! $time_exceeds_ms_enabled + && ! self::ignorable($query)) { + // only capture queries if: + // - time_exceeds_ms is not enabled + // - time_exceeds_ms is enabled and query time exceeds the threshold. + // - query isn't on table that is ignorable. + // - query isn't a sql statement that is ignorable + event(new QueryEvent($query)); } }); @@ -44,4 +52,27 @@ public static function getTimeExceedsMs(): ?float { return config('querywatcher.scope.time_exceeds_ms.threshold') ?? 0; } + + /** + * @param QueryExecuted $queryExecuted + * @return bool + */ + public static function ignorable(QueryExecuted $queryExecuted): bool + { + // check if query is on table that is ignorable + foreach (Config::get('querywatcher.scope.ignorable_tables') as $table) { + if (preg_match('~(from "'.$table.'"|update "'.$table.'"|into "'.$table.'")~i', $queryExecuted->sql)) { + return true; + } + } + + // check if query includes ignorable statement + foreach (Config::get('querywatcher.scope.ignorable_statements') as $statement) { + if (str_starts_with(strtolower($queryExecuted->sql), strtolower($statement))) { + return true; + } + } + + return false; + } } diff --git a/tests/Feature/CaptureQueryTest.php b/tests/Feature/CaptureQueryTest.php index b42404b..2939cf5 100644 --- a/tests/Feature/CaptureQueryTest.php +++ b/tests/Feature/CaptureQueryTest.php @@ -4,6 +4,7 @@ use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Http; use YorCreative\QueryWatcher\Events\QueryEvent; @@ -23,6 +24,73 @@ public function setUp(): void self::trackQueries(); } + /** + * @test + * @group Feature + */ + public function it_can_ignore_a_query_by_ignorable_table_scope() + { + Event::fake(); + + Config::set('querywatcher.scope.ignorable_tables', [ + 'tests', + ]); + + (new Test()) + ->newQuery() + ->create([ + 'field' => 'testing', + ]); + + (new Test()) + ->newQuery() + ->get(); + + (new Test()) + ->newQuery() + ->where('field', 'testing') + ->update([ + 'field' => 'okay', + ]); + + Event::assertNotDispatched(QueryEvent::class); + + $this->assertQueryCountMatches(3); + } + + /** + * @test + * @group Feature + */ + public function it_can_ignore_a_query_by_ignorable_statement_scope() + { + HTTP::fake(); + + Config::set('querywatcher.scope.ignorable_statements', [ + 'delete', + ]); + + (new Test()) + ->newQuery() + ->create([ + 'field' => 'testing', + ]); + + $tests = (new Test()) + ->newQuery() + ->get(); + + $tests->first()->delete(); + + $this->assertEventBroadcasted( + 'query.event', + null, + 2 + ); + + $this->assertQueryCountMatches(3); + } + /** * @test * @group Feature