Skip to content

Commit 2b293a1

Browse files
committed
Adding support for CASE.. WHEN.. THEN... constructs
1 parent f00cd75 commit 2b293a1

File tree

6 files changed

+257
-17
lines changed

6 files changed

+257
-17
lines changed

src/SQLParser/Node/CaseOperation.php

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
namespace SQLParser\Node;
4+
5+
use Mouf\Utils\Common\ConditionInterface\ConditionTrait;
6+
use Doctrine\DBAL\Connection;
7+
use Mouf\MoufManager;
8+
use Mouf\MoufInstanceDescriptor;
9+
use SQLParser\Node\Traverser\NodeTraverser;
10+
use SQLParser\Node\Traverser\VisitorInterface;
11+
12+
/**
13+
* This class represents a CASE ... END statement.
14+
*
15+
* @author David Négrier <[email protected]>
16+
*/
17+
class CaseOperation implements NodeInterface
18+
{
19+
private $operation;
20+
21+
public function getOperation()
22+
{
23+
return $this->operation;
24+
}
25+
26+
/**
27+
* Sets the operation.
28+
*
29+
* @Important
30+
*
31+
* @param NodeInterface|NodeInterface[]|string $operation
32+
*/
33+
public function setOperation($operation)
34+
{
35+
$this->operation = $operation;
36+
}
37+
38+
/**
39+
* Returns a Mouf instance descriptor describing this object.
40+
*
41+
* @param MoufManager $moufManager
42+
*
43+
* @return MoufInstanceDescriptor
44+
*/
45+
public function toInstanceDescriptor(MoufManager $moufManager)
46+
{
47+
$instanceDescriptor = $moufManager->createInstance(get_called_class());
48+
$instanceDescriptor->getProperty('operation')->setValue(NodeFactory::nodeToInstanceDescriptor($this->operation, $moufManager));
49+
50+
return $instanceDescriptor;
51+
}
52+
53+
/**
54+
* Renders the object as a SQL string.
55+
*
56+
* @param Connection $dbConnection
57+
* @param array $parameters
58+
* @param number $indent
59+
* @param int $conditionsMode
60+
*
61+
* @return string
62+
*/
63+
public function toSql(array $parameters = array(), Connection $dbConnection = null, $indent = 0, $conditionsMode = self::CONDITION_APPLY)
64+
{
65+
66+
$sql = 'CASE '.NodeFactory::toSql($this->operation, $dbConnection, $parameters, ' ', false, $indent, $conditionsMode).' END';
67+
68+
return $sql;
69+
}
70+
71+
/**
72+
* Walks the tree of nodes, calling the visitor passed in parameter.
73+
*
74+
* @param VisitorInterface $visitor
75+
*/
76+
public function walk(VisitorInterface $visitor) {
77+
$node = $this;
78+
$result = $visitor->enterNode($node);
79+
if ($result instanceof NodeInterface) {
80+
$node = $result;
81+
}
82+
if ($result !== NodeTraverser::DONT_TRAVERSE_CHILDREN) {
83+
$result2 = $this->operation->walk($visitor);
84+
if ($result2 === NodeTraverser::REMOVE_NODE) {
85+
return NodeTraverser::REMOVE_NODE;
86+
} elseif ($result2 instanceof NodeInterface) {
87+
$this->operation = $result2;
88+
}
89+
}
90+
return $visitor->leaveNode($node);
91+
}
92+
}

src/SQLParser/Node/ElseOperation.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace SQLParser\Node;
4+
5+
/**
6+
* This class represents a ELSE (in a CASE WHEN ... THEN ... ELSE ... END construct)
7+
*
8+
* @author David Négrier <[email protected]>
9+
*/
10+
class ElseOperation extends AbstractTwoOperandsOperator
11+
{
12+
/**
13+
* Returns the symbol for this operator.
14+
*
15+
* @return string
16+
*/
17+
protected function getOperatorSymbol()
18+
{
19+
return 'ELSE';
20+
}
21+
}

src/SQLParser/Node/NodeFactory.php

Lines changed: 68 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -293,24 +293,42 @@ public static function toObject(array $desc)
293293

294294
return $expr;
295295
case ExpressionType::RESERVED:
296-
$res = new Reserved();
297-
$res->setBaseExpression($desc['base_expr']);
296+
if (in_array(strtoupper($desc['base_expr']), ['CASE', 'WHEN', 'THEN', 'ELSE', 'END'])) {
297+
$operator = new Operator();
298+
$operator->setValue($desc['base_expr']);
299+
// Debug:
300+
unset($desc['base_expr']);
301+
unset($desc['expr_type']);
302+
if (!empty($desc['sub_tree'])) {
303+
throw new \InvalidArgumentException('Unexpected operator with subtree: '.var_export($desc['sub_tree'], true));
304+
}
305+
unset($desc['sub_tree']);
306+
if (!empty($desc)) {
307+
throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
308+
}
298309

299-
if ($desc['expr_type'] == ExpressionType::BRACKET_EXPRESSION) {
300-
$res->setBrackets(true);
301-
}
310+
return $operator;
311+
} else {
302312

303-
// Debug:
304-
unset($desc['base_expr']);
305-
unset($desc['expr_type']);
306-
unset($desc['sub_tree']);
307-
unset($desc['alias']);
308-
unset($desc['direction']);
309-
if (!empty($desc)) {
310-
throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
311-
}
313+
$res = new Reserved();
314+
$res->setBaseExpression($desc['base_expr']);
312315

313-
return $res;
316+
if ($desc['expr_type'] == ExpressionType::BRACKET_EXPRESSION) {
317+
$res->setBrackets(true);
318+
}
319+
320+
// Debug:
321+
unset($desc['base_expr']);
322+
unset($desc['expr_type']);
323+
unset($desc['sub_tree']);
324+
unset($desc['alias']);
325+
unset($desc['direction']);
326+
if (!empty($desc)) {
327+
throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
328+
}
329+
330+
return $res;
331+
}
314332
case ExpressionType::USER_VARIABLE:
315333
case ExpressionType::SESSION_VARIABLE:
316334
case ExpressionType::GLOBAL_VARIABLE:
@@ -402,7 +420,10 @@ private static function buildFromSubtree($subTree)
402420
array('|'),
403421
array('=' /*(comparison)*/, '<=>', '>=', '>', '<=', '<', '<>', '!=', 'IS', 'LIKE', 'REGEXP', 'IN', 'IS NOT', 'NOT IN'),
404422
array('AND_FROM_BETWEEN'),
405-
array('BETWEEN', 'CASE', 'WHEN', 'THEN', 'ELSE'),
423+
array('THEN'),
424+
array('WHEN'),
425+
array('ELSE'),
426+
array('BETWEEN', 'CASE', 'END'),
406427
array('NOT'),
407428
array('&&', 'AND'),
408429
array('XOR'),
@@ -441,6 +462,9 @@ private static function buildFromSubtree($subTree)
441462
'||' => 'SQLParser\Node\OrOp',
442463
'OR' => 'SQLParser\Node\OrOp',
443464
'XOR' => 'SQLParser\Node\XorOp',
465+
'WHEN' => 'SQLParser\Node\WhenConditions',
466+
'THEN' => 'SQLParser\Node\Then',
467+
'ELSE' => 'SQLParser\Node\ElseOperation'
444468
);
445469

446470
/**
@@ -587,7 +611,7 @@ public static function simplify($nodes)
587611
array('INTERVAL'),
588612
array('BINARY', 'COLLATE'),
589613
array('!'),
590-
array('BETWEEN', 'CASE', 'WHEN', 'THEN', 'ELSE'),
614+
array('CASE', 'WHEN', 'THEN', 'ELSE'),
591615
array('NOT'),
592616
*/
593617

@@ -625,6 +649,33 @@ public static function simplify($nodes)
625649
$instance->setMaxValueOperand($maxOperand);
626650

627651
return $instance;
652+
/*} elseif ($operation === 'WHEN') {
653+
$when = array_shift($operands);
654+
655+
$whenOperands = $when->getOperands();
656+
$condition = array_shift($whenOperands);
657+
$result = array_shift($whenOperands);
658+
659+
$instance = new WhenThen();
660+
$instance->setCondition($condition);
661+
$instance->setResult($result);
662+
663+
return $instance;
664+
*/
665+
} elseif ($operation === 'CASE') {
666+
$innerOperation = array_shift($operands);
667+
668+
if (!empty($operands)) {
669+
throw new MagicQueryException('A CASE statement should contain only a ThenConditions or a ElseOperand object.');
670+
}
671+
672+
$instance = new CaseOperation();
673+
$instance->setOperation($innerOperation);
674+
return $instance;
675+
} elseif ($operation === 'END') {
676+
// Simply bypass the END operation. We already have a CASE matching node:
677+
$caseOperation = array_shift($operands);
678+
return $caseOperation;
628679
} else {
629680
$instance = new Operation();
630681
$instance->setOperatorSymbol($operation);

src/SQLParser/Node/Then.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace SQLParser\Node;
4+
5+
/**
6+
* This class represents a THEN (in a CASE WHEN ... THEN ... END construct)
7+
*
8+
* @author David Négrier <[email protected]>
9+
*/
10+
class Then extends AbstractTwoOperandsOperator
11+
{
12+
/**
13+
* Returns the symbol for this operator.
14+
*
15+
* @return string
16+
*/
17+
protected function getOperatorSymbol()
18+
{
19+
return 'THEN';
20+
}
21+
}

src/SQLParser/Node/WhenConditions.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace SQLParser\Node;
4+
5+
use Mouf\Utils\Common\ConditionInterface\ConditionTrait;
6+
use Doctrine\DBAL\Connection;
7+
use Mouf\MoufManager;
8+
use Mouf\MoufInstanceDescriptor;
9+
use SQLParser\Node\Traverser\NodeTraverser;
10+
use SQLParser\Node\Traverser\VisitorInterface;
11+
12+
/**
13+
* This class represents a set of WHEN ... THEN ... construct (inside a CASE).
14+
*
15+
* @author David Négrier <[email protected]>
16+
*/
17+
class WhenConditions extends AbstractManyInstancesOperator
18+
{
19+
/**
20+
* Renders the object as a SQL string.
21+
*
22+
* @param Connection $dbConnection
23+
* @param array $parameters
24+
* @param number $indent
25+
* @param int $conditionsMode
26+
*
27+
* @return string
28+
*/
29+
public function toSql(array $parameters = array(), Connection $dbConnection = null, $indent = 0, $conditionsMode = self::CONDITION_APPLY)
30+
{
31+
$fullSql = '';
32+
foreach ($this->getOperands() as $operand) {
33+
$sql = NodeFactory::toSql($operand, $dbConnection, $parameters, ' ', false, $indent, $conditionsMode);
34+
if ($sql != null) {
35+
$fullSql .= "\n".str_repeat(' ', $indent).'WHEN '.$sql;
36+
}
37+
}
38+
39+
return $fullSql;
40+
}
41+
42+
/**
43+
* Returns the symbol for this operator.
44+
*
45+
* @return string
46+
*/
47+
protected function getOperatorSymbol()
48+
{
49+
return 'WHEN';
50+
}
51+
}

tests/Mouf/Database/MagicQueryTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ public function testStandardSelect()
8888
// Tests parameters with a ! (to force NULL values)
8989
$sql = 'SELECT * FROM users WHERE status = :status!';
9090
$this->assertEquals("SELECT * FROM users WHERE status = null", self::simplifySql($magicQuery->build($sql, ['status' => null])));
91+
92+
// Test CASE WHERE
93+
$sql = "SELECT CASE WHEN status = 'on' THEN '1' WHEN status = 'off' THEN '0' ELSE '-1' END AS my_case FROM users";
94+
$this->assertEquals("SELECT CASE WHEN status = 'on' THEN '1' WHEN status = 'off' THEN '0' ELSE '-1' END AS my_case FROM users", self::simplifySql($magicQuery->build($sql)));
9195
}
9296

9397
public function testWithCache() {

0 commit comments

Comments
 (0)