# Source for file Query.php

Documentation is available at Query.php

1. <?php
2. /*
3. * $Id: Query.php 2287 2007-08-29 21:36:36Z zYne $
4. *
5. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16. *
17. * This software consists of voluntary contributions made by many individuals
18. * and is licensed under the LGPL. For more information, see
19. * <http://www.phpdoctrine.com>.
20. */
21. Doctrine::autoload('Doctrine_Query_Abstract');
22. /**
23. * Doctrine_Query
24. *
25. * @package Doctrine
26. * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
27. * @category Object Relational Mapping
28. * @link www.phpdoctrine.com
29. * @since 1.0
30. * @version $Revision: 2287 $
31. * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
32. */
33. class Doctrine_Query extends Doctrine_Query_Abstract implements Countable
34. {
35. const STATE_CLEAN = 1;
37. const STATE_DIRTY = 2;
39. const STATE_DIRECT = 3;
41. const STATE_LOCKED = 4;
44. protected $subqueryAliases = array();
45. /**
46. * @param boolean $needsSubquery
47. */
48. protected $needsSubquery = false;
49. /**
50. * @param boolean $isSubquery whether or not this query object is a subquery of another
51. * query object
52. */
53. protected $isSubquery;
55. protected $isLimitSubqueryUsed = false;
56. /**
57. * @var array $_neededTableAliases an array containing the needed table aliases
58. */
59. protected $_neededTables = array();
60. /**
61. * @var array $pendingFields
62. */
63. protected $pendingFields = array();
64. /**
65. * @var array $pendingSubqueries SELECT part subqueries, these are called pending subqueries since
66. * they cannot be parsed directly (some queries might be correlated)
67. */
68. protected $pendingSubqueries = array();
69. /**
70. * @var array $_parsers an array of parser objects, each DQL query part has its own parser
71. */
72. protected $_parsers = array();
73. /**
74. * @var array $_enumParams an array containing the keys of the parameters that should be enumerated
75. */
76. protected $_enumParams = array();
78. /**
79. * @var array $_dqlParts an array containing all DQL query parts
80. */
81. protected $_dqlParts = array(
82. 'select' => array(),
83. 'forUpdate' => false,
84. 'from' => array(),
85. 'set' => array(),
86. 'join' => array(),
87. 'where' => array(),
88. 'groupby' => array(),
89. 'having' => array(),
90. 'orderby' => array(),
91. 'limit' => array(),
92. 'offset' => array(),
93. );
94. /**
95. * @var array $_pendingJoinConditions an array containing pending joins
96. */
97. protected $_pendingJoinConditions = array();
99. protected $_expressionMap = array();
101. protected $_state = Doctrine_Query::STATE_CLEAN;
103. /**
104. * create
105. * returns a new Doctrine_Query object
106. *
107. * @param Doctrine_Connection $conn optional connection parameter
108. * @return Doctrine_Query
109. */
110. public static function create($conn = null)
111. {
112. return new Doctrine_Query($conn);
113. }
114. public function reset()
115. {
116. $this->_pendingJoinConditions = array();
117. $this->pendingSubqueries = array();
118. $this->pendingFields = array();
119. $this->_neededTables = array();
120. $this->_expressionMap = array();
121. $this->subqueryAliases = array();
122. $this->needsSubquery = false;
123. $this->isLimitSubqueryUsed = false;
124. }
125. /**
126. * setOption
127. *
128. * @param string $name option name
129. * @param string $value option value
130. * @return Doctrine_Query this object
131. */
132. public function setOption($name, $value)
133. {
134. if ( ! isset($this->_options[$name])) {
135. throw new Doctrine_Query_Exception('Unknown option ' . $name);
136. }
137. $this->_options[$name] = $value;
138. }
139. /**
140. * addPendingJoinCondition
141. *
142. * @param string $componentAlias component alias
143. * @param string $joinCondition dql join condition
144. * @return Doctrine_Query this object
145. */
146. public function addPendingJoinCondition($componentAlias, $joinCondition)
147. {
148. $this->_pendingJoins[$componentAlias] = $joinCondition;
149. }
150. /**
151. * addEnumParam
152. * sets input parameter as an enumerated parameter
153. *
154. * @param string $key the key of the input parameter
155. * @return Doctrine_Query
156. */
157. public function addEnumParam($key, $table = null, $column = null)
158. {
159. $array = (isset($table) || isset($column)) ? array($table, $column) : array();
161. if ($key === '?') {
162. $this->_enumParams[] = $array;
163. } else {
164. $this->_enumParams[$key] = $array;
165. }
166. }
167. /**
168. * getEnumParams
169. * get all enumerated parameters
170. *
171. * @return array all enumerated parameters
172. */
173. public function getEnumParams()
174. {
175. return $this->_enumParams;
176. }
177. /**
178. * limitSubqueryUsed
179. *
180. * @return boolean
181. */
182. public function isLimitSubqueryUsed()
183. {
184. return $this->isLimitSubqueryUsed;
185. }
186. /**
187. * convertEnums
188. * convert enum parameters to their integer equivalents
189. *
190. * @return array converted parameter array
191. */
192. public function convertEnums($params)
193. {
194. foreach ($this->_enumParams as $key => $values) {
195. if (isset($params[$key])) {
196. if ( ! empty($values)) {
197. $params[$key] = $values[0]->enumIndex($values[1], $params[$key]);
198. }
199. }
200. }
201. return $params;
202. }
203. /**
204. * isSubquery
205. * if $bool parameter is set this method sets the value of
206. * Doctrine_Query::$isSubquery. If this value is set to true
207. * the query object will not load the primary key fields of the selected
208. * components.
209. *
210. * If null is given as the first parameter this method retrieves the current
211. * value of Doctrine_Query::$isSubquery.
212. *
213. * @param boolean $bool whether or not this query acts as a subquery
214. * @return Doctrine_Query|bool
215. */
216. public function isSubquery($bool = null)
217. {
218. if ($bool === null) {
219. return $this->isSubquery;
220. }
222. $this->isSubquery = (bool) $bool;
223. return $this;
224. }
225. /**
226. * getAggregateAlias
227. *
228. * @param string $dqlAlias the dql alias of an aggregate value
229. * @return string
230. */
231. public function getAggregateAlias($dqlAlias)
232. {
233. if (isset($this->aggregateMap[$dqlAlias])) {
234. // mark the expression as used
235. $this->_expressionMap[$dqlAlias][1] = true;
237. return $this->aggregateMap[$dqlAlias];
238. }
239. if ( ! empty($this->pendingAggregates)) {
240. $this->processPendingAggregates();
242. return $this->getAggregateAlias($dqlAlias);
243. }
244. throw new Doctrine_Query_Exception('Unknown aggregate alias ' . $dqlAlias);
245. }
246. /**
247. * getParser
248. * parser lazy-loader
249. *
250. * @throws Doctrine_Query_Exception if unknown parser name given
251. * @return Doctrine_Query_Part
252. */
253. public function getParser($name)
254. {
255. if ( ! isset($this->_parsers[$name])) {
256. $class = 'Doctrine_Query_' . ucwords(strtolower($name));
258. Doctrine::autoload($class);
260. if ( ! class_exists($class)) {
261. throw new Doctrine_Query_Exception('Unknown parser ' . $name);
262. }
264. $this->_parsers[$name] = new $class($this);
265. }
267. return $this->_parsers[$name];
268. }
269. /**
270. * parseQueryPart
271. * parses given DQL query part
272. *
273. * @param string $queryPartName the name of the query part
274. * @param string $queryPart query part to be parsed
275. * @param boolean $append whether or not to append the query part to its stack
276. * if false is given, this method will overwrite
277. * the given query part stack with $queryPart
278. * @return Doctrine_Query this object
279. */
280. public function parseQueryPart($queryPartName, $queryPart, $append = false)
281. {
282. if ($this->_state === self::STATE_LOCKED) {
283. throw new Doctrine_Query_Exception('This query object is locked. No query parts can be manipulated.');
284. }
286. // sanity check
287. if ($queryPart === '' || $queryPart === null) {
288. throw new Doctrine_Query_Exception('Empty ' . $queryPartName . ' part given.');
289. }
291. // add query part to the dql part array
292. if ($append) {
293. $this->_dqlParts[$queryPartName][] = $queryPart;
294. } else {
295. $this->_dqlParts[$queryPartName] = array($queryPart);
296. }
298. if ($this->_state === self::STATE_DIRECT) {
299. $parser = $this->getParser($queryPartName);
301. $sql = $parser->parse($queryPart);
303. if (isset($sql)) {
304. if ($append) {
305. $this->addQueryPart($queryPartName, $sql);
306. } else {
307. $this->setQueryPart($queryPartName, $sql);
308. }
309. }
310. }
312. $this->_state = Doctrine_Query::STATE_DIRTY;
314. return $this;
315. }
316. /**
317. * getDqlPart
318. * returns the given DQL query part
319. *
320. * @param string $queryPart the name of the query part
321. * @return string the DQL query part
322. */
323. public function getDqlPart($queryPart)
324. {
325. if ( ! isset($this->_dqlParts[$queryPart])) {
326. throw new Doctrine_Query_Exception('Unknown query part ' . $queryPart);
327. }
329. return $this->_dqlParts[$queryPart];
330. }
331. /**
332. * getDql
333. * returns the DQL query associated with this object
334. *
335. * the query is built from $_dqlParts
336. *
337. * @return string the DQL query
338. */
339. public function getDql()
340. {
341. $q = '';
342. $q .= ( ! empty($this->_dqlParts['select']))? 'SELECT ' . implode(', ', $this->_dqlParts['select']) : '';
343. $q .= ( ! empty($this->_dqlParts['from']))? ' FROM ' . implode(' ', $this->_dqlParts['from']) : '';
344. $q .= ( ! empty($this->_dqlParts['where']))? ' WHERE ' . implode(' AND ', $this->_dqlParts['where']) : '';
345. $q .= ( ! empty($this->_dqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_dqlParts['groupby']) : '';
346. $q .= ( ! empty($this->_dqlParts['having']))? ' HAVING ' . implode(' AND ', $this->_dqlParts['having']) : '';
347. $q .= ( ! empty($this->_dqlParts['orderby']))? ' ORDER BY ' . implode(', ', $this->_dqlParts['orderby']) : '';
348. $q .= ( ! empty($this->_dqlParts['limit']))? ' LIMIT ' . implode(' ', $this->_dqlParts['limit']) : '';
349. $q .= ( ! empty($this->_dqlParts['offset']))? ' OFFSET ' . implode(' ', $this->_dqlParts['offset']) : '';
351. return $q;
352. }
353. /**
354. * processPendingFields
355. * the fields in SELECT clause cannot be parsed until the components
356. * in FROM clause are parsed, hence this method is called everytime a
357. * specific component is being parsed.
358. *
359. * @throws Doctrine_Query_Exception if unknown component alias has been given
360. * @param string $componentAlias the alias of the component
361. * @return void
362. */
363. public function processPendingFields($componentAlias)
364. {
365. $tableAlias = $this->getTableAlias($componentAlias);
366. $table = $this->_aliasMap[$componentAlias]['table'];
368. if (isset($this->pendingFields[$componentAlias])) {
369. $fields = $this->pendingFields[$componentAlias];
371. // check for wildcards
372. if (in_array('*', $fields)) {
373. $fields = $table->getColumnNames();
374. } else {
375. // only auto-add the primary key fields if this query object is not
376. // a subquery of another query object
377. if ( ! $this->isSubquery) {
378. $fields = array_unique(array_merge($table->getPrimaryKeys(), $fields));
379. }
380. }
381. }
382. foreach ($fields as $name) {
383. $name = $table->getColumnName($name);
385. $this->parts['select'][] = $this->_conn->quoteIdentifier($tableAlias . '.' . $name)
386. . ' AS '
387. . $this->_conn->quoteIdentifier($tableAlias . '__' . $name);
388. }
390. $this->neededTables[] = $tableAlias;
392. }
393. /**
394. * parseSelect
395. * parses the query select part and
396. * adds selected fields to pendingFields array
397. *
398. * @param string $dql
399. */
400. public function parseSelect($dql)
401. {
402. $refs = Doctrine_Tokenizer::bracketExplode($dql, ',');
404. $pos = strpos(trim($refs[0]), ' ');
405. $first = substr($refs[0], 0, $pos);
407. if ($first === 'DISTINCT') {
408. $this->parts['distinct'] = true;
410. $refs[0] = substr($refs[0], ++$pos);
411. }
413. foreach ($refs as $reference) {
414. $reference = trim($reference);
415. if (strpos($reference, '(') !== false) {
416. if (substr($reference, 0, 1) === '(') {
417. // subselect found in SELECT part
418. $this->parseSubselect($reference);
419. } else {
420. $this->parseAggregateFunction($reference);
421. }
422. } else {
425. $e = explode('.', $reference);
426. if (count($e) > 2) {
427. $this->pendingFields[] = $reference;
428. } else {
429. $this->pendingFields[$e[0]][] = $e[1];
430. }
431. }
432. }
433. }
434. /**
435. * parseSubselect
436. *
437. * parses the subquery found in DQL SELECT part and adds the
438. * parsed form into $pendingSubqueries stack
439. *
440. * @param string $reference
441. * @return void
442. */
443. public function parseSubselect($reference)
444. {
445. $e = Doctrine_Tokenizer::bracketExplode($reference, ' ');
446. $alias = $e[1];
448. if (count($e) > 2) {
449. if (strtoupper($e[1]) !== 'AS') {
450. throw new Doctrine_Query_Exception('Syntax error near: ' . $reference);
451. }
452. $alias = $e[2];
453. }
455. $subquery = substr($e[0], 1, -1);
457. $this->pendingSubqueries[] = array($subquery, $alias);
458. }
459. /**
460. * parseClause
461. * parses given DQL clause
462. *
463. * this method handles five tasks:
464. *
465. * 1. Converts all DQL functions to their native SQL equivalents
466. * 2. Converts all component references to their table alias equivalents
467. * 3. Converts all column aliases to actual column names
468. * 4. Quotes all identifiers
469. * 5. Parses nested clauses and subqueries recursively
470. *
471. * @return string SQL string
472. */
473. public function parseClause($clause)
474. {
475. $terms = Doctrine_Tokenizer::clauseExplode($clause, array(' ', '+', '-', '*', '/'));
477. $str = '';
478. foreach ($terms as $term) {
479. $pos = strpos($term[0], '(');
481. if ($pos !== false) {
482. $name = substr($term[0], 0, $pos);
483. if ($name !== '') {
484. $argStr = substr($term[0], ($pos + 1), -1);
486. $args = array();
487. // parse args
489. foreach (Doctrine_Tokenizer::sqlExplode($argStr, ',') as $expr) {
490. $args[] = $this->parseClause($expr);
491. }
493. // convert DQL function to its RDBMS specific equivalent
494. try {
495. $expr = call_user_func_array(array($this->_conn->expression, $name), $args);
496. } catch(Doctrine_Expression_Exception $e) {
497. throw new Doctrine_Query_Exception('Unknown function ' . $func . '.');
498. }
499. $term[0] = $expr;
500. } else {
501. $trimmed = trim(Doctrine_Tokenizer::bracketTrim($term[0]));
503. // check for possible subqueries
504. if (substr($trimmed, 0, 4) == 'FROM' || substr($trimmed, 0, 6) == 'SELECT') {
505. // parse subquery
506. $trimmed = $this->createSubquery()->parseQuery($trimmed)->getQuery();
507. } else {
508. // parse normal clause
509. $trimmed = $this->parseClause($trimmed);
510. }
512. $term[0] = '(' . $trimmed . ')';
513. }
514. } else {
515. if (substr($term[0], 0, 1) !== "'" && substr($term[0], -1) !== "'") {
516. if (strpos($term[0], '.') !== false) {
517. if ( ! is_numeric($term[0])) {
518. $e = explode('.', $term[0]);
520. $field = array_pop($e);
521. $componentAlias = implode('.', $e);
523. // check the existence of the component alias
524. if ( ! isset($this->_aliasMap[$componentAlias])) {
525. throw new Doctrine_Query_Exception('Unknown component alias ' . $componentAlias);
526. }
528. $table = $this->_aliasMap[$componentAlias]['table'];
530. // get the actual field name from alias
531. $field = $table->getColumnName($field);
533. // check column existence
534. if ( ! $table->hasColumn($field)) {
535. throw new Doctrine_Query_Exception('Unknown column ' . $field);
536. }
538. $tableAlias = $this->getTableAlias($componentAlias);
540. // build sql expression
541. $term[0] = $this->_conn->quoteIdentifier($tableAlias)
542. . '.'
543. . $this->_conn->quoteIdentifier($field);
544. }
545. }
546. }
547. }
549. $str .= $term[0] . $term[1];
550. }
551. return $str;
552. }
553. /**
554. * parseAggregateFunction
555. * parses an aggregate function and returns the parsed form
556. *
557. * @see Doctrine_Expression
558. * @param string $expr DQL aggregate function
559. * @throws Doctrine_Query_Exception if unknown aggregate function given
560. * @return array parsed form of given function
561. */
562. public function parseAggregateFunction($expr, $nestedCall = false)
563. {
564. $e = Doctrine_Tokenizer::bracketExplode($expr, ' ');
565. $func = $e[0];
567. $pos = strpos($func, '(');
568. if ($pos === false) {
569. return $expr;
570. }
572. // get the name of the function
573. $name = substr($func, 0, $pos);
574. $argStr = substr($func, ($pos + 1), -1);
576. $args = array();
577. // parse args
578. foreach (Doctrine_Tokenizer::bracketExplode($argStr, ',') as $expr) {
579. $args[] = $this->parseAggregateFunction($expr, true);
580. }
582. // convert DQL function to its RDBMS specific equivalent
583. try {
584. $expr = call_user_func_array(array($this->_conn->expression, $name), $args);
585. } catch(Doctrine_Expression_Exception $e) {
586. throw new Doctrine_Query_Exception('Unknown function ' . $func . '.');
587. }
589. if ( ! $nestedCall) {
590. // try to find all component references
591. preg_match_all("/[a-z0-9_]+\.[a-z0-9_]+[\.[a-z0-9]+]*/i", $argStr, $m);
593. if (isset($e[1])) {
594. if (strtoupper($e[1]) === 'AS') {
595. if ( ! isset($e[2])) {
596. throw new Doctrine_Query_Exception('Missing aggregate function alias.');
597. }
598. $alias = $e[2];
599. } else {
600. $alias = $e[1];
601. }
602. } else {
603. $alias = substr($expr, 0, strpos($expr, '('));
604. }
606. $this->pendingAggregates[] = array($expr, $m[0], $alias);
607. }
609. return $expr;
610. }
611. /**
612. * processPendingSubqueries
613. * processes pending subqueries
614. *
615. * subqueries can only be processed when the query is fully constructed
616. * since some subqueries may be correlated
617. *
618. * @return void
619. */
620. public function processPendingSubqueries()
621. {
622. foreach ($this->pendingSubqueries as $value) {
623. list($dql, $alias) = $value;
625. $subquery = $this->createSubquery();
627. $sql = $subquery->parseQuery($dql, false)->getQuery();
629. reset($this->_aliasMap);
630. $componentAlias = key($this->_aliasMap);
631. $tableAlias = $this->getTableAlias($componentAlias);
633. $sqlAlias = $tableAlias . '__' . count($this->aggregateMap);
635. $this->parts['select'][] = '(' . $sql . ') AS ' . $this->_conn->quoteIdentifier($sqlAlias);
637. $this->aggregateMap[$alias] = $sqlAlias;
638. $this->_aliasMap[$componentAlias]['agg'][] = $alias;
639. }
640. $this->pendingSubqueries = array();
641. }
642. /**
643. * processPendingAggregates
644. * processes pending aggregate values for given component alias
645. *
646. * @return void
647. */
648. public function processPendingAggregates()
649. {
650. // iterate trhough all aggregates
651. foreach ($this->pendingAggregates as $aggregate) {
652. list ($expression, $components, $alias) = $aggregate;
654. $tableAliases = array();
656. // iterate through the component references within the aggregate function
657. if ( ! empty ($components)) {
658. foreach ($components as $component) {
660. if (is_numeric($component)) {
661. continue;
662. }
664. $e = explode('.', $component);
666. $field = array_pop($e);
667. $componentAlias = implode('.', $e);
669. // check the existence of the component alias
670. if ( ! isset($this->_aliasMap[$componentAlias])) {
671. throw new Doctrine_Query_Exception('Unknown component alias ' . $componentAlias);
672. }
674. $table = $this->_aliasMap[$componentAlias]['table'];
676. $field = $table->getColumnName($field);
678. // check column existence
679. if ( ! $table->hasColumn($field)) {
680. throw new Doctrine_Query_Exception('Unknown column ' . $field);
681. }
683. $tableAlias = $this->getTableAlias($componentAlias);
685. $tableAliases[$tableAlias] = true;
687. // build sql expression
689. $identifier = $this->_conn->quoteIdentifier($tableAlias . '.' . $field);
690. $expression = str_replace($component, $identifier, $expression);
691. }
692. }
694. if (count($tableAliases) !== 1) {
695. $componentAlias = reset($this->tableAliases);
696. $tableAlias = key($this->tableAliases);
697. }
699. $index = count($this->aggregateMap);
700. $sqlAlias = $this->_conn->quoteIdentifier($tableAlias . '__' . $index);
702. $this->parts['select'][] = $expression . ' AS ' . $sqlAlias;
704. $this->aggregateMap[$alias] = $sqlAlias;
705. $this->_expressionMap[$alias][0] = $expression;
707. $this->_aliasMap[$componentAlias]['agg'][$index] = $alias;
709. $this->neededTables[] = $tableAlias;
710. }
711. // reset the state
712. $this->pendingAggregates = array();
713. }
714. /**
715. * getQueryBase
716. * returns the base of the generated sql query
717. * On mysql driver special strategy has to be used for DELETE statements
718. *
719. * @return string the base of the generated sql query
720. */
721. public function getQueryBase()
722. {
723. switch ($this->type) {
724. case self::DELETE:
725. $q = 'DELETE FROM ';
726. break;
727. case self::UPDATE:
728. $q = 'UPDATE ';
729. break;
730. case self::SELECT:
731. $distinct = ($this->parts['distinct']) ? 'DISTINCT ' : '';
733. $q = 'SELECT ' . $distinct . implode(', ', $this->parts['select']) . ' FROM ';
734. break;
735. }
736. return $q;
737. }
738. /**
739. * buildFromPart
740. * builds the from part of the query and returns it
741. *
742. * @return string the query sql from part
743. */
744. public function buildFromPart()
745. {
746. $q = '';
747. foreach ($this->parts['from'] as $k => $part) {
748. if ($k === 0) {
749. $q .= $part;
750. continue;
751. }
752. // preserve LEFT JOINs only if needed
754. if (substr($part, 0, 9) === 'LEFT JOIN') {
755. $e = explode(' ', $part);
757. $aliases = array_merge($this->subqueryAliases,
758. array_keys($this->neededTables));
760. if( ! in_array($e[3], $aliases) &&
761. ! in_array($e[2], $aliases) &&
763. ! empty($this->pendingFields)) {
764. continue;
765. }
767. }
769. if (isset($this->_pendingJoinConditions[$k])) {
770. $parser = new Doctrine_Query_JoinCondition($this);
772. if (strpos($part, ' ON ') !== false) {
773. $part .= ' AND ';
774. } else {
775. $part .= ' ON ';
776. }
777. $part .= $parser->parse($this->_pendingJoinConditions[$k]);
779. unset($this->_pendingJoinConditions[$k]);
780. }
782. $q .= ' ' . $part;
784. $this->parts['from'][$k] = $part;
785. }
786. return $q;
787. }
788. /**
789. * preQuery
790. *
791. * Empty template method to provide Query subclasses with the possibility
792. * to hook into the query building procedure, doing any custom / specialized
793. * query building procedures that are neccessary.
794. *
795. * @return void
796. */
797. public function preQuery()
798. {
800. }
801. /**
802. * postQuery
803. *
804. * Empty template method to provide Query subclasses with the possibility
805. * to hook into the query building procedure, doing any custom / specialized
806. * post query procedures (for example logging) that are neccessary.
807. *
808. * @return void
809. */
810. public function postQuery()
811. {
813. }
814. /**
815. * builds the sql query from the given parameters and applies things such as
816. * column aggregation inheritance and limit subqueries if needed
817. *
818. * @param array $params an array of prepared statement params (needed only in mysql driver
819. * when limit subquery algorithm is used)
820. * @return string the built sql query
821. */
822. public function getQuery($params = array())
823. {
824. if ($this->_state !== self::STATE_DIRTY) {
825. return $this->_sql;
826. }
828. $parts = $this->_dqlParts;
830. // reset the state
831. if ( ! $this->isSubquery()) {
832. $this->_aliasMap = array();
833. $this->pendingAggregates = array();
834. $this->aggregateMap = array();
835. }
836. $this->reset();
838. // parse the DQL parts
839. foreach ($this->_dqlParts as $queryPartName => $queryParts) {
841. $this->removeQueryPart($queryPartName);
843. if (is_array($queryParts) && ! empty($queryParts)) {
845. foreach ($queryParts as $queryPart) {
846. $parser = $this->getParser($queryPartName);
849. $sql = $parser->parse($queryPart);
851. if (isset($sql)) {
852. if ($queryPartName == 'limit' ||
853. $queryPartName == 'offset') {
855. $this->setQueryPart($queryPartName, $sql);
856. } else {
857. $this->addQueryPart($queryPartName, $sql);
858. }
859. }
860. }
861. }
862. }
863. $params = $this->convertEnums($params);
865. $this->_state = self::STATE_DIRECT;
867. // invoke the preQuery hook
868. $this->preQuery();
869. $this->_state = self::STATE_CLEAN;
871. $this->_dqlParts = $parts;
873. if (empty($this->parts['from'])) {
874. return false;
875. }
877. $needsSubQuery = false;
878. $subquery = '';
879. $map = reset($this->_aliasMap);
880. $table = $map['table'];
881. $rootAlias = key($this->_aliasMap);
883. if ( ! empty($this->parts['limit']) && $this->needsSubquery && $table->getAttribute(Doctrine::ATTR_QUERY_LIMIT) == Doctrine::LIMIT_RECORDS) {
884. $this->isLimitSubqueryUsed = true;
885. $needsSubQuery = true;
886. }
888. // process all pending SELECT part subqueries
889. $this->processPendingSubqueries();
890. $this->processPendingAggregates();
892. // build the basic query
894. $q = $this->getQueryBase();
895. $q .= $this->buildFromPart();
897. if ( ! empty($this->parts['set'])) {
898. $q .= ' SET ' . implode(', ', $this->parts['set']);
899. }
902. $string = $this->applyInheritance();
904. // apply inheritance to WHERE part
905. if ( ! empty($string)) {
906. $this->parts['where'][] = '(' . $string . ')';
907. }
910. $modifyLimit = true;
911. if ( ! empty($this->parts['limit']) || ! empty($this->parts['offset'])) {
913. if ($needsSubQuery) {
914. $subquery = $this->getLimitSubquery();
917. switch (strtolower($this->_conn->getName())) {
918. case 'mysql':
919. // mysql doesn't support LIMIT in subqueries
920. $list = $this->_conn->execute($subquery, $params)->fetchAll(Doctrine::FETCH_COLUMN);
921. $subquery = implode(', ', array_map(array($this->_conn, 'quote'), $list));
922. break;
923. case 'pgsql':
924. // pgsql needs special nested LIMIT subquery
925. $subquery = 'SELECT doctrine_subquery_alias.' . $table->getIdentifier(). ' FROM (' . $subquery . ') AS doctrine_subquery_alias';
926. break;
927. }
929. $field = $this->getTableAlias($rootAlias) . '.' . $table->getIdentifier();
931. // only append the subquery if it actually contains something
932. if ($subquery !== '') {
933. array_unshift($this->parts['where'], $this->_conn->quoteIdentifier($field) . ' IN (' . $subquery . ')');
934. }
936. $modifyLimit = false;
937. }
938. }
940. $q .= ( ! empty($this->parts['where']))? ' WHERE ' . implode(' AND ', $this->parts['where']) : '';
941. $q .= ( ! empty($this->parts['groupby']))? ' GROUP BY ' . implode(', ', $this->parts['groupby']) : '';
942. $q .= ( ! empty($this->parts['having']))? ' HAVING ' . implode(' AND ', $this->parts['having']): '';
943. $q .= ( ! empty($this->parts['orderby']))? ' ORDER BY ' . implode(', ', $this->parts['orderby']) : '';
945. if ($modifyLimit) {
947. $q = $this->_conn->modifyLimitQuery($q, $this->parts['limit'], $this->parts['offset']);
948. }
950. // return to the previous state
951. if ( ! empty($string)) {
952. array_pop($this->parts['where']);
953. }
954. if ($needsSubQuery) {
955. array_shift($this->parts['where']);
956. }
957. $this->_sql = $q;
959. return $q;
960. }
961. /**
962. * getLimitSubquery
963. * this is method is used by the record limit algorithm
964. *
965. * when fetching one-to-many, many-to-many associated data with LIMIT clause
966. * an additional subquery is needed for limiting the number of returned records instead
967. * of limiting the number of sql result set rows
968. *
969. * @return string the limit subquery
970. */
971. public function getLimitSubquery()
972. {
973. $map = reset($this->_aliasMap);
974. $table = $map['table'];
975. $componentAlias = key($this->_aliasMap);
977. // get short alias
978. $alias = $this->getTableAlias($componentAlias);
979. $primaryKey = $alias . '.' . $table->getIdentifier();
981. // initialize the base of the subquery
982. $subquery = 'SELECT DISTINCT ' . $this->_conn->quoteIdentifier($primaryKey);
984. $driverName = $this->_conn->getAttribute(Doctrine::ATTR_DRIVER_NAME);
987. // pgsql needs the order by fields to be preserved in select clause
988. if ($driverName == 'pgsql') {
989. foreach ($this->parts['orderby'] as $part) {
990. $part = trim($part);
991. $e = Doctrine_Tokenizer::bracketExplode($part, ' ');
992. $part = trim($e[0]);
994. if (strpos($part, '.') === false) {
995. continue;
996. }
998. // don't add functions
999. if (strpos($part, '(') !== false) {
1000. continue;
1001. }
1003. // don't add primarykey column (its already in the select clause)
1004. if ($part !== $primaryKey) {
1005. $subquery .= ', ' . $part;
1006. }
1007. }
1008. }
1010. if ($driverName == 'mysql' || $driverName == 'pgsql') {
1011. foreach ($this->_expressionMap as $dqlAlias => $expr) {
1012. if (isset($expr[1])) {
1013. $subquery .= ', ' . $expr[0] . ' AS ' . $this->aggregateMap[$dqlAlias];
1014. }
1015. }
1016. }
1019. $subquery .= ' FROM';
1022. foreach ($this->parts['from'] as $part) {
1023. // preserve LEFT JOINs only if needed
1024. if (substr($part, 0, 9) === 'LEFT JOIN') {
1025. $e = explode(' ', $part);
1027. if (empty($this->parts['orderby']) && empty($this->parts['where'])) {
1028. continue;
1029. }
1030. }
1032. $subquery .= ' ' . $part;
1033. }
1035. // all conditions must be preserved in subquery
1036. $subquery .= ( ! empty($this->parts['where']))? ' WHERE ' . implode(' AND ', $this->parts['where']) : '';
1037. $subquery .= ( ! empty($this->parts['groupby']))? ' GROUP BY ' . implode(', ', $this->parts['groupby']) : '';
1038. $subquery .= ( ! empty($this->parts['having']))? ' HAVING ' . implode(' AND ', $this->parts['having']) : '';
1040. $subquery .= ( ! empty($this->parts['orderby']))? ' ORDER BY ' . implode(', ', $this->parts['orderby']) : '';
1042. // add driver specific limit clause
1043. $subquery = $this->_conn->modifyLimitQuery($subquery, $this->parts['limit'], $this->parts['offset']);
1045. $parts = Doctrine_Tokenizer::quoteExplode($subquery, ' ', "'", "'");
1047. foreach ($parts as $k => $part) {
1048. if (strpos($part, ' ') !== false) {
1049. continue;
1050. }
1052. $part = trim($part, "\"'`");
1054. if ($this->hasTableAlias($part)) {
1055. $parts[$k] = $this->_conn->quoteIdentifier($this->generateNewTableAlias($part));
1056. continue;
1057. }
1059. if (strpos($part, '.') === false) {
1060. continue;
1061. }
1062. preg_match_all("/[a-zA-Z0-9_]+\.[a-z0-9_]+/i", $part, $m);
1064. foreach ($m[0] as $match) {
1065. $e = explode('.', $match);
1066. $e[0] = $this->generateNewTableAlias($e[0]);
1068. $parts[$k] = str_replace($match, implode('.', $e), $parts[$k]);
1069. }
1070. }
1072. if ($driverName == 'mysql' || $driverName == 'pgsql') {
1073. foreach ($parts as $k => $part) {
1074. if (strpos($part, "'") !== false) {
1075. continue;
1076. }
1077. if (strpos($part, '__') == false) {
1078. continue;
1079. }
1081. preg_match_all("/[a-zA-Z0-9_]+\_\_[a-z0-9_]+/i", $part, $m);
1083. foreach ($m[0] as $match) {
1084. $e = explode('__', $match);
1085. $e[0] = $this->generateNewTableAlias($e[0]);
1087. $parts[$k] = str_replace($match, implode('__', $e), $parts[$k]);
1088. }
1089. }
1090. }
1092. $subquery = implode(' ', $parts);
1093. return $subquery;
1094. }
1095. /**
1096. * tokenizeQuery
1097. * splits the given dql query into an array where keys
1098. * represent different query part names and values are
1099. * arrays splitted using sqlExplode method
1100. *
1101. * example:
1102. *
1103. * parameter:
1104. * $query = "SELECT u.* FROM User u WHERE u.name LIKE ?"
1105. * returns:
1106. * array('select' => array('u.*'),
1107. * 'from' => array('User', 'u'),
1108. * 'where' => array('u.name', 'LIKE', '?'))
1109. *
1110. * @param string $query DQL query
1111. * @throws Doctrine_Query_Exception if some generic parsing error occurs
1112. * @return array an array containing the query string parts
1113. */
1114. public function tokenizeQuery($query)
1115. {
1116. $e = Doctrine_Tokenizer::sqlExplode($query, ' ');
1118. foreach ($e as $k=>$part) {
1119. $part = trim($part);
1120. switch (strtolower($part)) {
1121. case 'delete':
1122. case 'update':
1123. case 'select':
1124. case 'set':
1125. case 'from':
1126. case 'where':
1127. case 'limit':
1128. case 'offset':
1129. case 'having':
1130. $p = $part;
1131. $parts[$part] = array();
1132. break;
1133. case 'order':
1134. case 'group':
1135. $i = ($k + 1);
1136. if (isset($e[$i]) && strtolower($e[$i]) === 'by') {
1137. $p = $part;
1138. $parts[$part] = array();
1139. } else {
1140. $parts[$p][] = $part;
1141. }
1142. break;
1143. case 'by':
1144. continue;
1145. default:
1146. if ( ! isset($p))
1147. throw new Doctrine_Query_Exception("Couldn't parse query.");
1149. $parts[$p][] = $part;
1150. }
1151. }
1152. return $parts;
1153. }
1154. /**
1155. * DQL PARSER
1156. * parses a DQL query
1157. * first splits the query in parts and then uses individual
1158. * parsers for each part
1159. *
1160. * @param string $query DQL query
1161. * @param boolean $clear whether or not to clear the aliases
1162. * @throws Doctrine_Query_Exception if some generic parsing error occurs
1163. * @return Doctrine_Query
1164. */
1165. public function parseQuery($query, $clear = true)
1166. {
1167. if ($clear) {
1168. $this->clear();
1169. }
1171. $query = trim($query);
1172. $query = str_replace("\n", ' ', $query);
1173. $query = str_replace("\r", ' ', $query);
1175. $parts = $this->tokenizeQuery($query);
1177. foreach($parts as $k => $part) {
1178. $part = implode(' ', $part);
1179. $k = strtolower($k);
1180. switch ($k) {
1181. case 'create':
1182. $this->type = self::CREATE;
1183. break;
1184. case 'insert':
1185. $this->type = self::INSERT;
1186. break;
1187. case 'delete':
1188. $this->type = self::DELETE;
1189. break;
1190. case 'select':
1191. $this->type = self::SELECT;
1192. $this->parseQueryPart($k, $part);
1193. break;
1194. case 'update':
1195. $this->type = self::UPDATE;
1196. $k = 'from';
1197. case 'from':
1198. $this->parseQueryPart($k, $part);
1199. break;
1200. case 'set':
1201. $this->parseQueryPart($k, $part, true);
1202. break;
1203. case 'group':
1204. case 'order':
1205. $k .= 'by';
1206. case 'where':
1207. case 'having':
1208. case 'limit':
1209. case 'offset':
1210. $this->parseQueryPart($k, $part);
1211. break;
1212. }
1213. }
1215. return $this;
1216. }
1218. public function load($path, $loadFields = true)
1219. {
1220. // parse custom join conditions
1221. $e = explode(' ON ', $path);
1223. $joinCondition = '';
1225. if (count($e) > 1) {
1226. $joinCondition = $e[1];
1227. $overrideJoin = true;
1228. $path = $e[0];
1229. } else {
1230. $e = explode(' WITH ', $path);
1232. if (count($e) > 1) {
1233. $joinCondition = $e[1];
1234. $path = $e[0];
1235. }
1236. $overrideJoin = false;
1237. }
1239. $tmp = explode(' ', $path);
1240. $componentAlias = $originalAlias = (count($tmp) > 1) ? end($tmp) : null;
1242. $e = preg_split("/[.:]/", $tmp[0], -1);
1244. $fullPath = $tmp[0];
1245. $prevPath = '';
1246. $fullLength = strlen($fullPath);
1248. if (isset($this->_aliasMap[$e[0]])) {
1249. $table = $this->_aliasMap[$e[0]]['table'];
1250. $componentAlias = $e[0];
1252. $prevPath = $parent = array_shift($e);
1253. }
1255. foreach ($e as $key => $name) {
1256. // get length of the previous path
1257. $length = strlen($prevPath);
1259. // build the current component path
1260. $prevPath = ($prevPath) ? $prevPath . '.' . $name : $name;
1262. $delimeter = substr($fullPath, $length, 1);
1264. // if an alias is not given use the current path as an alias identifier
1265. if (strlen($prevPath) === $fullLength && isset($originalAlias)) {
1266. $componentAlias = $originalAlias;
1267. } else {
1268. $componentAlias = $prevPath;
1269. }
1271. // if the current alias already exists, skip it
1272. if (isset($this->_aliasMap[$componentAlias])) {
1273. continue;
1274. }
1276. if ( ! isset($table)) {
1277. // process the root of the path
1279. $table = $this->loadRoot($name, $componentAlias);
1280. } else {
1281. $join = ($delimeter == ':') ? 'INNER JOIN ' : 'LEFT JOIN ';
1283. $relation = $table->getRelation($name);
1284. $localTable = $table;
1286. $table = $relation->getTable();
1287. $this->_aliasMap[$componentAlias] = array('table' => $table,
1288. 'parent' => $parent,
1289. 'relation' => $relation);
1290. if ( ! $relation->isOneToOne()) {
1291. $this->needsSubquery = true;
1292. }
1294. $localAlias = $this->getTableAlias($parent, $table->getTableName());
1295. $foreignAlias = $this->getTableAlias($componentAlias, $relation->getTable()->getTableName());
1296. $localSql = $this->_conn->quoteIdentifier($table->getTableName())
1297. . ' '
1298. . $this->_conn->quoteIdentifier($localAlias);
1300. $foreignSql = $this->_conn->quoteIdentifier($relation->getTable()->getTableName())
1301. . ' '
1302. . $this->_conn->quoteIdentifier($foreignAlias);
1304. $map = $relation->getTable()->inheritanceMap;
1306. if ( ! $loadFields || ! empty($map) || $joinCondition) {
1307. $this->subqueryAliases[] = $foreignAlias;
1308. }
1310. if ($relation instanceof Doctrine_Relation_Association) {
1311. $asf = $relation->getAssociationTable();
1313. $assocTableName = $asf->getTableName();
1315. if( ! $loadFields || ! empty($map) || $joinCondition) {
1316. $this->subqueryAliases[] = $assocTableName;
1317. }
1319. $assocPath = $prevPath . '.' . $asf->getComponentName();
1321. $assocAlias = $this->getTableAlias($assocPath, $asf->getTableName());
1323. $queryPart = $join . $assocTableName . ' ' . $assocAlias;
1325. $queryPart .= ' ON ' . $localAlias
1326. . '.'
1327. . $localTable->getIdentifier()
1328. . ' = '
1329. . $assocAlias . '.' . $relation->getLocal();
1331. if ($relation->isEqual()) {
1332. // equal nest relation needs additional condition
1333. $queryPart .= ' OR ' . $localAlias
1334. . '.'
1335. . $table->getColumnName($table->getIdentifier())
1336. . ' = '
1337. . $assocAlias . '.' . $relation->getForeign();
1339. }
1341. $this->parts['from'][] = $queryPart;
1343. $queryPart = $join . $foreignSql;
1345. if ( ! $overrideJoin) {
1346. $queryPart .= ' ON ';
1348. if ($relation->isEqual()) {
1349. $queryPart .= '(';
1350. }
1352. $queryPart .= $this->_conn->quoteIdentifier($foreignAlias . '.' . $relation->getTable()->getIdentifier())
1353. . ' = '
1354. . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getForeign());
1356. if ($relation->isEqual()) {
1357. $queryPart .= ' OR '
1358. . $this->_conn->quoteIdentifier($foreignAlias . '.' . $table->getColumnName($table->getIdentifier()))
1359. . ' = '
1360. . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getLocal())
1361. . ') AND '
1362. . $this->_conn->quoteIdentifier($foreignAlias . '.' . $table->getIdentifier())
1363. . ' != '
1364. . $this->_conn->quoteIdentifier($localAlias . '.' . $table->getIdentifier());
1365. }
1366. }
1367. } else {
1369. $queryPart = $join . $foreignSql;
1371. if ( ! $overrideJoin) {
1372. $queryPart .= ' ON '
1373. . $this->_conn->quoteIdentifier($localAlias . '.' . $relation->getLocal())
1374. . ' = '
1375. . $this->_conn->quoteIdentifier($foreignAlias . '.' . $relation->getForeign());
1376. }
1378. }
1379. $this->parts['from'][$componentAlias] = $queryPart;
1380. if ( ! empty($joinCondition)) {
1381. $this->_pendingJoinConditions[$componentAlias] = $joinCondition;
1382. }
1383. }
1384. if ($loadFields) {
1386. $restoreState = false;
1387. // load fields if necessary
1388. if ($loadFields && empty($this->pendingFields)
1389. && empty($this->pendingAggregates)
1390. && empty($this->pendingSubqueries)) {
1392. $this->pendingFields[$componentAlias] = array('*');
1394. $restoreState = true;
1395. }
1397. if(isset($this->pendingFields[$componentAlias])) {
1398. $this->processPendingFields($componentAlias);
1399. }
1401. if ($restoreState) {
1402. $this->pendingFields = array();
1403. $this->pendingAggregates = array();
1404. }
1405. }
1406. $parent = $prevPath;
1407. }
1409. return $this->_aliasMap[$componentAlias];
1410. }
1412. /**
1413. * loadRoot
1414. *
1415. * @param string $name
1416. * @param string $componentAlias
1417. */
1418. public function loadRoot($name, $componentAlias)
1419. {
1420. // get the connection for the component
1421. $this->_conn = Doctrine_Manager::getInstance()
1422. ->getConnectionForComponent($name);
1424. $table = $this->_conn->getTable($name);
1425. $tableName = $table->getTableName();
1427. // get the short alias for this table
1428. $tableAlias = $this->getTableAlias($componentAlias, $tableName);
1429. // quote table name
1430. $queryPart = $this->_conn->quoteIdentifier($tableName);
1432. if ($this->type === self::SELECT) {
1433. $queryPart .= ' ' . $this->_conn->quoteIdentifier($tableAlias);
1434. }
1436. $this->parts['from'][] = $queryPart;
1437. $this->tableAliases[$tableAlias] = $componentAlias;
1438. $this->_aliasMap[$componentAlias] = array('table' => $table);
1440. return $table;
1441. }
1442. /**
1443. * count
1444. * fetches the count of the query
1445. *
1446. * This method executes the main query without all the
1447. * selected fields, ORDER BY part, LIMIT part and OFFSET part.
1448. *
1449. * Example:
1450. * Main query:
1451. * SELECT u.*, p.phonenumber FROM User u
1452. * LEFT JOIN u.Phonenumber p
1453. * WHERE p.phonenumber = '123 123' LIMIT 10
1454. *
1455. * The modified DQL query:
1456. * SELECT COUNT(DISTINCT u.id) FROM User u
1457. * LEFT JOIN u.Phonenumber p
1458. * WHERE p.phonenumber = '123 123'
1459. *
1460. * @param array $params an array of prepared statement parameters
1461. * @return integer the count of this query
1462. */
1463. public function count($params = array())
1464. {
1465. $this->getQuery();
1467. // initialize temporary variables
1468. $where = $this->parts['where'];
1469. $having = $this->parts['having'];
1470. $groupby = $this->parts['groupby'];
1471. $map = reset($this->_aliasMap);
1472. $componentAlias = key($this->_aliasMap);
1473. $table = $map['table'];
1475. // build the query base
1476. $q = 'SELECT COUNT(DISTINCT ' . $this->getTableAlias($componentAlias)
1477. . '.' . implode(',', (array) $table->getIdentifier())
1478. . ') AS num_results';
1480. foreach ($this->parts['select'] as $field) {
1481. if (strpos($field, '(') !== false) {
1482. $q .= ', ' . $field;
1483. }
1484. }
1486. $q .= ' FROM ' . $this->buildFromPart();
1488. // append column aggregation inheritance (if needed)
1489. $string = $this->applyInheritance();
1491. if ( ! empty($string)) {
1492. $where[] = $string;
1493. }
1494. // append conditions
1495. $q .= ( ! empty($where)) ? ' WHERE ' . implode(' AND ', $where) : '';
1496. $q .= ( ! empty($groupby)) ? ' GROUP BY ' . implode(', ', $groupby) : '';
1497. $q .= ( ! empty($having)) ? ' HAVING ' . implode(' AND ', $having): '';
1499. if ( ! is_array($params)) {
1500. $params = array($params);
1501. }
1502. // append parameters
1503. $params = array_merge($this->_params, $params);
1505. $results = $this->getConnection()->fetchAll($q, $params);
1507. if (count($results) > 1) {
1508. $count = 0;
1509. foreach ($results as $result) {
1510. $count += $result['num_results'];
1511. }
1512. } else {
1513. $count = isset($results[0]) ? $results[0]['num_results']:0;
1514. }
1516. return (int) $count;
1517. }
1519. /**
1520. * query
1521. * query the database with DQL (Doctrine Query Language)
1522. *
1523. * @param string $query DQL query
1524. * @param array $params prepared statement parameters
1525. * @see Doctrine::FETCH_* constants
1526. * @return mixed
1527. */
1528. public function query($query, $params = array())
1529. {
1530. $this->parseQuery($query);
1532. return $this->execute($params);
1533. }
1535. public function copy(Doctrine_Query $query = null)
1536. {
1537. if ( ! $query) {
1538. $query = $this;
1539. }
1541. $new = new Doctrine_Query();
1542. $new->_dqlParts = $query->_dqlParts;
1543. $new->_hydrationMode = $query->_hydrationMode;
1545. return $new;
1546. }
1548. /**
1549. * Frees the resources used by the query object. It especially breaks a
1550. * cyclic reference between the query object and it's parsers. This enables
1551. * PHP's current GC to reclaim the memory.
1552. * This method can therefore be used to reduce memory usage when creating a lot
1553. * of query objects during a request.
1554. *
1555. * @return Doctrine_Query this object
1556. */
1557. public function free()
1558. {
1559. $this->reset();
1560. $this->_parsers = array();
1561. $this->_dqlParts = array();
1562. $this->_enumParams = array();
1563. }
1564. }