DatabaseConnectorTest.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. <?php
  2. namespace Illuminate\Tests\Database;
  3. use Illuminate\Database\Connectors\Connector;
  4. use Illuminate\Database\Connectors\MySqlConnector;
  5. use Illuminate\Database\Connectors\PostgresConnector;
  6. use Illuminate\Database\Connectors\SQLiteConnector;
  7. use Illuminate\Database\Connectors\SqlServerConnector;
  8. use Mockery as m;
  9. use PDO;
  10. use PDOStatement;
  11. use PHPUnit\Framework\TestCase;
  12. use stdClass;
  13. class DatabaseConnectorTest extends TestCase
  14. {
  15. protected function tearDown(): void
  16. {
  17. m::close();
  18. }
  19. public function testOptionResolution()
  20. {
  21. $connector = new Connector;
  22. $connector->setDefaultOptions([0 => 'foo', 1 => 'bar']);
  23. $this->assertEquals([0 => 'baz', 1 => 'bar', 2 => 'boom'], $connector->getOptions(['options' => [0 => 'baz', 2 => 'boom']]));
  24. }
  25. /**
  26. * @dataProvider mySqlConnectProvider
  27. */
  28. public function testMySqlConnectCallsCreateConnectionWithProperArguments($dsn, $config)
  29. {
  30. $connector = $this->getMockBuilder(MySqlConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
  31. $connection = m::mock(PDO::class);
  32. $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
  33. $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
  34. $statement = m::mock(PDOStatement::class);
  35. $connection->shouldReceive('prepare')->once()->with('set names \'utf8\' collate \'utf8_unicode_ci\'')->andReturn($statement);
  36. $statement->shouldReceive('execute')->once();
  37. $connection->shouldReceive('exec')->zeroOrMoreTimes();
  38. $result = $connector->connect($config);
  39. $this->assertSame($result, $connection);
  40. }
  41. public function mySqlConnectProvider()
  42. {
  43. return [
  44. ['mysql:host=foo;dbname=bar', ['host' => 'foo', 'database' => 'bar', 'collation' => 'utf8_unicode_ci', 'charset' => 'utf8']],
  45. ['mysql:host=foo;port=111;dbname=bar', ['host' => 'foo', 'database' => 'bar', 'port' => 111, 'collation' => 'utf8_unicode_ci', 'charset' => 'utf8']],
  46. ['mysql:unix_socket=baz;dbname=bar', ['host' => 'foo', 'database' => 'bar', 'port' => 111, 'unix_socket' => 'baz', 'collation' => 'utf8_unicode_ci', 'charset' => 'utf8']],
  47. ];
  48. }
  49. public function testMySqlConnectCallsCreateConnectionWithIsolationLevel()
  50. {
  51. $dsn = 'mysql:host=foo;dbname=bar';
  52. $config = ['host' => 'foo', 'database' => 'bar', 'collation' => 'utf8_unicode_ci', 'charset' => 'utf8', 'isolation_level' => 'REPEATABLE READ'];
  53. $connector = $this->getMockBuilder(MySqlConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
  54. $connection = m::mock(PDO::class);
  55. $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
  56. $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
  57. $statement = m::mock(PDOStatement::class);
  58. $connection->shouldReceive('prepare')->once()->with('set names \'utf8\' collate \'utf8_unicode_ci\'')->andReturn($statement);
  59. $connection->shouldReceive('prepare')->once()->with('SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ')->andReturn($statement);
  60. $statement->shouldReceive('execute')->zeroOrMoreTimes();
  61. $connection->shouldReceive('exec')->zeroOrMoreTimes();
  62. $result = $connector->connect($config);
  63. $this->assertSame($result, $connection);
  64. }
  65. public function testPostgresConnectCallsCreateConnectionWithProperArguments()
  66. {
  67. $dsn = 'pgsql:host=foo;dbname=\'bar\';port=111';
  68. $config = ['host' => 'foo', 'database' => 'bar', 'port' => 111, 'charset' => 'utf8'];
  69. $connector = $this->getMockBuilder(PostgresConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
  70. $connection = m::mock(stdClass::class);
  71. $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
  72. $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
  73. $statement = m::mock(PDOStatement::class);
  74. $connection->shouldReceive('prepare')->once()->with('set names \'utf8\'')->andReturn($statement);
  75. $statement->shouldReceive('execute')->once();
  76. $result = $connector->connect($config);
  77. $this->assertSame($result, $connection);
  78. }
  79. public function testPostgresSearchPathIsSet()
  80. {
  81. $dsn = 'pgsql:host=foo;dbname=\'bar\'';
  82. $config = ['host' => 'foo', 'database' => 'bar', 'schema' => 'public', 'charset' => 'utf8'];
  83. $connector = $this->getMockBuilder(PostgresConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
  84. $connection = m::mock(stdClass::class);
  85. $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
  86. $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
  87. $statement = m::mock(PDOStatement::class);
  88. $connection->shouldReceive('prepare')->once()->with('set names \'utf8\'')->andReturn($statement);
  89. $connection->shouldReceive('prepare')->once()->with('set search_path to "public"')->andReturn($statement);
  90. $statement->shouldReceive('execute')->twice();
  91. $result = $connector->connect($config);
  92. $this->assertSame($result, $connection);
  93. }
  94. public function testPostgresSearchPathArraySupported()
  95. {
  96. $dsn = 'pgsql:host=foo;dbname=\'bar\'';
  97. $config = ['host' => 'foo', 'database' => 'bar', 'schema' => ['public', 'user'], 'charset' => 'utf8'];
  98. $connector = $this->getMockBuilder(PostgresConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
  99. $connection = m::mock(stdClass::class);
  100. $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
  101. $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
  102. $statement = m::mock(PDOStatement::class);
  103. $connection->shouldReceive('prepare')->once()->with('set names \'utf8\'')->andReturn($statement);
  104. $connection->shouldReceive('prepare')->once()->with('set search_path to "public", "user"')->andReturn($statement);
  105. $statement->shouldReceive('execute')->twice();
  106. $result = $connector->connect($config);
  107. $this->assertSame($result, $connection);
  108. }
  109. public function testPostgresApplicationNameIsSet()
  110. {
  111. $dsn = 'pgsql:host=foo;dbname=\'bar\'';
  112. $config = ['host' => 'foo', 'database' => 'bar', 'charset' => 'utf8', 'application_name' => 'Laravel App'];
  113. $connector = $this->getMockBuilder(PostgresConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
  114. $connection = m::mock(stdClass::class);
  115. $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
  116. $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
  117. $statement = m::mock(PDOStatement::class);
  118. $connection->shouldReceive('prepare')->once()->with('set names \'utf8\'')->andReturn($statement);
  119. $connection->shouldReceive('prepare')->once()->with('set application_name to \'Laravel App\'')->andReturn($statement);
  120. $statement->shouldReceive('execute')->twice();
  121. $result = $connector->connect($config);
  122. $this->assertSame($result, $connection);
  123. }
  124. public function testPostgresConnectorReadsIsolationLevelFromConfig()
  125. {
  126. $dsn = 'pgsql:host=foo;dbname=\'bar\';port=111';
  127. $config = ['host' => 'foo', 'database' => 'bar', 'port' => 111, 'isolation_level' => 'SERIALIZABLE'];
  128. $connector = $this->getMockBuilder(PostgresConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
  129. $connection = m::mock(PDO::class);
  130. $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
  131. $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
  132. $statement = m::mock(PDOStatement::class);
  133. $connection->shouldReceive('prepare')->once()->with('set session characteristics as transaction isolation level SERIALIZABLE')->andReturn($statement);
  134. $statement->shouldReceive('execute')->zeroOrMoreTimes();
  135. $connection->shouldReceive('exec')->zeroOrMoreTimes();
  136. $result = $connector->connect($config);
  137. $this->assertSame($result, $connection);
  138. }
  139. public function testSQLiteMemoryDatabasesMayBeConnectedTo()
  140. {
  141. $dsn = 'sqlite::memory:';
  142. $config = ['database' => ':memory:'];
  143. $connector = $this->getMockBuilder(SQLiteConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
  144. $connection = m::mock(stdClass::class);
  145. $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
  146. $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
  147. $result = $connector->connect($config);
  148. $this->assertSame($result, $connection);
  149. }
  150. public function testSQLiteFileDatabasesMayBeConnectedTo()
  151. {
  152. $dsn = 'sqlite:'.__DIR__;
  153. $config = ['database' => __DIR__];
  154. $connector = $this->getMockBuilder(SQLiteConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
  155. $connection = m::mock(stdClass::class);
  156. $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
  157. $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
  158. $result = $connector->connect($config);
  159. $this->assertSame($result, $connection);
  160. }
  161. public function testSqlServerConnectCallsCreateConnectionWithProperArguments()
  162. {
  163. $config = ['host' => 'foo', 'database' => 'bar', 'port' => 111];
  164. $dsn = $this->getDsn($config);
  165. $connector = $this->getMockBuilder(SqlServerConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
  166. $connection = m::mock(stdClass::class);
  167. $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
  168. $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
  169. $result = $connector->connect($config);
  170. $this->assertSame($result, $connection);
  171. }
  172. public function testSqlServerConnectCallsCreateConnectionWithOptionalArguments()
  173. {
  174. $config = ['host' => 'foo', 'database' => 'bar', 'port' => 111, 'readonly' => true, 'charset' => 'utf-8', 'pooling' => false, 'appname' => 'baz'];
  175. $dsn = $this->getDsn($config);
  176. $connector = $this->getMockBuilder(SqlServerConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
  177. $connection = m::mock(stdClass::class);
  178. $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
  179. $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
  180. $result = $connector->connect($config);
  181. $this->assertSame($result, $connection);
  182. }
  183. /**
  184. * @requires extension odbc
  185. */
  186. public function testSqlServerConnectCallsCreateConnectionWithPreferredODBC()
  187. {
  188. $config = ['odbc' => true, 'odbc_datasource_name' => 'server=localhost;database=test;'];
  189. $dsn = $this->getDsn($config);
  190. $connector = $this->getMockBuilder(SqlServerConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
  191. $connection = m::mock(stdClass::class);
  192. $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
  193. $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
  194. $result = $connector->connect($config);
  195. $this->assertSame($result, $connection);
  196. }
  197. protected function getDsn(array $config)
  198. {
  199. extract($config, EXTR_SKIP);
  200. $availableDrivers = PDO::getAvailableDrivers();
  201. if (in_array('odbc', $availableDrivers) &&
  202. ($config['odbc'] ?? null) === true) {
  203. return isset($config['odbc_datasource_name'])
  204. ? 'odbc:'.$config['odbc_datasource_name'] : '';
  205. }
  206. if (in_array('sqlsrv', $availableDrivers)) {
  207. $port = isset($config['port']) ? ','.$port : '';
  208. $appname = isset($config['appname']) ? ';APP='.$config['appname'] : '';
  209. $readonly = isset($config['readonly']) ? ';ApplicationIntent=ReadOnly' : '';
  210. $pooling = (isset($config['pooling']) && $config['pooling'] == false) ? ';ConnectionPooling=0' : '';
  211. return "sqlsrv:Server={$host}{$port};Database={$database}{$readonly}{$pooling}{$appname}";
  212. } else {
  213. $port = isset($config['port']) ? ':'.$port : '';
  214. $appname = isset($config['appname']) ? ';appname='.$config['appname'] : '';
  215. $charset = isset($config['charset']) ? ';charset='.$config['charset'] : '';
  216. return "dblib:host={$host}{$port};dbname={$database}{$charset}{$appname}";
  217. }
  218. }
  219. }