DeleteQueryTest.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 5.0.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Database\Query;
  17. use Cake\Database\Exception\DatabaseException;
  18. use Cake\Database\Expression\IdentifierExpression;
  19. use Cake\Database\Query\DeleteQuery;
  20. use Cake\Database\Query\SelectQuery;
  21. use Cake\Database\StatementInterface;
  22. use Cake\Datasource\ConnectionManager;
  23. use Cake\Test\TestCase\Database\QueryAssertsTrait;
  24. use Cake\TestSuite\TestCase;
  25. /**
  26. * Tests DeleteQuery class
  27. */
  28. class DeleteQueryTest extends TestCase
  29. {
  30. use QueryAssertsTrait;
  31. /**
  32. * @var int
  33. */
  34. public const AUTHOR_COUNT = 4;
  35. protected array $fixtures = [
  36. 'core.Articles',
  37. 'core.Authors',
  38. ];
  39. /**
  40. * @var \Cake\Database\Connection
  41. */
  42. protected $connection;
  43. /**
  44. * @var bool
  45. */
  46. protected $autoQuote;
  47. public function setUp(): void
  48. {
  49. parent::setUp();
  50. $this->connection = ConnectionManager::get('test');
  51. $this->autoQuote = $this->connection->getDriver()->isAutoQuotingEnabled();
  52. }
  53. public function tearDown(): void
  54. {
  55. parent::tearDown();
  56. $this->connection->getDriver()->enableAutoQuoting($this->autoQuote);
  57. unset($this->connection);
  58. }
  59. /**
  60. * Test a basic delete using from()
  61. */
  62. public function testDeleteWithFrom(): void
  63. {
  64. $query = new DeleteQuery($this->connection);
  65. $query->delete()
  66. ->from('authors')
  67. ->where('1 = 1');
  68. $result = $query->sql();
  69. $this->assertQuotedQuery('DELETE FROM <authors>', $result, !$this->autoQuote);
  70. $result = $query->execute();
  71. $this->assertInstanceOf(StatementInterface::class, $result);
  72. $this->assertSame(self::AUTHOR_COUNT, $result->rowCount());
  73. $result->closeCursor();
  74. }
  75. /**
  76. * Test delete with from and alias.
  77. */
  78. public function testDeleteWithAliasedFrom(): void
  79. {
  80. $query = new DeleteQuery($this->connection);
  81. $query->delete()
  82. ->from(['a ' => 'authors'])
  83. ->where(['a.id !=' => 99]);
  84. $result = $query->sql();
  85. $this->assertQuotedQuery('DELETE FROM <authors> WHERE <id> != :c0', $result, !$this->autoQuote);
  86. $result = $query->execute();
  87. $this->assertInstanceOf(StatementInterface::class, $result);
  88. $this->assertSame(self::AUTHOR_COUNT, $result->rowCount());
  89. $result->closeCursor();
  90. }
  91. /**
  92. * Test a basic delete with no from() call.
  93. */
  94. public function testDeleteNoFrom(): void
  95. {
  96. $query = new DeleteQuery($this->connection);
  97. $query->delete('authors')
  98. ->where('1 = 1');
  99. $result = $query->sql();
  100. $this->assertQuotedQuery('DELETE FROM <authors>', $result, !$this->autoQuote);
  101. $result = $query->execute();
  102. $this->assertInstanceOf(StatementInterface::class, $result);
  103. $this->assertSame(self::AUTHOR_COUNT, $result->rowCount());
  104. $result->closeCursor();
  105. }
  106. /**
  107. * Tests that delete queries that contain joins do trigger a notice,
  108. * warning about possible incompatibilities with aliases being removed
  109. * from the conditions.
  110. */
  111. public function testDeleteRemovingAliasesCanBreakJoins(): void
  112. {
  113. $this->expectException(DatabaseException::class);
  114. $this->expectExceptionMessage('Aliases are being removed from conditions for UPDATE/DELETE queries, this can break references to joined tables.');
  115. $query = new DeleteQuery($this->connection);
  116. $query
  117. ->delete('authors')
  118. ->from(['a ' => 'authors'])
  119. ->innerJoin('articles')
  120. ->where(['a.id' => 1]);
  121. $query->sql();
  122. }
  123. /**
  124. * Tests that aliases are stripped from delete query conditions
  125. * where possible.
  126. */
  127. public function testDeleteStripAliasesFromConditions(): void
  128. {
  129. $query = new DeleteQuery($this->connection);
  130. $query
  131. ->delete()
  132. ->from(['a' => 'authors'])
  133. ->where([
  134. 'OR' => [
  135. 'a.id' => 1,
  136. 'a.name IS' => null,
  137. 'a.email IS NOT' => null,
  138. 'AND' => [
  139. 'b.name NOT IN' => ['foo', 'bar'],
  140. 'OR' => [
  141. $query->newExpr()->eq(new IdentifierExpression('c.name'), 'zap'),
  142. 'd.name' => 'baz',
  143. (new SelectQuery($this->connection))->select(['e.name'])->where(['e.name' => 'oof']),
  144. ],
  145. ],
  146. ],
  147. ]);
  148. $this->assertQuotedQuery(
  149. 'DELETE FROM <authors> WHERE \(' .
  150. '<id> = :c0 OR \(<name>\) IS NULL OR \(<email>\) IS NOT NULL OR \(' .
  151. '<name> NOT IN \(:c1,:c2\) AND \(' .
  152. '<name> = :c3 OR <name> = :c4 OR \(SELECT <e>\.<name> WHERE <e>\.<name> = :c5\)' .
  153. '\)' .
  154. '\)' .
  155. '\)',
  156. $query->sql(),
  157. !$this->autoQuote
  158. );
  159. }
  160. /**
  161. * Test that epilog() will actually append a string to a delete query
  162. */
  163. public function testAppendDelete(): void
  164. {
  165. $query = new DeleteQuery($this->connection);
  166. $sql = $query
  167. ->delete('articles')
  168. ->where(['id' => 1])
  169. ->epilog('RETURNING id')
  170. ->sql();
  171. $this->assertStringContainsString('DELETE FROM', $sql);
  172. $this->assertStringContainsString('WHERE', $sql);
  173. $this->assertSame(' RETURNING id', substr($sql, -13));
  174. }
  175. /**
  176. * Test use of modifiers in a DELETE query
  177. *
  178. * Testing the generated SQL since the modifiers are usually different per driver
  179. */
  180. public function testDeleteModifiers(): void
  181. {
  182. $query = new DeleteQuery($this->connection);
  183. $result = $query->delete()
  184. ->from('authors')
  185. ->where('1 = 1')
  186. ->modifier('IGNORE');
  187. $this->assertQuotedQuery(
  188. 'DELETE IGNORE FROM <authors> WHERE 1 = 1',
  189. $result->sql(),
  190. !$this->autoQuote
  191. );
  192. $query = new DeleteQuery($this->connection);
  193. $result = $query->delete()
  194. ->from('authors')
  195. ->where('1 = 1')
  196. ->modifier(['IGNORE', 'QUICK']);
  197. $this->assertQuotedQuery(
  198. 'DELETE IGNORE QUICK FROM <authors> WHERE 1 = 1',
  199. $result->sql(),
  200. !$this->autoQuote
  201. );
  202. }
  203. }