Skip to content

Commit

Permalink
Add case expression class for simple SQL case statements
Browse files Browse the repository at this point in the history
  • Loading branch information
Walther Lalk committed Aug 11, 2014
1 parent ad2567f commit 369d15d
Show file tree
Hide file tree
Showing 3 changed files with 260 additions and 0 deletions.
160 changes: 160 additions & 0 deletions src/Database/Expression/CaseExpression.php
@@ -0,0 +1,160 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Expression;

use Cake\Database\ExpressionInterface;
use Cake\Database\Expression\QueryExpression;
use Cake\Database\ValueBinder;

/**
* This class represents a SQL Case statement
*
* @internal
*/
class CaseExpression implements ExpressionInterface {

protected $_expression;

protected $_isTrue;

protected $_isFalse;

/**
* Constructs the case expression
*
* @param QueryExpression $expression The expression to test
* @param mixed $isTrue Value if the expression is true
* @param mixed $isFalse Value if the expression is false
*/
public function __construct(QueryExpression $expression, $isTrue = 1, $isFalse = 0) {
$this->_expression = $expression;
$this->_isTrue = $this->_getValue($isTrue);
$this->_isFalse = $this->_getValue($isFalse);
}

/**
* Gets/sets the isTrue part
*
* @param mixed $value Value to set
*
* @return array|mixed
*/
public function isTrue($value = null) {
return $this->_part('isTrue', $value);
}

/**
* Gets/sets the isFalse part
*
* @param mixed $value Value to set
*
* @return array|mixed
* @codeCoverageIgnore
*/
public function isFalse($value = null) {
return $this->_part('isFalse', $value);
}

/**
* Gets/sets the passed part
*
* @param string $part The part to get or set
* @param mixed $value Value to set
*
* @return array|mixed
*/
protected function _part($part, $value) {
if ($value !== null) {
$this->{'_' . $part} = $this->_getValue($value);
}

return $this->{'_' . $part};
}

/**
* Parses the value into a understandable format
*
* @param mixed $value The value to parse
*
* @return array|mixed
*/
protected function _getValue($value) {
if (is_string($value)) {
$value = [
'value' => $value,
'type' => null
];
} elseif (is_array($value) && !isset($value['value'])) {
$value = array_keys($value);
$value = end($value);
}
return $value;
}

/**
* Compiles the true or false part into sql
*
* @param mixed $part The part to compile
* @param ValueBinder $generator Sql generator
*
* @return string
*/
protected function _compile($part, ValueBinder $generator) {
$part = $this->{'_' . $part};
if ($part instanceof ExpressionInterface) {
$part = $part->sql($generator);
} elseif (is_array($part)) {
$placeholder = $generator->placeholder('param');
$generator->bind($placeholder, $part['value'], $part['type']);
$part = $placeholder;
}

return $part;
}

/**
* Converts the Node into a SQL string fragment.
*
* @param \Cake\Database\ValueBinder $generator Placeholder generator object
*
* @return string
*/
public function sql(ValueBinder $generator) {
$parts = [];
$parts[] = 'CASE WHEN';
$parts[] = $this->_expression->sql($generator);
$parts[] = 'THEN';
$parts[] = $this->_compile('isTrue', $generator);
$parts[] = 'ELSE';
$parts[] = $this->_compile('isFalse', $generator);
$parts[] = 'END';

return implode(' ', $parts);
}

/**
* {@inheritDoc}
*
*/
public function traverse(callable $visitor) {
foreach (['_expression', '_isTrue', '_isFalse'] as $c) {
if ($this->{$c} instanceof ExpressionInterface) {
$visitor($this->{$c});
$this->{$c}->traverse($visitor);
}
}
}

}
4 changes: 4 additions & 0 deletions src/Database/Expression/QueryExpression.php
Expand Up @@ -266,6 +266,10 @@ public function in($field, $values, $type = null) {
return $this->add(new Comparison($field, $values, $type, 'IN'));
}

public function addCase(QueryExpression $expression, $isTrue = 1, $isFalse = 0) {
return $this->add(new CaseExpression($expression, $isTrue, $isFalse));
}

/**
* Adds a new condition to the expression object in the form
* "field NOT IN (value1, value2)".
Expand Down
96 changes: 96 additions & 0 deletions tests/TestCase/Database/Expression/CaseExpressionTest.php
@@ -0,0 +1,96 @@
<?php
/**
* Project: hesa-mbit.
* User: walther
* Date: 2014/08/08
* Time: 9:37 AM
*/

namespace Cake\Test\TestCase\Database\Expression;

use Cake\Database\Expression\QueryExpression;
use Cake\Database\ValueBinder;
use Cake\TestSuite\TestCase;
use Cake\Database\Expression\CaseExpression;

/**
* Tests CaseExpression class
*/
class CaseExpressionTest extends TestCase {

/**
* Test that the sql output works correctly
*
* @return void
*/
public function testSqlOutput() {
$expr = new QueryExpression();
$expr->eq('test', 'true');
$caseExpression = new CaseExpression($expr);

$this->assertInstanceOf('Cake\Database\ExpressionInterface', $caseExpression);
$expected = 'CASE WHEN test = :c0 THEN 1 ELSE 0 END';
$this->assertSame($expected, $caseExpression->sql(new ValueBinder()));
}

/**
* Test that we can pass in things as the isTrue/isFalse part
*
* @return void
*/
public function testSetTrue() {
$expr = new QueryExpression();
$expr->eq('test', 'true');
$caseExpression = new CaseExpression($expr);
$expr2 = new QueryExpression();

$caseExpression->isTrue($expr2);
$this->assertSame($expr2, $caseExpression->isTrue());

$caseExpression->isTrue('test_string');
$this->assertSame(['value' => 'test_string', 'type' => null], $caseExpression->isTrue());

$caseExpression->isTrue(['test_string' => 'literal']);
$this->assertSame('test_string', $caseExpression->isTrue());
}

/**
* Test that things are compiled correctly
*
* @return void
*/
public function testSqlCompiler() {
$expr = new QueryExpression();
$expr->eq('test', 'true');
$caseExpression = new CaseExpression($expr);
$expr2 = new QueryExpression();
$expr2->eq('test', 'false');

$caseExpression->isTrue($expr2);
$this->assertSame('CASE WHEN test = :c0 THEN test = :c1 ELSE 0 END', $caseExpression->sql(new ValueBinder()));

$caseExpression->isTrue('test_string');
$this->assertSame('CASE WHEN test = :c0 THEN :c1 ELSE 0 END', $caseExpression->sql(new ValueBinder()));

$caseExpression->isTrue(['test_string' => 'literal']);
$this->assertSame('CASE WHEN test = :c0 THEN test_string ELSE 0 END', $caseExpression->sql(new ValueBinder()));
}

/**
* Tests that the expression is correctly traversed
*
* @return void
*/
public function testTraverse() {
$count = 0;
$visitor = function() use (&$count) {
$count++;
};

$expr = new QueryExpression();
$expr->eq('test', 'true');
$caseExpression = new CaseExpression($expr);
$caseExpression->traverse($visitor);
$this->assertSame(2, $count);
}
}

0 comments on commit 369d15d

Please sign in to comment.