Skip to content

Commit 384a32b

Browse files
committed
Merge branch '1.1' of github.com:thecodingmachine/magic-query into 1.1
2 parents 2b3d1e3 + 3424082 commit 384a32b

20 files changed

+1116
-89
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

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ with Doctrine Cache. You simply have to pass a Doctrine Cache instance has the s
191191

192192
```php
193193
use Mouf\Database\MagicQuery;
194-
use Doctrine\Common\Cache\ApcCache();
194+
use Doctrine\Common\Cache\ApcCache;
195195

196196
// $conn is a Doctrine connection
197197
$magicQuery = new MagicQuery($conn, new ApcCache());

doc/discard_unused_parameters.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ $result2 = $magicQuery->build($sql, []);
2424
// The whole WHERE condition disappeared because it is not needed anymore!
2525
```
2626

27+
Magic-parameters can also collapse a BETWEEN filter into a simple `>=` or `<=` filter:
28+
29+
```php
30+
$sql = "SELECT * FROM products WHERE status BETWEEN :lowerStatus AND :upperStatus";
31+
32+
// Let's pass only the "lowerStatus" parameter
33+
$result = $magicQuery->build($sql, [ "lowerStatus" => 2 ]);
34+
// $result = SELECT * FROM products WHERE status >= 2
35+
// See? The BETWEEN filter was transformed in a >= filter because we did not provide a higher limit.
36+
```
37+
2738
###Why should I care?
2839

2940
Because it is **the most efficient way to deal with queries that can have a variable number of parameters**!

src/Mouf/Database/MagicQuery.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ private function magicJoin(NodeInterface $select) {
153153
*/
154154
private function magicJoinOnOneQuery(MagicJoinSelect $magicJoinSelect) {
155155
$tableSearchNodeTraverser = new NodeTraverser();
156-
$detectTableVisitor = new DetectTablesVisitor();
156+
$detectTableVisitor = new DetectTablesVisitor($magicJoinSelect->getMainTable());
157157
$tableSearchNodeTraverser->addVisitor($detectTableVisitor);
158158

159159
$select = $magicJoinSelect->getSelect();

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/Between.php

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
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 BETWEEN operation.
14+
*
15+
* @author David Négrier <[email protected]>
16+
*/
17+
class Between implements NodeInterface
18+
{
19+
private $leftOperand;
20+
21+
public function getLeftOperand()
22+
{
23+
return $this->leftOperand;
24+
}
25+
26+
/**
27+
* Sets the leftOperand.
28+
*
29+
* @Important
30+
*
31+
* @param NodeInterface|NodeInterface[]|string $leftOperand
32+
*/
33+
public function setLeftOperand($leftOperand)
34+
{
35+
$this->leftOperand = $leftOperand;
36+
}
37+
38+
/**
39+
* @var string|NodeInterface|NodeInterface[]
40+
*/
41+
private $minValueOperand;
42+
43+
/**
44+
* @var string|NodeInterface|NodeInterface[]
45+
*/
46+
private $maxValueOperand;
47+
48+
/**
49+
* @return NodeInterface|NodeInterface[]|string
50+
*/
51+
public function getMinValueOperand()
52+
{
53+
return $this->minValueOperand;
54+
}
55+
56+
/**
57+
* @param NodeInterface|NodeInterface[]|string $minValueOperand
58+
*/
59+
public function setMinValueOperand($minValueOperand)
60+
{
61+
$this->minValueOperand = $minValueOperand;
62+
}
63+
64+
/**
65+
* @return NodeInterface|NodeInterface[]|string
66+
*/
67+
public function getMaxValueOperand()
68+
{
69+
return $this->maxValueOperand;
70+
}
71+
72+
/**
73+
* @param NodeInterface|NodeInterface[]|string $maxValueOperand
74+
*/
75+
public function setMaxValueOperand($maxValueOperand)
76+
{
77+
$this->maxValueOperand = $maxValueOperand;
78+
}
79+
80+
/**
81+
* @var ConditionInterface
82+
*/
83+
protected $minValueCondition;
84+
85+
/**
86+
* Sets the condition.
87+
*
88+
* @Important IfSet
89+
* @param ConditionInterface $minValueCondition
90+
*/
91+
public function setMinValueCondition(ConditionInterface $minValueCondition = null) {
92+
$this->minValueCondition = $minValueCondition;
93+
}
94+
95+
/**
96+
* @var ConditionInterface
97+
*/
98+
protected $maxValueCondition;
99+
100+
/**
101+
* Sets the condition.
102+
*
103+
* @Important IfSet
104+
* @param ConditionInterface $maxValueCondition
105+
*/
106+
public function setMaxValueCondition(ConditionInterface $maxValueCondition = null) {
107+
$this->maxValueCondition = $maxValueCondition;
108+
}
109+
110+
111+
/**
112+
* Returns a Mouf instance descriptor describing this object.
113+
*
114+
* @param MoufManager $moufManager
115+
*
116+
* @return MoufInstanceDescriptor
117+
*/
118+
public function toInstanceDescriptor(MoufManager $moufManager)
119+
{
120+
$instanceDescriptor = $moufManager->createInstance(get_called_class());
121+
$instanceDescriptor->getProperty('leftOperand')->setValue(NodeFactory::nodeToInstanceDescriptor($this->leftOperand, $moufManager));
122+
$instanceDescriptor->getProperty('minValueOperand')->setValue(NodeFactory::nodeToInstanceDescriptor($this->minValueOperand, $moufManager));
123+
$instanceDescriptor->getProperty('maxValueOperand')->setValue(NodeFactory::nodeToInstanceDescriptor($this->maxValueOperand, $moufManager));
124+
125+
if ($this->minValueOperand instanceof Parameter) {
126+
// Let's add a condition on the parameter.
127+
$conditionDescriptor = $moufManager->createInstance('Mouf\\Database\\QueryWriter\\Condition\\ParamAvailableCondition');
128+
$conditionDescriptor->getProperty('parameterName')->setValue($this->minValueOperand->getName());
129+
$instanceDescriptor->getProperty('minValueCondition')->setValue($conditionDescriptor);
130+
}
131+
132+
if ($this->maxValueOperand instanceof Parameter) {
133+
// Let's add a condition on the parameter.
134+
$conditionDescriptor = $moufManager->createInstance('Mouf\\Database\\QueryWriter\\Condition\\ParamAvailableCondition');
135+
$conditionDescriptor->getProperty('parameterName')->setValue($this->maxValueOperand->getName());
136+
$instanceDescriptor->getProperty('maxValueCondition')->setValue($conditionDescriptor);
137+
}
138+
139+
return $instanceDescriptor;
140+
}
141+
142+
/**
143+
* Renders the object as a SQL string.
144+
*
145+
* @param Connection $dbConnection
146+
* @param array $parameters
147+
* @param number $indent
148+
* @param int $conditionsMode
149+
*
150+
* @return string
151+
*/
152+
public function toSql(array $parameters = array(), Connection $dbConnection = null, $indent = 0, $conditionsMode = self::CONDITION_APPLY)
153+
{
154+
$minBypass = false;
155+
$maxBypass = false;
156+
157+
if ($conditionsMode == self::CONDITION_GUESS) {
158+
if ($this->minValueOperand instanceof Parameter) {
159+
if ($this->minValueOperand->isDiscardedOnNull() && !isset($parameters[$this->minValueOperand->getName()])) {
160+
$minBypass = true;
161+
}
162+
}
163+
164+
if ($this->maxValueOperand instanceof Parameter) {
165+
if ($this->maxValueOperand->isDiscardedOnNull() && !isset($parameters[$this->maxValueOperand->getName()])) {
166+
$maxBypass = true;
167+
}
168+
}
169+
} elseif ($conditionsMode == self::CONDITION_IGNORE) {
170+
$minBypass = false;
171+
$maxBypass = false;
172+
} else {
173+
if ($this->minValueCondition && !$this->minValueCondition->isOk($parameters)) {
174+
$minBypass = true;
175+
}
176+
if ($this->maxValueCondition && !$this->maxValueCondition->isOk($parameters)) {
177+
$maxBypass = true;
178+
}
179+
}
180+
181+
if (!$minBypass && !$maxBypass) {
182+
$sql = NodeFactory::toSql($this->leftOperand, $dbConnection, $parameters, ' ', false, $indent, $conditionsMode);
183+
$sql .= ' BETWEEN ';
184+
$sql .= NodeFactory::toSql($this->minValueOperand, $dbConnection, $parameters, ' ', false, $indent, $conditionsMode);
185+
$sql .= ' AND ';
186+
$sql .= NodeFactory::toSql($this->maxValueOperand, $dbConnection, $parameters, ' ', false, $indent, $conditionsMode);
187+
} elseif (!$minBypass && $maxBypass) {
188+
$sql = NodeFactory::toSql($this->leftOperand, $dbConnection, $parameters, ' ', false, $indent, $conditionsMode);
189+
$sql .= ' >= ';
190+
$sql .= NodeFactory::toSql($this->minValueOperand, $dbConnection, $parameters, ' ', false, $indent, $conditionsMode);
191+
} elseif ($minBypass && !$maxBypass) {
192+
$sql = NodeFactory::toSql($this->leftOperand, $dbConnection, $parameters, ' ', false, $indent, $conditionsMode);
193+
$sql .= ' <= ';
194+
$sql .= NodeFactory::toSql($this->maxValueOperand, $dbConnection, $parameters, ' ', false, $indent, $conditionsMode);
195+
} else {
196+
$sql = null;
197+
}
198+
199+
return $sql;
200+
}
201+
202+
/**
203+
* Walks the tree of nodes, calling the visitor passed in parameter.
204+
*
205+
* @param VisitorInterface $visitor
206+
*/
207+
public function walk(VisitorInterface $visitor) {
208+
$node = $this;
209+
$result = $visitor->enterNode($node);
210+
if ($result instanceof NodeInterface) {
211+
$node = $result;
212+
}
213+
if ($result !== NodeTraverser::DONT_TRAVERSE_CHILDREN) {
214+
$result2 = $this->leftOperand->walk($visitor);
215+
if ($result2 === NodeTraverser::REMOVE_NODE) {
216+
return NodeTraverser::REMOVE_NODE;
217+
} elseif ($result2 instanceof NodeInterface) {
218+
$this->leftOperand = $result2;
219+
}
220+
221+
$result2 = $this->minValueOperand->walk($visitor);
222+
if ($result2 === NodeTraverser::REMOVE_NODE) {
223+
return NodeTraverser::REMOVE_NODE;
224+
} elseif ($result2 instanceof NodeInterface) {
225+
$this->minValueOperand = $result2;
226+
}
227+
228+
$result2 = $this->maxValueOperand->walk($visitor);
229+
if ($result2 === NodeTraverser::REMOVE_NODE) {
230+
return NodeTraverser::REMOVE_NODE;
231+
} elseif ($result2 instanceof NodeInterface) {
232+
$this->maxValueOperand = $result2;
233+
}
234+
}
235+
return $visitor->leaveNode($node);
236+
}
237+
}

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
}

0 commit comments

Comments
 (0)