Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/*
3
 *  $Id: Query.php 1393 2007-05-19 17:49:16Z 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.doctrine-project.org>.
20
 */
21
 
22
/**
23
 * Doctrine_Query_Abstract
24
 *
25
 * @package     Doctrine
26
 * @subpackage  Query
27
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
28
 * @link        www.doctrine-project.org
29
 * @since       1.0
30
 * @version     $Revision: 1393 $
31
 * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
32
 * @todo        See {@link Doctrine_Query}
33
 */
34
abstract class Doctrine_Query_Abstract
35
{
36
    /**
37
     * QUERY TYPE CONSTANTS
38
     */
39
 
40
    /**
41
     * constant for SELECT queries
42
     */
43
    const SELECT = 0;
44
 
45
    /**
46
     * constant for DELETE queries
47
     */
48
    const DELETE = 1;
49
 
50
    /**
51
     * constant for UPDATE queries
52
     */
53
    const UPDATE = 2;
54
 
55
    /**
56
     * constant for INSERT queries
57
     */
58
    const INSERT = 3;
59
 
60
    /**
61
     * constant for CREATE queries
62
     */
63
    const CREATE = 4;
64
 
65
    /** @todo document the query states (and the transitions between them). */
66
    /**
67
     * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts.
68
     */
69
    const STATE_CLEAN  = 1;
70
 
71
    /**
72
     * A query object is in state DIRTY when it has DQL parts that have not yet been
73
     * parsed/processed.
74
     */
75
    const STATE_DIRTY  = 2;
76
 
77
    /**
78
     * A query is in DIRECT state when ... ?
79
     */
80
    const STATE_DIRECT = 3;
81
 
82
    /**
83
     * A query object is on LOCKED state when ... ?
84
     */
85
    const STATE_LOCKED = 4;
86
 
87
    /**
88
     * @var array  Table alias map. Keys are SQL aliases and values DQL aliases.
89
     */
90
    protected $_tableAliasMap = array();
91
 
92
    /**
93
     * @var Doctrine_View  The view object used by this query, if any.
94
     */
95
    protected $_view;
96
 
97
    /**
98
     * @var integer $_state   The current state of this query.
99
     */
100
    protected $_state = Doctrine_Query::STATE_CLEAN;
101
 
102
    /**
103
     * @var array $_params  The parameters of this query.
104
     */
105
    protected $_params = array('exec' => array(),
106
                               'join' => array(),
107
                               'where' => array(),
108
                               'set' => array(),
109
                               'having' => array());
110
 
111
    /**
112
     * @var array $_execParams The parameters passed to connection statement
113
     */
114
    protected $_execParams = array();
115
 
116
    /* Caching properties */
117
    /**
118
     * @var Doctrine_Cache_Interface  The cache driver used for caching result sets.
119
     */
120
    protected $_resultCache;
121
 
122
    /**
123
     * @var string  Key to use for result cache entry in the cache driver
124
     */
125
    protected $_resultCacheHash;
126
 
127
    /**
128
     * @var boolean $_expireResultCache  A boolean value that indicates whether or not
129
     *                                   expire the result cache.
130
     */
131
    protected $_expireResultCache = false;
132
    protected $_resultCacheTTL;
133
 
134
    /**
135
     * @var Doctrine_Cache_Interface  The cache driver used for caching queries.
136
     */
137
    protected $_queryCache;
138
    protected $_expireQueryCache = false;
139
    protected $_queryCacheTTL;
140
 
141
 
142
    /**
143
     * @var Doctrine_Connection  The connection used by this query object.
144
     */
145
    protected $_conn;
146
 
147
    /**
148
     * @var bool Whether or not a connection was passed to this query object to use
149
     */
150
    protected $_passedConn = false;
151
 
152
    /**
153
     * @var array $_sqlParts  The SQL query string parts. Filled during the DQL parsing process.
154
     */
155
    protected $_sqlParts = array(
156
            'select'    => array(),
157
            'distinct'  => false,
158
            'forUpdate' => false,
159
            'from'      => array(),
160
            'set'       => array(),
161
            'join'      => array(),
162
            'where'     => array(),
163
            'groupby'   => array(),
164
            'having'    => array(),
165
            'orderby'   => array(),
166
            'limit'     => false,
167
            'offset'    => false,
168
            );
169
 
170
    /**
171
     * @var array $_dqlParts    an array containing all DQL query parts; @see Doctrine_Query::getDqlPart()
172
     */
173
    protected $_dqlParts = array(
174
                            'from'      => array(),
175
                            'select'    => array(),
176
                            'forUpdate' => false,
177
                            'set'       => array(),
178
                            'join'      => array(),
179
                            'where'     => array(),
180
                            'groupby'   => array(),
181
                            'having'    => array(),
182
                            'orderby'   => array(),
183
                            'limit'     => array(),
184
                            'offset'    => array(),
185
                            );
186
 
187
 
188
    /**
189
     * @var array $_queryComponents   Two dimensional array containing the components of this query,
190
     *                                informations about their relations and other related information.
191
     *                                The components are constructed during query parsing.
192
     *
193
     *      Keys are component aliases and values the following:
194
     *
195
     *          table               table object associated with given alias
196
     *
197
     *          relation            the relation object owned by the parent
198
     *
199
     *          parent              the alias of the parent
200
     *
201
     *          agg                 the aggregates of this component
202
     *
203
     *          map                 the name of the column / aggregate value this
204
     *                              component is mapped to a collection
205
     */
206
    protected $_queryComponents = array();
207
 
208
	/**
209
     * Stores the root DQL alias
210
     *
211
     * @var string
212
     */
213
    protected $_rootAlias = '';
214
 
215
    /**
216
     * @var integer $type                   the query type
217
     *
218
     * @see Doctrine_Query::* constants
219
     */
220
    protected $_type = self::SELECT;
221
 
222
    /**
223
     * @var Doctrine_Hydrator   The hydrator object used to hydrate query results.
224
     */
225
    protected $_hydrator;
226
 
227
    /**
228
     * @var Doctrine_Query_Tokenizer  The tokenizer that is used during the query parsing process.
229
     */
230
    protected $_tokenizer;
231
 
232
    /**
233
     * @var Doctrine_Query_Parser  The parser that is used for query parsing.
234
     */
235
    protected $_parser;
236
 
237
    /**
238
     * @var array $_tableAliasSeeds         A simple array keys representing table aliases and values
239
     *                                      table alias seeds. The seeds are used for generating short table
240
     *                                      aliases.
241
     */
242
    protected $_tableAliasSeeds = array();
243
 
244
    /**
245
     * @var array $_options                 an array of options
246
     */
247
    protected $_options    = array(
248
        'hydrationMode'      => Doctrine_Core::HYDRATE_RECORD
249
    );
250
 
251
    /**
252
     * @var boolean
253
     */
254
    protected $_isLimitSubqueryUsed = false;
255
 
256
    /**
257
     * @var array components used in the DQL statement
258
     */
259
    protected $_components;
260
 
261
    /**
262
     * @var bool Boolean variable for whether or not the preQuery process has been executed
263
     */
264
    protected $_preQueried = false;
265
 
266
    /**
267
     * Fix for http://www.doctrine-project.org/jira/browse/DC-701
268
     *
269
     * @var bool Boolean variable for whether the limitSubquery method of accessing tables via a many relationship should be used.
270
     */
271
    protected $disableLimitSubquery = false;
272
 
273
    /**
274
     * Constructor.
275
     *
276
     * @param Doctrine_Connection  The connection object the query will use.
277
     * @param Doctrine_Hydrator_Abstract  The hydrator that will be used for generating result sets.
278
     */
279
    public function __construct(Doctrine_Connection $connection = null,
280
            Doctrine_Hydrator_Abstract $hydrator = null)
281
    {
282
        if ($connection === null) {
283
            $connection = Doctrine_Manager::getInstance()->getCurrentConnection();
284
        } else {
285
            $this->_passedConn = true;
286
        }
287
        if ($hydrator === null) {
288
            $hydrator = new Doctrine_Hydrator();
289
        }
290
        $this->_conn = $connection;
291
        $this->_hydrator = $hydrator;
292
        $this->_tokenizer = new Doctrine_Query_Tokenizer();
293
        $this->_resultCacheTTL = $this->_conn->getAttribute(Doctrine_Core::ATTR_RESULT_CACHE_LIFESPAN);
294
        $this->_queryCacheTTL = $this->_conn->getAttribute(Doctrine_Core::ATTR_QUERY_CACHE_LIFESPAN);
295
    }
296
 
297
    /**
298
     * Set the connection this query object should use
299
     *
300
     * @param Doctrine_Connection $connection
301
     * @return void
302
     */
303
    public function setConnection(Doctrine_Connection $connection)
304
    {
305
        $this->_passedConn = true;
306
        $this->_conn = $connection;
307
    }
308
 
309
    /**
310
     * setOption
311
     *
312
     * @param string $name      option name
313
     * @param string $value     option value
314
     * @return Doctrine_Query   this object
315
     */
316
    public function setOption($name, $value)
317
    {
318
        if ( ! isset($this->_options[$name])) {
319
            throw new Doctrine_Query_Exception('Unknown option ' . $name);
320
        }
321
        $this->_options[$name] = $value;
322
    }
323
 
324
    /**
325
     * hasSqlTableAlias
326
     * whether or not this object has given tableAlias
327
     *
328
     * @param string $tableAlias    the table alias to be checked
329
     * @return boolean              true if this object has given alias, otherwise false
330
     */
331
    public function hasSqlTableAlias($sqlTableAlias)
332
    {
333
        return (isset($this->_tableAliasMap[$sqlTableAlias]));
334
    }
335
 
336
    /**
337
     * getTableAliasMap
338
     * returns all table aliases
339
     *
340
     * @return array        table aliases as an array
341
     */
342
    public function getTableAliasMap()
343
    {
344
        return $this->_tableAliasMap;
345
    }
346
 
347
    /**
348
     * getDql
349
     * returns the DQL query that is represented by this query object.
350
     *
351
     * the query is built from $_dqlParts
352
     *
353
     * @return string   the DQL query
354
     */
355
    public function getDql()
356
    {
357
        $q = '';
358
        if ($this->_type == self::SELECT) {
359
            $q .= ( ! empty($this->_dqlParts['select'])) ? 'SELECT ' . implode(', ', $this->_dqlParts['select']) : '';
360
            $q .= ( ! empty($this->_dqlParts['from'])) ? ' FROM ' . implode(' ', $this->_dqlParts['from']) : '';
361
        } else if ($this->_type == self::DELETE) {
362
            $q .= 'DELETE';
363
            $q .= ( ! empty($this->_dqlParts['from'])) ? ' FROM ' . implode(' ', $this->_dqlParts['from']) : '';
364
        } else if ($this->_type == self::UPDATE) {
365
            $q .= 'UPDATE ';
366
            $q .= ( ! empty($this->_dqlParts['from'])) ? implode(' ', $this->_dqlParts['from']) : '';
367
            $q .= ( ! empty($this->_dqlParts['set'])) ? ' SET ' . implode(' ', $this->_dqlParts['set']) : '';
368
        }
369
        $q .= ( ! empty($this->_dqlParts['where'])) ? ' WHERE ' . implode(' ', $this->_dqlParts['where']) : '';
370
        $q .= ( ! empty($this->_dqlParts['groupby'])) ? ' GROUP BY ' . implode(', ', $this->_dqlParts['groupby']) : '';
371
        $q .= ( ! empty($this->_dqlParts['having'])) ? ' HAVING ' . implode(' AND ', $this->_dqlParts['having']) : '';
372
        $q .= ( ! empty($this->_dqlParts['orderby'])) ? ' ORDER BY ' . implode(', ', $this->_dqlParts['orderby']) : '';
373
        $q .= ( ! empty($this->_dqlParts['limit'])) ? ' LIMIT ' . implode(' ', $this->_dqlParts['limit']) : '';
374
        $q .= ( ! empty($this->_dqlParts['offset'])) ? ' OFFSET ' . implode(' ', $this->_dqlParts['offset']) : '';
375
 
376
        return $q;
377
    }
378
 
379
    /**
380
     * getSqlQueryPart
381
     * gets an SQL query part from the SQL query part array
382
     *
383
     * @param string $name          the name of the query part to be set
384
     * @param string $part          query part string
385
     * @throws Doctrine_Query_Exception   if trying to set unknown query part
386
     * @return mixed     this object
387
     */
388
    public function getSqlQueryPart($part)
389
    {
390
        if ( ! isset($this->_sqlParts[$part])) {
391
            throw new Doctrine_Query_Exception('Unknown SQL query part ' . $part);
392
        }
393
        return $this->_sqlParts[$part];
394
    }
395
 
396
    /**
397
     * setSqlQueryPart
398
     * sets an SQL query part in the SQL query part array
399
     *
400
     * @param string $name          the name of the query part to be set
401
     * @param string $part          query part string
402
     * @throws Doctrine_Query_Exception   if trying to set unknown query part
403
     * @return Doctrine_Query     this object
404
     */
405
    public function setSqlQueryPart($name, $part)
406
    {
407
        if ( ! isset($this->_sqlParts[$name])) {
408
            throw new Doctrine_Query_Exception('Unknown query part ' . $name);
409
        }
410
 
411
        if ($name !== 'limit' && $name !== 'offset') {
412
            if (is_array($part)) {
413
                $this->_sqlParts[$name] = $part;
414
            } else {
415
                $this->_sqlParts[$name] = array($part);
416
            }
417
        } else {
418
            $this->_sqlParts[$name] = $part;
419
        }
420
 
421
        return $this;
422
    }
423
 
424
    /**
425
     * addSqlQueryPart
426
     * adds an SQL query part to the SQL query part array
427
     *
428
     * @param string $name          the name of the query part to be added
429
     * @param string $part          query part string
430
     * @throws Doctrine_Query_Exception   if trying to add unknown query part
431
     * @return Doctrine_Query     this object
432
     */
433
    public function addSqlQueryPart($name, $part)
434
    {
435
        if ( ! isset($this->_sqlParts[$name])) {
436
            throw new Doctrine_Query_Exception('Unknown query part ' . $name);
437
        }
438
        if (is_array($part)) {
439
            $this->_sqlParts[$name] = array_merge($this->_sqlParts[$name], $part);
440
        } else {
441
            $this->_sqlParts[$name][] = $part;
442
        }
443
        return $this;
444
    }
445
 
446
    /**
447
     * removeSqlQueryPart
448
     * removes a query part from the query part array
449
     *
450
     * @param string $name          the name of the query part to be removed
451
     * @throws Doctrine_Query_Exception   if trying to remove unknown query part
452
     * @return Doctrine_Query     this object
453
     */
454
    public function removeSqlQueryPart($name)
455
    {
456
        if ( ! isset($this->_sqlParts[$name])) {
457
            throw new Doctrine_Query_Exception('Unknown query part ' . $name);
458
        }
459
 
460
        if ($name == 'limit' || $name == 'offset' || $name == 'forUpdate') {
461
            $this->_sqlParts[$name] = false;
462
        } else {
463
            $this->_sqlParts[$name] = array();
464
        }
465
 
466
        return $this;
467
    }
468
 
469
    /**
470
     * removeDqlQueryPart
471
     * removes a dql query part from the dql query part array
472
     *
473
     * @param string $name          the name of the query part to be removed
474
     * @throws Doctrine_Query_Exception   if trying to remove unknown query part
475
     * @return Doctrine_Query     this object
476
     */
477
    public function removeDqlQueryPart($name)
478
    {
479
        if ( ! isset($this->_dqlParts[$name])) {
480
            throw new Doctrine_Query_Exception('Unknown query part ' . $name);
481
        }
482
 
483
        if ($name == 'limit' || $name == 'offset') {
484
            $this->_dqlParts[$name] = false;
485
        } else {
486
            $this->_dqlParts[$name] = array();
487
        }
488
 
489
        return $this;
490
    }
491
 
492
    /**
493
     * Get raw array of parameters for query and all parts.
494
     *
495
     * @return array $params
496
     */
497
    public function getParams()
498
    {
499
        return $this->_params;
500
    }
501
 
502
    /**
503
     * Get flattened array of parameters for query.
504
     * Used internally and used to pass flat array of params to the database.
505
     *
506
     * @param array $params
507
     * @return void
508
     */
509
    public function getFlattenedParams($params = array())
510
    {
511
        return array_merge(
512
            (array) $params, (array) $this->_params['exec'],
513
            $this->_params['join'], $this->_params['set'],
514
            $this->_params['where'], $this->_params['having']
515
        );
516
    }
517
 
518
    /**
519
     * getInternalParams
520
     *
521
     * @return array
522
     */
523
    public function getInternalParams($params = array())
524
    {
525
        return array_merge($params, $this->_execParams);
526
    }
527
 
528
    /**
529
     * setParams
530
     *
531
     * @param array $params
532
     */
533
    public function setParams(array $params = array())
534
    {
535
        $this->_params = $params;
536
    }
537
 
538
    /**
539
     * getCountQueryParams
540
     * Retrieves the parameters for count query
541
     *
542
     * @return array Parameters array
543
     */
544
    public function getCountQueryParams($params = array())
545
    {
546
        if ( ! is_array($params)) {
547
            $params = array($params);
548
        }
549
 
550
        $this->_params['exec'] = $params;
551
 
552
        $params = array_merge($this->_params['join'], $this->_params['where'], $this->_params['having'], $this->_params['exec']);
553
 
554
        $this->fixArrayParameterValues($params);
555
 
556
        return $this->_execParams;
557
    }
558
 
559
    /**
560
     * @nodoc
561
     */
562
    public function fixArrayParameterValues($params = array())
563
    {
564
        $i = 0;
565
 
566
        foreach ($params as $param) {
567
            if (is_array($param)) {
568
                $c = count($param);
569
 
570
                array_splice($params, $i, 1, $param);
571
 
572
                $i += $c;
573
            } else {
574
                $i++;
575
            }
576
        }
577
 
578
        $this->_execParams = $params;
579
    }
580
 
581
    /**
582
     * setView
583
     * sets a database view this query object uses
584
     * this method should only be called internally by doctrine
585
     *
586
     * @param Doctrine_View $view       database view
587
     * @return void
588
     */
589
    public function setView(Doctrine_View $view)
590
    {
591
        $this->_view = $view;
592
    }
593
 
594
    /**
595
     * getView
596
     * returns the view associated with this query object (if any)
597
     *
598
     * @return Doctrine_View        the view associated with this query object
599
     */
600
    public function getView()
601
    {
602
        return $this->_view;
603
    }
604
 
605
    /**
606
     * limitSubqueryUsed
607
     *
608
     * @return boolean
609
     */
610
    public function isLimitSubqueryUsed()
611
    {
612
        return $this->_isLimitSubqueryUsed;
613
    }
614
 
615
    /**
616
     * Returns the inheritance condition for the passed componentAlias
617
     * If no component alias is specified it defaults to the root component
618
     *
619
     * This function is used to append a SQL condition to models which have inheritance mapping
620
     * The condition is applied to the FROM component in the WHERE, but the condition is applied to
621
     * JOINS in the ON condition and not the WHERE
622
     *
623
     * @return string $str  SQL condition string
624
     */
625
    public function getInheritanceCondition($componentAlias)
626
    {
627
        $map = $this->_queryComponents[$componentAlias]['table']->inheritanceMap;
628
 
629
        // No inheritance map so lets just return
630
        if (empty($map)) {
631
          return;
632
        }
633
 
634
        $tableAlias = $this->getSqlTableAlias($componentAlias);
635
 
636
        if ($this->_type !== Doctrine_Query::SELECT) {
637
            $tableAlias = '';
638
        } else {
639
            $tableAlias .= '.';
640
        }
641
 
642
        // Fix for 2015: loop through whole inheritanceMap to add all
643
        // keyFields for inheritance (and not only the first)
644
        $retVal = "";
645
        $count = 0;
646
 
647
        foreach ($map as $field => $value) {
648
            if ($count++ > 0) {
649
                $retVal .= ' AND ';
650
            }
651
 
652
            $identifier = $this->_conn->quoteIdentifier($tableAlias . $field);
653
            $retVal .= $identifier . ' = ' . $this->_conn->quote($value);
654
        }
655
 
656
        return $retVal;
657
    }
658
 
659
    /**
660
     * getSqlTableAlias
661
     * some database such as Oracle need the identifier lengths to be < ~30 chars
662
     * hence Doctrine creates as short identifier aliases as possible
663
     *
664
     * this method is used for the creation of short table aliases, its also
665
     * smart enough to check if an alias already exists for given component (componentAlias)
666
     *
667
     * @param string $componentAlias    the alias for the query component to search table alias for
668
     * @param string $tableName         the table name from which the table alias is being created
669
     * @return string                   the generated / fetched short alias
670
     */
671
    public function getSqlTableAlias($componentAlias, $tableName = null)
672
    {
673
        $alias = array_search($componentAlias, $this->_tableAliasMap);
674
 
675
        if ($alias !== false) {
676
            return $alias;
677
        }
678
 
679
        if ($tableName === null) {
680
            throw new Doctrine_Query_Exception("Couldn't get short alias for " . $componentAlias);
681
        }
682
 
683
        return $this->generateSqlTableAlias($componentAlias, $tableName);
684
    }
685
 
686
    /**
687
     * generateNewSqlTableAlias
688
     * generates a new alias from given table alias
689
     *
690
     * @param string $tableAlias    table alias from which to generate the new alias from
691
     * @return string               the created table alias
692
     */
693
    public function generateNewSqlTableAlias($oldAlias)
694
    {
695
        if (isset($this->_tableAliasMap[$oldAlias])) {
696
            // generate a new alias
697
            $name = substr($oldAlias, 0, 1);
698
            $i    = ((int) substr($oldAlias, 1));
699
 
700
            // Fix #1530: It was reaching unexistent seeds index
701
            if ( ! isset($this->_tableAliasSeeds[$name])) {
702
                $this->_tableAliasSeeds[$name] = 1;
703
            }
704
 
705
            $newIndex  = ($this->_tableAliasSeeds[$name] + (($i == 0) ? 1 : $i));
706
 
707
            return $name . $newIndex;
708
        }
709
 
710
        return $oldAlias;
711
    }
712
 
713
    /**
714
     * getSqlTableAliasSeed
715
     * returns the alias seed for given table alias
716
     *
717
     * @param string $tableAlias    table alias that identifies the alias seed
718
     * @return integer              table alias seed
719
     */
720
    public function getSqlTableAliasSeed($sqlTableAlias)
721
    {
722
        if ( ! isset($this->_tableAliasSeeds[$sqlTableAlias])) {
723
            return 0;
724
        }
725
        return $this->_tableAliasSeeds[$sqlTableAlias];
726
    }
727
 
728
    /**
729
     * hasAliasDeclaration
730
     * whether or not this object has a declaration for given component alias
731
     *
732
     * @param string $componentAlias    the component alias the retrieve the declaration from
733
     * @return boolean
734
     */
735
    public function hasAliasDeclaration($componentAlias)
736
    {
737
        return isset($this->_queryComponents[$componentAlias]);
738
    }
739
 
740
    /**
741
     * getQueryComponent
742
     * get the declaration for given component alias
743
     *
744
     * @param string $componentAlias    the component alias the retrieve the declaration from
745
     * @return array                    the alias declaration
746
     */
747
    public function getQueryComponent($componentAlias)
748
    {
749
        if ( ! isset($this->_queryComponents[$componentAlias])) {
750
            throw new Doctrine_Query_Exception('Unknown component alias ' . $componentAlias);
751
        }
752
 
753
        return $this->_queryComponents[$componentAlias];
754
    }
755
 
756
    /**
757
     * copySubqueryInfo
758
     * copy aliases from another Hydrate object
759
     *
760
     * this method is needed by DQL subqueries which need the aliases
761
     * of the parent query
762
     *
763
     * @param Doctrine_Hydrate $query   the query object from which the
764
     *                                  aliases are copied from
765
     * @return Doctrine_Query         this object
766
     */
767
    public function copySubqueryInfo(Doctrine_Query_Abstract $query)
768
    {
769
        $this->_params =& $query->_params;
770
        $this->_tableAliasMap =& $query->_tableAliasMap;
771
        $this->_queryComponents =& $query->_queryComponents;
772
        $this->_tableAliasSeeds = $query->_tableAliasSeeds;
773
        return $this;
774
    }
775
 
776
    /**
777
     * getRootAlias
778
     * returns the alias of the root component
779
     *
780
     * @return array
781
     */
782
    public function getRootAlias()
783
    {
784
        if ( ! $this->_queryComponents) {
785
            $this->getSqlQuery(array(), false);
786
        }
787
 
788
        return $this->_rootAlias;
789
    }
790
 
791
    /**
792
     * getRootDeclaration
793
     * returns the root declaration
794
     *
795
     * @return array
796
     */
797
    public function getRootDeclaration()
798
    {
799
        $map = $this->_queryComponents[$this->_rootAlias];
800
        return $map;
801
    }
802
 
803
    /**
804
     * getRoot
805
     * returns the root component for this object
806
     *
807
     * @return Doctrine_Table       root components table
808
     */
809
    public function getRoot()
810
    {
811
        $map = $this->_queryComponents[$this->_rootAlias];
812
 
813
        if ( ! isset($map['table'])) {
814
            throw new Doctrine_Query_Exception('Root component not initialized.');
815
        }
816
 
817
        return $map['table'];
818
    }
819
 
820
    /**
821
     * generateSqlTableAlias
822
     * generates a table alias from given table name and associates
823
     * it with given component alias
824
     *
825
     * @param string $componentAlias    the component alias to be associated with generated table alias
826
     * @param string $tableName         the table name from which to generate the table alias
827
     * @return string                   the generated table alias
828
     */
829
    public function generateSqlTableAlias($componentAlias, $tableName)
830
    {
831
        preg_match('/([^_|\d])/', $tableName, $matches);
832
        $char = strtolower($matches[0]);
833
 
834
        $alias = $char;
835
 
836
        if ( ! isset($this->_tableAliasSeeds[$alias])) {
837
            $this->_tableAliasSeeds[$alias] = 1;
838
        }
839
 
840
        while (isset($this->_tableAliasMap[$alias])) {
841
            if ( ! isset($this->_tableAliasSeeds[$alias])) {
842
                $this->_tableAliasSeeds[$alias] = 1;
843
            }
844
            $alias = $char . ++$this->_tableAliasSeeds[$alias];
845
        }
846
 
847
        $this->_tableAliasMap[$alias] = $componentAlias;
848
 
849
        return $alias;
850
    }
851
 
852
    /**
853
     * getComponentAlias
854
     * get component alias associated with given table alias
855
     *
856
     * @param string $sqlTableAlias    the SQL table alias that identifies the component alias
857
     * @return string               component alias
858
     */
859
    public function getComponentAlias($sqlTableAlias)
860
    {
861
        $sqlTableAlias = trim($sqlTableAlias, '[]`"');
862
        if ( ! isset($this->_tableAliasMap[$sqlTableAlias])) {
863
            throw new Doctrine_Query_Exception('Unknown table alias ' . $sqlTableAlias);
864
        }
865
        return $this->_tableAliasMap[$sqlTableAlias];
866
    }
867
 
868
    /**
869
     * calculateQueryCacheHash
870
     * calculate hash key for query cache
871
     *
872
     * @return string    the hash
873
     */
874
    public function calculateQueryCacheHash()
875
    {
876
        $dql = $this->getDql();
877
        $hash = md5($dql . var_export($this->_pendingJoinConditions, true) . 'DOCTRINE_QUERY_CACHE_SALT');
878
        return $hash;
879
    }
880
 
881
    /**
882
     * calculateResultCacheHash
883
     * calculate hash key for result cache
884
     *
885
     * @param array $params
886
     * @return string    the hash
887
     */
888
    public function calculateResultCacheHash($params = array())
889
    {
890
        $dql = $this->getDql();
891
        $conn = $this->getConnection();
892
        $params = $this->getFlattenedParams($params);
893
        $hash = md5($this->_hydrator->getHydrationMode() . $conn->getName() . $conn->getOption('dsn') . $dql . var_export($this->_pendingJoinConditions, true) . var_export($params, true));
894
        return $hash;
895
    }
896
 
897
    /**
898
     * Get the result cache hash/key. Returns key set with useResultCache()
899
     * or generates a unique key from the query automatically.
900
     *
901
     * @param array $params
902
     * @return string $hash
903
     */
904
    public function getResultCacheHash($params = array())
905
    {
906
      if ($this->_resultCacheHash) {
907
          return $this->_resultCacheHash;
908
      } else {
909
          return $this->calculateResultCacheHash($params);
910
      }
911
    }
912
 
913
    /**
914
     * _execute
915
     *
916
     * @param array $params
917
     * @return PDOStatement  The executed PDOStatement.
918
     */
919
    protected function _execute($params)
920
    {
921
        // Apply boolean conversion in DQL params
922
        $params = $this->_conn->convertBooleans($params);
923
 
924
        foreach ($this->_params as $k => $v) {
925
            $this->_params[$k] = $this->_conn->convertBooleans($v);
926
        }
927
 
928
        $dqlParams = $this->getFlattenedParams($params);
929
 
930
        // Check if we're not using a Doctrine_View
931
        if ( ! $this->_view) {
932
            if ($this->_queryCache !== false && ($this->_queryCache || $this->_conn->getAttribute(Doctrine_Core::ATTR_QUERY_CACHE))) {
933
                $queryCacheDriver = $this->getQueryCacheDriver();
934
                $hash = $this->calculateQueryCacheHash();
935
                $cached = $queryCacheDriver->fetch($hash);
936
 
937
                // If we have a cached query...
938
                if ($cached) {
939
                    // Rebuild query from cache
940
                    $query = $this->_constructQueryFromCache($cached);
941
 
942
                    // Assign building/execution specific params
943
                    $this->_params['exec'] = $params;
944
 
945
                    // Initialize prepared parameters array
946
                    $this->_execParams = $this->getFlattenedParams();
947
 
948
                    // Fix possible array parameter values in SQL params
949
                    $this->fixArrayParameterValues($this->getInternalParams());
950
                } else {
951
                    // Generate SQL or pick already processed one
952
                    $query = $this->getSqlQuery($params);
953
 
954
                    // Check again because getSqlQuery() above could have flipped the _queryCache flag
955
                    // if this query contains the limit sub query algorithm we don't need to cache it
956
                    if ($this->_queryCache !== false && ($this->_queryCache || $this->_conn->getAttribute(Doctrine_Core::ATTR_QUERY_CACHE))) {
957
                        // Convert query into a serialized form
958
                        $serializedQuery = $this->getCachedForm($query);
959
 
960
                        // Save cached query
961
                        $queryCacheDriver->save($hash, $serializedQuery, $this->getQueryCacheLifeSpan());
962
                    }
963
                }
964
            } else {
965
                $query = $this->getSqlQuery($params);
966
            }
967
        } else {
968
            $query = $this->_view->getSelectSql();
969
        }
970
 
971
        // Get prepared SQL params for execution
972
        $params = $this->getInternalParams();
973
 
974
        if ($this->isLimitSubqueryUsed() &&
975
                $this->_conn->getAttribute(Doctrine_Core::ATTR_DRIVER_NAME) !== 'mysql') {
976
            $params = array_merge((array) $params, (array) $params);
977
        }
978
 
979
        if ($this->_type !== self::SELECT) {
980
            return $this->_conn->exec($query, $params);
981
        }
982
 
983
        $stmt = $this->_conn->execute($query, $params);
984
 
985
        $this->_params['exec'] = array();
986
 
987
        return $stmt;
988
    }
989
 
990
    /**
991
     * execute
992
     * executes the query and populates the data set
993
     *
994
     * @param array $params
995
     * @return Doctrine_Collection            the root collection
996
     */
997
    public function execute($params = array(), $hydrationMode = null)
998
    {
999
        // Clean any possible processed params
1000
        $this->_execParams = array();
1001
 
1002
        if (empty($this->_dqlParts['from']) && empty($this->_sqlParts['from'])) {
1003
            throw new Doctrine_Query_Exception('You must have at least one component specified in your from.');
1004
        }
1005
 
1006
        $dqlParams = $this->getFlattenedParams($params);
1007
 
1008
        $this->_preQuery($dqlParams);
1009
 
1010
        if ($hydrationMode !== null) {
1011
            $this->_hydrator->setHydrationMode($hydrationMode);
1012
        }
1013
 
1014
        $hydrationMode = $this->_hydrator->getHydrationMode();
1015
 
1016
        if ($this->_resultCache && $this->_type == self::SELECT) {
1017
            $cacheDriver = $this->getResultCacheDriver();
1018
            $hash = $this->getResultCacheHash($params);
1019
            $cached = ($this->_expireResultCache) ? false : $cacheDriver->fetch($hash);
1020
 
1021
            if ($cached === false) {
1022
                // cache miss
1023
                $stmt = $this->_execute($params);
1024
                $this->_hydrator->setQueryComponents($this->_queryComponents);
1025
                $result = $this->_hydrator->hydrateResultSet($stmt, $this->_tableAliasMap);
1026
 
1027
                $cached = $this->getCachedForm($result);
1028
                $cacheDriver->save($hash, $cached, $this->getResultCacheLifeSpan());
1029
            } else {
1030
                $result = $this->_constructQueryFromCache($cached);
1031
            }
1032
        } else {
1033
            $stmt = $this->_execute($params);
1034
 
1035
            if (is_integer($stmt)) {
1036
                $result = $stmt;
1037
            } else {
1038
                $this->_hydrator->setQueryComponents($this->_queryComponents);
1039
                if ($this->_type == self::SELECT && $hydrationMode == Doctrine_Core::HYDRATE_ON_DEMAND) {
1040
                    $hydrationDriver = $this->_hydrator->getHydratorDriver($hydrationMode, $this->_tableAliasMap);
1041
                    $result = new Doctrine_Collection_OnDemand($stmt, $hydrationDriver, $this->_tableAliasMap);
1042
                } else {
1043
                    $result = $this->_hydrator->hydrateResultSet($stmt, $this->_tableAliasMap);
1044
                }
1045
            }
1046
        }
1047
        if ($this->getConnection()->getAttribute(Doctrine_Core::ATTR_AUTO_FREE_QUERY_OBJECTS)) {
1048
            $this->free();
1049
        }
1050
 
1051
        return $result;
1052
    }
1053
 
1054
    /**
1055
     * Blank template method free(). Override to be used to free query object memory
1056
     */
1057
    public function free()
1058
    {
1059
    }
1060
 
1061
    /**
1062
     * Get the dql call back for this query
1063
     *
1064
     * @return array $callback
1065
     */
1066
    protected function _getDqlCallback()
1067
    {
1068
        $callback = false;
1069
        if ( ! empty($this->_dqlParts['from'])) {
1070
            switch ($this->_type) {
1071
                case self::DELETE:
1072
                    $callback = array(
1073
                        'callback' => 'preDqlDelete',
1074
                        'const' => Doctrine_Event::RECORD_DQL_DELETE
1075
                    );
1076
                break;
1077
                case self::UPDATE:
1078
                    $callback = array(
1079
                        'callback' => 'preDqlUpdate',
1080
                        'const' => Doctrine_Event::RECORD_DQL_UPDATE
1081
                    );
1082
                break;
1083
                case self::SELECT:
1084
                    $callback = array(
1085
                        'callback' => 'preDqlSelect',
1086
                        'const' => Doctrine_Event::RECORD_DQL_SELECT
1087
                    );
1088
                break;
1089
            }
1090
        }
1091
 
1092
        return $callback;
1093
    }
1094
 
1095
    /**
1096
     * Pre query method which invokes the pre*Query() methods on the model instance or any attached
1097
     * record listeners
1098
     *
1099
     * @return void
1100
     */
1101
    protected function _preQuery($params = array())
1102
    {
1103
        if ( ! $this->_preQueried && $this->getConnection()->getAttribute(Doctrine_Core::ATTR_USE_DQL_CALLBACKS)) {
1104
            $this->_preQueried = true;
1105
 
1106
            $callback = $this->_getDqlCallback();
1107
 
1108
            // if there is no callback for the query type, then we can return early
1109
            if ( ! $callback) {
1110
                return;
1111
            }
1112
 
1113
            foreach ($this->_getDqlCallbackComponents($params) as $alias => $component) {
1114
                $table = $component['table'];
1115
                $record = $table->getRecordInstance();
1116
 
1117
                // Trigger preDql*() callback event
1118
                $params = array('component' => $component, 'alias' => $alias);
1119
                $event = new Doctrine_Event($record, $callback['const'], $this, $params);
1120
 
1121
                $record->$callback['callback']($event);
1122
                $table->getRecordListener()->$callback['callback']($event);
1123
            }
1124
        }
1125
 
1126
        // Invoke preQuery() hook on Doctrine_Query for child classes which implement this hook
1127
        $this->preQuery();
1128
    }
1129
 
1130
    /**
1131
     * Returns an array of components to execute the query callbacks for
1132
     *
1133
     * @param  array $params
1134
     * @return array $components
1135
     */
1136
    protected function _getDqlCallbackComponents($params = array())
1137
    {
1138
        $componentsBefore = array();
1139
        if ($this->isSubquery()) {
1140
            $componentsBefore = $this->getQueryComponents();
1141
        }
1142
 
1143
        $copy = $this->copy();
1144
        $copy->getSqlQuery($params, false);
1145
        $componentsAfter = $copy->getQueryComponents();
1146
 
1147
        $this->_rootAlias = $copy->getRootAlias();
1148
 
1149
        $copy->free();
1150
 
1151
        if ($componentsBefore !== $componentsAfter) {
1152
            return array_diff($componentsAfter, $componentsBefore);
1153
        } else {
1154
            return $componentsAfter;
1155
        }
1156
    }
1157
 
1158
    /**
1159
     * Blank hook methods which can be implemented in Doctrine_Query child classes
1160
     *
1161
     * @return void
1162
     */
1163
    public function preQuery()
1164
    {
1165
    }
1166
 
1167
    /**
1168
     * Constructs the query from the cached form.
1169
     *
1170
     * @param string  The cached query, in a serialized form.
1171
     * @return array  The custom component that was cached together with the essential
1172
     *                query data. This can be either a result set (result caching)
1173
     *                or an SQL query string (query caching).
1174
     */
1175
    protected function _constructQueryFromCache($cached)
1176
    {
1177
        $cached = unserialize($cached);
1178
        $this->_tableAliasMap = $cached[2];
1179
        $customComponent = $cached[0];
1180
 
1181
        $queryComponents = array();
1182
        $cachedComponents = $cached[1];
1183
        foreach ($cachedComponents as $alias => $components) {
1184
            $e = explode('.', $components['name']);
1185
            if (count($e) === 1) {
1186
                $manager = Doctrine_Manager::getInstance();
1187
                if ( ! $this->_passedConn && $manager->hasConnectionForComponent($e[0])) {
1188
                    $this->_conn = $manager->getConnectionForComponent($e[0]);
1189
                }
1190
                $queryComponents[$alias]['table'] = $this->_conn->getTable($e[0]);
1191
            } else {
1192
                $queryComponents[$alias]['parent'] = $e[0];
1193
                $queryComponents[$alias]['relation'] = $queryComponents[$e[0]]['table']->getRelation($e[1]);
1194
                $queryComponents[$alias]['table'] = $queryComponents[$alias]['relation']->getTable();
1195
            }
1196
            if (isset($components['agg'])) {
1197
                $queryComponents[$alias]['agg'] = $components['agg'];
1198
            }
1199
            if (isset($components['map'])) {
1200
                $queryComponents[$alias]['map'] = $components['map'];
1201
            }
1202
        }
1203
        $this->_queryComponents = $queryComponents;
1204
 
1205
        return $customComponent;
1206
    }
1207
 
1208
    /**
1209
     * getCachedForm
1210
     * returns the cached form of this query for given resultSet
1211
     *
1212
     * @param array $resultSet
1213
     * @return string           serialized string representation of this query
1214
     */
1215
    public function getCachedForm($customComponent = null)
1216
    {
1217
        $componentInfo = array();
1218
 
1219
        foreach ($this->getQueryComponents() as $alias => $components) {
1220
            if ( ! isset($components['parent'])) {
1221
                $componentInfo[$alias]['name'] = $components['table']->getComponentName();
1222
            } else {
1223
                $componentInfo[$alias]['name'] = $components['parent'] . '.' . $components['relation']->getAlias();
1224
            }
1225
            if (isset($components['agg'])) {
1226
                $componentInfo[$alias]['agg'] = $components['agg'];
1227
            }
1228
            if (isset($components['map'])) {
1229
                $componentInfo[$alias]['map'] = $components['map'];
1230
            }
1231
        }
1232
 
1233
        if ($customComponent instanceof Doctrine_Collection) {
1234
            foreach ($customComponent as $record) {
1235
                $record->serializeReferences(true);
1236
            }
1237
        }
1238
 
1239
        return serialize(array($customComponent, $componentInfo, $this->getTableAliasMap()));
1240
    }
1241
 
1242
    /**
1243
     * Adds fields or aliased functions.
1244
     *
1245
     * This method adds fields or dbms functions to the SELECT query part.
1246
     * <code>
1247
     * $query->addSelect('COUNT(p.id) as num_phonenumbers');
1248
     * </code>
1249
     *
1250
     * @param string $select        Query SELECT part
1251
     * @return Doctrine_Query
1252
     */
1253
    public function addSelect($select)
1254
    {
1255
        return $this->_addDqlQueryPart('select', $select, true);
1256
    }
1257
 
1258
    /**
1259
     * addSqlTableAlias
1260
     * adds an SQL table alias and associates it a component alias
1261
     *
1262
     * @param string $componentAlias    the alias for the query component associated with given tableAlias
1263
     * @param string $tableAlias        the table alias to be added
1264
     * @return Doctrine_Query_Abstract
1265
     */
1266
    public function addSqlTableAlias($sqlTableAlias, $componentAlias)
1267
    {
1268
        $this->_tableAliasMap[$sqlTableAlias] = $componentAlias;
1269
        return $this;
1270
    }
1271
 
1272
    /**
1273
     * addFrom
1274
     * adds fields to the FROM part of the query
1275
     *
1276
     * @param string $from        Query FROM part
1277
     * @return Doctrine_Query
1278
     */
1279
    public function addFrom($from)
1280
    {
1281
        return $this->_addDqlQueryPart('from', $from, true);
1282
    }
1283
 
1284
    /**
1285
     * Alias for @see andWhere().
1286
     * @return Doctrine_Query   this object
1287
     */
1288
    public function addWhere($where, $params = array())
1289
    {
1290
        return $this->andWhere($where, $params);
1291
    }
1292
 
1293
    /**
1294
     * Adds conditions to the WHERE part of the query.
1295
     * <code>
1296
     * $q->andWhere('u.birthDate > ?', '1975-01-01');
1297
     * </code>
1298
     *
1299
     * @param string $where Query WHERE part
1300
     * @param mixed $params An array of parameters or a simple scalar
1301
     * @return Doctrine_Query
1302
     */
1303
    public function andWhere($where, $params = array())
1304
    {
1305
        if (is_array($params)) {
1306
            $this->_params['where'] = array_merge($this->_params['where'], $params);
1307
        } else {
1308
            $this->_params['where'][] = $params;
1309
        }
1310
 
1311
        if ($this->_hasDqlQueryPart('where')) {
1312
            $this->_addDqlQueryPart('where', 'AND', true);
1313
        }
1314
 
1315
        return $this->_addDqlQueryPart('where', $where, true);
1316
    }
1317
 
1318
    /**
1319
     * Adds conditions to the WHERE part of the query
1320
     * <code>
1321
     * $q->orWhere('u.role = ?', 'admin');
1322
     * </code>
1323
     *
1324
     * @param string $where Query WHERE part
1325
     * @param mixed $params An array of parameters or a simple scalar
1326
     * @return Doctrine_Query
1327
     */
1328
    public function orWhere($where, $params = array())
1329
    {
1330
        if (is_array($params)) {
1331
            $this->_params['where'] = array_merge($this->_params['where'], $params);
1332
        } else {
1333
            $this->_params['where'][] = $params;
1334
        }
1335
 
1336
        if ($this->_hasDqlQueryPart('where')) {
1337
            $this->_addDqlQueryPart('where', 'OR', true);
1338
        }
1339
 
1340
        return $this->_addDqlQueryPart('where', $where, true);
1341
    }
1342
 
1343
    /**
1344
     * Adds IN condition to the query WHERE part. Alias to @see andWhereIn().
1345
     *
1346
     * @param string $expr          the operand of the IN
1347
     * @param mixed $params         an array of parameters or a simple scalar
1348
     * @param boolean $not          whether or not to use NOT in front of IN
1349
     * @return Doctrine_Query
1350
     */
1351
    public function whereIn($expr, $params = array(), $not = false)
1352
    {
1353
        return $this->andWhereIn($expr, $params, $not);
1354
    }
1355
 
1356
    /**
1357
     * Adds IN condition to the query WHERE part
1358
     * <code>
1359
     * $q->whereIn('u.id', array(10, 23, 44));
1360
     * </code>
1361
     *
1362
     * @param string $expr      The operand of the IN
1363
     * @param mixed $params     An array of parameters or a simple scalar
1364
     * @param boolean $not      Whether or not to use NOT in front of IN. Defaults to false (simple IN clause)
1365
     * @return Doctrine_Query   this object.
1366
     */
1367
    public function andWhereIn($expr, $params = array(), $not = false)
1368
    {
1369
        // if there's no params, return (else we'll get a WHERE IN (), invalid SQL)
1370
        if (isset($params) and (count($params) == 0)) {
1371
            return $this;
1372
        }
1373
 
1374
        if ($this->_hasDqlQueryPart('where')) {
1375
            $this->_addDqlQueryPart('where', 'AND', true);
1376
        }
1377
 
1378
        return $this->_addDqlQueryPart('where', $this->_processWhereIn($expr, $params, $not), true);
1379
    }
1380
 
1381
    /**
1382
     * Adds IN condition to the query WHERE part, appending it with an OR operator.
1383
     * <code>
1384
     * $q->orWhereIn('u.id', array(10, 23))
1385
     *   ->orWhereIn('u.id', 44);
1386
     * // will select all record with id equal to 10, 23 or 44
1387
     * </code>
1388
     *
1389
     * @param string $expr The operand of the IN
1390
     * @param mixed $params An array of parameters or a simple scalar
1391
     * @param boolean $not Whether or not to use NOT in front of IN
1392
     * @return Doctrine_Query
1393
     */
1394
    public function orWhereIn($expr, $params = array(), $not = false)
1395
    {
1396
        // if there's no params, return (else we'll get a WHERE IN (), invalid SQL)
1397
        if (isset($params) and (count($params) == 0)) {
1398
            return $this;
1399
        }
1400
 
1401
        if ($this->_hasDqlQueryPart('where')) {
1402
            $this->_addDqlQueryPart('where', 'OR', true);
1403
        }
1404
 
1405
        return $this->_addDqlQueryPart('where', $this->_processWhereIn($expr, $params, $not), true);
1406
    }
1407
 
1408
    /**
1409
     * @nodoc
1410
     */
1411
    protected function _processWhereIn($expr, $params = array(), $not = false)
1412
    {
1413
        $params = (array) $params;
1414
 
1415
        // if there's no params, return (else we'll get a WHERE IN (), invalid SQL)
1416
        if (count($params) == 0) {
1417
            throw new Doctrine_Query_Exception('You must pass at least one parameter when using an IN() condition.');
1418
        }
1419
 
1420
        $a = array();
1421
        foreach ($params as $k => $value) {
1422
            if ($value instanceof Doctrine_Expression) {
1423
                $value = $value->getSql();
1424
                unset($params[$k]);
1425
            } else {
1426
                $value = '?';
1427
            }
1428
            $a[] = $value;
1429
        }
1430
 
1431
        $this->_params['where'] = array_merge($this->_params['where'], $params);
1432
 
1433
        return $expr . ($not === true ? ' NOT' : '') . ' IN (' . implode(', ', $a) . ')';
1434
    }
1435
 
1436
    /**
1437
     * Adds NOT IN condition to the query WHERE part.
1438
     * <code>
1439
     * $q->whereNotIn('u.id', array(10, 20));
1440
     * // will exclude users with id 10 and 20 from the select
1441
     * </code>
1442
     *
1443
     * @param string $expr          the operand of the NOT IN
1444
     * @param mixed $params         an array of parameters or a simple scalar
1445
     * @return Doctrine_Query       this object
1446
     */
1447
    public function whereNotIn($expr, $params = array())
1448
    {
1449
        return $this->whereIn($expr, $params, true);
1450
    }
1451
 
1452
    /**
1453
     * Adds NOT IN condition to the query WHERE part
1454
     * Alias for @see whereNotIn().
1455
     *
1456
     * @param string $expr The operand of the NOT IN
1457
     * @param mixed $params An array of parameters or a simple scalar
1458
     * @return Doctrine_Query
1459
     */
1460
    public function andWhereNotIn($expr, $params = array())
1461
    {
1462
        return $this->andWhereIn($expr, $params, true);
1463
    }
1464
 
1465
    /**
1466
     * Adds NOT IN condition to the query WHERE part
1467
     *
1468
     * @param string $expr The operand of the NOT IN
1469
     * @param mixed $params An array of parameters or a simple scalar
1470
     * @return Doctrine_Query
1471
     */
1472
    public function orWhereNotIn($expr, $params = array())
1473
    {
1474
        return $this->orWhereIn($expr, $params, true);
1475
    }
1476
 
1477
    /**
1478
     * Adds fields to the GROUP BY part of the query.
1479
     * <code>
1480
     * $q->groupBy('u.id');
1481
     * </code>
1482
     *
1483
     * @param string $groupby       Query GROUP BY part
1484
     * @return Doctrine_Query
1485
     */
1486
    public function addGroupBy($groupby)
1487
    {
1488
        return $this->_addDqlQueryPart('groupby', $groupby, true);
1489
    }
1490
 
1491
    /**
1492
     * Adds conditions to the HAVING part of the query.
1493
     *
1494
     * This methods add HAVING clauses. These clauses are used to narrow the
1495
     * results by operating on aggregated values.
1496
     * <code>
1497
     * $q->having('num_phonenumbers > ?', 1);
1498
     * </code>
1499
     *
1500
     * @param string $having        Query HAVING part
1501
     * @param mixed $params         an array of parameters or a simple scalar
1502
     * @return Doctrine_Query
1503
     */
1504
    public function addHaving($having, $params = array())
1505
    {
1506
        if (is_array($params)) {
1507
            $this->_params['having'] = array_merge($this->_params['having'], $params);
1508
        } else {
1509
            $this->_params['having'][] = $params;
1510
        }
1511
        return $this->_addDqlQueryPart('having', $having, true);
1512
    }
1513
 
1514
    /**
1515
     * addOrderBy
1516
     * adds fields to the ORDER BY part of the query
1517
     *
1518
     * @param string $orderby       Query ORDER BY part
1519
     * @return Doctrine_Query
1520
     */
1521
    public function addOrderBy($orderby)
1522
    {
1523
        return $this->_addDqlQueryPart('orderby', $orderby, true);
1524
    }
1525
 
1526
    /**
1527
     * select
1528
     * sets the SELECT part of the query
1529
     *
1530
     * @param string $select        Query SELECT part
1531
     * @return Doctrine_Query
1532
     */
1533
    public function select($select = null)
1534
    {
1535
        $this->_type = self::SELECT;
1536
        if ($select) {
1537
            return $this->_addDqlQueryPart('select', $select);
1538
        } else {
1539
            return $this;
1540
        }
1541
    }
1542
 
1543
    /**
1544
     * distinct
1545
     * Makes the query SELECT DISTINCT.
1546
     * <code>
1547
     * $q->distinct();
1548
     * </code>
1549
     *
1550
     * @param bool $flag            Whether or not the SELECT is DISTINCT (default true).
1551
     * @return Doctrine_Query
1552
     */
1553
    public function distinct($flag = true)
1554
    {
1555
        $this->_sqlParts['distinct'] = (bool) $flag;
1556
        return $this;
1557
    }
1558
 
1559
    /**
1560
     * forUpdate
1561
     * Makes the query SELECT FOR UPDATE.
1562
     *
1563
     * @param bool $flag            Whether or not the SELECT is FOR UPDATE (default true).
1564
     * @return Doctrine_Query
1565
     */
1566
    public function forUpdate($flag = true)
1567
    {
1568
        $this->_sqlParts['forUpdate'] = (bool) $flag;
1569
        return $this;
1570
    }
1571
 
1572
    /**
1573
     * delete
1574
     * sets the query type to DELETE
1575
     *
1576
     * @return Doctrine_Query
1577
     */
1578
    public function delete($from = null)
1579
    {
1580
        $this->_type = self::DELETE;
1581
        if ($from != null) {
1582
            return $this->_addDqlQueryPart('from', $from);
1583
        }
1584
        return $this;
1585
    }
1586
 
1587
    /**
1588
     * update
1589
     * sets the UPDATE part of the query
1590
     *
1591
     * @param string $update        Query UPDATE part
1592
     * @return Doctrine_Query
1593
     */
1594
    public function update($from = null)
1595
    {
1596
        $this->_type = self::UPDATE;
1597
        if ($from != null) {
1598
            return $this->_addDqlQueryPart('from', $from);
1599
        }
1600
        return $this;
1601
    }
1602
 
1603
    /**
1604
     * set
1605
     * sets the SET part of the query
1606
     *
1607
     * @param string $update        Query UPDATE part
1608
     * @return Doctrine_Query
1609
     */
1610
    public function set($key, $value = null, $params = null)
1611
    {
1612
        if (is_array($key)) {
1613
            foreach ($key as $k => $v) {
1614
                $this->set($k, '?', array($v));
1615
            }
1616
            return $this;
1617
        } else {
1618
            if ($params !== null) {
1619
                if (is_array($params)) {
1620
                    $this->_params['set'] = array_merge($this->_params['set'], $params);
1621
                } else {
1622
                    $this->_params['set'][] = $params;
1623
                }
1624
            }
1625
 
1626
            return $this->_addDqlQueryPart('set', $key . ' = ' . $value, true);
1627
        }
1628
    }
1629
 
1630
    /**
1631
     * from
1632
     * sets the FROM part of the query
1633
     * <code>
1634
     * $q->from('User u');
1635
     * </code>
1636
     *
1637
     * @param string $from          Query FROM part
1638
     * @return Doctrine_Query
1639
     */
1640
    public function from($from)
1641
    {
1642
        return $this->_addDqlQueryPart('from', $from);
1643
    }
1644
 
1645
    /**
1646
     * innerJoin
1647
     * appends an INNER JOIN to the FROM part of the query
1648
     *
1649
     * @param string $join         Query INNER JOIN
1650
     * @return Doctrine_Query
1651
     */
1652
    public function innerJoin($join, $params = array())
1653
    {
1654
        if (is_array($params)) {
1655
            $this->_params['join'] = array_merge($this->_params['join'], $params);
1656
        } else {
1657
            $this->_params['join'][] = $params;
1658
        }
1659
 
1660
        return $this->_addDqlQueryPart('from', 'INNER JOIN ' . $join, true);
1661
    }
1662
 
1663
    /**
1664
     * leftJoin
1665
     * appends a LEFT JOIN to the FROM part of the query
1666
     *
1667
     * @param string $join         Query LEFT JOIN
1668
     * @return Doctrine_Query
1669
     */
1670
    public function leftJoin($join, $params = array())
1671
    {
1672
        if (is_array($params)) {
1673
            $this->_params['join'] = array_merge($this->_params['join'], $params);
1674
        } else {
1675
            $this->_params['join'][] = $params;
1676
        }
1677
 
1678
        return $this->_addDqlQueryPart('from', 'LEFT JOIN ' . $join, true);
1679
    }
1680
 
1681
    /**
1682
     * groupBy
1683
     * sets the GROUP BY part of the query
1684
     *
1685
     * @param string $groupby      Query GROUP BY part
1686
     * @return Doctrine_Query
1687
     */
1688
    public function groupBy($groupby)
1689
    {
1690
        return $this->_addDqlQueryPart('groupby', $groupby);
1691
    }
1692
 
1693
    /**
1694
     * where
1695
     * sets the WHERE part of the query
1696
     *
1697
     * @param string $join         Query WHERE part
1698
     * @param mixed $params        an array of parameters or a simple scalar
1699
     * @return Doctrine_Query
1700
     */
1701
    public function where($where, $params = array())
1702
    {
1703
        $this->_params['where'] = array();
1704
 
1705
        if (is_array($params)) {
1706
            $this->_params['where'] = $params;
1707
        } else {
1708
            $this->_params['where'][] = $params;
1709
        }
1710
 
1711
        return $this->_addDqlQueryPart('where', $where);
1712
    }
1713
 
1714
    /**
1715
     * having
1716
     * sets the HAVING part of the query
1717
     *
1718
     * @param string $having       Query HAVING part
1719
     * @param mixed $params        an array of parameters or a simple scalar
1720
     * @return Doctrine_Query
1721
     */
1722
    public function having($having, $params = array())
1723
    {
1724
        $this->_params['having'] = array();
1725
        if (is_array($params)) {
1726
            $this->_params['having'] = $params;
1727
        } else {
1728
            $this->_params['having'][] = $params;
1729
        }
1730
 
1731
        return $this->_addDqlQueryPart('having', $having);
1732
    }
1733
 
1734
    /**
1735
     * Sets the ORDER BY part of the query.
1736
     * <code>
1737
     * $q->orderBy('u.name');
1738
     * $query->orderBy('u.birthDate DESC');
1739
     * </code>
1740
     *
1741
     * @param string $orderby      Query ORDER BY part
1742
     * @return Doctrine_Query
1743
     */
1744
    public function orderBy($orderby)
1745
    {
1746
        return $this->_addDqlQueryPart('orderby', $orderby);
1747
    }
1748
 
1749
    /**
1750
     * limit
1751
     * sets the Query query limit
1752
     *
1753
     * @param integer $limit        limit to be used for limiting the query results
1754
     * @return Doctrine_Query
1755
     */
1756
    public function limit($limit)
1757
    {
1758
        return $this->_addDqlQueryPart('limit', $limit);
1759
    }
1760
 
1761
    /**
1762
     * offset
1763
     * sets the Query query offset
1764
     *
1765
     * @param integer $offset       offset to be used for paginating the query
1766
     * @return Doctrine_Query
1767
     */
1768
    public function offset($offset)
1769
    {
1770
        return $this->_addDqlQueryPart('offset', $offset);
1771
    }
1772
 
1773
    /**
1774
     * Resets all the sql parts.
1775
     *
1776
     * @return void
1777
     */
1778
    protected function clear()
1779
    {
1780
        $this->_sqlParts = array(
1781
                    'select'    => array(),
1782
                    'distinct'  => false,
1783
                    'forUpdate' => false,
1784
                    'from'      => array(),
1785
                    'set'       => array(),
1786
                    'join'      => array(),
1787
                    'where'     => array(),
1788
                    'groupby'   => array(),
1789
                    'having'    => array(),
1790
                    'orderby'   => array(),
1791
                    'limit'     => false,
1792
                    'offset'    => false,
1793
                    );
1794
    }
1795
 
1796
    public function setHydrationMode($hydrationMode)
1797
    {
1798
        $this->_hydrator->setHydrationMode($hydrationMode);
1799
        return $this;
1800
    }
1801
 
1802
    /**
1803
     * Gets the components of this query.
1804
     */
1805
    public function getQueryComponents()
1806
    {
1807
        return $this->_queryComponents;
1808
    }
1809
 
1810
    /**
1811
     * Return the SQL parts.
1812
     *
1813
     * @return array The parts
1814
     */
1815
    public function getSqlParts()
1816
    {
1817
        return $this->_sqlParts;
1818
    }
1819
 
1820
    /**
1821
     * getType
1822
     *
1823
     * returns the type of this query object
1824
     * by default the type is Doctrine_Query_Abstract::SELECT but if update() or delete()
1825
     * are being called the type is Doctrine_Query_Abstract::UPDATE and Doctrine_Query_Abstract::DELETE,
1826
     * respectively
1827
     *
1828
     * @see Doctrine_Query_Abstract::SELECT
1829
     * @see Doctrine_Query_Abstract::UPDATE
1830
     * @see Doctrine_Query_Abstract::DELETE
1831
     *
1832
     * @return integer      return the query type
1833
     */
1834
    public function getType()
1835
    {
1836
        return $this->_type;
1837
    }
1838
 
1839
    /**
1840
     * useResultCache
1841
     *
1842
     * @param Doctrine_Cache_Interface|bool $driver      cache driver
1843
     * @param integer $timeToLive                        how long the cache entry is valid
1844
     * @param string $resultCacheHash                     The key to use for storing the queries result cache entry
1845
     * @return Doctrine_Query         this object
1846
     */
1847
    public function useResultCache($driver = true, $timeToLive = null, $resultCacheHash = null)
1848
    {
1849
        if ($driver !== null && $driver !== true && ! ($driver instanceOf Doctrine_Cache_Interface)) {
1850
            $msg = 'First argument should be instance of Doctrine_Cache_Interface or null.';
1851
            throw new Doctrine_Query_Exception($msg);
1852
        }
1853
        $this->_resultCache = $driver;
1854
        $this->_resultCacheHash = $resultCacheHash;
1855
 
1856
        if ($timeToLive !== null) {
1857
            $this->setResultCacheLifeSpan($timeToLive);
1858
        }
1859
        return $this;
1860
    }
1861
 
1862
    /**
1863
     * Set the result cache hash to be used for storing the results in the cache driver
1864
     *
1865
     * @param string $resultCacheHash
1866
     * @return void
1867
     */
1868
    public function setResultCacheHash($resultCacheHash)
1869
    {
1870
        $this->_resultCacheHash = $resultCacheHash;
1871
 
1872
        return $this;
1873
    }
1874
 
1875
    /**
1876
     * Clear the result cache entry for this query
1877
     *
1878
     * @return void
1879
     */
1880
    public function clearResultCache()
1881
    {
1882
        $this->getResultCacheDriver()
1883
            ->delete($this->getResultCacheHash());
1884
 
1885
        return $this;
1886
    }
1887
 
1888
    /**
1889
     * useQueryCache
1890
     *
1891
     * @param Doctrine_Cache_Interface|bool $driver      cache driver
1892
     * @param integer $timeToLive                        how long the cache entry is valid
1893
     * @return Doctrine_Query         this object
1894
     */
1895
    public function useQueryCache($driver = true, $timeToLive = null)
1896
    {
1897
        if ($driver !== null && $driver !== true && $driver !== false && ! ($driver instanceOf Doctrine_Cache_Interface)) {
1898
            $msg = 'First argument should be instance of Doctrine_Cache_Interface or null.';
1899
            throw new Doctrine_Query_Exception($msg);
1900
        }
1901
        $this->_queryCache = $driver;
1902
 
1903
        if ($timeToLive !== null) {
1904
            $this->setQueryCacheLifeSpan($timeToLive);
1905
        }
1906
        return $this;
1907
    }
1908
 
1909
    /**
1910
     * expireCache
1911
     *
1912
     * @param boolean $expire       whether or not to force cache expiration
1913
     * @return Doctrine_Query     this object
1914
     */
1915
    public function expireResultCache($expire = true)
1916
    {
1917
        $this->_expireResultCache = $expire;
1918
        return $this;
1919
    }
1920
 
1921
    /**
1922
     * expireQueryCache
1923
     *
1924
     * @param boolean $expire       whether or not to force cache expiration
1925
     * @return Doctrine_Query     this object
1926
     */
1927
    public function expireQueryCache($expire = true)
1928
    {
1929
        $this->_expireQueryCache = $expire;
1930
        return $this;
1931
    }
1932
 
1933
    /**
1934
     * setResultCacheLifeSpan
1935
     *
1936
     * @param integer $timeToLive   how long the cache entry is valid (in seconds)
1937
     * @return Doctrine_Query     this object
1938
     */
1939
    public function setResultCacheLifeSpan($timeToLive)
1940
    {
1941
        if ($timeToLive !== null) {
1942
            $timeToLive = (int) $timeToLive;
1943
        }
1944
        $this->_resultCacheTTL = $timeToLive;
1945
 
1946
        return $this;
1947
    }
1948
 
1949
    /**
1950
     * Gets the life span of the result cache in seconds.
1951
     *
1952
     * @return integer
1953
     */
1954
    public function getResultCacheLifeSpan()
1955
    {
1956
        return $this->_resultCacheTTL;
1957
    }
1958
 
1959
    /**
1960
     * setQueryCacheLifeSpan
1961
     *
1962
     * @param integer $timeToLive   how long the cache entry is valid
1963
     * @return Doctrine_Query     this object
1964
     */
1965
    public function setQueryCacheLifeSpan($timeToLive)
1966
    {
1967
        if ($timeToLive !== null) {
1968
            $timeToLive = (int) $timeToLive;
1969
        }
1970
        $this->_queryCacheTTL = $timeToLive;
1971
 
1972
        return $this;
1973
    }
1974
 
1975
    /**
1976
     * Gets the life span of the query cache the Query object is using.
1977
     *
1978
     * @return integer  The life span in seconds.
1979
     */
1980
    public function getQueryCacheLifeSpan()
1981
    {
1982
        return $this->_queryCacheTTL;
1983
    }
1984
 
1985
    /**
1986
     * getResultCacheDriver
1987
     * returns the cache driver used for caching result sets
1988
     *
1989
     * @return Doctrine_Cache_Interface|boolean|null    cache driver
1990
     */
1991
    public function getResultCacheDriver()
1992
    {
1993
        if ($this->_resultCache instanceof Doctrine_Cache_Interface) {
1994
            return $this->_resultCache;
1995
        } else {
1996
            return $this->_conn->getResultCacheDriver();
1997
        }
1998
    }
1999
 
2000
    /**
2001
     * getQueryCacheDriver
2002
     * returns the cache driver used for caching queries
2003
     *
2004
     * @return Doctrine_Cache_Interface|boolean|null    cache driver
2005
     */
2006
    public function getQueryCacheDriver()
2007
    {
2008
        if ($this->_queryCache instanceof Doctrine_Cache_Interface) {
2009
            return $this->_queryCache;
2010
        } else {
2011
            return $this->_conn->getQueryCacheDriver();
2012
        }
2013
    }
2014
 
2015
    /**
2016
     * getConnection
2017
     *
2018
     * @return Doctrine_Connection
2019
     */
2020
    public function getConnection()
2021
    {
2022
        return $this->_conn;
2023
    }
2024
 
2025
    /**
2026
     * Checks if there's at least one DQL part defined to the internal parts collection.
2027
     *
2028
     * @param string $queryPartName  The name of the query part.
2029
     * @return boolean
2030
     */
2031
    protected function _hasDqlQueryPart($queryPartName)
2032
    {
2033
        return count($this->_dqlParts[$queryPartName]) > 0;
2034
    }
2035
 
2036
    /**
2037
     * Adds a DQL part to the internal parts collection.
2038
     *
2039
     * This method add the part specified to the array named by $queryPartName.
2040
     * Most part names support multiple parts addition.
2041
     *
2042
     * @see $_dqlParts;
2043
     * @see Doctrine_Query::getDqlPart()
2044
     * @param string $queryPartName  The name of the query part.
2045
     * @param string $queryPart      The actual query part to add.
2046
     * @param boolean $append        Whether to append $queryPart to already existing
2047
     *                               parts under the same $queryPartName. Defaults to FALSE
2048
     *                               (previously added parts with the same name get overridden).
2049
     */
2050
    protected function _addDqlQueryPart($queryPartName, $queryPart, $append = false)
2051
    {
2052
        // We should prevent nullable query parts
2053
        if ($queryPart === null) {
2054
            throw new Doctrine_Query_Exception('Cannot define NULL as part of query when defining \'' . $queryPartName . '\'.');
2055
        }
2056
 
2057
        if ($append) {
2058
            $this->_dqlParts[$queryPartName][] = $queryPart;
2059
        } else {
2060
            $this->_dqlParts[$queryPartName] = array($queryPart);
2061
        }
2062
 
2063
        $this->_state = Doctrine_Query::STATE_DIRTY;
2064
        return $this;
2065
    }
2066
 
2067
    /**
2068
     * _processDqlQueryPart
2069
     * parses given query part
2070
     *
2071
     * @param string $queryPartName     the name of the query part
2072
     * @param array $queryParts         an array containing the query part data
2073
     * @return Doctrine_Query           this object
2074
     * @todo Better description. "parses given query part" ??? Then wheres the difference
2075
     *       between process/parseQueryPart? I suppose this does something different.
2076
     */
2077
    protected function _processDqlQueryPart($queryPartName, $queryParts)
2078
    {
2079
        $this->removeSqlQueryPart($queryPartName);
2080
 
2081
        if (is_array($queryParts) && ! empty($queryParts)) {
2082
            foreach ($queryParts as $queryPart) {
2083
                $parser = $this->_getParser($queryPartName);
2084
                $sql = $parser->parse($queryPart);
2085
                if (isset($sql)) {
2086
                    if ($queryPartName == 'limit' || $queryPartName == 'offset') {
2087
                        $this->setSqlQueryPart($queryPartName, $sql);
2088
                    } else {
2089
                        $this->addSqlQueryPart($queryPartName, $sql);
2090
                    }
2091
                }
2092
            }
2093
        }
2094
    }
2095
 
2096
    /**
2097
     * _getParser
2098
     * parser lazy-loader
2099
     *
2100
     * @throws Doctrine_Query_Exception     if unknown parser name given
2101
     * @return Doctrine_Query_Part
2102
     * @todo Doc/Description: What is the parameter for? Which parsers are available?
2103
     */
2104
    protected function _getParser($name)
2105
    {
2106
        if ( ! isset($this->_parsers[$name])) {
2107
            $class = 'Doctrine_Query_' . ucwords(strtolower($name));
2108
 
2109
            Doctrine_Core::autoload($class);
2110
 
2111
            if ( ! class_exists($class)) {
2112
                throw new Doctrine_Query_Exception('Unknown parser ' . $name);
2113
            }
2114
 
2115
            $this->_parsers[$name] = new $class($this, $this->_tokenizer);
2116
        }
2117
 
2118
        return $this->_parsers[$name];
2119
    }
2120
 
2121
    /**
2122
     * Gets the SQL query that corresponds to this query object.
2123
     * The returned SQL syntax depends on the connection driver that is used
2124
     * by this query object at the time of this method call.
2125
     *
2126
     * @param array $params
2127
     */
2128
    abstract public function getSqlQuery($params = array());
2129
 
2130
    /**
2131
     * parseDqlQuery
2132
     * parses a dql query
2133
     *
2134
     * @param string $query         query to be parsed
2135
     * @return Doctrine_Query_Abstract  this object
2136
     */
2137
    abstract public function parseDqlQuery($query);
2138
 
2139
    /**
2140
     * toString magic call
2141
     * this method is automatically called when Doctrine_Query object is trying to be used as a string
2142
     * So, it it converted into its DQL correspondant
2143
     *
2144
     * @return string DQL string
2145
     */
2146
    public function __toString()
2147
    {
2148
        return $this->getDql();
2149
    }
2150
 
2151
    /**
2152
     * Gets the disableLimitSubquery property.
2153
     *
2154
     * @return boolean
2155
     */
2156
    public function getDisableLimitSubquery()
2157
    {
2158
        return $this->disableLimitSubquery;
2159
    }
2160
 
2161
    /**
2162
     * Allows you to set the disableLimitSubquery property -- setting this to true will
2163
     * restrict the query object from using the limit sub query method of tranversing many relationships.
2164
     *
2165
     * @param boolean $disableLimitSubquery
2166
     */
2167
    public function setDisableLimitSubquery($disableLimitSubquery)
2168
    {
2169
        $this->disableLimitSubquery = $disableLimitSubquery;
2170
    }
2171
}