[DC-1060] Datetime Fomatting not working on findId in DB2 Platform Created: 24/Sep/13  Updated: 24/Sep/13

Status: Open
Project: Doctrine 1
Component/s: Record
Affects Version/s: 1.2.3
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Preethi Srinivasan Assignee: Jonathan H. Wage
Resolution: Unresolved Votes: 0
Labels: None
Environment:

In DB2/Symfony2


Attachments: PNG File Screen Shot 2013-09-24 at 3.55.44 PM.png     PNG File Screen Shot 2013-09-24 at 3.56.13 PM.png    

 Description   

We had to put this fix to check for DateTimeTz in SimpleObjectHydartor to make it work for DB2. Same code works for mysql. Is it possible to put a fix as part of release of next Doctrine so we need not update vendor code locally.
if (isset($cache[$column]['field'])) {
$type = Type::getType($cache[$column]['class']->fieldMappings[$cache[$column]['name']]['type']);
if ($type == 'DateTimeTz')

{ $value = substr($value, 0, 19); }

$value = $type->convertToPHPValue($value, $this->_platform);
}

The error happens when we do a find($id) in
$ep = $em->getRepository('some name')->find($id);






[DC-1040] allow queries with table joins across different databases Created: 17/Nov/11  Updated: 17/Nov/11

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

Type: Improvement Priority: Blocker
Reporter: Fabrice Agnello Assignee: Jonathan H. Wage
Resolution: Unresolved Votes: 0
Labels: None
Environment:

Windows XP SP3, Apache 2, PHP 5.3, MySQL 5.1.36, Symfony 1.4.8, Doctrine 1.2.3


Attachments: File Query.php    

 Description   

I'm currently working on a project which relies upon several databases declared in databases.yml in symfony 1.4.8.

I was facing an issue that has already been raised by other people, namely that you can't join tables which reference each other among different mysql databases.

I've dug a bit in the Doctrine_Query class and came to a solution that is acceptable for us, and allows now to make joins accross databases. Description follows :

first change is in the Doctrine_Core class, that gets stuffed with a new constant :
const ATTR_DATABASE_NAME = 0x1DB;

this constant allows us to add a new attribute to the databases.yml file as in :
gesdoc:
class: sfDoctrineDatabase
param:
dsn: mysql:host=127.0.0.1;dbname=gesdoc
username: root
password:
attributes:

  1. ************* NEW ATTRIBUTE BELOW ************
    database_name: gesdoc
    default_table_collate: utf8_general_ci
    default_table_charset: utf8

after that, a few changes have been done in the Doctrine_Query class which is attached to this issue for the sake of readability.

This may not be optimal, and probably need some regression testing, but it is currently working fine on our test server.

after this is done, I was able to issue queries like the following :
$x = DocumentTable::getInstance()->createQuery('d')
->distinct()
->leftJoin('d.Travail t')
->leftJoin('t.CdcIndInt ci')
->leftJoin('ci.CdcIndExt ce')
->leftJoin('ce.Cahierdescharge cdc')
->where('cdc.cdc_chro = ?', $cdc_chro)
->addWhere('d.id != ?', $document_id)
->execute();

where the referenced tables are in different databases. The necessary object binding has been done in every model class following the paradigm :

Doctrine_Manager::getInstance()->bindComponent('CdcIndInt ', 'gescdc');
abstract class BaseCdcIndInt extends sfDoctrineRecord
{
...
}

I don't know if this description is clear enough, so let me know if something is missing/wrong.






[DC-1031] CLONE -Multiple connections and i18n (raised as unresolved - original ticket marked as resolved) Created: 23/Aug/11  Updated: 28/Mar/12

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

Type: Bug Priority: Blocker
Reporter: James Bell Assignee: Jonathan H. Wage
Resolution: Unresolved Votes: 0
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 James Bell [ 23/Aug/11 ]

Original issue: DC-363 (http://www.doctrine-project.org/jira/browse/DC-363)

There are some additional comments in there that explain the issue as currently being seen in released versions of Doctrine 1.2. Can I help verify that this is the same ticket?

What is needed to help with debug (in addition to the extra information already provided)?

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

meet the same issue and it really blocked my project, seems no one will maintain 1.x. I'm considering whether to replace symfony 1.4 with 2.0.





[DC-1004] ATTR_TBLNAME_FORMAT not used when creating models from database Created: 08/May/11  Updated: 17/Apr/14

Status: Open
Project: Doctrine 1
Component/s: Import/Export
Affects Version/s: 1.2.3
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Robin Parker Assignee: Jonathan H. Wage
Resolution: Unresolved Votes: 0
Labels: None

Attachments: File doctrine_bug_diff.diff    

 Description   

if you set prefix to "xyz_%s" and have the model "BackgroundColor" it will become the table => "xyz_background_color"
if you have the table "xyz_background_color" with unknown model and create the the model from the table, you will get => "XyzBackgroundColor".

The fix (diff):

368a369,370
> $tablePrefix = $manager->getAttribute(Doctrine_Core::ATTR_TBLNAME_FORMAT);
>
381d382
<
385c386
< $definition['className'] = Doctrine_Inflector::classify(Doctrine_Inflector::tableize($table));

> $definition['className'] = Doctrine_Inflector::classify(Doctrine_Inflector::tableize(preg_replace(sprintf('/\A%s\z/', str_replace('%s', '(.*?)', $tablePrefix)), '$1', $table)));
396c397
< $class = Doctrine_Inflector::classify(Doctrine_Inflector::tableize($table));

> $class = Doctrine_Inflector::classify(Doctrine_Inflector::tableize(preg_replace(sprintf('/\A%s\z/', str_replace('%s', '(.*?)', $tablePrefix)), '$1', $table)));



 Comments   
Comment by Robin Parker [ 08/May/11 ]

The diff output as .diff





[DC-941] Spatial index type for mysql Created: 29/Nov/10  Updated: 29/Nov/10

Status: Open
Project: Doctrine 1
Component/s: Import/Export
Affects Version/s: 1.2.3
Fix Version/s: 1.2.3

Type: Improvement Priority: Major
Reporter: Mishal Assignee: Jonathan H. Wage
Resolution: Unresolved Votes: 0
Labels: None

Attachments: Text File patch.patch    

 Description   

I'm using doctrine and some of mysql's spatial functions. I need to specify spatial index for my tables.
My declaration looks like:

Geometry:
tableName: geometry
options:
type: MyISAM
collate: utf8_unicode_ci
charset: utf8
columns:
id:
type: integer(4)
primary: true
autoincrement: true
geometry:
type: geometry
notnull: true
indexes:
geometry:
fields:

  • geometry
    type: spatial

Exporting this definitions throws an exception: Unknown type spatial for index geometry_idx






[DC-924] type mismatch for keyfield in column aggregation Created: 11/Nov/10  Updated: 11/Nov/10

Status: Open
Project: Doctrine 1
Component/s: Inheritance
Affects Version/s: 1.2.3
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Arnaud Morvan Assignee: Roman S. Borschel
Resolution: Unresolved Votes: 0
Labels: None
Environment:

PostgreSQL



 Description   

This is the doc exemple on column aggregation inheritance :

Entity:
columns:
username: string(20)
password: string(16)
created_at: timestamp
updated_at: timestamp

User:
inheritance:
extends: Entity
type: column_aggregation
keyField: type
keyValue: 1

Group:
inheritance:
extends: Entity
type: column_aggregation
keyField: type
keyValue: 2

But the keyField (type) is created as VARCHAR(255) so PostgreSQL return an error on applying inheritance condition :

SQLSTATE[42883]: Undefined function: 7 ERROR: operator does not exist: character varying = integer
LINE 1: ...D s.application = 'frontend' AND s.lft = 1) AND (s.type = 2)
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.

I found this with symfony sfFilebasePlugin on sfFilebase:create-root task.






[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-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-813] Add ability to register a custom hydrator as a class instance instead of a class name Created: 09/Aug/10  Updated: 11/Aug/10  Resolved: 11/Aug/10

Status: Closed
Project: Doctrine 1
Component/s: Query
Affects Version/s: 1.2.2
Fix Version/s: 1.2.3

Type: New Feature Priority: Major
Reporter: Lukas Kahwe Assignee: Guilherme Blanco
Resolution: Fixed Votes: 0
Labels: None

Attachments: File DC-813.diff     File DC-813_test.diff    

 Description   

Currently its only possible to register a custom hydrator as a string name. However in order to inject additional attributes it would be beneficial to be able to register an instance of the custom hydrator class.

http://www.doctrine-project.org/documentation/manual/1_2/en/data-hydrators#writing-hydration-method

So instead of:
$manager->registerHydrator('my_hydrator', 'Doctrine_Hydrator_MyHydrator');

One could do:
$manager->registerHydrator('my_hydrator', new Doctrine_Hydrator_MyHydrator());



 Comments   
Comment by Lukas Kahwe [ 09/Aug/10 ]

patch with tests is in the works

Comment by Lukas Kahwe [ 09/Aug/10 ]

patch

Comment by Lukas Kahwe [ 09/Aug/10 ]

tests

Comment by Guilherme Blanco [ 11/Aug/10 ]

Fixed





[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-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-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-745] Exported foreign keys names are not formatted with attribute FKNAME_FORMAT Created: 16/Jun/10  Updated: 24/Aug/10  Resolved: 24/Aug/10

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

Type: Bug Priority: Minor
Reporter: Guilliam X Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

Doctrine SVN 1.2 r7676


Attachments: Text File DC-745.patch    

 Description   

Class Doctrine_Formatter formats index names using Doctrine_Core::ATTR_IDXNAME_FORMAT
but for now it does not format foreign key names with Doctrine_Core::ATTR_FKNAME_FORMAT (in function getForeignKeyName).

Attached simple patch (copied from function getIndexName)



 Comments   
Comment by Jonathan H. Wage [ 24/Aug/10 ]

Fixed in http://trac.doctrine-project.org/changeset/7682

Thanks, Jon





[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-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-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-709] Column comments are not escaped Created: 28/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

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

Type: Bug Priority: Minor
Reporter: Claudio Nicora Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

MySQL 5.1.41



 Description   

Following schema.yml causes MySQL error because of the first column comment that contains the reserved ' char.

Client:
  columns:
    serial:    { type: string(50), primary: true, comment: "Seriale dell'hardware" }

The following works (note the double ''), but Doctrine schema should be automatically escaped

Client:
  columns:
    serial:    { type: string(50), primary: true, comment: "Seriale dell''hardware" }


 Comments   
Comment by John Kary [ 01/Jun/10 ]

Confirmed and fixed with test case: http://github.com/johnkary/doctrine1/commit/640d2a11866e49b4315419ba665c4069c122e6b5
Pull request sent.

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

Thanks for the issue, test and patch!

Comment by Claudio Nicora [ 08/Jun/10 ]

You're welcome. Mine was the easiest part





[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-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-699] DC292 test doesn't pass due to missing 'migrations' directory Created: 24/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: Minor
Reporter: Federico Gimenez Nieto Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

Debian Lenny



 Description   

After downloading Doctrine 1.2.2 from [1] and unpacking it, when trying to run the tests by:

  1. cd Doctrine-1.2.2/tests
  2. php ./run.php

i get:

Unexpected UnexpectedValueException thrown in [Doctrine_Ticket_DC292_TestCase] with message [RecursiveDirectoryIterator::__construct(/home/fgimenez/alioth/doctrine_test/Doctrine-1.2.2/tests/Ticket/DC292/migrations): failed to open dir: No such file or directory] in /home/fgimenez/alioth/doctrine_test/Doctrine-1.2.2/lib/Doctrine/Migration.php on line 137

If i create a 'migrations' directory under Doctrine-1.2.2/tests/Ticket/DC292 all tests pass.

Cheers,
Federico

[1] http://www.doctrine-project.org/downloads/Doctrine-1.2.2.tgz



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

SVN contains an empty directory named migrations already. I will add some code to ensure the directory is created though.





[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-697] Doctrine Support of custom enum types in Pgsql 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: Improvement Priority: Minor
Reporter: Leif Jackson Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None

Attachments: Text File 0001-Postgresql-custome-enum-types-as-native-enum.patch     Text File 0002-Update-Pgsql-test-case-to-include-enum-changes-to-ta.patch    

 Description   

In postgres with existing schema you can have custom enum types. To support this with our current code base (symfony w/ doctrine 1.2.2) I had to make changes to doctrine import and datadict for pgsql driver. The patches attached are from the git tree.

0001-Postgresql-custome-enum-types-as-native-enum.patch
0002-Update-Pgsql-test-case-to-include-enum-changes-to-ta.patch

The patches are formatted from the git tree.

Thanks for an excellent ORM for php!






[DC-696] Sluggable behavior doesn't work properly with french accents in utf8 Created: 20/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

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

Type: Bug Priority: Minor
Reporter: Nicolas Deguine Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

Windows XP, Symfony 1.4.4, PHP 5.2.8, MySQL 5.0.x



 Description   

In UTF8, "Législation 2008" was "slugged" as "legislation-ta-2-8" which is not correct.

It is caused as a bug in the Doctrine_Inflector::unaccent function.

If $string seems to be UTF8, we use the $chars array to replace accents. But the last line of the $chars array (Norwegian characters replacements) is incorrect :

'Å'=>'Aa','Æ'=>'Ae','Ø'=>'O','æ'=>'a','ø'=>'o','å','aa'

should be replaced by

'Å'=>'Aa','Æ'=>'Ae','Ø'=>'O','æ'=>'a','ø'=>'o','å' => 'aa'






[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-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-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-687] Doctrine_Inflector::unaccent replaces UTF8-Chars - 0 with å, 1 with aa Created: 18/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

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

Type: Bug Priority: Minor
Reporter: Steffkes Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None

Attachments: Text File 100518_Doctrine_Inflector.patch    

 Description   

Changeset 7359 which fixes #DC-543 [Doctrine_Inflector::unaccent does not replace the norwegian characters æøå] introduces a new bug ... 0 and 1 are now replaced with å and aa






[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-683] orderBy many field defined in yml files doesnt work Created: 14/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

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

Type: Bug Priority: Trivial
Reporter: wojtek kolodziejczyk Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

symfony - sfDoctrinePlugin, MySQL



 Description   

Hi,
When you have defined orderBy table option in yml file, like:

orderBy[ field1 ASC, field2 DESC ]

and models build by doctrine:build --model

Doctrine_Table class reports warnings, when executing select query i.e.:
Warning: array_map() [function.array-map]: Argument #2 should be an array in ....\doctrine\Doctrine\Table.php on line 1134

that's causes no order at all in sql query

quick solution for this problem is change file \doctrine\Doctrine\Table.php, function processOrderBy() - around line 1134

from:

$e1 = explode(',', $orderBy);

to:

if( !is_array($orderBy)){
$e1 = explode(',', $orderBy);
}else{
$e1 = $orderBy;
}

hope it helps to someone

best,
Wojtek






[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-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-677] Incorrect property uset before Collection serialize Created: 11/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: Minor
Reporter: Daniel Bojdo Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None


 Description   

On line 155 (Doctrine/Collection.php) should unset "referenceFiled" property not "reference_field". It causes problems after Collection unserialize.






[DC-675] Doctrine_Connection_Mssql charset problem Created: 10/May/10  Updated: 23/Jul/14  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-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-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-670] Doctrine/Collection: toHierarchy() accepts not only NestedSet Created: 08/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

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

Type: Improvement Priority: Minor
Reporter: Maxim Olenik Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None

Attachments: Text File 0001-Doctrine-Collection-toHierarchy-accepts-not-only-Nes.patch    

 Description   

I've got it after trying to implement NestedSet+AjacencyList behaviour.






[DC-669] listTableIndexes() return an empty array with doctrine-1.2.2 Created: 07/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: Minor
Reporter: Christophe Beaujean Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

On linux fedora with MySQL 5.1.45



 Description   

I've a table A with 3 indexes. I would like to retrieve this list of indexes by using listTableIndexes() but I just get an empty array.

I found the problem into listTableIndexes():
The $keyName and $nonUnique are set in lowercase in my case.
But the result of $indexes = $this->conn->fetchAssoc($query); have field names in both lower/upper case, e.g.:
'Non_unique' => string '1' (length=1)
'Key_name' => string 'module' (length=6)

But maybe that's a config problem from my part and the case of $keyName and $nonUnique should not be modified.



 Comments   
Comment by Christophe Beaujean [ 17/May/10 ]

Ref to
http://www.doctrine-project.org/jira/browse/DC-685





[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-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-662] Obsolete Doctrine::getTable() call in Template_Listener_Sluggable Created: 04/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

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

Type: Bug Priority: Minor
Reporter: Aleksey V. Zapprov Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None


 Description   

Subj. Found in Doctrine/Template/Listener/Sluggable.php on line 165th:

          $table = Doctrine::getTable($parentTable[$i]);

Should be replaced with:

          $table = Doctrine_Core::getTable($parentTable[$i]);





[DC-658] _setPage in Doctrine_Pager is set to Private method Created: 01/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

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

Type: Bug Priority: Trivial
Reporter: Jerry Verhoef Assignee: Guilherme Blanco
Resolution: Fixed Votes: 0
Labels: None


 Description   

The method _setPage is set as private. This will make extending the Doctrine_Pager unnecessary complex.






[DC-657] EXISTS not correctly parsed if there's no space between keyword and the open bracket Created: 01/May/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Query
Affects Version/s: 1.2.1
Fix Version/s: 1.2.3

Type: Bug Priority: Minor
Reporter: Andrea Baron Assignee: Guilherme Blanco
Resolution: Fixed Votes: 0
Labels: None
Environment:

Mac OS X 10.6.3, MySQL 5.1.34, PHP 5.2.10



 Description   

Executing a query that has an EXISTS in the WHERE clause without a space before its bracket make Doctrine ignore the EXISTS part and output it as is, this cause problems because, as an example, table aliases are not correctly resolved.

For example this works

SELECT * FROM TableA a WHERE EXISTS (SELECT id FROM TableB b WHERE b.a = a.id)

while this doesn't

SELECT * FROM TableA a WHERE EXISTS(SELECT id FROM TableB b WHERE b.a = a.id)

I found the error could be in Doctrine/Query/Where.php line 49:

        if (count($terms) > 1) {
            if (substr($where, 0, 6) == 'EXISTS') {
                return $this->parseExists($where, true);
            } elseif (substr($where, 0, 10) == 'NOT EXISTS') {
                return $this->parseExists($where, false);
            }
        }

could be modified with:

            if (substr($where, 0, 6) == 'EXISTS') {
                return $this->parseExists($where, true);
            } elseif (substr($where, 0, 10) == 'NOT EXISTS') {
                return $this->parseExists($where, false);
            }

to solve the problem. As a plus, since one could write "NOT EXISTS" (for some reason), I think it's better to write it this way:

        if (substr($where, 0, 6) == 'EXISTS') {
            return $this->parseExists($where, true);
        } elseif (preg_match('/^NOT\s+EXISTS\b/i', $where) !== 0) {
            return $this->parseExists($where, false);
        }

Hope this helps.

PS. Please add php code formatter






[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-651] [PATCH] Doctrine_Record::option('orderBy', ...) of join's right side being applied to refTable in m2m relationship Created: 26/Apr/10  Updated: 17/Apr/14

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

Type: Bug Priority: Major
Reporter: suhock Assignee: Guilherme Blanco
Resolution: Unresolved Votes: 2
Labels: None
Environment:

CentOS 5.4
PHP 5.3.2
MySQL 5.1.44, for unknown-linux-gnu (x86_64)


Attachments: File DC651TestCase.php     File Query_orderBy_relation.diff     Text File Ticket_DC651.patch    

 Description   

When using the Doctrine_Record::option('orderBy', ...) feature on a table definition, where that table is the target of a many-to-many join, the specified orderBy columns are applied to the relation table's alias. So for example, given the following definitions:

class User extends Doctrine_Record {
  public function setTableDefinition() {
    $this->hasColumn('uid', 'integer', null, array('primary' => true));
    $this->option('orderBy', 'uid');
  }

  public function setUp() {
    $this->hasMany('Group as groups', array('refClass' => 'UserGroup', 'local' => 'user_uid', 'foreign' => 'group_id'));
  }
}

class Group extends Doctrine_Record {
  public function setTableDefinition() {
    $this->hasColumn('gid', 'integer', null, array('primary' => true));
  }

  public function setUp() {
    $this->hasMany('User as users', array('refClass' => 'UserGroup', 'local' => 'group_gid', 'foreign' => 'user_id'));
  }
}

class UserGroup extends Doctrine_Record {
  public function setTableDefinition() {
    $this->hasColumn('user_uid', 'integer', null, array('primary' => true));
    $this->hasColumn('group_gid', 'integer', null, array('primary' => true));
  }

  public function setUp() {
    $this->hasOne('User as user', array('local' => 'user_uid', 'foreign' => 'uid'));
    $this->hasOne('Group as group', array('local' => 'group_gid', 'foreign' => 'gid'));
  }
}

the following queries:

$query = Doctrine_Query::create()
  ->select('u.*')
  ->from('User u')
  ->leftJoin('u.groups g WITH g.gid=?', 1);
echo $query->getSqlQuery() . "\n";

$query = Doctrine_Query::create()
  ->select('g.*')
  ->from('Group g')
  ->leftJoin('g.users u WITH u.uid=?', 1);
echo $query->getSqlQuery() . "\n";

will output the following:

SELECT u.uid AS u__uid FROM user u LEFT JOIN user_group u2 ON (u.uid = u2.user_uid) LEFT JOIN group g ON g.gid = u2.group_id AND (g.gid = ?) ORDER BY u.uid
SELECT g.gid AS g__gid FROM group g LEFT JOIN user_group u2 ON (g.gid = u2.group_gid) LEFT JOIN user u ON u.uid = u2.user_id AND (u.uid = ?) ORDER BY u.uid, u2.uid

The orderBy option() call is applied to the User definition. The SQL for the first query is correct (where User is on the left side of the join). The SQL for the second query (where User is on the right-most side of the join), however, is obviously incorrect (UserGroup doesn't even have a uid column). Basically, User's orderBy option is being applied to both the User table and its respective reference table, UserGroup, when it is the target of a join.

After digging through the source for a while, I believe I've come up with a patch for this issue (which should be checked by someone more knowledgeable of Doctrine's internals). Basically, in the Doctrine_Query::buildSqlQuery() function, a call is made to Doctrine_Relation::getOrderByStatement() with the reference table (UserGroup)'s alias (u2), which in turn makes a call to Doctrine_Table::getOrderByStatement() on the referenced table (User), filling in the ORDER BY clause with User columns using UserGroup's alias. My solution was to reorder the logic so that the test for a reference class is made before the initial call to getOrderByStatement() is made. It seems to work against my test case and the test cases in the repository. I'll post my patch momentarily.

This bug was first mentioned in the comments in DC-313, but the original ticket comes across as more of a feature request for the hasMany() orderBy feature.



 Comments   
Comment by suhock [ 26/Apr/10 ]

attached a test case for this bug

Comment by suhock [ 26/Apr/10 ]

patch against /branches/1.2 HEAD (should also work apply to 1.2.2 tag)

Comment by Dan Ordille [ 30/Aug/10 ]

I can confirm this as an issue. However I don't think the above patch adequately fixes the problem it seems like with it an order by is still added for the ref column however the relation alias is lost.

My query with the patch became
SELECT g.gid AS g__gid FROM group g LEFT JOIN user_group u2 ON (g.gid = u2.group_gid) LEFT JOIN user u ON u.uid = u2.user_id AND (u.uid = ?) ORDER BY u.uid, uid

I made an another patch that prevents this extra order by clause from being added and have attached it.

Comment by suhock [ 21/Sep/10 ]

I tried out the new patch (Query_orderby_relation.diff), but it provides a reversed diff (patching goes from a patched version to the original). After applying it manually, it fails the provided test case and several additional test cases from the repository.

The original patch DOES pass the provided test case, when applied against 1.2.2, 1.2.3, or the 1.2 branch from the repository. It does not pass, however, Doctrine_Query_Orderby_TestCase. As the previous poster mentioned, it fails to resolve aliases in instances where the 'orderBy' option is specified in a relation definition.

I deleted the original patch and am providing a revised patch (Ticket_DC651.patch) against branch 1.2 HEAD (also works with 1.2.3), which fixes this issue. It passes all working test cases, including Doctrine_Query_Orderby_TestCase and DC651TestCase.

Comment by José De Araujo [ 31/Aug/11 ]

I had this issue recently on a application I'm working on as described the oderBy option was applied on the joined table on a column that even doesn't exist in it. I used the DC651 patch provided and it solved the issue, so far I haven't seen any side effect to it.





[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-647] Error in Doctrine_Manager#_buildDsnPartsArray() Created: 24/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: Minor
Reporter: Karma Dordrak (Drak) Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None


 Description   

The method Doctrine_Manager#_buildDsnPartsArray() ultimately relies on PHP's native parse_url(). This method, according to the PHP documentation does not parse unix_sockets in the DSN, so the following code will not find the unix_socket because parse_url only finds at most (http://us2.php.net/parse_url)

scheme
host
port
user
pass
path
query - after the question mark ?
fragment - after the hashmark #

// silence any warnings
$parts = @parse_url($dsn);

$names = array('dsn', 'scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment', 'unix_socket');

foreach ($names as $name) {
if ( ! isset($parts[$name]))

{ $parts[$name] = null; }

}

It would appear that in order to support unix sockets we have to use parsePdoDsn() instead but this is not clear because of parseDsn() which also claims to be able to find the unix_socket. I would suggest removing 'unix_socket' from the $names array so it is clear a unix_socket cannot be represented in the normal Dsn, but only a PdoDsn.






[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-626] Subqueries in HAVING Created: 11/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: New Feature Priority: Minor
Reporter: Ian Banfield Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None

Attachments: File patch.diff    

 Description   

Adds support for subqueries in HAVING.






[DC-621] Doctrine_Inflector :: urlize changed behaviour Created: 07/Apr/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

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

Type: Bug Priority: Major
Reporter: Gunther Groenewege Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None


 Description   

before when using the sluggable behavior the Doctrine_Inflector :: urlize function changed "catégorie 1" to "categorie-1"
since doctrine 1.2.2 "catégorie 1" becomes "categorie-aa"



 Comments   
Comment by Pavel Campr [ 08/Jun/10 ]

I see a bug here:
Inflector.php, line 221:
{{ 'Å'=>'Aa','Æ'=>'Ae','Ø'=>'O','æ'=>'a','ø'=>'o','å','aa'}}

probably, there is => missing and should be:

{{ 'Å'=>'Aa','Æ'=>'Ae','Ø'=>'O','æ'=>'a','ø'=>'o','å'=>'aa'}}

now, character '0' is replaced by 'å' and '1' is replaced by 'aa'





[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-613] Doctrine_Record::copy() ignores IDENTIFIER_SEQUENCE for NULLifying the id field on copy Created: 01/Apr/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Record
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: Minor
Reporter: Maurice Makaay Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None


 Description   

Doctrine_Record::copy() ignores IDENTIFIER_SEQUENCE for NULLifying the id field on copy. In our environment, it was fixed by adding Doctrine_Core::IDENTIFIER_SEQUENCE to the check at the start of the copy() method:

    public function copy($deep = false)
    {
        $data = $this->_data;
        $idtype = $this->_table->getIdentifierType();
!       if ($idtype === Doctrine_Core::IDENTIFIER_AUTOINC ||
!           $idtype === Doctrine_Core::IDENTIFIER_SEQUENCE) {
            $id = $this->_table->getIdentifier();

            unset($data[$id]);
        }





[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-584] MSSQL Server: Cannot alter / drop columns which have indexes or constraints applied to them Created: 18/Mar/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Migrations
Affects Version/s: 1.2.1
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Craig Marvelley Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

PHP 5.3
MSSQL Server 2008


Attachments: File DC584TestCase.php     Text File mssql_default_constraint_patch.patch    

 Description   

SQL Server will not allow attempts to alter or drop columns with indexes or constraints applied to them, because they are dependent on the column. In addition to this, SQL Server's syntax for altering columns is different for that of adding them in that it does not allow default value constraints to be present in the alter statement.

I've attached a patch which attempts to circumvent this issue by allowing the author of the changeset to provide a name for the default constraint, which SQL Server supports. Giving the constraint a name of our choosing allows us to reference and drop it before running a command against a constrained column.

In the case of an ALTER query the default constraint portion is spliced and subsequently run.

When dropping a column the author need only make sure they name the constraint, or know its name, then drop it before the query is run.



 Comments   
Comment by Craig Marvelley [ 22/Mar/10 ]

Added patch and test cases for this issue.





[DC-583] MSSQL Server: No accounting for max varchar size when creating columns. Created: 18/Mar/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Migrations
Affects Version/s: 1.2.1
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Craig Marvelley Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

PHP 5.3
MSSQL Server 2008


Attachments: Text File add_mssql_max_varchar_length.patch    

 Description   

The Doctrine_Connection_Mysql and Doctrine_Datadict_Mysql classes utilise a connection property max_varchar_size so if the field length of a string column is specified and exceeds 255 chars a 'text' column type is used instead of a 'varchar' one.

This logic is missing for MSSQL; Doctrine will attempt to create varchar columns even if greater than the max size (8000 chars).

I've attached a patch for MSSQL which mirrors the MySQL functionality, would you consider including it please?

Thanks!






[DC-558] CLONE -generate-migrations-diff is producing bogus migrations (drops the whole database) Created: 09/Mar/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Migrations
Affects Version/s: 1.2.0, 1.2.1
Fix Version/s: 1.2.3

Type: Bug Priority: Minor
Reporter: Kyle Spraggs Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 1
Labels: None
Environment:

Vanilla sandbox on version 1.2.1 and 1.2.0
both sqlite and mysql, so its not driver dependant



 Description   

Replicating the bug:
1. unzip vanilla sandbox.
2. create schema file with entity
3. run ./doctrine build-all
4. modify schema, add column to entity.
5. run ./doctrine generate-migrations-diff

Expected behaviour:
generates migration wich adds column to entity.

Real behaviour:
Drops entity from database

Similar issue:
http://groups.google.com/group/doctrine-user/browse_thread/thread/8b4a0fc0778a388a/79446784623b8497?lnk=gst&q=1.2.1+migration



 Comments   
Comment by Kyle Spraggs [ 09/Mar/10 ]

This issue is NOT fixed for certain configurations of Doctrine (namely PEAR).

See http://www.doctrine-project.org/jira/browse/DC-475

Comment by Martin Shopland [ 22/Apr/10 ]

I agree, this is an issue still in 1.2.2.

The problem seems to be to do with the model class name prefix, it manifests itself in Doctrine_Migration_Diff on line 190. The keys of two arrays are compared to see what needs to be changed:

Unable to find source-code formatter for language: php. Available languages are: actionscript, html, java, javascript, none, sql, xhtml, xml
$from = array (
    'Model_CaseStudy' => array(...),
    'Model_Event' => array(...),
    'Model_Group' => array(...)
);

$to = array(
    'CaseStudy' => array(...),
    'Event' => array(...),
    'Group' => array(...)
);

Because none of the array keys match it assumes that the $to array are all new models and so generates the migration to drop all of $from and create all of $to; as the models have the same table names, if you run the down you end up with no tables at all.

So I think we need to inject the prefix into the $to models creation process somehow, as yet I haven't figured out an effective place to do this.

Comment by Martin Shopland [ 22/Apr/10 ]

I've got it working. Not sure how robust or appropriate this fix is:

Unable to find source-code formatter for language: diff. Available languages are: actionscript, html, java, javascript, none, sql, xhtml, xml
 
diff --git a/lib/Doctrine/Core.php b/lib/Doctrine/Core.php
index ef10b6e..2fa7a73 100644
--- a/lib/Doctrine/Core.php
+++ b/lib/Doctrine/Core.php
@@ -664,7 +664,7 @@ class Doctrine_Core
                             $className = $e[0];
                         }

-                        if ($classPrefix) {
+                        if ($classPrefix && $classPrefix != substr($className, 0, strlen($classPrefix))) {
                             $className = $classPrefix . $className;
                         }
diff --git a/lib/Doctrine/Migration/Diff.php b/lib/Doctrine/Migration/Diff.php
index 7f00b38..4260f27 100644
--- a/lib/Doctrine/Migration/Diff.php
+++ b/lib/Doctrine/Migration/Diff.php
@@ -112,7 +112,13 @@ class Doctrine_Migration_Diff
         $this->_cleanup();

         $from = $this->_generateModels(self::$_fromPrefix, $this->_from);
-        $to = $this->_generateModels(self::$_toPrefix, $this->_to);
+        $to = $this->_generateModels(
+
+            Doctrine_Manager::getInstance()->getAttribute(Doctrine_Core::ATTR_MODEL_CLASS_PREFIX) .
+            self::$_toPrefix,
+
+            $this->_to
+        );

         return $this->_diff($from, $to);
     }




[DC-538] Doctrine_Table::enumIndex() and Doctrine_Table::enumValue() should return easily distinguishable value (e.g. false) when index/value is not found Created: 03/Mar/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: 1.2.3

Type: Improvement Priority: Major
Reporter: Michal Olszewski Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

Any


Attachments: Text File Column.patch     Text File DC-538_fix.patch     Text File Table.patch    

 Description   

At the moment:

  • Doctrine_Table::enumIndex($fieldName, $value) returns $value specified as a parameter if no index for it is found
  • Doctrine_Table::enumValue($fieldName, $index) returns $index specified as a parameter if no value for it is found

This actually makes writing code which deals with enums a bit difficult as one would like to detect incorrect parameter as soon as possible (e.g. not when Doctrine validation kicks in and checks that enum value is incorrect).

Current algorithm that returns index or value is not sufficient to determine error in situation when index=value, e.g. enum values are [0, 1, 2, 3]. Calling e.g. enumValue('sample', 4) would yield 4 - so it makes difficult to determine whether it's correct value.

I hope this makes sense, let me know if anything is unclear.

The workaround for this is to check whether mapped index/value exists in array returned by Doctrine_Table::getEnumValues(), but this is an extra unnecessary check.

So instead of writing:

$index = 5;
$someTable = Doctrine_Core::getTable('Sample');
$value = $someTable->enumValue('sample', $index);

$values = $someTable->getEnumValues('sample');
if (in_array($value, $values) == false)
{
  throw new UnexpectedValueException('illegal index specified!');
}

I'd like to write:

$index = 5;
$value = Doctrine_Core::getTable('Sample')->enumValue('sample', $index);
if ($value === false)
{
  throw new UnexpectedValueException('illegal index specified!');
}

Simpler, eh?



 Comments   
Comment by Michal Olszewski [ 03/Mar/10 ]

Please find patches for Doctrine/Table.php and Doctrine/Column.php against their versions from rev. 7298

Patches includes proposed change (returning false instead of original parameter).

Interestingly, Doctrine_Column::enumIndex() returns FALSE when index is not found for specified value as it uses array_search().
So - another reason why this processing should be unified

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

Hi, did you run your patch against the test suite? It breaks several tests so we cannot apply it. Please re-open if you have more information and a new patch. Thanks, Jon

Comment by Michal Olszewski [ 25/Mar/10 ]

Hi,

I think I've nailed this one down.

First of all, Doctrine_Table::enumValue() is used incorrectly in two places:

  1. Doctrine_Record::unserialize() has 'special case' for enums, which performs following conversion - from enum's 'index' to 'value':
    $this->_table->enumValue($k, $this->_data[$k]);

    Now this doesn't make sense because Doctrine_Record::serialize() does not store enum's 'index' but 'value' so Doctrine_Table::enumValue() always receives 'value'. Previous implementation simply returned the same 'value' in case when check:

    isset($this->_columns[$columnName]['values'][$index])

    failed. And it was always failing because 'index' is actually one of the 'values'.

  2. Doctrine_Table::prepareValue() is the same case - it receives 'value' and not 'index' so there is no point in calling Doctrine_Table::enumValue().

Please note I assumed two things:

  1. Correct use of 'emulated' enums (second case) is to set them as strings, not integer values (it seemed reasonable after looking at test cases and documentation). Also: 'emulated' enums are stored in DB as text, not integers.
  2. 'Native' enums are not mapped so they don't change anything.

Under these assumptions I propose two changes:

  1. Add special case for enums in Doctrine_Record::serialize() so enum value is mapped to enum index
  2. Remove special case for enums in Doctrine_Table::prepareValue() and always return $value parameter as it was specified (move it to 'string'/'int' case)

I've prepared patch for these changes and will attach it soon.

Thanks.

Comment by Michal Olszewski [ 25/Mar/10 ]

Attaching patch (DC-538_fix.patch), including:

  • Doctrine_Table::enumValue() and Doctrine_Table::enumIndex() now returns false if parameter cannot be mapped
  • Doctrine_Table::prepareValue() does call Doctrine_Table::enumValue() for enum type
  • Doctrine_Record::serialize() serializes enum's index, not value, so unserialization works fine

Please apply it if you agree with my previous comment.





[DC-470] [PATCH] Search : wrong connection used for some queries when multiples connections are defined Created: 01/Feb/10  Updated: 06/Jul/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Behaviors
Affects Version/s: 1.2.1
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Colin Darie Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

Debian Sid + php 5.3, symfony 1.4.1


Attachments: Text File Search.fix-connection.patch    

 Description   

When multiples connections are defined, sometimes the Search behavior use a wrong one.

This happens because the Query::loadRoot() method calls the Manager::getConnectionForComponent(), which returns the bound connection for the root component (like ModelIndex in this case), but the connection for this component is never bound.
The following and attached patch solved the issue for me.

Search / connection patch
Index: lib/Doctrine/Search.php
===================================================================
--- lib/Doctrine/Search.php	(révision 7080)
+++ lib/Doctrine/Search.php	(copie de travail)
@@ -76,7 +76,9 @@
         $result = parent::buildTable();

         if ( ! isset($this->_options['connection'])) {
-            $this->_options['connection'] = $this->_options['table']->getConnection();
+            $manager = Doctrine_Manager::getInstance();
+            $this->_options['connection'] = $manager->getConnectionForComponent($this->_options['table']->getComponentName());
+            $manager->bindComponent($this->_options['className'], $this->_options['connection']->getName());
         }

         return $result;

Note : we can't use directly
$this->_options['connection'] = $this->_options['table']->getConnection();
because I sometimes found the getConnection() returns the wrong connection (I didn't looked why).



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

Can you test this against the latest 1.2 SVN? I applied a fix to Generator.php that might help this issue.

Comment by Colin Darie [ 10/Mar/10 ]

No, it doesn't work (with the very last revision, 7323).

I basically did a $object = Doctrine::getTable('Foo')->search('bar'); and got the following error :

SQLSTATE[HY000]: General error: 1 no such table: foo_index.
Failing Query: "SELECT COUNT(keyword) AS relevance, id FROM foo_index WHERE keyword = ?
GROUP BY id ORDER BY relevance DESC"

(the first defined connection is used).

Plus if it can help, I just discovered a weird thing with the (odd) code :

$table = Doctrine::getTable('Foo');
$object = Doctrine::getTable('Foo')->search('bar');

returns the error :

Invalid argument type. Expected instance of Doctrine_Table.
Comment by Jonathan H. Wage [ 11/Mar/10 ]

Hi,

Please re-open if you can provide some more information. The error you mentioned, please also include the stacktrace and other relevant information. The error message itself is useless

Thanks, Jon

Comment by Colin Darie [ 11/Mar/10 ]

Hi,

I uploaded 2 stacktraces here (with revision 7326, in a symfony 1.4.4-dev environment) and for the simple code
$o = Doctrine::getTable('Athlete')->search('hey') :

The script was called by a "php /path/to/my/test" to have a more readable stacktrace, but of course in a "normal" context, the same error happens.

The relevant databases.yml :

test:
  dbsport:
    param:
      dsn: 'mysql:host=localhost;dbname=db_sport'
  dbclient:
    param:
      dsn: 'sqlite:%SF_DATA_DIR%/sqlite/db_client_test.db'

all:
  dbsport:
    param:
      dsn: 'mysql:host=localhost;dbname=db_sport_test'
      username: username
      password: 'password'
  dbclient:
    param:
      dsn: 'sqlite:%SF_DATA_DIR%/sqlite/db_client.db'

A simple switch of the connections order solves the issue, but then it's no possible to search on the dbclient because the last connection defined is used instead (unlike I told in my previous comment, it's not the first one).

Thanks for your time.

Comment by Colin Darie [ 11/Mar/10 ]

Reopen with my previous comment http://www.doctrine-project.org/jira/browse/DC-470?focusedCommentId=12091&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#action_12091

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

Hmm, I am not sure of the problem here. Any way to reproduce this in a Doctrine test case so that i can more clearly see the problem?

Comment by Colin Darie [ 16/Mar/10 ]

I'm unable to write a test case for this, because the problem seems to come only when the base model class has not been loaded before to perform the search.

Moreover, I found this base class is never loaded by the Doctrine::getTable('foo')->search() method, so the bindComponent() in top of the class is never executed.

Here is a scenario that fails, if it can help you :

Doctrine_Core::getTable('Foo')->search('bar');
 -> Doctrine_Manager::getInstance()
 -> getConnectionForComponent('Foo')
      -> Doctrine_Core::modelsAutoload('Foo')
           At this point, self::$_modelsDirectory is not setted and self::$_loadedModelFiles is empty
           because no model has been loaded yet, so the BaseFoo class is not loaded and the component is not bound
      -> getCurrentConnection() returns the last defined connection, not the connection for the Foo component
  -> getTable('Foo') .... the following operates on the bad connection,
       so the query tries to select from the foo_index table in the wrong database.

(sorry for my poor english ^^)

Comment by Marcel Berteler [ 06/Jul/10 ]

Seems like this could be a duplicate of DC-740 and not an actual Doctrine bug but an sfDoctrinePlugin bug.





[DC-439] Import of table with (silly) name "index" Created: 20/Jan/10  Updated: 24/Mar/11  Resolved: 09/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Import/Export
Affects Version/s: 1.2.1
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Jochen Bayer Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

MS-SQL-Server 2005
php 5.2.11
openSUSE 11.1



 Description   

In Doctrine/Import/Mssql.php are this procedure calls:

92: $sql = 'EXEC sp_primary_keys_rowset @table_name = ' . $this->conn->quoteIdentifier($table, true);
99: $sql = 'EXEC sp_columns @table_name = ' . $this->conn->quoteIdentifier($table, true);
219: $query = 'EXEC sp_statistics @table_name = ' . $table;
222: $query = 'EXEC sp_pkeys @table_name = ' . $table;

which fail with sql "keyword" table names.

Using ...['. $table . ' ]' around the table name worked for me.
I don't know anything about the backend of doctrine.
Should this be done by quoteIdentifier, or is this fix okay?
.. @table_name = [' . $this->conn->quoteIdentifier($table, true) . ']';

Many thanx for all!
Jochen Bayer



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

It is hard to understand what you changed exactly. Can you provide a diff?

Comment by Jochen Bayer [ 09/Jun/10 ]

Index: Mssql.php
===================================================================
— Mssql.php (Revision 49)
+++ Mssql.php (Arbeitskopie)
@@ -89,14 +89,14 @@
*/
public function listTableColumns($table)
{

  • $sql = 'EXEC sp_primary_keys_rowset @table_name = ' . $this->conn->quoteIdentifier($table, true);
    + $sql = 'EXEC sp_primary_keys_rowset @table_name = [' . $this->conn->quoteIdentifier($table, true) . ']';
    $result = $this->conn->fetchAssoc($sql);
    $primary = array();
    foreach ($result as $key => $val) { $primary[] = $val['COLUMN_NAME']; }
  • $sql = 'EXEC sp_columns @table_name = ' . $this->conn->quoteIdentifier($table, true);
    + $sql = 'EXEC sp_columns @table_name = [' . $this->conn->quoteIdentifier($table, true) . ']';
    $result = $this->conn->fetchAssoc($sql);
    $columns = array();

@@ -216,10 +216,10 @@
}
}
$table = $this->conn->quote($table, 'text');

  • $query = 'EXEC sp_statistics @table_name = ' . $table;
    + $query = 'EXEC sp_statistics @table_name = [' . $table . ']';
    $indexes = $this->conn->fetchColumn($query, $keyName);
  • $query = 'EXEC sp_pkeys @table_name = ' . $table;
    + $query = 'EXEC sp_pkeys @table_name = [' . $table. ']';
    $pkAll = $this->conn->fetchColumn($query, $pkName);

$result = array();

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

You need to turn on identifier quoting. The quoteIdentifier() method takes care of this for you.

http://www.doctrine-project.org/documentation/manual/1_2/en/configuration:identifier-quoting

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

Fixed missed calls to quoteIdentifier(). Turn on identifier quoting for identifiers to be wrapped with []

http://www.doctrine-project.org/documentation/manual/1_2/en/configuration:identifier-quoting

Comment by T. B. [ 24/Mar/11 ]

I have a similar problem after executing the symfony (1.4.10) build-schema task:

SQLSTATE[42000]: Syntax error or access violation: 2812 [Microsoft][SQL Server Native Client 10.0][SQL Server]Could not find stored procedure 'sp_primary_keys_rowset'. (SQLExecute[2812] at ext\pdo_odbc\odbc_stmt.c:254). Failing Query: "EXEC sp_primary_keys_rowset @table_name = Appointment"

I hoped ATTR_QUOTE_IDENTIFIER will also solve my problem but I get the sama error message:

SQLSTATE[42000]: Syntax error or access violation: 2812 [Microsoft][SQL Server Native Client 10.0][SQL Server]Could not find stored procedure 'sp_primary_keys_rowset'. (SQLExecute[2812] at ext\pdo_odbc\odbc_stmt.c:254). Failing Query: "EXEC sp_primary_keys_rowset @table_name = [Appointment]"





[DC-405] isset() via array interface does not "see" virtual property defined inside the model definition Created: 07/Jan/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

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

Type: Bug Priority: Minor
Reporter: Lukas Kahwe Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None


 Description   

see: http://groups.google.com/group/doctrine-user/browse_thread/thread/0e41373cd46872a9?hl=en#

The issue is caused by isset() returning false if the column is not setup via hasColumn(). The accessor method is however obviously callable on the OO interface. By preventing the isset() call in contains() we got things to work, but its obviously a hack:

class entity extends base_entity 
{ 
    public function setUp() 
    { 
        parent::setUp(); 
        $this->hasAccessor('urlizedname', 'getUrlizedname'); 
    } 
    public function getUrlizedid() 
    { 
        $type = $this->_get('type'); 
        return $this->_get('id') - $type::$idOffset; 
    } 
    /** 
     * FIXME:hackish override of the parent contains method to support array access of urlizedid, remove if possible 
     */ 
    public function contains($fieldName) 
    { 
        if ($fieldName === 'urlizedid') { 
            return true; 
        } 
        return parent::contains($fieldName); 
    } 
}


 Comments   
Comment by Jordi Boggiano [ 08/Jan/10 ]

To specify a bit more, the problem is caused only if you call isset() to check if the var exists, which obviously is not really required in this case since we know it's there, but the template engine automatically adds it to prevent notices so it means we can't read the value unless we call $entity->getUrlizedid() manually, which is not so great. Other than that the arrayaccess interface still works if you don't check isset() first.

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

I've been looking at this, and I don't think we have any possible fix. This is just how it is, unfortunately. Doctrine 1 FTW

Comment by Reza Esmaili [ 23/Mar/10 ]

Why not extend contains() with this simple check?

if ($this->hasAccessor($fieldName) {
  return true;
}
Comment by Lukas Kahwe [ 24/Mar/10 ]

hmm .. that looks indeed like it would fix the issue. do not know the code in question well enough, but this should also not break anyones code .. would it?

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

I think that might work. Did anyone try it out against the test suite?

Comment by Reza Esmaili [ 24/Mar/10 ]

Tests against 1.2.1

Fails in:
Doctrine_I18n_TestCase
Doctrine_Cli_TestCase

Tests agains the current 1.2 branch from SVN:

Fails in:
Doctrine_I18n_TestCase
Doctrine_Cache_Db_TestCase

Those tests fail with and without the patch.

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

Thanks for the issue and the idea for the patch. It seems to work fine. Hopefully it has no adverse side effects.

Comment by Maciej Hołyszko [ 29/Mar/10 ]

Unfortunately, this patch caused problems in my case. Sample code:

if($invoker->contains('resource'))
{
	// ok, now we have value set, so do sth with it...
}
else
{	
	// no value set yet, do it for the first time
	$invoker->mapValue('resource', $someResourceValue);
}

Now the first condition is always met.

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

I have reverted the change for now. I don't quite understand your use case. Are you setting a hasAccessor() somewhere?

Comment by Maciej Hołyszko [ 29/Mar/10 ]

Yes, sorry I forgot to mention it, I must've thought it is obvious

I have something like:
$invoker->hasAccessorMutator('resource', 'getResource', 'setResource');

Thank you for reverting that change. An author of this ticket could become angry, but I'm sorry I think we have to find another solution.

Comment by Reza Esmaili [ 29/Mar/10 ]

Isn't contains() just for checking if the field exist - like isset()?
If so, you should check for an actual value and not for the existence of your field.

Alternatively, we could modify the patch with something like this (untested):

if ($this->hasAccessor($fieldName) && $this->$fieldName) {
  return true;
}
Comment by Reza Esmaili [ 29/Mar/10 ]

Just checked with the code-comment of contains() in Doctrine_Record

"test whether a field (column, mapped value, related component, accessor) is accessible by @see get()"

So, I guess the solution to the problem is still valid because the field is accessible. I think Maciej Hołyszko's code should be updated to check for an actual value and not just for the existence of the field itself.

If not - please reopen the issue. As of now it's included in the list of fixed issues of the 1.2.2 release which is no longer correct

Comment by Maciej Hołyszko [ 30/Mar/10 ]

Ok now I see that I did not notice hasMappedValue was introduced in rev [6730], which I should use now instead of contains() in my case.

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

The new method should take care of this problem now.





[DC-399] Doctrine_Record::unlink() causes records to be deleted Created: 05/Jan/10  Updated: 09/Jun/10  Resolved: 09/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Record, Relations
Affects Version/s: 1.2.1
Fix Version/s: 1.2.3

Type: Bug Priority: Minor
Reporter: Svetoslav Shterev Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 2
Labels: None
Environment:

Doctrine 1.2.1 svn tag checkout (http://svn.doctrine-project.org/tags/1.2.1)


Attachments: File DC399TestCase.php     Text File UnitOfWork.php.patch    

 Description   

Using Doctrine_Record::unlink() with a One-To-Many relationship, and then saving the record will cause the related records to be deleted. Example:

You have a Discount linked to many Customers. When revoking a customer's discount, you unlink the two records. $discount->unlink("Customers", $customer->id); if you $discount->save(); afterwards, the Customer will be deleted.

This seems to be caused due to unlink() not calling the collection's takeSnapshot() after removing the records, which causes the save to delete them. There didn't seem to be a clear indication in the documentation it would do this, and in version 1.0.14 it doesn't seem to do this deletion.



 Comments   
Comment by Svetoslav Shterev [ 05/Jan/10 ]

Testcase attached

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

Is this not the expected behavior?

Comment by Svetoslav Shterev [ 02/Mar/10 ]

Re-uploaded the test case to show the actually reported bug(oops :/)

unlink() even with $now=true only sets the foreign key in Phonenumber to null. But if you do unlink(), and then save(), the phonenumber is outright deleted.

I'm not sure if it is expected behavior, since in the example (Discounts and Customers), it deletes the customer record outright

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

Can you provide a patch? All i see is the full UnitOfWork file and I believe it has changed since you attached your version.

Comment by Steffen Zeidler [ 09/Jun/10 ]

patch for UnitOfWork.php

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

Thanks for the issue, patch and test case! All looks good and I committed it!





[DC-391] Connection rollback exception hides real exception Created: 01/Jan/10  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Connection
Affects Version/s: 1.1.6
Fix Version/s: 1.2.3

Type: Bug Priority: Major
Reporter: Alessandro Vermeulen Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None


 Description   

Same issue as http://www.doctrine-project.org/jira/browse/DC-207 but also for the 1.1 branch.

This is not only an issue when trying to talk to non-existing databases but for any exception thrown by the connection.

UnitOfWork.php:133

        } catch (Exception $e) {
            // Make sure we roll back our internal transaction
            //$record->state($state);
            $conn->rollback();
            throw $e;
        }


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

This is already fixed in Doctrine 1.2





[DC-364] Adding the fieldName to mutator/accessor methods Created: 16/Dec/09  Updated: 08/Jun/10  Resolved: 08/Jun/10

Status: Resolved
Project: Doctrine 1
Component/s: Record
Affects Version/s: 1.2.1
Fix Version/s: 1.2.3

Type: Improvement Priority: Minor
Reporter: Carl Alexander Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 1
Labels: None

Attachments: Text File accessor_mutator_fieldName.patch    

 Description   

Would it be possible to add the fieldName to the function variables when a mutator or accessor function is called. This would allow for more generalized accessor / mutator functions that can be reused easily.

example:

public function mutatorFunction($value, $load, $fieldName)
{
$this->_set($fieldName, someValidationFunction($value));
}

Thank you



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

I don't understand the issue. Can you please provide more valid information, detailed descriptions, sample code, test cases, etc.

Comment by Andrew Coulton [ 05/Apr/10 ]

I believe the issue is that there may be logic common to mutators across a whole record (or indeed a set of records). A classic example would be date fields. Doctrine uses the ISO 8601 format for dates entirely, but often we want a date field to be accessible directly as a DateTime object or a UNIX timestamp to allow use in date formatting functions without additional strtotime overhead. Or we might want the application to automatically convert a user-provided date from a locale based format to ISO 8601.

The current pattern requires a custom accessor/mutator to each field - therefore a record with three date fields would have three accessors and mutators. I believe the OP's suggestion is that the fieldName be added as a parameter to the call to the accessor/mutator so that a single accessor/mutator pair could serve multiple fields. Indeed, we could then write a behaviour that includes the accessor/mutator and registers it on all date fields, or whatever other recurring field type/name/etc we have in the application.

As PHP will allow a function to be called with additional arguments, I believe all that's needed is to add the parameter to the calls in Doctrine_Record::set and Doctrine_Record::get as per the attached patch. Mutators/Accessors that care would then be able to access the field name as a second parameter, but without breaking any existing code.

Comment by Andrew Coulton [ 05/Apr/10 ]

Suggested patch to Doctrine_Record to implement this improvement

Comment by Carl Alexander [ 06/Apr/10 ]

Sorry meant to get back to you about this sooner.

What Andrew has described is exactly the issue I am having and his suggested patch is what I would hope to see implemented.





[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-349] Issue with quoting of booleans in Oracle Created: 10/Dec/09  Updated: 08/Jun/10  Resolved: 08/Jun/10

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

Type: Bug Priority: Major
Reporter: David Brewer Assignee: Jonathan H. Wage
Resolution: Fixed Votes: 0
Labels: None
Environment:

Oracle Driver



 Description   

I've run into a problem where I'm unable to set a default value for a boolean field when creating Oracle tables using Doctrine. The summary of this issue is that when Doctrine quotes a '0' or false value for a boolean field, it comes back blank... which doesn't work as an option for the DEFAULT parameter of field definitions in table creation.

Here are the hairy details. I have a schema which looks something like this (simplified for the example):

User:

  tableName: users

  columns:

    id: {type: integer, primary: true, autoincrement: true}

    username: {type: string, length: 255, unique: true, notnull: true}

    disabled: {type: boolean, default: false}

When I export the table, it's generating invalid DDL like this:

CREATE TABLE users (id NUMBER(8), username VARCHAR2(255) NOT NULL UNIQUE, disabled NUMBER(1) DEFAULT , PRIMARY KEY(id))

The important part of this is the DEFAULT parameter on the 'disabled' field definition – it's missing the value. I tracked all this down to an issue in Doctrine_Formatter->quote(), which gets called when the DDL is being created. When passed '0' as its input with a type of boolean, it apparently returns the empty string. This may ultimately be a bug in the PDO_OCI driver, because the quote method is relying on the quote method of the database handle.

I've hacked around this by overriding the behaviour of the quote method in Doctrine_Connection_Oracle. I did this by adding the following method:

    /**
     * Override quote behaviour for boolean to fix issues with quoting of
     * boolean values.
     */
    public function quote($input, $type = null)
    {
        if ($type === 'boolean') {
            if ($input === null) {
                return null;
            } else {
                return (($input) ? 1 : 0);    
            }
      } else {
        return parent::quote($input, $type);  
      }
    }

It seems to work for me, but I'd appreciate it if wiser heads than me would take a look and see if this is a sign of some larger issue, and if the way I've fixed it seems appropriate. I also am unsure of how/if this needs to propogate to newer versions.






Generated at Tue Jul 29 00:53:34 UTC 2014 using JIRA 6.2.3#6260-sha1:63ef1d6dac3f4f4d7db4c1effd405ba38ccdc558.