[DC-717] Default value for columns is generated wrong. THe default value becomes the result value in the schema files. Created: 05/Jun/10  Updated: 06/Jun/10  Resolved: 06/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Schema Files
Affects Version/s: 1.2.2
Fix Version/s: 1.2.2

Type: Bug Priority: Blocker
Reporter: angelo ayres camargo Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

Apache 2.2, PostgresSQL 8.4, Windows XP



 Description   

When generating models from db with doctrine 1.2.2, default with functions in database get evaluated and the value goes to the generated schema files thus cousing error on insert/update.

Example:
Table:
CREATE TABLE Usuario (
id SERIAL NOT NULL,
apelido CHARACTER VARYING(50) NOT NULL,
email CHARACTER VARYING(255) NOT NULL,
dtRegistro TIMESTAMP DEFAULT 'now()' NOT NULL,
idUsuarioSituacao INTEGER,
senha CHARACTER(32),
CONSTRAINT PK_Usuario PRIMARY KEY (id),
CONSTRAINT TUC_Usuario_1 UNIQUE (email)
)

Code to generate files:
$dManager = Doctrine_Manager::getInstance();
$dManager->setAttribute(Doctrine::ATTR_MODEL_LOADING,Doctrine::MODEL_LOADING_CONSERVATIVE);
$dManager->setAttribute(Doctrine_Core::ATTR_MODEL_LOADING, Doctrine_Core::MODEL_LOADING_CONSERVATIVE);

$dManager->setAttribute(Doctrine::ATTR_PORTABILITY, Doctrine::PORTABILITY_NONE);
$dManager->setAttribute(Doctrine::ATTR_VALIDATE, Doctrine::VALIDATE_NONE);
$dManager->setAttribute(Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE, true);
$dManager->setAttribute(Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES, true);
$dManager->setAttribute(Doctrine::ATTR_QUERY_LIMIT, Doctrine::LIMIT_ROWS);

Doctrine_Core::generateModelsFromDb('c:\temp', array('dbcon'), array('generateTableClasses' => true, 'classPrefix' => 'D'));

Resulting file:
abstract class DBaseUsuario extends Doctrine_Record
{
public function setTableDefinition()

{ $this->setTableName('usuario'); $this->hasColumn('id', 'integer', 4, array( 'type' => 'integer', 'length' => 4, 'fixed' => false, 'unsigned' => false, 'primary' => true, 'sequence' => 'usuario_id', )); $this->hasColumn('apelido', 'string', null, array( 'type' => 'string', 'fixed' => false, 'unsigned' => false, 'notnull' => true, 'primary' => false, )); $this->hasColumn('email', 'string', null, array( 'type' => 'string', 'fixed' => false, 'unsigned' => false, 'notnull' => true, 'primary' => false, )); $this->hasColumn('dtregistro', 'timestamp', null, array( 'type' => 'timestamp', 'fixed' => false, 'unsigned' => false, 'notnull' => true, 'default' => '\'2010-06-04 11:10:12.078\'::timestamp without time zone', 'primary' => false, )); $this->hasColumn('idusuariosituacao', 'integer', 4, array( 'type' => 'integer', 'length' => 4, 'fixed' => false, 'unsigned' => false, 'notnull' => false, 'primary' => false, )); $this->hasColumn('senha', 'string', null, array( 'type' => 'string', 'fixed' => true, 'unsigned' => false, 'notnull' => false, 'primary' => false, )); }

}

The function now() for default value gets replaced by 'default' => '\'2010-06-04 11:10:12.078\'::timestamp without time zone',



 Comments   
Comment by angelo ayres camargo [ 06/Jun/10 ]

as of now i dont know if this is a error from case modeling tool or doctrine, will close and do further testing.





[DC-706] Subqueries do not work in select Created: 27/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: None
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Blocker
Reporter: will ferrer Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

XP, Xamp


Attachments: Text File Query.php.patch     Text File ticketDC706.patch    

 Description   

Hi All

I have found another bug in Doctrine 1.2.2.

Subqueries are not working in select statements at the moment.

The reason for this is that this line (found in the parseSelect function of the class Doctrine_Query) :

$componentAlias = $this->getExpressionOwner($expression);

Returns an unusable value when passed an $expression which is a subquery.

To fix this problem I have patched my version of the code to use an existing "$componentAlias" instead of the value that would be returned by this function when a subquery is encountered.

My code now reads:

if ($pos !== false && substr($term[0], 0, 1) !== "'" && substr($term[0], 0, $pos) == '') {
	$_queryComponents = $this->_queryComponents;
	reset($components);
	$componentAlias = key($_queryComponents);
} else {
        $componentAlias = $this->getExpressionOwner($expression);
}

I have not rigorously tested this patch yet but it has been working for me in what tests I have done so far.

I have posted several very large bugs into jira over the past few months and haven't heard back regarding them. This leads me to believe that the Doctrine team has moved on from Doctrine 1.2.2 and is focusing only on Doctrine 2 issues at this point. I still love version 1 and haven't had the heart (or the time) to migrate my code over to 2 yet. If this is the case then the job of patching bugs like the ones I have reported in 1.2.2 is probably up to us users at this point. As such I will post my patched version of Doctrine_Query in a comment to this bug (My patched version also fixes another bug I reported: DC-594) .

Best Regards

Will Ferrer



 Comments   
Comment by will ferrer [ 27/May/10 ]

Here is the whole of my Doctrine_Query posted for any one who wants to use the bug patches I put into the file (they are both marked with a comment that says: "Patched By Will Ferrer"

<?php
/*
 *  $Id: Query.php 7490 2010-03-29 19:53:27Z jwage $
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information, see
 * <http://www.doctrine-project.org>.
 */

/**
 * Doctrine_Query
 * A Doctrine_Query object represents a DQL query. It is used to query databases for
 * data in an object-oriented fashion. A DQL query understands relations and inheritance
 * and is dbms independant.
 *
 * @package     Doctrine
 * @subpackage  Query
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link        www.doctrine-project.org
 * @since       1.0
 * @version     $Revision: 7490 $
 * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
 * @todo        Proposal: This class does far too much. It should have only 1 task: Collecting
 *              the DQL query parts and the query parameters (the query state and caching options/methods
 *              can remain here, too).
 *              The actual SQL construction could be done by a separate object (Doctrine_Query_SqlBuilder?)
 *              whose task it is to convert DQL into SQL.
 *              Furthermore the SqlBuilder? can then use other objects (Doctrine_Query_Tokenizer?),
 *              (Doctrine_Query_Parser(s)?) to accomplish his work. Doctrine_Query does not need
 *              to know the tokenizer/parsers. There could be extending
 *              implementations of SqlBuilder? that cover the specific SQL dialects.
 *              This would release Doctrine_Connection and the Doctrine_Connection_xxx classes
 *              from this tedious task.
 *              This would also largely reduce the currently huge interface of Doctrine_Query(_Abstract)
 *              and better hide all these transformation internals from the public Query API.
 *
 * @internal    The lifecycle of a Query object is the following:
 *              After construction the query object is empty. Through using the fluent
 *              query interface the user fills the query object with DQL parts and query parameters.
 *              These get collected in {@link $_dqlParts} and {@link $_params}, respectively.
 *              When the query is executed the first time, or when {@link getSqlQuery()}
 *              is called the first time, the collected DQL parts get parsed and the resulting
 *              connection-driver specific SQL is generated. The generated SQL parts are
 *              stored in {@link $_sqlParts} and the final resulting SQL query is stored in
 *              {@link $_sql}.
 */
class Doctrine_Query extends Doctrine_Query_Abstract implements Countable
{
    /**
     * @var array  The DQL keywords.
     */
    protected static $_keywords  = array('ALL',
                                         'AND',
                                         'ANY',
                                         'AS',
                                         'ASC',
                                         'AVG',
                                         'BETWEEN',
                                         'BIT_LENGTH',
                                         'BY',
                                         'CHARACTER_LENGTH',
                                         'CHAR_LENGTH',
                                         'CURRENT_DATE',
                                         'CURRENT_TIME',
                                         'CURRENT_TIMESTAMP',
                                         'DELETE',
                                         'DESC',
                                         'DISTINCT',
                                         'EMPTY',
                                         'EXISTS',
                                         'FALSE',
                                         'FETCH',
                                         'FROM',
                                         'GROUP',
                                         'HAVING',
                                         'IN',
                                         'INDEXBY',
                                         'INNER',
                                         'IS',
                                         'JOIN',
                                         'LEFT',
                                         'LIKE',
                                         'LOWER',
                                         'MEMBER',
                                         'MOD',
                                         'NEW',
                                         'NOT',
                                         'NULL',
                                         'OBJECT',
                                         'OF',
                                         'OR',
                                         'ORDER',
                                         'OUTER',
                                         'POSITION',
                                         'SELECT',
                                         'SOME',
                                         'TRIM',
                                         'TRUE',
                                         'UNKNOWN',
                                         'UPDATE',
                                         'WHERE');

    /**
     * @var array
     */
    protected $_subqueryAliases = array();

    /**
     * @var array $_aggregateAliasMap       an array containing all aggregate aliases, keys as dql aliases
     *                                      and values as sql aliases
     */
    protected $_aggregateAliasMap      = array();

    /**
     * @var array
     */
    protected $_pendingAggregates = array();

    /**
     * @param boolean $needsSubquery
     */
    protected $_needsSubquery = false;

    /**
     * @param boolean $isSubquery           whether or not this query object is a subquery of another
     *                                      query object
     */
    protected $_isSubquery;

    /**
     * @var array $_neededTables            an array containing the needed table aliases
     */
    protected $_neededTables = array();

    /**
     * @var array $pendingSubqueries        SELECT part subqueries, these are called pending subqueries since
     *                                      they cannot be parsed directly (some queries might be correlated)
     */
    protected $_pendingSubqueries = array();

    /**
     * @var array $_pendingFields           an array of pending fields (fields waiting to be parsed)
     */
    protected $_pendingFields = array();

    /**
     * @var array $_parsers                 an array of parser objects, each DQL query part has its own parser
     */
    protected $_parsers = array();

    /**
     * @var array $_pendingJoinConditions    an array containing pending joins
     */
    protected $_pendingJoinConditions = array();

    /**
     * @var array
     */
    protected $_expressionMap = array();

    /**
     * @var string $_sql            cached SQL query
     */
    protected $_sql;

    /**
     * create
     * returns a new Doctrine_Query object
     *
     * @param Doctrine_Connection $conn  optional connection parameter
     * @param string $class              Query class to instantiate
     * @return Doctrine_Query
     */
    public static function create($conn = null, $class = null)
    {
        if ( ! $class) {
            $class = Doctrine_Manager::getInstance()
                ->getAttribute(Doctrine_Core::ATTR_QUERY_CLASS);
        }
        return new $class($conn);
    }

    /**
     * Clears all the sql parts.
     */
    protected function clear()
    {
        $this->_preQueried = false;
        $this->_pendingJoinConditions = array();
        $this->_state = self::STATE_DIRTY;
    }

    /**
     * Resets the query to the state just after it has been instantiated.
     */
    public function reset()
    {
        $this->_subqueryAliases = array();
        $this->_aggregateAliasMap = array();
        $this->_pendingAggregates = array();
        $this->_pendingSubqueries = array();
        $this->_pendingFields = array();
        $this->_neededTables = array();
        $this->_expressionMap = array();
        $this->_subqueryAliases = array();
        $this->_needsSubquery = false;
        $this->_isLimitSubqueryUsed = false;
    }

    /**
     * createSubquery
     * creates a subquery
     *
     * @return Doctrine_Hydrate
     */
    public function createSubquery()
    {
        $class = get_class($this);
        $obj   = new $class();

        // copy the aliases to the subquery
        $obj->copySubqueryInfo($this);

        // this prevents the 'id' being selected, re ticket #307
        $obj->isSubquery(true);

        return $obj;
    }

    /**
     * addPendingJoinCondition
     *
     * @param string $componentAlias    component alias
     * @param string $joinCondition     dql join condition
     * @return Doctrine_Query           this object
     */
    public function addPendingJoinCondition($componentAlias, $joinCondition)
    {
        if ( ! isset($this->_pendingJoinConditions[$componentAlias])) {
            $this->_pendingJoinConditions[$componentAlias] = array();
        }

        $this->_pendingJoinConditions[$componentAlias][] = $joinCondition;
    }

    /**
     * fetchArray
     * Convenience method to execute using array fetching as hydration mode.
     *
     * @param string $params
     * @return array
     */
    public function fetchArray($params = array())
    {
        return $this->execute($params, Doctrine_Core::HYDRATE_ARRAY);
    }

    /**
     * fetchOne
     * Convenience method to execute the query and return the first item
     * of the collection.
     *
     * @param string $params        Query parameters
     * @param int $hydrationMode    Hydration mode: see Doctrine_Core::HYDRATE_* constants
     * @return mixed                Array or Doctrine_Collection, depending on hydration mode. False if no result.
     */
    public function fetchOne($params = array(), $hydrationMode = null)
    {
        $collection = $this->execute($params, $hydrationMode);

        if (is_scalar($collection)) {
            return $collection;
        }

        if (count($collection) === 0) {
            return false;
        }

        if ($collection instanceof Doctrine_Collection) {
            return $collection->getFirst();
        } else if (is_array($collection)) {
            return array_shift($collection);
        }

        return false;
    }

    /**
     * isSubquery
     * if $bool parameter is set this method sets the value of
     * Doctrine_Query::$isSubquery. If this value is set to true
     * the query object will not load the primary key fields of the selected
     * components.
     *
     * If null is given as the first parameter this method retrieves the current
     * value of Doctrine_Query::$isSubquery.
     *
     * @param boolean $bool     whether or not this query acts as a subquery
     * @return Doctrine_Query|bool
     */
    public function isSubquery($bool = null)
    {
        if ($bool === null) {
            return $this->_isSubquery;
        }

        $this->_isSubquery = (bool) $bool;
        return $this;
    }

    /**
     * getSqlAggregateAlias
     *
     * @param string $dqlAlias      the dql alias of an aggregate value
     * @return string
     */
    public function getSqlAggregateAlias($dqlAlias)
    {
        if (isset($this->_aggregateAliasMap[$dqlAlias])) {
            // mark the expression as used
            $this->_expressionMap[$dqlAlias][1] = true;

            return $this->_aggregateAliasMap[$dqlAlias];
        } else if ( ! empty($this->_pendingAggregates)) {
            $this->processPendingAggregates();

            return $this->getSqlAggregateAlias($dqlAlias);
        } else if( ! ($this->_conn->getAttribute(Doctrine_Core::ATTR_PORTABILITY) & Doctrine_Core::PORTABILITY_EXPR)){
            return $dqlAlias;
        } else {
            throw new Doctrine_Query_Exception('Unknown aggregate alias: ' . $dqlAlias);
        }
    }

    /**
     * Check if a dql alias has a sql aggregate alias
     *
     * @param string $dqlAlias 
     * @return boolean
     */
    public function hasSqlAggregateAlias($dqlAlias)
    {
        try {
            $this->getSqlAggregateAlias($dqlAlias);
            return true;
        } catch (Exception $e) {
            return false;
        }
    }

    /**
     * Adjust the processed param index for "foo.bar IN ?" support
     *
     */
    public function adjustProcessedParam($index)
    {
        // Retrieve all params
        $params = $this->getInternalParams();

        // Retrieve already processed values
        $first = array_slice($params, 0, $index);
        $last = array_slice($params, $index, count($params) - $index);

        // Include array as values splicing the params array
        array_splice($last, 0, 1, $last[0]);

        // Put all param values into a single index
        $this->_execParams = array_merge($first, $last);
    }

    /**
     * Retrieves a specific DQL query part.
     *
     * @see Doctrine_Query_Abstract::$_dqlParts
     * <code>
     * var_dump($q->getDqlPart('where'));
     * // array(2) { [0] => string(8) 'name = ?' [1] => string(8) 'date > ?' }
     * </code>
     * @param string $queryPart     the name of the query part; can be:
     *     array from, containing strings;
     *     array select, containg string;
     *     boolean forUpdate;
     *     array set;
     *     array join;
     *     array where;
     *     array groupby;
     *     array having;
     *     array orderby, containing strings such as 'id ASC';
     *     array limit, containing numerics;
     *     array offset, containing numerics;
     * @return array
     */
    public function getDqlPart($queryPart)
    {
        if ( ! isset($this->_dqlParts[$queryPart])) {
           throw new Doctrine_Query_Exception('Unknown query part ' . $queryPart);
        }

        return $this->_dqlParts[$queryPart];
    }

    /**
     * contains
     *
     * Method to check if a arbitrary piece of dql exists
     *
     * @param string $dql Arbitrary piece of dql to check for
     * @return boolean
     */
    public function contains($dql)
    {
      return stripos($this->getDql(), $dql) === false ? false : true;
    }

    /**
     * processPendingFields
     * the fields in SELECT clause cannot be parsed until the components
     * in FROM clause are parsed, hence this method is called everytime a
     * specific component is being parsed. For instance, the wildcard '*'
     * is expanded in the list of columns.
     *
     * @throws Doctrine_Query_Exception     if unknown component alias has been given
     * @param string $componentAlias        the alias of the component
     * @return string SQL code
     * @todo Description: What is a 'pending field' (and are there non-pending fields, too)?
     *       What is 'processed'? (Meaning: What information is gathered & stored away)
     */
    public function processPendingFields($componentAlias)
    {
        $tableAlias = $this->getSqlTableAlias($componentAlias);
        $table = $this->_queryComponents[$componentAlias]['table'];

        if ( ! isset($this->_pendingFields[$componentAlias])) {
            if ($this->_hydrator->getHydrationMode() != Doctrine_Core::HYDRATE_NONE) {
                if ( ! $this->_isSubquery && $componentAlias == $this->getRootAlias()) {
                    throw new Doctrine_Query_Exception("The root class of the query (alias $componentAlias) "
                            . " must have at least one field selected.");
                }
            }
            return;
        }

        // At this point we know the component is FETCHED (either it's the base class of
        // the query (FROM xyz) or its a "fetch join").

        // Check that the parent join (if there is one), is a "fetch join", too.
        if ( ! $this->isSubquery() && isset($this->_queryComponents[$componentAlias]['parent'])) {
            $parentAlias = $this->_queryComponents[$componentAlias]['parent'];
            if (is_string($parentAlias) && ! isset($this->_pendingFields[$parentAlias])
                    && $this->_hydrator->getHydrationMode() != Doctrine_Core::HYDRATE_NONE
                    && $this->_hydrator->getHydrationMode() != Doctrine_Core::HYDRATE_SCALAR
                    && $this->_hydrator->getHydrationMode() != Doctrine_Core::HYDRATE_SINGLE_SCALAR) {
                throw new Doctrine_Query_Exception("The left side of the join between "
                        . "the aliases '$parentAlias' and '$componentAlias' must have at least"
                        . " the primary key field(s) selected.");
            }
        }

        $fields = $this->_pendingFields[$componentAlias];

        // check for wildcards
        if (in_array('*', $fields)) {
            $fields = $table->getFieldNames();
        } else {
            $driverClassName = $this->_hydrator->getHydratorDriverClassName();
            // only auto-add the primary key fields if this query object is not
            // a subquery of another query object or we're using a child of the Object Graph
            // hydrator
            if ( ! $this->_isSubquery && is_subclass_of($driverClassName, 'Doctrine_Hydrator_Graph')) {
                $fields = array_unique(array_merge((array) $table->getIdentifier(), $fields));
            }
        }

        $sql = array();
        foreach ($fields as $fieldName) {
            $columnName = $table->getColumnName($fieldName);
            if (($owner = $table->getColumnOwner($columnName)) !== null &&
                    $owner !== $table->getComponentName()) {

                $parent = $this->_conn->getTable($owner);
                $columnName = $parent->getColumnName($fieldName);
                $parentAlias = $this->getSqlTableAlias($componentAlias . '.' . $parent->getComponentName());
                $sql[] = $this->_conn->quoteIdentifier($parentAlias) . '.' . $this->_conn->quoteIdentifier($columnName)
                       . ' AS '
                       . $this->_conn->quoteIdentifier($tableAlias . '__' . $columnName);
            } else {
                $columnName = $table->getColumnName($fieldName);
                $sql[] = $this->_conn->quoteIdentifier($tableAlias) . '.' . $this->_conn->quoteIdentifier($columnName)
                       . ' AS '
                       . $this->_conn->quoteIdentifier($tableAlias . '__' . $columnName);
            }
        }

        $this->_neededTables[] = $tableAlias;

        return implode(', ', $sql);
    }

    /**
     * Parses a nested field
     * <code>
     * $q->parseSelectField('u.Phonenumber.value');
     * </code>
     *
     * @param string $field
     * @throws Doctrine_Query_Exception     if unknown component alias has been given
     * @return string   SQL fragment
     * @todo Description: Explain what this method does. Is there a relation to parseSelect()?
     *       This method is not used from any class or testcase in the Doctrine package.
     *
     */
    public function parseSelectField($field)
    {
        $terms = explode('.', $field);

        if (isset($terms[1])) {
            $componentAlias = $terms[0];
            $field = $terms[1];
        } else {
            reset($this->_queryComponents);
            $componentAlias = key($this->_queryComponents);
            $fields = $terms[0];
        }

        $tableAlias = $this->getSqlTableAlias($componentAlias);
        $table      = $this->_queryComponents[$componentAlias]['table'];


        // check for wildcards
        if ($field === '*') {
            $sql = array();

            foreach ($table->getColumnNames() as $field) {
                $sql[] = $this->parseSelectField($componentAlias . '.' . $field);
            }

            return implode(', ', $sql);
        } else {
            $name = $table->getColumnName($field);

            $this->_neededTables[] = $tableAlias;

            return $this->_conn->quoteIdentifier($tableAlias . '.' . $name)
                   . ' AS '
                   . $this->_conn->quoteIdentifier($tableAlias . '__' . $name);
        }
    }

    /**
     * getExpressionOwner
     * returns the component alias for owner of given expression
     *
     * @param string $expr      expression from which to get to owner from
     * @return string           the component alias
     * @todo Description: What does it mean if a component is an 'owner' of an expression?
     *       What kind of 'expression' are we talking about here?
     */
    public function getExpressionOwner($expr)
    {
        if (strtoupper(substr(trim($expr, '( '), 0, 6)) !== 'SELECT') {
            preg_match_all("/[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*[\.[a-z0-9]+]*/i", $expr, $matches);

            $match = current($matches);

            if (isset($match[0])) {
                $terms = explode('.', $match[0]);

                return $terms[0];
            }
        }
        return $this->getRootAlias();

    }

    /**
     * parseSelect
     * parses the query select part and
     * adds selected fields to pendingFields array
     *
     * @param string $dql
     * @todo Description: What information is extracted (and then stored)?
     */
    public function parseSelect($dql)
    {
        $refs = $this->_tokenizer->sqlExplode($dql, ',');

        $pos   = strpos(trim($refs[0]), ' ');
        $first = substr($refs[0], 0, $pos);

        // check for DISTINCT keyword
        if ($first === 'DISTINCT') {
            $this->_sqlParts['distinct'] = true;

            $refs[0] = substr($refs[0], ++$pos);
        }

        $parsedComponents = array();

        foreach ($refs as $reference) {
            $reference = trim($reference);

            if (empty($reference)) {
                continue;
            }

            $terms = $this->_tokenizer->sqlExplode($reference, ' ');
            $pos   = strpos($terms[0], '(');

            if (count($terms) > 1 || $pos !== false) {
                $expression = array_shift($terms);
                $alias = array_pop($terms);

                if ( ! $alias) {
                    $alias = substr($expression, 0, $pos);
                }
				
			//Patched By Will Ferrer to detect when a subquery was encountered and use an existing componentAlias if the expression is a subquery. (not well tested) Fixes Bug -- DC-706
				if ($pos !== false && substr($term[0], 0, 1) !== "'" && substr($term[0], 0, $pos) == '') {
					$_queryComponents = $this->_queryComponents;
					reset($components);
					$componentAlias = key($_queryComponents);
				} else {
               		$componentAlias = $this->getExpressionOwner($expression);
				}
			//End Patch
			
				$expression = $this->parseClause($expression);

                $tableAlias = $this->getSqlTableAlias($componentAlias);
				
                $index    = count($this->_aggregateAliasMap);

                $sqlAlias = $this->_conn->quoteIdentifier($tableAlias . '__' . $index);

                $this->_sqlParts['select'][] = $expression . ' AS ' . $sqlAlias;

                $this->_aggregateAliasMap[$alias] = $sqlAlias;
                $this->_expressionMap[$alias][0] = $expression;

                $this->_queryComponents[$componentAlias]['agg'][$index] = $alias;
				
                $this->_neededTables[] = $tableAlias;
            } else {
                $e = explode('.', $terms[0]);

                if (isset($e[1])) {
                    $componentAlias = $e[0];
                    $field = $e[1];
                } else {
                    reset($this->_queryComponents);
                    $componentAlias = key($this->_queryComponents);
                    $field = $e[0];
                }

                $this->_pendingFields[$componentAlias][] = $field;
            }
        }
    }

    /**
     * parseClause
     * parses given DQL clause
     *
     * this method handles five tasks:
     *
     * 1. Converts all DQL functions to their native SQL equivalents
     * 2. Converts all component references to their table alias equivalents
     * 3. Converts all field names to actual column names
     * 4. Quotes all identifiers
     * 5. Parses nested clauses and subqueries recursively
     *
     * @return string   SQL string
     * @todo Description: What is a 'dql clause' (and what not)?
     *       Refactor: Too long & nesting level
     */
    public function parseClause($clause)
    {
        $clause = $this->_conn->dataDict->parseBoolean(trim($clause));

        if (is_numeric($clause)) {
           return $clause;
        }

        $terms = $this->_tokenizer->clauseExplode($clause, array(' ', '+', '-', '*', '/', '<', '>', '=', '>=', '<=', '&', '|'));
        $str = '';

        foreach ($terms as $term) {
            $pos = strpos($term[0], '(');

            if ($pos !== false && substr($term[0], 0, 1) !== "'") {
                $name = substr($term[0], 0, $pos);

                $term[0] = $this->parseFunctionExpression($term[0]);
            } else {
                if (substr($term[0], 0, 1) !== "'" && substr($term[0], -1) !== "'") {
                    if (strpos($term[0], '.') !== false) {
                        if ( ! is_numeric($term[0])) {
                            $e = explode('.', $term[0]);

                            $field = array_pop($e);

                            if ($this->getType() === Doctrine_Query::SELECT) {
                                $componentAlias = implode('.', $e);

                                if (empty($componentAlias)) {
                                    $componentAlias = $this->getRootAlias();
                                }

                                $this->load($componentAlias);

                                // check the existence of the component alias
                                if ( ! isset($this->_queryComponents[$componentAlias])) {
                                    throw new Doctrine_Query_Exception('Unknown component alias ' . $componentAlias);
                                }

                                $table = $this->_queryComponents[$componentAlias]['table'];

                                $def = $table->getDefinitionOf($field);

                                // get the actual field name from alias
                                $field = $table->getColumnName($field);

                                // check column existence
                                if ( ! $def) {
                                    throw new Doctrine_Query_Exception('Unknown column ' . $field);
                                }

                                if (isset($def['owner'])) {
                                    $componentAlias = $componentAlias . '.' . $def['owner'];
                                }

                                $tableAlias = $this->getSqlTableAlias($componentAlias);

                                // build sql expression
                                $term[0] = $this->_conn->quoteIdentifier($tableAlias)
                                         . '.'
                                         . $this->_conn->quoteIdentifier($field);
                            } else {
                                // build sql expression
                                $field = $this->getRoot()->getColumnName($field);
                                $term[0] = $this->_conn->quoteIdentifier($field);
                            }
                        }
                    } else {
                        if ( ! empty($term[0]) && ! in_array(strtoupper($term[0]), self::$_keywords) &&
                             ! is_numeric($term[0]) && $term[0] !== '?' && substr($term[0], 0, 1) !== ':') {

                            $componentAlias = $this->getRootAlias();

                            $found = false;

                            if ($componentAlias !== false && $componentAlias !== null) {
                                $table = $this->_queryComponents[$componentAlias]['table'];

                                // check column existence
                                if ($table->hasField($term[0])) {
                                    $found = true;

                                    $def = $table->getDefinitionOf($term[0]);

                                    // get the actual column name from field name
                                    $term[0] = $table->getColumnName($term[0]);


                                    if (isset($def['owner'])) {
                                        $componentAlias = $componentAlias . '.' . $def['owner'];
                                    }

                                    $tableAlias = $this->getSqlTableAlias($componentAlias);

                                    if ($this->getType() === Doctrine_Query::SELECT) {
                                        // build sql expression
                                        $term[0] = $this->_conn->quoteIdentifier($tableAlias)
                                                 . '.'
                                                 . $this->_conn->quoteIdentifier($term[0]);
                                    } else {
                                        // build sql expression
                                        $term[0] = $this->_conn->quoteIdentifier($term[0]);
                                    }
                                } else {
                                    $found = false;
                                }
                            }

                            if ( ! $found) {
                                $term[0] = $this->getSqlAggregateAlias($term[0]);
                            }
                        }
                    }
                }
            }

            $str .= $term[0] . $term[1];
        }
        return $str;
    }

    public function parseIdentifierReference($expr)
    {

    }

    public function parseFunctionExpression($expr)
    {
        $pos = strpos($expr, '(');
        $name = substr($expr, 0, $pos);

        if ($name === '') {
            return $this->parseSubquery($expr);
        }

        $argStr = substr($expr, ($pos + 1), -1);
        $args   = array();
        // parse args

        foreach ($this->_tokenizer->sqlExplode($argStr, ',') as $arg) {
           $args[] = $this->parseClause($arg);
        }

        // convert DQL function to its RDBMS specific equivalent
        try {
            $expr = call_user_func_array(array($this->_conn->expression, $name), $args);
        } catch (Doctrine_Expression_Exception $e) {
            throw new Doctrine_Query_Exception('Unknown function ' . $name . '.');
        }

        return $expr;
    }


    public function parseSubquery($subquery)
    {
        $trimmed = trim($this->_tokenizer->bracketTrim($subquery));

        // check for possible subqueries
        if (substr($trimmed, 0, 4) == 'FROM' || substr($trimmed, 0, 6) == 'SELECT') {
            // parse subquery
            $q = $this->createSubquery()->parseDqlQuery($trimmed);
            $trimmed = $q->getSqlQuery();
            $q->free();
        } else if (substr($trimmed, 0, 4) == 'SQL:') {
            $trimmed = substr($trimmed, 4);
        } else {
            $e = $this->_tokenizer->sqlExplode($trimmed, ',');

            $value = array();
            $index = false;

            foreach ($e as $part) {
                $value[] = $this->parseClause($part);
            }

            $trimmed = implode(', ', $value);
        }

        return '(' . $trimmed . ')';
    }


    /**
     * processPendingSubqueries
     * processes pending subqueries
     *
     * subqueries can only be processed when the query is fully constructed
     * since some subqueries may be correlated
     *
     * @return void
     * @todo Better description. i.e. What is a 'pending subquery'? What does 'processed' mean?
     *       (parsed? sql is constructed? some information is gathered?)
     */
    public function processPendingSubqueries()
    {
        foreach ($this->_pendingSubqueries as $value) {
            list($dql, $alias) = $value;

            $subquery = $this->createSubquery();

            $sql = $subquery->parseDqlQuery($dql, false)->getQuery();
            $subquery->free();

            reset($this->_queryComponents);
            $componentAlias = key($this->_queryComponents);
            $tableAlias = $this->getSqlTableAlias($componentAlias);

            $sqlAlias = $tableAlias . '__' . count($this->_aggregateAliasMap);

            $this->_sqlParts['select'][] = '(' . $sql . ') AS ' . $this->_conn->quoteIdentifier($sqlAlias);

            $this->_aggregateAliasMap[$alias] = $sqlAlias;
            $this->_queryComponents[$componentAlias]['agg'][] = $alias;
        }
        $this->_pendingSubqueries = array();
    }

    /**
     * processPendingAggregates
     * processes pending aggregate values for given component alias
     *
     * @return void
     * @todo Better description. i.e. What is a 'pending aggregate'? What does 'processed' mean?
     */
    public function processPendingAggregates()
    {
        // iterate trhough all aggregates
        foreach ($this->_pendingAggregates as $aggregate) {
            list ($expression, $components, $alias) = $aggregate;

            $tableAliases = array();

            // iterate through the component references within the aggregate function
            if ( ! empty ($components)) {
                foreach ($components as $component) {

                    if (is_numeric($component)) {
                        continue;
                    }

                    $e = explode('.', $component);

                    $field = array_pop($e);
                    $componentAlias = implode('.', $e);

                    // check the existence of the component alias
                    if ( ! isset($this->_queryComponents[$componentAlias])) {
                        throw new Doctrine_Query_Exception('Unknown component alias ' . $componentAlias);
                    }

                    $table = $this->_queryComponents[$componentAlias]['table'];

                    $field = $table->getColumnName($field);

                    // check column existence
                    if ( ! $table->hasColumn($field)) {
                        throw new Doctrine_Query_Exception('Unknown column ' . $field);
                    }

                    $sqlTableAlias = $this->getSqlTableAlias($componentAlias);

                    $tableAliases[$sqlTableAlias] = true;

                    // build sql expression

                    $identifier = $this->_conn->quoteIdentifier($sqlTableAlias . '.' . $field);
                    $expression = str_replace($component, $identifier, $expression);
                }
            }

            if (count($tableAliases) !== 1) {
                $componentAlias = reset($this->_tableAliasMap);
                $tableAlias = key($this->_tableAliasMap);
            }

            $index    = count($this->_aggregateAliasMap);
            $sqlAlias = $this->_conn->quoteIdentifier($tableAlias . '__' . $index);

            $this->_sqlParts['select'][] = $expression . ' AS ' . $sqlAlias;

            $this->_aggregateAliasMap[$alias] = $sqlAlias;
            $this->_expressionMap[$alias][0] = $expression;

            $this->_queryComponents[$componentAlias]['agg'][$index] = $alias;

            $this->_neededTables[] = $tableAlias;
        }
        // reset the state
        $this->_pendingAggregates = array();
    }

    /**
     * _buildSqlQueryBase
     * returns the base of the generated sql query
     * On mysql driver special strategy has to be used for DELETE statements
     * (where is this special strategy??)
     *
     * @return string       the base of the generated sql query
     */
    protected function _buildSqlQueryBase()
    {
        switch ($this->_type) {
            case self::DELETE:
                $q = 'DELETE FROM ';
            break;
            case self::UPDATE:
                $q = 'UPDATE ';
            break;
            case self::SELECT:
                $distinct = ($this->_sqlParts['distinct']) ? 'DISTINCT ' : '';
                $q = 'SELECT ' . $distinct . implode(', ', $this->_sqlParts['select']) . ' FROM ';
            break;
        }
        return $q;
    }

    /**
     * _buildSqlFromPart
     * builds the from part of the query and returns it
     *
     * @return string   the query sql from part
     */
    protected function _buildSqlFromPart($ignorePending = false)
    {
        $q = '';

        foreach ($this->_sqlParts['from'] as $k => $part) {
            $e = explode(' ', $part);

            if ($k === 0) {
                if ( ! $ignorePending && $this->_type == self::SELECT) {
                    // We may still have pending conditions
                    $alias = count($e) > 1
                        ? $this->getComponentAlias($e[1])
                        : null;
                    $where = $this->_processPendingJoinConditions($alias);

                    // apply inheritance to WHERE part
                    if ( ! empty($where)) {
                        if (count($this->_sqlParts['where']) > 0) {
                            $this->_sqlParts['where'][] = 'AND';
                        }

                        if (substr($where, 0, 1) === '(' && substr($where, -1) === ')') {
                            $this->_sqlParts['where'][] = $where;
                        } else {
                            $this->_sqlParts['where'][] = '(' . $where . ')';
                        }
                    }
                }

                $q .= $part;

                continue;
            }

            // preserve LEFT JOINs only if needed
            // Check if it's JOIN, if not add a comma separator instead of space
            if ( ! preg_match('/\bJOIN\b/i', $part) && ! isset($this->_pendingJoinConditions[$k])) {
                $q .= ', ' . $part;
            } else {
                if (substr($part, 0, 9) === 'LEFT JOIN') {
                    $aliases = array_merge($this->_subqueryAliases,
                                array_keys($this->_neededTables));

                    if ( ! in_array($e[3], $aliases) && ! in_array($e[2], $aliases) && ! empty($this->_pendingFields)) {
                        continue;
                    }

                }

                if ( ! $ignorePending && isset($this->_pendingJoinConditions[$k])) {
                    if (strpos($part, ' ON ') !== false) {
                        $part .= ' AND ';
                    } else {
                        $part .= ' ON ';
                    }

                    $part .= $this->_processPendingJoinConditions($k);
                }

                $componentAlias = $this->getComponentAlias($e[3]);
                $string = $this->getInheritanceCondition($componentAlias);

                if ($string) {
                    $part = $part . ' AND ' . $string;
                }
                $q .= ' ' . $part;
            }

            $this->_sqlParts['from'][$k] = $part;
        }
        return $q;
    }

    /**
     * Processes the pending join conditions, used for dynamically add conditions
     * to root component/joined components without interfering in the main dql
     * handling.
     *
     * @param string $alias Component Alias
     * @return Processed pending conditions
     */
    protected function _processPendingJoinConditions($alias)
    {
        $parts = array();

        if ($alias !== null && isset($this->_pendingJoinConditions[$alias])) {
            $parser = new Doctrine_Query_JoinCondition($this, $this->_tokenizer);

            foreach ($this->_pendingJoinConditions[$alias] as $joinCondition) {
                $parts[] = $parser->parse($joinCondition);
            }

            // FIX #1860 and #1876: Cannot unset them, otherwise query cannot be reused later
            //unset($this->_pendingJoinConditions[$alias]);
        }

        return (count($parts) > 0 ? '(' . implode(') AND (', $parts) . ')' : '');
    }

    /**
     * builds the sql query from the given parameters and applies things such as
     * column aggregation inheritance and limit subqueries if needed
     *
     * @param array $params             an array of prepared statement params (needed only in mysql driver
     *                                  when limit subquery algorithm is used)
     * @param bool $limitSubquery Whether or not to try and apply the limit subquery algorithm
     * @return string                   the built sql query
     */
    public function getSqlQuery($params = array(), $limitSubquery = true)
    {
        // Assign building/execution specific params
        $this->_params['exec'] = $params;

        // Initialize prepared parameters array
        $this->_execParams = $this->getFlattenedParams();

        if ($this->_state !== self::STATE_DIRTY) {
            $this->fixArrayParameterValues($this->getInternalParams());

            // Return compiled SQL
            return $this->_sql;
        }
        return $this->buildSqlQuery($limitSubquery);
    }

    /**
     * Build the SQL query from the DQL
     *
     * @param bool $limitSubquery Whether or not to try and apply the limit subquery algorithm
     * @return string $sql The generated SQL string
     */
    public function buildSqlQuery($limitSubquery = true)
    {
        // reset the state
        if ( ! $this->isSubquery()) {
            $this->_queryComponents = array();
            $this->_pendingAggregates = array();
            $this->_aggregateAliasMap = array();
        }

        $this->reset();

        // invoke the preQuery hook
        $this->_preQuery();

        // process the DQL parts => generate the SQL parts.
        // this will also populate the $_queryComponents.
        foreach ($this->_dqlParts as $queryPartName => $queryParts) {
            // If we are parsing FROM clause, we'll need to diff the queryComponents later
            if ($queryPartName == 'from') {
                // Pick queryComponents before processing
                $queryComponentsBefore = $this->getQueryComponents();
            }

            // FIX #1667: _sqlParts are cleaned inside _processDqlQueryPart.
            if ($queryPartName != 'forUpdate') {
                $this->_processDqlQueryPart($queryPartName, $queryParts);
            }

            // We need to define the root alias
            if ($queryPartName == 'from') {
                // Pick queryComponents aftr processing
                $queryComponentsAfter = $this->getQueryComponents();

                // Root alias is the key of difference of query components
                $diffQueryComponents = array_diff_key($queryComponentsAfter, $queryComponentsBefore);
                $this->_rootAlias = key($diffQueryComponents);
            }
        }
        $this->_state = self::STATE_CLEAN;

        // Proceed with the generated SQL
        if (empty($this->_sqlParts['from'])) {
            return false;
        }

        $needsSubQuery = false;
        $subquery = '';
        $map = $this->getRootDeclaration();
        $table = $map['table'];
        $rootAlias = $this->getRootAlias();

        if ( ! empty($this->_sqlParts['limit']) && $this->_needsSubquery &&
                $table->getAttribute(Doctrine_Core::ATTR_QUERY_LIMIT) == Doctrine_Core::LIMIT_RECORDS) {
            // We do not need a limit-subquery if DISTINCT is used
            // and the selected fields are either from the root component or from a localKey relation (hasOne)
            // (i.e. DQL: SELECT DISTINCT u.id FROM User u LEFT JOIN u.phonenumbers LIMIT 5).
            if(!$this->_sqlParts['distinct']) {
                $this->_isLimitSubqueryUsed = true;
                $needsSubQuery = true;
            } else {
                foreach( array_keys($this->_pendingFields) as $alias){
                    //no subquery for root fields
                    if($alias == $this->getRootAlias()){
                        continue;
                    }

                    //no subquery for ONE relations
                    if(isset($this->_queryComponents[$alias]['relation']) &&
                        $this->_queryComponents[$alias]['relation']->getType() == Doctrine_Relation::ONE){
                        continue;
                    }

                    $this->_isLimitSubqueryUsed = true;
                    $needsSubQuery = true;
                }
            }
        }

        $sql = array();

        if ( ! empty($this->_pendingFields)) {
            foreach ($this->_queryComponents as $alias => $map) {
                $fieldSql = $this->processPendingFields($alias);
                if ( ! empty($fieldSql)) {
                    $sql[] = $fieldSql;
                }
            }
        }

        if ( ! empty($sql)) {
            array_unshift($this->_sqlParts['select'], implode(', ', $sql));
        }

        $this->_pendingFields = array();

        // build the basic query
        $q  = $this->_buildSqlQueryBase();
        $q .= $this->_buildSqlFromPart();

        if ( ! empty($this->_sqlParts['set'])) {
            $q .= ' SET ' . implode(', ', $this->_sqlParts['set']);
        }

        $string = $this->getInheritanceCondition($this->getRootAlias());

        // apply inheritance to WHERE part
        if ( ! empty($string)) {
            if (count($this->_sqlParts['where']) > 0) {
                $this->_sqlParts['where'][] = 'AND';
            }

            if (substr($string, 0, 1) === '(' && substr($string, -1) === ')') {
                $this->_sqlParts['where'][] = $string;
            } else {
                $this->_sqlParts['where'][] = '(' . $string . ')';
            }
        }

        $modifyLimit = true;
        $limitSubquerySql = '';

        if ( ( ! empty($this->_sqlParts['limit']) || ! empty($this->_sqlParts['offset'])) && $needsSubQuery && $limitSubquery) {
            $subquery = $this->getLimitSubquery();

            // what about composite keys?
            $idColumnName = $table->getColumnName($table->getIdentifier());

            switch (strtolower($this->_conn->getDriverName())) {
                case 'mysql':
                    $this->useQueryCache(false);

                    // mysql doesn't support LIMIT in subqueries
                    $list = $this->_conn->execute($subquery, $this->_execParams)->fetchAll(Doctrine_Core::FETCH_COLUMN);
                    $subquery = implode(', ', array_map(array($this->_conn, 'quote'), $list));

                    break;

                case 'pgsql':
                    $subqueryAlias = $this->_conn->quoteIdentifier('doctrine_subquery_alias');

                    // pgsql needs special nested LIMIT subquery
                    $subquery = 'SELECT ' . $subqueryAlias . '.' . $this->_conn->quoteIdentifier($idColumnName)
                            . ' FROM (' . $subquery . ') AS ' . $subqueryAlias;

                    break;
            }

            $field = $this->getSqlTableAlias($rootAlias) . '.' . $idColumnName;

            // FIX #1868: If not ID under MySQL is found to be restricted, restrict pk column for null
            //            (which will lead to a return of 0 items)
            $limitSubquerySql = $this->_conn->quoteIdentifier($field)
                              . (( ! empty($subquery)) ? ' IN (' . $subquery . ')' : ' IS NULL')
                              . ((count($this->_sqlParts['where']) > 0) ? ' AND ' : '');

            $modifyLimit = false;
        }

        // FIX #DC-26: Include limitSubquerySql as major relevance in conditions
        $emptyWhere = empty($this->_sqlParts['where']);

        if ( ! ($emptyWhere && $limitSubquerySql == '')) {
            $where = implode(' ', $this->_sqlParts['where']);
            $where = ($where == '' || (substr($where, 0, 1) === '(' && substr($where, -1) === ')'))
                ? $where : '(' . $where . ')';

            $q .= ' WHERE ' . $limitSubquerySql . $where;
            //   .  (($limitSubquerySql == '' && count($this->_sqlParts['where']) == 1) ? substr($where, 1, -1) : $where);
        }

        // Fix the orderbys so we only have one orderby per value
        foreach ($this->_sqlParts['orderby'] as $k => $orderBy) {
            $e = explode(', ', $orderBy);
            unset($this->_sqlParts['orderby'][$k]);
            foreach ($e as $v) {
                $this->_sqlParts['orderby'][] = $v;
            }
        }

        // Add the default orderBy statements defined in the relationships and table classes
        // Only do this for SELECT queries
        if ($this->_type === self::SELECT) {
            foreach ($this->_queryComponents as $alias => $map) {
                $sqlAlias = $this->getSqlTableAlias($alias);
                if (isset($map['relation'])) {
                    $orderBy = $map['relation']->getOrderByStatement($sqlAlias, true);
                    if ($orderBy == $map['relation']['orderBy']) {
                        if (isset($map['ref'])) {
                            $orderBy = $map['relation']['refTable']->processOrderBy($sqlAlias, $map['relation']['orderBy'], true);
                        } else {
                            $orderBy = null;
                        }
                    }
				} else {
					$orderBy = $map['table']->getOrderByStatement($sqlAlias, true);
				}

                if ($orderBy) {
                    $e = explode(',', $orderBy);
                    $e = array_map('trim', $e);
                    foreach ($e as $v) {
                        if ( ! in_array($v, $this->_sqlParts['orderby'])) {
                            $this->_sqlParts['orderby'][] = $v;
                        }
                    }
                }
            }
        }

        $q .= ( ! empty($this->_sqlParts['groupby'])) ? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby'])  : '';
        $q .= ( ! empty($this->_sqlParts['having'])) ?  ' HAVING '   . implode(' AND ', $this->_sqlParts['having']): '';
        $q .= ( ! empty($this->_sqlParts['orderby'])) ? ' ORDER BY ' . implode(', ', $this->_sqlParts['orderby'])  : '';

        if ($modifyLimit) {
            $q = $this->_conn->modifyLimitQuery($q, $this->_sqlParts['limit'], $this->_sqlParts['offset']);
        }

        $q .= $this->_sqlParts['forUpdate'] === true ? ' FOR UPDATE ' : '';

        $this->_sql = $q;

        $this->clear();

        return $q;
    }

    /**
     * getLimitSubquery
     * this is method is used by the record limit algorithm
     *
     * when fetching one-to-many, many-to-many associated data with LIMIT clause
     * an additional subquery is needed for limiting the number of returned records instead
     * of limiting the number of sql result set rows
     *
     * @return string       the limit subquery
     * @todo A little refactor to make the method easier to understand & maybe shorter?
     */
    public function getLimitSubquery()
    {
        $map = reset($this->_queryComponents);
        $table = $map['table'];
        $componentAlias = key($this->_queryComponents);

        // get short alias
        $alias = $this->getSqlTableAlias($componentAlias);
        // what about composite keys?
        $primaryKey = $alias . '.' . $table->getColumnName($table->getIdentifier());

        $driverName = $this->_conn->getAttribute(Doctrine_Core::ATTR_DRIVER_NAME);

        // initialize the base of the subquery
        if (($driverName == 'oracle' || $driverName == 'oci') && $this->_isOrderedByJoinedColumn()) {
            $subquery = 'SELECT ';
        } else {
            $subquery = 'SELECT DISTINCT ';
        }
        $subquery .= $this->_conn->quoteIdentifier($primaryKey);

        // pgsql & oracle need the order by fields to be preserved in select clause
        if ($driverName == 'pgsql' || $driverName == 'oracle' || $driverName == 'oci' || $driverName == 'mssql' || $driverName == 'odbc') {
            foreach ($this->_sqlParts['orderby'] as $part) {
                // Remove identifier quoting if it exists
                $e = $this->_tokenizer->bracketExplode($part, ' ');
                foreach ($e as $f) {
                    if ($f == 0 || $f % 2 == 0) {
                        $partOriginal = str_replace(',', '', trim($f));
                        $callback = create_function('$e', 'return trim($e, \'[]`"\');');
                        $part = trim(implode('.', array_map($callback, explode('.', $partOriginal))));
                
                        if (strpos($part, '.') === false) {
                            continue;
                        }
                
                        // don't add functions
                        if (strpos($part, '(') !== false) {
                            continue;
                        }
                
                        // don't add primarykey column (its already in the select clause)
                        if ($part !== $primaryKey) {
                            $subquery .= ', ' . $partOriginal;
                        }
                    }
                }
            }
        }

        $orderby = $this->_sqlParts['orderby'];
        $having = $this->_sqlParts['having'];
        if ($driverName == 'mysql' || $driverName == 'pgsql') {
            foreach ($this->_expressionMap as $dqlAlias => $expr) {
                if (isset($expr[1])) {
                    $subquery .= ', ' . $expr[0] . ' AS ' . $this->_aggregateAliasMap[$dqlAlias];
                }
            }
        } else {
            foreach ($this->_expressionMap as $dqlAlias => $expr) {
                if (isset($expr[1])) {
                    foreach ($having as $k => $v) {
                        $having[$k] = str_replace($this->_aggregateAliasMap[$dqlAlias], $expr[0], $v);
                    }
                    foreach ($orderby as $k => $v) {
                        $e = explode(' ', $v);
                        if ($e[0] == $this->_aggregateAliasMap[$dqlAlias]) {
                            $orderby[$k] = $expr[0];
                        }
                    }
                }
            }
        }

        // Add having fields that got stripped out of select
        preg_match_all('/`[a-z0-9_]+`\.`[a-z0-9_]+`/i', implode(' ', $having), $matches, PREG_PATTERN_ORDER);
        if (count($matches[0]) > 0) {
            $subquery .= ', ' . implode(', ', array_unique($matches[0]));
        }

        $subquery .= ' FROM';

        foreach ($this->_sqlParts['from'] as $part) {
            // preserve LEFT JOINs only if needed
            if (substr($part, 0, 9) === 'LEFT JOIN') {
                $e = explode(' ', $part);
			//Patched by Will Ferrer to also check for groupBys. Fixes Bug -- DC-594
                if (empty($this->_sqlParts['orderby']) && empty($this->_sqlParts['where']) && empty($this->_sqlParts['having']) && empty($this->_sqlParts['groupby'])) {
             //End Patch
				   continue;
                }
            }

            $subquery .= ' ' . $part;
        }

        // all conditions must be preserved in subquery
        $subquery .= ( ! empty($this->_sqlParts['where']))?   ' WHERE '    . implode(' ', $this->_sqlParts['where'])  : '';
        $subquery .= ( ! empty($this->_sqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby'])   : '';
        $subquery .= ( ! empty($having))?  ' HAVING '   . implode(' AND ', $having) : '';
        $subquery .= ( ! empty($orderby))? ' ORDER BY ' . implode(', ', $orderby)  : '';

        if (($driverName == 'oracle' || $driverName == 'oci') && $this->_isOrderedByJoinedColumn()) {
            // When using "ORDER BY x.foo" where x.foo is a column of a joined table,
            // we may get duplicate primary keys because all columns in ORDER BY must appear
            // in the SELECT list when using DISTINCT. Hence we need to filter out the
            // primary keys with an additional DISTINCT subquery.
            // #1038
            $quotedIdentifierColumnName = $this->_conn->quoteIdentifier($table->getColumnName($table->getIdentifier()));
            $subquery = 'SELECT doctrine_subquery_alias.' . $quotedIdentifierColumnName
                    . ' FROM (' . $subquery . ') doctrine_subquery_alias'
                    . ' GROUP BY doctrine_subquery_alias.' . $quotedIdentifierColumnName
                    . ' ORDER BY MIN(ROWNUM)';
        }

        // add driver specific limit clause
        $subquery = $this->_conn->modifyLimitSubquery($table, $subquery, $this->_sqlParts['limit'], $this->_sqlParts['offset']);

        $parts = $this->_tokenizer->quoteExplode($subquery, ' ', "'", "'");

        foreach ($parts as $k => $part) {
            if (strpos($part, ' ') !== false) {
                continue;
            }

            $part = str_replace(array('"', "'", '`'), "", $part);

            if ($this->hasSqlTableAlias($part)) {
                $parts[$k] = $this->_conn->quoteIdentifier($this->generateNewSqlTableAlias($part));
                continue;
            }

            if (strpos($part, '.') === false) {
                continue;
            }

            preg_match_all("/[a-zA-Z0-9_]+\.[a-z0-9_]+/i", $part, $m);

            foreach ($m[0] as $match) {
                $e = explode('.', $match);

                // Rebuild the original part without the newly generate alias and with quoting reapplied
                $e2 = array();
                foreach ($e as $k2 => $v2) {
                  $e2[$k2] = $this->_conn->quoteIdentifier($v2);
                }
                $match = implode('.', $e2);

                // Generate new table alias
                $e[0] = $this->generateNewSqlTableAlias($e[0]);

                // Requote the part with the newly generated alias
                foreach ($e as $k2 => $v2) {
                  $e[$k2] = $this->_conn->quoteIdentifier($v2);
                }

                $replace = implode('.' , $e);

                // Replace the original part with the new part with new sql table alias
                $parts[$k] = str_replace($match, $replace, $parts[$k]);
            }
        }

        if ($driverName == 'mysql' || $driverName == 'pgsql') {
            foreach ($parts as $k => $part) {
                if (strpos($part, "'") !== false) {
                    continue;
                }
                if (strpos($part, '__') == false) {
                    continue;
                }

                preg_match_all("/[a-zA-Z0-9_]+\_\_[a-z0-9_]+/i", $part, $m);

                foreach ($m[0] as $match) {
                    $e = explode('__', $match);
                    $e[0] = $this->generateNewSqlTableAlias($e[0]);

                    $parts[$k] = str_replace($match, implode('__', $e), $parts[$k]);
                }
            }
        }

        $subquery = implode(' ', $parts);
        return $subquery;
    }

    /**
     * Checks whether the query has an ORDER BY on a column of a joined table.
     * This information is needed in special scenarios like the limit-offset when its
     * used with an Oracle database.
     *
     * @return boolean  TRUE if the query is ordered by a joined column, FALSE otherwise.
     */
    private function _isOrderedByJoinedColumn() {
        if ( ! $this->_queryComponents) {
            throw new Doctrine_Query_Exception("The query is in an invalid state for this "
                    . "operation. It must have been fully parsed first.");
        }
        $componentAlias = key($this->_queryComponents);
        $mainTableAlias = $this->getSqlTableAlias($componentAlias);
        foreach ($this->_sqlParts['orderby'] as $part) {
            $part = trim($part);
            $e = $this->_tokenizer->bracketExplode($part, ' ');
            $part = trim($e[0]);
            if (strpos($part, '.') === false) {
                continue;
            }
            list($tableAlias, $columnName) = explode('.', $part);
            if ($tableAlias != $mainTableAlias) {
                return true;
            }
        }
        return false;
    }

    /**
     * DQL PARSER
     * parses a DQL query
     * first splits the query in parts and then uses individual
     * parsers for each part
     *
     * @param string $query                 DQL query
     * @param boolean $clear                whether or not to clear the aliases
     * @throws Doctrine_Query_Exception     if some generic parsing error occurs
     * @return Doctrine_Query
     */
    public function parseDqlQuery($query, $clear = true)
    {
        if ($clear) {
            $this->clear();
        }

        $query = trim($query);
        $query = str_replace("\r", "\n", str_replace("\r\n", "\n", $query));
        $query = str_replace("\n", ' ', $query);

        $parts = $this->_tokenizer->tokenizeQuery($query);

        foreach ($parts as $partName => $subParts) {
            $subParts = trim($subParts);
            $partName = strtolower($partName);
            switch ($partName) {
                case 'create':
                    $this->_type = self::CREATE;
                break;
                case 'insert':
                    $this->_type = self::INSERT;
                break;
                case 'delete':
                    $this->_type = self::DELETE;
                break;
                case 'select':
                    $this->_type = self::SELECT;
                    $this->_addDqlQueryPart($partName, $subParts);
                break;
                case 'update':
                    $this->_type = self::UPDATE;
                    $partName = 'from';
                case 'from':
                    $this->_addDqlQueryPart($partName, $subParts);
                break;
                case 'set':
                    $this->_addDqlQueryPart($partName, $subParts, true);
                break;
                case 'group':
                case 'order':
                    $partName .= 'by';
                case 'where':
                case 'having':
                case 'limit':
                case 'offset':
                    $this->_addDqlQueryPart($partName, $subParts);
                break;
            }
        }

        return $this;
    }

    /**
     * @todo Describe & refactor... too long and nested.
     * @param string $path          component alias
     * @param boolean $loadFields
     */
    public function load($path, $loadFields = true)
    {
        if (isset($this->_queryComponents[$path])) {
            return $this->_queryComponents[$path];
        }

        $e = $this->_tokenizer->quoteExplode($path, ' INDEXBY ');

        $mapWith = null;
        if (count($e) > 1) {
            $mapWith = trim($e[1]);

            $path = $e[0];
        }

        // parse custom join conditions
        $e = explode(' ON ', str_ireplace(' on ', ' ON ', $path));

        $joinCondition = '';

        if (count($e) > 1) {
            $joinCondition = substr($path, strlen($e[0]) + 4, strlen($e[1]));
            $path = substr($path, 0, strlen($e[0]));

            $overrideJoin = true;
        } else {
            $e = explode(' WITH ', str_ireplace(' with ', ' WITH ', $path));

            if (count($e) > 1) {
                $joinCondition = substr($path, strlen($e[0]) + 6, strlen($e[1]));
                $path = substr($path, 0, strlen($e[0]));
            }

            $overrideJoin = false;
        }

        $tmp            = explode(' ', $path);
        $componentAlias = $originalAlias = (count($tmp) > 1) ? end($tmp) : null;

        $e = preg_split("/[.:]/", $tmp[0], -1);

        $fullPath = $tmp[0];
        $prevPath = '';
        $fullLength = strlen($fullPath);

        if (isset($this->_queryComponents[$e[0]])) {
            $table = $this->_queryComponents[$e[0]]['table'];
            $componentAlias = $e[0];

            $prevPath = $parent = array_shift($e);
        }

        foreach ($e as $key => $name) {
            // get length of the previous path
            $length = strlen($prevPath);

            // build the current component path
            $prevPath = ($prevPath) ? $prevPath . '.' . $name : $name;

            $delimeter = substr($fullPath, $length, 1);

            // if an alias is not given use the current path as an alias identifier
            if (strlen($prevPath) === $fullLength && isset($originalAlias)) {
                $componentAlias = $originalAlias;
            } else {
                $componentAlias = $prevPath;
            }

            // if the current alias already exists, skip it
            if (isset($this->_queryComponents[$componentAlias])) {
                throw new Doctrine_Query_Exception("Duplicate alias '$componentAlias' in query.");
            }

            if ( ! isset($table)) {
                // process the root of the path

                $table = $this->loadRoot($name, $componentAlias);
            } else {
                $join = ($delimeter == ':') ? 'INNER JOIN ' : 'LEFT JOIN ';

                $relation = $table->getRelation($name);
                $localTable = $table;

                $table = $relation->getTable();
                $this->_queryComponents[$componentAlias] = array('table' => $table,
                                                                 'parent'   => $parent,
                                                                 'relation' => $relation,
                                                                 'map'      => null);
                if ( ! $relation->isOneToOne()) {
                   $this->_needsSubquery = true;
                }

                $localAlias   = $this->getSqlTableAlias($parent, $localTable->getTableName());
                $foreignAlias = $this->getSqlTableAlias($componentAlias, $relation->getTable()->getTableName());

                $foreignSql   = $this->_conn->quoteIdentifier($relation->getTable()->getTableName())
                              . ' '
                              . $this->_conn->quoteIdentifier($foreignAlias);

                $map = $relation->getTable()->inheritanceMap;

                if ( ! $loadFields || ! empty($map) || $joinCondition) {
                    $this->_subqueryAliases[] = $foreignAlias;
                }

                if ($relation instanceof Doctrine_Relation_Association) {
                    $asf = $relation->getAssociationTable();

                    $assocTableName = $asf->getTableName();

                    if ( ! $loadFields || ! empty($map) || $joinCondition) {
                        $this->_subqueryAliases[] = $assocTableName;
                    }

                    $assocPath = $prevPath . '.' . $asf->getComponentName() . ' ' . $componentAlias;

                    $this->_queryComponents[$assocPath] = array(
                        'parent' => $prevPath,
                        'relation' => $relation,
                        'table' => $asf,
                        'ref' => true);

                    $assocAlias = $this->getSqlTableAlias($assocPath, $asf->getTableName());

                    $queryPart = $join
                            . $this->_conn->quoteIdentifier($assocTableName)
                            . ' '
                            . $this->_conn->quoteIdentifier($assocAlias);

                    $queryPart .= ' ON (' . $this->_conn->quoteIdentifier($localAlias
                                . '.'
                                . $localTable->getColumnName($localTable->getIdentifier())) // what about composite keys?
                                . ' = '
                                . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getLocalRefColumnName());

                    if ($relation->isEqual()) {
                        // equal nest relation needs additional condition
                        $queryPart .= ' OR '
                                    . $this->_conn->quoteIdentifier($localAlias
                                    . '.'
                                    . $table->getColumnName($table->getIdentifier()))
                                    . ' = '
                                    . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getForeignRefColumnName());
                    }

                    $queryPart .= ')';

                    $this->_sqlParts['from'][] = $queryPart;

                    $queryPart = $join . $foreignSql;

                    if ( ! $overrideJoin) {
                        $queryPart .= $this->buildAssociativeRelationSql($relation, $assocAlias, $foreignAlias, $localAlias);
                    }
                } else {
                    $queryPart = $this->buildSimpleRelationSql($relation, $foreignAlias, $localAlias, $overrideJoin, $join);
                }

                $queryPart .= $this->buildInheritanceJoinSql($table->getComponentName(), $componentAlias);
                $this->_sqlParts['from'][$componentAlias] = $queryPart;

                if ( ! empty($joinCondition)) {
                    $this->addPendingJoinCondition($componentAlias, $joinCondition);
                }
            }

            if ($loadFields) {
                $restoreState = false;

                // load fields if necessary
                if ($loadFields && empty($this->_dqlParts['select'])) {
                    $this->_pendingFields[$componentAlias] = array('*');
                }
            }

            $parent = $prevPath;
        }

        $table = $this->_queryComponents[$componentAlias]['table'];

        return $this->buildIndexBy($componentAlias, $mapWith);
    }

    protected function buildSimpleRelationSql(Doctrine_Relation $relation, $foreignAlias, $localAlias, $overrideJoin, $join)
    {
        $queryPart = $join . $this->_conn->quoteIdentifier($relation->getTable()->getTableName())
                           . ' '
                           . $this->_conn->quoteIdentifier($foreignAlias);

        if ( ! $overrideJoin) {
            $queryPart .= ' ON '
                       . $this->_conn->quoteIdentifier($localAlias . '.' . $relation->getLocalColumnName())
                       . ' = '
                       . $this->_conn->quoteIdentifier($foreignAlias . '.' . $relation->getForeignColumnName());
        }

        return $queryPart;
    }

    protected function buildIndexBy($componentAlias, $mapWith = null)
    {
        $table = $this->_queryComponents[$componentAlias]['table'];

        $indexBy = null;
        $column = false;

        if (isset($mapWith)) {
            $terms = explode('.', $mapWith);

            if (count($terms) == 1) {
                $indexBy = $terms[0];
            } else if (count($terms) == 2) {
                $column = true;
                $indexBy = $terms[1];
            }
        } else if ($table->getBoundQueryPart('indexBy') !== null) {
            $indexBy = $table->getBoundQueryPart('indexBy');
        }

        if ($indexBy !== null) {
            if ( $column && ! $table->hasColumn($table->getColumnName($indexBy))) {
                throw new Doctrine_Query_Exception("Couldn't use key mapping. Column " . $indexBy . " does not exist.");
            }

            $this->_queryComponents[$componentAlias]['map'] = $indexBy;
        }

        return $this->_queryComponents[$componentAlias];
    }


    protected function buildAssociativeRelationSql(Doctrine_Relation $relation, $assocAlias, $foreignAlias, $localAlias)
    {
        $table = $relation->getTable();

        $queryPart = ' ON ';

        if ($relation->isEqual()) {
            $queryPart .= '(';
        }

        $localIdentifier = $table->getColumnName($table->getIdentifier());

        $queryPart .= $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier)
                    . ' = '
                    . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getForeignRefColumnName());

        if ($relation->isEqual()) {
            $queryPart .= ' OR '
                        . $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier)
                        . ' = '
                        . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getLocalRefColumnName())
                        . ') AND '
                        . $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier)
                        . ' != '
                        . $this->_conn->quoteIdentifier($localAlias . '.' . $localIdentifier);
        }

        return $queryPart;
    }

    /**
     * loadRoot
     *
     * @param string $name
     * @param string $componentAlias
     * @return Doctrine_Table
     * @todo DESCRIBE ME!
     * @todo this method is called only in Doctrine_Query class. Shouldn't be private or protected?
     */
    public function loadRoot($name, $componentAlias)
    {
        // get the connection for the component
        $manager = Doctrine_Manager::getInstance();
        if ( ! $this->_passedConn && $manager->hasConnectionForComponent($name)) {
            $this->_conn = $manager->getConnectionForComponent($name);
        }

        $table = $this->_conn->getTable($name);
        $tableName = $table->getTableName();

        // get the short alias for this table
        $tableAlias = $this->getSqlTableAlias($componentAlias, $tableName);
        // quote table name
        $queryPart = $this->_conn->quoteIdentifier($tableName);

        if ($this->_type === self::SELECT) {
            $queryPart .= ' ' . $this->_conn->quoteIdentifier($tableAlias);
        }

        $this->_tableAliasMap[$tableAlias] = $componentAlias;

        $queryPart .= $this->buildInheritanceJoinSql($name, $componentAlias);

        $this->_sqlParts['from'][] = $queryPart;

        $this->_queryComponents[$componentAlias] = array('table' => $table, 'map' => null);

        return $table;
    }

    /**
     * @todo DESCRIBE ME!
     * @param string $name              component class name
     * @param string $componentAlias    alias of the component in the dql
     * @return string                   query part
     */
    public function buildInheritanceJoinSql($name, $componentAlias)
    {
        // get the connection for the component
        $manager = Doctrine_Manager::getInstance();
        if ( ! $this->_passedConn && $manager->hasConnectionForComponent($name)) {
            $this->_conn = $manager->getConnectionForComponent($name);
        }

        $table = $this->_conn->getTable($name);
        $tableName = $table->getTableName();

        // get the short alias for this table
        $tableAlias = $this->getSqlTableAlias($componentAlias, $tableName);

        $queryPart = '';

        foreach ($table->getOption('joinedParents') as $parent) {
            $parentTable = $this->_conn->getTable($parent);

            $parentAlias = $componentAlias . '.' . $parent;

            // get the short alias for the parent table
            $parentTableAlias = $this->getSqlTableAlias($parentAlias, $parentTable->getTableName());

            $queryPart .= ' LEFT JOIN ' . $this->_conn->quoteIdentifier($parentTable->getTableName())
                        . ' ' . $this->_conn->quoteIdentifier($parentTableAlias) . ' ON ';

            //Doctrine_Core::dump($table->getIdentifier());
            foreach ((array) $table->getIdentifier() as $identifier) {
                $column = $table->getColumnName($identifier);

                $queryPart .= $this->_conn->quoteIdentifier($tableAlias)
                            . '.' . $this->_conn->quoteIdentifier($column)
                            . ' = ' . $this->_conn->quoteIdentifier($parentTableAlias)
                            . '.' . $this->_conn->quoteIdentifier($column);
            }
        }

        return $queryPart;
    }

    /**
     * Get count sql query for this Doctrine_Query instance.
     *
     * This method is used in Doctrine_Query::count() for returning an integer
     * for the number of records which will be returned when executed.
     *
     * @return string $q
     */
    public function getCountSqlQuery()
    {
        // triggers dql parsing/processing
        $this->getSqlQuery(array(), false); // this is ugly

        // initialize temporary variables
        $where   = $this->_sqlParts['where'];
        $having  = $this->_sqlParts['having'];
        $groupby = $this->_sqlParts['groupby'];

        $rootAlias = $this->getRootAlias();
        $tableAlias = $this->getSqlTableAlias($rootAlias);

        // Build the query base
        $q = 'SELECT COUNT(*) AS ' . $this->_conn->quoteIdentifier('num_results') . ' FROM ';

        // Build the from clause
        $from = $this->_buildSqlFromPart(true);

        // Build the where clause
        $where = ( ! empty($where)) ? ' WHERE ' . implode(' ', $where) : '';

        // Build the group by clause
        $groupby = ( ! empty($groupby)) ? ' GROUP BY ' . implode(', ', $groupby) : '';

        // Build the having clause
        $having = ( ! empty($having)) ? ' HAVING ' . implode(' AND ', $having) : '';

        // Building the from clause and finishing query
        if (count($this->_queryComponents) == 1 && empty($having)) {
            $q .= $from . $where . $groupby . $having;
        } else {
            // Subselect fields will contain only the pk of root entity
            $ta = $this->_conn->quoteIdentifier($tableAlias);

            $map = $this->getRootDeclaration();
            $idColumnNames = $map['table']->getIdentifierColumnNames();

            $pkFields = $ta . '.' . implode(', ' . $ta . '.', $this->_conn->quoteMultipleIdentifier($idColumnNames));

            // We need to do some magic in select fields if the query contain anything in having clause
            $selectFields = $pkFields;

            if ( ! empty($having)) {
                // For each field defined in select clause
                foreach ($this->_sqlParts['select'] as $field) {
                    // We only include aggregate expressions to count query
                    // This is needed because HAVING clause will use field aliases
                    if (strpos($field, '(') !== false) {
                        $selectFields .= ', ' . $field;
                    }
                }
                // Add having fields that got stripped out of select
                preg_match_all('/`[a-z0-9_]+`\.`[a-z0-9_]+`/i', $having, $matches, PREG_PATTERN_ORDER);
                if (count($matches[0]) > 0) {
                    $selectFields .= ', ' . implode(', ', array_unique($matches[0]));
                }
            }

            // If we do not have a custom group by, apply the default one
            if (empty($groupby)) {
                $groupby = ' GROUP BY ' . $pkFields;
            }

            $q .= '(SELECT ' . $selectFields . ' FROM ' . $from . $where . $groupby . $having . ') '
                . $this->_conn->quoteIdentifier('dctrn_count_query');
        }

        return $q;
    }

    /**
     * Fetches the count of the query.
     *
     * This method executes the main query without all the
     * selected fields, ORDER BY part, LIMIT part and OFFSET part.
     *
     * Example:
     * Main query:
     *      SELECT u.*, p.phonenumber FROM User u
     *          LEFT JOIN u.Phonenumber p
     *          WHERE p.phonenumber = '123 123' LIMIT 10
     *
     * The modified DQL query:
     *      SELECT COUNT(DISTINCT u.id) FROM User u
     *          LEFT JOIN u.Phonenumber p
     *          WHERE p.phonenumber = '123 123'
     *
     * @param array $params        an array of prepared statement parameters
     * @return integer             the count of this query
     */
    public function count($params = array())
    {
        $q = $this->getCountSqlQuery();
        $params = $this->getCountQueryParams($params);
        $params = $this->_conn->convertBooleans($params);

        if ($this->_resultCache) {
            $conn = $this->getConnection(); 
            $cacheDriver = $this->getResultCacheDriver();
            $hash = $this->getResultCacheHash($params).'_count';
            $cached = ($this->_expireResultCache) ? false : $cacheDriver->fetch($hash);

            if ($cached === false) {
                // cache miss
                $results = $this->getConnection()->fetchAll($q, $params);
                $cacheDriver->save($hash, serialize($results), $this->getResultCacheLifeSpan());
            } else {
                $results = unserialize($cached);
            }
        } else {
            $results = $this->getConnection()->fetchAll($q, $params);
        }

        if (count($results) > 1) {
            $count = count($results);
        } else {
            if (isset($results[0])) {
                $results[0] = array_change_key_case($results[0], CASE_LOWER);
                $count = $results[0]['num_results'];
            } else {
                $count = 0;
            }
        }

        return (int) $count;
    }

    /**
     * Queries the database with DQL (Doctrine Query Language).
     *
     * This methods parses a Dql query and builds the query parts.
     *
     * @param string $query      Dql query
     * @param array $params      prepared statement parameters
     * @param int $hydrationMode Doctrine_Core::HYDRATE_ARRAY or Doctrine_Core::HYDRATE_RECORD
     * @see Doctrine_Core::FETCH_* constants
     * @return mixed
     */
    public function query($query, $params = array(), $hydrationMode = null)
    {
        $this->parseDqlQuery($query);
        return $this->execute($params, $hydrationMode);
    }

    /**
     * Copies a Doctrine_Query object.
     *
     * @return Doctrine_Query  Copy of the Doctrine_Query instance.
     */
    public function copy(Doctrine_Query $query = null)
    {
        if ( ! $query) {
            $query = $this;
        }

        $new = clone $query;

        return $new;
    }

    /**
     * Magic method called after cloning process.
     *
     * @return void
     */
    public function __clone()
    {
        $this->_parsers = array();
        $this->_hydrator = clone $this->_hydrator;

        // Subqueries share some information from the parent so it can intermingle
        // with the dql of the main query. So when a subquery is cloned we need to
        // kill those references or it causes problems
        if ($this->isSubquery()) {
            $this->_killReference('_params');
            $this->_killReference('_tableAliasMap');
            $this->_killReference('_queryComponents');
        }
    }

    /**
     * Kill the reference for the passed class property.
     * This method simply copies the value to a temporary variable and then unsets
     * the reference and re-assigns the old value but not by reference
     *
     * @param string $key
     */
    protected function _killReference($key)
    {
        $tmp = $this->$key;
        unset($this->$key);
        $this->$key = $tmp;
    }

    /**
     * Frees the resources used by the query object. It especially breaks a
     * cyclic reference between the query object and it's parsers. This enables
     * PHP's current GC to reclaim the memory.
     * This method can therefore be used to reduce memory usage when creating
     * a lot of query objects during a request.
     *
     * @return Doctrine_Query   this object
     */
    public function free()
    {
        $this->reset();
        $this->_parsers = array();
        $this->_dqlParts = array();
    }
}
Comment by Jonathan H. Wage [ 27/May/10 ]

Do you test your changes against our test suite? We will still be releasing bug fix releases, we're just not monitoring and fixing bugs on a day to day basis. As I don't have any help on Doctrine 1 I have to spend a week every 1-2 months just going through issues and closing as many as possible.

Comment by will ferrer [ 27/May/10 ]

Hi Jonathan

Thanks for the write back.

I hadn't been using the test cases but I just tried and this latest patch was causing many of them to fail (I have resolved the issues however – see below).

The current problem is that with out my patch the following dql:

SELECT Charity.id, Charity.id as charity_id, (SQL:SELECT p.id AS p__0 FROM product_customers p LIMIT 1) as custom_subQuery FROM Charity Charity LIMIT 20

Produces the following exception:

{"type":"exception","tid":3,"exception":{},"message":"Couldn't get short alias for p","where":"#0 C:\\htdocs\\php_library\\Doctrine-1.2.2\\lib\\Doctrine\\Query.php(641): Doctrine_Query_Abstract->getSqlTableAlias('p')\n#1 C:\\htdocs\\php_library\\Doctrine-1.2.2\\lib\\Doctrine\\Query\\Select.php(37): Doctrine_Query->parseSelect('(SQL:SELECT p.i...')\n#2 C:\\htdocs\\php_library\\Doctrine-1.2.2\\lib\\Doctrine\\Query\\Abstract.php(2078): Doctrine_Query_Select->parse('(SQL:SELECT p.i...')\n#3 C:\\htdocs\\php_library\\Doctrine-1.2.2\\lib\\Doctrine\\Query.php(1168): Doctrine_Query_Abstract->_processDqlQueryPart('select', Array)\n#4 C:\\htdocs\\php_library\\Doctrine-1.2.2\\lib\\Doctrine\\Query.php(1134): Doctrine_Query->buildSqlQuery(true)\n#5 C:\\htdocs\\Root\\modules\\default\\util\\AnalyticsGrid.php(166): Doctrine_Query->getSqlQuery()\n#6 C:\\htdocs\\Root\\modules\\default\\models\\FileInFolders.php(166): Util_AnalyticsGrid->readAnalyticsGrid('{\"select\":{\"chi...', 0, 20, Array, Array, Array, Array, Array)\n#7 [internal function]: Models_FileInFolders->readAnalyticsTableProxy(37, 0, 20, Array, Array, Array, Array, Array)\n#8 C:\\htdocs\\php_library\\Doctrine-1.2.2\\lib\\Doctrine\\Table.php(2808): call_user_func_array(Array, Array)\n#9 [internal function]: Doctrine_Table->__call('readAnalytics', Array)\n#10 C:\\htdocs\\Root\\modules\\event\\controllers\\AnalyticsController.php(96): Doctrine_Table->readAnalytics(37, 0, 20, Array, Array, Array, Array, Array)\n#11 [internal function]: Event_AnalyticsController->readAnalyticsAction(Object(stdClass))\n#12 C:\\htdocs\\Root\\modules\\default\\util\\helper\\ZendDirectRouter.php(194): call_user_func_array(Array, Array)\n#13 C:\\htdocs\\Root\\modules\\default\\util\\helper\\ZendDirectRouter.php(68): Util_Helper_ZendDirectRouter->rpc(Object(stdClass))\n#14 C:\\htdocs\\Root\\modules\\default\\base\\ActionController.php(80): Util_Helper_ZendDirectRouter->dispatch()\n#15 C:\\htdocs\\php_library\\zendframework\\library\\Zend\\Controller\\Action.php(513): Base_ActionController->directAction()\n#16 C:\\htdocs\\php_library\\zendframework\\library\\Zend\\Controller\\Dispatcher\\Standard.php(289): Zend_Controller_Action->dispatch('directAction')\n#17 C:\\htdocs\\php_library\\zendframework\\library\\Zend\\Controller\\Front.php(955): Zend_Controller_Dispatcher_Standard->dispatch(Object(Zend_Controller_Request_Http), Object(Zend_Controller_Response_Http))\n#18 C:\\htdocs\\Root\\public\\index.php(78): Zend_Controller_Front->dispatch()\n#19 {main}"}

I looked at my code again and saw some very glaring errors in it (I was rushing through bugs and as soon as I thought I had fixed this one I didn't take the time to read it over – in the future will be sure to use the tests in order to avoid any such oversights).

At any rate I have changed my patch to the code and this is both working for my needs and passing all the tests again. Here is the newest version of my patched code:

<?php
/*
 *  $Id: Query.php 7490 2010-03-29 19:53:27Z jwage $
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information, see
 * <http://www.doctrine-project.org>.
 */

/**
 * Doctrine_Query
 * A Doctrine_Query object represents a DQL query. It is used to query databases for
 * data in an object-oriented fashion. A DQL query understands relations and inheritance
 * and is dbms independant.
 *
 * @package     Doctrine
 * @subpackage  Query
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link        www.doctrine-project.org
 * @since       1.0
 * @version     $Revision: 7490 $
 * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
 * @todo        Proposal: This class does far too much. It should have only 1 task: Collecting
 *              the DQL query parts and the query parameters (the query state and caching options/methods
 *              can remain here, too).
 *              The actual SQL construction could be done by a separate object (Doctrine_Query_SqlBuilder?)
 *              whose task it is to convert DQL into SQL.
 *              Furthermore the SqlBuilder? can then use other objects (Doctrine_Query_Tokenizer?),
 *              (Doctrine_Query_Parser(s)?) to accomplish his work. Doctrine_Query does not need
 *              to know the tokenizer/parsers. There could be extending
 *              implementations of SqlBuilder? that cover the specific SQL dialects.
 *              This would release Doctrine_Connection and the Doctrine_Connection_xxx classes
 *              from this tedious task.
 *              This would also largely reduce the currently huge interface of Doctrine_Query(_Abstract)
 *              and better hide all these transformation internals from the public Query API.
 *
 * @internal    The lifecycle of a Query object is the following:
 *              After construction the query object is empty. Through using the fluent
 *              query interface the user fills the query object with DQL parts and query parameters.
 *              These get collected in {@link $_dqlParts} and {@link $_params}, respectively.
 *              When the query is executed the first time, or when {@link getSqlQuery()}
 *              is called the first time, the collected DQL parts get parsed and the resulting
 *              connection-driver specific SQL is generated. The generated SQL parts are
 *              stored in {@link $_sqlParts} and the final resulting SQL query is stored in
 *              {@link $_sql}.
 */
class Doctrine_Query extends Doctrine_Query_Abstract implements Countable
{
    /**
     * @var array  The DQL keywords.
     */
    protected static $_keywords  = array('ALL',
                                         'AND',
                                         'ANY',
                                         'AS',
                                         'ASC',
                                         'AVG',
                                         'BETWEEN',
                                         'BIT_LENGTH',
                                         'BY',
                                         'CHARACTER_LENGTH',
                                         'CHAR_LENGTH',
                                         'CURRENT_DATE',
                                         'CURRENT_TIME',
                                         'CURRENT_TIMESTAMP',
                                         'DELETE',
                                         'DESC',
                                         'DISTINCT',
                                         'EMPTY',
                                         'EXISTS',
                                         'FALSE',
                                         'FETCH',
                                         'FROM',
                                         'GROUP',
                                         'HAVING',
                                         'IN',
                                         'INDEXBY',
                                         'INNER',
                                         'IS',
                                         'JOIN',
                                         'LEFT',
                                         'LIKE',
                                         'LOWER',
                                         'MEMBER',
                                         'MOD',
                                         'NEW',
                                         'NOT',
                                         'NULL',
                                         'OBJECT',
                                         'OF',
                                         'OR',
                                         'ORDER',
                                         'OUTER',
                                         'POSITION',
                                         'SELECT',
                                         'SOME',
                                         'TRIM',
                                         'TRUE',
                                         'UNKNOWN',
                                         'UPDATE',
                                         'WHERE');

    /**
     * @var array
     */
    protected $_subqueryAliases = array();

    /**
     * @var array $_aggregateAliasMap       an array containing all aggregate aliases, keys as dql aliases
     *                                      and values as sql aliases
     */
    protected $_aggregateAliasMap      = array();

    /**
     * @var array
     */
    protected $_pendingAggregates = array();

    /**
     * @param boolean $needsSubquery
     */
    protected $_needsSubquery = false;

    /**
     * @param boolean $isSubquery           whether or not this query object is a subquery of another
     *                                      query object
     */
    protected $_isSubquery;

    /**
     * @var array $_neededTables            an array containing the needed table aliases
     */
    protected $_neededTables = array();

    /**
     * @var array $pendingSubqueries        SELECT part subqueries, these are called pending subqueries since
     *                                      they cannot be parsed directly (some queries might be correlated)
     */
    protected $_pendingSubqueries = array();

    /**
     * @var array $_pendingFields           an array of pending fields (fields waiting to be parsed)
     */
    protected $_pendingFields = array();

    /**
     * @var array $_parsers                 an array of parser objects, each DQL query part has its own parser
     */
    protected $_parsers = array();

    /**
     * @var array $_pendingJoinConditions    an array containing pending joins
     */
    protected $_pendingJoinConditions = array();

    /**
     * @var array
     */
    protected $_expressionMap = array();

    /**
     * @var string $_sql            cached SQL query
     */
    protected $_sql;

    /**
     * create
     * returns a new Doctrine_Query object
     *
     * @param Doctrine_Connection $conn  optional connection parameter
     * @param string $class              Query class to instantiate
     * @return Doctrine_Query
     */
    public static function create($conn = null, $class = null)
    {
        if ( ! $class) {
            $class = Doctrine_Manager::getInstance()
                ->getAttribute(Doctrine_Core::ATTR_QUERY_CLASS);
        }
        return new $class($conn);
    }

    /**
     * Clears all the sql parts.
     */
    protected function clear()
    {
        $this->_preQueried = false;
        $this->_pendingJoinConditions = array();
        $this->_state = self::STATE_DIRTY;
    }

    /**
     * Resets the query to the state just after it has been instantiated.
     */
    public function reset()
    {
        $this->_subqueryAliases = array();
        $this->_aggregateAliasMap = array();
        $this->_pendingAggregates = array();
        $this->_pendingSubqueries = array();
        $this->_pendingFields = array();
        $this->_neededTables = array();
        $this->_expressionMap = array();
        $this->_subqueryAliases = array();
        $this->_needsSubquery = false;
        $this->_isLimitSubqueryUsed = false;
    }

    /**
     * createSubquery
     * creates a subquery
     *
     * @return Doctrine_Hydrate
     */
    public function createSubquery()
    {
        $class = get_class($this);
        $obj   = new $class();

        // copy the aliases to the subquery
        $obj->copySubqueryInfo($this);

        // this prevents the 'id' being selected, re ticket #307
        $obj->isSubquery(true);

        return $obj;
    }

    /**
     * addPendingJoinCondition
     *
     * @param string $componentAlias    component alias
     * @param string $joinCondition     dql join condition
     * @return Doctrine_Query           this object
     */
    public function addPendingJoinCondition($componentAlias, $joinCondition)
    {
        if ( ! isset($this->_pendingJoinConditions[$componentAlias])) {
            $this->_pendingJoinConditions[$componentAlias] = array();
        }

        $this->_pendingJoinConditions[$componentAlias][] = $joinCondition;
    }

    /**
     * fetchArray
     * Convenience method to execute using array fetching as hydration mode.
     *
     * @param string $params
     * @return array
     */
    public function fetchArray($params = array())
    {
        return $this->execute($params, Doctrine_Core::HYDRATE_ARRAY);
    }

    /**
     * fetchOne
     * Convenience method to execute the query and return the first item
     * of the collection.
     *
     * @param string $params        Query parameters
     * @param int $hydrationMode    Hydration mode: see Doctrine_Core::HYDRATE_* constants
     * @return mixed                Array or Doctrine_Collection, depending on hydration mode. False if no result.
     */
    public function fetchOne($params = array(), $hydrationMode = null)
    {
        $collection = $this->execute($params, $hydrationMode);

        if (is_scalar($collection)) {
            return $collection;
        }

        if (count($collection) === 0) {
            return false;
        }

        if ($collection instanceof Doctrine_Collection) {
            return $collection->getFirst();
        } else if (is_array($collection)) {
            return array_shift($collection);
        }

        return false;
    }

    /**
     * isSubquery
     * if $bool parameter is set this method sets the value of
     * Doctrine_Query::$isSubquery. If this value is set to true
     * the query object will not load the primary key fields of the selected
     * components.
     *
     * If null is given as the first parameter this method retrieves the current
     * value of Doctrine_Query::$isSubquery.
     *
     * @param boolean $bool     whether or not this query acts as a subquery
     * @return Doctrine_Query|bool
     */
    public function isSubquery($bool = null)
    {
        if ($bool === null) {
            return $this->_isSubquery;
        }

        $this->_isSubquery = (bool) $bool;
        return $this;
    }

    /**
     * getSqlAggregateAlias
     *
     * @param string $dqlAlias      the dql alias of an aggregate value
     * @return string
     */
    public function getSqlAggregateAlias($dqlAlias)
    {
        if (isset($this->_aggregateAliasMap[$dqlAlias])) {
            // mark the expression as used
            $this->_expressionMap[$dqlAlias][1] = true;

            return $this->_aggregateAliasMap[$dqlAlias];
        } else if ( ! empty($this->_pendingAggregates)) {
            $this->processPendingAggregates();

            return $this->getSqlAggregateAlias($dqlAlias);
        } else if( ! ($this->_conn->getAttribute(Doctrine_Core::ATTR_PORTABILITY) & Doctrine_Core::PORTABILITY_EXPR)){
            return $dqlAlias;
        } else {
            throw new Doctrine_Query_Exception('Unknown aggregate alias: ' . $dqlAlias);
        }
    }

    /**
     * Check if a dql alias has a sql aggregate alias
     *
     * @param string $dqlAlias 
     * @return boolean
     */
    public function hasSqlAggregateAlias($dqlAlias)
    {
        try {
            $this->getSqlAggregateAlias($dqlAlias);
            return true;
        } catch (Exception $e) {
            return false;
        }
    }

    /**
     * Adjust the processed param index for "foo.bar IN ?" support
     *
     */
    public function adjustProcessedParam($index)
    {
        // Retrieve all params
        $params = $this->getInternalParams();

        // Retrieve already processed values
        $first = array_slice($params, 0, $index);
        $last = array_slice($params, $index, count($params) - $index);

        // Include array as values splicing the params array
        array_splice($last, 0, 1, $last[0]);

        // Put all param values into a single index
        $this->_execParams = array_merge($first, $last);
    }

    /**
     * Retrieves a specific DQL query part.
     *
     * @see Doctrine_Query_Abstract::$_dqlParts
     * <code>
     * var_dump($q->getDqlPart('where'));
     * // array(2) { [0] => string(8) 'name = ?' [1] => string(8) 'date > ?' }
     * </code>
     * @param string $queryPart     the name of the query part; can be:
     *     array from, containing strings;
     *     array select, containg string;
     *     boolean forUpdate;
     *     array set;
     *     array join;
     *     array where;
     *     array groupby;
     *     array having;
     *     array orderby, containing strings such as 'id ASC';
     *     array limit, containing numerics;
     *     array offset, containing numerics;
     * @return array
     */
    public function getDqlPart($queryPart)
    {
        if ( ! isset($this->_dqlParts[$queryPart])) {
           throw new Doctrine_Query_Exception('Unknown query part ' . $queryPart);
        }

        return $this->_dqlParts[$queryPart];
    }

    /**
     * contains
     *
     * Method to check if a arbitrary piece of dql exists
     *
     * @param string $dql Arbitrary piece of dql to check for
     * @return boolean
     */
    public function contains($dql)
    {
      return stripos($this->getDql(), $dql) === false ? false : true;
    }

    /**
     * processPendingFields
     * the fields in SELECT clause cannot be parsed until the components
     * in FROM clause are parsed, hence this method is called everytime a
     * specific component is being parsed. For instance, the wildcard '*'
     * is expanded in the list of columns.
     *
     * @throws Doctrine_Query_Exception     if unknown component alias has been given
     * @param string $componentAlias        the alias of the component
     * @return string SQL code
     * @todo Description: What is a 'pending field' (and are there non-pending fields, too)?
     *       What is 'processed'? (Meaning: What information is gathered & stored away)
     */
    public function processPendingFields($componentAlias)
    {
        $tableAlias = $this->getSqlTableAlias($componentAlias);
        $table = $this->_queryComponents[$componentAlias]['table'];

        if ( ! isset($this->_pendingFields[$componentAlias])) {
            if ($this->_hydrator->getHydrationMode() != Doctrine_Core::HYDRATE_NONE) {
                if ( ! $this->_isSubquery && $componentAlias == $this->getRootAlias()) {
                    throw new Doctrine_Query_Exception("The root class of the query (alias $componentAlias) "
                            . " must have at least one field selected.");
                }
            }
            return;
        }

        // At this point we know the component is FETCHED (either it's the base class of
        // the query (FROM xyz) or its a "fetch join").

        // Check that the parent join (if there is one), is a "fetch join", too.
        if ( ! $this->isSubquery() && isset($this->_queryComponents[$componentAlias]['parent'])) {
            $parentAlias = $this->_queryComponents[$componentAlias]['parent'];
            if (is_string($parentAlias) && ! isset($this->_pendingFields[$parentAlias])
                    && $this->_hydrator->getHydrationMode() != Doctrine_Core::HYDRATE_NONE
                    && $this->_hydrator->getHydrationMode() != Doctrine_Core::HYDRATE_SCALAR
                    && $this->_hydrator->getHydrationMode() != Doctrine_Core::HYDRATE_SINGLE_SCALAR) {
                throw new Doctrine_Query_Exception("The left side of the join between "
                        . "the aliases '$parentAlias' and '$componentAlias' must have at least"
                        . " the primary key field(s) selected.");
            }
        }

        $fields = $this->_pendingFields[$componentAlias];

        // check for wildcards
        if (in_array('*', $fields)) {
            $fields = $table->getFieldNames();
        } else {
            $driverClassName = $this->_hydrator->getHydratorDriverClassName();
            // only auto-add the primary key fields if this query object is not
            // a subquery of another query object or we're using a child of the Object Graph
            // hydrator
            if ( ! $this->_isSubquery && is_subclass_of($driverClassName, 'Doctrine_Hydrator_Graph')) {
                $fields = array_unique(array_merge((array) $table->getIdentifier(), $fields));
            }
        }

        $sql = array();
        foreach ($fields as $fieldName) {
            $columnName = $table->getColumnName($fieldName);
            if (($owner = $table->getColumnOwner($columnName)) !== null &&
                    $owner !== $table->getComponentName()) {

                $parent = $this->_conn->getTable($owner);
                $columnName = $parent->getColumnName($fieldName);
                $parentAlias = $this->getSqlTableAlias($componentAlias . '.' . $parent->getComponentName());
                $sql[] = $this->_conn->quoteIdentifier($parentAlias) . '.' . $this->_conn->quoteIdentifier($columnName)
                       . ' AS '
                       . $this->_conn->quoteIdentifier($tableAlias . '__' . $columnName);
            } else {
                $columnName = $table->getColumnName($fieldName);
                $sql[] = $this->_conn->quoteIdentifier($tableAlias) . '.' . $this->_conn->quoteIdentifier($columnName)
                       . ' AS '
                       . $this->_conn->quoteIdentifier($tableAlias . '__' . $columnName);
            }
        }

        $this->_neededTables[] = $tableAlias;

        return implode(', ', $sql);
    }

    /**
     * Parses a nested field
     * <code>
     * $q->parseSelectField('u.Phonenumber.value');
     * </code>
     *
     * @param string $field
     * @throws Doctrine_Query_Exception     if unknown component alias has been given
     * @return string   SQL fragment
     * @todo Description: Explain what this method does. Is there a relation to parseSelect()?
     *       This method is not used from any class or testcase in the Doctrine package.
     *
     */
    public function parseSelectField($field)
    {
        $terms = explode('.', $field);

        if (isset($terms[1])) {
            $componentAlias = $terms[0];
            $field = $terms[1];
        } else {
            reset($this->_queryComponents);
            $componentAlias = key($this->_queryComponents);
            $fields = $terms[0];
        }

        $tableAlias = $this->getSqlTableAlias($componentAlias);
        $table      = $this->_queryComponents[$componentAlias]['table'];


        // check for wildcards
        if ($field === '*') {
            $sql = array();

            foreach ($table->getColumnNames() as $field) {
                $sql[] = $this->parseSelectField($componentAlias . '.' . $field);
            }

            return implode(', ', $sql);
        } else {
            $name = $table->getColumnName($field);

            $this->_neededTables[] = $tableAlias;

            return $this->_conn->quoteIdentifier($tableAlias . '.' . $name)
                   . ' AS '
                   . $this->_conn->quoteIdentifier($tableAlias . '__' . $name);
        }
    }

    /**
     * getExpressionOwner
     * returns the component alias for owner of given expression
     *
     * @param string $expr      expression from which to get to owner from
     * @return string           the component alias
     * @todo Description: What does it mean if a component is an 'owner' of an expression?
     *       What kind of 'expression' are we talking about here?
     */
    public function getExpressionOwner($expr)
    {
        if (strtoupper(substr(trim($expr, '( '), 0, 6)) !== 'SELECT') {
            preg_match_all("/[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*[\.[a-z0-9]+]*/i", $expr, $matches);

            $match = current($matches);

            if (isset($match[0])) {
                $terms = explode('.', $match[0]);

                return $terms[0];
            }
        }
        return $this->getRootAlias();

    }

    /**
     * parseSelect
     * parses the query select part and
     * adds selected fields to pendingFields array
     *
     * @param string $dql
     * @todo Description: What information is extracted (and then stored)?
     */
    public function parseSelect($dql)
    {
        $refs = $this->_tokenizer->sqlExplode($dql, ',');

        $pos   = strpos(trim($refs[0]), ' ');
        $first = substr($refs[0], 0, $pos);

        // check for DISTINCT keyword
        if ($first === 'DISTINCT') {
            $this->_sqlParts['distinct'] = true;

            $refs[0] = substr($refs[0], ++$pos);
        }

        $parsedComponents = array();

        foreach ($refs as $reference) {
            $reference = trim($reference);

            if (empty($reference)) {
                continue;
            }

            $terms = $this->_tokenizer->sqlExplode($reference, ' ');
            $pos   = strpos($terms[0], '(');

            if (count($terms) > 1 || $pos !== false) {
                $expression = array_shift($terms);
                $alias = array_pop($terms);

                if ( ! $alias) {
                    $alias = substr($expression, 0, $pos);
                }
				
				
			//Patched By Will Ferrer to detect when a subquery was encountered and use an existing componentAlias if the expression is a subquery. (not well tested) Fixes Bug -- DC-706
				if ($pos !== false && substr($expression, 0, 1) !== "'" && substr($expression, 0, $pos) == '') {
					$_queryComponents = $this->_queryComponents;
					reset($_queryComponents);
					$componentAlias = key($_queryComponents);
				} else {
               		$componentAlias = $this->getExpressionOwner($expression);
				}
			//End Patch
			
				$expression = $this->parseClause($expression);

                $tableAlias = $this->getSqlTableAlias($componentAlias);
				
                $index    = count($this->_aggregateAliasMap);

                $sqlAlias = $this->_conn->quoteIdentifier($tableAlias . '__' . $index);

                $this->_sqlParts['select'][] = $expression . ' AS ' . $sqlAlias;

                $this->_aggregateAliasMap[$alias] = $sqlAlias;
                $this->_expressionMap[$alias][0] = $expression;

                $this->_queryComponents[$componentAlias]['agg'][$index] = $alias;
				
                $this->_neededTables[] = $tableAlias;
            } else {
                $e = explode('.', $terms[0]);

                if (isset($e[1])) {
                    $componentAlias = $e[0];
                    $field = $e[1];
                } else {
                    reset($this->_queryComponents);
                    $componentAlias = key($this->_queryComponents);
                    $field = $e[0];
                }

                $this->_pendingFields[$componentAlias][] = $field;
            }
        }
    }

    /**
     * parseClause
     * parses given DQL clause
     *
     * this method handles five tasks:
     *
     * 1. Converts all DQL functions to their native SQL equivalents
     * 2. Converts all component references to their table alias equivalents
     * 3. Converts all field names to actual column names
     * 4. Quotes all identifiers
     * 5. Parses nested clauses and subqueries recursively
     *
     * @return string   SQL string
     * @todo Description: What is a 'dql clause' (and what not)?
     *       Refactor: Too long & nesting level
     */
    public function parseClause($clause)
    {
        $clause = $this->_conn->dataDict->parseBoolean(trim($clause));

        if (is_numeric($clause)) {
           return $clause;
        }

        $terms = $this->_tokenizer->clauseExplode($clause, array(' ', '+', '-', '*', '/', '<', '>', '=', '>=', '<=', '&', '|'));
        $str = '';

        foreach ($terms as $term) {
            $pos = strpos($term[0], '(');

            if ($pos !== false && substr($term[0], 0, 1) !== "'") {
                $name = substr($term[0], 0, $pos);

                $term[0] = $this->parseFunctionExpression($term[0]);
            } else {
                if (substr($term[0], 0, 1) !== "'" && substr($term[0], -1) !== "'") {
                    if (strpos($term[0], '.') !== false) {
                        if ( ! is_numeric($term[0])) {
                            $e = explode('.', $term[0]);

                            $field = array_pop($e);

                            if ($this->getType() === Doctrine_Query::SELECT) {
                                $componentAlias = implode('.', $e);

                                if (empty($componentAlias)) {
                                    $componentAlias = $this->getRootAlias();
                                }

                                $this->load($componentAlias);

                                // check the existence of the component alias
                                if ( ! isset($this->_queryComponents[$componentAlias])) {
                                    throw new Doctrine_Query_Exception('Unknown component alias ' . $componentAlias);
                                }

                                $table = $this->_queryComponents[$componentAlias]['table'];

                                $def = $table->getDefinitionOf($field);

                                // get the actual field name from alias
                                $field = $table->getColumnName($field);

                                // check column existence
                                if ( ! $def) {
                                    throw new Doctrine_Query_Exception('Unknown column ' . $field);
                                }

                                if (isset($def['owner'])) {
                                    $componentAlias = $componentAlias . '.' . $def['owner'];
                                }

                                $tableAlias = $this->getSqlTableAlias($componentAlias);

                                // build sql expression
                                $term[0] = $this->_conn->quoteIdentifier($tableAlias)
                                         . '.'
                                         . $this->_conn->quoteIdentifier($field);
                            } else {
                                // build sql expression
                                $field = $this->getRoot()->getColumnName($field);
                                $term[0] = $this->_conn->quoteIdentifier($field);
                            }
                        }
                    } else {
                        if ( ! empty($term[0]) && ! in_array(strtoupper($term[0]), self::$_keywords) &&
                             ! is_numeric($term[0]) && $term[0] !== '?' && substr($term[0], 0, 1) !== ':') {

                            $componentAlias = $this->getRootAlias();

                            $found = false;

                            if ($componentAlias !== false && $componentAlias !== null) {
                                $table = $this->_queryComponents[$componentAlias]['table'];

                                // check column existence
                                if ($table->hasField($term[0])) {
                                    $found = true;

                                    $def = $table->getDefinitionOf($term[0]);

                                    // get the actual column name from field name
                                    $term[0] = $table->getColumnName($term[0]);


                                    if (isset($def['owner'])) {
                                        $componentAlias = $componentAlias . '.' . $def['owner'];
                                    }

                                    $tableAlias = $this->getSqlTableAlias($componentAlias);

                                    if ($this->getType() === Doctrine_Query::SELECT) {
                                        // build sql expression
                                        $term[0] = $this->_conn->quoteIdentifier($tableAlias)
                                                 . '.'
                                                 . $this->_conn->quoteIdentifier($term[0]);
                                    } else {
                                        // build sql expression
                                        $term[0] = $this->_conn->quoteIdentifier($term[0]);
                                    }
                                } else {
                                    $found = false;
                                }
                            }

                            if ( ! $found) {
                                $term[0] = $this->getSqlAggregateAlias($term[0]);
                            }
                        }
                    }
                }
            }

            $str .= $term[0] . $term[1];
        }
        return $str;
    }

    public function parseIdentifierReference($expr)
    {

    }

    public function parseFunctionExpression($expr)
    {
        $pos = strpos($expr, '(');
        $name = substr($expr, 0, $pos);

        if ($name === '') {
            return $this->parseSubquery($expr);
        }

        $argStr = substr($expr, ($pos + 1), -1);
        $args   = array();
        // parse args

        foreach ($this->_tokenizer->sqlExplode($argStr, ',') as $arg) {
           $args[] = $this->parseClause($arg);
        }

        // convert DQL function to its RDBMS specific equivalent
        try {
            $expr = call_user_func_array(array($this->_conn->expression, $name), $args);
        } catch (Doctrine_Expression_Exception $e) {
            throw new Doctrine_Query_Exception('Unknown function ' . $name . '.');
        }

        return $expr;
    }


    public function parseSubquery($subquery)
    {
        $trimmed = trim($this->_tokenizer->bracketTrim($subquery));

        // check for possible subqueries
        if (substr($trimmed, 0, 4) == 'FROM' || substr($trimmed, 0, 6) == 'SELECT') {
            // parse subquery
            $q = $this->createSubquery()->parseDqlQuery($trimmed);
            $trimmed = $q->getSqlQuery();
            $q->free();
        } else if (substr($trimmed, 0, 4) == 'SQL:') {
            $trimmed = substr($trimmed, 4);
        } else {
            $e = $this->_tokenizer->sqlExplode($trimmed, ',');

            $value = array();
            $index = false;

            foreach ($e as $part) {
                $value[] = $this->parseClause($part);
            }

            $trimmed = implode(', ', $value);
        }

        return '(' . $trimmed . ')';
    }


    /**
     * processPendingSubqueries
     * processes pending subqueries
     *
     * subqueries can only be processed when the query is fully constructed
     * since some subqueries may be correlated
     *
     * @return void
     * @todo Better description. i.e. What is a 'pending subquery'? What does 'processed' mean?
     *       (parsed? sql is constructed? some information is gathered?)
     */
    public function processPendingSubqueries()
    {
        foreach ($this->_pendingSubqueries as $value) {
            list($dql, $alias) = $value;

            $subquery = $this->createSubquery();

            $sql = $subquery->parseDqlQuery($dql, false)->getQuery();
            $subquery->free();

            reset($this->_queryComponents);
            $componentAlias = key($this->_queryComponents);
            $tableAlias = $this->getSqlTableAlias($componentAlias);

            $sqlAlias = $tableAlias . '__' . count($this->_aggregateAliasMap);

            $this->_sqlParts['select'][] = '(' . $sql . ') AS ' . $this->_conn->quoteIdentifier($sqlAlias);

            $this->_aggregateAliasMap[$alias] = $sqlAlias;
            $this->_queryComponents[$componentAlias]['agg'][] = $alias;
        }
        $this->_pendingSubqueries = array();
    }

    /**
     * processPendingAggregates
     * processes pending aggregate values for given component alias
     *
     * @return void
     * @todo Better description. i.e. What is a 'pending aggregate'? What does 'processed' mean?
     */
    public function processPendingAggregates()
    {
        // iterate trhough all aggregates
        foreach ($this->_pendingAggregates as $aggregate) {
            list ($expression, $components, $alias) = $aggregate;

            $tableAliases = array();

            // iterate through the component references within the aggregate function
            if ( ! empty ($components)) {
                foreach ($components as $component) {

                    if (is_numeric($component)) {
                        continue;
                    }

                    $e = explode('.', $component);

                    $field = array_pop($e);
                    $componentAlias = implode('.', $e);

                    // check the existence of the component alias
                    if ( ! isset($this->_queryComponents[$componentAlias])) {
                        throw new Doctrine_Query_Exception('Unknown component alias ' . $componentAlias);
                    }

                    $table = $this->_queryComponents[$componentAlias]['table'];

                    $field = $table->getColumnName($field);

                    // check column existence
                    if ( ! $table->hasColumn($field)) {
                        throw new Doctrine_Query_Exception('Unknown column ' . $field);
                    }

                    $sqlTableAlias = $this->getSqlTableAlias($componentAlias);

                    $tableAliases[$sqlTableAlias] = true;

                    // build sql expression

                    $identifier = $this->_conn->quoteIdentifier($sqlTableAlias . '.' . $field);
                    $expression = str_replace($component, $identifier, $expression);
                }
            }

            if (count($tableAliases) !== 1) {
                $componentAlias = reset($this->_tableAliasMap);
                $tableAlias = key($this->_tableAliasMap);
            }

            $index    = count($this->_aggregateAliasMap);
            $sqlAlias = $this->_conn->quoteIdentifier($tableAlias . '__' . $index);

            $this->_sqlParts['select'][] = $expression . ' AS ' . $sqlAlias;

            $this->_aggregateAliasMap[$alias] = $sqlAlias;
            $this->_expressionMap[$alias][0] = $expression;

            $this->_queryComponents[$componentAlias]['agg'][$index] = $alias;

            $this->_neededTables[] = $tableAlias;
        }
        // reset the state
        $this->_pendingAggregates = array();
    }

    /**
     * _buildSqlQueryBase
     * returns the base of the generated sql query
     * On mysql driver special strategy has to be used for DELETE statements
     * (where is this special strategy??)
     *
     * @return string       the base of the generated sql query
     */
    protected function _buildSqlQueryBase()
    {
        switch ($this->_type) {
            case self::DELETE:
                $q = 'DELETE FROM ';
            break;
            case self::UPDATE:
                $q = 'UPDATE ';
            break;
            case self::SELECT:
                $distinct = ($this->_sqlParts['distinct']) ? 'DISTINCT ' : '';
                $q = 'SELECT ' . $distinct . implode(', ', $this->_sqlParts['select']) . ' FROM ';
            break;
        }
        return $q;
    }

    /**
     * _buildSqlFromPart
     * builds the from part of the query and returns it
     *
     * @return string   the query sql from part
     */
    protected function _buildSqlFromPart($ignorePending = false)
    {
        $q = '';

        foreach ($this->_sqlParts['from'] as $k => $part) {
            $e = explode(' ', $part);

            if ($k === 0) {
                if ( ! $ignorePending && $this->_type == self::SELECT) {
                    // We may still have pending conditions
                    $alias = count($e) > 1
                        ? $this->getComponentAlias($e[1])
                        : null;
                    $where = $this->_processPendingJoinConditions($alias);

                    // apply inheritance to WHERE part
                    if ( ! empty($where)) {
                        if (count($this->_sqlParts['where']) > 0) {
                            $this->_sqlParts['where'][] = 'AND';
                        }

                        if (substr($where, 0, 1) === '(' && substr($where, -1) === ')') {
                            $this->_sqlParts['where'][] = $where;
                        } else {
                            $this->_sqlParts['where'][] = '(' . $where . ')';
                        }
                    }
                }

                $q .= $part;

                continue;
            }

            // preserve LEFT JOINs only if needed
            // Check if it's JOIN, if not add a comma separator instead of space
            if ( ! preg_match('/\bJOIN\b/i', $part) && ! isset($this->_pendingJoinConditions[$k])) {
                $q .= ', ' . $part;
            } else {
                if (substr($part, 0, 9) === 'LEFT JOIN') {
                    $aliases = array_merge($this->_subqueryAliases,
                                array_keys($this->_neededTables));

                    if ( ! in_array($e[3], $aliases) && ! in_array($e[2], $aliases) && ! empty($this->_pendingFields)) {
                        continue;
                    }

                }

                if ( ! $ignorePending && isset($this->_pendingJoinConditions[$k])) {
                    if (strpos($part, ' ON ') !== false) {
                        $part .= ' AND ';
                    } else {
                        $part .= ' ON ';
                    }

                    $part .= $this->_processPendingJoinConditions($k);
                }

                $componentAlias = $this->getComponentAlias($e[3]);
                $string = $this->getInheritanceCondition($componentAlias);

                if ($string) {
                    $part = $part . ' AND ' . $string;
                }
                $q .= ' ' . $part;
            }

            $this->_sqlParts['from'][$k] = $part;
        }
        return $q;
    }

    /**
     * Processes the pending join conditions, used for dynamically add conditions
     * to root component/joined components without interfering in the main dql
     * handling.
     *
     * @param string $alias Component Alias
     * @return Processed pending conditions
     */
    protected function _processPendingJoinConditions($alias)
    {
        $parts = array();

        if ($alias !== null && isset($this->_pendingJoinConditions[$alias])) {
            $parser = new Doctrine_Query_JoinCondition($this, $this->_tokenizer);

            foreach ($this->_pendingJoinConditions[$alias] as $joinCondition) {
                $parts[] = $parser->parse($joinCondition);
            }

            // FIX #1860 and #1876: Cannot unset them, otherwise query cannot be reused later
            //unset($this->_pendingJoinConditions[$alias]);
        }

        return (count($parts) > 0 ? '(' . implode(') AND (', $parts) . ')' : '');
    }

    /**
     * builds the sql query from the given parameters and applies things such as
     * column aggregation inheritance and limit subqueries if needed
     *
     * @param array $params             an array of prepared statement params (needed only in mysql driver
     *                                  when limit subquery algorithm is used)
     * @param bool $limitSubquery Whether or not to try and apply the limit subquery algorithm
     * @return string                   the built sql query
     */
    public function getSqlQuery($params = array(), $limitSubquery = true)
    {
        // Assign building/execution specific params
        $this->_params['exec'] = $params;

        // Initialize prepared parameters array
        $this->_execParams = $this->getFlattenedParams();

        if ($this->_state !== self::STATE_DIRTY) {
            $this->fixArrayParameterValues($this->getInternalParams());

            // Return compiled SQL
            return $this->_sql;
        }
        return $this->buildSqlQuery($limitSubquery);
    }

    /**
     * Build the SQL query from the DQL
     *
     * @param bool $limitSubquery Whether or not to try and apply the limit subquery algorithm
     * @return string $sql The generated SQL string
     */
    public function buildSqlQuery($limitSubquery = true)
    {
        // reset the state
        if ( ! $this->isSubquery()) {
            $this->_queryComponents = array();
            $this->_pendingAggregates = array();
            $this->_aggregateAliasMap = array();
        }

        $this->reset();

        // invoke the preQuery hook
        $this->_preQuery();

        // process the DQL parts => generate the SQL parts.
        // this will also populate the $_queryComponents.
        foreach ($this->_dqlParts as $queryPartName => $queryParts) {
            // If we are parsing FROM clause, we'll need to diff the queryComponents later
            if ($queryPartName == 'from') {
                // Pick queryComponents before processing
                $queryComponentsBefore = $this->getQueryComponents();
            }

            // FIX #1667: _sqlParts are cleaned inside _processDqlQueryPart.
            if ($queryPartName != 'forUpdate') {
                $this->_processDqlQueryPart($queryPartName, $queryParts);
            }

            // We need to define the root alias
            if ($queryPartName == 'from') {
                // Pick queryComponents aftr processing
                $queryComponentsAfter = $this->getQueryComponents();

                // Root alias is the key of difference of query components
                $diffQueryComponents = array_diff_key($queryComponentsAfter, $queryComponentsBefore);
                $this->_rootAlias = key($diffQueryComponents);
            }
        }
        $this->_state = self::STATE_CLEAN;

        // Proceed with the generated SQL
        if (empty($this->_sqlParts['from'])) {
            return false;
        }

        $needsSubQuery = false;
        $subquery = '';
        $map = $this->getRootDeclaration();
        $table = $map['table'];
        $rootAlias = $this->getRootAlias();

        if ( ! empty($this->_sqlParts['limit']) && $this->_needsSubquery &&
                $table->getAttribute(Doctrine_Core::ATTR_QUERY_LIMIT) == Doctrine_Core::LIMIT_RECORDS) {
            // We do not need a limit-subquery if DISTINCT is used
            // and the selected fields are either from the root component or from a localKey relation (hasOne)
            // (i.e. DQL: SELECT DISTINCT u.id FROM User u LEFT JOIN u.phonenumbers LIMIT 5).
            if(!$this->_sqlParts['distinct']) {
                $this->_isLimitSubqueryUsed = true;
                $needsSubQuery = true;
            } else {
                foreach( array_keys($this->_pendingFields) as $alias){
                    //no subquery for root fields
                    if($alias == $this->getRootAlias()){
                        continue;
                    }

                    //no subquery for ONE relations
                    if(isset($this->_queryComponents[$alias]['relation']) &&
                        $this->_queryComponents[$alias]['relation']->getType() == Doctrine_Relation::ONE){
                        continue;
                    }

                    $this->_isLimitSubqueryUsed = true;
                    $needsSubQuery = true;
                }
            }
        }

        $sql = array();

        if ( ! empty($this->_pendingFields)) {
            foreach ($this->_queryComponents as $alias => $map) {
                $fieldSql = $this->processPendingFields($alias);
                if ( ! empty($fieldSql)) {
                    $sql[] = $fieldSql;
                }
            }
        }

        if ( ! empty($sql)) {
            array_unshift($this->_sqlParts['select'], implode(', ', $sql));
        }

        $this->_pendingFields = array();

        // build the basic query
        $q  = $this->_buildSqlQueryBase();
        $q .= $this->_buildSqlFromPart();

        if ( ! empty($this->_sqlParts['set'])) {
            $q .= ' SET ' . implode(', ', $this->_sqlParts['set']);
        }

        $string = $this->getInheritanceCondition($this->getRootAlias());

        // apply inheritance to WHERE part
        if ( ! empty($string)) {
            if (count($this->_sqlParts['where']) > 0) {
                $this->_sqlParts['where'][] = 'AND';
            }

            if (substr($string, 0, 1) === '(' && substr($string, -1) === ')') {
                $this->_sqlParts['where'][] = $string;
            } else {
                $this->_sqlParts['where'][] = '(' . $string . ')';
            }
        }

        $modifyLimit = true;
        $limitSubquerySql = '';

        if ( ( ! empty($this->_sqlParts['limit']) || ! empty($this->_sqlParts['offset'])) && $needsSubQuery && $limitSubquery) {
            $subquery = $this->getLimitSubquery();

            // what about composite keys?
            $idColumnName = $table->getColumnName($table->getIdentifier());

            switch (strtolower($this->_conn->getDriverName())) {
                case 'mysql':
                    $this->useQueryCache(false);

                    // mysql doesn't support LIMIT in subqueries
                    $list = $this->_conn->execute($subquery, $this->_execParams)->fetchAll(Doctrine_Core::FETCH_COLUMN);
                    $subquery = implode(', ', array_map(array($this->_conn, 'quote'), $list));

                    break;

                case 'pgsql':
                    $subqueryAlias = $this->_conn->quoteIdentifier('doctrine_subquery_alias');

                    // pgsql needs special nested LIMIT subquery
                    $subquery = 'SELECT ' . $subqueryAlias . '.' . $this->_conn->quoteIdentifier($idColumnName)
                            . ' FROM (' . $subquery . ') AS ' . $subqueryAlias;

                    break;
            }

            $field = $this->getSqlTableAlias($rootAlias) . '.' . $idColumnName;

            // FIX #1868: If not ID under MySQL is found to be restricted, restrict pk column for null
            //            (which will lead to a return of 0 items)
            $limitSubquerySql = $this->_conn->quoteIdentifier($field)
                              . (( ! empty($subquery)) ? ' IN (' . $subquery . ')' : ' IS NULL')
                              . ((count($this->_sqlParts['where']) > 0) ? ' AND ' : '');

            $modifyLimit = false;
        }

        // FIX #DC-26: Include limitSubquerySql as major relevance in conditions
        $emptyWhere = empty($this->_sqlParts['where']);

        if ( ! ($emptyWhere && $limitSubquerySql == '')) {
            $where = implode(' ', $this->_sqlParts['where']);
            $where = ($where == '' || (substr($where, 0, 1) === '(' && substr($where, -1) === ')'))
                ? $where : '(' . $where . ')';

            $q .= ' WHERE ' . $limitSubquerySql . $where;
            //   .  (($limitSubquerySql == '' && count($this->_sqlParts['where']) == 1) ? substr($where, 1, -1) : $where);
        }

        // Fix the orderbys so we only have one orderby per value
        foreach ($this->_sqlParts['orderby'] as $k => $orderBy) {
            $e = explode(', ', $orderBy);
            unset($this->_sqlParts['orderby'][$k]);
            foreach ($e as $v) {
                $this->_sqlParts['orderby'][] = $v;
            }
        }

        // Add the default orderBy statements defined in the relationships and table classes
        // Only do this for SELECT queries
        if ($this->_type === self::SELECT) {
            foreach ($this->_queryComponents as $alias => $map) {
                $sqlAlias = $this->getSqlTableAlias($alias);
                if (isset($map['relation'])) {
                    $orderBy = $map['relation']->getOrderByStatement($sqlAlias, true);
                    if ($orderBy == $map['relation']['orderBy']) {
                        if (isset($map['ref'])) {
                            $orderBy = $map['relation']['refTable']->processOrderBy($sqlAlias, $map['relation']['orderBy'], true);
                        } else {
                            $orderBy = null;
                        }
                    }
				} else {
					$orderBy = $map['table']->getOrderByStatement($sqlAlias, true);
				}

                if ($orderBy) {
                    $e = explode(',', $orderBy);
                    $e = array_map('trim', $e);
                    foreach ($e as $v) {
                        if ( ! in_array($v, $this->_sqlParts['orderby'])) {
                            $this->_sqlParts['orderby'][] = $v;
                        }
                    }
                }
            }
        }

        $q .= ( ! empty($this->_sqlParts['groupby'])) ? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby'])  : '';
        $q .= ( ! empty($this->_sqlParts['having'])) ?  ' HAVING '   . implode(' AND ', $this->_sqlParts['having']): '';
        $q .= ( ! empty($this->_sqlParts['orderby'])) ? ' ORDER BY ' . implode(', ', $this->_sqlParts['orderby'])  : '';

        if ($modifyLimit) {
            $q = $this->_conn->modifyLimitQuery($q, $this->_sqlParts['limit'], $this->_sqlParts['offset']);
        }

        $q .= $this->_sqlParts['forUpdate'] === true ? ' FOR UPDATE ' : '';

        $this->_sql = $q;

        $this->clear();

        return $q;
    }

    /**
     * getLimitSubquery
     * this is method is used by the record limit algorithm
     *
     * when fetching one-to-many, many-to-many associated data with LIMIT clause
     * an additional subquery is needed for limiting the number of returned records instead
     * of limiting the number of sql result set rows
     *
     * @return string       the limit subquery
     * @todo A little refactor to make the method easier to understand & maybe shorter?
     */
    public function getLimitSubquery()
    {
        $map = reset($this->_queryComponents);
        $table = $map['table'];
        $componentAlias = key($this->_queryComponents);

        // get short alias
        $alias = $this->getSqlTableAlias($componentAlias);
        // what about composite keys?
        $primaryKey = $alias . '.' . $table->getColumnName($table->getIdentifier());

        $driverName = $this->_conn->getAttribute(Doctrine_Core::ATTR_DRIVER_NAME);

        // initialize the base of the subquery
        if (($driverName == 'oracle' || $driverName == 'oci') && $this->_isOrderedByJoinedColumn()) {
            $subquery = 'SELECT ';
        } else {
            $subquery = 'SELECT DISTINCT ';
        }
        $subquery .= $this->_conn->quoteIdentifier($primaryKey);

        // pgsql & oracle need the order by fields to be preserved in select clause
        if ($driverName == 'pgsql' || $driverName == 'oracle' || $driverName == 'oci' || $driverName == 'mssql' || $driverName == 'odbc') {
            foreach ($this->_sqlParts['orderby'] as $part) {
                // Remove identifier quoting if it exists
                $e = $this->_tokenizer->bracketExplode($part, ' ');
                foreach ($e as $f) {
                    if ($f == 0 || $f % 2 == 0) {
                        $partOriginal = str_replace(',', '', trim($f));
                        $callback = create_function('$e', 'return trim($e, \'[]`"\');');
                        $part = trim(implode('.', array_map($callback, explode('.', $partOriginal))));
                
                        if (strpos($part, '.') === false) {
                            continue;
                        }
                
                        // don't add functions
                        if (strpos($part, '(') !== false) {
                            continue;
                        }
                
                        // don't add primarykey column (its already in the select clause)
                        if ($part !== $primaryKey) {
                            $subquery .= ', ' . $partOriginal;
                        }
                    }
                }
            }
        }

        $orderby = $this->_sqlParts['orderby'];
        $having = $this->_sqlParts['having'];
        if ($driverName == 'mysql' || $driverName == 'pgsql') {
            foreach ($this->_expressionMap as $dqlAlias => $expr) {
                if (isset($expr[1])) {
                    $subquery .= ', ' . $expr[0] . ' AS ' . $this->_aggregateAliasMap[$dqlAlias];
                }
            }
        } else {
            foreach ($this->_expressionMap as $dqlAlias => $expr) {
                if (isset($expr[1])) {
                    foreach ($having as $k => $v) {
                        $having[$k] = str_replace($this->_aggregateAliasMap[$dqlAlias], $expr[0], $v);
                    }
                    foreach ($orderby as $k => $v) {
                        $e = explode(' ', $v);
                        if ($e[0] == $this->_aggregateAliasMap[$dqlAlias]) {
                            $orderby[$k] = $expr[0];
                        }
                    }
                }
            }
        }

        // Add having fields that got stripped out of select
        preg_match_all('/`[a-z0-9_]+`\.`[a-z0-9_]+`/i', implode(' ', $having), $matches, PREG_PATTERN_ORDER);
        if (count($matches[0]) > 0) {
            $subquery .= ', ' . implode(', ', array_unique($matches[0]));
        }

        $subquery .= ' FROM';

        foreach ($this->_sqlParts['from'] as $part) {
            // preserve LEFT JOINs only if needed
            if (substr($part, 0, 9) === 'LEFT JOIN') {
                $e = explode(' ', $part);
			//Patched by Will Ferrer to also check for groupBys. Fixes Bug -- DC-594
                if (empty($this->_sqlParts['orderby']) && empty($this->_sqlParts['where']) && empty($this->_sqlParts['having']) && empty($this->_sqlParts['groupby'])) {
             //End Patch
				   continue;
                }
            }

            $subquery .= ' ' . $part;
        }

        // all conditions must be preserved in subquery
        $subquery .= ( ! empty($this->_sqlParts['where']))?   ' WHERE '    . implode(' ', $this->_sqlParts['where'])  : '';
        $subquery .= ( ! empty($this->_sqlParts['groupby']))? ' GROUP BY ' . implode(', ', $this->_sqlParts['groupby'])   : '';
        $subquery .= ( ! empty($having))?  ' HAVING '   . implode(' AND ', $having) : '';
        $subquery .= ( ! empty($orderby))? ' ORDER BY ' . implode(', ', $orderby)  : '';

        if (($driverName == 'oracle' || $driverName == 'oci') && $this->_isOrderedByJoinedColumn()) {
            // When using "ORDER BY x.foo" where x.foo is a column of a joined table,
            // we may get duplicate primary keys because all columns in ORDER BY must appear
            // in the SELECT list when using DISTINCT. Hence we need to filter out the
            // primary keys with an additional DISTINCT subquery.
            // #1038
            $quotedIdentifierColumnName = $this->_conn->quoteIdentifier($table->getColumnName($table->getIdentifier()));
            $subquery = 'SELECT doctrine_subquery_alias.' . $quotedIdentifierColumnName
                    . ' FROM (' . $subquery . ') doctrine_subquery_alias'
                    . ' GROUP BY doctrine_subquery_alias.' . $quotedIdentifierColumnName
                    . ' ORDER BY MIN(ROWNUM)';
        }

        // add driver specific limit clause
        $subquery = $this->_conn->modifyLimitSubquery($table, $subquery, $this->_sqlParts['limit'], $this->_sqlParts['offset']);

        $parts = $this->_tokenizer->quoteExplode($subquery, ' ', "'", "'");

        foreach ($parts as $k => $part) {
            if (strpos($part, ' ') !== false) {
                continue;
            }

            $part = str_replace(array('"', "'", '`'), "", $part);

            if ($this->hasSqlTableAlias($part)) {
                $parts[$k] = $this->_conn->quoteIdentifier($this->generateNewSqlTableAlias($part));
                continue;
            }

            if (strpos($part, '.') === false) {
                continue;
            }

            preg_match_all("/[a-zA-Z0-9_]+\.[a-z0-9_]+/i", $part, $m);

            foreach ($m[0] as $match) {
                $e = explode('.', $match);

                // Rebuild the original part without the newly generate alias and with quoting reapplied
                $e2 = array();
                foreach ($e as $k2 => $v2) {
                  $e2[$k2] = $this->_conn->quoteIdentifier($v2);
                }
                $match = implode('.', $e2);

                // Generate new table alias
                $e[0] = $this->generateNewSqlTableAlias($e[0]);

                // Requote the part with the newly generated alias
                foreach ($e as $k2 => $v2) {
                  $e[$k2] = $this->_conn->quoteIdentifier($v2);
                }

                $replace = implode('.' , $e);

                // Replace the original part with the new part with new sql table alias
                $parts[$k] = str_replace($match, $replace, $parts[$k]);
            }
        }

        if ($driverName == 'mysql' || $driverName == 'pgsql') {
            foreach ($parts as $k => $part) {
                if (strpos($part, "'") !== false) {
                    continue;
                }
                if (strpos($part, '__') == false) {
                    continue;
                }

                preg_match_all("/[a-zA-Z0-9_]+\_\_[a-z0-9_]+/i", $part, $m);

                foreach ($m[0] as $match) {
                    $e = explode('__', $match);
                    $e[0] = $this->generateNewSqlTableAlias($e[0]);

                    $parts[$k] = str_replace($match, implode('__', $e), $parts[$k]);
                }
            }
        }

        $subquery = implode(' ', $parts);
        return $subquery;
    }

    /**
     * Checks whether the query has an ORDER BY on a column of a joined table.
     * This information is needed in special scenarios like the limit-offset when its
     * used with an Oracle database.
     *
     * @return boolean  TRUE if the query is ordered by a joined column, FALSE otherwise.
     */
    private function _isOrderedByJoinedColumn() {
        if ( ! $this->_queryComponents) {
            throw new Doctrine_Query_Exception("The query is in an invalid state for this "
                    . "operation. It must have been fully parsed first.");
        }
        $componentAlias = key($this->_queryComponents);
        $mainTableAlias = $this->getSqlTableAlias($componentAlias);
        foreach ($this->_sqlParts['orderby'] as $part) {
            $part = trim($part);
            $e = $this->_tokenizer->bracketExplode($part, ' ');
            $part = trim($e[0]);
            if (strpos($part, '.') === false) {
                continue;
            }
            list($tableAlias, $columnName) = explode('.', $part);
            if ($tableAlias != $mainTableAlias) {
                return true;
            }
        }
        return false;
    }

    /**
     * DQL PARSER
     * parses a DQL query
     * first splits the query in parts and then uses individual
     * parsers for each part
     *
     * @param string $query                 DQL query
     * @param boolean $clear                whether or not to clear the aliases
     * @throws Doctrine_Query_Exception     if some generic parsing error occurs
     * @return Doctrine_Query
     */
    public function parseDqlQuery($query, $clear = true)
    {
        if ($clear) {
            $this->clear();
        }

        $query = trim($query);
        $query = str_replace("\r", "\n", str_replace("\r\n", "\n", $query));
        $query = str_replace("\n", ' ', $query);

        $parts = $this->_tokenizer->tokenizeQuery($query);

        foreach ($parts as $partName => $subParts) {
            $subParts = trim($subParts);
            $partName = strtolower($partName);
            switch ($partName) {
                case 'create':
                    $this->_type = self::CREATE;
                break;
                case 'insert':
                    $this->_type = self::INSERT;
                break;
                case 'delete':
                    $this->_type = self::DELETE;
                break;
                case 'select':
                    $this->_type = self::SELECT;
                    $this->_addDqlQueryPart($partName, $subParts);
                break;
                case 'update':
                    $this->_type = self::UPDATE;
                    $partName = 'from';
                case 'from':
                    $this->_addDqlQueryPart($partName, $subParts);
                break;
                case 'set':
                    $this->_addDqlQueryPart($partName, $subParts, true);
                break;
                case 'group':
                case 'order':
                    $partName .= 'by';
                case 'where':
                case 'having':
                case 'limit':
                case 'offset':
                    $this->_addDqlQueryPart($partName, $subParts);
                break;
            }
        }

        return $this;
    }

    /**
     * @todo Describe & refactor... too long and nested.
     * @param string $path          component alias
     * @param boolean $loadFields
     */
    public function load($path, $loadFields = true)
    {
        if (isset($this->_queryComponents[$path])) {
            return $this->_queryComponents[$path];
        }

        $e = $this->_tokenizer->quoteExplode($path, ' INDEXBY ');

        $mapWith = null;
        if (count($e) > 1) {
            $mapWith = trim($e[1]);

            $path = $e[0];
        }

        // parse custom join conditions
        $e = explode(' ON ', str_ireplace(' on ', ' ON ', $path));

        $joinCondition = '';

        if (count($e) > 1) {
            $joinCondition = substr($path, strlen($e[0]) + 4, strlen($e[1]));
            $path = substr($path, 0, strlen($e[0]));

            $overrideJoin = true;
        } else {
            $e = explode(' WITH ', str_ireplace(' with ', ' WITH ', $path));

            if (count($e) > 1) {
                $joinCondition = substr($path, strlen($e[0]) + 6, strlen($e[1]));
                $path = substr($path, 0, strlen($e[0]));
            }

            $overrideJoin = false;
        }

        $tmp            = explode(' ', $path);
        $componentAlias = $originalAlias = (count($tmp) > 1) ? end($tmp) : null;

        $e = preg_split("/[.:]/", $tmp[0], -1);

        $fullPath = $tmp[0];
        $prevPath = '';
        $fullLength = strlen($fullPath);

        if (isset($this->_queryComponents[$e[0]])) {
            $table = $this->_queryComponents[$e[0]]['table'];
            $componentAlias = $e[0];

            $prevPath = $parent = array_shift($e);
        }

        foreach ($e as $key => $name) {
            // get length of the previous path
            $length = strlen($prevPath);

            // build the current component path
            $prevPath = ($prevPath) ? $prevPath . '.' . $name : $name;

            $delimeter = substr($fullPath, $length, 1);

            // if an alias is not given use the current path as an alias identifier
            if (strlen($prevPath) === $fullLength && isset($originalAlias)) {
                $componentAlias = $originalAlias;
            } else {
                $componentAlias = $prevPath;
            }

            // if the current alias already exists, skip it
            if (isset($this->_queryComponents[$componentAlias])) {
                throw new Doctrine_Query_Exception("Duplicate alias '$componentAlias' in query.");
            }

            if ( ! isset($table)) {
                // process the root of the path

                $table = $this->loadRoot($name, $componentAlias);
            } else {
                $join = ($delimeter == ':') ? 'INNER JOIN ' : 'LEFT JOIN ';

                $relation = $table->getRelation($name);
                $localTable = $table;

                $table = $relation->getTable();
                $this->_queryComponents[$componentAlias] = array('table' => $table,
                                                                 'parent'   => $parent,
                                                                 'relation' => $relation,
                                                                 'map'      => null);
                if ( ! $relation->isOneToOne()) {
                   $this->_needsSubquery = true;
                }

                $localAlias   = $this->getSqlTableAlias($parent, $localTable->getTableName());
                $foreignAlias = $this->getSqlTableAlias($componentAlias, $relation->getTable()->getTableName());

                $foreignSql   = $this->_conn->quoteIdentifier($relation->getTable()->getTableName())
                              . ' '
                              . $this->_conn->quoteIdentifier($foreignAlias);

                $map = $relation->getTable()->inheritanceMap;

                if ( ! $loadFields || ! empty($map) || $joinCondition) {
                    $this->_subqueryAliases[] = $foreignAlias;
                }

                if ($relation instanceof Doctrine_Relation_Association) {
                    $asf = $relation->getAssociationTable();

                    $assocTableName = $asf->getTableName();

                    if ( ! $loadFields || ! empty($map) || $joinCondition) {
                        $this->_subqueryAliases[] = $assocTableName;
                    }

                    $assocPath = $prevPath . '.' . $asf->getComponentName() . ' ' . $componentAlias;

                    $this->_queryComponents[$assocPath] = array(
                        'parent' => $prevPath,
                        'relation' => $relation,
                        'table' => $asf,
                        'ref' => true);

                    $assocAlias = $this->getSqlTableAlias($assocPath, $asf->getTableName());

                    $queryPart = $join
                            . $this->_conn->quoteIdentifier($assocTableName)
                            . ' '
                            . $this->_conn->quoteIdentifier($assocAlias);

                    $queryPart .= ' ON (' . $this->_conn->quoteIdentifier($localAlias
                                . '.'
                                . $localTable->getColumnName($localTable->getIdentifier())) // what about composite keys?
                                . ' = '
                                . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getLocalRefColumnName());

                    if ($relation->isEqual()) {
                        // equal nest relation needs additional condition
                        $queryPart .= ' OR '
                                    . $this->_conn->quoteIdentifier($localAlias
                                    . '.'
                                    . $table->getColumnName($table->getIdentifier()))
                                    . ' = '
                                    . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getForeignRefColumnName());
                    }

                    $queryPart .= ')';

                    $this->_sqlParts['from'][] = $queryPart;

                    $queryPart = $join . $foreignSql;

                    if ( ! $overrideJoin) {
                        $queryPart .= $this->buildAssociativeRelationSql($relation, $assocAlias, $foreignAlias, $localAlias);
                    }
                } else {
                    $queryPart = $this->buildSimpleRelationSql($relation, $foreignAlias, $localAlias, $overrideJoin, $join);
                }

                $queryPart .= $this->buildInheritanceJoinSql($table->getComponentName(), $componentAlias);
                $this->_sqlParts['from'][$componentAlias] = $queryPart;

                if ( ! empty($joinCondition)) {
                    $this->addPendingJoinCondition($componentAlias, $joinCondition);
                }
            }

            if ($loadFields) {
                $restoreState = false;

                // load fields if necessary
                if ($loadFields && empty($this->_dqlParts['select'])) {
                    $this->_pendingFields[$componentAlias] = array('*');
                }
            }

            $parent = $prevPath;
        }

        $table = $this->_queryComponents[$componentAlias]['table'];

        return $this->buildIndexBy($componentAlias, $mapWith);
    }

    protected function buildSimpleRelationSql(Doctrine_Relation $relation, $foreignAlias, $localAlias, $overrideJoin, $join)
    {
        $queryPart = $join . $this->_conn->quoteIdentifier($relation->getTable()->getTableName())
                           . ' '
                           . $this->_conn->quoteIdentifier($foreignAlias);

        if ( ! $overrideJoin) {
            $queryPart .= ' ON '
                       . $this->_conn->quoteIdentifier($localAlias . '.' . $relation->getLocalColumnName())
                       . ' = '
                       . $this->_conn->quoteIdentifier($foreignAlias . '.' . $relation->getForeignColumnName());
        }

        return $queryPart;
    }

    protected function buildIndexBy($componentAlias, $mapWith = null)
    {
        $table = $this->_queryComponents[$componentAlias]['table'];

        $indexBy = null;
        $column = false;

        if (isset($mapWith)) {
            $terms = explode('.', $mapWith);

            if (count($terms) == 1) {
                $indexBy = $terms[0];
            } else if (count($terms) == 2) {
                $column = true;
                $indexBy = $terms[1];
            }
        } else if ($table->getBoundQueryPart('indexBy') !== null) {
            $indexBy = $table->getBoundQueryPart('indexBy');
        }

        if ($indexBy !== null) {
            if ( $column && ! $table->hasColumn($table->getColumnName($indexBy))) {
                throw new Doctrine_Query_Exception("Couldn't use key mapping. Column " . $indexBy . " does not exist.");
            }

            $this->_queryComponents[$componentAlias]['map'] = $indexBy;
        }

        return $this->_queryComponents[$componentAlias];
    }


    protected function buildAssociativeRelationSql(Doctrine_Relation $relation, $assocAlias, $foreignAlias, $localAlias)
    {
        $table = $relation->getTable();

        $queryPart = ' ON ';

        if ($relation->isEqual()) {
            $queryPart .= '(';
        }

        $localIdentifier = $table->getColumnName($table->getIdentifier());

        $queryPart .= $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier)
                    . ' = '
                    . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getForeignRefColumnName());

        if ($relation->isEqual()) {
            $queryPart .= ' OR '
                        . $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier)
                        . ' = '
                        . $this->_conn->quoteIdentifier($assocAlias . '.' . $relation->getLocalRefColumnName())
                        . ') AND '
                        . $this->_conn->quoteIdentifier($foreignAlias . '.' . $localIdentifier)
                        . ' != '
                        . $this->_conn->quoteIdentifier($localAlias . '.' . $localIdentifier);
        }

        return $queryPart;
    }

    /**
     * loadRoot
     *
     * @param string $name
     * @param string $componentAlias
     * @return Doctrine_Table
     * @todo DESCRIBE ME!
     * @todo this method is called only in Doctrine_Query class. Shouldn't be private or protected?
     */
    public function loadRoot($name, $componentAlias)
    {
        // get the connection for the component
        $manager = Doctrine_Manager::getInstance();
        if ( ! $this->_passedConn && $manager->hasConnectionForComponent($name)) {
            $this->_conn = $manager->getConnectionForComponent($name);
        }

        $table = $this->_conn->getTable($name);
        $tableName = $table->getTableName();

        // get the short alias for this table
        $tableAlias = $this->getSqlTableAlias($componentAlias, $tableName);
        // quote table name
        $queryPart = $this->_conn->quoteIdentifier($tableName);

        if ($this->_type === self::SELECT) {
            $queryPart .= ' ' . $this->_conn->quoteIdentifier($tableAlias);
        }

        $this->_tableAliasMap[$tableAlias] = $componentAlias;

        $queryPart .= $this->buildInheritanceJoinSql($name, $componentAlias);

        $this->_sqlParts['from'][] = $queryPart;

        $this->_queryComponents[$componentAlias] = array('table' => $table, 'map' => null);

        return $table;
    }

    /**
     * @todo DESCRIBE ME!
     * @param string $name              component class name
     * @param string $componentAlias    alias of the component in the dql
     * @return string                   query part
     */
    public function buildInheritanceJoinSql($name, $componentAlias)
    {
        // get the connection for the component
        $manager = Doctrine_Manager::getInstance();
        if ( ! $this->_passedConn && $manager->hasConnectionForComponent($name)) {
            $this->_conn = $manager->getConnectionForComponent($name);
        }

        $table = $this->_conn->getTable($name);
        $tableName = $table->getTableName();

        // get the short alias for this table
        $tableAlias = $this->getSqlTableAlias($componentAlias, $tableName);

        $queryPart = '';

        foreach ($table->getOption('joinedParents') as $parent) {
            $parentTable = $this->_conn->getTable($parent);

            $parentAlias = $componentAlias . '.' . $parent;

            // get the short alias for the parent table
            $parentTableAlias = $this->getSqlTableAlias($parentAlias, $parentTable->getTableName());

            $queryPart .= ' LEFT JOIN ' . $this->_conn->quoteIdentifier($parentTable->getTableName())
                        . ' ' . $this->_conn->quoteIdentifier($parentTableAlias) . ' ON ';

            //Doctrine_Core::dump($table->getIdentifier());
            foreach ((array) $table->getIdentifier() as $identifier) {
                $column = $table->getColumnName($identifier);

                $queryPart .= $this->_conn->quoteIdentifier($tableAlias)
                            . '.' . $this->_conn->quoteIdentifier($column)
                            . ' = ' . $this->_conn->quoteIdentifier($parentTableAlias)
                            . '.' . $this->_conn->quoteIdentifier($column);
            }
        }

        return $queryPart;
    }

    /**
     * Get count sql query for this Doctrine_Query instance.
     *
     * This method is used in Doctrine_Query::count() for returning an integer
     * for the number of records which will be returned when executed.
     *
     * @return string $q
     */
    public function getCountSqlQuery()
    {
        // triggers dql parsing/processing
        $this->getSqlQuery(array(), false); // this is ugly

        // initialize temporary variables
        $where   = $this->_sqlParts['where'];
        $having  = $this->_sqlParts['having'];
        $groupby = $this->_sqlParts['groupby'];

        $rootAlias = $this->getRootAlias();
        $tableAlias = $this->getSqlTableAlias($rootAlias);

        // Build the query base
        $q = 'SELECT COUNT(*) AS ' . $this->_conn->quoteIdentifier('num_results') . ' FROM ';

        // Build the from clause
        $from = $this->_buildSqlFromPart(true);

        // Build the where clause
        $where = ( ! empty($where)) ? ' WHERE ' . implode(' ', $where) : '';

        // Build the group by clause
        $groupby = ( ! empty($groupby)) ? ' GROUP BY ' . implode(', ', $groupby) : '';

        // Build the having clause
        $having = ( ! empty($having)) ? ' HAVING ' . implode(' AND ', $having) : '';

        // Building the from clause and finishing query
        if (count($this->_queryComponents) == 1 && empty($having)) {
            $q .= $from . $where . $groupby . $having;
        } else {
            // Subselect fields will contain only the pk of root entity
            $ta = $this->_conn->quoteIdentifier($tableAlias);

            $map = $this->getRootDeclaration();
            $idColumnNames = $map['table']->getIdentifierColumnNames();

            $pkFields = $ta . '.' . implode(', ' . $ta . '.', $this->_conn->quoteMultipleIdentifier($idColumnNames));

            // We need to do some magic in select fields if the query contain anything in having clause
            $selectFields = $pkFields;

            if ( ! empty($having)) {
                // For each field defined in select clause
                foreach ($this->_sqlParts['select'] as $field) {
                    // We only include aggregate expressions to count query
                    // This is needed because HAVING clause will use field aliases
                    if (strpos($field, '(') !== false) {
                        $selectFields .= ', ' . $field;
                    }
                }
                // Add having fields that got stripped out of select
                preg_match_all('/`[a-z0-9_]+`\.`[a-z0-9_]+`/i', $having, $matches, PREG_PATTERN_ORDER);
                if (count($matches[0]) > 0) {
                    $selectFields .= ', ' . implode(', ', array_unique($matches[0]));
                }
            }

            // If we do not have a custom group by, apply the default one
            if (empty($groupby)) {
                $groupby = ' GROUP BY ' . $pkFields;
            }

            $q .= '(SELECT ' . $selectFields . ' FROM ' . $from . $where . $groupby . $having . ') '
                . $this->_conn->quoteIdentifier('dctrn_count_query');
        }

        return $q;
    }

    /**
     * Fetches the count of the query.
     *
     * This method executes the main query without all the
     * selected fields, ORDER BY part, LIMIT part and OFFSET part.
     *
     * Example:
     * Main query:
     *      SELECT u.*, p.phonenumber FROM User u
     *          LEFT JOIN u.Phonenumber p
     *          WHERE p.phonenumber = '123 123' LIMIT 10
     *
     * The modified DQL query:
     *      SELECT COUNT(DISTINCT u.id) FROM User u
     *          LEFT JOIN u.Phonenumber p
     *          WHERE p.phonenumber = '123 123'
     *
     * @param array $params        an array of prepared statement parameters
     * @return integer             the count of this query
     */
    public function count($params = array())
    {
        $q = $this->getCountSqlQuery();
        $params = $this->getCountQueryParams($params);
        $params = $this->_conn->convertBooleans($params);

        if ($this->_resultCache) {
            $conn = $this->getConnection(); 
            $cacheDriver = $this->getResultCacheDriver();
            $hash = $this->getResultCacheHash($params).'_count';
            $cached = ($this->_expireResultCache) ? false : $cacheDriver->fetch($hash);

            if ($cached === false) {
                // cache miss
                $results = $this->getConnection()->fetchAll($q, $params);
                $cacheDriver->save($hash, serialize($results), $this->getResultCacheLifeSpan());
            } else {
                $results = unserialize($cached);
            }
        } else {
            $results = $this->getConnection()->fetchAll($q, $params);
        }

        if (count($results) > 1) {
            $count = count($results);
        } else {
            if (isset($results[0])) {
                $results[0] = array_change_key_case($results[0], CASE_LOWER);
                $count = $results[0]['num_results'];
            } else {
                $count = 0;
            }
        }

        return (int) $count;
    }

    /**
     * Queries the database with DQL (Doctrine Query Language).
     *
     * This methods parses a Dql query and builds the query parts.
     *
     * @param string $query      Dql query
     * @param array $params      prepared statement parameters
     * @param int $hydrationMode Doctrine_Core::HYDRATE_ARRAY or Doctrine_Core::HYDRATE_RECORD
     * @see Doctrine_Core::FETCH_* constants
     * @return mixed
     */
    public function query($query, $params = array(), $hydrationMode = null)
    {
        $this->parseDqlQuery($query);
        return $this->execute($params, $hydrationMode);
    }

    /**
     * Copies a Doctrine_Query object.
     *
     * @return Doctrine_Query  Copy of the Doctrine_Query instance.
     */
    public function copy(Doctrine_Query $query = null)
    {
        if ( ! $query) {
            $query = $this;
        }

        $new = clone $query;

        return $new;
    }

    /**
     * Magic method called after cloning process.
     *
     * @return void
     */
    public function __clone()
    {
        $this->_parsers = array();
        $this->_hydrator = clone $this->_hydrator;

        // Subqueries share some information from the parent so it can intermingle
        // with the dql of the main query. So when a subquery is cloned we need to
        // kill those references or it causes problems
        if ($this->isSubquery()) {
            $this->_killReference('_params');
            $this->_killReference('_tableAliasMap');
            $this->_killReference('_queryComponents');
        }
    }

    /**
     * Kill the reference for the passed class property.
     * This method simply copies the value to a temporary variable and then unsets
     * the reference and re-assigns the old value but not by reference
     *
     * @param string $key
     */
    protected function _killReference($key)
    {
        $tmp = $this->$key;
        unset($this->$key);
        $this->$key = $tmp;
    }

    /**
     * Frees the resources used by the query object. It especially breaks a
     * cyclic reference between the query object and it's parsers. This enables
     * PHP's current GC to reclaim the memory.
     * This method can therefore be used to reduce memory usage when creating
     * a lot of query objects during a request.
     *
     * @return Doctrine_Query   this object
     */
    public function free()
    {
        $this->reset();
        $this->_parsers = array();
        $this->_dqlParts = array();
    }
}

I have loved working with Doctrine and would be happy to contribute back to the code base any patches I resolve for my project in the future.

Best regards

Will Ferrer

Comment by Jonathan H. Wage [ 27/May/10 ]

Hi, in order to apply your changes we need a patch and not the whole file. Thanks, Jon

Comment by will ferrer [ 02/Jun/10 ]

Hi Jonathan

I tried using winmerge to make a patch file but it seems contain both the entire before and after files in it. I am not sure if I am doing something wrong with the software or if this is just what a patch file looks like.

At any rate I have attached the patch file to this issue.

Please let me know if this patch file is correct.

Best Regards

Will Ferrer

Comment by Jonathan H. Wage [ 08/Jun/10 ]

Hi, the patch is not correct. Just go into your svn checkout where the changes are and run the command svn diff. Output that to a file and attach it here. Thanks, Jon

Comment by Jonathan H. Wage [ 08/Jun/10 ]

I was able to generate a patch. It had some errors in our test suite but I fixed them. Since we don't have a test case for it I am not sure if the changes I made affected anything for you. Can you test the patch or provide a test case?

Comment by will ferrer [ 08/Jun/10 ]

Hi Jon

I checked out the svn branch 1.2.2 and noticed that you had my original/broken patch in the code (that one failed some tests for me so I fixed it and tried to upload it in the last patch file I attached to this thread – the patch file that didn't work).

Using the technique you described I made a working patch to put the correct version of my code into the 1.2.2 branch.

I also added a test case for this fix in my patch.

Please see the new patch I have attached to this thread.

Thanks for all your help.

Will Ferrer

Comment by will ferrer [ 08/Jun/10 ]

Here is the correct patch for the bug fix, along with the test case for it.

Comment by will ferrer [ 08/Jun/10 ]

I reopened the issue to call attention to the fixed patch/test case I added to the thread.

Comment by Jonathan H. Wage [ 08/Jun/10 ]

That one works and looks better. Just had a few tabs vs spaces problems that I fixed. Thanks for your work on this. It is much appreciated!!!!!

Comment by will ferrer [ 08/Jun/10 ]

Hi Jon

No problem – I am very glad to contribute back to the project . Doctrine has really been invaluable to me in my development, thanks for building it.

Hope you are well.

Will Ferrer





[DC-594] When using a combination of: a group by field referencing a table in a relation, a join to a different table via a many type relation and a limit clause, doctrine creates a broken query then throws an exception Created: 22/Mar/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Query, Relations
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Blocker
Reporter: will ferrer Assignee: Guilherme Blanco
Resolution: Fixed Votes: 0
Labels: None
Environment:

XP Xamp Current



 Description   

Hi All

I have run into a very problematic Doctrine 1.2.2 bug which puts me in quite a bit of danger of having to rewrite my whole app in Doctrine 2 (hopefully if this is a bug it isn't present in Doctrine 2).

The problem I am running into seems like something that probably would have been found and rectified however so hopefully there is something wrong in my execution.

The problem I am running into is caused when I have a combination of the following 3 things in my query:
1) A group by field referencing a table in a relation
2) A join to a different table via a many type relation
3) A limit clause

This combination causes Doctrine to create a broken SQL query which it then throws an exception about when I try to execute the query or call getSqlQuery() on it.

Using some sample data I put together some very simple examples to illustrate the problem:

$q = Doctrine_Query::create(); 
$q->from('Customer Customer'); 
$q->leftJoin('Customer.Order Order'); 
$q->leftJoin('Customer.Zip Zip'); 
$q->addGroupBy('Zip.city');
$q->addSelect('Zip.city as city');
$q->addSelect('Customer.customer_id'); 
$q->addSelect('Order.order_id');
$q->offset(0);
$q->limit(5);

This creates the following dql:

SELECT Zip.city as city, Customer.customer_id, Order.order_id FROM Customer Customer LEFT JOIN Customer.Order Order LEFT JOIN Customer.Zip Zip GROUP BY Zip.city LIMIT 5 OFFSET 0

However when I attempt to run $q->getSqlQuery() an exception is thrown:
{"type":"exception","tid":3,"exception":{},"message":"SQLSTATE[42S22]: Column not found: 1054 Unknown column 'z2.city' in 'group statement'. Failing Query: \"SELECT DISTINCT c2.customer_id FROM customers c2 GROUP BY z2.city LIMIT 5\"","where":"#0 C:\\htdocs\\php_library\\Doctrine-1.2.1\\lib\\Doctrine
Connection.php(1025): Doctrine_Connection->rethrowException(Object(PDOException), Object(Doctrine_Connection_Mysql), 'SELECT DISTINCT...')\n#1 C:\\htdocs\\php_library\\Doctrine-1.2.1\\lib\\Doctrine
Query.php(1263): Doctrine_Connection->execute('SELECT DISTINCT...', Array)\n#2 C:\\htdocs\\php_library\\Doctrine-1.2.1\\lib\\Doctrine
Query.php(1122): Doctrine_Query->buildSqlQuery(true)\n#3............

As you can see from the SQL in the exception Doctrine is trying to create a query that groups by a field from the Zip table with out first joining to it (SELECT DISTINCT c2.customer_id FROM customers c2 GROUP BY z2.city LIMIT 5).

I know Doctrine works some magic to get limit statements to work with joins and I suspect that something with in that magic may be broken, but hopefully its something I am doing wrong.

Take out any one of the 3 things I mentioned I above and everything works fine – the following all work:

Remove the limit:

$q = Doctrine_Query::create(); 
$q->from('Customer Customer'); 
$q->leftJoin('Customer.Order Order'); 
$q->leftJoin('Customer.Zip Zip'); 
$q->addGroupBy('Zip.city'); 
$q->addSelect('Zip.city as city');
$q->addSelect('Order.order_id');
$q->addSelect('Customer.customer_id'); 

Remove the additional join:

$q = Doctrine_Query::create(); 
$q->from('Customer Customer'); 
$q->leftJoin('Customer.Zip Zip'); 
$q->addGroupBy('Zip.city'); 
$q->addSelect('Zip.city as city');
$q->addSelect('Customer.customer_id');
$q->offset(0);
$q->limit(5);

Remove the group by:

$q = Doctrine_Query::create(); 
$q->from('Customer Customer'); 
$q->leftJoin('Customer.Order Order'); 
$q->leftJoin('Customer.Zip Zip'); 
$q->addSelect('Zip.city as city');
$q->addSelect('Customer.customer_id'); 
$q->addSelect('Order.order_id');
$q->offset(0);
$q->limit(5);

Its also worth noting that the following changes also stop the problem from happening:
Changing the relation to the Order table to be a one relation instead of a many relation.
Changing the group by to a field located in the "from" table (such as: $q->addGroupBy('Customer.customer_id')

Here are the relevant parts of my sample data schema:

detect_relations: false
package: Example
options:
  type: INNODB
  charset: utf8
Order:
  tableName: orders
  columns:
    order_id:
      type: integer(4)
      primary: true
      notnull: true
    customer_id:
      type: integer(4)
    order_date: timestamp
  relations:
    OrderItem:
      type: many
      local: order_id
      foreign: order_id
    Customer:
      type: one
      local: customer_id
      foreign: customer_id
  options:
    type: InnoDB
Customer:
  tableName: customers
  columns:
    customer_id:
      type: integer(4)
      primary: true
      notnull: true
      autoincrement: true
    firstname:
      type: string(45)
    lastname:
      type: string(45)
    streetaddress:
      type: string(45)
    city:
      type: string(45)
    state:
      type: string(45)
    postalcode:
      type: string(45)
  relations:
    Order:
      type: many
      local: customer_id
      foreign: customer_id
    Zip:
      type: one
      local: postalcode
      foreign: postalcode
  options:
    type: InnoDB
Zip:
  connection: default_schema
  tableName: zips
  columns:
    postalcode:
      type: varchar(30)
      primary: true
    latitude: 'float(10,6)'
    longitude: 'float(10,6)'
    city: string(50)
    state: string(50)
    country: string(50)
    type: string(50)
  relations:
    Customer:
      type: many
      local: postalcode
      foreign: postalcode

Thank for any advice or information you can give me on this.

Best regards

Will Ferrer



 Comments   
Comment by will ferrer [ 24/Mar/10 ]

I realized another crucial aspect of the problem. I am using a left join to my Zip table instead of using an inner join. When I change my code to do an inner join it works again:

$q = Doctrine_Query::create(); 
$q->from('Customer Customer'); 
$q->leftJoin('Customer.Order Order'); 
$q->innerJoin('Customer.Zip Zip'); 
$q->addGroupBy('Zip.city');
$q->addSelect('Zip.city as city');
$q->addSelect('Customer.customer_id'); 
$q->addSelect('Order.order_id');
$q->offset(0);
$q->limit(5);

Thanks much in advance.

Will Ferrer

Comment by will ferrer [ 03/Apr/10 ]

I took a look at the doctrine 1.2.2 code to try to track down what was causing this bug and I think I have found and fixed it in my copy of the code base.

The problem is on line 1459 of Doctrine_Query and looks like it was just an oversight. The code was checking if it should preserve left joins while generating the subquery based on whether or not there were any orderBys, wheres, or havings added to the query. I changed the code to also watch for groupBys and it seems to have resolved this issue.

The code was:

 if (empty($this->_sqlParts['orderby']) && empty($this->_sqlParts['where']) && empty($this->_sqlParts['having'])) {

I changed it to:

if (empty($this->_sqlParts['orderby']) && empty($this->_sqlParts['where']) && empty($this->_sqlParts['having']) && empty($this->_sqlParts['groupby'])) {

Please let me know if I am over looking anything. If I am not then this change should probably be added to the next revision of doctrine.

Sincerely

Will Ferrer





[DC-578] Severe documentation omission Created: 16/Mar/10  Updated: 29/Mar/10  Resolved: 29/Mar/10

Status: Resolved
Project: Doctrine 1
Component/s: Documentation
Affects Version/s: 1.2.0
Fix Version/s: None

Type: Improvement Priority: Blocker
Reporter: Danny Kopping Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None


 Description   

I wanted to give you guys a heads-up about a fairly critical missing line of code from the "Getting Started" docs... From what i can tell, the

spl_autoload_register(array('Doctrine_Core', 'modelsAutoload'));

statement is not mentioned anywhere in the docs and for beginners (like myself) who start to experiment with creating tables from YAML schemas, this is a dead-end since the models do not load and hence no tables are generated (without a warning i might add).

After some fierce Googling, i managed to find that critical line of code, popped it in and Bob's my father's brother... For newbies i fear that this might be a dealbreaker.
Thanks



 Comments   
Comment by Jonathan H. Wage [ 29/Mar/10 ]

This is mentioned in the Introduction to Models chapter now.





[DC-483] Indentifiers are not quoted in queries build for Nested Relations (patch included) Created: 08/Feb/10  Updated: 01/Mar/10  Resolved: 01/Mar/10

Status: Resolved
Project: Doctrine 1
Component/s: Native SQL, Query, Relations
Affects Version/s: 1.2.1
Fix Version/s: 1.2.2

Type: Bug Priority: Blocker
Reporter: Elnur Abdurrakhimov Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

Ubuntu, PHP, MySQL


Attachments: File patch.diff    

 Description   

It was OK using reserved words as column names before I started using Nested Relations: queries started to fail because Doctrine_Core::ATTR_QUOTE_IDENTIFIER is ignored by query builder for Nested Relations.

I solved this problem by wrapping identifiers with Doctrine_Formatter::quoteIdentifier() in several places. I'm providing a patch for your to review and commit.

Thanks.






[DC-363] Multiple connections and i18n Created: 16/Dec/09  Updated: 28/Mar/12  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Connection, I18n
Affects Version/s: 1.0.14, 1.2.2, 1.2.3
Fix Version/s: 1.2.3

Type: Bug Priority: Blocker
Reporter: Xav. Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 1
Labels: None
Environment:

MySQL 5.1.37
PHP 5.2.11
symfony 1.2.11 DEV

symfony 1.4.13
PHP 5.3.6
Postgres 8.4.8



 Description   

I used to work with a single database named "doctrine". The query was working properly.

I then decided to use 2 databases so I got my schema like this:

connection: doctrine

Category:
actAs:
I18n:
actAs:
Sluggable:
fields: [name]
Timestampable: ~
fields: [name, description]
columns:
id: ~
name:
type: string(255)
notnull: true
description: string

User:
connection: second
columns:
id: ~
name:
type: string(255)
notnull: true

I did setup my connections in config/databases.yml this way:

all:
doctrine:
// ....
second:
// ....

build-model, build-forms, build-filters and cc got ran. But now, I got an exception saying the "Translation" relation doesn't exist. The Base Models include correctly the bindComponent line:

Doctrine_Manager::getInstance()->bindComponent('Category', 'doctrine');

For now, I managed to kind of fixing it with simply swapping the databases order in my config/databases.yml and it's now working again perfectly.

I forgot to mention that in the CategoryTable when i call $this->getConnection()->getName(), it outputs "second"



 Comments   
Comment by Colin Darie [ 04/Feb/10 ]

I'm experiencing the same issue with 4 connections. The I18n behavior is almost unusable for models whose connection is not the last one defined. I searched for an acceptable solution but I haven't found one (IMHO the setting to the right connection before each query is not acceptable in large projects). I tried to do like in Search behavior, but it didn't work, I doesn't know enough doctrine internals to understand why.

Comment by Jonathan H. Wage [ 01/Mar/10 ]

Can you test this in Doctrine 1.2? I believe it is fixed.

Comment by Georg [ 17/Feb/11 ]

I am using the latest symfony 1.4 which is doctrine 1.2.3 afaik, and this bug still exists.
Try

actAs:
I18n:
fields: [name]
generateFiles: true
generatePath: /project/lib/model/doctrine/i18n

and the resulting model base class is not bound to the correct connection.

Comment by Joe Siponen [ 27/May/11 ]

I've just now battled with the very same problem in Doctrine 1.2 (the version bundled with symfony 1.4) and the problem seems to be caused by the fact that Doctrine_Record_Generator simply isn't written such that it is able to reinitialize generators for unloaded table instances after a connection is closed. This problem also manifests itself after a table has been loaded in a connection and one tries retrieve a table again after Doctrine_Connection->evictTables() has been called. This makes it impossible to to open more than one connection at a time in a request/script when using behaviors that dynamically modify table instances (such as the i18n behavior). The issue states that this has been fixed but I looked at the latest code and the problem still seems to be very much the same.

Doctrine_Record_Generator determines if it needs to run its initialization methods simply by checking if the to-be generated class, as defined by the className option, exists using a class_exists call. This means that the first time this method is called the initialization happens but for every subsequent call no initialization is made. Now, in the i18m behavior, the important initialization happens in its setTableDefinition method in which it removes any of the translated fields from the table instance that is been setup and redefines them as relations on the to-be-created Translation class. It then finishes off by dynamically declaring the new class for the translation record using its generateClassFromTable method.

Thus, the first time everything goes smoothly and the i18n generator's setTableDefinition is called and the table instance is properly initialized. Everything will now work as expected while the current connection is open since the connection instance keeps the i18n modified table instances alive and well for callers.

But, when the current connection is closed the i18n modified table instances it holds are also removed (goes out of scope). Then, when a new connection is opened, this new connection will start without having any table instances. This means that the next time one asks the new connection for a table instance of the same class with the i18n behavior the i18n behaviors will fail to initialize because the generator at this time believes its class has actually been initialized which, in turn, means that the table using the i18n behavior isn't properly initialized. No initialization means that this table will now include the non-existant i18n fields in the select part of its queries (those are in the translation table) causing those queries to fail miserably.

I believe this could be fixed by adding a static attribute to Doctrine_Record_Generator that tracks the spl_object_hash of the underlying dbh instance variable of the doctrine connection of the table parameter. If the hash is the same the next time that the initialize method is called the generator can decide not to reinitialize itself but if it detects that the hash of the current connection is different then that is definitely a clue to the generator that it needs to reinitialize itself (i.e. run all of the initialization methods but generateClassFromTable which should't be called more than once).

Maybe do it like this perhaps:

 
abstract class Doctrine_Record_Generator extends Doctrine_Record_Abstract
{
  public function initialize(Doctrine_Table $table)
  {
    /* ... */ 
  
    $currentConnectionHash = spl_object_hash($table->getConnection()->getDbh());
    
    //Next part is called if this is the first connection made or if this is a new open connection with new table instances
    if ($currentConnectionHash != self::$lastConnectionHash)
    {
      self::$lastConnectionHash = $currentConnectionHash;
      
      $this->buildTable();

      $fk = $this->buildForeignKeys($this->_options['table']);

      $this->_table->setColumns($fk);

      $this->buildRelation();

      $this->setTableDefinition();
      $this->setUp();
      
      if ($this->_options['generateFiles'] === false && class_exists($this->_options['className'])) {
        $this->generateClassFromTable($this->_table); //Don't generate the class more than once ever
      }
      
      $this->buildChildDefinitions();

      $this->_table->initIdentifier();
    }
  }
}
Comment by James Bell [ 23/Aug/11 ]

I'm also experiencing this issue, using the stable version of Symfony 1.4.13. If I define multiple database connections, the i18n Translation relations fail with this call: Doctrine_Relation_Parser->getRelation('Translation', )

'Unknown relation alias Translation'

dev:
mysql1:
class: sfDoctrineDatabase
param:
dsn: 'mysql:host=localhost;dbname=microscooters'
username: microuser_uk
password: sailing

mysql2:
class: sfDoctrineDatabase
param:
dsn: 'mysql:host=localhost;dbname=microscooters_ie'
username: microuser_ie
password: windy

postgresql:
class: sfDoctrineDatabase
param:
dsn: 'pgsql:host=localhost;dbname=mses6'
username: postgres
password: postgres

In this case, the primary connection is the postgresql one, and that is where the i18n behaviour is defined:

Category:
actAs:
Timestampable: ~
Auditable: ~
NestedSet: ~
I18n:
fields: [picture_id, sort_type, name, handle, subheading, breadcrumb, description, enabled, meta_title, meta_keywords, meta_description]
i18nField: culture
length: 5
actAs:
Timestampable: ~
Auditable: ~
...

I tried to implement the suggest above (ie adding a static hash of the database handle to the Doctrine_Record_Generator class file, which does clear out the connections. However, I then have difficulty with Doctrine recognizing CategoryTranslation as a class:

"( ! ) Fatal error: Class 'CategoryTranslation' not found in /sitename/lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/lib/vendor/doctrine/Doctrine/Table.php on line 545"

Is there anything else I can to do test/fix this?

Comment by Joe Siponen [ 23/Aug/11 ]

This is a duplicate of http://www.doctrine-project.org/jira/browse/DC-373. I've also added a failing test case to that issue that should reproduce the issue as described here.

Comment by Andy.L [ 28/Mar/12 ]

still exists in 1.2.4





[DC-203] MsSQL TIMESTAMPS Fail Created: 09/Nov/09  Updated: 23/Nov/09  Resolved: 23/Nov/09

Status: Resolved
Project: Doctrine 1
Component/s: Record
Affects Version/s: 1.2.0-BETA1
Fix Version/s: None

Type: Bug Priority: Blocker
Reporter: Trevor Lanyon Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

Doctrine Version: 1.2.0-BETA1 on Symfony 1.3 - BETA
pdo_dblib / MsSQL
Linux



 Description   

sfDoctrineRecord.class.php incorrectly attempts to parse dates from the MsSQL datetime field.

MsSQL dates return in the following format:

Oct 12 2009 12:16:22:000AM



 Comments   
Comment by Jonathan H. Wage [ 16/Nov/09 ]

I don't understand your issue. Can you provide some more information?

Comment by Trevor Lanyon [ 16/Nov/09 ]

After reviewing this might be a Symfony issue and not a doctrine issue as the problem is with this class. I'll explain this better in case:

lib/plugins/sfDoctrinePlugin/lib/record/sfDoctrineRecord.class.php

It has this method:

public function getDateTimeObject($dateFieldName)
{
$type = $this->getTable()->getTypeOf($dateFieldName);
if ($type == 'date' || $type == 'timestamp')

{ return new DateTime($this->get($dateFieldName)); }

else

{ throw new sfException('Cannot call getDateTimeObject() on a field that is not of type date or timestamp.'); }

}

Microsoft SQL server returns TIMESTAMPS in a ridiculous format like this "Nov 16 2009 12:08:44:000AM" so this method breaks. Easy fix but a bug nonetheless.

Again, this doesn't belong here does it, it's a symfony thing?

Comment by Jonathan H. Wage [ 16/Nov/09 ]

I don't think this is something that can be "fixed". the DateTime object uses strtotime() and is mssql provides a date it doesn't understand, we can't do much about it. I think you need to configure your mssql server to return a date format that php can parse properly.

Comment by Trevor Lanyon [ 17/Nov/09 ]

I don't want to be working with Mssql (who would?). I can't change the database or the configuration of the database, other systems rely on the stuff.

There are some serious problems with trying to use Doctrine with MsSQL. PDO for mssql (pdo_dblib) doesn't let you indicate columns more then 30 characters. With the aliasing of columns scheme some columns in my tables go over that and Doctrine thinks it needs to hydrate all of the objects I return because it sees a null (pragmatic: I didn't trace the code to find that, only trial and error). And now it looks like I'll have to keep the modification I made to the code to work with dates (which means I will without exception run into further problems) .

I suppose I'll have to crawl back to my propel code base. With the no real composite key support and no interest in supporting MsSQL I'll have a hard time explaining the choice to anyone else in my group.

Awesome tool if I'm building something out of the box and I can design the environment for it's needs. Too many problems if you have to wrap it around something existing. Good luck! I look forward to future releases. Thank you for your responses!

Comment by Jonathan H. Wage [ 17/Nov/09 ]

I'm open to any suggestions to fix the issue. Before we can really say mssql works well in Doctrine, we'll really need to thoroughly test it against a mssql server. Currently none of us have one.

Comment by Trevor Lanyon [ 23/Nov/09 ]

In the newest version of Symfony, Doctrine (and php 5.3.x) this is no longer a problem.

There is a new problem now (http://trac.symfony-project.org/ticket/7676) but I thought i was probably more a symfony problem.

I'm not ready to throw the towel in just yet.

In response to your need for a mssql server: I have a clustered environment that I have to work with. If I can do any testing please let me know. I would very much like to contribute to Doctrine's success.

Thank you for your help.





[DC-83] Doctrine_Record::fromArray() calls protected methods with wrong arguments when dealing with relations. Created: 06/Oct/09  Updated: 07/Oct/09  Resolved: 07/Oct/09

Status: Resolved
Project: Doctrine 1
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Blocker
Reporter: Marc Weistroff Assignee: Jonathan H. Wage
Resolution: Cannot Reproduce Votes: 0
Labels: None
Environment:

php 5.3 / symfony 1.3


Attachments: File DC83TestCase.php    

 Description   

Hi there.

This a bug related with Symfony but I think the problem comes from Doctrine so I post it here.
When trying to update an object with this array:

{{
var_dump($values)
array
'id' => string '14' (length=2)
'foo' => null
'bar_relation' =>
array
'id' => string '15' (length=2)
'title' => string '' (length=0)
'bar_other_relation' =>
array
'id' => string '16' (length=2)
'title' => string '' (length=0)
}}

When calling
{{
$object->fromArray($values):
}}

I'l have this error message:
{{
Call to a member function getTable() on a non-object in [...]/Doctrine/Record.php on line 1511.
}}
And the stack trace
{{
sfFormDoctrine->doSave( ) ../sfFormDoctrine.class.php:218
sfFormDoctrine->updateObject( ) ../sfFormDoctrine.class.php:415
sfFormDoctrine->doUpdateObject( ) ../sfFormDoctrine.class.php:249
Doctrine_Record->fromArray( ) ../sfFormDoctrine.class.php:267
MyModel->setBarRelation( ) ../Record.php:1923
sfDoctrineRecord->__call( ) ../sfDoctrineRecord.class.php:0
call_user_func_array ( ) ../sfDoctrineRecord.class.php:204
Doctrine_Record->set( ) ../sfDoctrineRecord.class.php:0
Doctrine_Record->_set( ) ../Record.php:1382
Doctrine_Record->coreSetRelated( ) ../Record.php:1426
}}

Doctrine_Record->coreSetRelated is called with an array instead of a proper Doctrine_Record, and Doctrine try to call ->getTable on this array, causing the error.

Is this a symfony problem or a doctrine one?



 Comments   
Comment by Jonathan H. Wage [ 06/Oct/09 ]

When I try and reproduce this in a test case with your example it doesn't behave that way. Can you try and make a failing test case for us to look at?

Comment by Marc Weistroff [ 06/Oct/09 ]

Thx for the prompt reply Jon. i'll do it asap

Comment by Marc Weistroff [ 07/Oct/09 ]

I can't reproduce the case and I found a workaround by changing the name of my embed forms in symfony.

Comment by Marc Weistroff [ 07/Oct/09 ]

I attach the test case I tried to work on even if it DOES NOT reproduce the problem.





[DC-22] Record instances are shared; local data is overwritten. Created: 15/Sep/09  Updated: 15/Sep/09  Resolved: 15/Sep/09

Status: Resolved
Project: Doctrine 1
Component/s: Record
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Blocker
Reporter: Craig Marvelley Assignee: Roman S. Borschel
Resolution: Fixed Votes: 0
Labels: None
Environment:

LAMP, PHP 5.2.9 & 5.3.0


Attachments: File 2486TestCase.php     File index.php    

 Description   

If I make changes to the properties of an instance of a record but don't persist them (so the record's dirty), then in a separate process hydrate another record instance, the original record instance's properties are altered.

I'm not aware of all the instances in which this happens, but it definitely occurs when finding records through a DQL query or loading related records.

This is particularly troublesome since we're experiencing the loading of one record affecting another instance thousands of lines of code away. It seems like the records are shared somehow. This occurs without any caching of any kind.

I'd really appreciate someone taking a look at this. Example and test attached.



 Comments   
Comment by Roman S. Borschel [ 15/Sep/09 ]

This is unfortunately the default behavior of Doctrine 1.x. Because of backwards compatibility this default behavior can not be easily changed. However, some time ago a new attribute was introduced: Doctrine::ATTR_HYDRATE_OVERWRITE. You can set this to FALSE which does what you want I think. Doctrine 2 already behaves this way by default.

Comment by Craig Marvelley [ 15/Sep/09 ]

Ah, fab. Sounds like what I need! Missed it in the docs, sorry. Is there a list somewhere of all the settings and what they do? That would be handy if not, since they seem to be dotted around the manual at the moment so they're easy to miss. Are there any other side effects I should be expecting if I turn that setting on? Thanks!

Comment by Roman S. Borschel [ 15/Sep/09 ]

I dont think any such list exists. As you said all of the attributes are spread throughout the documentation. You can create an issue as an enhancement request for the documentation for that.

Setting that attribute to false should have no other side effects. It really only affects hydration and controls whether the values from the database or the local values should be considered "newer" and therefore kept around.

I will close this issue. If the attribute does not solve your issue, please let us know.





[DC-24] "AS" clause in DQL crashes the further update of the row in one-many relationship Created: 15/Sep/09  Updated: 19/Sep/09  Resolved: 17/Sep/09

Status: Resolved
Project: Doctrine 1
Component/s: Query
Affects Version/s: 1.1.4
Fix Version/s: 1.2.0-ALPHA1

Type: Bug Priority: Blocker
Reporter: Jacek Jędrzejewski Assignee: Jonathan H. Wage
Resolution: Won't Fix Votes: 1
Labels: None

Attachments: File 9999bTestCase.php     File 9999cTestCase.php     File 9999TestCase.php     File select_alias.diff    

 Description   

moving from trac, issue #2410 by zyxist

This ticket is related to the MySQL database and InnoDB engine with exporting all the information about the models (including foreign keys).

I have two tables connected with one-to-many relationship. First, I select a row from a "master" table with attached a field from the related "servant" row:

$master = Doctrine_Query::create()
	->select('m.*, s.bar AS joe')
	->from('Ticket_9999_Master m')
	->innerJoin('m.Ticket_9999_Servant s')
	->where('m.id = 1')
	->fetchOne();

Next, I modify one of the columns in the master row and save everything:

$master->foo = 5;
$master->save();

Expected result: the master row is updated.

Actual result: MySQL error:

Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`doctrine`.`ticket_9999_master`, CONSTRAINT `ticket_9999_master_servant_id_ticket_9999_servant_id` FOREIGN KEY (`servant_id`) REFERENCES `ticket_9999_servant` (`id`))

If we remove "AS joe" part from the query, it gives the correct result. I noticed that with this alias, Doctrine forgets to retrieve the primary key of the servant row and this produces an invalid UPDATE query. There are two possible ways to retrieve this:

1. Use the data from the "Master" row, as we fetch everything, including the ID of the servant row. Doctrine knows about the relationship and potentially can use it.

2. Simply select the servant id automatically with the "s.bar" column.

Tested on software:
PHP 5.2.10, PHP 5.3.0
MySQL 5.1.36
Doctrine 1.1-DEV (rev. 6217)



 Comments   
Comment by Jacek Jędrzejewski [ 15/Sep/09 ]

Two words from me:

Well, the problem is not the saving process but the thing that selecting a relation only with aliased field does not retrieve the ID. I mean that this query:

SELECT m.*, s.bar FROM Master m, m.Servant S

is also selecting s.id field, because Doctrine always needs it, right? Adding an alias "s.bar AS something" makes the query not select "s.id", so in save() Doctrine thinks Servant is a new record. And that's why mysql throws "Integrity constraint violation".

Comment by Ionut E [ 17/Sep/09 ]

Patch for the 1.1 branch. Fixes the alias problem but messes a lot of other tests which are query related.

My fix forces all fields of the table with the column alias to be inserted in sql as well.

Comment by Jonathan H. Wage [ 17/Sep/09 ]

You just need to select the id.

$master = Doctrine_Query::create()
	->select('m.*, s.id, s.bar AS joe')
	->from('Ticket_9999_Master m')
	->innerJoin('m.Ticket_9999_Servant s')
	->where('m.id = 1')
	->fetchOne();

If you have a valid suggestion for how to fix this or make it "smarter" we're open to suggestions.

Comment by Jacek Jędrzejewski [ 17/Sep/09 ]

lol, I know that selecting ID solves this issue. But it is illogical, because a query on field without alias select ID automatically while aliased - no.

Comment by Jonathan H. Wage [ 17/Sep/09 ]

It doesn't automatically select the id unless you also select some real property. In your case you selected and aliased so the object has no real properties loaded.

Comment by Roman S. Borschel [ 17/Sep/09 ]

It actually is logical but I absolutely understand why it looks illogical to you.

Doctrine 1.x auto-adds the primary key only if it is needed. It is needed only if the hydration mode is record/array and any non-scalar value is selected. If neither the hydration mode requires the primary key, nor the selected result expressions contains object fields (not object fields selected as scalar values, i.e. what AS .. does) then it is not added.

"select x.foo as something" only selects a scalar value. The fact that makes this so confusing is that scalar values are put into Record objects in Doctrine 1.x, so to you it looks like a full object was hydrated when in fact it was just a scalar value.

in Doctrine 1.x this is unfortunate but expected behavior. In Doctrine 2 this confusion will not arise as there is a clear separation and scalar values are never put into objects.

Comment by Ionut E [ 18/Sep/09 ]

Jacek, could you please show me an result of the select from the test? On my computer foreign key gets deleted and I have a really hard time beliving this is logical and expected behaviour.

Comment by Jacek Jędrzejewski [ 18/Sep/09 ]

Roman S. Borschel - so if it is not a "real" record but only a "plain" record with a scalar value, why doctrine tries to use it for save? I'm not sure about the internals of this process but something must be wrong there

lonut: this is from toArray(true)

Array
(
    [id] => 1
    [foo] => 6
    [servant_id] =>
    [Ticket_9999_Servant] => Array
        (
            [id] =>
            [bar] =>
            [joe] => 6
        )

    [joe] => 6
)

OKOK, so now try new testcase (attachment 9999b):
I have checked it and this issue is not only related to MySQL and InnoDB. This testcase fails with this error:
Doctrine_Ticket_9999b_TestCase : method testTest failed on line 72 SQLSTATE[23000]: Integrity constraint violation: 19 ticket_9999b__master.servant_id may not be NULL

So now let's try this without notnull constraint on "servant_id" (testcase 9999c):
It works ok, no error on saving. But now let's retrieve that record from DB:

$master2 = Doctrine_Query::create()->select('m.*')->from('Ticket_9999c_Master m')->where('m.id = 1')->fetchOne(array(), Doctrine::HYDRATE_ARRAY);

"servant_id" is empty! Please don't tell me it is a correct behavior!

Comment by Ionut E [ 18/Sep/09 ]

Caching seems to be badly implemented. See DC-41

Comment by Roman S. Borschel [ 18/Sep/09 ]

Jacek,

because it is broken by design. Whoever made the decision to put scalar values into record objects (I think it was Konsta) did not think that through. Doctrine itself does not make a distinction internally between "real" records and "records with only scalars". Hence why it pukes on save(). I just dont see a good way to fix this without significant work and breaking existing code. If you have an idea I am all ears. If we start to add the primary key even if only scalar values are selected of a class we start to make all kinds of queries impossible that compute aggregate values.

Ionut,

this has nothing to do with DC-41, as far as I can see.

Comment by Roman S. Borschel [ 18/Sep/09 ]

Of course, if anyone has a decent idea on how to fix this inconsistency, ideally without breaking backwards-compatibility, that would be great.

Comment by Jonathan H. Wage [ 18/Sep/09 ]

In 1.2 I fixed this by removing the hydration of aggregate values in to the relationship record. This was something that was built by design in 1.0, we realized it was not good so in 1.1 I made it so the aggregate values ALSO went in to the root component, and I deprecated the use of the value from the relationship. So now, in 1.2 we can remove it. I committed your test case and it passes now with this changeset.

Thanks, Jon

Comment by Jacek Jędrzejewski [ 19/Sep/09 ]

Thanks Jon! Hell, I want doctrine 1.2 and symfony 1.3!

BTW. it can be now marked as "fixed" xd





[DC-16] postHydrate listeners should be called after relationships are loaded. Created: 15/Sep/09  Updated: 14/Apr/10  Resolved: 17/Sep/09

Status: Resolved
Project: Doctrine 1
Component/s: None
Affects Version/s: 1.2.0-ALPHA1, 1.2.2
Fix Version/s: None

Type: Improvement Priority: Blocker
Reporter: Roman S. Borschel Assignee: Jonathan H. Wage
Resolution: Invalid Votes: 0
Labels: None

Attachments: File Doctrine-real-postHydrate(2).diff     File Doctrine-real-postHydrate.diff    

 Description   

Doctrine 1.1.3 calls post hydration listeners before relationships are loaded.

This O patch caches the information needed to call the listeners and calls the listeners in reverse order after all the data has been loaded. The net effect is that postHydrate listeners are called for all an object's children before being called for a parent object.

Memory cost should be minor (O @ two pointers per record), and the (additional) processing cost should be flat and almost zero (n function calls to postHydrate have simply been moved outside the loop to a second loop). There is an additional call to $table->getComponentName() in the second loop that could be eliminated by storing $componentName with the other event initializers in the new stack).



 Comments   
Comment by Jonathan H. Wage [ 17/Sep/09 ]

This patch no longer applies as the hydration code is re factored a lot in 1.2

Comment by Exception e [ 14/Apr/10 ]

It seems that even after the rewrite postHydrate() doesn't work correctly.

class Member extends BaseMember 
       private $roles = array();

	function postHydrate($event)	{
		if ($this->relatedExists('Artist'))	{
			array_push($this->roles, 'artist');
		}
		
	}
}

throws exception «Unknown record property / related component "member_id" on "Member"», even if I have joined the related object Artist. member_id is the PK of Member.

Trying isset in

function postHydrate($event) {
if (isset($this->Artist)))

{ array_push($this->roles, 'artist'); }

}

will consistently return false.

This is a blocker since there is no workaround. postHydrate is the last event on record loading afaik.





[DC-933] Results from Doctrine_Query::execute inconsistent with results from Doctrine_Query::getSqlQuery() Created: 19/Nov/10  Updated: 15/Apr/14  Resolved: 28/Feb/11

Status: Resolved
Project: Doctrine 1
Component/s: Query
Affects Version/s: 1.2.3
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: Roger Webb Assignee: Guilherme Blanco
Resolution: Invalid Votes: 0
Labels: None
Environment:

Redhat Linux, Apache, PHP 5,



 Description   

The DQL Query:

$query = Doctrine_Query::create()
->select("
b.borrowers_date as borrowers_date,
b.borrower_id as borrower_id,
borrower_contact.first_name as borrower_first_name,
borrower_contact.last_name as borrower_last_name,
lo_contact.first_name as lo_first_name,
lo_contact.last_name as lo_last_name,
realtor_contact.first_name as realtor_first_name,
realtor_contact.last_name as realtor_last_name,
lo_company_contact.first_name as lender,
b.current_status as current_status
")
->from("Borrowers b")
>innerJoin("b.UserBorrowerAssigned ubass WITH ubass.user_id = " . $this>user_id)
->innerJoin("b.ContactInfo borrower_contact")
->innerJoin("b.LoBorrowerAssigned lobass")
->innerJoin("lobass.LoanOfficers lo")
->innerJoin("lo.ContactInfo lo_contact")
->innerJoin("lo.Companies lo_company")
->innerJoin("lo_company.ContactInfo lo_company_contact")
->leftJoin("b.RealtorBorrowerAssigned realbass")
->leftJoin("realbass.Realtors realtor")
->leftJoin("realtor.ContactInfo realtor_contact");
...
$query->where("b.current_status != 'finialized'")
->andWhere("b.current_status != 'ignored'")
->andWhere("b.current_status != 'dead'");

$query->execute Returns only 1 row

--------------------------------------------------------------------------------

$query->getSqlQuery() returns:

SELECT b.borrowers_date AS b_0, b.borrower_id AS b1, c.first_name AS c2, c.last_name AS c3, c2.first_name AS c24, c2.last_name AS c25, c5.first_name AS c56, c5.last_name AS c57, c4.first_name AS c48, b.current_status AS b_9 FROM borrowers b INNER JOIN user_borrower_assigned u ON b.borrower_id = u.borrower_id AND (u.user_id = 129) INNER JOIN contact_info c ON b.contact_info_id = c.contact_info_id INNER JOIN lo_borrower_assigned l ON b.borrower_id = l.borrower_id INNER JOIN loan_officers l2 ON l.loan_officer_id = l2.loan_officer_id INNER JOIN contact_info c2 ON l2.contact_info_id = c2.contact_info_id INNER JOIN companies c3 ON l2.company_id = c3.company_id INNER JOIN contact_info c4 ON c3.contact_info_id = c4.contact_info_id INNER JOIN realtor_borrower_assigned r ON b.borrower_id = r.borrower_id INNER JOIN realtors r2 ON r.realtor_id = r2.realtor_id INNER JOIN contact_info c5 ON r2.contact_info_id = c5.contact_info_id WHERE (b.current_status != 'finialized' AND b.current_status != 'ignored' AND b.current_status != 'dead')

Running this query in PhpMyAdmin returns 1,095 rows.

-----------------------------------------------------------------------------------

Results Inconsistent



 Comments   
Comment by Roger Webb [ 19/Nov/10 ]

I have stripped the query down to this:

->select("
b.borrowers_date as borrowers_date,
b.borrower_id as borrower_id,
borrower_contact.first_name as borrower_first_name,
borrower_contact.last_name as borrower_last_name,
b.current_status as current_status
")
->from("Borrowers b")
->innerJoin("b.ContactInfo borrower_contact")
->innerJoin("b.UserBorrowerAssigned ubass");

$query->where("b.current_status != 'finialized'")
->andWhere("b.current_status != 'ignored'")
->andWhere("b.current_status != 'dead'")
>andWhere("ubass.user_id = ?", $this>user_id);

Results are still consistent with but report above.

Classes:

class Borrowers extends BaseBorrowers
{

function setUp()

{ parent::setUp(); $this->hasOne('ContactInfo', array('local' => 'contact_info_id', 'foreign' => 'contact_info_id')); $this->hasOne('LoBorrowerAssigned', array('local' => 'borrower_id', 'foreign' => 'borrower_id')); $this->hasOne('RealtorBorrowerAssigned', array('local' => 'borrower_id', 'foreign' => 'borrower_id')); $this->hasOne('UserBorrowerAssigned', array('local' => 'borrower_id', 'foreign' => 'borrower_id')); $this->unshiftFilter(new Doctrine_Record_Filter_Compound(array('ContactInfo'))); }

}

class UserBorrowerAssigned extends BaseUserBorrowerAssigned {

function setUp()

{ parent::setUp(); $this->hasOne("Borrowers", array("local" => "borrower_id", "foreign" => "borrower_id")); $this->hasOne("Users", array("local" => "user_id", "foreign" => "user_id")); }

}

class Users extends BaseUsers {

function setUp()

{ parent::setUp(); $this->hasOne("ContactInfo", array("local" => "contact_info_id", "foreign" => "contact_info_id")); $this->unshiftFilter(new Doctrine_Record_Filter_Compound(array('ContactInfo'))); }

}

The users class also contains the function that issues the query in question.

Comment by Roger Webb [ 28/Feb/11 ]

Non issue. Wasn't familiar with use of Doctrine.

Comment by Jacob Spizziri [ 15/Apr/14 ]

I know this is an old issue, but I'm experiencing a similar problem. What did you do to fix this?

Here is my QueryBuilder query:

$qb = $query = $this->repoLibrary->createQueryBuilder('l');

$query = $qb
->select('l')
->innerJoin('l.productVariant', 'v')
->innerJoin('v.product', 'p')
->innerJoin('p.taxons', 't', 'WITH', 't.id IN (:array)')
->where('l.user = :user')
->groupBy('l.id HAVING count(DISTINCT t.id) >= :count')
->setParameter('user', $user)
->setParameter('array', $s)
->setParameter('count', count($taxons))
->getQuery();

$query->getSql() executes perfectly in MySQL but
$query->getResult() doesn't return what I'm looking for.





[DC-895] [I18n] Defining languages with locality (eg. en_GB) breaks functionality with SQL Integrity error - fix included Created: 20/Oct/10  Updated: 20/Oct/10  Resolved: 20/Oct/10

Status: Resolved
Project: Doctrine 1
Component/s: I18n
Affects Version/s: 1.2.0, 1.2.1, 1.2.2, 1.2.3
Fix Version/s: 1.2.0, 1.2.1, 1.2.2, 1.2.3

Type: Bug Priority: Critical
Reporter: Erik Van Kelst Assignee: Jonathan H. Wage
Resolution: Invalid Votes: 0
Labels: None
Environment:

all



 Description   

When defining languages as language_COUNTRY codes (supported by symfony by default), the functionality to work with I18n records breaks, resulting in "SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry" errors.

The reason is very simple: Doctrine's I18n language column is defined as a CHAR(2), thus shortening eg. "en_GB" value to "en", thus causing the above SQL error when a "en" translation for a record already exists.

The solution is even simpler: change the column's length to 7 in the Doctrine_I18n class's options: I've tested this and all runs great: the correct SQL is being generated, the models behave correct, ...



 Comments   
Comment by Erik Van Kelst [ 20/Oct/10 ]

Length of the i18n column is configurable...





[DC-832] PostgreSQL - lastInsertId fails because sequence name on table with column alias on primary key does not work [+patch] Created: 18/Aug/10  Updated: 24/Aug/10  Resolved: 24/Aug/10

Status: Resolved
Project: Doctrine 1
Component/s: Query
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Critical
Reporter: Enrico Stahn Assignee: Guilherme Blanco
Resolution: Fixed Votes: 0
Labels: None


 Description   

PostgreSQL - lastInsertId fails because sequence name on table with column alias on primary key does not work.

DDL created sequence name: <table>_<column name>
Last Insert ID sequence name: <table>_<identifier> (identifer is the field name which can be different to the column name)



 Comments   
Comment by Enrico Stahn [ 18/Aug/10 ]

Patch:
http://github.com/estahn/doctrine1/tree/DC-832
http://github.com/estahn/doctrine1/commit/5921da9fe159844e354fd280dca8cc156a8680a7

Comment by Jonathan H. Wage [ 24/Aug/10 ]

Thanks, fixed by http://trac.doctrine-project.org/changeset/7684





[DC-800] PostgreSQL does not have LOCATE expressions Created: 28/Jul/10  Updated: 24/Aug/10  Resolved: 24/Aug/10

Status: Resolved
Project: Doctrine 1
Component/s: Native SQL, Query
Affects Version/s: None
Fix Version/s: 1.2.3

Type: Bug Priority: Critical
Reporter: Ilya Sabelnikov Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

PHP: v5.2.13 (cli) (built: May 6 2010 01:51:58) Zend Engine v2.2.0, Xdebug v2.0.5

OS: FreeBSD x 8.0-RELEASE-p2 FreeBSD 8.0-RELEASE-p2 #0: Thu May 6 03:37:19 EEST 2010 x@y.z:/usr/obj/usr/src/sys/CUSTOM_8_0 amd64

Database: postgres (PostgreSQL) 8.4.3

Symfony: 1.4.7-DEV (/web/vendor/symfony/1.4-svn/lib)

Web-server: nginx/0.7.65



 Description   

Introduction

As it's described in Doctrine documentation: http://www.doctrine-project.org/projects/orm/1.2/docs/manual/dql-doctrine-query-language/en#functional-expressions. In case I have correctly understood this documentation, I can use registered expressions (CONCAT,TRIM,LOCATE etc.) with supported database drivers.

Issue

The problem is with PostgreSQL, - it does not have the string function "LOCATE" since v7.4 (i have no info about previous version)

Here is my example:

  $q = PortalTable::getInstance()
      ->createQuery()
      ->addSelect(
        '(1 <= LOCATE(host, ?) as is_host_matched)',
        array('.google.com')
  );

  $q->execute();

And this code dies with an error:

Doctrine_Connection_Pgsql_Exception: SQLSTATE[42601]: Syntax error: 7 ERROR: syntax error at or near "as" LINE 1: SELECT (1 <= LOCATE("p"."host", $1) as is_host_matched) AS "... ^. Failing Query: "SELECT (1 <= LOCATE("p"."host", ?) as is_host_matched) AS "p__0" FROM "portal" "p"" in /web/vendor/symfony/1.4-svn/lib/plugins/sfDoctrinePlugin/lib/vendor/doctrine/Doctrine/Connection.php on line 1082

In PgSQL you can use POSITION for this needs (I want to mention, MySQL has this function too as an alias for LOCATE)

btw, POSITION is SQL-92 standard http://owen.sj.ca.us/~rk/howto/sql92.html - maybe it is better to rename LOCATE with POSITION?

Patch

Index: Doctrine/Expression/Pgsql.php
===================================================================
--- Doctrine/Expression/Pgsql.php	(revision 7678)
+++ Doctrine/Expression/Pgsql.php	(working copy)
@@ -230,4 +230,31 @@
     	$translate = 'TRANSLATE(' . $string . ', ' . $from . ', ' . $to . ')';
     	return $translate;
     }
-}
\ No newline at end of file
+
+    /**
+     * transform locate to position
+     *
+     * @param string $substr string to find
+     * @param string $str to find where
+     * @return string
+     */
+    public function locate($substr, $str)
+    {
+        return $this->position($substr, $str);
+    }
+
+    /**
+     * position
+     *
+     * @param string $substr string to find
+     * @param string $str to find where
+     * @return string
+     */
+    public function position($substr, $str)
+    {
+        $substr = $this->getIdentifier($substr);
+        $str = $this->getIdentifier($str);
+        
+        return sprintf('POSITION(%s IN %s)', $substr, $str);
+    }
+}

Solution without patch:

  # will work with PgSQL and MySQL (tested)
  $exp = new Doctrine_Expression('POSITION(host IN ?)');

  $q = PortalTable::getInstance()
      ->createQuery()
      ->addSelect(
        "(1 <= {$exp} as is_host_matched)",
        array('.google.com')
  );

  $q->execute();


 Comments   
Comment by Jonathan H. Wage [ 24/Aug/10 ]

Fixed in http://trac.doctrine-project.org/changeset/7685

Thanks, Jon





[DC-720] Add primary key for count / groupby query Created: 08/Jun/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: None
Affects Version/s: 1.2.2
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: Brice Maron Assignee: Jonathan H. Wage
Resolution: Invalid Votes: 0
Labels: None
Environment:

linux symfony 1.4.5



 Description   

with table

Maintenance:
columns:
id:

Unknown macro: { type}

record_id:

Unknown macro: { type}

,
referenced_relation:

Unknown macro: { type}

people_ref:

Unknown macro: { type}

,
category:

Unknown macro: { type}

i'm trying to count number of maintenances by record_id

and doctrine construct my dql correctly :

SELECT COUNT(m.id) AS cnt, m.record_id FROM Maintenance m WHERE m.record_id IN GROUP BY m.record_id

But after this, it translate to a wrong sql query :

SELECT c.id AS c_id, c.record_id AS crecord_id, COUNT(c.id) AS c_0 FROM collection_maintenance c WHERE (c.record_id IN ) GROUP BY c.record_id

And postgresql says that c.id must be in the group by... but i don't want it!

Please help me



 Comments   
Comment by Jonathan H. Wage [ 08/Jun/10 ]

Hi, if you execute object record hydration it will always include the identifier. You must bypass the hydration.

For future reference, the Jira system is for reporting bugs. You can ask questions using the mailing lists here: http://www.doctrine-project.org/community





[DC-693] Last_insert_id is not save in properties "id" after save() (using sqlite) Created: 20/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Connection
Affects Version/s: 1.2.2
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: Valery Sizov Assignee: Jonathan H. Wage
Resolution: Can't Fix Votes: 1
Labels: None
Environment:

php- 5.2.10, mysql 5.1,sqlite 3.0



 Description   

using mysql
--------------------
$user = new User;
$user->name = 'Job';
$user->save();

echo $user->id; //1 (for example);

it`s ok!
-------------------------------------------------
using sqlite
-//-
echo $user->id; //0 (always and any where );



 Comments   
Comment by Dennis Gearon [ 28/May/10 ]

I have same problem on a postgresql table, 1 out of 10 in the schema. The table has 6 primary foreign keys and a single autoincrementing primary integer key, 'id', column. 'id' is unavailable after a save(). I'm getting around this with a search for the inserted record, but it's a slow PITA.

Comment by Dennis Gearon [ 28/May/10 ]

sorry, ubuntu 9.10, postgres-8.4, symfoy 1.4.4

Comment by Jonathan H. Wage [ 08/Jun/10 ]

I think this is an unfortunate problem that cannot be fixed. You cannot have a auto increment primary key with some other composite primary keys.





[DC-672] Adding DISTINCT when not needed hurts performance badly Created: 10/May/10  Updated: 10/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Query
Affects Version/s: 1.2.2
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: Amir W Assignee: Guilherme Blanco
Resolution: Can't Fix Votes: 0
Labels: None
Environment:

LAMP - other details seem irrelevant



 Description   

Doctrine seems to be adding DISTINCT to SQL queries without any good reason (to the table's primary key!) and thus SERIOUSLY HURTS performance. It even seems like Doctrine adds a whole new DISTINCT query without a good reason. I believe the extra query is the result of the different implementation of LIMIT in Doctrine vs. SQL. However, when a unique index is involved, this extra query seems redundant.

In the following example, execution time went from ~5sec down to ~1sec when I simply removed the DISTINCT added by Doctrine.

Why is the extra query created anyway?
Is there any way to prevent Doctrine to add the DISTINCT part when there's a unique index on the column?

Here's the DQL:

SELECT i.id, ip.item_id, pic.id, pic.width, pic.height, i.producer_id, i.series_id, i.issue_date, t_i.name, FROM ZpcPhonecard i INNER JOIN i.Picture pic WITH pic.width > 125 AND pic.width >= pic.height LEFT JOIN i.Translation t_i WITH t_i.lang = 0 INDEXBY t_i.lang WHERE i.front_picture_id > 0 AND i.state = ? AND i.zpc_system_id = 4 ORDER BY i.id DESC LIMIT 20

And the resulting SQLs:

SELECT DISTINCT z3.id FROM zpc_phonecard z3 INNER JOIN picture p2 ON z3.front_picture_id = p2.id AND ((p2.width > 125 AND p2.width >= p2.height)) LEFT JOIN zpc_phonecard_translation z4 ON z3.id = z4.id AND (z4.lang = 0) WHERE (z3.front_picture_id > 0 AND z3.state = 'Active') AND z3.zpc_system_id = 4 ORDER BY z3.id DESC LIMIT 20;

SELECT z.id AS z_id, z.producer_id AS zproducer_id, z.series_id AS zseries_id, z.issue_date AS zissue_date, p.id AS pid, p.width AS pwidth, p.height AS pheight, z2.id AS z2id, z2.lang AS z2lang, z2.name AS z2_name FROM zpc_phonecard z INNER JOIN picture p ON z.front_picture_id = p.id AND ((p.width > 125 AND p.width >= p.height)) LEFT JOIN zpc_phonecard_translation z2 ON z.id = z2.id AND (z2.lang = 0) WHERE z.id IN ('231871', '231870', '231869', '231868', '231865', '231864', '231863', '231862', '231861', '231860', '231859', '231858', '231857', '231856', '231855', '231853', '231852', '231851', '231850', '231849') AND ((z.front_picture_id > 0 AND z.state = 'Active') AND z.zpc_system_id = 4) ORDER BY z.id DESC

Here's my own translation from DQL to A MUCH FASTER SINGLE SQL statement:
SELECT i.id, pic.id, pic.width, pic.height, i.producer_id, i.series_id, i.issue_date, t_i.name FROM Zpc_Phonecard i INNER JOIN Picture pic ON i.front_picture_id=pic.id AND pic.width > 125 AND pic.width >= pic.height LEFT JOIN Zpc_Phonecard_translation t_i ON i.id = t_i.id AND t_i.lang = 0 WHERE i.front_picture_id > 0 AND i.state = 'Active' AND i.zpc_system_id = 4 ORDER BY i.id DESC LIMIT 20



 Comments   
Comment by Amir W [ 10/May/10 ]

Here's a workaround to avoid Doctrine's handling of LIMIT queries. Is this there recommended way or is there another?

Before the $q->execute() part simply add these lines:

$q->buildSqlQuery(false);
$map = $q->getRootDeclaration();
$table = $map['table'];
$table->setAttribute(Doctrine_Core::ATTR_QUERY_LIMIT, Doctrine_Core::LIMIT_ROWS);

Now the DQL is automatically translated to the following SQL:

SELECT z.id AS z_id, z.producer_id AS zproducer_id, z.series_id AS zseries_id, z.issue_date AS zissue_date, p.id AS pid, p.width AS pwidth, p.height AS pheight, z2.id AS z2id, z2.lang AS z2lang, z2.name AS z2_name FROM zpc_phonecard z INNER JOIN picture p ON z.front_picture_id = p.id AND ((p.width > 125 AND p.width >= p.height)) LEFT JOIN zpc_phonecard_translation z2 ON z.id = z2.id AND (z2.lang = 0) WHERE ((z.front_picture_id > 0 AND z.state = 'Active') AND z.zpc_system_id = 4) ORDER BY z.id DESC LIMIT 20

Comment by Jonathan H. Wage [ 08/Jun/10 ]

This is the dreaded limit subquery algorithm at work. I don't think we can do anything to patch/fix this in Doctrine 1.

Comment by Amir W [ 10/Jun/10 ]

As there is a workaround (as I've suggested above) you can obviously add a flag (via a function or any other way) allowing the user NOT to use the "dreaded limit subquery"...





[DC-671] Record could be saved, even tough a NotNull column is null Created: 09/May/10  Updated: 10/May/10  Resolved: 09/May/10

Status: Resolved
Project: Doctrine 1
Component/s: Validators
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Critical
Reporter: Fabian Spillner Assignee: Jonathan H. Wage
Resolution: Invalid Votes: 0
Labels: None
Environment:

OS X 10.6.3 - php 5.3.1


Attachments: File DC671TestCase.php    

 Description   

Strange behavior: The record could be saved without getting validation exception even tough a NotNull column is empty.

If you use Sqlite as mock driver you get validation exception. Test case is added into ticket!



 Comments   
Comment by Fabian Spillner [ 09/May/10 ]

there the test case (About filename: I wonder why there is same file (empty test) on the doctrine ticket tests - please rename this test case if I call it wrong)

Comment by Jonathan H. Wage [ 09/May/10 ]

Do you have validation enabled? It is off by default.

Comment by Fabian Spillner [ 09/May/10 ]

Ah! It works now! Thank you! It's new default behavior since 1.2?

Comment by Fabian Spillner [ 09/May/10 ]

I forget to enable the attribute Doctrine_Core::ATTR_VALIDATE.

Comment by Jonathan H. Wage [ 09/May/10 ]

No it has always been off by default.

Comment by Fabian Spillner [ 10/May/10 ]

The reason of my confusion:

On Symfony 1.2 the validation is enabled by default:

http://trac.symfony-project.org/browser/branches/1.2/lib/plugins/sfDoctrinePlugin/config/config.php

And nothing is talked about it:
http://www.symfony-project.org/tutorial/1_4/en/whats-new#chapter_a2fae23c9403b0e9ec99806fccf6b53e_doctrine_integration





[DC-645] Query with a leftJoin() + where(NOT IN) + limit() generate wrong SQL alias in the NOT IN part Created: 23/Apr/10  Updated: 17/Apr/14  Resolved: 02/Sep/10

Status: Resolved
Project: Doctrine 1
Component/s: Query
Affects Version/s: 1.2.2
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: David Jeanmonod Assignee: Guilherme Blanco
Resolution: Fixed Votes: 1
Labels: None
Environment:

PHP 5.3.1 (cli) (built: Feb 11 2010 02:32:22)
mysql Ver 14.14 Distrib 5.1.41, for apple-darwin9.5.0 (i386) using readline 5.1
Doctrine version 1.2.2 from SVN: http://doctrine.mirror.svn.symfony-project.com/tags/1.2.2/lib/Doctrine.php


Attachments: File NotIn_LeftJoin_Limit_TestCase.php    

 Description   

I have a simple case with 3 Classes. Contact, Phone and Email. A Contact can many many phones, but only one email.

When doing this simple query:
$query = Doctrine_Query::create()->from('Contact c');
$query->leftJoin('c.Phones p ON c.id = p.contact_id');
$query->where('c.id NOT IN (SELECT Email.contact_id FROM Email)');
$query->execute();
Every thing went fine.

But when adding a limit() condition like:
$query->limit(20);
$query->execute();

The SQL generated query is not valid. There is a problem with the alias used in the NOT IN subquery. This query is generated like this:
WHERE c2.id NOT IN (SELECT e2.contact_id AS e2__contact_id FROM email e)
There is a mix between alias e2 and e

I have been trying to debug, but I didn't understand what was going wrong. The problem seems to happend in the class Doctrine_Query between line 1486 and 1554, but this part is obscur to me.

I attach to this ticket a valid TestCase

Thanks for support



 Comments   
Comment by David Jeanmonod [ 23/Apr/10 ]

TEXT VERSION OF THE TEST CASE

<?php

require_once('doctrine/lib/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));
$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_ALL);
$conn = Doctrine_Manager::connection('mysql://root:@localhost/test_doctrine');
echo "Connection is set up\n";

class Contact extends Doctrine_Record {
public function setTableDefinition()

{ $this->setTableName('contact'); $this->hasColumn('name', 'string', 300, array('type' => 'string', 'length' => 300)); }

public function setUp()

{ $this->hasMany('Phone as Phones', array('local' => 'id', 'foreign' => 'contact_id')); $this->hasOne('Email as Email', array('local' => 'id', 'foreign' => 'contact_id')); }

}
class Phone extends Doctrine_Record {
public function setTableDefinition()

{ $this->setTableName('phone'); $this->hasColumn('contact_id', 'integer', null, array('type' => 'integer')); }

public function setUp()

{ $this->hasOne('Contact', array('local' => 'contact_id', 'foreign' => 'id', 'onDelete' => 'CASCADE')); }

}
class Email extends Doctrine_Record {
public function setTableDefinition()

{ $this->setTableName('email'); $this->hasColumn('contact_id', 'integer', null, array('type' => 'integer')); }

public function setUp()

{ $this->hasOne('Contact', array('local' => 'contact_id', 'foreign' => 'id')); }

}
echo "Classes Contact, Phone and Email are defines\n";

try

{Doctrine::dropDatabases();}

catch(Exception $e){} // Drop if exist
Doctrine::createDatabases();
Doctrine::createTablesFromArray(array('Contact', 'Phone', 'Email'));
echo "Databases tables are create\n";

$query = Doctrine_Query::create()->from('Contact c');
$query->leftJoin('c.Phones p ON c.id = p.contact_id');
$query->where('c.id NOT IN (SELECT Email.contact_id FROM Email)');
$query->limit(20);
try

{ $query->execute(); echo "TEST: Doctrine LEFTJOIN + NOT IN + LIMIT is OK\n"; }

catch (Exception $e)

{ echo "TEST: Doctrine LEFTJOIN + NOTIN + LIMIT is NOT working\n Alias in the NOT IN part are wrong, we get e2 and e\n\nDetail of the error:\n ", $e->getMessage(), "\n"; }
Comment by will ferrer [ 30/Jun/10 ]

Hi David

I had a problem with subqueries in the which I worked around by including real sql in the subquery with a prefix of SQL:.

My bug occurred trying to run this code:

$q = Doctrine_Query::create(); 
$q->from('Customer Customer'); 
$q->addWhere(' Customer.id in (SELECT Customer.id as customer_id FROM Customer Customer)'); 
$q->addSelect('Customer.id'); 
$q->addSelect('Customer.id as customer_id');
$q->limit(20);

However this code works fine for me now (though make sure you have the latest svn build of doctrine because there were some patches that helped this work):

$q = Doctrine_Query::create(); 
$q->from('Customer Customer'); 
$q->addWhere(' Customer.id in (SQL:SELECT p.id AS p__0 FROM product_customers p)'); 
$q->addSelect('Customer.id'); 
$q->addSelect('Customer.id as customer_id');

Notice the use of SQL: in the subquery.

Here is the bug:

http://www.doctrine-project.org/jira/browse/DC-692

Also worth noting that I am getting that sql by building another query and then running the getSqlQuery method to return the sql I am then using in the other query.

Hope that helps.

Will Ferrer





[DC-633] Charset problem Created: 15/Apr/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: None
Affects Version/s: 1.2.2
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: Oguzhan Cetin Assignee: Jonathan H. Wage
Resolution: Cannot Reproduce Votes: 0
Labels: None
Environment:

windows 7 , bitnami wappstack



 Description   

Hi,

I'm trying to insert query . But i have a charset problem.

I'm poor speak english, sorry.

my bootstrap code quote:

$conn = Doctrine_Manager::connection($db);
$conn->setCollate('utf8_unicode_ci');
$conn->setCharset('utf8');

controller code:
$user = new YpUser();
$user -> business_name = 'Turcom';
$user -> complete_adress = 'Maslak/Itü';
$user -> contact_person = 'Oguzhan Cetin';
$user -> contact_number = '05065000000';
$user -> save();

///////////////////
Error:

Fatal error: Uncaught exception 'Doctrine_Connection_Pgsql_Exception' with message 'SQLSTATE[22021]: Character not in repertoire: 7 HATA: "UTF8" dil kodlaması için geçersiz bayt dizisi: 0xfc HINT: Bu hata ayrıca bayt sırasının sunucunun beklediÄ� kodlamada olmadıÄ�± zaman meydana gelmektedir. Ä°stemci dil kodlaması "client_encoding" seçeneÄ� ile ayarlanmaktadır.' in C:\LOCAL\stack\apache2\htdocs\pbmp\libraries\doctrine\Doctrine\Connection.php:1082 Stack trace: #0 C:\LOCAL\stack\apache2\htdocs\pbmp\libraries\doctrine\Doctrine\Connection\Statement.php(269): Doctrine_Connection->rethrowException(Object(PDOException), Object(Doctrine_Connection_Statement)) #1 C:\LOCAL\stack\apache2\htdocs\pbmp\libraries\doctrine\Doctrine\Connection.php(1042): Doctrine_Connection_Statement->execute(Array) #2 C:\LOCAL\stack\apache2\htdocs\pbmp\libraries\doctrine\Doctrine\Connection\Pgsql.php(244): Doctrine_Connection->exec('INSERT INTO yp_...', Array) #3 C:\LOCAL\stack\apache2\htdocs\pbmp\libraries\doctrine\Doctrine\Connection\U in C:\LOCAL\stack\apache2\htdocs\pbmp\libraries\doctrine\Doctrine\Connection.php on line 1082

Thanks For help.






[DC-620] Unserialize does not add entity to the table entitymap Created: 06/Apr/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Record
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Critical
Reporter: Maurice Makaay Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None

Attachments: Text File Doctrine_Record.unserialize.patch     Text File Doctrine_Record.unserialize.patch    

 Description   

Hello,

Considering this test script:

if (!file_exists("/tmp/serialize.doctrine"))
{
  $a = Address::getById(1);
  $b = Address::getById(2);
  $c = Address::getById(3);
  $d = Address::getById(4);
  print "Serialize using oid=" . $d->getOid() . "\n";
  file_put_contents("/tmp/serialize.doctrine", serialize($d));
}
else
{
  $d = unserialize(file_get_contents("/tmp/serialize.doctrine"));
  print "unserialized oid = " . $d->getOid() . "\n";

  $d2 = Address::getById(4);
  print "new object oid = " . $d2->getOid() . "\n";

  $d->housenumber ++;
  print "{$d->housenumber} versus {$d2->housenumber}\n";
}

Things are going wrong in the unserialization. In this test script, I manage to create two objects holding the same oid, but pointing at different object instances (but for the same database id). The output of this script after running it twice shows me:

$ php test.php
Serialize using oid=5
$ php test.php
unserialized oid = 5
new object oid = 5
24 versus 23

Looking at the unserialize() code in Doctrine_Record, the problem seems to be coming from two issues in there:

  • $this->_oid is generated before the data unserialization. Because of this, the generated $this->_oid is overwritten with whatever was in the serialized data (this is why in above example, the oid became 5 for the unserialized record)
  • The unserialized record is not added to the table's identitymap, making it kind of invisible for the table's getRecord() code, resulting in two different objects that both represent the entity with id = 4 (as you can see in the "24 versus 23" output).

See the attached patch for a fix that I did on our code tree to make the test work.

Note that I added some extra code in the unserialize method to cleanup any existing entitymap and table repository entry for the unserialized object. This is of course not the best route, but it helps with some unit testing code where a lot of serialize/unserialize handling is going on and objects got mixed up when not doing this cleanup.
IMO, better would be to add an unserialization method to the Doctrine_Table, which would, like find(), act as a factory for turning unserialized record data into a Doctrine_Record object, possibly making use of already loaded entities that are available in the table's internal caches. With unserialize being handled directly from the Doctrine_Record, there is no way AFAICS to keep up the rule of always having exactly one object in a scope that represents a certain object in the database.

When running above script with this patch applied and with removing the $a, $b and $c assignments (just for making the oid's different between the two script runs), we get the following output:

$ php test.php
Serialize using oid=2
$ php test.php
unserialized oid = 5
new object oid = 5
24 versus 24

So here, unserializing an object and then reloading the object through the table object gives use two times the same object, representing db object with id = 4;

When doing things in a different order, we still can force an issue, but this is due to the things mentioned above: to cleanly handle this, unserialization should be handled from a factory method on Table. This script shows the behavior:

if (!file_exists("/tmp/serialize.doctrine"))
{
  $d = Address::getById(4);
  print "Serialize using oid=" . $d->getOid() . "\n";
  file_put_contents("/tmp/serialize.doctrine", serialize($d));
}
else
{
  $d2 = Address::getById(4);
  print "new object oid = " . $d2->getOid() . "\n";

  $d = unserialize(file_get_contents("/tmp/serialize.doctrine"));
  print "unserialized oid = " . $d->getOid() . "\n";

  $d3 = Address::getById(4);
  print "new object oid = " . $d3->getOid() . "\n";

  $d->housenumber ++;
  print "{$d->housenumber} versus {$d2->housenumber} versus {$d3->housenumber}\n";
}

The output of the second run being:

$php test.php
new object oid = 2
unserialized oid = 5
new object oid = 5
24 versus 23 versus 24

The first object that was created is still different from the unserialized object, but at least the third object that is created, is the object that was unserialized.

I hope that this makes the issue clear and that my input helps in fixing things. This issue provided us with some really unexpected behavior and we're glad that we were able to track it down to here.



 Comments   
Comment by Maurice Makaay [ 07/Apr/10 ]

FYI: we removed the "Remove existing record from the repository and table entity map" code from our patch. We had some issues with unit testing without this bit, but we updated the unit tests to work without this bit of code. The "Add the unserialized record to repository and entity map' part stays of course. I think you can ignore the cleanup code in the patch. As long as scripts unserialize data before working with it, things should be fine without it.

Comment by Maurice Makaay [ 07/Apr/10 ]

New version of the patch, without the cleanup code in it.

Comment by Maurice Makaay [ 07/Apr/10 ]

Trying to always use the same object for the same entity in the database, we came up with a factory method that we put on our record class as a static public. Maybe this is an idea that you want to include in Doctrine as well?

  static public function fromSerialized($serialized)
  {
    $entity = unserialize($serialized);

    if (!($entity instanceof Doctrine_Record)) throw new Exception(
      __METHOD__ . ': serialized object is not a Doctrine_Record object'
    );

    // If the unserialized object is a persisted entity, then we must
    // check if there is already an object for that entity available in
    // Doctrine's table repository.
    if ($entity->exists())
    {
      // Retrieve the entity through the table repository.
      $table = $entity->getTable();
      $repository_entity = $table->find($entity->id);

      // If a different object was returned than our unserialized
      // object, then there was an object loaded before unserialization.
      // We will merge the data from the unserialized object with
      // the existing object and return the existing object to the caller.
      if ($entity->getOid() !== $repository_entity->getOid()) {
        $repository_entity->merge($entity);
        $entity = $repository_entity;
      }
    }

    return $entity;
  }

For us, it's working wonders in combination with above Doctrine_Record::unserialize() patch. When letting this code handle our unserialization, the same object (checked by its oid) is used for referencing the same entity throughout the code. We call the code like this:

$object = Doctrine_Record::fromSerialized($serialized_data);




[DC-603] Doctrine model loading broken on Centos/RHEL 5 Created: 26/Mar/10  Updated: 29/Mar/10  Resolved: 29/Mar/10

Status: Resolved
Project: Doctrine 1
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: Dennis Jacobfeuerborn Assignee: Jonathan H. Wage
Resolution: Invalid Votes: 0
Labels: None
Environment:

Doctrine 1.1.5 on Centos 5.4



 Description   

The loading of models fails on Centos 5 systems because loadModels() relies on RecursiveDirectoryIterator which seems to return filenames in different orders on different operating systems (see this older doctrine bug: http://trac.doctrine-project.org/ticket/1688 ).

The result is that Doctrine tries to load the model class before it's base class which fails because the base-class is not yet known at that time.

Example:

I setup the auto-loading like this:

spl_autoload_register( array('Doctrine', 'autoload') );
Doctrine::loadModels('/data/models');

and "/data" looks like this:
/data/models/ftp/FtpTransfers.php
/data/models/ftp/FtpTransfersTable.php
/data/models/ftp/generated/BaseFtpTransfers.php

However when I run a test app I get the following error:
PHP Fatal error: Class 'BaseFtpTransfers' not found in /data/models/
ftp/FtpTransfers.php on line 13

Executing the same code works fine on Fedora.

This is the order in which loadModels() tries to load the files on both systems:

Centos 5:
/data/models/ftp/ftp_logs/FtpTransfers.php
/data/models/ftp/ftp_logs/FtpStatsHour.php
/data/models/ftp/ftp_logs/FtpStatsDayTable.php
/data/models/ftp/ftp_logs/FtpTransfersTable.php
/data/models/ftp/ftp_logs/generated/BaseFtpStatsHour.php
/data/models/ftp/ftp_logs/generated/BaseFtpTransfers.php
/data/models/ftp/ftp_logs/generated/BaseFtpStatsDay.php
/data/models/ftp/ftp_logs/FtpStatsHourTable.php
/data/models/ftp/ftp_logs/FtpStatsDay.php

Fedora 11:
/data/models/ftp/ftp_logs/generated/BaseFtpStatsHour.php
/data/models/ftp/ftp_logs/generated/BaseFtpTransfers.php
/data/models/ftp/ftp_logs/generated/BaseFtpStatsDay.php
/data/models/ftp/ftp_logs/FtpStatsHourTable.php
/data/models/ftp/ftp_logs/FtpStatsDayTable.php
/data/models/ftp/ftp_logs/FtpTransfers.php
/data/models/ftp/ftp_logs/FtpStatsHour.php
/data/models/ftp/ftp_logs/FtpStatsDay.php
/data/models/ftp/ftp_logs/FtpTransfersTable.php



 Comments   
Comment by Jonathan H. Wage [ 29/Mar/10 ]

The aggressive model loading can only be used if you handle the dependencies between classes yourself or you don't have any dependencies and it is no problem to aggressively require all files it finds in a directory. It sounds like you need to use conservative or pear style model loading.





[DC-582] DataDict entry missing for datetime type for MySQL causes migrations to fail due to sql error Created: 18/Mar/10  Updated: 10/Jan/12  Resolved: 29/Mar/10

Status: Resolved
Project: Doctrine 1
Component/s: Migrations
Affects Version/s: 1.2.0, 1.2.1
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: Rich Birch Assignee: Jonathan H. Wage
Resolution: Invalid Votes: 0
Labels: None
Environment:

LAMP



 Description   

I discovered this whilst trying out migrations via symfony. I added a datetime field to my schema.yml and generated the migrations, but upon running the migration I got the following error:

SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '()' at line 1. Failing Query: "ALTER TABLE order_item ADD purchased_at datetime()"

The following code causes the failure in the actual migration:

$this->addColumn('order_item', 'purchased_at', 'datetime', '', array());

because it generates the following sql:

ALTER TABLE order_item ADD purchased_at datetime()

The diff from my patched version which fixes the issue is as follows:

Index: Doctrine/DataDict/Mysql.php
===================================================================
— Doctrine/DataDict/Mysql.php (revision 7415)
+++ Doctrine/DataDict/Mysql.php (working copy)
@@ -227,6 +227,7 @@
return 'DATE';
case 'time':
return 'TIME';
+ case 'datetime':
case 'timestamp':
return 'DATETIME';
case 'float':

It's against the following repository file:

http://doctrine.mirror.svn.symfony-project.com/branches/1.2/lib/Doctrine/DataDict/Mysql.php

I hope this is useful and gets committed



 Comments   
Comment by Rich Birch [ 22/Mar/10 ]

I've just discovered that the same issue exists for fields of type 'text':

SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ') NOT NULL, session_time DATETIME NOT NULL, INDEX session_id_index_idx (session_' at line 1. Failing Query: "CREATE TABLE session (id BIGINT AUTO_INCREMENT, session_id VARCHAR(64) NOT NULL, session_data text() NOT NULL, session_time DATETIME NOT NULL, INDEX session_id_index_idx (session_id), PRIMARY KEY(id)) ENGINE = INNODB"

from the following schema:

Session:
columns:
session_id:

{ type: string(64), notnull: true }

session_data:

{ type: text, notnull: true }

session_time:

{ type: timestamp, notnull: true }

indexes:
session_id_index:
fields: [ session_id ]
unique: true

I guess there may be other field entries missing too. Is there a comprehensive list of doctrine field types somewhere?

Comment by Rich Birch [ 22/Mar/10 ]

Ok, I might have been being dumb here. I've just checked the doctrine documentation for defining the schema (http://www.doctrine-project.org/documentation/manual/1_2/en/defining-models) and there's no mention of a datetime or text field (I've just realised that I should have used string instead of text anyway), but datetime still works as a column type so shouldn't it be documented?

I guess either datetime should be fully removed or fully supported

Comment by Jonathan H. Wage [ 29/Mar/10 ]

You should be using the Doctrine portable types. So you would use date or timestamp I believe and Doctrine will convert it to the appropriate type for your dbms.

Comment by ToleaN [ 10/Jan/12 ]

not correctly generated migration, in my case generated:

$this->addColumn('tree', 'published_at', 'datetime', '', array(
));

but if change the fourth parameters on null, all ok





[DC-562] Broken OneToOne Relationship on two Primary Keys Created: 11/Mar/10  Updated: 27/Aug/10  Resolved: 15/Mar/10

Status: Resolved
Project: Doctrine 1
Component/s: Data Fixtures
Affects Version/s: 1.2.1
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: Ulf Thomas Assignee: Jonathan H. Wage
Resolution: Invalid Votes: 0
Labels: None
Environment:

Doctrine 1.2.1
MySQL 5.1.37



 Description   

Hi,
if you have a One-To-One Relationship which uses Primary Keys on both ends Doctrine screws up the relationship if you import the data from a data fixture.

Example:

DB contents:

User:
id|username|password
1|eins|eins
2|zwei|zwei
3||drei|drei
4|vier|vier

Contact:
id|name
1|eins
3|drei

Contact:
id|name
2|zwei
4|vier

{{
//schema.yml
User:
columns:
id:
primary: true
autoincrement: true
type: integer(4)
username: string(255)
password: string(255)
relations:
Contact:
local: id
foreign: id
foreignType: one

Contact:
columns:
id:
type: integer(4)
primary: true
autoincrement: true
name:
type: string(255)

Contact2:
columns:
id:
type: integer(4)
primary: true
autoincrement: true
name:
type: string(255)
}}

./doctrine dump-data

{{
//data/fixtures/data.yml
Contact:
Contact_1:
name: eins
Contact_3:
name: drei
Contact2:
Contact2_2:
name: zwei
Contact2_4:
name: vier
User:
User_1:
username: eins
password: eins
User_2:
username: zwei
password: zwei
User_3:
username: drei
password: drei
User_4:
username: vier
password: vier
}}

./doctrine build-all-reload

DB contents:

User:
id|username|password
1|eins|eins
2|zwei|zwei
3||drei|drei
4|vier|vier

Contact:
id|name
1|eins
2|drei

Contact:
id|name
1|zwei
2|vier

Now the relations are broken.



 Comments   
Comment by Jonathan H. Wage [ 15/Mar/10 ]

Your ticket is hard to read, just for future reference it would be wise to make sure the issue is readable for us. Anyways, I had a look and your schema is incorrect. You have a foreign key but it is also set as primary and auto increment.

Comment by Ulf Thomas [ 16/Mar/10 ]

Sorry for the unreadable ticket, I tryed to make it understadable as much as I can.

The thing with the foreign key and primary key is that we have a existing database and want to port it to Doctrine.
In the database the structure is like that and in the documentation I didn't see anything that say that this won't work. Can you point me to some aditional information?

Comment by Shirley Chan [ 27/Aug/10 ]

I'm working on an open source application, Sahana Agasti (http://sahanafoundation.org/), using Symfony 1.4 as the framework and Doctrine as the ORM. This project stumble into a similar issue with establishing a direct 1:1 relationships on primary keys. Below is an example of a person and person's date of birth table. In the yml file, Person has an id field as the primary key. This field is referenced by the primary key field person_id in PersonDateOfBirth. However, Doctrine only creates the tables and not the relationship between the two tables correctly.

schema.yml
agPerson:
columns:
id:
primary: true
type: integer(5)
autoincrement: true
notnull: true
relations:
agPersonDateOfBirth:
type: one
local: id
foreign: person_id
agPersonDateOfBirth:
columns:
person_id:
primary: true
type: integer(5)
notnull: true
date_of_birth:
type: date
notnull: true
relations:
agPerson:
foreignType: one
type: one
local: person_id
foreign: id

schema.sql
CREATE TABLE ag_person (id BIGINT AUTO_INCREMENT, PRIMARY KEY(id)) ENGINE = INNODB;
CREATE TABLE ag_person_date_of_birth (person_id BIGINT, date_of_birth DATE NOT NULL, PRIMARY KEY(person_id)) ENGINE = INNODB;





[DC-534] Couldn't hydrate. Found non-unique key mapping named 'lang' Created: 02/Mar/10  Updated: 28/Dec/11  Resolved: 15/Mar/10

Status: Resolved
Project: Doctrine 1
Component/s: I18n
Affects Version/s: 1.2.1
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: Adamczewski Assignee: Jonathan H. Wage
Resolution: Incomplete Votes: 0
Labels: None
Environment:

PHP 5.2.9 Symfony 1.4



 Description   

For the following query

return $this->createQuery('a')
->innerJoin('a.Translation t WITH t.lang = ?', $lang)
->innerJoin('a.UserAttributes ua')
->innerJoin('ua.AttrOptions o WITH o.attribute_id = a.id')
->innerJoin('o.Translation ot WITH ot.lang = ?', $lang)
->addWhere('ua.user_id = ?', $userId)
->addWhere('a.tab = ?', $tab)
->addOrderBy('a.ord asc')
->execute();

Unknown macro: {/quote}

i get

Couldn't hydrate. Found non-unique key mapping named 'lang'.

{/quote}

error, so i cannot join more than one translation table in one query because it cause error.



 Comments   
Comment by Jonathan H. Wage [ 02/Mar/10 ]

Can you create a test case for this? You can find information about Doctrine unit testing here: http://www.doctrine-project.org/documentation/manual/1_2/en/unit-testing

Comment by Mikhail Menshinskiy [ 28/Dec/11 ]

I have the same error.
How to solve this issue?





[DC-523] Aggregating values in select(), always joins a record, even if there is no relation Created: 25/Feb/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: None
Affects Version/s: 1.2.1
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: pbijl Assignee: Jonathan H. Wage
Resolution: Can't Fix Votes: 0
Labels: None
Environment:

mysql5, unix


Attachments: File AggregateFunctionalExpressiosTestCase.php    

 Description   

Example:

I want to aggregate a expression, so i need to select everything:

->select('a., d., CONCAT_WS(" ", d.initials, d.surname) as formalName))
->from('Account a')
->leftJoin('a.Details d')

Result will be an empty record of Details if there is no relation, removing the aggregated value will fix this and not join a record.



 Comments   
Comment by Jonathan H. Wage [ 01/Mar/10 ]

Sorry, I don't quite understand with such minimal information. Can you provide a failing test case?

Comment by pbijl [ 02/Mar/10 ]
 

// logical example
// result: working as expected
$q = Doctrine_Query::create()
->from('Accounts a')
->leftJoin('a.Data d')
->execute()
;

// this time the same query but with a ambigious select clause
// result: working as expected, 
$q = Doctrine_Query::create()
->select('a.*, d.*')
->from('Accounts a')
->leftJoin('a.Data d')
->execute()
;

// same query, but there should not be a Data row because of the bogus WITH condition
// result: working as expected
$q = Doctrine_Query::create()
->select('a.*, d.*')
->from('Accounts a')
->leftJoin('a.Data d with d.type = "blabla"')
->execute()
;

// same query, but with an aggregated column
// result: working as expected, again, no Data row
$q = Doctrine_Query::create()
->select('a.*, d.*, d.surname as test')
->from('Accounts a')
->leftJoin('a.Data d with d.type = "blabla"')
->execute()
;

// same query, but with an aggregated functional expression
// result: FAILS. `test` is joined as an empty column in the Accounts record, AND the Data record, which results in an empty Data record 
// you can imagine saving the Accounts object to save an empty Data record in return
$q = Doctrine_Query::create()
->select('a.*, d.*, concat_ws(" ", d.firstname, d.surname) as test')
->from('Accounts a')
->leftJoin('a.Data d with d.type = "blabla"')
->execute()
;

// a expression that doesnt concatenates doesnt join an empty Data record, but does aggregate a empty `test` column on Accounts
$q = Doctrine_Query::create()
->select('a.*, d.*, trim(d.firstname) as test')
->from('Accounts a')
->leftJoin('a.Data d with d.type = "blabla"')
->execute()
;

Comment by Jonathan H. Wage [ 02/Mar/10 ]

Hi, you can find information about how to create a valid Doctrine unit test case here: http://www.doctrine-project.org/documentation/manual/1_2/en/unit-testing

Comment by pbijl [ 10/Mar/10 ]
<?php

class Doctrine_AggregateFunctionalExpressios_TestCase extends Doctrine_UnitTestCase 
{
	private function baseQueryForAllMethods() {
		return Doctrine_Query::create()
		->from('User u')
		->where('u.id = ?', 4)
		;
	}
    public function testAggregateColumnInParentModel()
    {
		$res = $this->baseQueryForAllMethods()
		->select('u.*, concat(u.name, u.loginname) as aggregatedColumn, p.*')
		->leftJoin('u.Phonenumber p')		
		->fetchOne()
		;
		$this->assertTrue(isset($res->aggregatedColumn));
		$this->assertFalse(isset($res->Phonenumber->aggregatedColumn));
	}	
    public function testAggregateColumnInJoinedModel()
    {
		$res = $this->baseQueryForAllMethods()
		->select('u.*, p.*, concat(p.phonenumber, p.entity_id) as aggregatedColumn')
		->leftJoin('u.Phonenumber p')		
		->fetchOne()
		;
		$this->assertTrue(isset($res->Phonenumber->aggregatedColumn));
		$this->assertFalse(isset($res->aggregatedColumn));
	}
    public function testAggregateColumnInJoinedModelWithFailingWhereClause()
    {
		$res = $this->baseQueryForAllMethods()
		->select('u.*, p.*, concat(p.phonenumber, p.entity_id) as aggregatedColumn')
		->leftJoin('u.Phonenumber p WITH p.id = ?', 100)		
		->fetchOne()
		;
		$this->assertFalse(isset($res->Phonenumber->aggregatedColumn));		
		$this->assertFalse(isset($res->aggregatedColumn));				
	}
}
Comment by pbijl [ 10/Mar/10 ]

testcase attached~

Comment by Jonathan H. Wage [ 08/Jun/10 ]

Your test case has some errors in it. The ->Phonenumber relationship is a many so it is a Doctrine_Collection. Also, this is all expected behavior for aggregates to be located in the root. They only exist in the relationship as well for BC reasons. The whole functionality is flawed a bit and can't be patched without breaking backwards compatibility. All these issues have been thought through and addressed in Doctrine 2





[DC-514] cascading behaviors Created: 22/Feb/10  Updated: 15/Mar/10  Resolved: 15/Mar/10

Status: Resolved
Project: Doctrine 1
Component/s: Behaviors, Record
Affects Version/s: 1.1.4, 1.1.5, 1.1.6, 1.2.0, 1.2.1
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: Andreas Wissl Assignee: Jonathan H. Wage
Resolution: Invalid Votes: 0
Labels: None


 Description   

If you want to add a behavior to a behavior, you (depending on your template) can get an error message telling you, that the _table property is not an object.

This happens, as actAs() method of Record_Abstract does first the $tpl->setUp() call and then the $tpl->setTableDefnition(), as for the cascading templates - which are using Record_Generator - the $child->setTableDefinition() is called before $child->setUp() in method buildChildDefinitions(). Switching those two lines fixes the error and has, as far we can see (having it in our production system for quite a while now) no side effects besides fixing the error.



 Comments   
Comment by Jonathan H. Wage [ 01/Mar/10 ]

Hi, when providing feedback and you have a change, it is best to provide a patch. Can you generate a patch for your changes and attach it to the ticket?

However, if I understand your verbal description of the change correctly, it is not right. We can't call setUp() first, because setTableDefinition() is supposed to be called first. This is how it is supposed to work.





[DC-444] Reimporting exported data-fixtures fails if they contain <? or <?php Created: 23/Jan/10  Updated: 11/May/10  Resolved: 01/Mar/10

Status: Resolved
Project: Doctrine 1
Component/s: Data Fixtures, File Parser
Affects Version/s: 1.0.14, 1.1.4, 1.2.1
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: Benjamin Steininger Assignee: Jonathan H. Wage
Resolution: Won't Fix Votes: 0
Labels: None


 Description   

In Doctrine_Parser::doLoad() the fixtures are included which will try to execute the file with the php-interpreter resulting in all unescapted code which starts with <? (if shorttags are on) or <?php being executed (as long as it is in one line and ends with ?>).
As long as the php-code is multi-lined the break after <? or <?php gets converted to \r\n which will create a parse-error and make it impossible to import the fixture:

build-all-reload - Are you sure you wish to drop your databases? (y/n)
y
build-all-reload - Successfully dropped database for connection named 'development'
build-all-reload - Generated models successfully from YAML schema
build-all-reload - Successfully created database for connection named 'development'
build-all-reload - Created tables successfully


Warning: Unexpected character in input:  '\' (ASCII=92) state=1 in /..../doctrine/data/fixtures/data.yml on line 9724

Call Stack:
    0.0003      72344   1. {main}() /.../scripts/doctrine-cli:0
    0.7356   14656192   2. Doctrine_Cli->run() /.../scripts/doctrine-cli:25
    0.7356   14656256   3. Doctrine_Cli->_run() /.../library/doctrine/1.2.1/lib/Doctrine/Cli.php:452
    0.7367   14666244   4. Doctrine_Cli->executeTask() /.../library/doctrine/1.2.1/lib/Doctrine/Cli.php:498
    0.7367   14666388   5. Doctrine_Task_BuildAllReload->execute() /.../library/doctrine/1.2.1/lib/Doctrine/Cli.php:516
    4.2097   22318416   6. Doctrine_Task_LoadData->execute() /.../library/doctrine/1.2.1/lib/Doctrine/Task/BuildAllReload.php:56
    4.2176   22318488   7. Doctrine_Core::loadData() /.../library/doctrine/1.2.1/lib/Doctrine/Task/LoadData.php:43
    4.2185   22357660   8. Doctrine_Data->importData() /.../library/doctrine/1.2.1/lib/Doctrine/Core.php:996
    4.2202   22472372   9. Doctrine_Data_Import->doImport() /.../library/doctrine/1.2.1/lib/Doctrine/Data.php:222
    4.2202   22472440  10. Doctrine_Data_Import->doParsing() /.../library/doctrine/1.2.1/lib/Doctrine/Data/Import.php:112
    4.2204   22488760  11. Doctrine_Parser::load() /.../library/doctrine/1.2.1/lib/Doctrine/Data/Import.php:95
    4.2204   22489152  12. Doctrine_Parser_Yml->loadData() /.../library/doctrine/1.2.1/lib/Doctrine/Parser.php:89
    4.2204   22489216  13. Doctrine_Parser->doLoad() /.../library/doctrine/1.2.1/lib/Doctrine/Parser/Yml.php:78

Parse error: syntax error, unexpected T_STRING in /.../data/fixtures/data.yml on line 9724

Call Stack:
    0.0003      72344   1. {main}() /.../scripts/doctrine-cli:0
    0.7356   14656192   2. Doctrine_Cli->run() /.../scripts/doctrine-cli:25
    0.7356   14656256   3. Doctrine_Cli->_run() /.../library/doctrine/1.2.1/lib/Doctrine/Cli.php:452
    0.7367   14666244   4. Doctrine_Cli->executeTask() /.../library/doctrine/1.2.1/lib/Doctrine/Cli.php:498
    0.7367   14666388   5. Doctrine_Task_BuildAllReload->execute() /.../library/doctrine/1.2.1/lib/Doctrine/Cli.php:516
    4.2097   22318416   6. Doctrine_Task_LoadData->execute() /.../library/doctrine/1.2.1/lib/Doctrine/Task/BuildAllReload.php:56
    4.2176   22318488   7. Doctrine_Core::loadData() /.../library/doctrine/1.2.1/lib/Doctrine/Task/LoadData.php:43
    4.2185   22357660   8. Doctrine_Data->importData() /.../library/doctrine/1.2.1/lib/Doctrine/Core.php:996
    4.2202   22472372   9. Doctrine_Data_Import->doImport() /.../library/doctrine/1.2.1/lib/Doctrine/Data.php:222
    4.2202   22472440  10. Doctrine_Data_Import->doParsing() /.../library/doctrine/1.2.1/lib/Doctrine/Data/Import.php:112
    4.2204   22488760  11. Doctrine_Parser::load() /.../library/doctrine/1.2.1/lib/Doctrine/Data/Import.php:95
    4.2204   22489152  12. Doctrine_Parser_Yml->loadData() /.../library/doctrine/1.2.1/lib/Doctrine/Parser.php:89
    4.2204   22489216  13. Doctrine_Parser->doLoad() /.../library/doctrine/1.2.1/lib/Doctrine/Parser/Yml.php:78

That can happen with unescapted <?xml too if short-tags are active and result in parse-error.

A way to load fixtures without running it through the php-interpreter would be nice or the <? needs to be escaped on export and unescaped on import.

Checked with version 1.2.1, 1.1.4 and 1.0.14, all versions share this code.

This "bug" can probably get a bit ugly in the raw case that a persons is using that dump-feature with something like

<?php system('rm -rf /'); ?>

anywhere in the database.
As long as the code is in one line there aren't any \r\n added and it will get executed without.



 Comments   
Comment by Jonathan H. Wage [ 01/Mar/10 ]

I think this is somewhat expected. As I have stressed in the past. Dumping data fixtures from the database can never work 100%. The idea is that you dump to get started, then modify the dumped fixtures so that they will re-import properly. If you have a suggested fix/patch please feel free to share and re-open.

Comment by Jannis Mosshammer [ 11/May/10 ]

I modified the doLoad() function to not use include but file_get_contents and the import of some critical fields (which contained xml data as well as raw php data) worked fine.

But honestly, I've got the feeling that I haven't really understood why the yaml load is done via including the file and saving the output buffer - so maybe I'm missing the big picture.
My altered version of the doLoad function looks like this:

Unable to find source-code formatter for language: php. Available languages are: actionscript, html, java, javascript, none, sql, xhtml, xml
abstract class Doctrine_Parser
{
    ...
    /**
     * doLoad
     *
     * Get contents whether it is the path to a file file or a string of txt.
     * Either should allow php code in it.
     *
     * @param string $path 
     * @return void
     */
    public function doLoad($path)
    {
        //ob_start();
        if ( ! file_exists($path)) {
            $contents = $path;
            $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'dparser_' . microtime();

            file_put_contents($path, $contents);
        }

	    $contents = file_get_contents($path);
        /*include($path);
        // Fix #1569. Need to check if it's still all valid
        $contents = ob_get_clean(); //iconv("UTF-8", "UTF-8", ob_get_clean());
		*/
        return $contents;
    }
    ...
}





[DC-433] UnitOfWork saveRelatedLocalKeys() creating erroneous child/parent rows Created: 15/Jan/10  Updated: 15/Mar/10  Resolved: 15/Mar/10

Status: Resolved
Project: Doctrine 1
Component/s: Record, Relations
Affects Version/s: 1.1.6
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: Justin Mazzi Assignee: Jonathan H. Wage
Resolution: Incomplete Votes: 0
Labels: None

Attachments: Text File unitofwork.patch    

 Description   

This issue is a lot like the bug with refreshRelated() in ticket #DC-93. Calling save() on a record will cause hasOne rows to be generated in error (with all NULL values). It looks the saveRelatedLocalKeys function checks for foreign keys, calls isModified, which returns true, and calls save on that related object. isModified is returning true for a row that doesn't exist.

schema.yml
 
---
Staff:
  columns:
    username: string(50)
    password: string(255)
    active:
      type: boolean
      default: true
  indexes:
    user_pass_active_idx:
      fields: [username, password, active]


Tickets:
  columns:
    mask:
      unique: true
      type: string(10)
    staff_id: integer(9)
  relations:
    Staff:
      local: staff_id
      foreign: id
test.php
 
<?php
$ticket = new Tickets();
$ticket->staff = null;
// This save generates the NULL record in the staff table
$ticket->save();


 Comments   
Comment by Jonathan H. Wage [ 01/Mar/10 ]

Hi, this patch is not valid. We can't only save records that already exist. That means it stops all new records from being saved. If you want to help fix this, first we'll need a reproducible test case. Thanks, Jon





[DC-367] generate-models-db doesn't fetch cascade delete constraint Created: 18/Dec/09  Updated: 01/Mar/10  Resolved: 01/Mar/10

Status: Resolved
Project: Doctrine 1
Component/s: Cli
Affects Version/s: 1.2.0
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: Francesco Montefoschi Assignee: Jonathan H. Wage
Resolution: Invalid Votes: 0
Labels: None
Environment:

Windows / PostgreSQL



 Description   

Running
doctrine-cli.php generate-models-db

everything works fine, I get in setUp() all the relations, by the way no one of them has the 'cascade' => array('delete') option, even if I setup all the constraints correctly.



 Comments   
Comment by Benjamin Eberlei [ 27/Jan/10 ]

however, this information cant be detected on mysql 5.0 versions and came in late in other versions.

Comment by Francesco Montefoschi [ 28/Jan/10 ]

Why can't this be detected on mysql 5?

Comment by Jonathan H. Wage [ 01/Mar/10 ]

I just tested this with the most recent mysql 5 version and it works as expected.

Comment by Francesco Montefoschi [ 01/Mar/10 ]

By the way, I reported this bug against Postgres





[DC-352] Doctrine_RawSql/Caching fatal error Created: 10/Dec/09  Updated: 01/Mar/10  Resolved: 01/Mar/10

Status: Resolved
Project: Doctrine 1
Component/s: Caching, Native SQL
Affects Version/s: 1.2.0, 1.2.1
Fix Version/s: 1.2.2

Type: Bug Priority: Critical
Reporter: Jonathan Shelly Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 1
Labels: None
Environment:

Solaris



 Description   

Doctrine_RawSql::calculateResultCacheHash tries to call $this->getSql() - an undefined method - on line 444 of RawSql.php.



 Comments   
Comment by Witold Wasiczko [ 24/Jan/10 ]

I get the same error during:
$q = new Doctrine_RawSql();
$q->useResultCache(true);

I fixed it, by change from:
$sql = $this->getSql();
to:
$sql = $this->getDql();

In Doctrine_RawSql 444 line.





[DC-324] Quote identifier breaks multi-column index sql generation Created: 03/Dec/09  Updated: 01/Mar/10  Resolved: 01/Mar/10

Status: Resolved
Project: Doctrine 1
Component/s: Connection
Affects Version/s: 1.2.0
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: Juozas Kaziukenas Assignee: Jonathan H. Wage
Resolution: Invalid Votes: 0
Labels: None

Attachments: Text File connection2.patch    

 Description   

By enabling quote identifier:

Doctrine_Manager::getInstance()->setAttribute(
Doctrine::ATTR_QUOTE_IDENTIFIER,
true
);

If table has multi-column index, results are:

CREATE INDEX name ON table ("column1, column2")

expected result

CREATE INDEX name ON table ("column1", "column2")

I created a patch for this bug, which extends quoteIdentifer() method in connection to detect comma separated fields and escape them separately. It also supports any amount of fields (not just two). It's done in this method, and not in getIndexFieldDeclarationList() in export classes, because it would require a major refactoring for a lot of classes and methods inside different classes. Please review this issue, so I can commit.



 Comments   
Comment by Jonathan H. Wage [ 07/Dec/09 ]

I don't think it should be done this way. It should be done specifically where it is needed, in getIndexFieldDeclarationList()

Comment by Jonathan H. Wage [ 07/Dec/09 ]

Hmm. After looking at this, it doesn't seem right. The method getIndexFieldDeclarationList() is already implemented this way and each field is quoted individually. Let me see your schema where you define the index..

Comment by Juozas Kaziukenas [ 07/Dec/09 ]

Ok, it might have been not right, but I made it like this, because this data was tunelling from definition to getIndexFieldDeclarationList() and at that point fields are already as list (comma separated), so I guess my fix worked more like a hack.

Model looks something like this:

<?php

abstract class Model extends Doctrine_Record
{
public function setTableDefinition()
{
$this->setTableName('table');
$this->hasColumn('userid', 'integer', 4, array(
'type' => 'integer',
'length' => 4,
'fixed' => false,
'unsigned' => false,
'notnull' => true,
'primary' => false,
));
$this->hasColumn('moduleid', 'integer', 4, array(
'type' => 'integer',
'length' => 4,
'fixed' => false,
'unsigned' => false,
'notnull' => true,
'primary' => false,
));
$this->hasColumn('privilegeid', 'string', null, array(
'type' => 'string',
'fixed' => true,
'unsigned' => false,
'notnull' => true,
'primary' => false,
));
}

public function setUp()
{
parent::setUp();

$this->hasOne('Model_Privileges as Privileges', array(
'local' => 'privilegeid, moduleid',
'foreign' => 'id, moduleid'));
}
}

It is generated by doctrine task generate-models-db, so it is a generator issue then?

Comment by Jonathan H. Wage [ 07/Dec/09 ]

What database type?

$this->hasOne('Model_Privileges as Privileges', array(
'local' => 'privilegeid, moduleid',
'foreign' => 'id, moduleid'));
}

This part is wrong. the comma separated list of fields is not supported and Doctrine doesn't support composite foreign keys. So it is a problem with the importer for the database you're using. Is it mssql?

Comment by Juozas Kaziukenas [ 07/Dec/09 ]

I was not aware that comma separated fields were invalid. My patch is invalid them and I need to fix generator.

I'm working with pgsql, and will review the problem, propose a pach.

Comment by Jonathan H. Wage [ 07/Dec/09 ]

If composite foreign keys were fully supported in Doctrine 1 it would be with an array:

$this->hasOne('Model_Privileges as Privileges', array(
'local' => array('privilegeid', 'moduleid'),
'foreign' => array('id', 'moduleid')));
}
Comment by Jonathan H. Wage [ 07/Dec/09 ]

If you define it that way, then everything is generated fine.





[DC-237] HYDRATE_ARRAY_HIERARCHY: Output is not an array Created: 16/Nov/09  Updated: 16/Nov/09  Resolved: 16/Nov/09

Status: Resolved
Project: Doctrine 1
Component/s: Nested Set
Affects Version/s: 1.2.0-BETA2
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: Nate Assignee: Jonathan H. Wage
Resolution: Cannot Reproduce Votes: 0
Labels: None


 Description   

When HYDRATE_ARRAY_HIERARCHY to get descendants, the output is not an array as expected. It is a Doctrine_Collection instead. In 1.2 Beta1 it worked as expected. This bug only occurs in Beta2.

Example Code: http://pastebin.ca/1673773
The class used for $file (if you need it): http://pastebin.ca/1673706



 Comments   
Comment by Jonathan H. Wage [ 16/Nov/09 ]

This is working for me as expected with the latest version of Doctrine 1.2 from SVN.





[DC-160] Search doesn't use the Search Analyzer to escape the query Created: 30/Oct/09  Updated: 13/Jul/12  Resolved: 02/Nov/09

Status: Resolved
Project: Doctrine 1
Component/s: Searchable
Affects Version/s: 1.0.12, 1.1.4, 1.2.0-ALPHA1, 1.2.0-ALPHA2, 1.2.0-ALPHA3
Fix Version/s: None

Type: Bug Priority: Critical
Reporter: Markus Lanthaler Assignee: Jonathan H. Wage
Resolution: Can't Fix Votes: 0
Labels: None


 Description   

When you use the Doctrine_Search_Analyzer_Standard all special characters like "ü" are removed or converted to e.g. "ue". So far so good.. The problem arises when a user performs a search.
Since Doctrine is using the plain query with the special char "ü" instead of converting it also there no results are returned.

Using the UTF8 analyzer is no option because often the normalization is a desired feature. It allows for example a user to formulate the query either as "Muenchen" or "München" and he still receives a relevant result.



 Comments   
Comment by Jonathan H. Wage [ 30/Oct/09 ]

The only option I can see is to do this in Doctrine_Search_Query::query()

$text = Doctrine_Inflector::unaccent($text);

I am not sure about doing this though. What do you think?

Comment by Markus Lanthaler [ 30/Oct/09 ]

I don't think that that's a good idea since it breaks the UTF8 analyzer. I would refactor the analyzers to include a method like normalize(). Those methods could then be called in the analyzers analyze() method.

Doctrine_Search_Analyzer_Standard::normalize($text, $encoding = $null) would look as follows:

public function normalize($text, $encoding = null) 
{ 
  $text = preg_replace('/[\'`�"]/', '', $text); 
  $text = Doctrine_Inflector::unaccent($text); 
  $text = preg_replace('/[^A-Za-z0-9]/', ' ', $text); 
  $text = str_replace('  ', ' ', $text); 

  return strtolower(trim($text));
}

Doctrine_Search_Analyzer_Utf8::normalize($text, $encoding = $null) would look as follows:

public function normalize($text, $encoding = null) 
{ 
  if (is_null($encoding)) { 
    $encoding = isset($this->_options['encoding']) ? $this->_options['encoding']:'utf-8'; 
  } 

  // check that $text encoding is utf-8, if not convert it 
  if (strcasecmp($encoding, 'utf-8') != 0 && strcasecmp($encoding, 'utf8') != 0) { 
    $text = iconv($encoding, 'UTF-8', $text); 
  } 

  $text = preg_replace('/[^\p{L}\p{N}]+/u', ' ', $text); 
  $text = str_replace('  ', ' ', $text); 

  return mb_strtolower(trim($text), 'UTF-8');
}

This would then also allow to remove some code duplication in the analyze() method. It could be changed to the following code in Doctrine_Search_Analyzer_Standard and could be completely removed in Doctrine_Search_Analyzer_Utf8:

public function analyze($text, $encoding = null)
{
  $text = $this->normalize($text, $encoding);

  $terms = explode(' ', $text);

  $ret = array();
  if ( ! empty($terms)) {
      foreach ($terms as $i => $term) {
          if (empty($term)) {
              continue;
          }

          if (in_array($lower, self::$_stopwords)) {
              continue;
          }

          $ret[$i] = $lower;
      }
  }
  return $ret;
}

Finally the normalize() method is called in Doctrine_Search_Query::query(). Unfortunately I have no idea how to call it there!?
What do you think?

Comment by Jonathan H. Wage [ 02/Nov/09 ]

At first this seems like a good solution but I realized it will break things even more. We allow wildcards and certain keywords in the query string. *, OR, AND, etc. If we were to run the normalize() method on the query text it would break all that functionality.

Comment by Markus Lanthaler [ 02/Nov/09 ]

Well.. that's nowhere documented.. the only thing I found was

Doctrine_Search provides a query language similar to Apache Lucene. The Doctrine_Search_Query converts human readable, easy-to-construct search queries to their complex DQL equivalents which are then converted to SQL like normal.

So I would rather break those special things than to have the search missing existing items. But maybe there's a better place to call that normalize() - perhaps where the query is analyzed and converted to a DQL statement. It should be possible there to run normalize on every search term.

Comment by Jonathan H. Wage [ 02/Nov/09 ]

That's what this means:

Doctrine_Search provides a query language similar to Apache Lucene

You can do things like

$query->query('some text* OR some more test*');

If we normalized each term/word it will still remove those wildcards. That is what query language similar to Apache lucene means.

Comment by João Veríssimo [ 13/Jul/12 ]

What do you think about this solution?
class ErpSearchAnalizer extends Doctrine_Search_Analyzer_Standard {
public function analyze($text, $encoding = null) {
$text = preg_replace('/[\'`�"]/', '', $text);
$text = Doctrine_Inflector::unaccent($text);
// for * search
//$text = preg_replace('/[^A-Za-z0-9]/', ' ', $text);
$text = str_replace(' ', ' ', $text);
$terms = explode(' ', $text);

$ret = array();
if (!empty($terms)) {
foreach ($terms as $i => $term) {
if (empty($term))

{ continue; }
if($term == 'OR'){ $ret[$i] = $term; continue; }
$lower = strtolower(trim($term));

if (in_array($lower, parent::$_stopwords)) { continue; }

$ret[$i] = $lower;
}
}
return $ret;
}
}
to make a search like this
$analize = new ErpSearchAnalizer();
$val_user = $analizar->analyze($val_user);
$tempresult = $table->search(implode(" ", $val_user));

will it work?





[DC-162] Doctrine_Migration_Builder fail to buildChangeColumn() Created: 30/Oct/09  Updated: 02/Nov/09  Resolved: 02/Nov/09

Status: Resolved
Project: Doctrine 1
Component/s: Migrations
Affects Version/s: 1.2.0-ALPHA3
Fix Version/s: 1.2.0-BETA1

Type: Bug Priority: Critical
Reporter: thibault duplessis Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

Symfony 1.3 daily svn



 Description   

Doctrine_Migration_Builder->buildChangeColumn contains a simple error.

return "        \$this->changeColumn('" . $tableName . "', '" . $columnName. "', '" . $length . "', '" . $type . "', " . $this->varExport($column) . ");";

When I look Doctrine_Migration_Base->changeColumn declaration, i see

public function changeColumn($tableName, $columnName, $type = null, $length = null, array $options = array())

The $length and $type parameters are inverted. It makes all column changes fail.

Thank you for your work, Doctrine is great !



 Comments   
Comment by Jonathan H. Wage [ 02/Nov/09 ]

This has already been fixed in another issue/commit.





[DC-1064] EntityManager::clear doesn't working with inserting Created: 16/Apr/14  Updated: 16/Apr/14  Resolved: 16/Apr/14

Status: Resolved
Project: Doctrine 1
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Major
Reporter: Adrian Ch Assignee: Jonathan H. Wage
Resolution: Invalid Votes: 0
Labels: None


 Description   

It looks like EntityManager's clear() method doesn't remove objects that was persisted during script execution.

Bellows are two functions. First one insert 10.000 records and use clear after each flush that should remove objects from memory, but instead of that memory usage growths in each iteration. There isn't any other reference for this objects.

I've checked how it works for reading and with clearing it works perfectly - script uses only constant memory.

 
    private function testInserting($em, $entityClass, $batchSize, $startMemoryUsage)
    {
        for ($i = 1; $i <= 10000; ++$i) {

            $item = new $entityClass();
            $item->setName($i);
            $item->setPresentation($i);
            $em->persist($item);

            if ($i % $batchSize == 0) {
                $em->flush();
                $em->clear();

                $currentMemoryUsage = memory_get_usage();
                printf("%d:\n\t%.2f MB (%.2f MB)\n", $i, $currentMemoryUsage /1024 / 1024, ($currentMemoryUsage - $startMemoryUsage) / 1024 / 1024);
            }
        }
    }
    
    
private function testReading($em, $entityClass, $batchSize, $startMemoryUsage)
    {
        $q = $em->createQuery("select i from $entityClass i");
        $iterableResult = $q->iterate();

        $i = 0;
        while (($row = $iterableResult->next()) !== false) {
            $em->clear();
            if ($i % $batchSize == 0) {
                $currentMemoryUsage = memory_get_usage();
                printf("%d:\n\t%.2f MB (%.2f MB)\n", $i, $currentMemoryUsage /1024 / 1024, ($currentMemoryUsage - $startMemoryUsage) / 1024 / 1024);
            }

            $i++;
        }
    }
    

My results:

1) Reading without clearing ($em->clear(); removed)

0:
22.89 MB (2.63 MB)
1000:
33.41 MB (13.15 MB)
2000:
44.04 MB (23.78 MB)
3000:
53.50 MB (33.24 MB)
4000:
65.13 MB (44.86 MB)
5000:
74.81 MB (54.55 MB)
6000:
84.27 MB (64.01 MB)
7000:
97.96 MB (77.69 MB)
8000:
107.40 MB (87.14 MB)
9000:
117.17 MB (96.91 MB)
10000:
126.61 MB (106.35 MB)

2) Reading with using clear

0:
22.89 MB (2.63 MB)
1000:
26.25 MB (5.99 MB)
2000:
24.74 MB (4.48 MB)
3000:
26.72 MB (6.46 MB)
4000:
24.79 MB (4.52 MB)
5000:
26.76 MB (6.50 MB)
6000:
24.81 MB (4.55 MB)
7000:
26.77 MB (6.51 MB)
8000:
24.83 MB (4.57 MB)
9000:
26.81 MB (6.54 MB)
10000:
24.86 MB (4.60 MB)

3) Inserting without clearing

1000:
29.50 MB (9.24 MB)
2000:
37.76 MB (17.50 MB)
3000:
45.12 MB (24.86 MB)
4000:
54.34 MB (34.07 MB)
5000:
61.79 MB (41.53 MB)
6000:
69.09 MB (48.82 MB)
7000:
76.40 MB (56.13 MB)
8000:
83.75 MB (63.48 MB)
9000:
95.64 MB (75.37 MB)
10000:
102.98 MB (82.71 MB)

4) Inserting with using clear

1000:
27.90 MB (7.63 MB)
2000:
34.64 MB (14.37 MB)
3000:
40.43 MB (20.17 MB)
4000:
48.20 MB (27.93 MB)
5000:
54.12 MB (33.85 MB)
6000:
59.89 MB (39.63 MB)
7000:
65.67 MB (45.40 MB)
8000:
71.43 MB (51.16 MB)
9000:
81.51 MB (61.25 MB)
10000:
87.29 MB (67.02 MB)






[DC-913] A way to auto detect what connection should be used to run a query based on what table was used in the from Created: 01/Nov/10  Updated: 01/Nov/10  Resolved: 01/Nov/10

Status: Resolved
Project: Doctrine 1
Component/s: Query
Affects Version/s: None
Fix Version/s: None

Type: New Feature Priority: Major
Reporter: will ferrer Assignee: Guilherme Blanco
Resolution: Fixed Votes: 0
Labels: None
Environment:

XP Xamp



 Description   

I needed a way to allow my queries to figure out what connection to use based on what table was used in the from. In other words I needed my queries to be able to run on the same connection that the table in the from used, but the way my code is structured I didn't have a convenient way to do this outside of doctrine.

I built a feature into doctrine for this I called: autodetectConnection.

I will post the patch for this after I build a test case for this ticket.

Will Ferrer



 Comments   
Comment by will ferrer [ 01/Nov/10 ]

Upon trying to make the test cases for this I looked into the feature I had added further and found that it seems as though doctrine has this ability already. I suspect my addition of this feature was in response to a bug I had which I have since fixed.





[DC-848] Validator Timestamp does not validate "YYYY-MM-DD hh:mm:ss"-Timestamps Created: 31/Aug/10  Updated: 17/Apr/14  Resolved: 01/Sep/10

Status: Resolved
Project: Doctrine 1
Component/s: Validators
Affects Version/s: 1.2.2, 1.2.3
Fix Version/s: None

Type: Bug Priority: Major
Reporter: Steffen Zeidler Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None

Attachments: File DC848TestCase.php     Text File Timestamp.patch    

 Comments   
Comment by Steffen Zeidler [ 31/Aug/10 ]

patch & test

Comment by Steffen Zeidler [ 31/Aug/10 ]

patch

Comment by Jonathan H. Wage [ 01/Sep/10 ]

Thanks for the issue and patches!

Fixed here http://github.com/doctrine/doctrine1/commit/680b4ba489d15a4c7fba73ec6a832ca142877b7b





[DC-826] Doctrine_Collection::replace() EASY PATCH! Created: 13/Aug/10  Updated: 24/Aug/10  Resolved: 24/Aug/10

Status: Resolved
Project: Doctrine 1
Component/s: Record
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Improvement Priority: Major
Reporter: Severin Puschkarski Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

linux, symfony-framework. I dont know the Version but Revision is 7490



 Description   

Record::replace() exists already.

but Doctrine_Collection::replace() does not exist.

I just copied the Doctrine_Collection::save() function, renamed it to replace and replaced
$record->save($conn);
with
$record->replace($conn);

WORKS GREAT!
Please include a replace for Doctrine_Collection.
Its really an easy patch!

By the way ... why is there no component for collection in the above select-field?



 Comments   
Comment by Jonathan H. Wage [ 24/Aug/10 ]

Fixed in http://trac.doctrine-project.org/changeset/7686

Thanks, Jon





[DC-794] findBy issue with field names containing "Or" Created: 20/Jul/10  Updated: 29/Sep/10  Resolved: 24/Aug/10

Status: Resolved
Project: Doctrine 1
Component/s: Record
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Eduardo Gulias Davis Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 1
Labels: None
Environment:

Tested with Windows Vista / Ubuntu 10.4, PHP 5.2.10, MySQL 5.



 Description   

The problem is in Doctrine_Table::buildFindByWhere, line 2715. I put the code here for clarity:

public function buildFindByWhere($fieldName)
{
$ands = array();
$e = explode('And', $fieldName);
foreach ($e as $k => $v) {
$and = '';
$e2 = explode('Or', $v);<- LINE 2715
$ors = array();
foreach ($e2 as $k2 => $v2) {
if ($v2 = $this->_resolveFindByFieldName($v2))

{ $ors[] = 'dctrn_find.' . $v2 . ' = ?'; }

else

{ throw new Doctrine_Table_Exception('Invalid field name to find by: ' . $v2); }

}
$and .= implode(' OR ', $ors);
$and = count($ors) > 1 ? '(' . $and . ')':$and;
$ands[] = $and;
}
$where = implode(' AND ', $ands);
return $where;
}

In my proyect I have a table called OrigenesOportunidadCliente, which id field name is idOrigenOportunidadCliente. As you have probably noticed, the name contains Or: idOrigenOportunidadCliente. And there is where it fails, it gets as if there where an OR statement, not finding a valid field name in the below foreach as the field is "OrigenOportunidad".



 Comments   
Comment by Enrico Stahn [ 22/Aug/10 ]

fixed http://github.com/estahn/doctrine1/compare/master...DC-794

Comment by Jonathan H. Wage [ 24/Aug/10 ]

Fixed in http://trac.doctrine-project.org/changeset/7681

Thanks

Comment by Eduardo Gulias Davis [ 29/Sep/10 ]

Sorry for the delay in posting this comment!!

thank you very very much for the quick response on this issue.

Great work!





[DC-785] Unexpected resuls(set of columns) when using Doctrine_Table->find() method. Created: 14/Jul/10  Updated: 15/Jul/10  Resolved: 15/Jul/10

Status: Resolved
Project: Doctrine 1
Component/s: Query
Affects Version/s: 1.2.2
Fix Version/s: None

Type: Bug Priority: Major
Reporter: Fedulov Ivan Assignee: Guilherme Blanco
Resolution: Fixed Votes: 0
Labels: None


 Description   

Doctrine is using inside of symfony framework with following server configuration:
php 5.2.10-2ubuntu6.4, Mysql 5.1.37-1ubuntu5, symfony 1.4.6.

The promplem is, that the same query on the same models produce diffrent results. In some cases not all columns from database table are present in genereted sql query. For example: we have three columns ID, TITLE, NAME. But doctrine generates query like "SELECT t.id AS t_id, t.title AS t_title FROM..." ignoring NAME column. I tried manually add "->select( * )" in method find(line 1616 of file Table.php), and this resolves problem.



 Comments   
Comment by Fedulov Ivan [ 15/Jul/10 ]

Sorry, it was my error. Wrong realisation of query cashing. After rebuilding schema, queries does`t updated automaticly...





$record->link('Alias', $id) throws an exception because of getIdentifier returns array, not string (DC-776)

[DC-775] $record->link('Alias', $id) throws an exception because of getIdentifier returns array, not string Created: 05/Jul/10  Updated: 06/Jul/10  Resolved: 06/Jul/10

Status: Resolved
Project: Doctrine 1
Component/s: Record, Relations, Schema Files
Affects Version/s: 1.2.2
Fix Version/s: None

Type: Sub-task Priority: Major
Reporter: Viktoras Bezaras Assignee: Jonathan H. Wage
Resolution: Invalid Votes: 0
Labels: None
Environment:

Symfony 1.4.6-DEV



 Description   

I want to bookmark ads on my site. So i have Ads table, users table (sfDoctrineGuard) and a refclass Bookmarks. I've created an ajax link to an action, where i retrieved $ad object, $user_id and
$ad->link('Bookmarks', $user_id, true);
So I expected a record in a database to be created.

Bookmarks:
...
columns:
user_id:

{ type: integer(4), primary: true }

ad_id:

{ type: integer(4), primary: true }

relations:
sfGuardUser:

{ onDelete: CASCADE, local: user_id, foreign: id }

Ads:

{ onDelete: CASCADE, local: ad_id, foreign: ad_id }

As my Bookmarks table has 2 primary keys in the link method

if (count($ids) > 0)

{ $q->whereIn($rel->getTable()->getIdentifier(), array_values($this->identifier())); }

$rel->getTable()->getIdentifier() returned me an array('user_id', 'ad_id') instead of a string. So the query which was generated looked like
UPDATE bookmarks SET ad_id = ? WHERE (Array IN )
And Array is not the name of a column.






[DC-762] Doctrine distinct issue Created: 24/Jun/10  Updated: 24/Jun/10  Resolved: 24/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Query
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Major
Reporter: Maxim Tsepkov Assignee: Guilherme Blanco
Resolution: Invalid Votes: 0
Labels: None
Environment:

symfony 1.4.6-DEV



 Description   

Doctrine DISTINCT selecting doesn't work as expected:

// The hydration method has no matter
// The problems appears if you want to select more than one field:

Doctrine_Core::getTable('Firm')->createQuery('f')
->select('DISTINCT title as title')
->innerJoin('f.City c')
->limit(20)
->execute()
;

// resulting query is fine:
// SELECT DISTINCT f.title AS f__0 FROM firm f INNER JOIN cs_geo_city c ON f.city_id = c.id LIMIT 20

// but:

Doctrine_Core::getTable('Firm')->createQuery('f')
->select('DISTINCT title as title')
->addSelect('city_id') // adding extra field cause problems
->innerJoin('f.City c')
->limit(20)
;

// resulting query:
// SELECT DISTINCT f.id AS f_id, f.city_id AS fcity_id, f.title AS f_0 FROM firm f INNER JOIN cs_geo_city c ON f.city_id = c.id LIMIT 20

// even with scalar hydration mode:

Doctrine_Core::getTable('Firm')->createQuery('f')
->setHydrationMode(Doctrine_Core::HYDRATE_SCALAR)
->addSelect('DISTINCT title as title')
->addSelect('city_id')
->limit(20)
->execute()
;

// SELECT DISTINCT f.city_id AS f_city_id, f.title AS f_0 FROM firm f INNER JOIN cs_geo_city c ON f.city_id = c.id LIMIT 20



 Comments   
Comment by Maxim Tsepkov [ 24/Jun/10 ]

nevermind, i did RTFM and it is all clear now





[DC-764] Major->please.....Value of Primary key from sequence in Postgres table NOT being set (although sequence gets incremented) Created: 24/Jun/10  Updated: 25/Jun/10  Resolved: 25/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Connection, Record
Affects Version/s: 1.2.1
Fix Version/s: 1.2.0, 1.2.1, 1.2.2, 1.2.3

Type: Bug Priority: Major
Reporter: Dennis Gearon Assignee: Jonathan H. Wage
Resolution: Invalid Votes: 0
Labels: None
Environment:

Ubuntu9.10 / PHP 5.2.6-3ubuntu4.5 with Suhosin-Patch 0.9.6.2 / Postgres-8.4 / Symfony 1.4.1


Attachments: Zip Archive bugreport.zip     File bug_report_create_postgresql.sql     File schema.yml    

 Description   

In the ERD/schema that I have set up, a couple levels down in hierarchal order, a table has 3 composite foreign keys, and one sequence of its own. That sequence does not get get set into the 'Table->sequence variable'. That means when the file 'UnitOfWork' executes the function '_assignSequence()', it finds no sequence name, and skips the assignment of the sequence value.

This of course blows up my inserts.

I have included the following documentation:

A/ An installation and further description README.tx file.
B/ SQL script to generate a anonymous version of my ERD - I.E. the table names and column names have been changed to protect the guilty (and proprietary)
C/ A fixture file to load some data.
D/ A *.png file showing a graphical view of the ERD.
E/ The generated schema.yml file from ./symfony doctrine:build-schema
F/ A modifiled (has certain echo statements for troubleshooting purposes) UnitOfWork.php file.
G/ A task file to run that tries to load the schema with valid values.
H/ An output file from running the Task and modified UnitOfWork.php file showing the exact point of error during insert.

Please let me know what I can do to help get this troubleshot quicly. Thx
E/



 Comments   
Comment by Dennis Gearon [ 25/Jun/10 ]

Don't know if it's related, but I ran:

./symfony doctrine:build-sql

on the database in this bug report, and none of the tables got sequences assigned to them, nor default values set coming from a sequence.

This is Postgres.

Comment by Dennis Gearon [ 25/Jun/10 ]

So much for getting around this problem easily,

I tried doing this:

$e_table=Doctrine::getTable('E');
$e_table->setOption('sequenceName', 'e_id');
$e_options=$e_table->getOptions();
var_dump($e_options);

before inserting a record into the 'E' table. The option value 'sequenceName' is in the option array and returns correctly. However, when doing an insert immediatley after the above code, I get:

'sequence name was Array' (from my troubleshooting 'echo' statements in the modified UnitOfWork.php file)

and the following errors: (you have to be using my modified UOW.php file to get the same line number there.)

Warning: Illegal offset type in /home/bugreport/lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/lib/vendor/doctrine/Doctrine/Connection/UnitOfWork.php on line 917

Warning: Illegal offset type in /home/bugreport/lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/lib/vendor/doctrine/Doctrine/Record.php on line 2222

Warning: Illegal offset type in /home/bugreport/lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/lib/vendor/doctrine/Doctrine/Record.php on line 2223
PREVIOUS line was processingSingleInsert

Warning: Invalid argument supplied for foreach() in /home/bugreport/lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/lib/vendor/doctrine/Doctrine/Record.php on line 1151

So I am wondering, does the public function table->setOption(); even work, correclty that is?

Comment by Dennis Gearon [ 25/Jun/10 ]

If I instead just retrieve the next val of the sequence manually, change 'id' column manually, then it works. But It then fails on the insert into the 'J' table.

Apparently, Doctrine does not like composite primary foreign keys with a sequence also part of the foreign key. I wonder if my file would work on the Oracle version?

Comment by Dennis Gearon [ 25/Jun/10 ]

Showing simpler version of ERD/Schema converting Primary Foreign Keys to Foreign keys. The then required unique index on the former Primary Foreign Keys has not yet been coded. Just create it on all the keys in tables E and J, that were listed as primary in the first version in the zip file

Comment by Dennis Gearon [ 25/Jun/10 ]

See last comment, but the short answer is . . . at this date, 2010-06-25, even Doctrine 2.0-DBAL can't do what I'm trying to get verson 1.2.1 to do.

So I got around it by converting primary foreign keys to foreign keys and then putting a unique index on the formerly primary keys.

However, the child of the table treated that way, E(parent), J(child), now only has one foreign key, for E. To get all the ancestors, I will have to do subselects and joins. Oh well.





[DC-754] When using a dot inside a string doctrine throws an exception because it believes what comes before the dot is a class name Created: 20/Jun/10  Updated: 17/Apr/14  Resolved: 01/Sep/10

Status: Resolved
Project: Doctrine 1
Component/s: Query
Affects Version/s: 1.2.2
Fix Version/s: None

Type: Bug Priority: Major
Reporter: will ferrer Assignee: Guilherme Blanco
Resolution: Fixed Votes: 0
Labels: None
Environment:

XP Xamp


Attachments: Text File 2010-06-21_Doctrine_1.2_SVN.patch     Text File DC_754_fix.patch    

 Description   

Hi Jon

I ran into and patched another bug in Doctrine 1.2.2 (The menu in jija shows 1.2.3 as released but I am not seeing it in SVN or on the web site so I can't test the bug in that version).

When you include a dot in a string in your select statement the function getExpressionOwner in Doctrine_Query fires and considers that the text appearing before the dot in the string is the name of a class. There for it attempts to extract a short alias out of it.

I made a test case to illustrate this:

public function testQuoteEncapedDots()
    {
        $q = new Doctrine_Query();
        $q->select("'testing.dots.inquotes' as string, u.name")->from('User u');

        $this->assertEqual($q->getSqlQuery(), "SELECT e.id AS e__id, e.name AS e__name, 'testing.dots.inquotes' AS e__0 FROM entity e WHERE (e.type = 0)");
    }

It throws the following exception.

Unexpected Doctrine_Query_Exception thrown in [Doctrine_Query_ShortAliases_TestC
ase] with message [Couldn't get short alias for testing] in C:\htdocs\php_librar
y\Doctrine_1.2_SVN\lib\Doctrine\Query\Abstract.php on line 679

Trace
-------------
#0 C:\htdocs\php_library\Doctrine_1.2_SVN\lib\Doctrine\Query.php(641): Doctrine_
Query_Abstract->getSqlTableAlias('testing')
#1 C:\htdocs\php_library\Doctrine_1.2_SVN\lib\Doctrine\Query\Select.php(37): Doc
trine_Query->parseSelect(''testing.dots.i...')
#2 C:\htdocs\php_library\Doctrine_1.2_SVN\lib\Doctrine\Query\Abstract.php(2083):
 Doctrine_Query_Select->parse(''testing.dots.i...')
#3 C:\htdocs\php_library\Doctrine_1.2_SVN\lib\Doctrine\Query.php(1168): Doctrine
_Query_Abstract->_processDqlQueryPart('select', Array)
#4 C:\htdocs\php_library\Doctrine_1.2_SVN\lib\Doctrine\Query.php(1134): Doctrine
_Query->buildSqlQuery(true)
#5 C:\htdocs\php_library\Doctrine_1.2_SVN\tests\Query\ShortAliasesTestCase.php(3
0): Doctrine_Query->getSqlQuery()
#6 C:\htdocs\php_library\Doctrine_1.2_SVN\tests\DoctrineTest\UnitTestCase.php(15
8): Doctrine_Query_ShortAliases_TestCase->testQuoteEncapedDots()
#7 C:\htdocs\php_library\Doctrine_1.2_SVN\tests\DoctrineTest\GroupTest.php(75):
UnitTestCase->run()
#8 C:\htdocs\php_library\Doctrine_1.2_SVN\tests\DoctrineTest.php(183): GroupTest
->run(Object(DoctrineTest_Reporter_Cli), Array)
#9 C:\htdocs\php_library\Doctrine_1.2_SVN\tests\run.php(320): DoctrineTest->run(
)
#10 {main}

I patched the bug by doing a preg_replace where all quote encaped strings are removed before looking for the short alias. The getExpressionOwner function now reads as follows in my code:

public function getExpressionOwner($expr)
    {
        if (strtoupper(substr(trim($expr, '( '), 0, 6)) !== 'SELECT') {
			$expr = preg_replace('/([\\]*[\'\"])[^\1]*\1/', '', $expr);
            preg_match_all("/[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*[\.[a-z0-9]+]*/i", $expr, $matches);

            $match = current($matches);

            if (isset($match[0])) {
                $terms = explode('.', $match[0]);

                return $terms[0];
            }
        }
        return $this->getRootAlias();

    }

I am including the patch with this post but I think my version of the code base is starting diverge as I also have a few other things added to mine (as discussed in: DC-701)

Hope you are well.

Will Ferrer



 Comments   
Comment by will ferrer [ 21/Jun/10 ]

Improved patch to have a more rigorous test case, refined the regex added, and put in a comment referencing this bug.

Comment by Pablo Mateos [ 29/Jun/10 ]

Hi, we're having an issue which is related to this post and I'd like to share it with you so maybe it's posible to solve both.

The situation appears when you need to look for some data that includes a "." inside a string SQL comparison with LIKE operator, here you have an example:

$this->pager = new sfDoctrinePager('Recurso', sfConfig::get('app_res_pag'));
$this->pager->getQuery()
->select('r.*,
c.alias as cliente_alias,
c.nombre as cliente_nombre,
o.alias as origen_alias,
o.nombre as origen_nombre,
GROUP_CONCAT(DISTINCT CONCAT_WS(",", cat.id, e.nombre) ORDER BY cat.id ASC) as elemento_nombre,
cv.hash as cv_hash')
->from('Recurso r')
->innerJoin('r.Cliente c')
->innerJoin('r.Origen o')
->leftJoin('r.RecursoCategoriaItem rci')
->leftJoin('rci.CategoriaItem ci')
->leftJoin('ci.Categoria cat')
->leftJoin('ci.Elemento e')
->leftJoin('ci.Nivel n')
->leftJoin('r.RecursoCv rcv')
->leftJoin('rcv.Cv cv')
->leftJoin('cv.Idioma i')
->leftJoin('r.RecursoComentario rc')
->leftJoin('rc.Comentario comm')
->where('r.apellido LIKE "%.net%" OR r.nombre LIKE "%.net%" OR r.numero_doc LIKE "%.net%" OR r.mail LIKE "%.net%" OR o.nombre LIKE "%.net%" OR cat.nombre LIKE "%.net%" OR e.nombre LIKE "%.net%" OR n.nombre LIKE "%.net%" OR c.nombre LIKE "%.net%" OR c.alias LIKE "%.net%" OR r.contenido LIKE "%.net%"')
->groupBy('r.id')
>orderBy('r.' . $orderBy . ' ' . $this>order);
$this->pager->setPage($page);
$this->pager->init();

Look at the "WHERE" line, there is a LIKE ".net".

So when we execute this code, Doctrine tries to parse the "." in the ".net" like an SQL alias and of course it fails:

ERROR:

500 | Internal Server Error | Doctrine_Exception
Couldn't find class "%

Do you know some other why to solve this situation ?. In case you need further information about this, just ask me.

Thank you in advance.

Pablo Mateos.

Comment by will ferrer [ 29/Jun/10 ]

Hi Pablo

I think I found a work around for you – I tested this bug out to see if I could recreate and patch it and found that my own system didn't generate the bug. Turns out the difference is my code encapes the strings that go in the query with single quotes and this seems to solve the problem.

Try the same query you posted above but replace the "%.net%" with '%.net%'.

I think that should solve it.

Hope that helps.

Will

Comment by Pablo Mateos [ 30/Jun/10 ]

Hi Will, I tried what you said and it worked !!!!!! . It's great to have so easy solutions for debugging, isn't ?.

So, anyway do you think this issue should be considered as a bug ?.

I really appreciate your help.

Pablo.

Comment by will ferrer [ 30/Jun/10 ]

Hi Pablo

I am glad that worked .

I do think this is still a bug – I there are some problems with double quotes in dql in general I believe. Then again there may be something in the docs about this being a restriction that I overlooked (not sure).

But since its solvable with using single quotes I would recommend putting it as a low priority bug – which may mean it won't actually get fixed any time soon. Whether it gets fixed or not though its good to have these kinds of bugs in the system so that there is a record of everything that needs fixing and so other users can find work arounds to there problems by looking through the archive.

Hope you are well.

Will

Comment by Jonathan H. Wage [ 01/Sep/10 ]

Thanks for the issue and patches!

Fixed here http://github.com/doctrine/doctrine1/commit/2ad78e62e360133efc04bf6897bf679c7f3d833b

Comment by will ferrer [ 01/Sep/10 ]

individual patch for this issue with out other features included

Comment by smash company [ 18/Nov/10 ]

I'm curious if this could be the same issue that causes doctrine:build-schema to give either the 'Missing class name' or 'Couldn't find class Content ' error? I am going a little crazy trying to figure out why doctrine:build-schema doesn't work for me. Details here:

http://www.symfonyexperts.com/question/show/id/156

Comment by will ferrer [ 18/Nov/10 ]

Hi Smash

I did a quick glance at your issue. I have never encountered this error my self and I don't think it is related to my issue.

When I have a bug like that the first place I start looking is where the exception is being thrown.

For you its in the Doctrine_Import_Builder class on either line 949 or 995

You could start by putting some debugging to see what the $definition var is. That may offer some insights. Then if that doesn't clarify whats wrong with your schema you could start back tracking out of the method to see where else things could be screwing up .

Hope that helps at least alil bit.

Will Ferrer





[DC-726] 0 == string bug Created: 10/Jun/10  Updated: 19/Sep/10  Resolved: 10/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: None
Affects Version/s: 1.2.2
Fix Version/s: None

Type: Bug Priority: Major
Reporter: Jason Brumwell Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

win xp
xampp
php 5.3.1



 Description   

Using a simular setup to the one located in the documentation:

Docmentation
$data = array(
  'name' => 'John',
  'age' => '25',
  'Emails' => array(
    array('address' => 'john@mail.com'),
    array('address' => 'john@work.com')
));

fromArray would get called on the m/m relationship under the "Emails" value in Doctrine/Record.php

Doctrine/Record.php Line 1977
if (is_array($value)) {
                    if (isset($value[0]) && ! is_array($value[0])) {
                        $this->unlink($key, array(), false);
                        $this->link($key, $value, false);
                    } else {                        
                        $this->$key->fromArray($value, $deep);
                    }
                }

This will pass:

array(
    0 => array('address' => 'john@mail.com'),
    0 => array('address' => 'john@work.com')
)

When checking for the _identifier as the key it will receive 0 == '_identifier' which will evaluate as true. The fix would be to cast the key to a string in the evaluation:

Doctrine/Record.php Line 1961
foreach ($array as $key => $value) {
            if ((string) $key == '_identifier') {


 Comments   
Comment by Jason Brumwell [ 10/Jun/10 ]

N/M this was an issue where a o/m was declared as o/o relationship.

Comment by Dennis Gearon [ 19/Sep/10 ]

Is the casting a fix, was it checked in?

I just happened to be looking at the 'fromArray()' code in symfony 1.4.1 Not sure how to find Doctrine version in Syfony projects.

THIS version of Doctrine still does not have the '(string) fix.

It's not a problem for me, just wondering. Might be time to update my Symfony/Doctrine package.





[DC-721] [PATCH] GoogleI18n fix exception message wrong variable Created: 08/Jun/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Extensions
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Major
Reporter: Gordon Franke Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None

Attachments: Text File GoogleI18n_exception.patch    




[DC-718] Doctrine_Template_Taggable::getTagIds is not case insensitive where has MySQL unique index is, causes duplicate key errors Created: 07/Jun/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Extensions
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Major
Reporter: Jake Spencer Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

Debian Lenny, MySQL 5.0, PHP 5.2.6, PHP Suhosin 0.9.27


Attachments: Text File dc-718.patch     File Taggable.php    

 Description   

The getTagIds will not match tags in a case insensitive manner however MySQL unique indexes are case insensitive.

getTagIds('Foo') will create a new tag even if the tag 'foo' exists in the database, when trying to insert the tag 'Foo' you will get a MySQL duplicate key error.

I have written a new version of the function that handles this, it will not modify the case of new tags only match in a case insensitive manner for current tags (I know that is not 100% clear, wording sucks sorry about that). Oh and I think this function might be every so slightly faster but not sure on that one

public function getTagIds($tags)
{
if (is_string($tags)) {
$tagClass = $this->_options['tagClass'];
$tagField = $this->_options['tagField'];

$sep = strpos($tags, ',') !== false ? ',':' ';
$tags = explode($sep, $tags);
$tagNames = array();
foreach(array_keys($tags) as $key) {
$tagName = trim($tags[$key]);
if ($tagName)

{ $tagNames[strtolower($tagName)] = $tagName; }

}

$tagsList = array();
if (count($tagNames)) {
$existingTags = Doctrine_Query::create()
->from($tagClass.' t INDEXBY t.'.$tagField)
->whereIn('t.'.$tagField, array_keys($tagNames))
->fetchArray();

foreach(array_keys($existingTags) as $tag)

{ $tagsList[] = $existingTags[$tag]['id']; unset($tagNames[strtolower($tag)]); }

if (count($tagNames)) {
foreach($tagNames as $tagName)

{ $tag = new $tagClass(); $tag->$tagField = $tagName; $tag->save(); $tagsList[] = $tag['id']; }

}
}

return $tagsList;
} else if (is_array($tags)) {
if (is_numeric(current($tags)))

{ return $tags; }

else

{ return $this->getTagIds(implode(', ', $tags)); }

} else if ($tags instanceof Doctrine_Collection)

{ return $tags->getPrimaryKeys(); }

else

{ throw new Doctrine_Exception('Invalid $tags data provided. Must be a string of tags, an array of tag ids, or a Doctrine_Collection of tag records.'); }

}

Jake



 Comments   
Comment by Jake Spencer [ 07/Jun/10 ]

Attached code as the jira does not format whitespace nicely

Comment by Jonathan H. Wage [ 08/Jun/10 ]

Can you provide a patch instead of the whole file? Thanks, Jon

Comment by Jonathan H. Wage [ 08/Jun/10 ]

Thanks. Patch has been applied!





[DC-716] Fatal error: Class 'Agencia' not found in /var/www/tb.com.ar/funciones/busq.php on line 165 Created: 04/Jun/10  Updated: 06/Jun/10  Resolved: 06/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: None
Affects Version/s: 1.2.2
Fix Version/s: None

Type: Bug Priority: Major
Reporter: demolit Assignee: Jonathan H. Wage
Resolution: Invalid Votes: 0
Labels: None
Environment:

ubuntu 10.04, Mysql 5.1.41-3ubuntu12.1 UTF-8 Unicode (utf8) , Apache/2.2.14 (Ubuntu), php 5, Doctrine 1.2.2



 Description   

Hi,

I have the following issue:

I developed the entire project under Windows Xp, IIS 5, php 5 and Mysql 5; everything worked fine until I copied the project into a Ubuntu environment as the production system and the problems began...

"Fatal error: Class 'Agencia' not found in /funciones/busq.php on line 165"

For a while I have tried to solve this issue but without luck.

The files structure is the following:
Doctrine under: /usr/share/php/Doctrine
php under: /usr/share/php
The site: /var/www/sitename
Doctrine Models under: '/var/www/sitename/models' and '/var/www/sitename/models/generated'

The database Table name is 'agencia', the model class is named 'Agencia.php' and the base class is named 'BaseAgencia.php'. The name convention is Ok?

The code is the following:

Dbc.Class.php as the bootstrap.php. I use this code to open the connection:

require_once('/usr/share/php/Doctrine/lib/Doctrine.php');
Class Dbc{
private function conectar()

{ spl_autoload_register(array('Doctrine', 'autoload')); spl_autoload_register(array('Doctrine', 'modelsAutoload')); Doctrine_Manager::getInstance()->setAttribute(Doctrine_Core::ATTR_AUTOLOAD_TABLE_CLASSES, true); Doctrine_Manager::getInstance()->setAttribute(Doctrine_Core::ATTR_VALIDATE, Doctrine_Core::VALIDATE_ALL); Doctrine_Manager::getInstance()->setAttribute(Doctrine_Core::ATTR_EXPORT, Doctrine_Core::EXPORT_ALL); Doctrine_Manager::getInstance()->setAttribute(Doctrine::ATTR_QUERY_LIMIT, Doctrine::LIMIT_ROWS); Doctrine_Manager::getInstance()->setAttribute(Doctrine_Core::ATTR_MODEL_LOADING, Doctrine_Core::MODEL_LOADING_CONSERVATIVE); self::$_instance = new PDO("mysql:host=" . $this->servidor . ";dbname=" . $this->base_datos ."", $this->usuario , $this->password); self::$_instance-> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pathmodels=array(); array_push($pathmodels,"../models/generated"); array_push($pathmodels,"../models"); Doctrine::loadModels($pathmodels); self::$_conn = Doctrine_Manager::getInstance()->openConnection(self::$_instance); }

Etc...
}

I use this file and code to call the function that create the instance of the connection:

require 'Dbc.Class.php';
require 'conf.class.php';
$bd=Dbc::getInstance();

The model Agencia.php has this code:

class Agencia extends BaseAgencia {
etc...
}

The BaseAgencia.php:

abstract class BaseAgencia extends Doctrine_Record {
public function setTableDefinition()

{ $this->setTableName('agencia'); $this->hasColumn('idagencia', 'integer', 8, array( 'type' => 'integer', 'length' => 8, 'fixed' => false, 'unsigned' => true, 'primary' => true, 'autoincrement' => true, )); $this->hasColumn('identificacion', 'string', 100, array( 'type' => 'string', 'length' => 100, 'fixed' => false, 'unsigned' => false, 'primary' => false, 'notnull' => true, 'autoincrement' => false, )); $this->hasColumn('nombre', 'string', 150, array( 'type' => 'string', 'length' => 150, 'fixed' => false, 'unsigned' => false, 'primary' => false, 'notnull' => true, 'autoincrement' => false, )); $this->hasColumn('mail', 'string', 150, array( 'type' => 'string', 'length' => 150, 'fixed' => false, 'unsigned' => false, 'primary' => false, 'notnull' => true, 'autoincrement' => false, )); $this->hasColumn('telefono', 'string', 15, array( 'type' => 'string', 'length' => 15, 'fixed' => false, 'unsigned' => false, 'primary' => false, 'notnull' => false, 'autoincrement' => false, )); $this->hasColumn('contacto', 'string', 45, array( 'type' => 'string', 'length' => 45, 'fixed' => false, 'unsigned' => false, 'primary' => false, 'notnull' => false, 'autoincrement' => false, )); $this->hasColumn('web', 'string', 150, array( 'type' => 'string', 'length' => 150, 'fixed' => false, 'unsigned' => false, 'primary' => false, 'notnull' => false, 'autoincrement' => false, )); $this->hasColumn('activo', 'integer', 1, array( 'type' => 'integer', 'length' => 1, 'fixed' => false, 'unsigned' => false, 'primary' => false, 'default' => '0', 'notnull' => true, 'autoincrement' => false, )); $this->hasColumn('localidad_idlocalidad', 'integer', 4, array( 'type' => 'integer', 'length' => 4, 'fixed' => false, 'unsigned' => true, 'primary' => false, 'notnull' => true, 'autoincrement' => false, )); }

public function setUp()

{ parent::setUp(); $this->hasOne('Localidad', array( 'local' => 'localidad_idlocalidad', 'foreign' => 'idlocalidad')); $this->hasMany('AgenciaHasOperador', array( 'local' => 'idagencia', 'foreign' => 'agencia_idagencia')); $this->hasMany('Usuario', array( 'local' => 'idagencia', 'foreign' => 'agencia_idagencia')); }

}

The fatal error happens when I call the 'Agencia' class in busq.phpm, this is the line:

$a = new Agencia();

Can anyone help me?

Best regards,
Fernando.



 Comments   
Comment by Jonathan H. Wage [ 06/Jun/10 ]

Hi, the Jira issue tracker is for reporting bugs only. You can use the user mailing list to ask questions.

http://www.doctrine-project.org/community

Thanks, Jon





[DC-714] Fatal error(Call to a member function quoteIdentifier() on a non-object), on batchUpdateIndex(), file search.php on line 246 Created: 04/Jun/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Searchable
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Alex Kucherenko Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

Windows XP, php 5.2.4, Apache 2.0



 Description   

I am creating a search in such a way that's:
$tenders = Doctrine::getTable('TableTenders');
$tenders->batchUpdateIndex();

after excute i have fatal error: Fatal error: Call to a member function quoteIdentifier() on a non-object in E:\home\tender.loc\www\engine\class\Doctrine\Doctrine\Search.php on line 246

When i am opened Search.php on 246 line, i saw : $conn->quoteIdentifier($this->_table->getTableName())

var_dump($conn) return NULL

I climbed up the code and saw the error: $conn = $this->_options['connection']; (line 228)
i change this on $conn = $this->_options['table']->getConnection();

now works fine

Sory for my english



 Comments   
Comment by Jonathan H. Wage [ 08/Jun/10 ]

Thanks for the issue and patch!





[DC-715] Doctrine_Tree_NestedSet::fetchRoots does not return roots with no branches. Created: 04/Jun/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Behaviors, Nested Set
Affects Version/s: 1.2.2, 1.2.3
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Jacek Krysztofik Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

any


Attachments: Text File Doctrine_Tree_NestedSet.patch    

 Description   

Doctrine_Tree_NestedSet::fetchRoots does not return roots with no branches.



 Comments   
Comment by Jonathan H. Wage [ 08/Jun/10 ]

This patch breaks our test suite:

Doctrine_PessimisticLocking_TestCase............................................passed
Doctrine_NestedSet_SingleRoot_TestCase..........................................failed


Unexpected Doctrine_Query_Exception thrown in [Doctrine_NestedSet_SingleRoot_TestCase] with message [Unknown column ] in /Users/jwage/Sites/doctrine12/lib/Doctrine/Query.php on line 729

Trace
-------------

#0 /Users/jwage/Sites/doctrine12/lib/Doctrine/Query/Where.php(93): Doctrine_Query->parseClause('base.')
#1 /Users/jwage/Sites/doctrine12/lib/Doctrine/Query/Where.php(81): Doctrine_Query_Where->_buildSql('base.', 'IS', 'NULL')
#2 /Users/jwage/Sites/doctrine12/lib/Doctrine/Query/Condition.php(92): Doctrine_Query_Where->load('base. IS NULL')
#3 /Users/jwage/Sites/doctrine12/lib/Doctrine/Query/Abstract.php(2077): Doctrine_Query_Condition->parse('base. IS NULL')
#4 /Users/jwage/Sites/doctrine12/lib/Doctrine/Query.php(1160): Doctrine_Query_Abstract->_processDqlQueryPart('where', Array)
#5 /Users/jwage/Sites/doctrine12/lib/Doctrine/Query.php(1126): Doctrine_Query->buildSqlQuery(true)
#6 /Users/jwage/Sites/doctrine12/lib/Doctrine/Query/Abstract.php(945): Doctrine_Query->getSqlQuery(Array)
#7 /Users/jwage/Sites/doctrine12/lib/Doctrine/Query/Abstract.php(1026): Doctrine_Query_Abstract->_execute(Array)
#8 /Users/jwage/Sites/doctrine12/lib/Doctrine/Tree/NestedSet.php(132): Doctrine_Query_Abstract->execute()
#9 /Users/jwage/Sites/doctrine12/tests/NestedSet/SingleRootTestCase.php(60): Doctrine_Tree_NestedSet->fetchRoot()
#10 /Users/jwage/Sites/doctrine12/tests/DoctrineTest/UnitTestCase.php(158): Doctrine_NestedSet_SingleRoot_TestCase->testLftRgtValues()
#11 /Users/jwage/Sites/doctrine12/tests/DoctrineTest/GroupTest.php(75): UnitTestCase->run()
#12 /Users/jwage/Sites/doctrine12/tests/DoctrineTest.php(183): GroupTest->run(Object(DoctrineTest_Reporter_Cli), '')
#13 /Users/jwage/Sites/doctrine12/tests/run.php(320): DoctrineTest->run()
#14 {main}



Fatal error: Call to a member function getNode() on a non-object in /Users/jwage/Sites/doctrine12/tests/NestedSet/MultiRootTestCase.php on line 151

Call Stack:
    0.0047     521504   1. {main}() /Users/jwage/Sites/doctrine12/tests/run.php:0
    0.7401   50855368   2. DoctrineTest->run() /Users/jwage/Sites/doctrine12/tests/run.php:320
    0.7410   50872656   3. GroupTest->run() /Users/jwage/Sites/doctrine12/tests/DoctrineTest.php:183
   55.2363  158425064   4. UnitTestCase->run() /Users/jwage/Sites/doctrine12/tests/DoctrineTest/GroupTest.php:75
   55.3227  158747088   5. Doctrine_NestedSet_MultiRoot_TestCase->testSaveMultipleRootsWithChildren() /Users/jwage/Sites/doctrine12/tests/DoctrineTest/UnitTestCase.php:158


Comment by Jacek Krysztofik [ 08/Jun/10 ]

Works for me (doctrine1-git):
[code]
...
Doctrine_NestedSet_SingleRoot_TestCase..........................................passed
Doctrine_NestedSet_MultiRoot_TestCase...........................................passed
Doctrine_NestedSet_TimestampableMultiRoot_TestCase..............................passed
Doctrine_NestedSet_Hydration_TestCase...........................................passed

Tested: 440 test cases.
Successes: 0 passes.
Failures: 0 fails.
Number of new Failures: 0
Number of fixed Failures: 0
[/code]

Comment by Jonathan H. Wage [ 08/Jun/10 ]

I apply your patch cleanly to a fresh checkout from SVN and it fails for me. I tried on my laptop and the CI server. Hmm

Comment by Jacek Krysztofik [ 08/Jun/10 ]

Your failure log shows

#3 /Users/jwage/Sites/doctrine12/lib/Doctrine/Query/Abstract.php(2077): Doctrine_Query_Condition->parse('base. IS NULL')

which means the $this->getAttribute('rootColumnName') returns nothing. I think the problem is not in the patch but I cannot reproduce it, so I can't help.

Comment by Jonathan H. Wage [ 08/Jun/10 ]

rootColumnName can be empty in some cases, no? when you don't have multiple roots.





[DC-712] [pgsql] missing quoteIdentifier() on "alter table" queries Created: 02/Jun/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Import/Export
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Francesco Montefoschi Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

PostgreSQL



 Description   

In Doctrine_Export_Pgsql the table name of query like "ALTER TABLE mytable ..." are not quoted even if ATTR_QUOTE_IDENTIFIER is enabled.
This create failing queries, for example if the table to alter is called 'user'.

Please note in Mysql exporter the table name of ALTER TABLE queries is correctly quoted.



 Comments   
Comment by Francesco Montefoschi [ 02/Jun/10 ]

Proposed test and fix on github: http://github.com/fmntf/doctrine1/tree/DC-712

Comment by Jonathan H. Wage [ 08/Jun/10 ]

Thanks again for the issue and patch!





[DC-711] GoogleI18N fix empty and only call i one translation was changed Created: 01/Jun/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Extensions
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Major
Reporter: Gordon Franke Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None

Attachments: Text File GoogleI18N_fix_empty_reduce_calls_add_skipEmpty.patch    

 Description   

Fix Bugs:

  • skip empty query call to google api
  • skip if no translation was updated

Add Feature:

  • skipEmpty option to don't override languages when you change some language to empty





[DC-713] [pgsql] importer does not fetch varchar max length Created: 02/Jun/10  Updated: 17/Apr/14  Resolved: 25/Aug/10

Status: Resolved
Project: Doctrine 1
Component/s: Import/Export
Affects Version/s: 1.2.2
Fix Version/s: None

Type: Bug Priority: Major
Reporter: Francesco Montefoschi Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

PostgreSql


Attachments: PNG File pgsql.png    

 Description   

The importer doe not look for the max length of string columns.

For example, the task generate-models-db is generating definitions like
$this->hasColumn('code', 'string', null, array(/**/));

instead of
$this->hasColumn('code', 'string', 10, array(/**/));



 Comments   
Comment by Francesco Montefoschi [ 02/Jun/10 ]

I created a patch on GitHub: http://github.com/fmntf/doctrine1/tree/DC-713.

Reading from `information_schema` we can also get the string length.
I tried to `generate-models-db` with original 1.2.2 code and patched code: it seems there are no regressions.

Comment by Francesco Montefoschi [ 17/Jun/10 ]

I don't know what happened, but the `listTableColumns` query is

SELECT fields, t.typtype AS typtype FROM information_schema.columns WHERE ..

and the t relation is not specified. By the way, commenting out the typtype, the string length is no more detected.
I think there is something wrong around line 170 - the "type" column should contain "varchar", not "character varying".

Comment by Francesco Montefoschi [ 17/Jun/10 ]

I attached the query output, commenting the typtype column.

Comment by Ricardo Simoes [ 05/Aug/10 ]

My workaround for `listTableColumns` query:
Doctrine/Import/Pgsql.php ln 101-102:

'listTableColumns' => "SELECT
ordinal_position as attnum,
column_name as field,
udt_name as type,
data_type as complete_type,
--t.typtype AS typtype,
(SELECT t.typtype FROM pg_type t WHERE t.typname = udt_name) AS typtype,
is_nullable as isnotnull,
column_default as default,
(
SELECT 't'
FROM pg_index, pg_attribute a, pg_class c, pg_type t
WHERE c.relname = table_name AND a.attname = column_name
AND a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid
AND c.oid = pg_index.indrelid AND a.attnum = ANY (pg_index.indkey)
AND pg_index.indisprimary = 't'
AND format_type(a.atttypid, a.atttypmod) NOT LIKE 'information_schema%%'
) as pri,
character_maximum_length as length
FROM information_schema.COLUMNS
WHERE table_name = %s
ORDER BY ordinal_position", FROM pg_type t WHERE t.typname = udt_name) AS typtype,

Comment by Jonathan H. Wage [ 24/Aug/10 ]

Why was this re-opened? Your comment is not clear to me. Can you provide a test case or a proper patch so we can see the changes.

Comment by Francesco Montefoschi [ 24/Aug/10 ]

Sorry if the comment was not clear.

I proposed you a patch, I tested it before sending it back to you.
You applied something else to Doctrine source code that is not working. Try to copy and paste the query in http://svn.doctrine-project.org/tags/1.2.3/lib/Doctrine/Import/Pgsql.php (listTableColumns) to pgadmin and execute it. The postgres server will refuse to execute that query.
The error is due to a call to a "t" relation, which is not declared in the FROM (the query is something like: "SELECT a.field, ..., t.field FROM a").

The error is:
-----------------------------------------------------
ERROR: missing FROM-clause entry for table "t"
LINE 6: ... t.typtype ...
^
-----------------------------------------------------

Comment by Jonathan H. Wage [ 24/Aug/10 ]

It had looked like this patch: http://github.com/fmntf/doctrine1/commit/5a1123f78974fef7203500b082df754e1c69cbf6

was already applied. I did not apply any patch today. I am confused now, which patch is the right one?

Comment by Francesco Montefoschi [ 25/Aug/10 ]

The patch was not applied correctly.

In 1.2.3 importer the query has 8 direct fields in SELECT + 1 field as subquery
My patch has 7 direct fields + 1 as subquery.

Compare also the subquery. Mine is one line shorter.

Read the two queries line by line. They are different, and the one in 1.2.3 cannot be executed

Comment by Jonathan H. Wage [ 25/Aug/10 ]

Can you generate a patch and paste it in a code block?

Comment by Jonathan H. Wage [ 25/Aug/10 ]

Hopefully this fixes it for good.

http://trac.doctrine-project.org/changeset/7689

Comment by Francesco Montefoschi [ 27/Aug/10 ]

It seems to be fine!

Comment by Miha Vrhovnik [ 13/Sep/10 ]

Ok.. this is complete mess, somebody with permissions should reopen it.
This one works for Francesco as he is running with notices disabled.
if ttype is removed or commented out , then you get a notices that ttype key doesn't exist.
and it seems that ttype is required for enums to work.

in one of the comments Ricardo suggested replacing ...
t.typtype AS typtype with
(SELECT t.typtype FROM pg_type t WHERE t.typname = udt_name) AS typtype,

adding this get's rid of notices, but i don't know what happens for other things mentioned within comments of if this is a correct fix... The one who provided support for enums should take a look

I think Francesco should run import with notices enabled and then add the upper sub-query to get rid f them.. then it should also test if the rest of the import still works correctly.

Comment by Francesco Montefoschi [ 13/Sep/10 ]

My apologies, I developed the patch on a non development machine, so I used the default PHP configuration.





[DC-705] synchronizeWithArray does not properly set foreign key validation Created: 25/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Relations, Validators
Affects Version/s: 1.2.2, 1.2.3
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Jeff Chu Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None

Attachments: Text File doctrine-syncarray-test.patch     Text File doctrine-syncarray.patch    

 Description   

I've discovered a variation of the problem tested in tests/Validator/ForeignKeysTestCase.php. This happens when you synchronizeWithArray and a foreign relation is set - isValid will trigger the isnull validator.

This is probably better explained through two new test cases. I've included them below. The first test case passes. However, the second test case (testSynchronizedForeignKeyIsValidIfForeignRelationIsSet) fails.

tests/Validator/ForeignKeysTestCase.php - also attached as patch
// Place in tests/Validator/ForeignKeysTestCase.php
    public function testSynchronizedForeignKeyIsValidIfLocalRelationIsSet()
    {
        $person = new TestPerson();
        $person->synchronizeWithArray(array('Addresses' => array(array())));

        $address = $person->Addresses[0];
        $table = $address->getTable();
        
        $errors = $table->validateField('person_id', $address->person_id, $address);
        $this->assertEqual(0, $errors->count());
    }

    public function testSynchronizedForeignKeyIsValidIfForeignRelationIsSet()
    {
        $address = new TestAddress();
        $address->synchronizeWithArray(array('Person' => array()));

        $table = $address->getTable();
        $errors = $table->validateField('person_id', $address->person_id, $address);
        $this->assertEqual(0, $errors->count());
    }

I've discovered a workaround, if you reassign the value it will work.

$address->synchronizeWithArray(array('Person' => array()));
$address->Person = $address->Person;

A quick and likely terrible (or wrong?) fix is to have the synchronizeWithArray function do it for you. I've attached a patch does just that.



 Comments   
Comment by Jeff Chu [ 25/May/10 ]

Just as a note - I was looking at this further and noticed that doing this also fails:

public function testGetForeignKeyIsValidIfForeignRelationIsSet()
{
    $address = new TestAddress();
    $address->Person;

    $table = $address->getTable();
    $errors = $table->validateField('person_id', $address->person_id, $address);
    $this->assertEqual(0, $errors->count());
}

But oddly enough, the following works:

$address->Person;
$address->Person = $address->Person;

I think this has to do with the inconsistencies in whether get should create a real relation or fake it until it's actually set with a setter. From what I can tell, this all stems from the support for the following behavior:

$address = new Address();
$address->Person->first_name = "Bob";

This behavior is taken advantage of from within synchronizeWithArray:

$this->$key->synchronizeWithArray($value);

However, because it doesn't create a real relation this way - the original issue comes up. Updating record's get to create a real relation requires us to update Doctrine_Record's _get to use coreSetRelated (instead of directly modifying $this->_references). However, doing this will conflict directly with test Ticket 1072.

What is the intended behavior of all of this?





[DC-703] [PATCH] Doctrine_Connection_Oracle unquoted query aliases in _createLimitSubquery Created: 25/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Connection
Affects Version/s: 1.2.0-RC1, 1.2.0, 1.2.1, 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Miloslav "adrive" Kmet Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None

Attachments: Text File Doctrine_Connection_Oracle.patch    

 Description   

When using identifier quoting with oracle driver, the _createLimitSubquery is giving into the select statement unquoted subquery aliases. That leads to Oracle errors.

Simple patch:
{{
diff --git a/lib/Doctrine/Connection/Oracle.php b/lib/Doctrine/Connection/Oracle.php
index db538fb..6e5f629 100644
— a/lib/Doctrine/Connection/Oracle.php
+++ b/lib/Doctrine/Connection/Oracle.php
@@ -108,8 +108,8 @@ class Doctrine_Connection_Oracle extends Doctrine_Connection_Common
$column = $column === null ? '*' : $this->quoteIdentifier($column);
if ($offset > 0)

{ $min = $offset + 1; - $query = 'SELECT b.'.$column.' FROM ( '. - 'SELECT a.*, ROWNUM AS doctrine_rownum FROM ( ' + $query = 'SELECT '.$this->quoteIdentifier('b').'.'.$column.' FROM ( '. + 'SELECT '.$this->quoteIdentifier('a').'.*, ROWNUM AS doctrine_rownum FROM ( ' . $query . ' ) ' . $this->quoteIdentifier('a') . ' '. ' ) ' . $this->quoteIdentifier('b') . ' '. 'WHERE doctrine_rownum BETWEEN ' . $min . ' AND ' . $max; }

}



 Comments   
Comment by Miloslav "adrive" Kmet [ 25/May/10 ]

Fixed in http://github.com/milokmet/doctrine1/tree/DC-703





[DC-702] Migration commands always want to drop all database tables Created: 25/May/10  Updated: 07/May/12  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Migrations
Affects Version/s: 1.2.2
Fix Version/s: None

Type: Bug Priority: Major
Reporter: Benjamin Arthur Lupton Assignee: Jonathan H. Wage
Resolution: Cannot Reproduce Votes: 0
Labels: None
Environment:

Zend Server 5.0.1 for OSX, PHP 5.3.2, MySQL 5.1.43



 Description   

I'm trying to get migrations going, such that I install the database, make a change to the schema.yml (something simple such as a column length change), and then have a migration generated and performed.

No matter what I try, doctrine just generates an initial add all the database tables, then after that if I try any further migration generation, they all try to drop all the database tables. I am not having much luck.

$ rm ../application/data/migrations/*

$ php ./doctrine build-all-reload
build-all-reload - Are you sure you wish to drop your databases? (y/n)
y
build-all-reload - Successfully dropped database for connection named '0'
build-all-reload - Generated models successfully from YAML schema
build-all-reload - Successfully created database for connection named '0'
build-all-reload - Created tables successfully
build-all-reload - Data was successfully loaded

$ ls ../application/data/migrations

$ php ./doctrine generate-migrations-db
generate-migrations-db - Generated migration classes successfully from database

$ ls ../application/data/migrations
1274778030_addbal_file.php 1274778038_addbal_roleanduser.php 1274778046_addburn_fileandproject.php 1274778054_addinvoicedatabackup.php 1274778062_addburnprojectindex.php
1274778031_addbal_invoice.php 1274778039_addbal_user.php 1274778047_addburn_imageandproject.php 1274778055_addusergroup.php 1274778063_addideaindex.php
1274778032_addbal_invoiceitem.php 1274778040_addbrief.php 1274778048_addburn_project.php 1274778056_addusergroupanduserfor.php 1274778064_addusergroupindex.php
1274778033_addbal_message.php 1274778041_addbriefandfile.php 1274778049_addburn_recommendation.php 1274778057_addbalfileindex.php 1274778065_addfks.php
1274778034_addbal_permission.php 1274778042_addbriefanduserfor.php 1274778050_addburn_state.php 1274778058_addbalusertaggabletag.php
1274778035_addbal_permissionandrole.php 1274778043_addbrieftype.php 1274778051_addburn_suburb.php 1274778059_addtaggabletag.php
1274778036_addbal_permissionanduser.php 1274778044_addburn_company.php 1274778052_addidea.php 1274778060_addbriefindex.php
1274778037_addbal_role.php 1274778045_addburn_department.php 1274778053_addideaandimage.php 1274778061_addburncompanyindex.php

$ ... make a change to the schema.yml file in my text editor and save, in this case change a column length from 250 to 200 ...

$ php ./doctrine generate-migrations-diff
generate-migrations-diff - Generated migration classes successfully from difference

$ ls ../application/data/migrations
1274778030_addbal_file.php 1274778038_addbal_roleanduser.php 1274778046_addburn_fileandproject.php 1274778054_addinvoicedatabackup.php 1274778062_addburnprojectindex.php
1274778031_addbal_invoice.php 1274778039_addbal_user.php 1274778047_addburn_imageandproject.php 1274778055_addusergroup.php 1274778063_addideaindex.php
1274778032_addbal_invoiceitem.php 1274778040_addbrief.php 1274778048_addburn_project.php 1274778056_addusergroupanduserfor.php 1274778064_addusergroupindex.php
1274778033_addbal_message.php 1274778041_addbriefandfile.php 1274778049_addburn_recommendation.php 1274778057_addbalfileindex.php 1274778065_addfks.php
1274778034_addbal_permission.php 1274778042_addbriefanduserfor.php 1274778050_addburn_state.php 1274778058_addbalusertaggabletag.php 1274778089_version37.php
1274778035_addbal_permissionandrole.php 1274778043_addbrieftype.php 1274778051_addburn_suburb.php 1274778059_addtaggabletag.php
1274778036_addbal_permissionanduser.php 1274778044_addburn_company.php 1274778052_addidea.php 1274778060_addbriefindex.php
1274778037_addbal_role.php 1274778045_addburn_department.php 1274778053_addideaandimage.php 1274778061_addburncompanyindex.php

$ cat ../application/data/migrations/1274778089_version37.php
<?php
/**

  • This class has been auto-generated by the Doctrine ORM Framework
    */
    class Version37 extends Doctrine_Migration_Base
    {
    public function up() { $this->dropTable('bal__file'); $this->dropTable('bal__invoice'); $this->dropTable('bal__invoice_item'); $this->dropTable('bal__message'); $this->dropTable('bal__permission'); $this->dropTable('bal__permission_and_role'); $this->dropTable('bal__permission_and_user'); $this->dropTable('bal__role'); $this->dropTable('bal__role_and_user'); $this->dropTable('bal__user'); $this->dropTable('brief'); $this->dropTable('brief_and_file'); $this->dropTable('brief_and_user_for'); $this->dropTable('brief_type'); $this->dropTable('burn__company'); $this->dropTable('burn__department'); $this->dropTable('burn__file_and_project'); $this->dropTable('burn__image_and_project'); $this->dropTable('burn__project'); $this->dropTable('burn__recommendation'); $this->dropTable('burn__state'); $this->dropTable('burn__suburb'); $this->dropTable('idea'); $this->dropTable('idea_and_image'); $this->dropTable('invoice_data_backup'); $this->dropTable('user_group'); $this->dropTable('user_group_and_user_for'); $this->dropTable('bal_file_index'); $this->dropTable('bal_user_taggable_tag'); $this->dropTable('taggable_tag'); $this->dropTable('brief_index'); $this->dropTable('burn_company_index'); $this->dropTable('burn_project_index'); $this->dropTable('burn_user_taggable_tag'); $this->dropTable('burn_user_index'); $this->dropTable('buyer_taggable_tag'); $this->dropTable('buyer_index'); $this->dropTable('company_index'); $this->dropTable('file_index'); $this->dropTable('idea_index'); $this->dropTable('project_index'); $this->dropTable('seller_taggable_tag'); $this->dropTable('seller_index'); $this->dropTable('user_taggable_tag'); $this->dropTable('user_index'); $this->dropTable('user_group_index'); }

public function down()
{
$this->createTable('bal__file', array(
'id' =>
array(
....



 Comments   
Comment by Benjamin Arthur Lupton [ 25/May/10 ]

Clarity over tables and database.

Comment by Benjamin Arthur Lupton [ 25/May/10 ]

forgot to add the part about me changing the yaml file inbetween

Comment by Jonathan H. Wage [ 08/Jun/10 ]

Hi, I cannot reproduce the issue unfortunately

Comment by SkaveRat [ 26/Nov/10 ]

I have the exact same problem.

Looking at the paths, is seems OP uses the same ZendCasts.com setup for ZendFramework/Doctrine. I think the problem lies somewhere in there. Anyone got an idea?

Comment by SkaveRat [ 26/Nov/10 ]

Okay, found the problem, And I really hope there will be a fix soon, because it one of the biggest features of doctrine:

To get autoloading in ZendFramework correct, Doctrine has to prefix all models with "Model_" (see http://www.zendcasts.com/deep-integration-between-zend-and-doctrine-1-2/2010/01/ for more information)
The problem is, that the Diff-generating script searches for i.e. the class "User" instead of "Model_User", thinks that it doesnt exist, and wants to recreate it from scratch

Comment by Thomas Ingham [ 07/May/12 ]

We're having this same issue in 1.2.3 right now. Has anyone found a workaround? It seems likely that given the age of the last comment - I'm out of luck but yolo.





[DC-698] Record::link method does not work with setting null one-to-one Created: 22/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: None
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Martin Cohen Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None


 Description   

I have an Issue model with one owner User (relation Owner). I have this code:

$issue->link( 'Owner', array( $owner_id ) );

At the time of calling this code, the $issue->Owner is null. When this is called, the link fails to assign the new owner to the relation. I've tried to hotfix it changing line around 2516 in Record.php:

$this->get($alias)->add($record);

to this:

if( $c = $this->get($alias) )

{ $c->add($record); }

else

{ $this->set( $alias, $record ); }

In this case the $this->get($alias) returns NULL, so the subsequent add($record) fails.






[DC-694] Doctrine_Record::replace() discards Doctrine_Expression values, uses empty value instead Created: 20/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Record
Affects Version/s: 1.2.1, 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Exception e Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

php 5.3.0 @ Windows XP SP3
MySQL 5.1.40 @ Windows XP SP3



 Description   

I experienced a bug when working with a datetime field that only rears its head when I use $record->replace()

//  BUG!!
$record->date_created = new Doctrine_Expression('NOW()');
$record->replace();
// date_created will be 0000-00-00 00:00:00, i.e. empty

// WORKS FINE!
$record->date_created = '2022-12-23 14:14:12';
$record->replace();

// WORKS FINE!
$record->date_created = new Doctrine_Expression('NOW()');
$record->insert();

// WORKS FINE!
$record->date_created = '2022-12-23 14:14:12';
$record->insert();



 Comments   
Comment by Jonathan H. Wage [ 08/Jun/10 ]

Thanks for the issue and description. I committed a fix that addresses the problem.





[DC-695] Fixtures created badly when primary key is not autonumber Created: 20/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Import/Export
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Luís Eduardo Jason Santos Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

Tested on Linux/Mysql


Attachments: File fix.diff    
Issue Links:
Duplicate
is duplicated by DC-688 data-load don't work when the primary... Resolved

 Description   

When exporting data where the primary key is not an autonumber the export function will hide the field anyway, merging the keys to the text identifier of the resulting record.

This creates two problems:

  • the original value is lost due to size limit of the record identifier
  • the database is unable to generate the primary key when data-loading.

A fix is attached that prevents the export function of hiding the said field from the fixture.



 Comments   
Comment by Jonathan H. Wage [ 08/Jun/10 ]

Thanks for the issue and patch!





[DC-691] No sequence_id increment after fixtures loaded, while setting their ids Created: 18/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Data Fixtures
Affects Version/s: None
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: A. Simonneau Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

Ubuntu lucid up to date - Symfony 1.4.4 - Postgresql 8.4



 Description   

If I force id in fixtures (they are autoincremented in model), the sequence is not set correctly in postgresql.

Model :
T_Company:
actAs:
Timestampable: ~
Searchable:
fields: [name]
columns:
company_id:

{ type: integer, notnull: true, autoincrement: true, primary: true }

dn:

{ type: string(255) }

name:

{ type: string(127), notnull: true}

relations:
RssSettings:

{ local: company_id, foreign: rsssettings_id, foreignAlias: CompanySettings, refClass: J_Company_Rsssettings, class: T_RssSettings }

ParentCompany:

{ local: company_id, foreign: parent_company_id, foreignAlias: ChildCompany, refClass: J_Company_Company, class: T_Company }

ParentLinkType:

{ local: company_id, foreign: parent_company_id, refClass: J_Company_Company, class: S_CompanyLinkType }

Fixtures :
T_Company:
A:
company_id: 1
name: A
dn: A
B:
company_id: 2
name: B
dn: B
C:
company_id: 3
name: C
dn: C

Postgresql :
sl3dev=# select * from t__company;
company_id | dn | name | created_at | updated_at
-------------------------------------------------------------------------------------------------------------------------------------------------------
1 | A | A | 2010-05-18 16:55:12 | 2010-05-18 16:55:12
2 | B | B | 2010-05-18 16:55:12 | 2010-05-18 16:55:12
3 | C | C | 2010-05-18 16:55:12 | 2010-05-18 16:55:12

sl3dev=# select nextval('t__company_company_id_seq'::regclass);
nextval
---------
1
(1 ligne)

1 instead of 4 naturally.






[DC-688] data-load don't work when the primary key is a string Created: 18/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Import/Export
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Gilles Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

Windows 7
MySQL 5.1
Apache 2.2
PHP 5.3
Symphony 1.4.4


Issue Links:
Duplicate
duplicates DC-695 Fixtures created badly when primary k... Resolved

 Description   

The export/import system don't work if a table have a primary key string.

I have this schema :

 
TypeChampPerso:
  columns:
    id: { type: string(20), primary: true }
    description: { type: string(100), notnull: true }

Here is the result when I extract data with data-dump :

 
TypeChampPerso:
  TypeChampPerso_date:
    description: Date
  TypeChampPerso_numeric:
    description: Numérique
  TypeChampPerso_text:
    description: Texte

Then, I've got an error when I used data-load :
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '' for key 'PRIMARY'

The id column is empty is the DB.

It works if I add manually the id value in the yml file :

 
TypeChampPerso:
  TypeChampPerso_date:
    id: date
    description: Date
  TypeChampPerso_numeric:
    id: numeric
    description: Numérique
  TypeChampPerso_text:
    id: text
    description: Texte





[DC-685] Invalid conditions in Import/Mysql.php Created: 15/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Import/Export
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Karma Dordrak (Drak) Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None


 Description   

Lines 68 and 190 have the following conditional
if ($this->conn->getAttribute(Doctrine_Core::ATTR_PORTABILITY) & Doctrine_Core::PORTABILITY_FIX_CASE) {

Should read:
if ($this->conn->getAttribute(Doctrine_Core::ATTR_FIELD_CASE) &&
($this->conn->getAttribute(Doctrine_Core::ATTR_PORTABILITY) & Doctrine_Core::PORTABILITY_FIX_CASE)) {



 Comments   
Comment by Jonathan H. Wage [ 08/Jun/10 ]

Thanks for the issue and required change!





[DC-682] createTablesFromArray has heavy dependence on order of models with many-to-many relationships resulting in wrong CREATE TABLE statements Created: 13/May/10  Updated: 13/May/10  Resolved: 13/May/10

Status: Resolved
Project: Doctrine 1
Component/s: Import/Export
Affects Version/s: 1.2.2
Fix Version/s: None

Type: Bug Priority: Major
Reporter: Bryan Zarzuela Assignee: Jonathan H. Wage
Resolution: Invalid Votes: 0
Labels: None
Environment:

PHP 5.3, MySQL 5.1, OSX, Macports



 Description   

Given 3 models:

class Model_ProductFeature extends Doctrine_Record
{
public function setTableDefinition()

{ $this->setTableName('products_features'); $this->hasColumn('product_id', 'integer', null, array( 'primary' => true, )); $this->hasColumn('feature_id', 'integer', null, array( 'primary' => true, )); }

}

abstract class Model_Base_Product extends Doctrine_Record
{
/**

  • Table Def
    */
    public function setTableDefinition() { $this->setTableName('products'); $this->hasColumn('name', 'string', 50); }

/**

  • Doctrine stuff
    */
    public function setUp() { $this->actAs("Timestampable"); $this->hasMany('Model_Feature as features', array( 'local' => 'id', 'foreign' => 'product_id', 'refClass' => 'Model_ProductFeature', )); }

    }

abstract class Model_Base_Feature extends Doctrine_Record
{
/**

  • Table Def
    */
    public function setTableDefinition() { $this->setTableName('features'); $this->hasColumn('name', 'string', 50); $this->hasColumn('description', 'text'); $this->hasColumn('product_id', 'integer'); }

/**

  • Doctrine stuff
  • */
    public function setUp()

    { $this->actAs("Timestampable"); $this->hasMany('Model_Product as products', array( 'local' => 'id', 'foreign' => 'feature_id', 'refClass' => 'Model_ProductFeature', )); }

    }

a call to getLoadedModels will return Model_Feature, Model_Product, Model_ProductFeature

But if that order was passed to createTablesFromArray, the CREATE TABLE statement for products_features will be:
"CREATE TABLE products_features (product_id BIGINT, feature_id BIGINT, INDEX id_idx (id), PRIMARY KEY(product_id, feature_id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = INNODB"

As you can see, there should not be an INDEX created for "id".

If I change the order passed to createTablesFromArray to Model_ProductFeature, Model_Product, Model_Feature, the problem goes away.

So there is a problem with a dependency on the order that the loaded models that are passed. I will try to investigate further in the future, but have now resorted to naming the refClass to Model_AProductFeature instead.



 Comments   
Comment by Bryan Zarzuela [ 13/May/10 ]

The issue I described was caused by PHPUnit and not Doctrine.





[DC-681] Command-line option rebuild-db is touching the models Created: 13/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Cli
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Jerry Verhoef Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None


 Description   

The command-line option "rebuild-db" is doing the following tasks: ( from the constructor )

$this->dropDb = new Doctrine_Task_DropDb($this->dispatcher);
$this->buildAll = new Doctrine_Task_BuildAll($this->dispatcher);

But the BuildAll tasks is executing three tasks including one which is regenerating the models. ( from the constructor )
$this->models = new Doctrine_Task_GenerateModelsYaml($this->dispatcher);
$this->createDb = new Doctrine_Task_CreateDb($this->dispatcher);
$this->tables = new Doctrine_Task_CreateTables($this->dispatcher);

My suggestion is to replace the build-all in the rebuild-db task with just the following. This would make more sense.

$this->createDb = new Doctrine_Task_CreateDb($this->dispatcher);
$this->tables = new Doctrine_Task_CreateTables($this->dispatcher);






[DC-675] Doctrine_Connection_Mssql charset problem Created: 10/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: None
Affects Version/s: None
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Steve Müller Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None


 Description   

Recently I got problems with converting UTF-8 data to iso ISO-8859-1 while trying to insert/update a Microsoft SQL Server database.
I wrote a task that exports data from an UTF-8 MySQL database to a latin1 Microsoft SQL Server database. When converting the data from UTF-8 to ISO-8859-1 the insert/update fails:

SQLSTATE[HY000]: General error: trying to execute an empty query

As i tracked the error down I realized, that the exception only occurs if I try to insert/update data with special characters like "ü", "ö", "ä", "ß" etc.
The error's origin seems to lie in the Doctrine_Connection_Mssql class, more precisely in the replaceBoundParamsWithInlineValuesInQuery() method.
Debugging this method shows that after replacing a bound param with data containing a special character (see above), the replace action for the next bound param replaces the whole query string with an empty string. Therefore the parent class's method exec() throws an exception as it tries to execute an empty query.

Example:

Task snippet:
// $src is UTF-8 data (MySQL DB)
// $dest ist latin1 data (MSSQL DB)

$dest->setZip($src->getZip());
$dest->setCity(iconv("UTF-8", "ISO-8859-1//TRANSLIT", $src->getCity());
$dest->setStreet(iconv("UTF-8", "ISO-8859-1//TRANSLIT", $src->getStreet());
$dest->save();

RESULTING DQL: UPDATE table SET zip = ?, city = ?, street = ? WHERE id = ?;

Params:
array(
'22307',
'München',
'Dummystreet 18'
'1'
)

After replaceBoundParamsWithInlineValuesInQuery() replaces param 'München', the query string is replaced by an empty string in the following iteration.

The root of the Problem seems to lie in the regex modifier 'u' which treats the pattern as UTF-8 in the param replacements. Removing this modifier solves the problem for me. What purpose has this modifier?






[DC-678] Need case-insensitive checks for dupes in the $options['indexes'] array Created: 13/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Import/Export
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Bryan Zarzuela Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

PHP 5.3, OSX, APC



 Description   

I ran into a bug where there were two duplicate indexes that Doctrine was trying to create.

Not too sure what the cause is yet but this fix took care of the problem for me.

Line 161 of Doctrine/Export/MySQL.php

// Case Insensitive checking for duplicate indexes...
$dupes = array();
foreach ($options['indexes'] as $key => $index) {
if (in_array(strtolower($key), $dupes))

{ unset($options['indexes'][$key]); }

else

{ $dupes[] = strtolower($key); }

}
unset($dupes);



 Comments   
Comment by Jonathan H. Wage [ 13/May/10 ]

I don't think this is a good change. You should instead fix it by identifying why you have 2 indexes with the same name, instead of this hack.

Comment by Bryan Zarzuela [ 13/May/10 ]

I'll try to debug why there were two indexes named employeecode and employeeCode. I think there's a bug somewhere in the MySQL drivers where the case-conversions failed.

Had no time yesterday as I was focused more on getting my work done than actually fixing the problem

Comment by Bryan Zarzuela [ 13/May/10 ]

If you do this in the base model, note the capital C in the employeeCode index:

$this->hasColumn('employeeCode', 'string', 11);
$this->index('employeeCode', array('fields' => array('employeeCode')));

It will result in this SQL statement for the table creation:

CREATE TABLE employees (id BIGINT AUTO_INCREMENT, employeecode VARCHAR(11), first VARCHAR(50), last VARCHAR(50), created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, INDEX employeeCode_idx (employeecode), INDEX employeecode_idx (employeecode), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = INNODB

Note that it tries to create 2 indexes named employeeCode_idx and employeecode_idx.

If I change the declaration to $this->index('employeecode', array('fields' => array('employeeCode')));, then it works:

CREATE TABLE employees (id BIGINT AUTO_INCREMENT, first VARCHAR(50), last VARCHAR(50), employeecode VARCHAR(11), created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, INDEX employeecode_idx (employeecode), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = INNODB

Still not sure what the cause is. I'm not too familiar with the flow of the Export Drivers yet.

Comment by Jonathan H. Wage [ 14/May/10 ]

I see, I think we have some problems when you have columns which are camel case. I would recommend using underscores to avoid any issues until this can be resolved.





[DC-673] Doctrine_Export::dropForeignKey() doesn't format foreign key names Created: 10/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Import/Export
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Kousuke Ebihara Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

PHP 5.3.3-dev, mysql 5.1.41


Attachments: File drop-foreign-key-does-not-format.diff    

 Description   

Doctrine_Export::createForeignKey() formats foreign key names, but Doctrine_Export::dropForeignKey() doesn't. It is a problem.

I tested following:

echo "<?php

class addForeignKey extends Doctrine_Migration_Base
{
public function up()

Unknown macro: { $this->createForeignKey('user', 'dotted.foreign.key', array( 'local' => 'category_id', 'foreign' => 'id', 'foreignTable' => 'category', )); }

}" > migrations/1_add_foreign_key.php

echo "<?php

class removeForeignKey extends Doctrine_Migration_Base
{
public function up()

Unknown macro: { $this->dropForeignKey('user', 'dotted.foreign.key'); }

}" > migrations/2_add_foreign_key.php

./doctrine migrate 1 # successful

./doctrine migrate 2 # failed

1 error(s) encountered during migration
=======================================
Error #1 - SQLSTATE[42000]: Syntax error or access violation: 1103 Incorrect table name 'dotted'. Failing Query: "ALTER TABLE user DROP FOREIGN KEY dotted.foreign"

I have a patch to fix this problem.



 Comments   
Comment by Kousuke Ebihara [ 13/May/10 ]

http://github.com/ebihara/doctrine1/commit/6ae3d28a44796dad13d8387b390515ad45fd26aa

I commited a patch to fix this issue to my fork.





[DC-660] Can't update column with decimal type Created: 03/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Major
Reporter: Oleg Assignee: Jonathan H. Wage
Resolution: Invalid Votes: 0
Labels: None
Environment:

linux apache2



 Description   

I'm using symfony 1.4.4 with doctrine plugin (not sure which version it is)
whenever I try update a column which is deciamal type it;s throwing me this error

Unknown component alias 7

column defition

$this->hasColumn('price', 'decimal', 10, array(
'type' => 'decimal',
'fixed' => 0,
'unsigned' => false,
'primary' => false,
'notnull' => true,
'autoincrement' => false,
'length' => 10,
'scale' => '2',
));

code:

$q = Doctrine_Query::create()
->update('SomeModel m')
->set('m.price = ?', '7.99');
$rows = $q->execute();



 Comments   
Comment by Jonathan H. Wage [ 08/Jun/10 ]

Your syntax is wrong. It is ->set('field', '?', 'value')





[DC-663] Doctrine_Table::createQuery creates a query with the default connection instead of the current connection Created: 04/May/10  Updated: 28/Sep/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Query
Affects Version/s: 1.2.1, 1.2.2, 1.2.3
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Costache Catalin Assignee: Guilherme Blanco
Resolution: Fixed Votes: 0
Labels: None
Environment:

Ubuntu 9.10



 Description   

If we manually set a connection on the Doctrine_Table instance Doctrine silently ignores it when creating a Doctrine_Query instance through createQuery method

$manager->setConnection($defaultConnection);

$table = Doctrine::getTable('Users')->setConnection($newConnection);
$table->createQuery(); // -> connection to $defaultConnection

The code:

public function createQuery($alias = '')
{
if ( ! empty($alias))

{ $alias = ' ' . trim($alias); }

$class = $this->getAttribute(Doctrine_Core::ATTR_QUERY_CLASS);

return Doctrine_Query::create(null, $class) <-- fix too quick for a patch -> return Doctrine_Query::create($this>_conn, $class)
>from($this>getComponentName() . $alias);
}



 Comments   
Comment by Christoph Berg [ 28/Sep/10 ]

When using Table classes generated with the latest Symfony 1.4 version (1.4.8) the table gets bound to a specific connection, but Doctrine still uses the default connection.

Using the above mentioned quick fix - replacing $this->_conn with null - everything works as it should.





[DC-664] PATCH: Docrine_Record _isValueModified should not type check when comparing integers Created: 04/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Record
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Dennis Verspuij Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 1
Labels: None

Attachments: Text File Record.php.patch    

 Description   

The _isValueModified function in Record.php does the following for checking whether or not an integral value was modified:

...
} else if (in_array($type, array('integer', 'int')) && is_numeric($old) && is_numeric($new)) {
return $old !== $new;
...
} else {
return $old !== $new;
...

This does not make sense, it implies strict type checking is always done for integers.
I think the first check (line 1533) should be done loosely so that a string '123' is equal to integer 123, and thereby such a field is not considered modified.



 Comments   
Comment by Dennis Verspuij [ 04/May/10 ]

Attaching the simple patch to correct this.

Comment by David Jeanmonod [ 13/May/10 ]

This ticket seems to be a duplicate of this one: http://www.doctrine-project.org/jira/browse/DC-550
Maybe we could close it... Seems to be fix in the 1.2.2

Comment by David Jeanmonod [ 13/May/10 ]

Oh no, I misunderstood the DC-550. In fact the problem was introduce when fixing the 550. The strict type checking is a regression that have been introduce in the 550. Before the values old and new were cast to int.

Comment by David Jeanmonod [ 13/May/10 ]

This regression can be a real problem. With this, if we set an integer value to a record, then the record is set as modified even if the value was the same as before.
Here is a little test case to demonstrate the problem:

TestCase for DC-664
<?php
require_once('doctrine/lib/Doctrine.php');
//require_once(dirname(__FILE__).'/../../lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/lib/vendor/doctrine/Doctrine.php');
spl_autoload_register(array('Doctrine', 'autoload'));
$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_EXPORT, Doctrine::EXPORT_ALL);
$conn = Doctrine_Manager::connection('mysql://root:root@localhost/test_doctrine');
echo "Connection is set up\n";

class Record extends Doctrine_Record {
    public function setTableDefinition(){
        $this->setTableName('record');
        $this->hasColumn('number', 'integer');
    }
}

try {Doctrine::dropDatabases();}catch(Exception $e){} // Drop if exist :-)
Doctrine::createDatabases();
Doctrine::createTablesFromArray(array('Record'));
echo "Database table is create\n";

$record = new Record();
$record->number = 5;
$record->save();

$record = Doctrine::getTable('Record')->createQuery()->fetchOne();
$record->number = 5;

echo "BEFORE:";
var_dump($record->getModified(true));

echo "AFTER:";
var_dump($record->getModified());
Output
Connection is set up
Database table is create
BEFORE:array(1) {
  ["number"]=>
  string(1) "5"
}
AFTHER:array(1) {
  ["number"]=>
  int(5)
}





[DC-653] Model named 'Page' causes inpredictable results Created: 29/Apr/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: None
Affects Version/s: 1.2.2
Fix Version/s: None

Type: Bug Priority: Major
Reporter: Klaas van der Weij Assignee: Jonathan H. Wage
Resolution: Cannot Reproduce Votes: 0
Labels: None
Environment:

PHP Version 5.2.6-1+lenny8



 Description   

I had a model named 'Page' that didn't behave decently. It would only save when a certain field ('h1') was changed, if other values where changed the record wouldn't save the data but not notify me at all. Also the SoftDelete didn't work, probably because the same thing. Var_dumping the $page->toArray(); just showed logical data. Echoing the $this->deleted_at on postDelete() showed a correct DateTime (mysql), though directly after the $page->delete(); the value of $page->deleted; was NULL.

Recreating this same wrong-doing (avoid the word Bug) I cuold not, because a simple testscenario resulted in correct behaviour. However when I altered the name of the (non-base) class from 'Page' to 'Sitepage' it all worked.

This description is probably too indescriptive to extrapolate the actual cause for the wrong-doing in my scenario, but now, if some else runs into the same odd inexplicablications .. I've found me a friend



 Comments   
Comment by Jonathan H. Wage [ 08/Jun/10 ]

I cannot reproduce. If anyone has more information or a test case please re-open the issue.





[DC-656] Endless loop when using HYDRATE_SINGLE_SCALAR and Oracle OCI adapter Doctrine Created: 29/Apr/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Connection
Affects Version/s: 1.2.2, 1.2.3
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: vadik56 Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None


 Description   

Following loop inside Doctrine_Hydrator_SingleScalarDriver never terminates since fetchColumn inside Doctrine_Adapter_Statement_Oracle never returns FALSE.

while (($val = $stmt->fetchColumn()) !== false)

{ $result[] = $val; } Below is the patch for Doctrine_Adapter_Statement_Oracle ---------------------------------------------------------------------------------------------------------------------------------- Index: Oracle.php =================================================================== --- Oracle.php (revision 7546) +++ Oracle.php (working copy) @@ -398,7 +398,7 @@ return false; }

$row = $this->fetch(Doctrine_Core::FETCH_NUM);

  • return $row[$columnIndex];
    + return ($row===false) ? false : $row[$columnIndex];
    }

/**
----------------------------------------------------------------------------------------------------------------------------------



 Comments   
Comment by Jonathan H. Wage [ 08/Jun/10 ]

This appears to be already fixed by http://trac.doctrine-project.org/changeset/7578





[DC-650] SoftDelete sets "default" => null for deleted_at field, causing MSSQL to silently create a Default Constraint which in turns causes Migrations to fail when reverting Created: 26/Apr/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Behaviors
Affects Version/s: 1.2.0-ALPHA1, 1.2.0-ALPHA2, 1.2.0-ALPHA3, 1.2.0-BETA1, 1.2.0-BETA2, 1.2.0-BETA3, 1.2.0-RC1, 1.2.0, 1.2.1, 1.2.2, 1.2.3
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Daniel Cousineau Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

PHP 5.2.11, Windows 7, Sql Server 2005, php_mssql extension


Attachments: File fix_softdelete_mssql.diff    

 Description   

When MSSQL receives even just a default value of null it still creates a default constraint. Said constraint prevents doctrine from removing the deleted_at field in migrations (due to the dependency on the constraint).

Removing "default" => null prevents the silent creation of the constraint.

I have no machine to test the effects of this on MySQL. I would imagine that it would not materially affect MySQL as the default values are never used anyways (hence the default value of null).

Attached is a patch to fix the behavior (for rev 7544 in /branches/1.2)

If one is running into problems with migrations being unable to move backwards due to default constraints on SoftDelete columns, one can run the following T-SQL script to remove all default value constraints from a database (EVEN THOSE YOU SET MANUALLY, USE WITH CAUTION):

{{
– This script removes ALL default constraints

USE YOURDATABASENAMEHERE;

Declare @name nvarchar(155)
Declare @table nvarchar(155)
Declare @sql nvarchar(1000)

– find constraint names
DECLARE default_constraints CURSOR FOR
SELECT
object.name,
parent.name
FROM
sys.objects AS object
LEFT JOIN sys.objects AS parent ON object.parent_object_id = parent.object_id
WHERE
object.type_desc LIKE '%CONSTRAINT'
AND object.type_desc LIKE 'DEFAULT_CONSTRAINT'

OPEN default_constraints

FETCH NEXT FROM default_constraints INTO @name, @table

WHILE @@FETCH_STATUS = 0
BEGIN
IF NOT @name IS NULL
BEGIN
SELECT @sql = 'ALTER TABLE [' + @table + '] DROP CONSTRAINT [' + @name + '];'
--PRINT @sql
EXECUTE sp_executesql @sql
END

FETCH NEXT FROM default_constraints INTO @name, @table
END
CLOSE default_constraints
DEALLOCATE default_constraints
}}



 Comments   
Comment by Daniel Cousineau [ 26/Apr/10 ]

I should also note this affects not only rollbacks, but any sort of ability to drop a deleted_at column (say, removing the SoftDelete behavior)

Comment by Craig Marvelley [ 27/Apr/10 ]

This is related to an issue I reported a month or so ago - http://www.doctrine-project.org/jira/browse/DC-584. A solution I proposed there was to allow Doctrine to name constraints so they can be referenced and dropped later. If that were in place, the SoftDelete behaviour could manage the constraint itself?

Comment by Daniel Cousineau [ 27/Apr/10 ]

Craig, It maybe would if Doctrine were creating the default constraints itself. If Doctrine doesn't handle the default constraints then naming has no effect as MSSQL will silently create said constraint.

And now that I think of it, this issue is going to crop up any and every time you use default values in MSSQL. Perhaps it would be best to consider this ticket more of a cleanup (the behavior isn't using default values so there's no point in creating the constraint anyways) and side effect of the problems listed in DC-584 which should be the primary focus.





[DC-641] undefined method Doctrine_Manager::getExtensionsClasses() Created: 21/Apr/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: None
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Witold Wasiczko Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None


 Description   

On line 589 in Doctrine_Core:
public static function getExtensionsClasses()

{ return Doctrine_Manager::getInstance()->getExtensionsClasses(); }

In manager method getExtensionsClasses() doesn't exist.






[DC-646] DELETE and INNER JOIN Created: 23/Apr/10  Updated: 03/Jun/13  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: None
Affects Version/s: 1.2.0
Fix Version/s: None

Type: Bug Priority: Major
Reporter: jerome Assignee: Jonathan H. Wage
Resolution: Won't Fix Votes: 0
Labels: None
Environment:

WIndows 7
Wamp (PHP Version 5.3.0 mysqlnd 5.0.5-dev - 081106 - $Revision: 1.3.2.27 $ )



 Description   

I made a DQL delete query.

$q = Doctrine_Query::create()
->delete()
->from('termRelationship tr')
->innerJoin('tr.termTaxonomy tt')
->innerJoin('tr.Post p')
->where('p.id = ?', '1')
->andWhere('tt.taxonomy = ?','category');

//GENERATED SQL OF THE DQL
DELETE FROM term_relationship INNER JOIN term_taxonomy t2 ON t.term_taxonomy_id = t2.id INNER JOIN post p ON t.object_id = p.id WHERE (id = '1' AND taxonomy = 'category')

But this query is incorrect

The query must be( alias are not present beetween DELETE and FROM and for the FROM table)

DELETE tr FROM term_relationship tr INNER JOIN term_taxonomy t2 ON tr.term_taxonomy_id = t2.id INNER JOIN post p ON tr.object_id = p.id WHERE (p.id = '1' AND t2.taxonomy = 'category')

With this request all is good in pphmyadmin.



 Comments   
Comment by Jonathan H. Wage [ 08/Jun/10 ]

Joins are not supported on update and delete queries because it is not supported on all dbms. It won't be implemented in Doctrine 1 or Doctrine 2. You can however get the same affect by using subqueries.

Comment by James Bench [ 03/Jun/13 ]

You can't delete from a table when it's in a subquery in MySQL (I'm using Doctrine 2).

DELETE FROM tblA a WHERE a.id NOT IN(SELECT a2.id FROM tblA a2 JOIN a2.tblB b WHERE b.value = :value)




[DC-632] class_exists collision with Zend Framework Created: 15/Apr/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Import/Export
Affects Version/s: 1.2.2
Fix Version/s: None

Type: Bug Priority: Major
Reporter: admirau Assignee: Jonathan H. Wage
Resolution: Invalid Votes: 2
Labels: None
Environment:

Windows, PHP 5.3



 Description   

I use Zend autoloader with Doctrine and everything works fine,
until Doctrine_Import_Builder::emitAssign() is called (e.g. with Taggable Extension).

I get autoloader Warning: include_once(Doctrine\Template\Doctrine\Template\TaggableTag.php) failed to open stream
Since class_exists with true parameter automatically tries to autoload the class with wrong name.

This fails:

private function emitAssign($level, $name, $option)
{

// find class matching $name
$classname = $name;
if (class_exists("Doctrine_Template_$name", true))

{ $classname = "Doctrine_Template_$name"; }

Changing to false, fixes the problem:

private function emitAssign($level, $name, $option)
{

// find class matching $name
$classname = $name;
if (class_exists("Doctrine_Template_$name", false)) { $classname = "Doctrine_Template_$name"; }

 Comments   
Comment by Goran Juric [ 15/Apr/10 ]

I have the same problem running on Mac OSX PHP 5.2.11. Just to give some more info about how Doctrine is integrated into my ZF project:

   public function _initDoctrine()
    {
        require_once ROOT_PATH . '/library/Doctrine/Core.php';

        $autoloader = $this->getApplication()->getAutoloader();
        $autoloader->registerNamespace('Doctrine');
        $autoloader->pushAutoloader(array('Doctrine_Core', 'autoload'), 'Doctrine');
        $autoloader->pushAutoloader(array('Doctrine_Core', 'modelsAutoload'), '');

        $manager = Doctrine_Manager::getInstance();
        $manager->setAttribute(Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE, true);
        $manager->setAttribute(
            Doctrine::ATTR_MODEL_LOADING,
            Doctrine::MODEL_LOADING_CONSERVATIVE
        );

        $manager->setAttribute(Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES, true);

        $doctrineConfig = $this->getOption('doctrine');
        Doctrine::loadModels($doctrineConfig['models_path']);
        $conn = Doctrine_Manager::connection($doctrineConfig['dsn'],'doctrine');
        $conn->setAttribute(Doctrine::ATTR_USE_NATIVE_ENUM, true);

        return $conn;
    }

Taggable extension is currently located in the Doctrine folder.

Comment by Witold Wasiczko [ 21/Apr/10 ]

Did you try set Extensions Path with Doctrine::setExtensionsPath() ?

Comment by Goran Juric [ 21/Apr/10 ]

The proposed admiraus solution does not work for me.

I had to comment out the call to class_exists() and just set the class name to

$classname = "Doctrine_Template_$name";

Now my BaseArticle looks like this:

$timestampable0 = new Doctrine_Template_Timestampable();
$taggable0 = new Doctrine_Template_Taggable();
$this->actAs($timestampable0);
$this->actAs($taggable0);

and the autoloader does not have a problem finding the required classes. Before this the generated model had this:

$timestampable0 = new Timestampable();
$taggable0 = new Taggable();
$this->actAs($timestampable0);
$this->actAs($taggable0);

p.s. I have placed the files need for the Taggable extension inside the Doctrine folder.

I really whish there was a cleaner way of making Doctrine 1.2 work with Zend Framework.

Comment by Baryshev Ilya [ 28/May/10 ]

Put this in your Bootstrap.php to prevent warnings from ZF autoloader.
No need to modify anything else

 
protected function _initAutoload()
{
    $autoloader = Zend_Loader_Autoloader::getInstance();
    $autoloader->suppressNotFoundWarnings(true);
}