Coverage for Doctrine_Hydrate

Back to coverage report

1 <?php
2 /*
3  *  $Id: Hydrate.php 3032 2007-10-29 19:50:16Z meus $
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.org>.
20  */
21
22 /**
23  * Doctrine_Hydrate is a base class for Doctrine_RawSql and Doctrine_Query.
24  * Its purpose is to populate object graphs.
25  *
26  *
27  * @package     Doctrine
28  * @subpackage  Hydrate
29  * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
30  * @link        www.phpdoctrine.org
31  * @since       1.0
32  * @version     $Revision: 3032 $
33  * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
34  */
35 class Doctrine_Hydrate extends Doctrine_Locator_Injectable implements Serializable
36 {
37     /**
38      * QUERY TYPE CONSTANTS
39      */
40
41     /**
42      * constant for SELECT queries
43      */
44     const SELECT = 0;
45
46     /**
47      * constant for DELETE queries
48      */
49     const DELETE = 1;
50
51     /**
52      * constant for UPDATE queries
53      */
54     const UPDATE = 2;
55
56     /**
57      * constant for INSERT queries
58      */
59     const INSERT = 3;
60
61     /**
62      * constant for CREATE queries
63      */
64     const CREATE = 4;
65
66     /**
67      * @var array $params                       query input parameters
68      */
69     protected $_params      = array('where' => array(),
70                                     'set' => array(),
71                                     'having' => array());
72
73     /**
74      * @var Doctrine_Connection $conn           Doctrine_Connection object
75      */
76     protected $_conn;
77
78     /**
79      * @var Doctrine_View $_view                Doctrine_View object, when set this object will use the
80      *                                          the query given by the view object for object population
81      */
82     protected $_view;
83
84     /**
85      * @var array $_aliasMap                    two dimensional array containing the map for query aliases
86      *      Main keys are component aliases
87      *
88      *          table               table object associated with given alias
89      *
90      *          relation            the relation object owned by the parent
91      *
92      *          parent              the alias of the parent
93      *
94      *          agg                 the aggregates of this component
95      *
96      *          map                 the name of the column / aggregate value this
97      *                              component is mapped to a collection
98      */
99     protected $_aliasMap         = array();
100
101     /**
102      *
103      */
104     protected $pendingAggregates = array();
105
106     /**
107      * @var array $aggregateMap             an array containing all aggregate aliases, keys as dql aliases
108      *                                      and values as sql aliases
109      */
110     protected $aggregateMap      = array();
111
112     /**
113      * @var array $_options                 an array of options
114      */
115     protected $_options    = array(
116                             'fetchMode'      => Doctrine::FETCH_RECORD,
117                             'parserCache'    => false,
118                             'resultSetCache' => false,
119                             );
120
121     /**
122      * @var string $_sql            cached SQL query
123      */
124     protected $_sql;
125
126     /**
127      * @var array $parts            SQL query string parts
128      */
129     protected $parts = array(
130         'select'    => array(),
131         'distinct'  => false,
132         'forUpdate' => false,
133         'from'      => array(),
134         'set'       => array(),
135         'join'      => array(),
136         'where'     => array(),
137         'groupby'   => array(),
138         'having'    => array(),
139         'orderby'   => array(),
140         'limit'     => false,
141         'offset'    => false,
142         );
143
144     /**
145      * @var integer $type                   the query type
146      *
147      * @see Doctrine_Query::* constants
148      */
149     protected $type            = self::SELECT;
150
151     /**
152      * @var array
153      */
154     protected $_cache;
155
156     /**
157      * The current hydration mode.
158      */
159     protected $_hydrationMode = Doctrine::HYDRATE_RECORD;
160
161     /**
162      * @var boolean $_expireCache           a boolean value that indicates whether or not to force cache expiration
163      */
164     protected $_expireCache     = false;
165
166     protected $_timeToLive;
167
168     protected $_tableAliases    = array();
169
170     /**
171      * @var array $_tableAliasSeeds         A simple array keys representing table aliases and values
172      *                                      as table alias seeds. The seeds are used for generating short table
173      *                                      aliases.
174      */
175     protected $_tableAliasSeeds = array();
176
177     /**
178      * constructor
179      *
180      * @param Doctrine_Connection|null $connection
181      */
182     public function __construct($connection = null)
183     {
184         if ( ! ($connection instanceof Doctrine_Connection)) {
185             $connection = Doctrine_Manager::getInstance()->getCurrentConnection();
186         }
187         $this->_conn = $connection;
188     }
189
190     /**
191      * getRootAlias
192      * returns the alias of the the root component
193      *
194      * @return array
195      */
196     public function getRootAlias()
197     {
198         if ( ! $this->_aliasMap) {
199           $this->getSql();
200         }
201         
202         reset($this->_aliasMap);
203
204         return key($this->_aliasMap);
205     }
206
207     /**
208      * getRootDeclaration
209      * returns the root declaration
210      *
211      * @return array
212      */
213     public function getRootDeclaration()
214     {
215         $map = reset($this->_aliasMap);
216
217         return $map;
218     }
219
220     /**
221      * getRoot
222      * returns the root component for this object
223      *
224      * @return Doctrine_Table       root components table
225      */
226     public function getRoot()
227     {
228         $map = reset($this->_aliasMap);
229
230         if ( ! isset($map['table'])) {
231             throw new Doctrine_Hydrate_Exception('Root component not initialized.');
232         }
233
234         return $map['table'];
235     }
236
237     /**
238      * getSql
239      * return the sql associated with this object
240      *
241      * @return string   sql query string
242      */
243     public function getSql()
244     {
245         return $this->getQuery();
246     }
247
248     /**
249      * useCache
250      *
251      * @param Doctrine_Cache_Interface|bool $driver      cache driver
252      * @param integer $timeToLive                        how long the cache entry is valid
253      * @return Doctrine_Hydrate         this object
254      */
255     public function useCache($driver = true, $timeToLive = null)
256     {
257         if ($driver !== null) {
258             if ($driver !== true) {
259                 if ( ! ($driver instanceof Doctrine_Cache_Interface)) {
260                     $msg = 'First argument should be instance of Doctrine_Cache_Interface or null.';
261
262                     throw new Doctrine_Hydrate_Exception($msg);
263                 }
264             }
265         }
266         $this->_cache = $driver;
267
268         return $this->setCacheLifeSpan($timeToLive);
269     }
270
271     /**
272      * expireCache
273      *
274      * @param boolean $expire       whether or not to force cache expiration
275      * @return Doctrine_Hydrate     this object
276      */
277     public function expireCache($expire = true)
278     {
279         $this->_expireCache = true;
280
281         return $this;
282     }
283
284     /**
285      * setCacheLifeSpan
286      *
287      * @param integer $timeToLive   how long the cache entry is valid
288      * @return Doctrine_Hydrate     this object
289      */
290     public function setCacheLifeSpan($timeToLive)
291     {
292         if ($timeToLive !== null) {
293             $timeToLive = (int) $timeToLive;
294         }
295         $this->_timeToLive = $timeToLive;
296
297         return $this;
298     }
299
300     /**
301      * getCacheDriver
302      * returns the cache driver associated with this object
303      *
304      * @return Doctrine_Cache_Interface|boolean|null    cache driver
305      */
306     public function getCacheDriver()
307     {
308         if ($this->_cache instanceof Doctrine_Cache_Interface) {
309             return $this->_cache;
310         } else {
311             return $this->_conn->getCacheDriver();
312         }
313     }
314
315     /**
316      * Sets the fetchmode.
317      *
318      * @param integer $fetchmode  One of the Doctrine::HYDRATE_* constants.
319      */
320     public function setHydrationMode($hydrationMode)
321     {
322         $this->_hydrationMode = $hydrationMode;
323         return $this;
324     }
325
326     /**
327      * serialize
328      * this method is automatically called when this Doctrine_Hydrate is serialized
329      *
330      * @return array    an array of serialized properties
331      */
332     public function serialize()
333     {
334         $vars = get_object_vars($this);
335
336     }
337
338     /**
339      * unseralize
340      * this method is automatically called everytime a Doctrine_Hydrate object is unserialized
341      *
342      * @param string $serialized                Doctrine_Record as serialized string
343      * @return void
344      */
345     public function unserialize($serialized)
346     {
347
348     }
349
350     /**
351      * generateNewTableAlias
352      * generates a new alias from given table alias
353      *
354      * @param string $tableAlias    table alias from which to generate the new alias from
355      * @return string               the created table alias
356      */
357     public function generateNewTableAlias($tableAlias)
358     {
359         if (isset($this->_tableAliases[$tableAlias])) {
360             // generate a new alias
361             $name = substr($tableAlias, 0, 1);
362             $i    = ((int) substr($tableAlias, 1));
363
364             if ($i == 0) {
365                 $i = 1;
366             }
367
368             $newIndex  = ($this->_tableAliasSeeds[$name] + $i);
369
370             return $name . $newIndex;
371         }
372
373         return $tableAlias;
374     }
375
376     /**
377      * hasTableAlias
378      * whether or not this object has given tableAlias
379      *
380      * @param string $tableAlias    the table alias to be checked
381      * @return boolean              true if this object has given alias, otherwise false
382      */
383     public function hasTableAlias($tableAlias)
384     {
385         return (isset($this->_tableAliases[$tableAlias]));
386     }
387
388     /**
389      * getComponentAlias
390      * get component alias associated with given table alias
391      *
392      * @param string $tableAlias    the table alias that identifies the component alias
393      * @return string               component alias
394      */
395     public function getComponentAlias($tableAlias)
396     {
397         if ( ! isset($this->_tableAliases[$tableAlias])) {
398             throw new Doctrine_Hydrate_Exception('Unknown table alias ' . $tableAlias);
399         }
400         return $this->_tableAliases[$tableAlias];
401     }
402
403     /**
404      * getTableAliasSeed
405      * returns the alias seed for given table alias
406      *
407      * @param string $tableAlias    table alias that identifies the alias seed
408      * @return integer              table alias seed
409      */
410     public function getTableAliasSeed($tableAlias)
411     {
412         if ( ! isset($this->_tableAliasSeeds[$tableAlias])) {
413             return 0;
414         }
415         return $this->_tableAliasSeeds[$tableAlias];
416     }
417
418     /**
419      * generateTableAlias
420      * generates a table alias from given table name and associates 
421      * it with given component alias
422      *
423      * @param string $componentAlias    the component alias to be associated with generated table alias
424      * @param string $tableName         the table name from which to generate the table alias
425      * @return string                   the generated table alias
426      */
427     public function generateTableAlias($componentAlias, $tableName)
428     {
429         $char   = strtolower(substr($tableName, 0, 1));
430
431         $alias  = $char;
432
433         if ( ! isset($this->_tableAliasSeeds[$alias])) {
434             $this->_tableAliasSeeds[$alias] = 1;
435         }
436
437         while (isset($this->_tableAliases[$alias])) {
438             if ( ! isset($this->_tableAliasSeeds[$alias])) {
439                 $this->_tableAliasSeeds[$alias] = 1;
440             }
441             $alias = $char . ++$this->_tableAliasSeeds[$alias];
442         }
443
444         $this->_tableAliases[$alias] = $componentAlias;
445
446         return $alias;
447     }
448
449     /**
450      * getTableAliases
451      * returns all table aliases
452      *
453      * @return array        table aliases as an array
454      */
455     public function getTableAliases()
456     {
457         return $this->_tableAliases;
458     }
459
460     /** 
461      * addTableAlias
462      * adds an alias for table and associates it with given component alias
463      *
464      * @param string $componentAlias    the alias for the query component associated with given tableAlias
465      * @param string $tableAlias        the table alias to be added
466      * @return Doctrine_Hydrate
467      */
468     public function addTableAlias($tableAlias, $componentAlias)
469     {
470         $this->_tableAliases[$tableAlias] = $componentAlias;
471
472         return $this;
473     }
474
475     /**
476      * getTableAlias
477      * some database such as Oracle need the identifier lengths to be < ~30 chars
478      * hence Doctrine creates as short identifier aliases as possible
479      *
480      * this method is used for the creation of short table aliases, its also
481      * smart enough to check if an alias already exists for given component (componentAlias)
482      *
483      * @param string $componentAlias    the alias for the query component to search table alias for
484      * @param string $tableName         the table name from which the table alias is being created
485      * @return string                   the generated / fetched short alias
486      */
487     public function getTableAlias($componentAlias, $tableName = null)
488     {
489         $alias = array_search($componentAlias, $this->_tableAliases);
490
491         if ($alias !== false) {
492             return $alias;
493         }
494
495         if ($tableName === null) {
496             throw new Doctrine_Hydrate_Exception("Couldn't get short alias for " . $componentAlias);
497         }
498
499         return $this->generateTableAlias($componentAlias, $tableName);
500     }
501
502     /**
503      * addQueryPart
504      * adds a query part in the query part array
505      *
506      * @param string $name          the name of the query part to be added
507      * @param string $part          query part string
508      * @throws Doctrine_Hydrate_Exception   if trying to add unknown query part
509      * @return Doctrine_Hydrate     this object
510      */
511     public function addQueryPart($name, $part)
512     {
513         if ( ! isset($this->parts[$name])) {
514             throw new Doctrine_Hydrate_Exception('Unknown query part ' . $name);
515         }
516         if (is_array($part)) {
517             $this->parts[$name] = array_merge($this->parts[$name], $part);
518         } else {
519             $this->parts[$name][] = $part;
520         }
521         return $this;
522     }
523
524     /**
525      * setQueryPart
526      * sets a query part in the query part array
527      *
528      * @param string $name          the name of the query part to be set
529      * @param string $part          query part string
530      * @throws Doctrine_Hydrate_Exception   if trying to set unknown query part
531      * @return Doctrine_Hydrate     this object
532      */
533     public function getQueryPart($part)
534     {
535         if ( ! isset($this->parts[$part])) {
536             throw new Doctrine_Hydrate_Exception('Unknown query part ' . $part);
537         }
538
539         return $this->parts[$part];
540     }
541
542     /**
543      * removeQueryPart
544      * removes a query part from the query part array
545      *
546      * @param string $name          the name of the query part to be removed
547      * @throws Doctrine_Hydrate_Exception   if trying to remove unknown query part
548      * @return Doctrine_Hydrate     this object
549      */
550     public function removeQueryPart($name)
551     {
552         if (isset($this->parts[$name])) {
553             if ($name == 'limit' || $name == 'offset') {
554                 $this->parts[$name] = false;
555             } else {
556                 $this->parts[$name] = array();
557             }
558         } else {
559             throw new Doctrine_Hydrate_Exception('Unknown query part ' . $name);
560         }
561         return $this;
562     }
563
564     /**
565      * setQueryPart
566      * sets a query part in the query part array
567      *
568      * @param string $name          the name of the query part to be set
569      * @param string $part          query part string
570      * @throws Doctrine_Hydrate_Exception   if trying to set unknown query part
571      * @return Doctrine_Hydrate     this object
572      */
573     public function setQueryPart($name, $part)
574     {
575         if ( ! isset($this->parts[$name])) {
576             throw new Doctrine_Hydrate_Exception('Unknown query part ' . $name);
577         }
578
579         if ($name !== 'limit' && $name !== 'offset') {
580             if (is_array($part)) {
581                 $this->parts[$name] = $part;
582             } else {
583                 $this->parts[$name] = array($part);
584             }
585         } else {
586             $this->parts[$name] = $part;
587         }
588
589         return $this;
590     }
591
592     /**
593      * hasAliasDeclaration
594      * whether or not this object has a declaration for given component alias
595      *
596      * @param string $componentAlias    the component alias the retrieve the declaration from
597      * @return boolean
598      */
599     public function hasAliasDeclaration($componentAlias)
600     {
601         return isset($this->_aliasMap[$componentAlias]);
602     }
603
604     /**
605      * getAliasDeclaration
606      * get the declaration for given component alias
607      *
608      * @param string $componentAlias    the component alias the retrieve the declaration from
609      * @return array                    the alias declaration
610      */
611     public function getAliasDeclaration($componentAlias)
612     {
613         if ( ! isset($this->_aliasMap[$componentAlias])) {
614             throw new Doctrine_Hydrate_Exception('Unknown component alias ' . $componentAlias);
615         }
616
617         return $this->_aliasMap[$componentAlias];
618     }
619
620     /**
621      * copyAliases
622      * copy aliases from another Hydrate object
623      *
624      * this method is needed by DQL subqueries which need the aliases
625      * of the parent query
626      *
627      * @param Doctrine_Hydrate $query   the query object from which the
628      *                                  aliases are copied from
629      * @return Doctrine_Hydrate         this object
630      */
631     public function copyAliases(Doctrine_Hydrate $query)
632     {
633         $this->_tableAliases = $query->_tableAliases;
634         $this->_aliasMap     = $query->_aliasMap;
635         $this->_tableAliasSeeds = $query->_tableAliasSeeds;
636         return $this;
637     }
638
639     /**
640      * createSubquery
641      * creates a subquery
642      *
643      * @return Doctrine_Hydrate
644      */
645     public function createSubquery()
646     {
647         $class = get_class($this);
648         $obj   = new $class();
649
650         // copy the aliases to the subquery
651         $obj->copyAliases($this);
652
653         // this prevents the 'id' being selected, re ticket #307
654         $obj->isSubquery(true);
655
656         return $obj;
657     }
658
659     /**
660      * limitSubqueryUsed
661      * whether or not limit subquery was used
662      *
663      * @return boolean
664      */
665     public function isLimitSubqueryUsed()
666     {
667         return false;
668     }
669
670     /**
671      * clear
672      * resets all the variables
673      *
674      * @return void
675      */
676     protected function clear()
677     {
678         $this->parts = array(
679                     'select'    => array(),
680                     'distinct'  => false,
681                     'forUpdate' => false,
682                     'from'      => array(),
683                     'set'       => array(),
684                     'join'      => array(),
685                     'where'     => array(),
686                     'groupby'   => array(),
687                     'having'    => array(),
688                     'orderby'   => array(),
689                     'limit'     => false,
690                     'offset'    => false,
691                     );
692         $this->inheritanceApplied = false;
693     }
694
695     /**
696      * getConnection
697      *
698      * @return Doctrine_Connection
699      */
700     public function getConnection()
701     {
702         return $this->_conn;
703     }
704
705     /**
706      * setView
707      * sets a database view this query object uses
708      * this method should only be called internally by doctrine
709      *
710      * @param Doctrine_View $view       database view
711      * @return void
712      */
713     public function setView(Doctrine_View $view)
714     {
715         $this->_view = $view;
716     }
717
718     /**
719      * getView
720      * returns the view associated with this query object (if any)
721      *
722      * @return Doctrine_View        the view associated with this query object
723      */
724     public function getView()
725     {
726         return $this->_view;
727     }
728
729     /**
730      * getParams
731      *
732      * @return array
733      */
734     public function getParams()
735     {
736         return array_merge($this->_params['set'], $this->_params['where'], $this->_params['having']);
737     }
738
739     /**
740      * setParams
741      *
742      * @param array $params
743      */
744     public function setParams(array $params = array()) {
745         $this->_params = $params;
746     }
747     public function convertEnums($params)
748     {
749         return $params;
750     }
751
752     /**
753      * setAliasMap
754      * sets the whole component alias map
755      *
756      * @param array $map            alias map
757      * @return Doctrine_Hydrate     this object
758      */
759     public function setAliasMap(array $map)
760     {
761         $this->_aliasMap = $map;
762
763         return $this;
764     }
765
766     /**
767      * getAliasMap
768      * returns the component alias map
769      *
770      * @return array    component alias map
771      */
772     public function getAliasMap()
773     {
774         return $this->_aliasMap;
775     }
776
777     /**
778      * getCachedForm
779      * returns the cached form of this query for given resultSet
780      *
781      * @param array $resultSet
782      * @return string           serialized string representation of this query
783      */
784     public function getCachedForm(array $resultSet)
785     {
786         $map = '';
787
788         foreach ($this->getAliasMap() as $k => $v) {
789             if ( ! isset($v['parent'])) {
790                 $map[$k][] = $v['table']->getComponentName();
791             } else {
792                 $map[$k][] = $v['parent'] . '.' . $v['relation']->getAlias();
793             }
794             if (isset($v['agg'])) {
795                 $map[$k][] = $v['agg'];
796             }
797         }
798
799         return serialize(array($resultSet, $map, $this->getTableAliases()));
800     }
801     public function _execute($params)
802     {
803         $params = $this->_conn->convertBooleans($params);
804
805         if ( ! $this->_view) {
806             $query = $this->getQuery($params);
807         } else {
808             $query = $this->_view->getSelectSql();
809         }
810
811         $params = $this->convertEnums($params);
812
813         if ($this->isLimitSubqueryUsed() &&
814             $this->_conn->getAttribute(Doctrine::ATTR_DRIVER_NAME) !== 'mysql') {
815
816             $params = array_merge($params, $params);
817         }
818
819         if ($this->type !== self::SELECT) {
820             return $this->_conn->exec($query, $params);
821         }
822
823         $stmt = $this->_conn->execute($query, $params);
824         return $stmt;
825     }
826
827     /**
828      * execute
829      * executes the query and populates the data set
830      *
831      * @param string $params
832      * @return Doctrine_Collection            the root collection
833      */
834     public function execute($params = array(), $hydrationMode = null)
835     {
836         $params = array_merge($this->_params['set'], 
837                               $this->_params['where'],
838                               $this->_params['having'], 
839                               $params);
840         if ($this->_cache) {
841             $cacheDriver = $this->getCacheDriver();
842
843             $dql  = $this->getDql();
844             // calculate hash for dql query
845             $hash = md5($dql . var_export($params, true));
846
847             $cached = ($this->_expireCache) ? false : $cacheDriver->fetch($hash);
848
849
850             if ($cached === false) {
851                 // cache miss
852                 $stmt = $this->_execute($params);
853                 $array = $this->parseData2($stmt, Doctrine::HYDRATE_ARRAY);
854
855                 $cached = $this->getCachedForm($array);
856
857                 $cacheDriver->save($hash, $cached, $this->_timeToLive);
858             } else {
859                 $cached = unserialize($cached);
860                 $this->_tableAliases = $cached[2];
861                 $array = $cached[0];
862
863                 $map   = array();
864                 foreach ($cached[1] as $k => $v) {
865                     $e = explode('.', $v[0]);
866                     if (count($e) === 1) {
867                         $map[$k]['table'] = $this->_conn->getTable($e[0]);
868                     } else {
869                         $map[$k]['parent']   = $e[0];
870                         $map[$k]['relation'] = $map[$e[0]]['table']->getRelation($e[1]);
871                         $map[$k]['table']    = $map[$k]['relation']->getTable();
872                     }
873                     if (isset($v[1])) {
874                         $map[$k]['agg'] = $v[1];
875                     }
876                 }
877                 $this->_aliasMap = $map;
878             }
879         } else {
880             $stmt = $this->_execute($params);
881
882             if (is_integer($stmt)) {
883                 return $stmt;
884             }
885             
886             $array = $this->parseData2($stmt, $hydrationMode);
887         }
888         return $array;
889     }
890
891     /**
892      * getType
893      *
894      * returns the type of this query object
895      * by default the type is Doctrine_Hydrate::SELECT but if update() or delete()
896      * are being called the type is Doctrine_Hydrate::UPDATE and Doctrine_Hydrate::DELETE,
897      * respectively
898      *
899      * @see Doctrine_Hydrate::SELECT
900      * @see Doctrine_Hydrate::UPDATE
901      * @see Doctrine_Hydrate::DELETE
902      *
903      * @return integer      return the query type
904      */
905     public function getType()
906     {
907         return $this->type;
908     }
909
910     /**
911      * applyInheritance
912      * applies column aggregation inheritance to DQL / SQL query
913      *
914      * @return string
915      */
916     public function applyInheritance()
917     {
918         // get the inheritance maps
919         $array = array();
920
921         foreach ($this->_aliasMap as $componentAlias => $data) {
922             $tableAlias = $this->getTableAlias($componentAlias);
923             $array[$tableAlias][] = $data['table']->inheritanceMap;
924         }
925
926         // apply inheritance maps
927         $str = '';
928         $c = array();
929
930         $index = 0;
931         foreach ($array as $tableAlias => $maps) {
932             $a = array();
933
934             // don't use table aliases if the query isn't a select query
935             if ($this->type !== Doctrine_Query::SELECT) {
936                 $tableAlias = '';
937             } else {
938                 $tableAlias .= '.';
939             }
940
941             foreach ($maps as $map) {
942                 $b = array();
943                 foreach ($map as $field => $value) {
944                     $identifier = $this->_conn->quoteIdentifier($tableAlias . $field);
945
946                     if ($index > 0) {
947                         $b[] = '(' . $identifier . ' = ' . $this->_conn->quote($value)
948                              . ' OR ' . $identifier . ' IS NULL)';
949                     } else {
950                         $b[] = $identifier . ' = ' . $this->_conn->quote($value);
951                     }
952                 }
953
954                 if ( ! empty($b)) {
955                     $a[] = implode(' AND ', $b);
956                 }
957             }
958
959             if ( ! empty($a)) {
960                 $c[] = implode(' AND ', $a);
961             }
962             $index++;
963         }
964
965         $str .= implode(' AND ', $c);
966
967         return $str;
968     }
969
970     /**
971      * fetchArray
972      * Convenience method to execute using array fetching as hydration mode.
973      *
974      * @param string $params
975      * @return array
976      */
977     public function fetchArray($params = array()) {
978         return $this->execute($params, Doctrine::HYDRATE_ARRAY);
979     }
980
981     /**
982      * fetchOne
983      * Convenience method to execute the query and return the first item
984      * of the collection.
985      *
986      * @param string $params Parameters
987      * @param int $hydrationMode Hydration mode
988      * @return mixed Array or Doctrine_Collection or false if no result.
989      */
990     public function fetchOne($params = array(), $hydrationMode = null)
991     {
992         if (is_null($hydrationMode)) {
993             $hydrationMode = $this->_hydrationMode;
994         }
995
996         $collection = $this->execute($params, $hydrationMode);
997
998         if (count($collection) === 0) {
999             return false;
1000         }
1001
1002         switch ($hydrationMode) {
1003             case Doctrine::HYDRATE_RECORD:
1004                 return $collection->getFirst();
1005             case Doctrine::HYDRATE_ARRAY:
1006                 return array_shift($collection);
1007         }
1008
1009         return false;
1010     }
1011
1012     /**
1013      * parseData
1014      * parses the data returned by statement object
1015      *
1016      * This is method defines the core of Doctrine object population algorithm
1017      * hence this method strives to be as fast as possible
1018      *
1019      * The key idea is the loop over the rowset only once doing all the needed operations
1020      * within this massive loop.
1021      *
1022      * @param mixed $stmt
1023      * @return array
1024      */
1025     public function parseData2($stmt, $hydrationMode)
1026     {
1027         if ($hydrationMode == Doctrine::HYDRATE_NONE) {
1028             return $stmt->fetchAll(PDO::FETCH_NUM);
1029         }
1030         
1031         $cache = array();
1032         $rootMap   = reset($this->_aliasMap);
1033         $rootAlias = key($this->_aliasMap);
1034         $componentName = $rootMap['table']->getComponentName();
1035         $isSimpleQuery = count($this->_aliasMap) <= 1;
1036         
1037         if ($hydrationMode === null) {
1038             $hydrationMode = $this->_hydrationMode;
1039         }
1040
1041         if ($hydrationMode === Doctrine::HYDRATE_ARRAY) {
1042             $driver = new Doctrine_Hydrate_Array();
1043         } else {
1044             $driver = new Doctrine_Hydrate_Record();
1045         }
1046
1047         $array = $driver->getElementCollection($componentName);
1048
1049         if ($stmt === false || $stmt === 0) {
1050             return $array;
1051         }
1052
1053         $event = new Doctrine_Event(null, Doctrine_Event::HYDRATE, null);
1054
1055         // for every getRecordListener() there is a little bit 
1056         // logic behind it, hence calling it multiple times on
1057         // large result sets can be quite expensive.
1058         // So for efficiency we use little listener caching here
1059         foreach ($this->_aliasMap as $alias => $data) {
1060             $componentName = $data['table']->getComponentName();
1061             $listeners[$componentName] = $data['table']->getRecordListener();
1062             $identifierMap[$alias] = array();
1063             $currData[$alias] = array();
1064             $prev[$alias] = array();
1065             $id[$alias] = '';
1066         }
1067
1068         while ($data = $stmt->fetch(Doctrine::FETCH_ASSOC)) {
1069             $currData  = array();
1070             $identifiable = array();
1071
1072             foreach ($data as $key => $value) {
1073
1074                 // The following little cache solution ensures that field aliases are
1075                 // parsed only once. This increases speed on large result sets by an order
1076                 // of magnitude.
1077                 if ( ! isset($cache[$key])) {
1078                     $e = explode('__', $key);
1079                     $cache[$key]['field'] = $field = strtolower(array_pop($e));
1080                     $cache[$key]['alias'] = $this->_tableAliases[strtolower(implode('__', $e))];
1081                 }
1082
1083                 $map   = $this->_aliasMap[$cache[$key]['alias']];
1084                 $table = $map['table'];
1085                 $alias = $cache[$key]['alias'];
1086                 $field = $cache[$key]['field'];
1087
1088                 if (isset($this->_aliasMap[$alias]['agg'][$field])) {
1089                     $field = $this->_aliasMap[$alias]['agg'][$field];
1090                 }
1091
1092
1093                 if ($table->isIdentifier($field)) {
1094                     $id[$alias] .= '|' . $value;
1095                 }
1096
1097                 $currData[$alias][$field] = $table->prepareValue($field, $value);
1098
1099                 if ($value !== null) {
1100                     $identifiable[$alias] = true;
1101                 }
1102             }
1103
1104             // dealing with root component
1105             $table = $this->_aliasMap[$rootAlias]['table'];
1106             $componentName = $table->getComponentName();
1107             $event->set('data', $currData[$rootAlias]);
1108             $listeners[$componentName]->preHydrate($event);
1109             $element = $driver->getElement($currData[$rootAlias], $componentName);
1110
1111             $oneToOne = false;
1112
1113             if ($isSimpleQuery) {
1114                 $index = false;
1115             } else {
1116                 $index = isset($identifierMap[$rootAlias][$id[$rootAlias]]) ?
1117                          $identifierMap[$rootAlias][$id[$rootAlias]] : false;
1118             }
1119
1120             if ($index === false) {
1121                 $event->set('data', $element);
1122                 $listeners[$componentName]->postHydrate($event);
1123
1124                 if (isset($this->_aliasMap[$rootAlias]['map'])) {
1125                     $key = $this->_aliasMap[$rootAlias]['map'];
1126
1127                     if (isset($array[$key])) {
1128                         throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found non-unique key mapping.");
1129                     }
1130
1131                     if ( ! isset($element[$key])) {
1132                         throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found a non-existent key.");
1133                     }
1134
1135                     $array[$element[$key]] = $element;
1136                 } else {
1137                     $array[] = $element;
1138                 }
1139
1140                 $identifierMap[$rootAlias][$id[$rootAlias]] = $driver->getLastKey($array);
1141             }
1142
1143             $this->_setLastElement($prev, $array, $index, $rootAlias, $oneToOne);
1144             unset($currData[$rootAlias]);
1145
1146             foreach ($currData as $alias => $data) {
1147                 $index = false;
1148                 $map   = $this->_aliasMap[$alias];
1149                 $table = $this->_aliasMap[$alias]['table'];
1150                 $componentName = $table->getComponentName();
1151                 $event->set('data', $data);
1152                 $listeners[$componentName]->preHydrate($event);
1153
1154                 $element = $driver->getElement($data, $componentName);
1155
1156                 $parent   = $map['parent'];
1157                 $relation = $map['relation'];
1158                 $componentAlias = $map['relation']->getAlias();
1159
1160                 $path = $parent . '.' . $alias;
1161
1162                 if ( ! isset($prev[$parent])) {
1163                     break;
1164                 }
1165
1166                 // check the type of the relation
1167                 if ( ! $relation->isOneToOne()) {
1168                     // initialize the collection
1169
1170                     if ($driver->initRelated($prev[$parent], $componentAlias)) {
1171
1172                         // append element
1173                         if (isset($identifiable[$alias])) {
1174                             if ($isSimpleQuery) {
1175                                 $index = false;
1176                             } else {
1177                                 $index = isset($identifierMap[$path][$id[$parent]][$id[$alias]]) ?
1178                                          $identifierMap[$path][$id[$parent]][$id[$alias]] : false;
1179                             }
1180
1181                             if ($index === false) {
1182                                 $event->set('data', $element);
1183                                 $listeners[$componentName]->postHydrate($event);
1184
1185                                 if (isset($map['map'])) {
1186                                     $key = $map['map'];
1187                                     if (isset($prev[$parent][$componentAlias][$key])) {
1188                                         throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found non-unique key mapping.");
1189                                     }
1190                                     if ( ! isset($element[$key])) {
1191                                         throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found a non-existent key.");
1192                                     }
1193                                     $prev[$parent][$componentAlias][$element[$key]] = $element;
1194                                 } else {
1195                                     $prev[$parent][$componentAlias][] = $element;
1196                                 }
1197
1198                                 $identifierMap[$path][$id[$parent]][$id[$alias]] = $driver->getLastKey($prev[$parent][$componentAlias]);
1199                             }
1200                         }
1201                         // register collection for later snapshots
1202                         $driver->registerCollection($prev[$parent][$componentAlias]);
1203                     }
1204                 } else {
1205                     if ( ! isset($identifiable[$alias])) {
1206                         $prev[$parent][$componentAlias] = $driver->getNullPointer();
1207                     } else {
1208                         $prev[$parent][$componentAlias] = $element;
1209                     }
1210                     $oneToOne = true;
1211                 }
1212                 $coll =& $prev[$parent][$componentAlias];
1213                 $this->_setLastElement($prev, $coll, $index, $alias, $oneToOne);
1214                 $id[$alias] = '';
1215             }
1216             $id[$rootAlias] = '';
1217         }
1218
1219         $driver->flush();
1220
1221         $stmt->closeCursor();
1222         return $array;
1223     }
1224
1225     /**
1226      * _setLastElement
1227      *
1228      * sets the last element of given data array / collection
1229      * as previous element
1230      *
1231      * @param boolean|integer $index
1232      * @return void
1233      */
1234     public function _setLastElement(&$prev, &$coll, $index, $alias, $oneToOne)
1235     {
1236         if ($coll === self::$_null) {
1237             return false;
1238         }
1239         if ($index !== false) {
1240             $prev[$alias] =& $coll[$index];
1241         } else {
1242             // first check the count (we do not want to get the last element
1243             // of an empty collection/array)
1244             if (count($coll) > 0) {
1245                 if (is_array($coll)) {
1246                     if ($oneToOne) {
1247                         $prev[$alias] =& $coll;
1248                     } else {
1249                         end($coll);
1250                         $prev[$alias] =& $coll[key($coll)];
1251                     }
1252                 } else {
1253                     $prev[$alias] = $coll->getLast();
1254                 }
1255             } else {
1256                 if (isset($prev[$alias])) {
1257                     unset($prev[$alias]);
1258                 }
1259             }
1260         }
1261     }
1262
1263     /**
1264      * @return string                   returns a string representation of this object
1265      */
1266     public function __toString()
1267     {
1268         return Doctrine_Lib::formatSql($this->getQuery());
1269     }
1270
1271     /**
1272      * Return the parts
1273      *
1274      * @return array The parts
1275      */
1276     public function getParts()
1277     {
1278         return $this->parts;
1279     }
1280 }