TableTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  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\Table;
  17. use Cake\Datasource\ConnectionManager;
  18. use Cake\ORM\TableRegistry;
  19. use Cake\TestSuite\TestCase;
  20. use Symfony\Component\Yaml\Exception\RuntimeException;
  21. /**
  22. * Test case for Table
  23. */
  24. class TableTest extends TestCase
  25. {
  26. public $fixtures = ['core.customers', 'core.products', 'core.orders'];
  27. /**
  28. * Test construction with columns
  29. *
  30. * @return void
  31. */
  32. public function testConstructWithColumns()
  33. {
  34. $columns = [
  35. 'id' => [
  36. 'type' => 'integer',
  37. 'length' => 11,
  38. ],
  39. 'title' => [
  40. 'type' => 'string',
  41. 'length' => 255
  42. ]
  43. ];
  44. $table = new Table('articles', $columns);
  45. $this->assertEquals(['id', 'title'], $table->columns());
  46. }
  47. /**
  48. * Test adding columns.
  49. *
  50. * @return void
  51. */
  52. public function testAddColumn()
  53. {
  54. $table = new Table('articles');
  55. $result = $table->addColumn('title', [
  56. 'type' => 'string',
  57. 'length' => 25,
  58. 'null' => false
  59. ]);
  60. $this->assertSame($table, $result);
  61. $this->assertEquals(['title'], $table->columns());
  62. $result = $table->addColumn('body', 'text');
  63. $this->assertSame($table, $result);
  64. $this->assertEquals(['title', 'body'], $table->columns());
  65. }
  66. /**
  67. * Test isNullable method
  68. *
  69. * @return void
  70. */
  71. public function testIsNullable()
  72. {
  73. $table = new Table('articles');
  74. $table->addColumn('title', [
  75. 'type' => 'string',
  76. 'length' => 25,
  77. 'null' => false
  78. ])->addColumn('tagline', [
  79. 'type' => 'string',
  80. 'length' => 25,
  81. 'null' => true
  82. ]);
  83. $this->assertFalse($table->isNullable('title'));
  84. $this->assertTrue($table->isNullable('tagline'));
  85. $this->assertTrue($table->isNullable('missing'));
  86. }
  87. /**
  88. * Test columnType method
  89. *
  90. * @return void
  91. */
  92. public function testColumnType()
  93. {
  94. $table = new Table('articles');
  95. $table->addColumn('title', [
  96. 'type' => 'string',
  97. 'length' => 25,
  98. 'null' => false
  99. ]);
  100. $this->assertEquals('string', $table->columnType('title'));
  101. $this->assertNull($table->columnType('not there'));
  102. }
  103. /**
  104. * Test columnType setter method
  105. *
  106. * @return void
  107. */
  108. public function testColumnTypeSet()
  109. {
  110. $table = new Table('articles');
  111. $table->addColumn('title', [
  112. 'type' => 'string',
  113. 'length' => 25,
  114. 'null' => false
  115. ]);
  116. $this->assertEquals('string', $table->columnType('title'));
  117. $table->columnType('title', 'json');
  118. $this->assertEquals('json', $table->columnType('title'));
  119. }
  120. /**
  121. * Attribute keys should be filtered and have defaults set.
  122. *
  123. * @return void
  124. */
  125. public function testAddColumnFiltersAttributes()
  126. {
  127. $table = new Table('articles');
  128. $table->addColumn('title', [
  129. 'type' => 'string'
  130. ]);
  131. $result = $table->column('title');
  132. $expected = [
  133. 'type' => 'string',
  134. 'length' => null,
  135. 'precision' => null,
  136. 'default' => null,
  137. 'null' => null,
  138. 'fixed' => null,
  139. 'comment' => null,
  140. ];
  141. $this->assertEquals($expected, $result);
  142. $table->addColumn('author_id', [
  143. 'type' => 'integer'
  144. ]);
  145. $result = $table->column('author_id');
  146. $expected = [
  147. 'type' => 'integer',
  148. 'length' => null,
  149. 'precision' => null,
  150. 'default' => null,
  151. 'null' => null,
  152. 'unsigned' => null,
  153. 'comment' => null,
  154. 'autoIncrement' => null,
  155. ];
  156. $this->assertEquals($expected, $result);
  157. $table->addColumn('amount', [
  158. 'type' => 'decimal'
  159. ]);
  160. $result = $table->column('amount');
  161. $expected = [
  162. 'type' => 'decimal',
  163. 'length' => null,
  164. 'precision' => null,
  165. 'default' => null,
  166. 'null' => null,
  167. 'unsigned' => null,
  168. 'comment' => null,
  169. ];
  170. $this->assertEquals($expected, $result);
  171. }
  172. /**
  173. * Test reading default values.
  174. *
  175. * @return void
  176. */
  177. public function testDefaultValues()
  178. {
  179. $table = new Table('articles');
  180. $table->addColumn('id', [
  181. 'type' => 'integer',
  182. 'default' => 0
  183. ])->addColumn('title', [
  184. 'type' => 'string',
  185. 'default' => 'A title'
  186. ])->addColumn('name', [
  187. 'type' => 'string',
  188. 'null' => false,
  189. 'default' => null,
  190. ])->addColumn('body', [
  191. 'type' => 'text',
  192. 'null' => true,
  193. 'default' => null,
  194. ]);
  195. $result = $table->defaultValues();
  196. $expected = [
  197. 'id' => 0,
  198. 'title' => 'A title',
  199. 'body' => null
  200. ];
  201. $this->assertEquals($expected, $result);
  202. }
  203. /**
  204. * Test adding an constraint.
  205. * >
  206. * @return void
  207. */
  208. public function testAddConstraint()
  209. {
  210. $table = new Table('articles');
  211. $table->addColumn('id', [
  212. 'type' => 'integer'
  213. ]);
  214. $result = $table->addConstraint('primary', [
  215. 'type' => 'primary',
  216. 'columns' => ['id']
  217. ]);
  218. $this->assertSame($result, $table);
  219. $this->assertEquals(['primary'], $table->constraints());
  220. }
  221. /**
  222. * Dataprovider for invalid addConstraint calls.
  223. *
  224. * @return array
  225. */
  226. public static function addConstaintErrorProvider()
  227. {
  228. return [
  229. // No properties
  230. [[]],
  231. // Empty columns
  232. [['columns' => '', 'type' => Table::CONSTRAINT_UNIQUE]],
  233. [['columns' => [], 'type' => Table::CONSTRAINT_UNIQUE]],
  234. // Missing column
  235. [['columns' => ['derp'], 'type' => Table::CONSTRAINT_UNIQUE]],
  236. // Invalid type
  237. [['columns' => 'author_id', 'type' => 'derp']],
  238. ];
  239. }
  240. /**
  241. * Test that an exception is raised when constraints
  242. * are added for fields that do not exist.
  243. *
  244. * @dataProvider addConstaintErrorProvider
  245. * @expectedException \Cake\Database\Exception
  246. * @return void
  247. */
  248. public function testAddConstraintError($props)
  249. {
  250. $table = new Table('articles');
  251. $table->addColumn('author_id', 'integer');
  252. $table->addConstraint('author_idx', $props);
  253. }
  254. /**
  255. * Test adding an index.
  256. *
  257. * @return void
  258. */
  259. public function testAddIndex()
  260. {
  261. $table = new Table('articles');
  262. $table->addColumn('title', [
  263. 'type' => 'string'
  264. ]);
  265. $result = $table->addIndex('faster', [
  266. 'type' => 'index',
  267. 'columns' => ['title']
  268. ]);
  269. $this->assertSame($result, $table);
  270. $this->assertEquals(['faster'], $table->indexes());
  271. }
  272. /**
  273. * Dataprovider for invalid addIndex calls
  274. *
  275. * @return array
  276. */
  277. public static function addIndexErrorProvider()
  278. {
  279. return [
  280. // Empty
  281. [[]],
  282. // Invalid type
  283. [['columns' => 'author_id', 'type' => 'derp']],
  284. // No columns
  285. [['columns' => ''], 'type' => Table::INDEX_INDEX],
  286. [['columns' => [], 'type' => Table::INDEX_INDEX]],
  287. // Missing column
  288. [['columns' => ['not_there'], 'type' => Table::INDEX_INDEX]],
  289. ];
  290. }
  291. /**
  292. * Test that an exception is raised when indexes
  293. * are added for fields that do not exist.
  294. *
  295. * @dataProvider addIndexErrorProvider
  296. * @expectedException \Cake\Database\Exception
  297. * @return void
  298. */
  299. public function testAddIndexError($props)
  300. {
  301. $table = new Table('articles');
  302. $table->addColumn('author_id', 'integer');
  303. $table->addIndex('author_idx', $props);
  304. }
  305. /**
  306. * Test adding different kinds of indexes.
  307. *
  308. * @return void
  309. */
  310. public function testAddIndexTypes()
  311. {
  312. $table = new Table('articles');
  313. $table->addColumn('id', 'integer')
  314. ->addColumn('title', 'string')
  315. ->addColumn('author_id', 'integer');
  316. $table->addIndex('author_idx', [
  317. 'columns' => ['author_id'],
  318. 'type' => 'index'
  319. ])->addIndex('texty', [
  320. 'type' => 'fulltext',
  321. 'columns' => ['title']
  322. ]);
  323. $this->assertEquals(
  324. ['author_idx', 'texty'],
  325. $table->indexes()
  326. );
  327. }
  328. /**
  329. * Test getting the primary key.
  330. *
  331. * @return void
  332. */
  333. public function testPrimaryKey()
  334. {
  335. $table = new Table('articles');
  336. $table->addColumn('id', 'integer')
  337. ->addColumn('title', 'string')
  338. ->addColumn('author_id', 'integer')
  339. ->addConstraint('author_idx', [
  340. 'columns' => ['author_id'],
  341. 'type' => 'unique'
  342. ])->addConstraint('primary', [
  343. 'type' => 'primary',
  344. 'columns' => ['id']
  345. ]);
  346. $this->assertEquals(['id'], $table->primaryKey());
  347. $table = new Table('articles');
  348. $table->addColumn('id', 'integer')
  349. ->addColumn('title', 'string')
  350. ->addColumn('author_id', 'integer');
  351. $this->assertEquals([], $table->primaryKey());
  352. }
  353. /**
  354. * Test the options method.
  355. *
  356. * @return void
  357. */
  358. public function testOptions()
  359. {
  360. $table = new Table('articles');
  361. $options = [
  362. 'engine' => 'InnoDB'
  363. ];
  364. $return = $table->options($options);
  365. $this->assertInstanceOf('Cake\Database\Schema\Table', $return);
  366. $this->assertEquals($options, $table->options());
  367. }
  368. /**
  369. * Add a basic foreign key constraint.
  370. *
  371. * @return void
  372. */
  373. public function testAddConstraintForeignKey()
  374. {
  375. $table = new Table('articles');
  376. $table->addColumn('author_id', 'integer')
  377. ->addConstraint('author_id_idx', [
  378. 'type' => Table::CONSTRAINT_FOREIGN,
  379. 'columns' => ['author_id'],
  380. 'references' => ['authors', 'id'],
  381. 'update' => 'cascade',
  382. 'delete' => 'cascade',
  383. ]);
  384. $this->assertEquals(['author_id_idx'], $table->constraints());
  385. }
  386. /**
  387. * Test composite foreign keys support
  388. *
  389. * @return void
  390. */
  391. public function testAddConstraintForeignKeyTwoColumns()
  392. {
  393. $table = TableRegistry::get('Orders');
  394. $compositeConstraint = $table->schema()->constraint('product_id_fk');
  395. $expected = [
  396. 'type' => 'foreign',
  397. 'columns' => [
  398. 'product_id',
  399. 'product_category'
  400. ],
  401. 'references' => [
  402. 'products',
  403. ['id', 'category']
  404. ],
  405. 'update' => 'cascade',
  406. 'delete' => 'cascade',
  407. 'length' => []
  408. ];
  409. $this->assertEquals($expected, $compositeConstraint);
  410. $expectedSubstring = '#CONSTRAINT <product_id_fk> FOREIGN KEY \(<product_id>, <product_category>\)' .
  411. ' REFERENCES <products> \(<id>, <category>\)#';
  412. $expectedSubstring = str_replace(['<', '>'], ['[`"\[]', '[`"\]]'], $expectedSubstring);
  413. $this->assertRegExp($expectedSubstring, $table->schema()->createSql(ConnectionManager::get('default'))[0]);
  414. }
  415. /**
  416. * Provider for exceptionally bad foreign key data.
  417. *
  418. * @return array
  419. */
  420. public static function badForeignKeyProvider()
  421. {
  422. return [
  423. 'references is bad' => [[
  424. 'type' => Table::CONSTRAINT_FOREIGN,
  425. 'columns' => ['author_id'],
  426. 'references' => ['authors'],
  427. 'delete' => 'derp',
  428. ]],
  429. 'bad update value' => [[
  430. 'type' => Table::CONSTRAINT_FOREIGN,
  431. 'columns' => ['author_id'],
  432. 'references' => ['authors', 'id'],
  433. 'update' => 'derp',
  434. ]],
  435. 'bad delete value' => [[
  436. 'type' => Table::CONSTRAINT_FOREIGN,
  437. 'columns' => ['author_id'],
  438. 'references' => ['authors', 'id'],
  439. 'delete' => 'derp',
  440. ]],
  441. ];
  442. }
  443. /**
  444. * Add a foreign key constraint with bad data
  445. *
  446. * @dataProvider badForeignKeyProvider
  447. * @expectedException \Cake\Database\Exception
  448. * @return void
  449. */
  450. public function testAddConstraintForeignKeyBadData($data)
  451. {
  452. $table = new Table('articles');
  453. $table->addColumn('author_id', 'integer')
  454. ->addConstraint('author_id_idx', $data);
  455. }
  456. /**
  457. * Tests the temporary() method
  458. *
  459. * @return void
  460. */
  461. public function testTemporary()
  462. {
  463. $table = new Table('articles');
  464. $this->assertFalse($table->temporary());
  465. $this->assertSame($table, $table->temporary(true));
  466. $this->assertTrue($table->temporary());
  467. $table->temporary(false);
  468. $this->assertFalse($table->temporary());
  469. }
  470. /**
  471. * Assertion for comparing a regex pattern against a query having its identifiers
  472. * quoted. It accepts queries quoted with the characters `<` and `>`. If the third
  473. * parameter is set to true, it will alter the pattern to both accept quoted and
  474. * unquoted queries
  475. *
  476. * @param string $pattern
  477. * @param string $query the result to compare against
  478. * @param bool $optional
  479. * @return void
  480. */
  481. public function assertQuotedQuery($pattern, $query, $optional = false)
  482. {
  483. if ($optional) {
  484. $optional = '?';
  485. }
  486. $pattern = str_replace('<', '[`"\[]' . $optional, $pattern);
  487. $pattern = str_replace('>', '[`"\]]' . $optional, $pattern);
  488. $this->assertRegExp('#' . $pattern . '#', $query);
  489. }
  490. }