-
Notifications
You must be signed in to change notification settings - Fork 60
Capture query connection type (read or write) #281
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
base: 1.x
Are you sure you want to change the base?
Changes from 6 commits
64a6cc4
63bcdfd
d931ad0
dceddd7
edc39b9
de3c09c
69035c4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,13 +2,15 @@ | |
|
||
namespace Laravel\Nightwatch\Sensors; | ||
|
||
use Illuminate\Database\Connection; | ||
use Illuminate\Database\Events\QueryExecuted; | ||
use Laravel\Nightwatch\Clock; | ||
use Laravel\Nightwatch\Location; | ||
use Laravel\Nightwatch\Records\Query; | ||
use Laravel\Nightwatch\State\CommandState; | ||
use Laravel\Nightwatch\State\RequestState; | ||
use Laravel\Nightwatch\Types\Str; | ||
use PDO; | ||
|
||
use function hash; | ||
use function in_array; | ||
|
@@ -46,6 +48,7 @@ public function __invoke(QueryExecuted $event, array $trace): array | |
line: $line ?? 0, | ||
duration: $durationInMicroseconds, | ||
connection: $event->connectionName ?? '', // @phpstan-ignore nullCoalesce.property | ||
connectionType: $this->connectionType($event) ?? '', | ||
), | ||
function () use ($event, $record) { | ||
$this->executionState->queries++; | ||
|
@@ -68,6 +71,7 @@ function () use ($event, $record) { | |
'line' => $record->line, | ||
'duration' => $record->duration, | ||
'connection' => Str::tinyText($record->connection), | ||
'connection_type' => $record->connectionType, | ||
]; | ||
}, | ||
]; | ||
|
@@ -87,4 +91,34 @@ private function hash(QueryExecuted $event, Query $record): string | |
|
||
return hash('xxh128', "{$record->connection},{$sql}"); | ||
} | ||
|
||
/** | ||
* Get the read or write connection type if configured. | ||
* | ||
* @return 'read'|'write'|null | ||
*/ | ||
private function connectionType(QueryExecuted $event): ?string | ||
{ | ||
$connection = $event->connection; | ||
$readPdo = $connection->getRawReadPdo(); | ||
$writePdo = $connection->getRawPdo(); | ||
|
||
if ($readPdo === null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
return null; | ||
} | ||
|
||
if (! $readPdo instanceof PDO) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When |
||
return 'write'; | ||
} | ||
|
||
if (! $writePdo instanceof PDO) { | ||
return 'read'; | ||
} | ||
|
||
if ($connection->getReadPdo() === $writePdo) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
return 'write'; | ||
} | ||
|
||
return 'read'; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,9 +21,11 @@ | |
use SingleStore\Laravel\Connect\SingleStoreConnection; | ||
use Tests\TestCase; | ||
|
||
use function array_merge; | ||
use function base64_encode; | ||
use function class_exists; | ||
use function dirname; | ||
use function fake; | ||
use function hash; | ||
use function hex2bin; | ||
use function in_array; | ||
|
@@ -93,6 +95,7 @@ public function test_it_can_ingest_queries(): void | |
'line' => $line, | ||
'duration' => 4321, | ||
'connection' => $connection, | ||
'connection_type' => '', | ||
], | ||
]); | ||
} | ||
|
@@ -352,4 +355,201 @@ public function test_it_can_capture_null_connection_name() | |
$ingest->assertWrittenTimes(1); | ||
$ingest->assertLatestWrite('query:0.connection', ''); | ||
} | ||
|
||
public function test_it_captures_connection_type_as_empty_string_when_read_and_write_connections_are_not_configured() | ||
{ | ||
$ingest = $this->fakeIngest(); | ||
|
||
Route::get('/users', function () { | ||
return DB::table('users')->get(); | ||
}); | ||
|
||
$response = $this->get('/users'); | ||
|
||
$response->assertOk(); | ||
$ingest->assertWrittenTimes(1); | ||
$ingest->assertLatestWrite('query:0.connection_type', ''); | ||
} | ||
|
||
public function test_it_captures_connection_type_as_read_for_select_query() | ||
{ | ||
$this->configureReadWriteConnection(); | ||
|
||
$ingest = $this->fakeIngest(); | ||
|
||
Route::get('/users', function () { | ||
return DB::table('users')->get(); | ||
}); | ||
|
||
$response = $this->get('/users'); | ||
|
||
$response->assertOk(); | ||
$ingest->assertWrittenTimes(1); | ||
$ingest->assertLatestWrite('query:0.connection_type', 'read'); | ||
} | ||
|
||
public function test_it_captures_connection_type_as_write_for_write_query() | ||
{ | ||
$this->configureReadWriteConnection(); | ||
|
||
$ingest = $this->fakeIngest(); | ||
|
||
Route::get('/users', function () { | ||
return DB::table('users')->insert([ | ||
'name' => fake()->name(), | ||
'email' => fake()->email(), | ||
'password' => fake()->password(), | ||
]); | ||
}); | ||
|
||
$response = $this->get('/users'); | ||
|
||
$response->assertOk(); | ||
$ingest->assertWrittenTimes(1); | ||
$ingest->assertLatestWrite('query:0.connection_type', 'write'); | ||
} | ||
|
||
public function test_it_captures_connection_type_as_write_when_records_have_been_modified_and_sticky_connection_is_enabled() | ||
{ | ||
$this->configureReadWriteConnection(['sticky' => true]); | ||
|
||
$ingest = $this->fakeIngest(); | ||
|
||
Route::get('/users', function () { | ||
DB::table('users')->insert([ | ||
'name' => fake()->name(), | ||
'email' => fake()->email(), | ||
'password' => fake()->password(), | ||
]); | ||
|
||
return DB::table('users')->get(); | ||
}); | ||
|
||
$response = $this->get('/users'); | ||
|
||
$response->assertOk(); | ||
$ingest->assertWrittenTimes(1); | ||
$ingest->assertLatestWrite('query:0.connection_type', 'write'); // insert | ||
$ingest->assertLatestWrite('query:1.connection_type', 'write'); // select | ||
} | ||
|
||
public function test_it_captures_connection_type_as_write_for_insert_and_read_for_select_when_sticky_connection_is_disabled() | ||
{ | ||
$this->configureReadWriteConnection(['sticky' => false]); | ||
|
||
$ingest = $this->fakeIngest(); | ||
|
||
Route::get('/users', function () { | ||
DB::table('users')->insert([ | ||
'name' => fake()->name(), | ||
'email' => fake()->email(), | ||
'password' => fake()->password(), | ||
]); | ||
|
||
return DB::table('users')->get(); | ||
}); | ||
|
||
$response = $this->get('/users'); | ||
|
||
$response->assertOk(); | ||
$ingest->assertWrittenTimes(1); | ||
$ingest->assertLatestWrite('query:0.connection_type', 'write'); // insert | ||
$ingest->assertLatestWrite('query:1.connection_type', 'read'); // select | ||
} | ||
|
||
public function test_it_captures_connection_type_as_write_when_it_should_use_write_connection_when_reading() | ||
{ | ||
$this->configureReadWriteConnection(); | ||
|
||
$ingest = $this->fakeIngest(); | ||
|
||
Route::get('/users', function () { | ||
return DB::useWriteConnectionWhenReading()->table('users')->get(); | ||
}); | ||
|
||
$response = $this->get('/users'); | ||
|
||
$response->assertOk(); | ||
$ingest->assertWrittenTimes(1); | ||
$ingest->assertLatestWrite('query:0.connection_type', 'write'); | ||
} | ||
|
||
public function test_it_captures_connection_type_as_write_when_in_a_transaction() | ||
{ | ||
$this->configureReadWriteConnection(); | ||
|
||
$ingest = $this->fakeIngest(); | ||
|
||
Route::get('/users', function () { | ||
DB::beginTransaction(); | ||
|
||
$users = DB::table('users')->get(); | ||
|
||
DB::rollBack(); | ||
|
||
return $users; | ||
}); | ||
|
||
$response = $this->get('/users'); | ||
|
||
$response->assertOk(); | ||
$ingest->assertWrittenTimes(1); | ||
$ingest->assertLatestWrite('query:0.connection_type', 'write'); | ||
} | ||
|
||
public function test_it_captures_write_connection_when_forcing_select_from_write_after_read_pdo_is_resolved() | ||
{ | ||
$this->configureReadWriteConnection(); | ||
|
||
$ingest = $this->fakeIngest(); | ||
|
||
Route::get('/users', function () { | ||
DB::select('select 1'); | ||
DB::selectFromWriteConnection('select 1'); | ||
}); | ||
|
||
$response = $this->get('/users'); | ||
|
||
$response->assertOk(); | ||
$ingest->assertWrittenTimes(1); | ||
$ingest->assertLatestWrite('query:0.connection_type', 'read'); | ||
$ingest->assertLatestWrite('query:0.connection_type', 'write'); | ||
|
||
} | ||
|
||
public function test_it_captures_connection_type_when_forgetting_modified_records_state() | ||
{ | ||
$this->configureReadWriteConnection(['sticky' => true]); | ||
|
||
$ingest = $this->fakeIngest(); | ||
|
||
Route::get('/users', function () { | ||
DB::statement('select 1'); | ||
DB::forgetRecordModificationState(); | ||
DB::select('select 1'); | ||
}); | ||
|
||
$response = $this->get('/users'); | ||
|
||
$response->assertOk(); | ||
$ingest->assertWrittenTimes(1); | ||
$ingest->assertLatestWrite('query:0.connection_type', 'write'); | ||
$ingest->assertLatestWrite('query:0.connection_type', 'read'); | ||
} | ||
|
||
private function configureReadWriteConnection(array $options = []): void | ||
{ | ||
$connection = Config::get('database.default'); | ||
$config = Config::get("database.connections.{$connection}"); | ||
|
||
Config::set("database.connections.{$connection}", array_merge($config, [ | ||
'read' => [ | ||
'database' => $config['database'], | ||
], | ||
'write' => [ | ||
'database' => $config['database'], | ||
], | ||
], $options)); | ||
|
||
DB::purge($connection); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a user API perspective in PHP land, I wonder if
null
would be a better value, rather than an empty string, to indicate that it is not configured.Going further, I also wonder if this should be an enum (which rules out
null
)?ConnectionType::Read; ConnectionType::Write; ConnectionType::NotConfigured; // dunno about this ??