From 29a58fe79517ba8ad17198f42bea79c781b6aa6e Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Mon, 22 Jul 2024 17:20:37 +0330 Subject: [PATCH 1/5] [11.x] Fix SQL Server tests (#52222) * fix slqsrv tests * fix date tests --- tests/Foundation/Testing/WormholeTest.php | 12 +++++++----- .../Database/DatabaseSchemaBlueprintTest.php | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/Foundation/Testing/WormholeTest.php b/tests/Foundation/Testing/WormholeTest.php index 17f286d7e377..da57ca2fd103 100644 --- a/tests/Foundation/Testing/WormholeTest.php +++ b/tests/Foundation/Testing/WormholeTest.php @@ -9,6 +9,13 @@ class WormholeTest extends TestCase { + protected function tearDown(): void + { + parent::tearDown(); + + Date::useDefault(); + } + public function testCanTravelBackToPresent() { // Preserve the timelines we want to compare the reality with... @@ -42,9 +49,6 @@ public function testCarbonImmutableCompatibility() // Assert the time travel was successful... $this->assertEquals($future->format('Y-m-d'), now()->format('Y-m-d')); - - // Restore the default Date Factory... - Date::useDefault(); } public function testItCanTravelByMicroseconds() @@ -57,7 +61,5 @@ public function testItCanTravelByMicroseconds() (new Wormhole(5))->microseconds(); $this->assertSame('2000-01-01 00:00:00.000006', Date::now()->format('Y-m-d H:i:s.u')); - - Date::useDefault(); } } diff --git a/tests/Integration/Database/DatabaseSchemaBlueprintTest.php b/tests/Integration/Database/DatabaseSchemaBlueprintTest.php index 6e755b41e93b..dbe7a470fade 100644 --- a/tests/Integration/Database/DatabaseSchemaBlueprintTest.php +++ b/tests/Integration/Database/DatabaseSchemaBlueprintTest.php @@ -514,7 +514,7 @@ public function testAddColumnNamedCreateWorks() }); Schema::table('users', function (Blueprint $table) { - $table->string('create'); + $table->string('create')->nullable(); }); $this->assertTrue(Schema::hasColumn('users', 'create')); From 1301d9c6d3c31faa838379f91455b78fbaacd976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Debrauwer?= Date: Mon, 22 Jul 2024 16:20:01 +0200 Subject: [PATCH 2/5] [11.x] Inspect exception of assertThrows (#52224) * Inspect exception of assertThrows * Update InteractsWithExceptionHandling.php --------- Co-authored-by: Taylor Otwell --- .../InteractsWithExceptionHandling.php | 13 +++++-- .../FoundationExceptionsHandlerTest.php | 36 +++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithExceptionHandling.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithExceptionHandling.php index 67aac9d97483..cb20d9768d27 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithExceptionHandling.php +++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithExceptionHandling.php @@ -5,6 +5,7 @@ use Closure; use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Support\Testing\Fakes\ExceptionHandlerFake; +use Illuminate\Support\Traits\ReflectsClosures; use Illuminate\Testing\Assert; use Illuminate\Validation\ValidationException; use Symfony\Component\Console\Application as ConsoleApplication; @@ -13,6 +14,8 @@ trait InteractsWithExceptionHandling { + use ReflectsClosures; + /** * The original exception handler. * @@ -169,18 +172,22 @@ public function renderForConsole($output, Throwable $e) * Assert that the given callback throws an exception with the given message when invoked. * * @param \Closure $test - * @param class-string<\Throwable> $expectedClass + * @param \Closure|class-string<\Throwable> $expectedClass * @param string|null $expectedMessage * @return $this */ - protected function assertThrows(Closure $test, string $expectedClass = Throwable::class, ?string $expectedMessage = null) + protected function assertThrows(Closure $test, string|Closure $expectedClass = Throwable::class, ?string $expectedMessage = null) { + [$expectedClass, $expectedClassCallback] = $expectedClass instanceof Closure + ? [$this->firstClosureParameterType($expectedClass), $expectedClass] + : [$expectedClass, null]; + try { $test(); $thrown = false; } catch (Throwable $exception) { - $thrown = $exception instanceof $expectedClass; + $thrown = $exception instanceof $expectedClass && ($expectedClassCallback === null || $expectedClassCallback($exception)); $actualMessage = $exception->getMessage(); } diff --git a/tests/Foundation/FoundationExceptionsHandlerTest.php b/tests/Foundation/FoundationExceptionsHandlerTest.php index 70ac2a96f9c9..c4f1044f1e55 100644 --- a/tests/Foundation/FoundationExceptionsHandlerTest.php +++ b/tests/Foundation/FoundationExceptionsHandlerTest.php @@ -533,6 +533,42 @@ public function testAssertExceptionIsThrown() if ($testFailed) { Assert::fail('assertThrows failed: non matching message are thrown.'); } + + $this->assertThrows(function () { + throw new CustomException('Some message.'); + }, function (CustomException $exception) { + return $exception->getMessage() === 'Some message.'; + }); + + try { + $this->assertThrows(function () { + throw new CustomException('Some message.'); + }, function (CustomException $exception) { + return false; + }); + $testFailed = true; + } catch (AssertionFailedError) { + $testFailed = false; + } + + if ($testFailed) { + Assert::fail('assertThrows failed: exception callback succeeded.'); + } + + try { + $this->assertThrows(function () { + throw new Exception('Some message.'); + }, function (CustomException $exception) { + return true; + }); + $testFailed = true; + } catch (AssertionFailedError) { + $testFailed = false; + } + + if ($testFailed) { + Assert::fail('assertThrows failed: non matching exceptions are thrown.'); + } } public function testItReportsDuplicateExceptions() From ee1166ca8ebdf3d7f0903c03e312d250c6582fca Mon Sep 17 00:00:00 2001 From: Caleb White Date: Mon, 22 Jul 2024 09:33:35 -0500 Subject: [PATCH 3/5] fix: Request::json() json errors when decoding empty string (#52204) --- src/Illuminate/Http/Request.php | 2 +- tests/Http/HttpRequestTest.php | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Request.php b/src/Illuminate/Http/Request.php index f80f74700dcd..40a61eff5736 100644 --- a/src/Illuminate/Http/Request.php +++ b/src/Illuminate/Http/Request.php @@ -405,7 +405,7 @@ public function get(string $key, mixed $default = null): mixed public function json($key = null, $default = null) { if (! isset($this->json)) { - $this->json = new InputBag((array) json_decode($this->getContent(), true)); + $this->json = new InputBag((array) json_decode($this->getContent() ?: '[]', true)); } if (is_null($key)) { diff --git a/tests/Http/HttpRequestTest.php b/tests/Http/HttpRequestTest.php index 9c6a5246343c..7c4f26872d5c 100644 --- a/tests/Http/HttpRequestTest.php +++ b/tests/Http/HttpRequestTest.php @@ -1606,4 +1606,14 @@ public function testItCanHaveObjectsInJsonPayload() $this->assertSame(['name' => 'Laravel'], $request->get('framework')); } + + public function testItDoesNotGenerateJsonErrorsForEmptyContent() + { + // clear any existing errors + json_encode(null); + + Request::create('', 'GET')->json(); + + $this->assertTrue(json_last_error() === JSON_ERROR_NONE); + } } From 73630525c97fd3ad2357710328aca87234cd180d Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Mon, 22 Jul 2024 12:02:41 -0300 Subject: [PATCH 4/5] [11.x] Reduce the number of queries with `Cache::many` and `Cache::putMany` methods in the database driver (#52209) * Adds integration test for database store multiget * Tweaks the test * Tweaks the test and adds the many test with expired keys * Adds test for the putMany method * Tweaks the test * Reduce the number of database calls in the many methods of the database store cache driver * Fix tests * Make the get and put use the many and putMany methods so the logic is the same * Adds a test for fetching many with associative arrays * Tweaks types and fixes docblocks on non-interface methods * formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Cache/DatabaseStore.php | 116 ++++++++++++++---- tests/Cache/CacheDatabaseStoreTest.php | 34 ++--- .../Database/DatabaseCacheStoreTest.php | 73 ++++++++++- 3 files changed, 187 insertions(+), 36 deletions(-) diff --git a/src/Illuminate/Cache/DatabaseStore.php b/src/Illuminate/Cache/DatabaseStore.php index d51c1f5781d1..caebbcc25477 100755 --- a/src/Illuminate/Cache/DatabaseStore.php +++ b/src/Illuminate/Cache/DatabaseStore.php @@ -9,12 +9,13 @@ use Illuminate\Database\PostgresConnection; use Illuminate\Database\QueryException; use Illuminate\Database\SqlServerConnection; +use Illuminate\Support\Arr; use Illuminate\Support\InteractsWithTime; use Illuminate\Support\Str; class DatabaseStore implements LockProvider, Store { - use InteractsWithTime, RetrievesMultipleKeys; + use InteractsWithTime; /** * The database connection instance. @@ -98,29 +99,56 @@ public function __construct(ConnectionInterface $connection, */ public function get($key) { - $prefixed = $this->prefix.$key; - - $cache = $this->table()->where('key', '=', $prefixed)->first(); + return $this->many([$key])[$key]; + } - // If we have a cache record we will check the expiration time against current - // time on the system and see if the record has expired. If it has, we will - // remove the records from the database table so it isn't returned again. - if (is_null($cache)) { - return; + /** + * Retrieve multiple items from the cache by key. + * + * Items not found in the cache will have a null value. + * + * @return array + */ + public function many(array $keys) + { + if (count($keys) === 0) { + return []; } - $cache = is_array($cache) ? (object) $cache : $cache; + $results = array_fill_keys($keys, null); + + // First we will retrieve all of the items from the cache using their keys and + // the prefix value. Then we will need to iterate through each of the items + // and convert them to an object when they are currently in array format. + $values = $this->table() + ->whereIn('key', array_map(function ($key) { + return $this->prefix.$key; + }, $keys)) + ->get() + ->map(function ($value) { + return is_array($value) ? (object) $value : $value; + }); + + $currentTime = $this->currentTime(); // If this cache expiration date is past the current time, we will remove this // item from the cache. Then we will return a null value since the cache is // expired. We will use "Carbon" to make this comparison with the column. - if ($this->currentTime() >= $cache->expiration) { - $this->forgetIfExpired($key); + [$values, $expired] = $values->partition(function ($cache) use ($currentTime) { + return $cache->expiration > $currentTime; + }); - return; + if ($expired->isNotEmpty()) { + $this->forgetManyIfExpired($expired->pluck('key')->all(), prefixed: true); } - return $this->unserialize($cache->value); + return Arr::map($results, function ($value, $key) use ($values) { + if ($cache = $values->firstWhere('key', $this->prefix.$key)) { + return $this->unserialize($cache->value); + } + + return $value; + }); } /** @@ -133,11 +161,30 @@ public function get($key) */ public function put($key, $value, $seconds) { - $key = $this->prefix.$key; - $value = $this->serialize($value); + return $this->putMany([$key => $value], $seconds); + } + + /** + * Store multiple items in the cache for a given number of seconds. + * + * @param int $seconds + * @return bool + */ + public function putMany(array $values, $seconds) + { + $serializedValues = []; + $expiration = $this->getTime() + $seconds; - return $this->table()->upsert(compact('key', 'value', 'expiration'), 'key') > 0; + foreach ($values as $key => $value) { + $serializedValues[] = [ + 'key' => $this->prefix.$key, + 'value' => $this->serialize($value), + 'expiration' => $expiration, + ]; + } + + return $this->table()->upsert($serializedValues, 'key') > 0; } /** @@ -309,9 +356,7 @@ public function restoreLock($name, $owner) */ public function forget($key) { - $this->table()->where('key', '=', $this->prefix.$key)->delete(); - - return true; + return $this->forgetMany([$key]); } /** @@ -321,9 +366,38 @@ public function forget($key) * @return bool */ public function forgetIfExpired($key) + { + return $this->forgetManyIfExpired([$key]); + } + + /** + * Remove all items from the cache. + * + * @param array $keys + * @return bool + */ + protected function forgetMany(array $keys) + { + $this->table()->whereIn('key', array_map(function ($key) { + return $this->prefix.$key; + }, $keys))->delete(); + + return true; + } + + /** + * Remove all expired items from the given set from the cache. + * + * @param array $keys + * @param bool $prefixed + * @return bool + */ + protected function forgetManyIfExpired(array $keys, bool $prefixed = false) { $this->table() - ->where('key', '=', $this->prefix.$key) + ->whereIn('key', $prefixed ? $keys : array_map(function ($key) { + return $this->prefix.$key; + }, $keys)) ->where('expiration', '<=', $this->getTime()) ->delete(); diff --git a/tests/Cache/CacheDatabaseStoreTest.php b/tests/Cache/CacheDatabaseStoreTest.php index 4a2bfaa7fbf7..2e5393f21cb3 100755 --- a/tests/Cache/CacheDatabaseStoreTest.php +++ b/tests/Cache/CacheDatabaseStoreTest.php @@ -22,8 +22,8 @@ public function testNullIsReturnedWhenItemNotFound() $store = $this->getStore(); $table = m::mock(stdClass::class); $store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table); - $table->shouldReceive('where')->once()->with('key', '=', 'prefixfoo')->andReturn($table); - $table->shouldReceive('first')->once()->andReturn(null); + $table->shouldReceive('whereIn')->once()->with('key', ['prefixfoo'])->andReturn($table); + $table->shouldReceive('get')->once()->andReturn(collect([])); $this->assertNull($store->get('foo')); } @@ -31,11 +31,17 @@ public function testNullIsReturnedWhenItemNotFound() public function testNullIsReturnedAndItemDeletedWhenItemIsExpired() { $store = $this->getMockBuilder(DatabaseStore::class)->onlyMethods(['forgetIfExpired'])->setConstructorArgs($this->getMocks())->getMock(); - $table = m::mock(stdClass::class); - $store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table); - $table->shouldReceive('where')->once()->with('key', '=', 'prefixfoo')->andReturn($table); - $table->shouldReceive('first')->once()->andReturn((object) ['expiration' => 1]); - $store->expects($this->once())->method('forgetIfExpired')->with($this->equalTo('foo'))->willReturn(null); + + $getQuery = m::mock(stdClass::class); + $getQuery->shouldReceive('whereIn')->once()->with('key', ['prefixfoo'])->andReturn($getQuery); + $getQuery->shouldReceive('get')->once()->andReturn(collect([(object) ['key' => 'prefixfoo', 'expiration' => 1]])); + + $deleteQuery = m::mock(stdClass::class); + $deleteQuery->shouldReceive('whereIn')->once()->with('key', ['prefixfoo'])->andReturn($deleteQuery); + $deleteQuery->shouldReceive('where')->once()->with('expiration', '<=', m::any())->andReturn($deleteQuery); + $deleteQuery->shouldReceive('delete')->once()->andReturnNull(); + + $store->getConnection()->shouldReceive('table')->twice()->with('table')->andReturn($getQuery, $deleteQuery); $this->assertNull($store->get('foo')); } @@ -45,8 +51,8 @@ public function testDecryptedValueIsReturnedWhenItemIsValid() $store = $this->getStore(); $table = m::mock(stdClass::class); $store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table); - $table->shouldReceive('where')->once()->with('key', '=', 'prefixfoo')->andReturn($table); - $table->shouldReceive('first')->once()->andReturn((object) ['value' => serialize('bar'), 'expiration' => 999999999999999]); + $table->shouldReceive('whereIn')->once()->with('key', ['prefixfoo'])->andReturn($table); + $table->shouldReceive('get')->once()->andReturn(collect([(object) ['key' => 'prefixfoo', 'value' => serialize('bar'), 'expiration' => 999999999999999]])); $this->assertSame('bar', $store->get('foo')); } @@ -56,8 +62,8 @@ public function testValueIsReturnedOnPostgres() $store = $this->getPostgresStore(); $table = m::mock(stdClass::class); $store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table); - $table->shouldReceive('where')->once()->with('key', '=', 'prefixfoo')->andReturn($table); - $table->shouldReceive('first')->once()->andReturn((object) ['value' => base64_encode(serialize('bar')), 'expiration' => 999999999999999]); + $table->shouldReceive('whereIn')->once()->with('key', ['prefixfoo'])->andReturn($table); + $table->shouldReceive('get')->once()->andReturn(collect([(object) ['key' => 'prefixfoo', 'value' => base64_encode(serialize('bar')), 'expiration' => 999999999999999]])); $this->assertSame('bar', $store->get('foo')); } @@ -68,7 +74,7 @@ public function testValueIsUpserted() $table = m::mock(stdClass::class); $store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table); $store->expects($this->once())->method('getTime')->willReturn(1); - $table->shouldReceive('upsert')->once()->with(['key' => 'prefixfoo', 'value' => serialize('bar'), 'expiration' => 61], 'key')->andReturnTrue(); + $table->shouldReceive('upsert')->once()->with([['key' => 'prefixfoo', 'value' => serialize('bar'), 'expiration' => 61]], 'key')->andReturnTrue(); $result = $store->put('foo', 'bar', 60); $this->assertTrue($result); @@ -80,7 +86,7 @@ public function testValueIsUpsertedOnPostgres() $table = m::mock(stdClass::class); $store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table); $store->expects($this->once())->method('getTime')->willReturn(1); - $table->shouldReceive('upsert')->once()->with(['key' => 'prefixfoo', 'value' => base64_encode(serialize("\0")), 'expiration' => 61], 'key')->andReturn(1); + $table->shouldReceive('upsert')->once()->with([['key' => 'prefixfoo', 'value' => base64_encode(serialize("\0")), 'expiration' => 61]], 'key')->andReturn(1); $result = $store->put('foo', "\0", 60); $this->assertTrue($result); @@ -99,7 +105,7 @@ public function testItemsMayBeRemovedFromCache() $store = $this->getStore(); $table = m::mock(stdClass::class); $store->getConnection()->shouldReceive('table')->once()->with('table')->andReturn($table); - $table->shouldReceive('where')->once()->with('key', '=', 'prefixfoo')->andReturn($table); + $table->shouldReceive('whereIn')->once()->with('key', ['prefixfoo'])->andReturn($table); $table->shouldReceive('delete')->once(); $store->forget('foo'); diff --git a/tests/Integration/Database/DatabaseCacheStoreTest.php b/tests/Integration/Database/DatabaseCacheStoreTest.php index b284a861630f..4b09050dcae5 100644 --- a/tests/Integration/Database/DatabaseCacheStoreTest.php +++ b/tests/Integration/Database/DatabaseCacheStoreTest.php @@ -166,6 +166,77 @@ public function testForgetIfExpiredOperationShouldNotDeleteUnExpired() $this->assertDatabaseHas($this->getCacheTableName(), ['key' => $this->withCachePrefix('foo')]); } + public function testMany() + { + $this->insertToCacheTable('first', 'a', 60); + $this->insertToCacheTable('second', 'b', 60); + + $store = $this->getStore(); + + $this->assertEquals([ + 'first' => 'a', + 'second' => 'b', + 'third' => null, + ], $store->get(['first', 'second', 'third'])); + + $this->assertEquals([ + 'first' => 'a', + 'second' => 'b', + 'third' => null, + ], $store->many(['first', 'second', 'third'])); + } + + public function testManyWithExpiredKeys() + { + $this->insertToCacheTable('first', 'a', 0); + $this->insertToCacheTable('second', 'b', 60); + + $this->assertEquals([ + 'first' => null, + 'second' => 'b', + 'third' => null, + ], $this->getStore()->many(['first', 'second', 'third'])); + + $this->assertDatabaseMissing($this->getCacheTableName(), ['key' => $this->withCachePrefix('first')]); + } + + public function testManyAsAssociativeArray() + { + $this->insertToCacheTable('first', 'cached', 60); + + $result = $this->getStore()->many([ + 'first' => 'aa', + 'second' => 'bb', + 'third', + ]); + + $this->assertEquals([ + 'first' => 'cached', + 'second' => 'bb', + 'third' => null, + ], $result); + } + + public function testPutMany() + { + $store = $this->getStore(); + + $store->putMany($data = [ + 'first' => 'a', + 'second' => 'b', + ], 60); + + $this->assertEquals($data, $store->many(['first', 'second'])); + $this->assertDatabaseHas($this->getCacheTableName(), [ + 'key' => $this->withCachePrefix('first'), + 'value' => serialize('a'), + ]); + $this->assertDatabaseHas($this->getCacheTableName(), [ + 'key' => $this->withCachePrefix('second'), + 'value' => serialize('b'), + ]); + } + public function testResolvingSQLiteConnectionDoesNotThrowExceptions() { $originalConfiguration = config('database'); @@ -203,7 +274,7 @@ protected function insertToCacheTable(string $key, $value, $ttl = 60) ->insert( [ 'key' => $this->withCachePrefix($key), - 'value' => $value, + 'value' => serialize($value), 'expiration' => Carbon::now()->addSeconds($ttl)->getTimestamp(), ] ); From a13b726262e6da0fc7406fd2c9e281a92154ee22 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 22 Jul 2024 23:28:47 +0200 Subject: [PATCH 5/5] Add method `QueryExecuted::toRawSql()` (#52192) * Add method `QueryExecuted::toRawSql()` This makes it easier to debug the executed queries when using `DB::listen()`. Before, we did something like this and got a result that was hardly readable: ```php DB::listen(function (QueryExecuted $query): void { $sql = str_replace("\n", ' ', $query->sql); $bindings = json_encode($query->bindings); file_put_contents( filename: storage_path('logs/query.log'), data: "SQL: {$sql} ||| Bindings: {$bindings} ||| Time: {$query->time}ms\n", flags: FILE_APPEND, ); }); // SQL: insert into `competence_detail_criteria` (`competence_criteria_id`, `competence_detail_id`, `valid_from`, `valid_to`, `userid`, `first_id`) values (?, ?, ?, ?, ?, ?) ||| Bindings: [3,1,"2024-07-19 10:59:02","9999-12-31 23:55:55",1,0] ||| Time: 0.84ms ``` With this added method, achieving a readable result becomes much simpler: ```php DB::listen(function (QueryExecuted $query): void { file_put_contents( filename: storage_path('logs/query.log'), data: "SQL: {$query->toRawSql()} ||| Time: {$query->time}ms\n", flags: FILE_APPEND, ); }); // SQL: insert into `competence_detail_criteria` (`competence_criteria_id`, `competence_detail_id`, `valid_from`, `valid_to`, `userid`, `first_id`) values (4, 1, '2024-07-19 11:10:29', '9999-12-31 23:55:55', 1, 0) ||| Time: 0.2ms ``` * Update QueryExecuted.php --------- Co-authored-by: Taylor Otwell --- .../Database/Events/QueryExecuted.php | 13 +++++++ tests/Database/DatabaseIntegrationTest.php | 38 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 tests/Database/DatabaseIntegrationTest.php diff --git a/src/Illuminate/Database/Events/QueryExecuted.php b/src/Illuminate/Database/Events/QueryExecuted.php index 833a21e6f984..644d947332c3 100644 --- a/src/Illuminate/Database/Events/QueryExecuted.php +++ b/src/Illuminate/Database/Events/QueryExecuted.php @@ -56,4 +56,17 @@ public function __construct($sql, $bindings, $time, $connection) $this->connection = $connection; $this->connectionName = $connection->getName(); } + + /** + * Get the raw SQL representation of the query with embedded bindings. + * + * @return string + */ + public function toRawSql() + { + return $this->connection + ->query() + ->getGrammar() + ->substituteBindingsIntoRawSql($this->sql, $this->connection->prepareBindings($this->bindings)); + } } diff --git a/tests/Database/DatabaseIntegrationTest.php b/tests/Database/DatabaseIntegrationTest.php new file mode 100644 index 000000000000..ab32d3094784 --- /dev/null +++ b/tests/Database/DatabaseIntegrationTest.php @@ -0,0 +1,38 @@ +addConnection([ + 'driver' => 'sqlite', + 'database' => ':memory:', + ]); + $db->setAsGlobal(); + $db->setEventDispatcher(new Dispatcher); + } + + public function testQueryExecutedToRawSql(): void + { + $connection = DB::connection(); + + $connection->listen(function (QueryExecuted $query) use (&$queryExecuted): void { + $queryExecuted = $query; + }); + + $connection->select('select ?', [true]); + + $this->assertInstanceOf(QueryExecuted::class, $queryExecuted); + $this->assertSame('select ?', $queryExecuted->sql); + $this->assertSame([true], $queryExecuted->bindings); + $this->assertSame('select 1', $queryExecuted->toRawSql()); + } +}