SqlserverTest.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  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_ERRMODE => PDO::ERRMODE_EXCEPTION,
  127. PDO::SQLSRV_ATTR_ENCODING => 'a-language',
  128. ];
  129. $expected['attributes'] = [];
  130. $expected['app'] = null;
  131. $expected['connectionPooling'] = null;
  132. $expected['failoverPartner'] = null;
  133. $expected['loginTimeout'] = null;
  134. $expected['multiSubnetFailover'] = null;
  135. $expected['port'] = null;
  136. $connection = $this->getMockBuilder('stdClass')
  137. ->setMethods(['exec', 'quote'])
  138. ->getMock();
  139. $connection->expects($this->any())
  140. ->method('quote')
  141. ->will($this->onConsecutiveCalls(
  142. $this->returnArgument(0),
  143. $this->returnArgument(0),
  144. $this->returnArgument(0)
  145. ));
  146. $connection->expects($this->at(0))->method('exec')->with('Execute this');
  147. $connection->expects($this->at(1))->method('exec')->with('this too');
  148. $connection->expects($this->at(2))->method('exec')->with('SET config1 value1');
  149. $connection->expects($this->at(3))->method('exec')->with('SET config2 value2');
  150. $driver->setConnection($connection);
  151. $driver->expects($this->once())->method('_connect')
  152. ->with($dsn, $expected);
  153. $driver->expects($this->any())->method('getConnection')
  154. ->will($this->returnValue($connection));
  155. $driver->connect();
  156. }
  157. /**
  158. * Test connecting to Sqlserver with persistent set to false
  159. *
  160. * @return void
  161. */
  162. public function testConnectionPersistentFalse()
  163. {
  164. $this->skipIf($this->missingExtension, 'pdo_sqlsrv is not installed.');
  165. $config = [
  166. 'persistent' => false,
  167. 'host' => 'foo',
  168. 'username' => 'Administrator',
  169. 'password' => 'blablabla',
  170. 'database' => 'bar',
  171. 'encoding' => 'a-language',
  172. ];
  173. $driver = $this->getMockBuilder('Cake\Database\Driver\Sqlserver')
  174. ->setMethods(['_connect', 'connection'])
  175. ->setConstructorArgs([$config])
  176. ->getMock();
  177. $dsn = 'sqlsrv:Server=foo;Database=bar;MultipleActiveResultSets=false';
  178. $expected = $config;
  179. $expected['flags'] = [
  180. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  181. PDO::SQLSRV_ATTR_ENCODING => 'a-language',
  182. ];
  183. $expected['attributes'] = [];
  184. $expected['settings'] = [];
  185. $expected['init'] = [];
  186. $expected['app'] = null;
  187. $expected['connectionPooling'] = null;
  188. $expected['failoverPartner'] = null;
  189. $expected['loginTimeout'] = null;
  190. $expected['multiSubnetFailover'] = null;
  191. $expected['port'] = null;
  192. $driver->expects($this->once())->method('_connect')
  193. ->with($dsn, $expected);
  194. $driver->connect();
  195. }
  196. /**
  197. * Test if attempting to connect with the driver throws an exception when
  198. * using an invalid config setting.
  199. *
  200. * @return void
  201. */
  202. public function testConnectionPersistentTrueException()
  203. {
  204. $this->expectException(\InvalidArgumentException::class);
  205. $this->expectExceptionMessage('Config setting "persistent" cannot be set to true, as the Sqlserver PDO driver does not support PDO::ATTR_PERSISTENT');
  206. $this->skipIf($this->missingExtension, 'pdo_sqlsrv is not installed.');
  207. $config = [
  208. 'persistent' => true,
  209. 'host' => 'foo',
  210. 'username' => 'Administrator',
  211. 'password' => 'blablabla',
  212. 'database' => 'bar',
  213. ];
  214. $driver = $this->getMockBuilder('Cake\Database\Driver\Sqlserver')
  215. ->setMethods(['_connect', 'connection'])
  216. ->setConstructorArgs([$config])
  217. ->getMock();
  218. $driver->connect();
  219. }
  220. /**
  221. * Test select with limit only and SQLServer2012+
  222. *
  223. * @return void
  224. */
  225. public function testSelectLimitVersion12()
  226. {
  227. $driver = $this->getMockBuilder('Cake\Database\Driver\Sqlserver')
  228. ->setMethods(['_connect', 'getConnection', '_version'])
  229. ->setConstructorArgs([[]])
  230. ->getMock();
  231. $driver->expects($this->any())
  232. ->method('_version')
  233. ->will($this->returnValue(12));
  234. $connection = $this->getMockBuilder('\Cake\Database\Connection')
  235. ->setMethods(['connect', 'getDriver', 'setDriver'])
  236. ->setConstructorArgs([['log' => false]])
  237. ->getMock();
  238. $connection->expects($this->any())
  239. ->method('getDriver')
  240. ->will($this->returnValue($driver));
  241. $query = new Query($connection);
  242. $query->select(['id', 'title'])
  243. ->from('articles')
  244. ->order(['id'])
  245. ->offset(10);
  246. $this->assertEquals('SELECT id, title FROM articles ORDER BY id OFFSET 10 ROWS', $query->sql());
  247. $query = new Query($connection);
  248. $query->select(['id', 'title'])
  249. ->from('articles')
  250. ->order(['id'])
  251. ->limit(10)
  252. ->offset(50);
  253. $this->assertEquals('SELECT id, title FROM articles ORDER BY id OFFSET 50 ROWS FETCH FIRST 10 ROWS ONLY', $query->sql());
  254. $query = new Query($connection);
  255. $query->select(['id', 'title'])
  256. ->from('articles')
  257. ->offset(10);
  258. $this->assertEquals('SELECT id, title FROM articles ORDER BY (SELECT NULL) OFFSET 10 ROWS', $query->sql());
  259. $query = new Query($connection);
  260. $query->select(['id', 'title'])
  261. ->from('articles')
  262. ->limit(10);
  263. $this->assertEquals('SELECT TOP 10 id, title FROM articles', $query->sql());
  264. }
  265. /**
  266. * Test select with limit on lte SQLServer2008
  267. *
  268. * @return void
  269. */
  270. public function testSelectLimitOldServer()
  271. {
  272. $driver = $this->getMockBuilder('Cake\Database\Driver\Sqlserver')
  273. ->setMethods(['_connect', 'getConnection', '_version'])
  274. ->setConstructorArgs([[]])
  275. ->getMock();
  276. $driver->expects($this->any())
  277. ->method('_version')
  278. ->will($this->returnValue(8));
  279. $connection = $this->getMockBuilder('\Cake\Database\Connection')
  280. ->setMethods(['connect', 'getDriver', 'setDriver'])
  281. ->setConstructorArgs([['log' => false]])
  282. ->getMock();
  283. $connection->expects($this->any())
  284. ->method('getDriver')
  285. ->will($this->returnValue($driver));
  286. $query = new Query($connection);
  287. $query->select(['id', 'title'])
  288. ->from('articles')
  289. ->limit(10);
  290. $expected = 'SELECT TOP 10 id, title FROM articles';
  291. $this->assertEquals($expected, $query->sql());
  292. $query = new Query($connection);
  293. $query->select(['id', 'title'])
  294. ->from('articles')
  295. ->offset(10);
  296. $expected = 'SELECT * FROM (SELECT id, title, (ROW_NUMBER() OVER (ORDER BY (SELECT NULL))) AS [_cake_page_rownum_] ' .
  297. 'FROM articles) _cake_paging_ ' .
  298. 'WHERE _cake_paging_._cake_page_rownum_ > 10';
  299. $this->assertEquals($expected, $query->sql());
  300. $query = new Query($connection);
  301. $query->select(['id', 'title'])
  302. ->from('articles')
  303. ->order(['id'])
  304. ->offset(10);
  305. $expected = 'SELECT * FROM (SELECT id, title, (ROW_NUMBER() OVER (ORDER BY id)) AS [_cake_page_rownum_] ' .
  306. 'FROM articles) _cake_paging_ ' .
  307. 'WHERE _cake_paging_._cake_page_rownum_ > 10';
  308. $this->assertEquals($expected, $query->sql());
  309. $query = new Query($connection);
  310. $query->select(['id', 'title'])
  311. ->from('articles')
  312. ->order(['id'])
  313. ->where(['title' => 'Something'])
  314. ->limit(10)
  315. ->offset(50);
  316. $expected = 'SELECT * FROM (SELECT id, title, (ROW_NUMBER() OVER (ORDER BY id)) AS [_cake_page_rownum_] ' .
  317. 'FROM articles WHERE title = :c0) _cake_paging_ ' .
  318. 'WHERE (_cake_paging_._cake_page_rownum_ > 50 AND _cake_paging_._cake_page_rownum_ <= 60)';
  319. $this->assertEquals($expected, $query->sql());
  320. $query = new Query($connection);
  321. $subquery = new Query($connection);
  322. $subquery->select(1);
  323. $query
  324. ->select([
  325. 'id',
  326. 'computed' => $subquery,
  327. ])
  328. ->from('articles')
  329. ->order([
  330. 'computed' => 'ASC',
  331. ])
  332. ->offset(10);
  333. $expected =
  334. 'SELECT * FROM (' .
  335. 'SELECT id, (SELECT 1) AS [computed], ' .
  336. '(ROW_NUMBER() OVER (ORDER BY (SELECT 1) ASC)) AS [_cake_page_rownum_] FROM articles' .
  337. ') _cake_paging_ ' .
  338. 'WHERE _cake_paging_._cake_page_rownum_ > 10';
  339. $this->assertEquals($expected, $query->sql());
  340. $subqueryA = new Query($connection);
  341. $subqueryA
  342. ->select('count(*)')
  343. ->from(['a' => 'articles'])
  344. ->where([
  345. 'a.id = articles.id',
  346. 'a.published' => 'Y',
  347. ]);
  348. $subqueryB = new Query($connection);
  349. $subqueryB
  350. ->select('count(*)')
  351. ->from(['b' => 'articles'])
  352. ->where([
  353. 'b.id = articles.id',
  354. 'b.published' => 'N',
  355. ]);
  356. $query = new Query($connection);
  357. $query
  358. ->select([
  359. 'id',
  360. 'computedA' => $subqueryA,
  361. 'computedB' => $subqueryB,
  362. ])
  363. ->from('articles')
  364. ->order([
  365. 'computedA' => 'ASC',
  366. ])
  367. ->offset(10);
  368. $this->assertSame(
  369. 'SELECT * FROM (' .
  370. 'SELECT id, ' .
  371. '(SELECT count(*) FROM articles a WHERE (a.id = articles.id AND a.published = :c0)) AS [computedA], ' .
  372. '(SELECT count(*) FROM articles b WHERE (b.id = articles.id AND b.published = :c1)) AS [computedB], ' .
  373. '(ROW_NUMBER() OVER (ORDER BY (SELECT count(*) FROM articles a WHERE (a.id = articles.id AND a.published = :c2)) ASC)) AS [_cake_page_rownum_] FROM articles' .
  374. ') _cake_paging_ ' .
  375. 'WHERE _cake_paging_._cake_page_rownum_ > 10',
  376. $query->sql()
  377. );
  378. }
  379. /**
  380. * Test that insert queries have results available to them.
  381. *
  382. * @return void
  383. */
  384. public function testInsertUsesOutput()
  385. {
  386. $driver = $this->getMockBuilder('Cake\Database\Driver\Sqlserver')
  387. ->setMethods(['_connect', 'getConnection'])
  388. ->setConstructorArgs([[]])
  389. ->getMock();
  390. $connection = $this->getMockBuilder('\Cake\Database\Connection')
  391. ->setMethods(['connect', 'getDriver', 'setDriver'])
  392. ->setConstructorArgs([['log' => false]])
  393. ->getMock();
  394. $connection->expects($this->any())
  395. ->method('getDriver')
  396. ->will($this->returnValue($driver));
  397. $query = new Query($connection);
  398. $query->insert(['title'])
  399. ->into('articles')
  400. ->values(['title' => 'A new article']);
  401. $expected = 'INSERT INTO articles (title) OUTPUT INSERTED.* VALUES (:c0)';
  402. $this->assertEquals($expected, $query->sql());
  403. }
  404. }