3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15 * This software consists of voluntary contributions made by many individuals
16 * and is licensed under the MIT license. For more information, see
17 * <http://www.doctrine-project.org>.
20 namespace Doctrine\ORM;
22 use Doctrine\Common\Collections\ArrayCollection;
24 use Doctrine\DBAL\LockMode;
26 use Doctrine\ORM\Query\Parser;
27 use Doctrine\ORM\Query\ParserResult;
28 use Doctrine\ORM\Query\QueryException;
31 * A Query object represents a DQL query.
34 * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
35 * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
36 * @author Roman Borschel <roman@code-factory.org>
38 final class Query extends AbstractQuery
41 * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts.
43 const STATE_CLEAN = 1;
45 * A query object is in state DIRTY when it has DQL parts that have not yet been
46 * parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart
49 const STATE_DIRTY = 2;
53 * The refresh hint turns any query into a refresh query with the result that
54 * any local changes in entities are overridden with the fetched values.
58 const HINT_REFRESH = 'doctrine.refresh';
62 * Internal hint: is set to the proxy entity that is currently triggered for loading
66 const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity';
69 * The forcePartialLoad query hint forces a particular query to return
73 * @todo Rename: HINT_OPTIMIZE
75 const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad';
77 * The includeMetaColumns query hint causes meta columns like foreign keys and
78 * discriminator columns to be selected and returned as part of the query result.
80 * This hint does only apply to non-object queries.
84 const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns';
87 * An array of class names that implement \Doctrine\ORM\Query\TreeWalker and
88 * are iterated and executed after the DQL has been parsed into an AST.
92 const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers';
95 * A string with a class name that implements \Doctrine\ORM\Query\TreeWalker
96 * and is used for generating the target SQL from any DQL AST tree.
100 const HINT_CUSTOM_OUTPUT_WALKER = 'doctrine.customOutputWalker';
102 //const HINT_READ_ONLY = 'doctrine.readOnly';
107 const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration';
112 const HINT_LOCK_MODE = 'doctrine.lockMode';
116 * @var integer $_state The current state of this query.
118 private $_state = self::STATE_CLEAN;
121 * @var string $_dql Cached DQL query.
123 private $_dql = null;
126 * @var \Doctrine\ORM\Query\ParserResult The parser result that holds DQL => SQL information.
128 private $_parserResult;
131 * @var integer The first result to return (the "offset").
133 private $_firstResult = null;
136 * @var integer The maximum number of results to return (the "limit").
138 private $_maxResults = null;
141 * @var CacheDriver The cache driver used for caching queries.
143 private $_queryCache;
146 * @var boolean Boolean value that indicates whether or not expire the query cache.
148 private $_expireQueryCache = false;
151 * @var int Query Cache lifetime.
153 private $_queryCacheTTL;
156 * @var boolean Whether to use a query cache, if available. Defaults to TRUE.
158 private $_useQueryCache = true;
161 * Initializes a new Query instance.
163 * @param \Doctrine\ORM\EntityManager $entityManager
165 /*public function __construct(EntityManager $entityManager)
167 parent::__construct($entityManager);
171 * Gets the SQL query/queries that correspond to this DQL query.
173 * @return mixed The built sql query or an array of all sql queries.
176 public function getSQL()
178 return $this->_parse()->getSQLExecutor()->getSQLStatements();
182 * Returns the corresponding AST for this DQL query.
184 * @return \Doctrine\ORM\Query\AST\SelectStatement |
185 * \Doctrine\ORM\Query\AST\UpdateStatement |
186 * \Doctrine\ORM\Query\AST\DeleteStatement
188 public function getAST()
190 $parser = new Parser($this);
192 return $parser->getAST();
196 * Parses the DQL query, if necessary, and stores the parser result.
198 * Note: Populates $this->_parserResult as a side-effect.
200 * @return \Doctrine\ORM\Query\ParserResult
202 private function _parse()
204 // Return previous parser result if the query and the filter collection are both clean
205 if ($this->_state === self::STATE_CLEAN && $this->_em->isFiltersStateClean()) {
206 return $this->_parserResult;
209 $this->_state = self::STATE_CLEAN;
211 // Check query cache.
212 if ( ! ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver()))) {
213 $parser = new Parser($this);
215 $this->_parserResult = $parser->parse();
217 return $this->_parserResult;
220 $hash = $this->_getQueryCacheId();
221 $cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash);
223 if ($cached instanceof ParserResult) {
225 $this->_parserResult = $cached;
227 return $this->_parserResult;
231 $parser = new Parser($this);
233 $this->_parserResult = $parser->parse();
235 $queryCache->save($hash, $this->_parserResult, $this->_queryCacheTTL);
237 return $this->_parserResult;
243 protected function _doExecute()
245 $executor = $this->_parse()->getSqlExecutor();
247 if ($this->_queryCacheProfile) {
248 $executor->setQueryCacheProfile($this->_queryCacheProfile);
251 // Prepare parameters
252 $paramMappings = $this->_parserResult->getParameterMappings();
254 if (count($paramMappings) != count($this->parameters)) {
255 throw QueryException::invalidParameterNumber();
258 list($sqlParams, $types) = $this->processParameterMappings($paramMappings);
260 if ($this->_resultSetMapping === null) {
261 $this->_resultSetMapping = $this->_parserResult->getResultSetMapping();
264 return $executor->execute($this->_em->getConnection(), $sqlParams, $types);
268 * Processes query parameter mappings
270 * @param array $paramMappings
273 private function processParameterMappings($paramMappings)
275 $sqlParams = array();
278 foreach ($this->parameters as $parameter) {
279 $key = $parameter->getName();
281 if ( ! isset($paramMappings[$key])) {
282 throw QueryException::unknownParameter($key);
285 $value = $this->processParameterValue($parameter->getValue());
286 $type = ($parameter->getValue() === $value)
287 ? $parameter->getType()
288 : Query\ParameterTypeInferer::inferType($value);
290 foreach ($paramMappings[$key] as $position) {
291 $types[$position] = $type;
294 $sqlPositions = $paramMappings[$key];
296 // optimized multi value sql positions away for now,
297 // they are not allowed in DQL anyways.
298 $value = array($value);
299 $countValue = count($value);
301 for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) {
302 $sqlParams[$sqlPositions[$i]] = $value[($i % $countValue)];
306 if (count($sqlParams) != count($types)) {
307 throw QueryException::parameterTypeMissmatch();
312 $sqlParams = array_values($sqlParams);
315 $types = array_values($types);
318 return array($sqlParams, $types);
322 * Defines a cache driver to be used for caching queries.
324 * @param Doctrine_Cache_Interface|null $driver Cache driver
325 * @return Query This query instance.
327 public function setQueryCacheDriver($queryCache)
329 $this->_queryCache = $queryCache;
335 * Defines whether the query should make use of a query cache, if available.
337 * @param boolean $bool
338 * @return @return Query This query instance.
340 public function useQueryCache($bool)
342 $this->_useQueryCache = $bool;
348 * Returns the cache driver used for query caching.
350 * @return CacheDriver The cache driver used for query caching or NULL, if
351 * this Query does not use query caching.
353 public function getQueryCacheDriver()
355 if ($this->_queryCache) {
356 return $this->_queryCache;
359 return $this->_em->getConfiguration()->getQueryCacheImpl();
363 * Defines how long the query cache will be active before expire.
365 * @param integer $timeToLive How long the cache entry is valid
366 * @return Query This query instance.
368 public function setQueryCacheLifetime($timeToLive)
370 if ($timeToLive !== null) {
371 $timeToLive = (int) $timeToLive;
374 $this->_queryCacheTTL = $timeToLive;
380 * Retrieves the lifetime of resultset cache.
384 public function getQueryCacheLifetime()
386 return $this->_queryCacheTTL;
390 * Defines if the query cache is active or not.
392 * @param boolean $expire Whether or not to force query cache expiration.
393 * @return Query This query instance.
395 public function expireQueryCache($expire = true)
397 $this->_expireQueryCache = $expire;
403 * Retrieves if the query cache is active or not.
407 public function getExpireQueryCache()
409 return $this->_expireQueryCache;
415 public function free()
420 $this->_state = self::STATE_CLEAN;
424 * Sets a DQL query string.
426 * @param string $dqlQuery DQL Query
427 * @return \Doctrine\ORM\AbstractQuery
429 public function setDQL($dqlQuery)
431 if ($dqlQuery !== null) {
432 $this->_dql = $dqlQuery;
433 $this->_state = self::STATE_DIRTY;
440 * Returns the DQL query that is represented by this query object.
442 * @return string DQL query
444 public function getDQL()
450 * Returns the state of this query object
451 * By default the type is Doctrine_ORM_Query_Abstract::STATE_CLEAN but if it appears any unprocessed DQL
452 * part, it is switched to Doctrine_ORM_Query_Abstract::STATE_DIRTY.
454 * @see AbstractQuery::STATE_CLEAN
455 * @see AbstractQuery::STATE_DIRTY
457 * @return integer Return the query state
459 public function getState()
461 return $this->_state;
465 * Method to check if an arbitrary piece of DQL exists
467 * @param string $dql Arbitrary piece of DQL to check for
470 public function contains($dql)
472 return stripos($this->getDQL(), $dql) === false ? false : true;
476 * Sets the position of the first result to retrieve (the "offset").
478 * @param integer $firstResult The first result to return.
479 * @return Query This query object.
481 public function setFirstResult($firstResult)
483 $this->_firstResult = $firstResult;
484 $this->_state = self::STATE_DIRTY;
490 * Gets the position of the first result the query object was set to retrieve (the "offset").
491 * Returns NULL if {@link setFirstResult} was not applied to this query.
493 * @return integer The position of the first result.
495 public function getFirstResult()
497 return $this->_firstResult;
501 * Sets the maximum number of results to retrieve (the "limit").
503 * @param integer $maxResults
504 * @return Query This query object.
506 public function setMaxResults($maxResults)
508 $this->_maxResults = $maxResults;
509 $this->_state = self::STATE_DIRTY;
515 * Gets the maximum number of results the query object was set to retrieve (the "limit").
516 * Returns NULL if {@link setMaxResults} was not applied to this query.
518 * @return integer Maximum number of results.
520 public function getMaxResults()
522 return $this->_maxResults;
526 * Executes the query and returns an IterableResult that can be used to incrementally
527 * iterated over the result.
529 * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters The query parameters.
530 * @param integer $hydrationMode The hydration mode to use.
531 * @return \Doctrine\ORM\Internal\Hydration\IterableResult
533 public function iterate($parameters = null, $hydrationMode = self::HYDRATE_OBJECT)
535 $this->setHint(self::HINT_INTERNAL_ITERATION, true);
537 return parent::iterate($parameters, $hydrationMode);
543 public function setHint($name, $value)
545 $this->_state = self::STATE_DIRTY;
547 return parent::setHint($name, $value);
553 public function setHydrationMode($hydrationMode)
555 $this->_state = self::STATE_DIRTY;
557 return parent::setHydrationMode($hydrationMode);
561 * Set the lock mode for this Query.
563 * @see \Doctrine\DBAL\LockMode
564 * @param int $lockMode
567 public function setLockMode($lockMode)
569 if (in_array($lockMode, array(LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE))) {
570 if ( ! $this->_em->getConnection()->isTransactionActive()) {
571 throw TransactionRequiredException::transactionRequired();
575 $this->setHint(self::HINT_LOCK_MODE, $lockMode);
581 * Get the current lock mode for this query.
585 public function getLockMode()
587 $lockMode = $this->getHint(self::HINT_LOCK_MODE);
590 return LockMode::NONE;
597 * Generate a cache id for the query cache - reusing the Result-Cache-Id generator.
603 protected function _getQueryCacheId()
605 ksort($this->_hints);
608 $this->getDql() . var_export($this->_hints, true) .
609 ($this->_em->hasFilters() ? $this->_em->getFilters()->getHash() : '') .
610 '&firstResult=' . $this->_firstResult . '&maxResult=' . $this->_maxResults .
611 '&hydrationMode='.$this->_hydrationMode.'DOCTRINE_QUERY_CACHE_SALT'
616 * Cleanup Query resource when clone is called.
620 public function __clone()
624 $this->_state = self::STATE_DIRTY;