SqlserverTest.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\Database\Driver;
  16. use Cake\Database\Query;
  17. use Cake\TestSuite\TestCase;
  18. use PDO;
  19. /**
  20. * Tests Sqlserver driver
  21. */
  22. class SqlserverTest extends TestCase
  23. {
  24. /**
  25. * Set up
  26. *
  27. * @return void
  28. */
  29. public function setUp()
  30. {
  31. parent::setUp();
  32. $this->missingExtension = !defined('PDO::SQLSRV_ENCODING_UTF8');
  33. }
  34. /**
  35. * data provider for testDnsString
  36. *
  37. * @return array
  38. */
  39. public function dnsStringDataProvider()
  40. {
  41. return [
  42. [
  43. [
  44. 'app' => 'CakePHP-Testapp',
  45. 'encoding' => '',
  46. 'connectionPooling' => true,
  47. 'failoverPartner' => 'failover.local',
  48. 'loginTimeout' => 10,
  49. 'multiSubnetFailover' => 'failover.local',
  50. ],
  51. 'sqlsrv:Server=localhost\SQLEXPRESS;Database=cake;MultipleActiveResultSets=false;APP=CakePHP-Testapp;ConnectionPooling=1;Failover_Partner=failover.local;LoginTimeout=10;MultiSubnetFailover=failover.local',
  52. ],
  53. [
  54. [
  55. 'app' => 'CakePHP-Testapp',
  56. 'encoding' => '',
  57. 'failoverPartner' => 'failover.local',
  58. 'multiSubnetFailover' => 'failover.local',
  59. ],
  60. 'sqlsrv:Server=localhost\SQLEXPRESS;Database=cake;MultipleActiveResultSets=false;APP=CakePHP-Testapp;Failover_Partner=failover.local;MultiSubnetFailover=failover.local',
  61. ],
  62. [
  63. [
  64. 'encoding' => '',
  65. ],
  66. 'sqlsrv:Server=localhost\SQLEXPRESS;Database=cake;MultipleActiveResultSets=false',
  67. ],
  68. [
  69. [
  70. 'app' => 'CakePHP-Testapp',
  71. 'encoding' => '',
  72. 'host' => 'localhost\SQLEXPRESS',
  73. 'port' => 9001,
  74. ],
  75. 'sqlsrv:Server=localhost\SQLEXPRESS,9001;Database=cake;MultipleActiveResultSets=false;APP=CakePHP-Testapp',
  76. ],
  77. ];
  78. }
  79. /**
  80. * Test if all options in dns string are set
  81. *
  82. * @dataProvider dnsStringDataProvider
  83. * @param array $constructorArgs
  84. * @param string $dnsString
  85. * @return void
  86. */
  87. public function testDnsString($constructorArgs, $dnsString)
  88. {
  89. $driver = $this->getMockBuilder('Cake\Database\Driver\Sqlserver')
  90. ->setMethods(['_connect'])
  91. ->setConstructorArgs([$constructorArgs])
  92. ->getMock();
  93. $driver->method('_connect')
  94. ->with($this->callback(function ($dns) use ($dnsString) {
  95. $this->assertSame($dns, $dnsString);
  96. return true;
  97. }))
  98. ->will($this->returnValue([]));
  99. $driver->connect();
  100. }
  101. /**
  102. * Test connecting to Sqlserver with custom configuration
  103. *
  104. * @return void
  105. */
  106. public function testConnectionConfigCustom()
  107. {
  108. $this->skipIf($this->missingExtension, 'pdo_sqlsrv is not installed.');
  109. $config = [
  110. 'host' => 'foo',
  111. 'username' => 'Administrator',
  112. 'password' => 'blablabla',
  113. 'database' => 'bar',
  114. 'encoding' => 'a-language',
  115. 'flags' => [1 => true, 2 => false],
  116. 'init' => ['Execute this', 'this too'],
  117. 'settings' => ['config1' => 'value1', 'config2' => 'value2'],
  118. ];
  119. $driver = $this->getMockBuilder('Cake\Database\Driver\Sqlserver')
  120. ->setMethods(['_connect', 'setConnection', 'getConnection'])
  121. ->setConstructorArgs([$config])
  122. ->getMock();
  123. $dsn = 'sqlsrv:Server=foo;Database=bar;MultipleActiveResultSets=false';
  124. $expected = $config;
  125. $expected['flags'] += [
  126. PDO::ATTR_EMULATE_PREPARES => false,
  127. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  128. PDO::SQLSRV_ATTR_ENCODING => 'a-language',
  129. ];
  130. $expected['attributes'] = [];
  131. $expected['app'] = null;
  132. $expected['connectionPooling'] = null;
  133. $expected['failoverPartner'] = null;
  134. $expected['loginTimeout'] = null;
  135. $expected['multiSubnetFailover'] = null;
  136. $expected['port'] = null;
  137. $connection = $this->getMockBuilder('stdClass')
  138. ->setMethods(['exec', 'quote'])
  139. ->getMock();
  140. $connection->expects($this->any())
  141. ->method('quote')
  142. ->will($this->onConsecutiveCalls(
  143. $this->returnArgument(0),
  144. $this->returnArgument(0),
  145. $this->returnArgument(0)
  146. ));
  147. $connection->expects($this->at(0))->method('exec')->with('Execute this');
  148. $connection->expects($this->at(1))->method('exec')->with('this too');
  149. $connection->expects($this->at(2))->method('exec')->with('SET config1 value1');
  150. $connection->expects($this->at(3))->method('exec')->with('SET config2 value2');
  151. $driver->setConnection($connection);
  152. $driver->expects($this->once())->method('_connect')
  153. ->with($dsn, $expected);
  154. $driver->expects($this->any())->method('getConnection')
  155. ->will($this->returnValue($connection));
  156. $driver->connect();
  157. }
  158. /**
  159. * Test connecting to Sqlserver with persistent set to false
  160. *
  161. * @return void
  162. */
  163. public function testConnectionPersistentFalse()
  164. {
  165. $this->skipIf($this->missingExtension, 'pdo_sqlsrv is not installed.');
  166. $config = [
  167. 'persistent' => false,
  168. 'host' => 'foo',
  169. 'username' => 'Administrator',
  170. 'password' => 'blablabla',
  171. 'database' => 'bar',
  172. 'encoding' => 'a-language',
  173. ];
  174. $driver = $this->getMockBuilder('Cake\Database\Driver\Sqlserver')
  175. ->setMethods(['_connect', 'connection'])
  176. ->setConstructorArgs([$config])
  177. ->getMock();
  178. $dsn = 'sqlsrv:Server=foo;Database=bar;MultipleActiveResultSets=false';
  179. $expected = $config;
  180. $expected['flags'] = [
  181. PDO::ATTR_EMULATE_PREPARES => false,
  182. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  183. PDO::SQLSRV_ATTR_ENCODING => 'a-language',
  184. ];
  185. $expected['attributes'] = [];
  186. $expected['settings'] = [];
  187. $expected['init'] = [];
  188. $expected['app'] = null;
  189. $expected['connectionPooling'] = null;
  190. $expected['failoverPartner'] = null;
  191. $expected['loginTimeout'] = null;
  192. $expected['multiSubnetFailover'] = null;
  193. $expected['port'] = null;
  194. $driver->expects($this->once())->method('_connect')
  195. ->with($dsn, $expected);
  196. $driver->connect();
  197. }
  198. /**
  199. * Test if attempting to connect with the driver throws an exception when
  200. * using an invalid config setting.
  201. *
  202. * @return void
  203. */
  204. public function testConnectionPersistentTrueException()
  205. {
  206. $this->expectException(\InvalidArgumentException::class);
  207. $this->expectExceptionMessage('Config setting "persistent" cannot be set to true, as the Sqlserver PDO driver does not support PDO::ATTR_PERSISTENT');
  208. $this->skipIf($this->missingExtension, 'pdo_sqlsrv is not installed.');
  209. $config = [
  210. 'persistent' => true,
  211. 'host' => 'foo',
  212. 'username' => 'Administrator',
  213. 'password' => 'blablabla',
  214. 'database' => 'bar',
  215. ];
  216. $driver = $this->getMockBuilder('Cake\Database\Driver\Sqlserver')
  217. ->setMethods(['_connect', 'connection'])
  218. ->setConstructorArgs([$config])
  219. ->getMock();
  220. $driver->connect();
  221. }
  222. /**
  223. * Test select with limit only and SQLServer2012+
  224. *
  225. * @return void
  226. */
  227. public function testSelectLimitVersion12()
  228. {
  229. $driver = $this->getMockBuilder('Cake\Database\Driver\Sqlserver')
  230. ->setMethods(['_connect', 'getConnection', '_version'])
  231. ->setConstructorArgs([[]])
  232. ->getMock();
  233. $driver->expects($this->any())
  234. ->method('_version')
  235. ->will($this->returnValue(12));
  236. $connection = $this->getMockBuilder('\Cake\Database\Connection')
  237. ->setMethods(['connect', 'getDriver', 'setDriver'])
  238. ->setConstructorArgs([['log' => false]])
  239. ->getMock();
  240. $connection->expects($this->any())
  241. ->method('getDriver')
  242. ->will($this->returnValue($driver));
  243. $query = new Query($connection);
  244. $query->select(['id', 'title'])
  245. ->from('articles')
  246. ->order(['id'])
  247. ->offset(10);
  248. $this->assertEquals('SELECT id, title FROM articles ORDER BY id OFFSET 10 ROWS', $query->sql());
  249. $query = new Query($connection);
  250. $query->select(['id', 'title'])
  251. ->from('articles')
  252. ->order(['id'])
  253. ->limit(10)
  254. ->offset(50);
  255. $this->assertEquals('SELECT id, title FROM articles ORDER BY id OFFSET 50 ROWS FETCH FIRST 10 ROWS ONLY', $query->sql());
  256. $query = new Query($connection);
  257. $query->select(['id', 'title'])
  258. ->from('articles')
  259. ->offset(10);
  260. $this->assertEquals('SELECT id, title FROM articles ORDER BY (SELECT NULL) OFFSET 10 ROWS', $query->sql());
  261. $query = new Query($connection);
  262. $query->select(['id', 'title'])
  263. ->from('articles')
  264. ->limit(10);
  265. $this->assertEquals('SELECT TOP 10 id, title FROM articles', $query->sql());
  266. }
  267. /**
  268. * Test select with limit on lte SQLServer2008
  269. *
  270. * @return void
  271. */
  272. public function testSelectLimitOldServer()
  273. {
  274. $driver = $this->getMockBuilder('Cake\Database\Driver\Sqlserver')
  275. ->setMethods(['_connect', 'getConnection', '_version'])
  276. ->setConstructorArgs([[]])
  277. ->getMock();
  278. $driver->expects($this->any())
  279. ->method('_version')
  280. ->will($this->returnValue(8));
  281. $connection = $this->getMockBuilder('\Cake\Database\Connection')
  282. ->setMethods(['connect', 'getDriver', 'setDriver'])
  283. ->setConstructorArgs([['log' => false]])
  284. ->getMock();
  285. $connection->expects($this->any())
  286. ->method('getDriver')
  287. ->will($this->returnValue($driver));
  288. $query = new Query($connection);
  289. $query->select(['id', 'title'])
  290. ->from('articles')
  291. ->limit(10);
  292. $expected = 'SELECT TOP 10 id, title FROM articles';
  293. $this->assertEquals($expected, $query->sql());
  294. $query = new Query($connection);
  295. $query->select(['id', 'title'])
  296. ->from('articles')
  297. ->offset(10);
  298. $expected = 'SELECT * FROM (SELECT id, title, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) AS [_cake_page_rownum_] ' .
  299. 'FROM articles) _cake_paging_ ' .
  300. 'WHERE _cake_paging_._cake_page_rownum_ > 10';
  301. $this->assertEquals($expected, $query->sql());
  302. $query = new Query($connection);
  303. $query->select(['id', 'title'])
  304. ->from('articles')
  305. ->order(['id'])
  306. ->offset(10);
  307. $expected = 'SELECT * FROM (SELECT id, title, (ROW_NUMBER() OVER (ORDER BY id)) AS [_cake_page_rownum_] ' .
  308. 'FROM articles) _cake_paging_ ' .
  309. 'WHERE _cake_paging_._cake_page_rownum_ > 10';
  310. $this->assertEquals($expected, $query->sql());
  311. $query = new Query($connection);
  312. $query->select(['id', 'title'])
  313. ->from('articles')
  314. ->order(['id'])
  315. ->where(['title' => 'Something'])
  316. ->limit(10)
  317. ->offset(50);
  318. $expected = 'SELECT * FROM (SELECT id, title, (ROW_NUMBER() OVER (ORDER BY id)) AS [_cake_page_rownum_] ' .
  319. 'FROM articles WHERE title = :c0) _cake_paging_ ' .
  320. 'WHERE (_cake_paging_._cake_page_rownum_ > 50 AND _cake_paging_._cake_page_rownum_ <= 60)';
  321. $this->assertEquals($expected, $query->sql());
  322. }
  323. /**
  324. * Test that insert queries have results available to them.
  325. *
  326. * @return void
  327. */
  328. public function testInsertUsesOutput()
  329. {
  330. $driver = $this->getMockBuilder('Cake\Database\Driver\Sqlserver')
  331. ->setMethods(['_connect', 'getConnection'])
  332. ->setConstructorArgs([[]])
  333. ->getMock();
  334. $connection = $this->getMockBuilder('\Cake\Database\Connection')
  335. ->setMethods(['connect', 'getDriver', 'setDriver'])
  336. ->setConstructorArgs([['log' => false]])
  337. ->getMock();
  338. $connection->expects($this->any())
  339. ->method('getDriver')
  340. ->will($this->returnValue($driver));
  341. $query = new Query($connection);
  342. $query->insert(['title'])
  343. ->into('articles')
  344. ->values(['title' => 'A new article']);
  345. $expected = 'INSERT INTO articles (title) OUTPUT INSERTED.* VALUES (:c0)';
  346. $this->assertEquals($expected, $query->sql());
  347. }
  348. }