Skip to content

Commit

Permalink
Implementing support for subqueries in where clause
Browse files Browse the repository at this point in the history
  • Loading branch information
lorenzo committed Jan 16, 2013
1 parent 967aca3 commit 9470451
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 0 deletions.
58 changes: 58 additions & 0 deletions lib/Cake/Model/Datasource/Database/Expression/Comparisson.php
@@ -0,0 +1,58 @@
<?php

namespace Cake\Model\Datasource\Database\Expression;

class Comparisson extends QueryExpression {

This comment has been minimized.

Copy link
@markstory

markstory Jan 28, 2013

Member

Comparison only has one s.

This comment has been minimized.

Copy link
@lorenzo

lorenzo Jan 28, 2013

Author Member

Oh, that's embarrassing. Thanks! :P


protected $_field;

protected $_value;

protected $_type;

public function __construct(array $condition, $types = [], $conjuntion = '=') {
parent::__construct($condition, $types, $conjuntion);
}

public function field($field) {
$this->_field = $field;
}

public function value($value) {
$this->_value = $value;
}

public function getField() {
return $this->_field;
}

public function getValue() {
return $this->_value;
}

This comment has been minimized.

Copy link
@markstory

markstory Jan 28, 2013

Member

Would it be simpler if the getValue() and value() methods used a combined getter/setter?

This comment has been minimized.

Copy link
@dereuromark

dereuromark Jan 28, 2013

Member

Same might be true for field.

This comment has been minimized.

Copy link
@lorenzo

lorenzo Jan 28, 2013

Author Member

It is possible, my only concern is that I'm not 100% sure if I should allow null as value. In that case I might need another default to be checked so it returns and not stores.

This comment has been minimized.

Copy link
@markstory

markstory Jan 28, 2013

Member

Ok that's a good reason.


public function sql() {
$value = $this->_value;
$template = '%s %s (%s)';
if (is_scalar($this->_value)) {
$value = $this->_bindValue($this->_field,$value, $this->_type);
$template = '%s %s %s';
}

return sprintf($template, $this->_field, $this->_conjunction, $value);
}

public function count() {
return 1;
}

protected function _addConditions(array $condition, array $types) {
$this->_conditions[] = current($condition);
$this->_field = key($condition);
$this->_value = current($condition);

if (isset($types[$this->_field])) {
$this->_type = current($types);
}
}

}
@@ -1,6 +1,8 @@
<?php

namespace Cake\Model\Datasource\Database\Expression;

use Cake\Model\Datasource\Database\Query;
use \Countable;

class QueryExpression implements Countable {
Expand Down Expand Up @@ -240,6 +242,10 @@ protected function _parseCondition($field, $value, $types) {
$template = '%s %s (%s)';
}

if ($value instanceof Query) {
return new Comparisson([$expression => $value], [$field => $type], $operator);
}

return sprintf($template, $expression, $operator, $this->_bindValue($field, $value, $type));
}

Expand Down
9 changes: 9 additions & 0 deletions lib/Cake/Model/Datasource/Database/Query.php
Expand Up @@ -4,6 +4,7 @@
use Iterator;
use IteratorAggregate;
use Cake\Model\Datasource\Database\Expression\QueryExpression;
use Cake\Model\Datasource\Database\Expression\Comparisson;

class Query implements IteratorAggregate {

Expand Down Expand Up @@ -399,12 +400,20 @@ protected function _conjugate($part, $append, $conjunction, $types) {
protected function _bindParams($statement) {
$visitor = function($expression) use($statement) {
$params = $types = [];

if ($expression instanceof Comparisson) {
if ($expression->getValue() instanceof self) {
$expression->getValue()->_bindParams($statement);
}
}

foreach ($expression->bindings() as $b) {
$params[$b['placeholder']] = $b['value'];
$types[$b['placeholder']] = $b['type'];
}
$statement->bind($params, $types);
};

$binder = function($expression, $name) use($statement, $visitor, &$binder) {
if (is_array($expression)) {
foreach ($expression as $e) {
Expand Down
28 changes: 28 additions & 0 deletions lib/Cake/Test/TestCase/Model/Datasource/Database/QueryTest.php
Expand Up @@ -1328,4 +1328,32 @@ public function testSuqueryInFrom() {
$this->assertEquals($expected, $result->fetchAll('assoc'));
}

/**
* Tests that Query objects can be included inside the where clause
* and be used as a normal condition, including binding any passed parameter
*
* @return void
**/
public function testSuqueryInWhere() {

This comment has been minimized.

Copy link
@ceeram

ceeram Jan 29, 2013

Contributor

typo here, missing b

$this->_insertDateRecords();
$this->_insertTwoRecords();

$query = new Query($this->connection);
$subquery = (new Query($this->connection))
->select(['id'])
->from('authors')
->where(['id >' => 1]);
$result = $query
->select(['name'])
->from(['dates'])
->where(['id !=' => $subquery])
->execute();

$expected = [
['name' => 'Chuck Norris'],
['name' => 'Jet Li']
];
$this->assertEquals($expected, $result->fetchAll('assoc'));
}

}

0 comments on commit 9470451

Please sign in to comment.