TableTest.php 15 KB

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