SqlserverSchemaTest.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://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. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\Database\Schema;
  16. use Cake\Database\Schema\Collection as SchemaCollection;
  17. use Cake\Database\Schema\SqlserverSchema;
  18. use Cake\Database\Schema\Table;
  19. use Cake\Datasource\ConnectionManager;
  20. use Cake\TestSuite\TestCase;
  21. /**
  22. * SQL Server schema test case.
  23. */
  24. class SqlserverSchemaTest extends TestCase
  25. {
  26. /**
  27. * Helper method for skipping tests that need a real connection.
  28. *
  29. * @return void
  30. */
  31. protected function _needsConnection()
  32. {
  33. $config = ConnectionManager::config('test');
  34. $this->skipIf(strpos($config['driver'], 'Sqlserver') === false, 'Not using Sqlserver for test config');
  35. }
  36. /**
  37. * Helper method for testing methods.
  38. *
  39. * @return void
  40. */
  41. protected function _createTables($connection)
  42. {
  43. $this->_needsConnection();
  44. $connection->execute("IF OBJECT_ID('schema_articles', 'U') IS NOT NULL DROP TABLE schema_articles");
  45. $connection->execute("IF OBJECT_ID('schema_authors', 'U') IS NOT NULL DROP TABLE schema_authors");
  46. $table = <<<SQL
  47. CREATE TABLE schema_authors (
  48. id int IDENTITY(1,1) PRIMARY KEY,
  49. name VARCHAR(50),
  50. bio DATE,
  51. created DATETIME
  52. )
  53. SQL;
  54. $connection->execute($table);
  55. $table = <<<SQL
  56. CREATE TABLE schema_articles (
  57. id BIGINT PRIMARY KEY,
  58. title VARCHAR(20) COLLATE Japanese_Unicode_CI_AI,
  59. body VARCHAR(1000),
  60. author_id INTEGER NOT NULL,
  61. published BIT DEFAULT 0,
  62. views SMALLINT DEFAULT 0,
  63. created DATETIME,
  64. field1 VARCHAR(10) DEFAULT NULL,
  65. field2 VARCHAR(10) DEFAULT 'NULL',
  66. field3 VARCHAR(10) DEFAULT 'O''hare',
  67. CONSTRAINT [content_idx] UNIQUE ([title], [body]),
  68. CONSTRAINT [author_idx] FOREIGN KEY ([author_id]) REFERENCES [schema_authors] ([id]) ON DELETE CASCADE ON UPDATE CASCADE
  69. )
  70. SQL;
  71. $connection->execute($table);
  72. $connection->execute('CREATE INDEX [author_idx] ON [schema_articles] ([author_id])');
  73. }
  74. /**
  75. * Data provider for convert column testing
  76. *
  77. * @return array
  78. */
  79. public static function convertColumnProvider()
  80. {
  81. return [
  82. [
  83. 'DATETIME',
  84. null,
  85. null,
  86. null,
  87. ['type' => 'timestamp', 'length' => null]
  88. ],
  89. [
  90. 'DATE',
  91. null,
  92. null,
  93. null,
  94. ['type' => 'date', 'length' => null]
  95. ],
  96. [
  97. 'TIME',
  98. null,
  99. null,
  100. null,
  101. ['type' => 'time', 'length' => null]
  102. ],
  103. [
  104. 'SMALLINT',
  105. null,
  106. 4,
  107. null,
  108. ['type' => 'integer', 'length' => 4]
  109. ],
  110. [
  111. 'INTEGER',
  112. null,
  113. null,
  114. null,
  115. ['type' => 'integer', 'length' => 10]
  116. ],
  117. [
  118. 'INTEGER',
  119. null,
  120. 8,
  121. null,
  122. ['type' => 'integer', 'length' => 8]
  123. ],
  124. [
  125. 'BIGINT',
  126. null,
  127. null,
  128. null,
  129. ['type' => 'biginteger', 'length' => 20]
  130. ],
  131. [
  132. 'NUMERIC',
  133. null,
  134. 15,
  135. 5,
  136. ['type' => 'decimal', 'length' => 15, 'precision' => 5]
  137. ],
  138. [
  139. 'DECIMAL',
  140. null,
  141. 11,
  142. 3,
  143. ['type' => 'decimal', 'length' => 11, 'precision' => 3]
  144. ],
  145. [
  146. 'MONEY',
  147. null,
  148. null,
  149. null,
  150. ['type' => 'decimal', 'length' => null, 'precision' => null]
  151. ],
  152. [
  153. 'VARCHAR',
  154. null,
  155. null,
  156. null,
  157. ['type' => 'string', 'length' => 255]
  158. ],
  159. [
  160. 'VARCHAR',
  161. 10,
  162. null,
  163. null,
  164. ['type' => 'string', 'length' => 10]
  165. ],
  166. [
  167. 'NVARCHAR',
  168. 50,
  169. null,
  170. null,
  171. ['type' => 'string', 'length' => 50]
  172. ],
  173. [
  174. 'CHAR',
  175. 10,
  176. null,
  177. null,
  178. ['type' => 'string', 'fixed' => true, 'length' => 10]
  179. ],
  180. [
  181. 'NCHAR',
  182. 10,
  183. null,
  184. null,
  185. ['type' => 'string', 'fixed' => true, 'length' => 10]
  186. ],
  187. [
  188. 'UNIQUEIDENTIFIER',
  189. null,
  190. null,
  191. null,
  192. ['type' => 'uuid']
  193. ],
  194. [
  195. 'TEXT',
  196. null,
  197. null,
  198. null,
  199. ['type' => 'text', 'length' => null]
  200. ],
  201. [
  202. 'REAL',
  203. null,
  204. null,
  205. null,
  206. ['type' => 'float', 'length' => null]
  207. ],
  208. [
  209. 'VARCHAR',
  210. -1,
  211. null,
  212. null,
  213. ['type' => 'text', 'length' => null]
  214. ],
  215. ];
  216. }
  217. /**
  218. * Test parsing Sqlserver column types from field description.
  219. *
  220. * @dataProvider convertColumnProvider
  221. * @return void
  222. */
  223. public function testConvertColumn($type, $length, $precision, $scale, $expected)
  224. {
  225. $field = [
  226. 'name' => 'field',
  227. 'type' => $type,
  228. 'null' => '1',
  229. 'default' => 'Default value',
  230. 'char_length' => $length,
  231. 'precision' => $precision,
  232. 'scale' => $scale,
  233. 'collation_name' => 'Japanese_Unicode_CI_AI',
  234. ];
  235. $expected += [
  236. 'null' => true,
  237. 'default' => 'Default value',
  238. 'collate' => 'Japanese_Unicode_CI_AI',
  239. ];
  240. $driver = $this->getMockBuilder('Cake\Database\Driver\Sqlserver')->getMock();
  241. $dialect = new SqlserverSchema($driver);
  242. $table = $this->getMockBuilder('Cake\Database\Schema\Table')
  243. ->setConstructorArgs(['table'])
  244. ->getMock();
  245. $table->expects($this->at(0))->method('addColumn')->with('field', $expected);
  246. $dialect->convertColumnDescription($table, $field);
  247. }
  248. /**
  249. * Test listing tables with Sqlserver
  250. *
  251. * @return void
  252. */
  253. public function testListTables()
  254. {
  255. $connection = ConnectionManager::get('test');
  256. $this->_createTables($connection);
  257. $schema = new SchemaCollection($connection);
  258. $result = $schema->listTables();
  259. $this->assertInternalType('array', $result);
  260. $this->assertContains('schema_articles', $result);
  261. $this->assertContains('schema_authors', $result);
  262. }
  263. /**
  264. * Test describing a table with Sqlserver
  265. *
  266. * @return void
  267. */
  268. public function testDescribeTable()
  269. {
  270. $connection = ConnectionManager::get('test');
  271. $this->_createTables($connection);
  272. $schema = new SchemaCollection($connection);
  273. $result = $schema->describe('schema_articles');
  274. $expected = [
  275. 'id' => [
  276. 'type' => 'biginteger',
  277. 'null' => false,
  278. 'default' => null,
  279. 'length' => 19,
  280. 'precision' => null,
  281. 'unsigned' => null,
  282. 'autoIncrement' => null,
  283. 'comment' => null,
  284. ],
  285. 'title' => [
  286. 'type' => 'string',
  287. 'null' => true,
  288. 'default' => null,
  289. 'length' => 20,
  290. 'precision' => null,
  291. 'comment' => null,
  292. 'fixed' => null,
  293. 'collate' => 'Japanese_Unicode_CI_AI',
  294. ],
  295. 'body' => [
  296. 'type' => 'string',
  297. 'null' => true,
  298. 'default' => null,
  299. 'length' => 1000,
  300. 'precision' => null,
  301. 'fixed' => null,
  302. 'comment' => null,
  303. 'collate' => 'SQL_Latin1_General_CP1_CI_AS',
  304. ],
  305. 'author_id' => [
  306. 'type' => 'integer',
  307. 'null' => false,
  308. 'default' => null,
  309. 'length' => 10,
  310. 'precision' => null,
  311. 'unsigned' => null,
  312. 'autoIncrement' => null,
  313. 'comment' => null,
  314. ],
  315. 'published' => [
  316. 'type' => 'boolean',
  317. 'null' => true,
  318. 'default' => 0,
  319. 'length' => null,
  320. 'precision' => null,
  321. 'comment' => null,
  322. ],
  323. 'views' => [
  324. 'type' => 'integer',
  325. 'null' => true,
  326. 'default' => 0,
  327. 'length' => 5,
  328. 'precision' => null,
  329. 'unsigned' => null,
  330. 'autoIncrement' => null,
  331. 'comment' => null,
  332. ],
  333. 'created' => [
  334. 'type' => 'timestamp',
  335. 'null' => true,
  336. 'default' => null,
  337. 'length' => null,
  338. 'precision' => null,
  339. 'comment' => null,
  340. ],
  341. 'field1' => [
  342. 'type' => 'string',
  343. 'null' => true,
  344. 'default' => null,
  345. 'length' => 10,
  346. 'precision' => null,
  347. 'fixed' => null,
  348. 'comment' => null,
  349. 'collate' => 'SQL_Latin1_General_CP1_CI_AS',
  350. ],
  351. 'field2' => [
  352. 'type' => 'string',
  353. 'null' => true,
  354. 'default' => 'NULL',
  355. 'length' => 10,
  356. 'precision' => null,
  357. 'fixed' => null,
  358. 'comment' => null,
  359. 'collate' => 'SQL_Latin1_General_CP1_CI_AS',
  360. ],
  361. 'field3' => [
  362. 'type' => 'string',
  363. 'null' => true,
  364. 'default' => 'O\'hare',
  365. 'length' => 10,
  366. 'precision' => null,
  367. 'fixed' => null,
  368. 'comment' => null,
  369. 'collate' => 'SQL_Latin1_General_CP1_CI_AS',
  370. ],
  371. ];
  372. $this->assertEquals(['id'], $result->primaryKey());
  373. foreach ($expected as $field => $definition) {
  374. $this->assertEquals($definition, $result->column($field), 'Failed to match field ' . $field);
  375. }
  376. }
  377. /**
  378. * Test describing a table with postgres and composite keys
  379. *
  380. * @return void
  381. */
  382. public function testDescribeTableCompositeKey()
  383. {
  384. $this->_needsConnection();
  385. $connection = ConnectionManager::get('test');
  386. $sql = <<<SQL
  387. CREATE TABLE schema_composite (
  388. [id] INTEGER IDENTITY(1, 1),
  389. [site_id] INTEGER NOT NULL,
  390. [name] VARCHAR(255),
  391. PRIMARY KEY([id], [site_id])
  392. );
  393. SQL;
  394. $connection->execute($sql);
  395. $schema = new SchemaCollection($connection);
  396. $result = $schema->describe('schema_composite');
  397. $connection->execute('DROP TABLE schema_composite');
  398. $this->assertEquals(['id', 'site_id'], $result->primaryKey());
  399. $this->assertNull($result->column('site_id')['autoIncrement'], 'site_id should not be autoincrement');
  400. $this->assertTrue($result->column('id')['autoIncrement'], 'id should be autoincrement');
  401. }
  402. /**
  403. * Test that describe accepts tablenames containing `schema.table`.
  404. *
  405. * @return void
  406. */
  407. public function testDescribeWithSchemaName()
  408. {
  409. $connection = ConnectionManager::get('test');
  410. $this->_createTables($connection);
  411. $schema = new SchemaCollection($connection);
  412. $result = $schema->describe('dbo.schema_articles');
  413. $this->assertEquals(['id'], $result->primaryKey());
  414. $this->assertEquals('schema_articles', $result->name());
  415. }
  416. /**
  417. * Test describing a table with indexes
  418. *
  419. * @return void
  420. */
  421. public function testDescribeTableIndexes()
  422. {
  423. $connection = ConnectionManager::get('test');
  424. $this->_createTables($connection);
  425. $schema = new SchemaCollection($connection);
  426. $result = $schema->describe('schema_articles');
  427. $this->assertInstanceOf('Cake\Database\Schema\Table', $result);
  428. $this->assertCount(3, $result->constraints());
  429. $expected = [
  430. 'primary' => [
  431. 'type' => 'primary',
  432. 'columns' => ['id'],
  433. 'length' => []
  434. ],
  435. 'content_idx' => [
  436. 'type' => 'unique',
  437. 'columns' => ['title', 'body'],
  438. 'length' => []
  439. ],
  440. 'author_idx' => [
  441. 'type' => 'foreign',
  442. 'columns' => ['author_id'],
  443. 'references' => ['schema_authors', 'id'],
  444. 'length' => [],
  445. 'update' => 'cascade',
  446. 'delete' => 'cascade',
  447. ]
  448. ];
  449. $this->assertEquals($expected['primary'], $result->constraint('primary'));
  450. $this->assertEquals($expected['content_idx'], $result->constraint('content_idx'));
  451. $this->assertEquals($expected['author_idx'], $result->constraint('author_idx'));
  452. $this->assertCount(1, $result->indexes());
  453. $expected = [
  454. 'type' => 'index',
  455. 'columns' => ['author_id'],
  456. 'length' => []
  457. ];
  458. $this->assertEquals($expected, $result->index('author_idx'));
  459. }
  460. /**
  461. * Column provider for creating column sql
  462. *
  463. * @return array
  464. */
  465. public static function columnSqlProvider()
  466. {
  467. return [
  468. // strings
  469. [
  470. 'title',
  471. ['type' => 'string', 'length' => 25, 'null' => false],
  472. '[title] NVARCHAR(25) NOT NULL'
  473. ],
  474. [
  475. 'title',
  476. ['type' => 'string', 'length' => 25, 'null' => true, 'default' => 'ignored'],
  477. '[title] NVARCHAR(25) DEFAULT NULL'
  478. ],
  479. [
  480. 'id',
  481. ['type' => 'string', 'length' => 32, 'fixed' => true, 'null' => false],
  482. '[id] NCHAR(32) NOT NULL'
  483. ],
  484. [
  485. 'id',
  486. ['type' => 'uuid', 'null' => false],
  487. '[id] UNIQUEIDENTIFIER NOT NULL'
  488. ],
  489. [
  490. 'role',
  491. ['type' => 'string', 'length' => 10, 'null' => false, 'default' => 'admin'],
  492. "[role] NVARCHAR(10) NOT NULL DEFAULT 'admin'"
  493. ],
  494. [
  495. 'title',
  496. ['type' => 'string'],
  497. '[title] NVARCHAR(255)'
  498. ],
  499. [
  500. 'title',
  501. ['type' => 'string', 'length' => 25, 'null' => false, 'collate' => 'Japanese_Unicode_CI_AI'],
  502. '[title] NVARCHAR(25) COLLATE Japanese_Unicode_CI_AI NOT NULL'
  503. ],
  504. // Text
  505. [
  506. 'body',
  507. ['type' => 'text', 'null' => false],
  508. '[body] NVARCHAR(MAX) NOT NULL'
  509. ],
  510. [
  511. 'body',
  512. ['type' => 'text', 'length' => Table::LENGTH_TINY, 'null' => false],
  513. sprintf('[body] NVARCHAR(%s) NOT NULL', Table::LENGTH_TINY)
  514. ],
  515. [
  516. 'body',
  517. ['type' => 'text', 'length' => Table::LENGTH_MEDIUM, 'null' => false],
  518. '[body] NVARCHAR(MAX) NOT NULL'
  519. ],
  520. [
  521. 'body',
  522. ['type' => 'text', 'length' => Table::LENGTH_LONG, 'null' => false],
  523. '[body] NVARCHAR(MAX) NOT NULL'
  524. ],
  525. [
  526. 'body',
  527. ['type' => 'text', 'null' => false, 'collate' => 'Japanese_Unicode_CI_AI'],
  528. '[body] NVARCHAR(MAX) COLLATE Japanese_Unicode_CI_AI NOT NULL'
  529. ],
  530. // Integers
  531. [
  532. 'post_id',
  533. ['type' => 'integer', 'length' => 11],
  534. '[post_id] INTEGER'
  535. ],
  536. [
  537. 'post_id',
  538. ['type' => 'biginteger', 'length' => 20],
  539. '[post_id] BIGINT'
  540. ],
  541. // Decimal
  542. [
  543. 'value',
  544. ['type' => 'decimal'],
  545. '[value] DECIMAL'
  546. ],
  547. [
  548. 'value',
  549. ['type' => 'decimal', 'length' => 11],
  550. '[value] DECIMAL(11,0)'
  551. ],
  552. [
  553. 'value',
  554. ['type' => 'decimal', 'length' => 12, 'precision' => 5],
  555. '[value] DECIMAL(12,5)'
  556. ],
  557. // Float
  558. [
  559. 'value',
  560. ['type' => 'float'],
  561. '[value] FLOAT'
  562. ],
  563. [
  564. 'value',
  565. ['type' => 'float', 'length' => 11, 'precision' => 3],
  566. '[value] FLOAT(3)'
  567. ],
  568. // Binary
  569. [
  570. 'img',
  571. ['type' => 'binary', 'length' => null],
  572. '[img] VARBINARY(MAX)'
  573. ],
  574. [
  575. 'img',
  576. ['type' => 'binary', 'length' => Table::LENGTH_TINY],
  577. sprintf('[img] VARBINARY(%s)', Table::LENGTH_TINY)
  578. ],
  579. [
  580. 'img',
  581. ['type' => 'binary', 'length' => Table::LENGTH_MEDIUM],
  582. '[img] VARBINARY(MAX)'
  583. ],
  584. [
  585. 'img',
  586. ['type' => 'binary', 'length' => Table::LENGTH_LONG],
  587. '[img] VARBINARY(MAX)'
  588. ],
  589. // Boolean
  590. [
  591. 'checked',
  592. ['type' => 'boolean', 'default' => false],
  593. '[checked] BIT DEFAULT 0'
  594. ],
  595. [
  596. 'checked',
  597. ['type' => 'boolean', 'default' => true, 'null' => false],
  598. '[checked] BIT NOT NULL DEFAULT 1'
  599. ],
  600. // datetimes
  601. [
  602. 'created',
  603. ['type' => 'datetime'],
  604. '[created] DATETIME'
  605. ],
  606. // Date & Time
  607. [
  608. 'start_date',
  609. ['type' => 'date'],
  610. '[start_date] DATE'
  611. ],
  612. [
  613. 'start_time',
  614. ['type' => 'time'],
  615. '[start_time] TIME'
  616. ],
  617. // timestamps
  618. [
  619. 'created',
  620. ['type' => 'timestamp', 'null' => true],
  621. '[created] DATETIME DEFAULT NULL'
  622. ],
  623. ];
  624. }
  625. /**
  626. * Test generating column definitions
  627. *
  628. * @dataProvider columnSqlProvider
  629. * @return void
  630. */
  631. public function testColumnSql($name, $data, $expected)
  632. {
  633. $driver = $this->_getMockedDriver();
  634. $schema = new SqlserverSchema($driver);
  635. $table = (new Table('schema_articles'))->addColumn($name, $data);
  636. $this->assertEquals($expected, $schema->columnSql($table, $name));
  637. }
  638. /**
  639. * Provide data for testing constraintSql
  640. *
  641. * @return array
  642. */
  643. public static function constraintSqlProvider()
  644. {
  645. return [
  646. [
  647. 'primary',
  648. ['type' => 'primary', 'columns' => ['title']],
  649. 'PRIMARY KEY ([title])'
  650. ],
  651. [
  652. 'unique_idx',
  653. ['type' => 'unique', 'columns' => ['title', 'author_id']],
  654. 'CONSTRAINT [unique_idx] UNIQUE ([title], [author_id])'
  655. ],
  656. [
  657. 'author_id_idx',
  658. ['type' => 'foreign', 'columns' => ['author_id'], 'references' => ['authors', 'id']],
  659. 'CONSTRAINT [author_id_idx] FOREIGN KEY ([author_id]) ' .
  660. 'REFERENCES [authors] ([id]) ON UPDATE SET NULL ON DELETE SET NULL'
  661. ],
  662. [
  663. 'author_id_idx',
  664. ['type' => 'foreign', 'columns' => ['author_id'], 'references' => ['authors', 'id'], 'update' => 'cascade'],
  665. 'CONSTRAINT [author_id_idx] FOREIGN KEY ([author_id]) ' .
  666. 'REFERENCES [authors] ([id]) ON UPDATE CASCADE ON DELETE SET NULL'
  667. ],
  668. [
  669. 'author_id_idx',
  670. ['type' => 'foreign', 'columns' => ['author_id'], 'references' => ['authors', 'id'], 'update' => 'setDefault'],
  671. 'CONSTRAINT [author_id_idx] FOREIGN KEY ([author_id]) ' .
  672. 'REFERENCES [authors] ([id]) ON UPDATE SET DEFAULT ON DELETE SET NULL'
  673. ],
  674. [
  675. 'author_id_idx',
  676. ['type' => 'foreign', 'columns' => ['author_id'], 'references' => ['authors', 'id'], 'update' => 'setNull'],
  677. 'CONSTRAINT [author_id_idx] FOREIGN KEY ([author_id]) ' .
  678. 'REFERENCES [authors] ([id]) ON UPDATE SET NULL ON DELETE SET NULL'
  679. ],
  680. [
  681. 'author_id_idx',
  682. ['type' => 'foreign', 'columns' => ['author_id'], 'references' => ['authors', 'id'], 'update' => 'noAction'],
  683. 'CONSTRAINT [author_id_idx] FOREIGN KEY ([author_id]) ' .
  684. 'REFERENCES [authors] ([id]) ON UPDATE NO ACTION ON DELETE SET NULL'
  685. ],
  686. ];
  687. }
  688. /**
  689. * Test the constraintSql method.
  690. *
  691. * @dataProvider constraintSqlProvider
  692. */
  693. public function testConstraintSql($name, $data, $expected)
  694. {
  695. $driver = $this->_getMockedDriver();
  696. $schema = new SqlserverSchema($driver);
  697. $table = (new Table('schema_articles'))->addColumn('title', [
  698. 'type' => 'string',
  699. 'length' => 255
  700. ])->addColumn('author_id', [
  701. 'type' => 'integer',
  702. ])->addConstraint($name, $data);
  703. $this->assertEquals($expected, $schema->constraintSql($table, $name));
  704. }
  705. /**
  706. * Test the addConstraintSql method.
  707. *
  708. * @return void
  709. */
  710. public function testAddConstraintSql()
  711. {
  712. $driver = $this->_getMockedDriver();
  713. $connection = $this->getMockBuilder('Cake\Database\Connection')
  714. ->disableOriginalConstructor()
  715. ->getMock();
  716. $connection->expects($this->any())->method('driver')
  717. ->will($this->returnValue($driver));
  718. $table = (new Table('posts'))
  719. ->addColumn('author_id', [
  720. 'type' => 'integer',
  721. 'null' => false
  722. ])
  723. ->addColumn('category_id', [
  724. 'type' => 'integer',
  725. 'null' => false
  726. ])
  727. ->addColumn('category_name', [
  728. 'type' => 'integer',
  729. 'null' => false
  730. ])
  731. ->addConstraint('author_fk', [
  732. 'type' => 'foreign',
  733. 'columns' => ['author_id'],
  734. 'references' => ['authors', 'id'],
  735. 'update' => 'cascade',
  736. 'delete' => 'cascade'
  737. ])
  738. ->addConstraint('category_fk', [
  739. 'type' => 'foreign',
  740. 'columns' => ['category_id', 'category_name'],
  741. 'references' => ['categories', ['id', 'name']],
  742. 'update' => 'cascade',
  743. 'delete' => 'cascade'
  744. ]);
  745. $expected = [
  746. 'ALTER TABLE [posts] ADD CONSTRAINT [author_fk] FOREIGN KEY ([author_id]) REFERENCES [authors] ([id]) ON UPDATE CASCADE ON DELETE CASCADE;',
  747. 'ALTER TABLE [posts] ADD CONSTRAINT [category_fk] FOREIGN KEY ([category_id], [category_name]) REFERENCES [categories] ([id], [name]) ON UPDATE CASCADE ON DELETE CASCADE;'
  748. ];
  749. $result = $table->addConstraintSql($connection);
  750. $this->assertCount(2, $result);
  751. $this->assertEquals($expected, $result);
  752. }
  753. /**
  754. * Test the dropConstraintSql method.
  755. *
  756. * @return void
  757. */
  758. public function testDropConstraintSql()
  759. {
  760. $driver = $this->_getMockedDriver();
  761. $connection = $this->getMockBuilder('Cake\Database\Connection')
  762. ->disableOriginalConstructor()
  763. ->getMock();
  764. $connection->expects($this->any())->method('driver')
  765. ->will($this->returnValue($driver));
  766. $table = (new Table('posts'))
  767. ->addColumn('author_id', [
  768. 'type' => 'integer',
  769. 'null' => false
  770. ])
  771. ->addColumn('category_id', [
  772. 'type' => 'integer',
  773. 'null' => false
  774. ])
  775. ->addColumn('category_name', [
  776. 'type' => 'integer',
  777. 'null' => false
  778. ])
  779. ->addConstraint('author_fk', [
  780. 'type' => 'foreign',
  781. 'columns' => ['author_id'],
  782. 'references' => ['authors', 'id'],
  783. 'update' => 'cascade',
  784. 'delete' => 'cascade'
  785. ])
  786. ->addConstraint('category_fk', [
  787. 'type' => 'foreign',
  788. 'columns' => ['category_id', 'category_name'],
  789. 'references' => ['categories', ['id', 'name']],
  790. 'update' => 'cascade',
  791. 'delete' => 'cascade'
  792. ]);
  793. $expected = [
  794. 'ALTER TABLE [posts] DROP CONSTRAINT [author_fk];',
  795. 'ALTER TABLE [posts] DROP CONSTRAINT [category_fk];'
  796. ];
  797. $result = $table->dropConstraintSql($connection);
  798. $this->assertCount(2, $result);
  799. $this->assertEquals($expected, $result);
  800. }
  801. /**
  802. * Integration test for converting a Schema\Table into MySQL table creates.
  803. *
  804. * @return void
  805. */
  806. public function testCreateSql()
  807. {
  808. $driver = $this->_getMockedDriver();
  809. $connection = $this->getMockBuilder('Cake\Database\Connection')
  810. ->disableOriginalConstructor()
  811. ->getMock();
  812. $connection->expects($this->any())->method('driver')
  813. ->will($this->returnValue($driver));
  814. $table = (new Table('schema_articles'))->addColumn('id', [
  815. 'type' => 'integer',
  816. 'null' => false
  817. ])
  818. ->addColumn('title', [
  819. 'type' => 'string',
  820. 'null' => false,
  821. ])
  822. ->addColumn('body', ['type' => 'text'])
  823. ->addColumn('hash', [
  824. 'type' => 'string',
  825. 'fixed' => true,
  826. 'length' => 40,
  827. 'collate' => 'Latin1_General_BIN',
  828. 'null' => false,
  829. ])
  830. ->addColumn('created', 'datetime')
  831. ->addConstraint('primary', [
  832. 'type' => 'primary',
  833. 'columns' => ['id'],
  834. ])
  835. ->addIndex('title_idx', [
  836. 'type' => 'index',
  837. 'columns' => ['title'],
  838. ]);
  839. $expected = <<<SQL
  840. CREATE TABLE [schema_articles] (
  841. [id] INTEGER IDENTITY(1, 1),
  842. [title] NVARCHAR(255) NOT NULL,
  843. [body] NVARCHAR(MAX),
  844. [hash] NCHAR(40) COLLATE Latin1_General_BIN NOT NULL,
  845. [created] DATETIME,
  846. PRIMARY KEY ([id])
  847. )
  848. SQL;
  849. $result = $table->createSql($connection);
  850. $this->assertCount(2, $result);
  851. $this->assertEquals(str_replace("\r\n", "\n", $expected), str_replace("\r\n", "\n", $result[0]));
  852. $this->assertEquals(
  853. 'CREATE INDEX [title_idx] ON [schema_articles] ([title])',
  854. $result[1]
  855. );
  856. }
  857. /**
  858. * test dropSql
  859. *
  860. * @return void
  861. */
  862. public function testDropSql()
  863. {
  864. $driver = $this->_getMockedDriver();
  865. $connection = $this->getMockBuilder('Cake\Database\Connection')
  866. ->disableOriginalConstructor()
  867. ->getMock();
  868. $connection->expects($this->any())->method('driver')
  869. ->will($this->returnValue($driver));
  870. $table = new Table('schema_articles');
  871. $result = $table->dropSql($connection);
  872. $this->assertCount(1, $result);
  873. $this->assertEquals('DROP TABLE [schema_articles]', $result[0]);
  874. }
  875. /**
  876. * Test truncateSql()
  877. *
  878. * @return void
  879. */
  880. public function testTruncateSql()
  881. {
  882. $driver = $this->_getMockedDriver();
  883. $connection = $this->getMockBuilder('Cake\Database\Connection')
  884. ->disableOriginalConstructor()
  885. ->getMock();
  886. $connection->expects($this->any())->method('driver')
  887. ->will($this->returnValue($driver));
  888. $table = new Table('schema_articles');
  889. $table->addColumn('id', 'integer')
  890. ->addConstraint('primary', [
  891. 'type' => 'primary',
  892. 'columns' => ['id']
  893. ]);
  894. $result = $table->truncateSql($connection);
  895. $this->assertCount(2, $result);
  896. $this->assertEquals('DELETE FROM [schema_articles]', $result[0]);
  897. $this->assertEquals("DBCC CHECKIDENT('schema_articles', RESEED, 0)", $result[1]);
  898. }
  899. /**
  900. * Get a schema instance with a mocked driver/pdo instances
  901. *
  902. * @return \Cake\Database\Driver
  903. */
  904. protected function _getMockedDriver()
  905. {
  906. $driver = new \Cake\Database\Driver\Sqlserver();
  907. $mock = $this->getMockBuilder('FakePdo')
  908. ->setMethods(['quote'])
  909. ->getMock();
  910. $mock->expects($this->any())
  911. ->method('quote')
  912. ->will($this->returnCallback(function ($value) {
  913. return "'$value'";
  914. }));
  915. $driver->connection($mock);
  916. return $driver;
  917. }
  918. }