123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473 |
- <?php
- namespace Illuminate\Tests\Database;
- use DateTime;
- use ErrorException;
- use Exception;
- use Illuminate\Contracts\Events\Dispatcher;
- use Illuminate\Database\Connection;
- use Illuminate\Database\Events\QueryExecuted;
- use Illuminate\Database\Events\TransactionBeginning;
- use Illuminate\Database\Events\TransactionCommitted;
- use Illuminate\Database\Events\TransactionRolledBack;
- use Illuminate\Database\Query\Builder as BaseBuilder;
- use Illuminate\Database\Query\Grammars\Grammar;
- use Illuminate\Database\Query\Processors\Processor;
- use Illuminate\Database\QueryException;
- use Illuminate\Database\Schema\Builder;
- use Mockery as m;
- use PDO;
- use PDOException;
- use PDOStatement;
- use PHPUnit\Framework\TestCase;
- use ReflectionClass;
- use stdClass;
- class DatabaseConnectionTest extends TestCase
- {
- protected function tearDown(): void
- {
- m::close();
- }
- public function testSettingDefaultCallsGetDefaultGrammar()
- {
- $connection = $this->getMockConnection();
- $mock = m::mock(stdClass::class);
- $connection->expects($this->once())->method('getDefaultQueryGrammar')->willReturn($mock);
- $connection->useDefaultQueryGrammar();
- $this->assertEquals($mock, $connection->getQueryGrammar());
- }
- public function testSettingDefaultCallsGetDefaultPostProcessor()
- {
- $connection = $this->getMockConnection();
- $mock = m::mock(stdClass::class);
- $connection->expects($this->once())->method('getDefaultPostProcessor')->willReturn($mock);
- $connection->useDefaultPostProcessor();
- $this->assertEquals($mock, $connection->getPostProcessor());
- }
- public function testSelectOneCallsSelectAndReturnsSingleResult()
- {
- $connection = $this->getMockConnection(['select']);
- $connection->expects($this->once())->method('select')->with('foo', ['bar' => 'baz'])->willReturn(['foo']);
- $this->assertSame('foo', $connection->selectOne('foo', ['bar' => 'baz']));
- }
- public function testSelectProperlyCallsPDO()
- {
- $pdo = $this->getMockBuilder(DatabaseConnectionTestMockPDO::class)->onlyMethods(['prepare'])->getMock();
- $writePdo = $this->getMockBuilder(DatabaseConnectionTestMockPDO::class)->onlyMethods(['prepare'])->getMock();
- $writePdo->expects($this->never())->method('prepare');
- $statement = $this->getMockBuilder('PDOStatement')
- ->onlyMethods(['setFetchMode', 'execute', 'fetchAll', 'bindValue'])
- ->getMock();
- $statement->expects($this->once())->method('setFetchMode');
- $statement->expects($this->once())->method('bindValue')->with('foo', 'bar', 2);
- $statement->expects($this->once())->method('execute');
- $statement->expects($this->once())->method('fetchAll')->willReturn(['boom']);
- $pdo->expects($this->once())->method('prepare')->with('foo')->willReturn($statement);
- $mock = $this->getMockConnection(['prepareBindings'], $writePdo);
- $mock->setReadPdo($pdo);
- $mock->expects($this->once())->method('prepareBindings')->with($this->equalTo(['foo' => 'bar']))->willReturn(['foo' => 'bar']);
- $results = $mock->select('foo', ['foo' => 'bar']);
- $this->assertEquals(['boom'], $results);
- $log = $mock->getQueryLog();
- $this->assertSame('foo', $log[0]['query']);
- $this->assertEquals(['foo' => 'bar'], $log[0]['bindings']);
- $this->assertIsNumeric($log[0]['time']);
- }
- public function testInsertCallsTheStatementMethod()
- {
- $connection = $this->getMockConnection(['statement']);
- $connection->expects($this->once())->method('statement')->with($this->equalTo('foo'), $this->equalTo(['bar']))->willReturn('baz');
- $results = $connection->insert('foo', ['bar']);
- $this->assertSame('baz', $results);
- }
- public function testUpdateCallsTheAffectingStatementMethod()
- {
- $connection = $this->getMockConnection(['affectingStatement']);
- $connection->expects($this->once())->method('affectingStatement')->with($this->equalTo('foo'), $this->equalTo(['bar']))->willReturn('baz');
- $results = $connection->update('foo', ['bar']);
- $this->assertSame('baz', $results);
- }
- public function testDeleteCallsTheAffectingStatementMethod()
- {
- $connection = $this->getMockConnection(['affectingStatement']);
- $connection->expects($this->once())->method('affectingStatement')->with($this->equalTo('foo'), $this->equalTo(['bar']))->willReturn(true);
- $results = $connection->delete('foo', ['bar']);
- $this->assertTrue($results);
- }
- public function testStatementProperlyCallsPDO()
- {
- $pdo = $this->getMockBuilder(DatabaseConnectionTestMockPDO::class)->onlyMethods(['prepare'])->getMock();
- $statement = $this->getMockBuilder('PDOStatement')->onlyMethods(['execute', 'bindValue'])->getMock();
- $statement->expects($this->once())->method('bindValue')->with(1, 'bar', 2);
- $statement->expects($this->once())->method('execute')->willReturn(true);
- $pdo->expects($this->once())->method('prepare')->with($this->equalTo('foo'))->willReturn($statement);
- $mock = $this->getMockConnection(['prepareBindings'], $pdo);
- $mock->expects($this->once())->method('prepareBindings')->with($this->equalTo(['bar']))->willReturn(['bar']);
- $results = $mock->statement('foo', ['bar']);
- $this->assertTrue($results);
- $log = $mock->getQueryLog();
- $this->assertSame('foo', $log[0]['query']);
- $this->assertEquals(['bar'], $log[0]['bindings']);
- $this->assertIsNumeric($log[0]['time']);
- }
- public function testAffectingStatementProperlyCallsPDO()
- {
- $pdo = $this->getMockBuilder(DatabaseConnectionTestMockPDO::class)->onlyMethods(['prepare'])->getMock();
- $statement = $this->getMockBuilder('PDOStatement')->onlyMethods(['execute', 'rowCount', 'bindValue'])->getMock();
- $statement->expects($this->once())->method('bindValue')->with('foo', 'bar', 2);
- $statement->expects($this->once())->method('execute');
- $statement->expects($this->once())->method('rowCount')->willReturn(42);
- $pdo->expects($this->once())->method('prepare')->with('foo')->willReturn($statement);
- $mock = $this->getMockConnection(['prepareBindings'], $pdo);
- $mock->expects($this->once())->method('prepareBindings')->with($this->equalTo(['foo' => 'bar']))->willReturn(['foo' => 'bar']);
- $results = $mock->update('foo', ['foo' => 'bar']);
- $this->assertSame(42, $results);
- $log = $mock->getQueryLog();
- $this->assertSame('foo', $log[0]['query']);
- $this->assertEquals(['foo' => 'bar'], $log[0]['bindings']);
- $this->assertIsNumeric($log[0]['time']);
- }
- public function testTransactionLevelNotIncrementedOnTransactionException()
- {
- $pdo = $this->createMock(DatabaseConnectionTestMockPDO::class);
- $pdo->expects($this->once())->method('beginTransaction')->will($this->throwException(new Exception));
- $connection = $this->getMockConnection([], $pdo);
- try {
- $connection->beginTransaction();
- } catch (Exception $e) {
- $this->assertEquals(0, $connection->transactionLevel());
- }
- }
- public function testBeginTransactionMethodRetriesOnFailure()
- {
- $pdo = $this->createMock(DatabaseConnectionTestMockPDO::class);
- $pdo->method('beginTransaction')
- ->willReturnOnConsecutiveCalls($this->throwException(new ErrorException('server has gone away')), true);
- $connection = $this->getMockConnection(['reconnect'], $pdo);
- $connection->expects($this->once())->method('reconnect');
- $connection->beginTransaction();
- $this->assertEquals(1, $connection->transactionLevel());
- }
- public function testBeginTransactionMethodReconnectsMissingConnection()
- {
- $connection = $this->getMockConnection();
- $connection->setReconnector(function ($connection) {
- $pdo = $this->createMock(DatabaseConnectionTestMockPDO::class);
- $connection->setPdo($pdo);
- });
- $connection->disconnect();
- $connection->beginTransaction();
- $this->assertEquals(1, $connection->transactionLevel());
- }
- public function testBeginTransactionMethodNeverRetriesIfWithinTransaction()
- {
- $pdo = $this->createMock(DatabaseConnectionTestMockPDO::class);
- $pdo->expects($this->once())->method('beginTransaction');
- $pdo->expects($this->once())->method('exec')->will($this->throwException(new Exception));
- $connection = $this->getMockConnection(['reconnect'], $pdo);
- $queryGrammar = $this->createMock(Grammar::class);
- $queryGrammar->expects($this->once())->method('compileSavepoint')->willReturn('trans1');
- $queryGrammar->expects($this->once())->method('supportsSavepoints')->willReturn(true);
- $connection->setQueryGrammar($queryGrammar);
- $connection->expects($this->never())->method('reconnect');
- $connection->beginTransaction();
- $this->assertEquals(1, $connection->transactionLevel());
- try {
- $connection->beginTransaction();
- } catch (Exception $e) {
- $this->assertEquals(1, $connection->transactionLevel());
- }
- }
- public function testSwapPDOWithOpenTransactionResetsTransactionLevel()
- {
- $pdo = $this->createMock(DatabaseConnectionTestMockPDO::class);
- $pdo->expects($this->once())->method('beginTransaction')->willReturn(true);
- $connection = $this->getMockConnection([], $pdo);
- $connection->beginTransaction();
- $connection->disconnect();
- $this->assertEquals(0, $connection->transactionLevel());
- }
- public function testBeganTransactionFiresEventsIfSet()
- {
- $pdo = $this->createMock(DatabaseConnectionTestMockPDO::class);
- $connection = $this->getMockConnection(['getName'], $pdo);
- $connection->expects($this->any())->method('getName')->willReturn('name');
- $connection->setEventDispatcher($events = m::mock(Dispatcher::class));
- $events->shouldReceive('dispatch')->once()->with(m::type(TransactionBeginning::class));
- $connection->beginTransaction();
- }
- public function testCommittedFiresEventsIfSet()
- {
- $pdo = $this->createMock(DatabaseConnectionTestMockPDO::class);
- $connection = $this->getMockConnection(['getName'], $pdo);
- $connection->expects($this->any())->method('getName')->willReturn('name');
- $connection->setEventDispatcher($events = m::mock(Dispatcher::class));
- $events->shouldReceive('dispatch')->once()->with(m::type(TransactionCommitted::class));
- $connection->commit();
- }
- public function testRollBackedFiresEventsIfSet()
- {
- $pdo = $this->createMock(DatabaseConnectionTestMockPDO::class);
- $connection = $this->getMockConnection(['getName'], $pdo);
- $connection->expects($this->any())->method('getName')->willReturn('name');
- $connection->beginTransaction();
- $connection->setEventDispatcher($events = m::mock(Dispatcher::class));
- $events->shouldReceive('dispatch')->once()->with(m::type(TransactionRolledBack::class));
- $connection->rollBack();
- }
- public function testRedundantRollBackFiresNoEvent()
- {
- $pdo = $this->createMock(DatabaseConnectionTestMockPDO::class);
- $connection = $this->getMockConnection(['getName'], $pdo);
- $connection->expects($this->any())->method('getName')->willReturn('name');
- $connection->setEventDispatcher($events = m::mock(Dispatcher::class));
- $events->shouldNotReceive('dispatch');
- $connection->rollBack();
- }
- public function testTransactionMethodRunsSuccessfully()
- {
- $pdo = $this->getMockBuilder(DatabaseConnectionTestMockPDO::class)->onlyMethods(['beginTransaction', 'commit'])->getMock();
- $mock = $this->getMockConnection([], $pdo);
- $pdo->expects($this->once())->method('beginTransaction');
- $pdo->expects($this->once())->method('commit');
- $result = $mock->transaction(function ($db) {
- return $db;
- });
- $this->assertEquals($mock, $result);
- }
- public function testTransactionRetriesOnSerializationFailure()
- {
- $this->expectException(PDOException::class);
- $this->expectExceptionMessage('Serialization failure');
- $pdo = $this->getMockBuilder(DatabaseConnectionTestMockPDO::class)->onlyMethods(['beginTransaction', 'commit', 'rollBack'])->getMock();
- $mock = $this->getMockConnection([], $pdo);
- $pdo->expects($this->exactly(3))->method('commit')->will($this->throwException(new DatabaseConnectionTestMockPDOException('Serialization failure', '40001')));
- $pdo->expects($this->exactly(3))->method('beginTransaction');
- $pdo->expects($this->never())->method('rollBack');
- $mock->transaction(function () {
- }, 3);
- }
- public function testTransactionMethodRetriesOnDeadlock()
- {
- $this->expectException(QueryException::class);
- $this->expectExceptionMessage('Deadlock found when trying to get lock (SQL: )');
- $pdo = $this->getMockBuilder(DatabaseConnectionTestMockPDO::class)->onlyMethods(['beginTransaction', 'commit', 'rollBack'])->getMock();
- $mock = $this->getMockConnection([], $pdo);
- $pdo->expects($this->exactly(3))->method('beginTransaction');
- $pdo->expects($this->exactly(3))->method('rollBack');
- $pdo->expects($this->never())->method('commit');
- $mock->transaction(function () {
- throw new QueryException('', [], new Exception('Deadlock found when trying to get lock'));
- }, 3);
- }
- public function testTransactionMethodRollsbackAndThrows()
- {
- $pdo = $this->getMockBuilder(DatabaseConnectionTestMockPDO::class)->onlyMethods(['beginTransaction', 'commit', 'rollBack'])->getMock();
- $mock = $this->getMockConnection([], $pdo);
- $pdo->expects($this->once())->method('beginTransaction');
- $pdo->expects($this->once())->method('rollBack');
- $pdo->expects($this->never())->method('commit');
- try {
- $mock->transaction(function () {
- throw new Exception('foo');
- });
- } catch (Exception $e) {
- $this->assertSame('foo', $e->getMessage());
- }
- }
- public function testOnLostConnectionPDOIsNotSwappedWithinATransaction()
- {
- $this->expectException(QueryException::class);
- $this->expectExceptionMessage('server has gone away (SQL: foo)');
- $pdo = m::mock(PDO::class);
- $pdo->shouldReceive('beginTransaction')->once();
- $statement = m::mock(PDOStatement::class);
- $pdo->shouldReceive('prepare')->once()->andReturn($statement);
- $statement->shouldReceive('execute')->once()->andThrow(new PDOException('server has gone away'));
- $connection = new Connection($pdo);
- $connection->beginTransaction();
- $connection->statement('foo');
- }
- public function testOnLostConnectionPDOIsSwappedOutsideTransaction()
- {
- $pdo = m::mock(PDO::class);
- $statement = m::mock(PDOStatement::class);
- $statement->shouldReceive('execute')->once()->andThrow(new PDOException('server has gone away'));
- $statement->shouldReceive('execute')->once()->andReturn(true);
- $pdo->shouldReceive('prepare')->twice()->andReturn($statement);
- $connection = new Connection($pdo);
- $called = false;
- $connection->setReconnector(function ($connection) use (&$called) {
- $called = true;
- });
- $this->assertTrue($connection->statement('foo'));
- $this->assertTrue($called);
- }
- public function testRunMethodRetriesOnFailure()
- {
- $method = (new ReflectionClass(Connection::class))->getMethod('run');
- $method->setAccessible(true);
- $pdo = $this->createMock(DatabaseConnectionTestMockPDO::class);
- $mock = $this->getMockConnection(['tryAgainIfCausedByLostConnection'], $pdo);
- $mock->expects($this->once())->method('tryAgainIfCausedByLostConnection');
- $method->invokeArgs($mock, ['', [], function () {
- throw new QueryException('', [], new Exception);
- }]);
- }
- public function testRunMethodNeverRetriesIfWithinTransaction()
- {
- $this->expectException(QueryException::class);
- $this->expectExceptionMessage('(SQL: ) (SQL: )');
- $method = (new ReflectionClass(Connection::class))->getMethod('run');
- $method->setAccessible(true);
- $pdo = $this->getMockBuilder(DatabaseConnectionTestMockPDO::class)->onlyMethods(['beginTransaction'])->getMock();
- $mock = $this->getMockConnection(['tryAgainIfCausedByLostConnection'], $pdo);
- $pdo->expects($this->once())->method('beginTransaction');
- $mock->expects($this->never())->method('tryAgainIfCausedByLostConnection');
- $mock->beginTransaction();
- $method->invokeArgs($mock, ['', [], function () {
- throw new QueryException('', [], new Exception);
- }]);
- }
- public function testFromCreatesNewQueryBuilder()
- {
- $conn = $this->getMockConnection();
- $conn->setQueryGrammar(m::mock(Grammar::class));
- $conn->setPostProcessor(m::mock(Processor::class));
- $builder = $conn->table('users');
- $this->assertInstanceOf(BaseBuilder::class, $builder);
- $this->assertSame('users', $builder->from);
- }
- public function testPrepareBindings()
- {
- $date = m::mock(DateTime::class);
- $date->shouldReceive('format')->once()->with('foo')->andReturn('bar');
- $bindings = ['test' => $date];
- $conn = $this->getMockConnection();
- $grammar = m::mock(Grammar::class);
- $grammar->shouldReceive('getDateFormat')->once()->andReturn('foo');
- $conn->setQueryGrammar($grammar);
- $result = $conn->prepareBindings($bindings);
- $this->assertEquals(['test' => 'bar'], $result);
- }
- public function testLogQueryFiresEventsIfSet()
- {
- $connection = $this->getMockConnection();
- $connection->logQuery('foo', [], time());
- $connection->setEventDispatcher($events = m::mock(Dispatcher::class));
- $events->shouldReceive('dispatch')->once()->with(m::type(QueryExecuted::class));
- $connection->logQuery('foo', [], null);
- }
- public function testBeforeExecutingHooksCanBeRegistered()
- {
- $this->expectException(Exception::class);
- $this->expectExceptionMessage('The callback was fired');
- $connection = $this->getMockConnection();
- $connection->beforeExecuting(function () {
- throw new Exception('The callback was fired');
- });
- $connection->select('foo bar', ['baz']);
- }
- public function testPretendOnlyLogsQueries()
- {
- $connection = $this->getMockConnection();
- $queries = $connection->pretend(function ($connection) {
- $connection->select('foo bar', ['baz']);
- });
- $this->assertSame('foo bar', $queries[0]['query']);
- $this->assertEquals(['baz'], $queries[0]['bindings']);
- }
- public function testSchemaBuilderCanBeCreated()
- {
- $connection = $this->getMockConnection();
- $schema = $connection->getSchemaBuilder();
- $this->assertInstanceOf(Builder::class, $schema);
- $this->assertSame($connection, $schema->getConnection());
- }
- protected function getMockConnection($methods = [], $pdo = null)
- {
- $pdo = $pdo ?: new DatabaseConnectionTestMockPDO;
- $defaults = ['getDefaultQueryGrammar', 'getDefaultPostProcessor', 'getDefaultSchemaGrammar'];
- $connection = $this->getMockBuilder(Connection::class)->onlyMethods(array_merge($defaults, $methods))->setConstructorArgs([$pdo])->getMock();
- $connection->enableQueryLog();
- return $connection;
- }
- }
- class DatabaseConnectionTestMockPDO extends PDO
- {
- public function __construct()
- {
- //
- }
- }
- class DatabaseConnectionTestMockPDOException extends PDOException
- {
- /**
- * Overrides Exception::__construct, which casts $code to integer, so that we can create
- * an exception with a string $code consistent with the real PDOException behavior.
- *
- * @param string|null $message
- * @param string|null $code
- * @return void
- */
- public function __construct($message = null, $code = null)
- {
- $this->message = $message;
- $this->code = $code;
- }
- }
|