Skip to content

Commit cc99a92

Browse files
author
David Maechler
committed
adding limit paramerizable and get it functional, then fixing bug on order by case with params
1 parent 3709700 commit cc99a92

File tree

9 files changed

+264
-16
lines changed

9 files changed

+264
-16
lines changed

src/SQLParser/ExpressionType.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ class ExpressionType
4545
const RESERVED = 'reserved';
4646
const CONSTANT = 'const';
4747

48+
const LIMIT_CONST = 'limit_const';
49+
4850
const AGGREGATE_FUNCTION = 'aggregate_function';
4951
const SIMPLE_FUNCTION = 'function';
5052

src/SQLParser/Node/LimitNode.php

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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\MoufManager;
38+
use Mouf\MoufInstanceDescriptor;
39+
use SQLParser\Node\Traverser\VisitorInterface;
40+
41+
/**
42+
* This class represents a constant of a LIMIT in an SQL expression.
43+
*
44+
* @author David MAECHLER <[email protected]>
45+
*/
46+
class LimitNode implements NodeInterface
47+
{
48+
private $value;
49+
50+
public function getValue()
51+
{
52+
return $this->value;
53+
}
54+
55+
/**
56+
* Sets the value.
57+
*
58+
* @Important
59+
*
60+
* @param string $value
61+
*/
62+
public function setValue($value)
63+
{
64+
$this->value = $value;
65+
}
66+
67+
/**
68+
* Returns a Mouf instance descriptor describing this object.
69+
*
70+
* @param MoufManager $moufManager
71+
*
72+
* @return MoufInstanceDescriptor
73+
*/
74+
public function toInstanceDescriptor(MoufManager $moufManager)
75+
{
76+
$instanceDescriptor = $moufManager->createInstance(get_called_class());
77+
$instanceDescriptor->getProperty('value')->setValue(NodeFactory::nodeToInstanceDescriptor($this->value, $moufManager));
78+
79+
return $instanceDescriptor;
80+
}
81+
82+
/**
83+
* Renders the object as a SQL string.
84+
*
85+
* @param array $parameters
86+
* @param Connection $dbConnection
87+
* @param int|number $indent
88+
* @param int $conditionsMode
89+
* @return string
90+
* @throws \Exception
91+
*/
92+
public function toSql(array $parameters = array(), Connection $dbConnection = null, $indent = 0, $conditionsMode = self::CONDITION_APPLY)
93+
{
94+
if($this->value === null) {
95+
throw new \Exception('A limit parameter must be an integer');
96+
}
97+
98+
if(is_numeric($this->value)) {
99+
return (int) $this->value;
100+
} else if ($dbConnection != null) {
101+
return $dbConnection->quote($this->value);
102+
} else {
103+
return addslashes($this->value);
104+
}
105+
}
106+
107+
/**
108+
* Walks the tree of nodes, calling the visitor passed in parameter.
109+
*
110+
* @param VisitorInterface $visitor
111+
* @return NodeInterface|null|string Can return null if nothing is to be done or a node that should replace this node, or NodeTraverser::REMOVE_NODE to remove the node
112+
*/
113+
public function walk(VisitorInterface $visitor) {
114+
$node = $this;
115+
$result = $visitor->enterNode($node);
116+
if ($result instanceof NodeInterface) {
117+
$node = $result;
118+
}
119+
return $visitor->leaveNode($node);
120+
}
121+
}

src/SQLParser/Node/NodeFactory.php

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,32 @@ public static function toObject(array $desc)
5454
}
5555

5656
switch ($desc['expr_type']) {
57+
case ExpressionType::LIMIT_CONST:
58+
if (substr($desc['base_expr'], 0, 1) == ':') {
59+
$instance = new UnquotedParameter();
60+
$instance->setName(substr($desc['base_expr'], 1));
61+
} else {
62+
$instance = new LimitNode();
63+
$expr = $desc['base_expr'];
64+
if (strpos($expr, "'") === 0) {
65+
$expr = substr($expr, 1);
66+
}
67+
if (strrpos($expr, "'") === strlen($expr) - 1) {
68+
$expr = substr($expr, 0, strlen($expr) - 1);
69+
}
70+
$expr = stripslashes($expr);
71+
72+
$instance->setValue($expr);
73+
}
74+
// Debug:
75+
unset($desc['base_expr']);
76+
unset($desc['expr_type']);
77+
unset($desc['sub_tree']);
78+
if (!empty($desc)) {
79+
throw new \InvalidArgumentException('Unexpected parameters in exception: '.var_export($desc, true));
80+
}
81+
82+
return $instance;
5783
case ExpressionType::CONSTANT:
5884
$const = new ConstNode();
5985
$expr = $desc['base_expr'];
@@ -108,10 +134,10 @@ public static function toObject(array $desc)
108134
if (!empty($desc['alias'])) {
109135
$instance->setAlias($desc['alias']['name']);
110136
}
111-
}
112137

113-
if (!empty($desc['direction'])) {
114-
$instance->setDirection($desc['direction']);
138+
if (!empty($desc['direction'])) {
139+
$instance->setDirection($desc['direction']);
140+
}
115141
}
116142

117143
// Debug:

src/SQLParser/Node/Parameter.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@
4545
*/
4646
class Parameter implements NodeInterface
4747
{
48-
private $name;
49-
private $discardedOnNull = true;
48+
protected $name;
49+
protected $discardedOnNull = true;
5050

5151
/**
5252
* Returns the name.
@@ -80,12 +80,12 @@ public function setName($name)
8080
/**
8181
* @var string
8282
*/
83-
private $autoPrepend;
83+
protected $autoPrepend;
8484

8585
/**
8686
* @var string
8787
*/
88-
private $autoAppend;
88+
protected $autoAppend;
8989

9090
/**
9191
* @return string
@@ -180,9 +180,8 @@ public function toSql(array $parameters = array(), Connection $dbConnection = nu
180180
return "'".addslashes($this->autoPrepend.$item.$this->autoAppend)."'";
181181
}, $parameters[$this->name])).')';
182182
} else{
183-
return "'".addslashes($this->autoPrepend.$parameters[$this->name].$this->autoAppend)."'";
183+
return "'".addslashes($this->autoPrepend.$parameters[$this->name].$this->autoAppend)."'";
184184
}
185-
186185
}
187186
}
188187
} elseif (!$this->isDiscardedOnNull()) {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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\VisitorInterface;
40+
41+
/**
42+
* This class represents a parameter (as in parameterized query).
43+
*
44+
* @author David MAECHLER <[email protected]>
45+
*/
46+
class UnquotedParameter extends Parameter
47+
{
48+
/**
49+
* Renders the object as a SQL string without quote if its a numeric
50+
*
51+
* @param array $parameters
52+
* @param Connection $dbConnection
53+
* @param int|number $indent
54+
* @param int $conditionsMode
55+
* @return string
56+
*/
57+
public function toSql(array $parameters = array(), Connection $dbConnection = null, $indent = 0, $conditionsMode = self::CONDITION_APPLY)
58+
{
59+
$name = parent::toSql($parameters, $dbConnection, $indent, $conditionsMode);
60+
$name = str_replace("'", "", $name);
61+
return is_numeric($name) ? (int)$name : $name;
62+
}
63+
}

src/SQLParser/Query/Select.php

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,22 @@ public function setOptions($options)
229229
$this->options = $options;
230230
}
231231

232+
private $limit;
233+
234+
/**
235+
* @return NodeInterface[]|NodeInterface $limit
236+
*/
237+
public function getLimit() {
238+
return $this->limit;
239+
}
240+
241+
/**
242+
* @param NodeInterface[]|NodeInterface $limit
243+
*/
244+
public function setLimit($limit) {
245+
$this->limit = $limit;
246+
}
247+
232248
/**
233249
* @param MoufManager $moufManager
234250
*
@@ -244,6 +260,7 @@ public function toInstanceDescriptor(MoufManager $moufManager)
244260
$instanceDescriptor->getProperty('group')->setValue(NodeFactory::nodeToInstanceDescriptor($this->group, $moufManager));
245261
$instanceDescriptor->getProperty('having')->setValue(NodeFactory::nodeToInstanceDescriptor($this->having, $moufManager));
246262
$instanceDescriptor->getProperty('order')->setValue(NodeFactory::nodeToInstanceDescriptor($this->order, $moufManager));
263+
$instanceDescriptor->getProperty('limit')->setValue(NodeFactory::nodeToInstanceDescriptor($this->limit, $moufManager));
247264
$instanceDescriptor->getProperty('options')->setValue($this->options);
248265

249266
return $instanceDescriptor;
@@ -267,6 +284,7 @@ public function overwriteInstanceDescriptor($name, MoufManager $moufManager)
267284
$instanceDescriptor->getProperty('group')->setValue(NodeFactory::nodeToInstanceDescriptor($this->group, $moufManager));
268285
$instanceDescriptor->getProperty('having')->setValue(NodeFactory::nodeToInstanceDescriptor($this->having, $moufManager));
269286
$instanceDescriptor->getProperty('order')->setValue(NodeFactory::nodeToInstanceDescriptor($this->order, $moufManager));
287+
$instanceDescriptor->getProperty('limit')->setValue(NodeFactory::nodeToInstanceDescriptor($this->limit, $moufManager));
270288
$instanceDescriptor->getProperty('options')->setValue($this->options);
271289

272290
return $instanceDescriptor;
@@ -275,11 +293,10 @@ public function overwriteInstanceDescriptor($name, MoufManager $moufManager)
275293
/**
276294
* Renders the object as a SQL string.
277295
*
296+
* @param array $parameters
278297
* @param Connection $dbConnection
279-
* @param array $parameters
280-
* @param number $indent
281-
* @param int $conditionsMode
282-
*
298+
* @param int|number $indent
299+
* @param int $conditionsMode
283300
* @return string
284301
*/
285302
public function toSql(array $parameters = array(), Connection $dbConnection = null, $indent = 0, $conditionsMode = self::CONDITION_APPLY)
@@ -331,6 +348,13 @@ public function toSql(array $parameters = array(), Connection $dbConnection = nu
331348
}
332349
}
333350

351+
if (!empty($this->limit)) {
352+
$limit = NodeFactory::toSql($this->limit, $dbConnection, $parameters, ',', false, $indent + 2, $conditionsMode);
353+
if ($limit) {
354+
$sql .= "\nLIMIT ".$limit;
355+
}
356+
}
357+
334358
return $sql;
335359
}
336360

src/SQLParser/Query/StatementFactory.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ public static function toObject(array $desc)
9595
$select->setOrder($order);
9696
}
9797

98+
if (isset($desc['LIMIT'])) {
99+
$limit = self::mapArrayToNodeObjectList($desc['LIMIT']);
100+
$limit = NodeFactory::simplify($limit);
101+
$select->setLimit($limit);
102+
}
103+
98104
return $select;
99105
} else {
100106
throw new \BadMethodCallException('Unknown query');

src/SQLParser/SQLParser.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,10 @@ private function process_limit($tokens)
691691
}
692692
}
693693

694-
return array('offset' => trim($offset), 'rowcount' => trim($rowcount));
694+
return [
695+
['expr_type' => 'limit_const', 'base_expr' => trim($offset), 'sub_tree' => false ],
696+
['expr_type' => 'limit_const', 'base_expr' => trim($rowcount), 'sub_tree' => false ]
697+
];
695698
}
696699

697700
/**

tests/Mouf/Database/MagicQueryTest.php

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

15+
$sql = "SELECT id FROM users WHERE name LIKE :name LIMIT :offset, :limit";
16+
$this->assertEquals("SELECT id FROM users WHERE name LIKE 'foo' LIMIT 0, 20", self::simplifySql($magicQuery->build($sql, ['name' => 'foo', 'offset' => 0, 'limit' => 20])));
17+
18+
$sql = "SELECT id FROM users WHERE name LIKE :name LIMIT 0, 20";
19+
$this->assertEquals("SELECT id FROM users WHERE name LIKE 'foo' LIMIT 0, 20", self::simplifySql($magicQuery->build($sql, ['name' => 'foo'])));
20+
1521
$sql = "SELECT DATE_FORMAT(CURDATE(), '%d/%m/%Y') AS current_date FROM users WHERE name LIKE :name";
1622
$this->assertEquals("SELECT DATE_FORMAT(CURDATE(), '%d/%m/%Y') AS current_date FROM users WHERE name LIKE 'foo'", self::simplifySql($magicQuery->build($sql, ['name' => 'foo'])));
1723

@@ -25,8 +31,6 @@ public function testStandardSelect()
2531
$this->assertEquals("SELECT * FROM users WHERE name LIKE 'foo'", self::simplifySql($magicQuery->build($sql, ['name' => 'foo'])));
2632
$this->assertEquals('SELECT * FROM users', self::simplifySql($magicQuery->build($sql)));
2733

28-
29-
3034
$sql = 'SELECT SUM(users.age) FROM users WHERE name LIKE :name AND company LIKE :company';
3135
$this->assertEquals("SELECT SUM(users.age) FROM users WHERE (name LIKE 'foo')", self::simplifySql($magicQuery->build($sql, ['name' => 'foo'])));
3236
$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)