Source for file Query.php

Documentation is available at Query.php

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