Browse Source

Merge pull request #11782 from cakephp/3.next-query-in-not-in

Add whereIn() and whereNotIn() and allow to behave as intuitively expected
Mark Story 8 years ago
parent
commit
720d97fcb6
2 changed files with 168 additions and 0 deletions
  1. 58 0
      src/Database/Query.php
  2. 110 0
      tests/TestCase/Database/QueryTest.php

+ 58 - 0
src/Database/Query.php

@@ -882,6 +882,64 @@ class Query implements ExpressionInterface, IteratorAggregate
     }
 
     /**
+     * Adds an IN condition or set of conditions to be used in the WHERE clause for this
+     * query.
+     *
+     * This method does allow empty inputs in contrast to where() if you set
+     * 'allowEmpty' to true.
+     * Be careful about using it without proper sanity checks.
+     *
+     * Options:
+     * - `types` - Associative array of type names used to bind values to query
+     * - `allowEmpty` - Allow empty array.
+     *
+     * @param string $field Field
+     * @param array $values Array of values
+     * @param array $options Options
+     * @return $this
+     */
+    public function whereInList($field, array $values, array $options = [])
+    {
+        $options += [
+            'types' => [],
+            'allowEmpty' => false,
+        ];
+
+        if ($options['allowEmpty'] && !$values) {
+            return $this->where('1=0');
+        }
+
+        return $this->where([$field . ' IN' => $values], $options['types']);
+    }
+
+    /**
+     * Adds a NOT IN condition or set of conditions to be used in the WHERE clause for this
+     * query.
+     *
+     * This method does allow empty inputs in contrast to where() if you set
+     * 'allowEmpty' to true.
+     * Be careful about using it without proper sanity checks.
+     *
+     * @param string $field Field
+     * @param array $values Array of values
+     * @param array $options Options
+     * @return $this
+     */
+    public function whereNotInList($field, array $values, array $options = [])
+    {
+        $options += [
+            'types' => [],
+            'allowEmpty' => false,
+        ];
+
+        if ($options['allowEmpty'] && !$values) {
+            return $this->where([$field . ' IS NOT' => null]);
+        }
+
+        return $this->where([$field . ' NOT IN' => $values], $options['types']);
+    }
+
+    /**
      * Connects any previously defined set of conditions to the provided list
      * using the AND operator. This function accepts the conditions list in the same
      * format as the method `where` does, hence you can use arrays, expression objects

+ 110 - 0
tests/TestCase/Database/QueryTest.php

@@ -36,6 +36,16 @@ class QueryTest extends TestCase
     const AUTHOR_COUNT = 4;
     const COMMENT_COUNT = 6;
 
+    /**
+     * @var \Cake\Database\Connection
+     */
+    protected $connection;
+
+    /**
+     * @var bool
+     */
+    protected $autoQuote;
+
     public function setUp()
     {
         parent::setUp();
@@ -1704,6 +1714,106 @@ class QueryTest extends TestCase
     }
 
     /**
+     * Tests whereInArray() and its input types.
+     *
+     * @return void
+     */
+    public function testWhereInArray()
+    {
+        $this->loadFixtures('Articles');
+        $query = new Query($this->connection);
+        $query->select(['id'])
+            ->from('articles')
+            ->whereInList('id', [2, 3])
+            ->execute();
+        $sql = $query->sql();
+
+        $result = $query->execute();
+        $this->assertEquals(['id' => '2'], $result->fetch('assoc'));
+
+        $this->assertQuotedQuery(
+            'SELECT <id> FROM <articles> WHERE <id> in \\(:c0,:c1\\)',
+            $sql,
+            !$this->autoQuote
+        );
+    }
+
+    /**
+     * Tests whereInArray() and empty array input.
+     *
+     * @return void
+     */
+    public function testWhereInArrayEmpty()
+    {
+        $this->loadFixtures('Articles');
+        $query = new Query($this->connection);
+        $query->select(['id'])
+            ->from('articles')
+            ->whereInList('id', [], ['allowEmpty' => true])
+            ->execute();
+        $sql = $query->sql();
+
+        $result = $query->execute();
+        $this->assertFalse($result->fetch('assoc'));
+
+        $this->assertQuotedQuery(
+            'SELECT <id> FROM <articles> WHERE 1=0',
+            $sql,
+            !$this->autoQuote
+        );
+    }
+
+    /**
+     * Tests whereNotInArray() and its input types.
+     *
+     * @return void
+     */
+    public function testWhereNotInArray()
+    {
+        $this->loadFixtures('Articles');
+        $query = new Query($this->connection);
+        $query->select(['id'])
+            ->from('articles')
+            ->whereNotInList('id', [1, 3])
+            ->execute();
+        $sql = $query->sql();
+
+        $result = $query->execute();
+        $this->assertEquals(['id' => '2'], $result->fetch('assoc'));
+
+        $this->assertQuotedQuery(
+            'SELECT <id> FROM <articles> WHERE <id> not in \\(:c0,:c1\\)',
+            $sql,
+            !$this->autoQuote
+        );
+    }
+
+    /**
+     * Tests whereNotInArray() and empty array input.
+     *
+     * @return void
+     */
+    public function testWhereNotInArrayEmpty()
+    {
+        $this->loadFixtures('Articles');
+        $query = new Query($this->connection);
+        $query->select(['id'])
+            ->from('articles')
+            ->whereNotInList('id', [], ['allowEmpty' => true])
+            ->execute();
+        $sql = $query->sql();
+
+        $result = $query->execute();
+        $this->assertEquals(['id' => '1'], $result->fetch('assoc'));
+
+        $this->assertQuotedQuery(
+            'SELECT <id> FROM <articles> WHERE \(<id>\) IS NOT NULL',
+            $sql,
+            !$this->autoQuote
+        );
+    }
+
+    /**
      * Tests order() method both with simple fields and expressions
      *
      * @return void