Skip to content

Commit 333d3e4

Browse files
author
David Maechler
committed
Fixing simple function case in the sql parsing
1 parent 9887d04 commit 333d3e4

File tree

6 files changed

+267
-14
lines changed

6 files changed

+267
-14
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
vendor/
22
composer.lock
3+
phpunit.xml

src/SQLParser/Node/Expression.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
use Doctrine\DBAL\Connection;
3737
use Mouf\MoufInstanceDescriptor;
3838
use Mouf\MoufManager;
39+
use SQLParser\Node\Traverser\NodeTraverser;
3940
use SQLParser\Node\Traverser\VisitorInterface;
4041

4142
/**
@@ -181,6 +182,7 @@ public function toInstanceDescriptor(MoufManager $moufManager)
181182
public function toSql(array $parameters = array(), Connection $dbConnection = null, $indent = 0, $conditionsMode = self::CONDITION_APPLY)
182183
{
183184
$sql = NodeFactory::toSql($this->subTree, $dbConnection, $parameters, ' ', false, $indent, $conditionsMode);
185+
184186
if ($this->alias) {
185187
$sql .= ' AS '.$this->alias;
186188
}

src/SQLParser/Node/NodeFactory.php

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,32 @@ public static function toObject(array $desc)
239239
throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
240240
}
241241

242+
return $expr;
243+
case ExpressionType::SIMPLE_FUNCTION:
244+
$expr = new SimpleFunction();
245+
$expr->setBaseExpression($desc['base_expr']);
246+
247+
if (isset($desc['sub_tree'])) {
248+
$expr->setSubTree(self::buildFromSubtree($desc['sub_tree']));
249+
}
250+
251+
if (isset($desc['alias'])) {
252+
$expr->setAlias($desc['alias']['name']);
253+
}
254+
if (isset($desc['direction'])) {
255+
$expr->setDirection($desc['direction']);
256+
}
257+
258+
// Debug:
259+
unset($desc['base_expr']);
260+
unset($desc['expr_type']);
261+
unset($desc['sub_tree']);
262+
unset($desc['alias']);
263+
unset($desc['direction']);
264+
if (!empty($desc)) {
265+
throw new \InvalidArgumentException('Unexpected parameters in simple function: '.var_export($desc, true));
266+
}
267+
242268
return $expr;
243269

244270
case ExpressionType::USER_VARIABLE:
@@ -248,8 +274,6 @@ public static function toObject(array $desc)
248274

249275
case ExpressionType::RESERVED:
250276

251-
case ExpressionType::SIMPLE_FUNCTION:
252-
253277
case ExpressionType::EXPRESSION:
254278
case ExpressionType::BRACKET_EXPRESSION:
255279
case ExpressionType::TABLE_EXPRESSION:
@@ -277,7 +301,7 @@ public static function toObject(array $desc)
277301
}
278302

279303
if (isset($desc['alias'])) {
280-
$expr->setAlias($desc['alias']);
304+
$expr->setAlias($desc['alias']['name']);
281305
}
282306
if (isset($desc['direction'])) {
283307
$expr->setDirection($desc['direction']);
@@ -385,7 +409,9 @@ private static function buildFromSubtree($subTree)
385409
*/
386410
public static function simplify($nodes)
387411
{
388-
if (!is_array($nodes)) {
412+
if (empty($nodes)) {
413+
$nodes = array();
414+
} elseif (!is_array($nodes)) {
389415
$nodes = array($nodes);
390416
}
391417
$minPriority = -1;
@@ -439,8 +465,8 @@ public static function simplify($nodes)
439465
// At this point, the $selectedOperator list contains a list of operators of the same kind that will apply
440466
// at the same time.
441467
if (empty($selectedOperators)) {
442-
// If we have an Expression, let's simply discard it.
443-
// Indeed, the tree will add brackets by itself, and no Expression in needed for that.
468+
// If we have an Expression with no base expression, let's simply discard it.
469+
// Indeed, the tree will add brackets by itself, and no Expression is needed for that.
444470
$newNodes = array();
445471
/*foreach ($nodes as $key=>$operand) {
446472
if ($operand instanceof Expression) {
@@ -452,9 +478,13 @@ public static function simplify($nodes)
452478
}*/
453479
foreach ($nodes as $operand) {
454480
if ($operand instanceof Expression) {
455-
$subTree = $operand->getSubTree();
456-
if (count($subTree) == 1) {
457-
$newNodes = array_merge($newNodes, self::simplify($subTree));
481+
if (empty($operand->getBaseExpression())) {
482+
$subTree = $operand->getSubTree();
483+
if (count($subTree) == 1) {
484+
$newNodes = array_merge($newNodes, self::simplify($subTree));
485+
} else {
486+
$newNodes[] = $operand;
487+
}
458488
} else {
459489
$newNodes[] = $operand;
460490
}

src/SQLParser/Node/SimpleFunction.php

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
<?php
2+
3+
/**
4+
* expression-types.php.
5+
*
6+
*
7+
* Copyright (c) 2010-2013, Justin Swanhart
8+
* with contributions by André Rothe <[email protected], [email protected]>
9+
* and David Négrier <[email protected]>
10+
*
11+
* All rights reserved.
12+
*
13+
* Redistribution and use in source and binary forms, with or without modification,
14+
* are permitted provided that the following conditions are met:
15+
*
16+
* * Redistributions of source code must retain the above copyright notice,
17+
* this list of conditions and the following disclaimer.
18+
* * Redistributions in binary form must reproduce the above copyright notice,
19+
* this list of conditions and the following disclaimer in the documentation
20+
* and/or other materials provided with the distribution.
21+
*
22+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
23+
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24+
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
25+
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26+
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
27+
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28+
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30+
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
31+
* DAMAGE.
32+
*/
33+
34+
namespace SQLParser\Node;
35+
36+
use Doctrine\DBAL\Connection;
37+
use Mouf\MoufInstanceDescriptor;
38+
use Mouf\MoufManager;
39+
use SQLParser\Node\Traverser\NodeTraverser;
40+
use SQLParser\Node\Traverser\VisitorInterface;
41+
42+
/**
43+
* This class represents a node that is an SQL function call.
44+
*
45+
* @author David Négrier <[email protected]>
46+
*/
47+
class SimpleFunction implements NodeInterface
48+
{
49+
private $baseExpression;
50+
51+
/**
52+
* Returns the base expression (the string that generated this expression).
53+
*
54+
* @return string
55+
*/
56+
public function getBaseExpression()
57+
{
58+
return $this->baseExpression;
59+
}
60+
61+
/**
62+
* Sets the base expression (the string that generated this expression).
63+
*
64+
* @param string $baseExpression
65+
*/
66+
public function setBaseExpression($baseExpression)
67+
{
68+
$this->baseExpression = $baseExpression;
69+
}
70+
71+
private $subTree;
72+
73+
public function getSubTree()
74+
{
75+
return $this->subTree;
76+
}
77+
78+
/**
79+
* Sets the subtree.
80+
*
81+
* @Important
82+
*
83+
* @param array<NodeInterface>|NodeInterface $subTree
84+
*/
85+
public function setSubTree($subTree)
86+
{
87+
$this->subTree = $subTree;
88+
$this->subTree = NodeFactory::simplify($this->subTree);
89+
}
90+
91+
private $alias;
92+
93+
public function getAlias()
94+
{
95+
return $this->alias;
96+
}
97+
98+
/**
99+
* Sets the alias.
100+
*
101+
* @Important
102+
*
103+
* @param string $alias
104+
*/
105+
public function setAlias($alias)
106+
{
107+
$this->alias = $alias;
108+
}
109+
110+
private $direction;
111+
112+
public function getDiretion()
113+
{
114+
return $this->direction;
115+
}
116+
117+
/**
118+
* Sets the direction.
119+
*
120+
* @Important
121+
*
122+
* @param string $direction
123+
*/
124+
public function setDirection($direction)
125+
{
126+
$this->direction = $direction;
127+
}
128+
129+
private $brackets = false;
130+
131+
/**
132+
* Returns true if the expression is between brackets.
133+
*
134+
* @return bool
135+
*/
136+
public function hasBrackets()
137+
{
138+
return $this->brackets;
139+
}
140+
141+
/**
142+
* Returns a Mouf instance descriptor describing this object.
143+
*
144+
* @param MoufManager $moufManager
145+
*
146+
* @return MoufInstanceDescriptor
147+
*/
148+
public function toInstanceDescriptor(MoufManager $moufManager)
149+
{
150+
$instanceDescriptor = $moufManager->createInstance(get_called_class());
151+
$instanceDescriptor->getProperty('baseExpression')->setValue(NodeFactory::nodeToInstanceDescriptor($this->baseExpression, $moufManager));
152+
$instanceDescriptor->getProperty('subTree')->setValue(NodeFactory::nodeToInstanceDescriptor($this->subTree, $moufManager));
153+
$instanceDescriptor->getProperty('alias')->setValue(NodeFactory::nodeToInstanceDescriptor($this->alias, $moufManager));
154+
$instanceDescriptor->getProperty('direction')->setValue(NodeFactory::nodeToInstanceDescriptor($this->direction, $moufManager));
155+
156+
return $instanceDescriptor;
157+
}
158+
159+
/**
160+
* Renders the object as a SQL string.
161+
*
162+
* @param Connection $dbConnection
163+
* @param array $parameters
164+
* @param number $indent
165+
* @param int $conditionsMode
166+
*
167+
* @return string
168+
*/
169+
public function toSql(array $parameters = array(), Connection $dbConnection = null, $indent = 0, $conditionsMode = self::CONDITION_APPLY)
170+
{
171+
$sql = '';
172+
if (!empty($this->baseExpression)) {
173+
$sql .= $this->baseExpression.'(';
174+
}
175+
$sql .= NodeFactory::toSql($this->subTree, $dbConnection, $parameters, ' ', false, $indent, $conditionsMode);
176+
if (!empty($this->baseExpression)) {
177+
$sql .= ')';
178+
}
179+
180+
if ($this->alias) {
181+
$sql .= ' AS '.$this->alias;
182+
}
183+
if ($this->direction) {
184+
$sql .= ' '.$this->direction;
185+
}
186+
187+
return $sql;
188+
}
189+
190+
/**
191+
* Walks the tree of nodes, calling the visitor passed in parameter.
192+
*
193+
* @param VisitorInterface $visitor
194+
*/
195+
public function walk(VisitorInterface $visitor) {
196+
$node = $this;
197+
$result = $visitor->enterNode($node);
198+
if ($result instanceof NodeInterface) {
199+
$node = $result;
200+
}
201+
if ($result !== NodeTraverser::DONT_TRAVERSE_CHILDREN) {
202+
foreach ($this->subTree as $key => $operand) {
203+
$result2 = $operand->walk($visitor);
204+
if ($result2 === NodeTraverser::REMOVE_NODE) {
205+
unset($this->subTree[$key]);
206+
} elseif ($result2 instanceof NodeInterface) {
207+
$this->subTree[$key] = $result2;
208+
}
209+
}
210+
}
211+
return $visitor->leaveNode($node);
212+
}
213+
}

src/SQLParser/Query/Select.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -360,11 +360,13 @@ private function walkChildren(&$children, VisitorInterface $visitor) {
360360
if ($children) {
361361
if (is_array($children)) {
362362
foreach ($children as $key => $operand) {
363-
$result2 = $operand->walk($visitor);
364-
if ($result2 === NodeTraverser::REMOVE_NODE) {
365-
unset($children[$key]);
366-
} elseif ($result2 instanceof NodeInterface) {
367-
$children[$key] = $result2;
363+
if($operand) {
364+
$result2 = $operand->walk($visitor);
365+
if ($result2 === NodeTraverser::REMOVE_NODE) {
366+
unset($children[$key]);
367+
} elseif ($result2 instanceof NodeInterface) {
368+
$children[$key] = $result2;
369+
}
368370
}
369371
}
370372
} else {

tests/Mouf/Database/MagicQueryTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,18 @@ public function testStandardSelect()
1212
{
1313
$magicQuery = new MagicQuery();
1414

15+
$sql = 'SELECT YEAR(CURDATE()) AS current_year FROM users WHERE name LIKE :name';
16+
$this->assertEquals("SELECT YEAR(CURDATE()) AS current_year FROM users WHERE name LIKE 'foo'", self::simplifySql($magicQuery->build($sql, ['name' => 'foo'])));
17+
1518
$sql = 'SELECT * FROM users';
1619
$this->assertEquals($sql, self::simplifySql($magicQuery->build($sql)));
1720

1821
$sql = 'SELECT * FROM users WHERE name LIKE :name';
1922
$this->assertEquals("SELECT * FROM users WHERE name LIKE 'foo'", self::simplifySql($magicQuery->build($sql, ['name' => 'foo'])));
2023
$this->assertEquals('SELECT * FROM users', self::simplifySql($magicQuery->build($sql)));
2124

25+
26+
2227
$sql = 'SELECT SUM(users.age) FROM users WHERE name LIKE :name AND company LIKE :company';
2328
$this->assertEquals("SELECT SUM(users.age) FROM users WHERE (name LIKE 'foo')", self::simplifySql($magicQuery->build($sql, ['name' => 'foo'])));
2429
$this->assertEquals("SELECT SUM(users.age) FROM users WHERE (name LIKE 'foo') AND (company LIKE 'bar')", self::simplifySql($magicQuery->build($sql, ['name' => 'foo', 'company' => 'bar'])));

0 commit comments

Comments
 (0)