2006-10-16 14:04:38 +04:00
< ? php
/*
* $Id $
*
* 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 . phpdoctrine . com >.
*/
/**
* Doctrine_Query
*
* @ package Doctrine ORM
* @ url www . phpdoctrine . com
* @ license LGPL
*/
class Doctrine_Query extends Doctrine_Hydrate implements Countable {
2006-10-18 23:55:14 +04:00
/**
* QUERY TYPE CONSTANTS
*/
/**
* constant for SELECT queries
*/
2006-10-18 21:37:20 +04:00
const SELECT = 0 ;
2006-10-18 23:55:14 +04:00
/**
* constant for DELETE queries
*/
2006-10-18 21:37:20 +04:00
const DELETE = 1 ;
2006-10-18 23:55:14 +04:00
/**
* constant for UPDATE queries
*/
2006-10-18 21:37:20 +04:00
const UPDATE = 2 ;
2006-10-16 14:04:38 +04:00
/**
* @ param array $subqueryAliases the table aliases needed in some LIMIT subqueries
*/
private $subqueryAliases = array ();
/**
* @ param boolean $needsSubquery
*/
private $needsSubquery = false ;
/**
* @ param boolean $limitSubqueryUsed
*/
private $limitSubqueryUsed = false ;
private $tableStack ;
private $relationStack = array ();
private $isDistinct = false ;
2006-10-17 21:21:21 +04:00
private $pendingFields = array ();
2006-10-18 23:55:14 +04:00
/**
* @ var integer $type the query type
*
* @ see Doctrine_Query ::* constants
*/
2006-10-18 21:37:20 +04:00
protected $type = self :: SELECT ;
2006-10-16 14:04:38 +04:00
/**
* create
* returns a new Doctrine_Query object
*
* @ return Doctrine_Query
*/
public static function create () {
return new Doctrine_Query ();
}
public function getTableStack () {
return $this -> tableStack ;
}
public function getRelationStack () {
return $this -> relationStack ;
}
public function isDistinct ( $distinct = null ) {
if ( isset ( $distinct ))
$this -> isDistinct = ( bool ) $distinct ;
return $this -> isDistinct ;
}
2006-10-17 21:21:21 +04:00
public function processPendingFields ( $componentAlias ) {
$tableAlias = $this -> getTableAlias ( $componentAlias );
$componentPath = $this -> compAliases [ $componentAlias ];
if ( ! isset ( $this -> components [ $componentPath ]))
throw new Doctrine_Query_Exception ( 'Unknown component path ' . $componentPath );
$table = $this -> components [ $componentPath ];
if ( isset ( $this -> pendingFields [ $componentAlias ])) {
$fields = $this -> pendingFields [ $componentAlias ];
if ( in_array ( '*' , $fields ))
$fields = $table -> getColumnNames ();
else
$fields = array_unique ( array_merge ( $table -> getPrimaryKeys (), $fields ));
}
foreach ( $fields as $name ) {
$this -> parts [ " select " ][] = $tableAlias . '.' . $name . ' AS ' . $tableAlias . '__' . $name ;
}
}
public function parseSelect ( $dql ) {
$refs = Doctrine_Query :: bracketExplode ( $dql , ',' );
foreach ( $refs as $reference ) {
if ( strpos ( $reference , '(' ) !== false ) {
$this -> parseAggregateFunction2 ( $reference );
} else {
$e = explode ( '.' , $reference );
if ( count ( $e ) > 2 )
$this -> pendingFields [] = $reference ;
else
$this -> pendingFields [ $e [ 0 ]][] = $e [ 1 ];
}
}
}
public function parseAggregateFunction2 ( $func ) {
2006-10-18 01:18:57 +04:00
$e = Doctrine_Query :: bracketExplode ( $func , ' ' );
$func = $e [ 0 ];
2006-10-17 21:21:21 +04:00
$pos = strpos ( $func , '(' );
$name = substr ( $func , 0 , $pos );
switch ( $name ) {
case 'MAX' :
case 'MIN' :
case 'COUNT' :
case 'AVG' :
$reference = substr ( $func , ( $pos + 1 ), - 1 );
2006-10-18 01:18:57 +04:00
$parts = explode ( '.' , $reference );
$alias = ( isset ( $e [ 1 ])) ? $e [ 1 ] : $name ;
$this -> pendingAggregates [ $parts [ 0 ]][] = array ( $alias , $parts [ 1 ]);
2006-10-17 21:21:21 +04:00
break ;
default :
throw new Doctrine_Query_Exception ( 'Unknown aggregate function ' . $name );
}
}
public function processPendingAggregates ( $componentAlias ) {
$tableAlias = $this -> getTableAlias ( $componentAlias );
$componentPath = $this -> compAliases [ $componentAlias ];
if ( ! isset ( $this -> components [ $componentPath ]))
throw new Doctrine_Query_Exception ( 'Unknown component path ' . $componentPath );
$table = $this -> components [ $componentPath ];
foreach ( $this -> pendingAggregates [ $componentAlias ] as $args ) {
list ( $name , $arg ) = $args ;
$this -> parts [ " select " ][] = $name . '(' . $tableAlias . '.' . $arg . ') AS ' . $tableAlias . '__' . count ( $this -> aggregateMap );
$this -> aggregateMap [] = $table ;
}
}
/**
* count
2006-10-16 14:04:38 +04:00
*
2006-10-17 21:21:21 +04:00
* @ return integer
2006-10-16 14:04:38 +04:00
*/
2006-10-17 21:21:21 +04:00
public function count ( Doctrine_Table $table , $params = array ()) {
$this -> remove ( 'select' );
$join = $this -> join ;
$where = $this -> where ;
$having = $this -> having ;
$q = " SELECT COUNT(DISTINCT " . $table -> getTableName () . '.' . $table -> getIdentifier () . " ) FROM " . $table -> getTableName () . " " ;
foreach ( $join as $j ) {
2006-10-17 21:23:59 +04:00
$q .= ' ' . implode ( " " , $j );
2006-10-17 21:21:21 +04:00
}
2006-10-16 14:04:38 +04:00
$string = $this -> applyInheritance ();
if ( ! empty ( $where )) {
$q .= " WHERE " . implode ( " AND " , $where );
if ( ! empty ( $string ))
$q .= " AND ( " . $string . " ) " ;
} else {
if ( ! empty ( $string ))
$q .= " WHERE ( " . $string . " ) " ;
}
2006-10-17 21:21:21 +04:00
if ( ! empty ( $having ))
$q .= " HAVING " . implode ( ' AND ' , $having );
2006-10-16 14:04:38 +04:00
2006-10-17 21:21:21 +04:00
$a = $this -> getConnection () -> execute ( $q , $params ) -> fetch ( PDO :: FETCH_NUM );
return $a [ 0 ];
}
2006-10-16 14:04:38 +04:00
/**
* loadFields
* loads fields for a given table and
* constructs a little bit of sql for every field
*
* fields of the tables become : [ tablename ] . [ fieldname ] as [ tablename ] __ [ fieldname ]
*
* @ access private
* @ param object Doctrine_Table $table a Doctrine_Table object
* @ param integer $fetchmode fetchmode the table is using eg . Doctrine :: FETCH_LAZY
* @ param array $names fields to be loaded ( only used in lazy property loading )
* @ return void
*/
protected function loadFields ( Doctrine_Table $table , $fetchmode , array $names , $cpath ) {
$name = $table -> getComponentName ();
switch ( $fetchmode ) :
case Doctrine :: FETCH_OFFSET :
$this -> limit = $table -> getAttribute ( Doctrine :: ATTR_COLL_LIMIT );
case Doctrine :: FETCH_IMMEDIATE :
if ( ! empty ( $names ))
$names = array_unique ( array_merge ( $table -> getPrimaryKeys (), $names ));
else
$names = $table -> getColumnNames ();
break ;
case Doctrine :: FETCH_LAZY_OFFSET :
$this -> limit = $table -> getAttribute ( Doctrine :: ATTR_COLL_LIMIT );
case Doctrine :: FETCH_LAZY :
case Doctrine :: FETCH_BATCH :
$names = array_unique ( array_merge ( $table -> getPrimaryKeys (), $names ));
break ;
default :
throw new Doctrine_Exception ( " Unknown fetchmode. " );
endswitch ;
$component = $table -> getComponentName ();
$tablename = $this -> tableAliases [ $cpath ];
$this -> fetchModes [ $tablename ] = $fetchmode ;
$count = count ( $this -> tables );
foreach ( $names as $name ) {
if ( $count == 0 ) {
$this -> parts [ " select " ][] = $tablename . " . " . $name ;
} else {
$this -> parts [ " select " ][] = $tablename . " . " . $name . " AS " . $tablename . " __ " . $name ;
}
}
}
/**
* addFrom
*
* @ param strint $from
*/
public function addFrom ( $from ) {
$class = " Doctrine_Query_From " ;
$parser = new $class ( $this );
$parser -> parse ( $from );
}
/**
* addWhere
*
* @ param string $where
* @ param mixed $params
*/
public function addWhere ( $where , $params = array ()) {
$class = " Doctrine_Query_Where " ;
$parser = new $class ( $this );
$this -> parts [ 'where' ][] = $parser -> parse ( $where );
if ( is_array ( $params )) {
$this -> params = array_merge ( $this -> params , $params );
} else {
$this -> params [] = $params ;
}
}
/**
* sets a query part
*
* @ param string $name
* @ param array $args
* @ return void
*/
public function __call ( $name , $args ) {
$name = strtolower ( $name );
2006-10-18 21:37:20 +04:00
if ( $name == 'select' )
2006-10-17 21:21:21 +04:00
2006-10-16 14:04:38 +04:00
2006-10-18 21:37:20 +04:00
$method = " parse " . ucwords ( $name );
switch ( $name ) {
case 'select' :
$this -> type = self :: SELECT ;
if ( ! isset ( $args [ 0 ]))
throw new Doctrine_Query_Exception ( 'Empty select part' );
$this -> parseSelect ( $args [ 0 ]);
break ;
case 'delete' :
$this -> type = self :: DELETE ;
break ;
case 'update' :
$this -> type = self :: UPDATE ;
2006-10-18 23:55:14 +04:00
$name = 'from' ;
2006-10-18 21:37:20 +04:00
case 'from' :
$this -> parts [ 'from' ] = array ();
$this -> parts [ 'select' ] = array ();
$this -> parts [ 'join' ] = array ();
$this -> joins = array ();
$this -> tables = array ();
$this -> fetchModes = array ();
$this -> tableIndexes = array ();
$this -> tableAliases = array ();
$class = " Doctrine_Query_ " . ucwords ( $name );
$parser = new $class ( $this );
$parser -> parse ( $args [ 0 ]);
break ;
case 'where' :
case 'having' :
case 'orderby' :
case 'groupby' :
$class = " Doctrine_Query_ " . ucwords ( $name );
$parser = new $class ( $this );
$this -> parts [ $name ] = array ( $parser -> parse ( $args [ 0 ]));
break ;
case 'limit' :
case 'offset' :
if ( $args [ 0 ] == null )
$args [ 0 ] = false ;
$this -> parts [ $name ] = $args [ 0 ];
break ;
default :
$this -> parts [ $name ] = array ();
$this -> $method ( $args [ 0 ]);
2006-10-16 14:04:38 +04:00
throw new Doctrine_Query_Exception ( " Unknown overload method " );
2006-10-18 21:37:20 +04:00
}
2006-10-16 14:04:38 +04:00
return $this ;
}
/**
* returns a query part
*
* @ param $name query part name
* @ return mixed
*/
public function get ( $name ) {
if ( ! isset ( $this -> parts [ $name ]))
return false ;
return $this -> parts [ $name ];
}
/**
* sets a query part
*
* @ param $name query part name
* @ param $value query part value
* @ return boolean
*/
public function set ( $name , $value ) {
2006-10-18 23:55:14 +04:00
/**
2006-10-16 14:04:38 +04:00
if ( isset ( $this -> parts [ $name ])) {
$method = " parse " . ucwords ( $name );
switch ( $name ) :
case " where " :
case " having " :
$this -> parts [ $name ] = array ( $this -> $method ( $value ));
break ;
case " limit " :
2006-10-27 01:32:52 +04:00
case " offset " :
2006-10-16 14:04:38 +04:00
if ( $value == null )
$value = false ;
$this -> parts [ $name ] = $value ;
break ;
case " from " :
$this -> parts [ 'select' ] = array ();
$this -> parts [ 'join' ] = array ();
$this -> joins = array ();
$this -> tables = array ();
$this -> fetchModes = array ();
$this -> tableIndexes = array ();
$this -> tableAliases = array ();
default :
$this -> parts [ $name ] = array ();
$this -> $method ( $value );
endswitch ;
return true ;
}
return false ;
2006-10-18 23:55:14 +04:00
*/
$class = new Doctrine_Query_Set ( $this );
$class -> parse ( $name , $value );
2006-10-16 14:04:38 +04:00
}
/**
* @ return boolean
*/
public function isLimitSubqueryUsed () {
return $this -> limitSubqueryUsed ;
}
2006-10-18 21:37:20 +04:00
public function getQueryBase () {
switch ( $this -> type ) {
case self :: DELETE :
$q = 'DELETE FROM ' ;
break ;
case self :: UPDATE :
$q = 'UPDATE ' ;
break ;
case self :: SELECT :
$distinct = ( $this -> isDistinct ()) ? 'DISTINCT ' : '' ;
$q = 'SELECT ' . $distinct . implode ( ', ' , $this -> parts [ 'select' ]) . ' FROM ' ;
break ;
}
return $q ;
}
2006-10-16 14:04:38 +04:00
/**
* returns the built sql query
*
* @ return string
*/
2006-10-27 02:12:58 +04:00
public function getQuery ( $executeSubquery = false ) {
2006-10-16 14:04:38 +04:00
if ( empty ( $this -> parts [ " select " ]) || empty ( $this -> parts [ " from " ]))
return false ;
$needsSubQuery = false ;
$subquery = '' ;
$k = array_keys ( $this -> tables );
$table = $this -> tables [ $k [ 0 ]];
if ( ! empty ( $this -> parts [ 'limit' ]) && $this -> needsSubquery && $table -> getAttribute ( Doctrine :: ATTR_QUERY_LIMIT ) == Doctrine :: LIMIT_RECORDS ) {
$needsSubQuery = true ;
$this -> limitSubqueryUsed = true ;
}
// build the basic query
2006-10-18 21:37:20 +04:00
2006-10-16 14:04:38 +04:00
$str = '' ;
if ( $this -> isDistinct ())
$str = 'DISTINCT ' ;
2006-10-27 01:32:52 +04:00
$q = 'SELECT ' . $str . implode ( " , " , $this -> parts [ " select " ]) .
' FROM ' ;
2006-10-18 21:37:20 +04:00
$q = $this -> getQueryBase ();
2006-10-16 14:04:38 +04:00
foreach ( $this -> parts [ " from " ] as $tname => $bool ) {
$a [] = $tname ;
}
$q .= implode ( " , " , $a );
if ( ! empty ( $this -> parts [ 'join' ])) {
foreach ( $this -> parts [ 'join' ] as $part ) {
$q .= " " . implode ( ' ' , $part );
}
}
$string = $this -> applyInheritance ();
if ( ! empty ( $string ))
$this -> parts [ 'where' ][] = '(' . $string . ')' ;
2006-10-27 01:32:52 +04:00
2006-10-16 14:04:38 +04:00
$modifyLimit = true ;
if ( ! empty ( $this -> parts [ " limit " ]) || ! empty ( $this -> parts [ " offset " ])) {
if ( $needsSubQuery ) {
2006-10-27 01:32:52 +04:00
$subquery = $this -> getLimitSubquery ();
2006-10-16 14:04:38 +04:00
$dbh = $this -> connection -> getDBH ();
// mysql doesn't support LIMIT in subqueries
2006-10-27 03:05:55 +04:00
switch ( $dbh -> getAttribute ( PDO :: ATTR_DRIVER_NAME )) {
case 'mysql' :
$list = $dbh -> query ( $subquery ) -> fetchAll ( PDO :: FETCH_COLUMN );
$subquery = implode ( ', ' , $list );
break ;
case 'pgsql' :
$subquery = 'SELECT doctrine_subquery_alias.' . $table -> getIdentifier () . ' FROM (' . $subquery . ') AS doctrine_subquery_alias' ;
break ;
2006-10-27 02:12:58 +04:00
}
2006-10-16 14:04:38 +04:00
2006-10-27 03:20:01 +04:00
$field = $table -> getTableName () . '.' . $table -> getIdentifier ();
2006-10-27 03:05:55 +04:00
array_unshift ( $this -> parts [ 'where' ], $field . ' IN (' . $subquery . ')' );
2006-10-16 14:04:38 +04:00
$modifyLimit = false ;
}
}
2006-10-27 01:32:52 +04:00
$q .= ( ! empty ( $this -> parts [ 'where' ])) ? ' WHERE ' . implode ( ' AND ' , $this -> parts [ 'where' ]) : '' ;
$q .= ( ! empty ( $this -> parts [ 'groupby' ])) ? ' GROUP BY ' . implode ( ', ' , $this -> parts [ 'groupby' ]) : '' ;
$q .= ( ! empty ( $this -> parts [ 'having' ])) ? ' HAVING ' . implode ( ' ' , $this -> parts [ 'having' ]) : '' ;
$q .= ( ! empty ( $this -> parts [ 'orderby' ])) ? ' ORDER BY ' . implode ( ' ' , $this -> parts [ 'orderby' ]) : '' ;
2006-10-16 14:04:38 +04:00
if ( $modifyLimit )
2006-10-27 01:32:52 +04:00
$q = $this -> connection -> modifyLimitQuery ( $q , $this -> parts [ 'limit' ], $this -> parts [ 'offset' ]);
2006-10-16 14:04:38 +04:00
// return to the previous state
if ( ! empty ( $string ))
array_pop ( $this -> parts [ 'where' ]);
if ( $needsSubQuery )
array_shift ( $this -> parts [ 'where' ]);
return $q ;
}
2006-10-27 01:32:52 +04:00
/**
* 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
*/
public function getLimitSubquery () {
2006-11-01 20:08:35 +03:00
$k = array_keys ( $this -> tables );
$table = $this -> tables [ $k [ 0 ]];
$primaryKey = $table -> getTableName () . '.' . $table -> getIdentifier ();
// initialize the base of the subquery
$subquery = 'SELECT DISTINCT ' . $primaryKey ;
2006-10-27 01:32:52 +04:00
2006-10-27 03:20:01 +04:00
if ( $this -> connection -> getDBH () -> getAttribute ( PDO :: ATTR_DRIVER_NAME ) == 'pgsql' ) {
2006-11-01 20:08:35 +03:00
// pgsql needs the order by fields to be preserved in select clause
2006-10-27 03:20:01 +04:00
foreach ( $this -> parts [ 'orderby' ] as $part ) {
$e = explode ( ' ' , $part );
2006-11-01 20:08:35 +03:00
// don't add primarykey column (its already in the select clause)
if ( $e [ 0 ] !== $primaryKey )
$subquery .= ', ' . $e [ 0 ];
2006-10-27 03:20:01 +04:00
}
}
$subquery .= ' FROM ' . $table -> getTableName ();
2006-10-27 01:32:52 +04:00
foreach ( $this -> parts [ 'join' ] as $parts ) {
foreach ( $parts as $part ) {
// preserve LEFT JOINs only if needed
if ( substr ( $part , 0 , 9 ) === 'LEFT JOIN' ) {
$e = explode ( ' ' , $part );
if ( ! in_array ( $e [ 2 ], $this -> subqueryAliases ))
continue ;
}
$subquery .= ' ' . $part ;
}
}
// all conditions must be preserved in subquery
$subquery .= ( ! empty ( $this -> parts [ 'where' ])) ? ' WHERE ' . implode ( ' AND ' , $this -> parts [ 'where' ]) : '' ;
$subquery .= ( ! empty ( $this -> parts [ 'groupby' ])) ? ' GROUP BY ' . implode ( ', ' , $this -> parts [ 'groupby' ]) : '' ;
$subquery .= ( ! empty ( $this -> parts [ 'having' ])) ? ' HAVING ' . implode ( ' ' , $this -> parts [ 'having' ]) : '' ;
2006-10-27 02:12:58 +04:00
$subquery .= ( ! empty ( $this -> parts [ 'orderby' ])) ? ' ORDER BY ' . implode ( ' ' , $this -> parts [ 'orderby' ]) : '' ;
2006-10-27 03:05:55 +04:00
2006-10-27 01:32:52 +04:00
// add driver specific limit clause
$subquery = $this -> connection -> modifyLimitQuery ( $subquery , $this -> parts [ 'limit' ], $this -> parts [ 'offset' ]);
return $subquery ;
}
2006-10-16 14:04:38 +04:00
/**
* query the database with DQL ( Doctrine Query Language )
*
* @ param string $query DQL query
* @ param array $params parameters
*/
public function query ( $query , $params = array ()) {
$this -> parseQuery ( $query );
if ( $this -> aggregate ) {
$keys = array_keys ( $this -> tables );
$query = $this -> getQuery ();
$stmt = $this -> tables [ $keys [ 0 ]] -> getConnection () -> select ( $query , $this -> parts [ " limit " ], $this -> parts [ " offset " ]);
$data = $stmt -> fetch ( PDO :: FETCH_ASSOC );
if ( count ( $data ) == 1 ) {
return current ( $data );
} else {
return $data ;
}
} else {
return $this -> execute ( $params );
}
}
/**
2006-10-31 02:00:09 +03:00
* splitQuery
* splits the given dql query into an array where keys
* represent different query part names and values are
* arrays splitted using sqlExplode method
*
* example :
*
* parameter :
* $query = " SELECT u.* FROM User u WHERE u.name LIKE ? "
* returns :
* array ( 'select' => array ( 'u.*' ),
* 'from' => array ( 'User' , 'u' ),
* 'where' => array ( 'u.name' , 'LIKE' , '?' ))
2006-10-16 14:04:38 +04:00
*
* @ param string $query DQL query
* @ throws Doctrine_Query_Exception if some generic parsing error occurs
2006-10-31 02:00:09 +03:00
* @ return array an array containing the query string parts
2006-10-16 14:04:38 +04:00
*/
2006-10-31 02:00:09 +03:00
public function splitQuery ( $query ) {
$e = self :: sqlExplode ( $query , ' ' );
2006-10-16 14:04:38 +04:00
foreach ( $e as $k => $part ) {
$part = trim ( $part );
switch ( strtolower ( $part )) {
2006-10-18 21:37:20 +04:00
case 'delete' :
2006-10-18 23:55:14 +04:00
case 'update' :
2006-10-18 21:37:20 +04:00
case 'select' :
2006-10-18 23:55:14 +04:00
case 'set' :
2006-10-18 21:37:20 +04:00
case 'from' :
case 'where' :
case 'limit' :
case 'offset' :
case 'having' :
2006-10-16 14:04:38 +04:00
$p = $part ;
$parts [ $part ] = array ();
break ;
2006-10-18 21:37:20 +04:00
case 'order' :
case 'group' :
2006-10-16 14:04:38 +04:00
$i = ( $k + 1 );
if ( isset ( $e [ $i ]) && strtolower ( $e [ $i ]) === " by " ) {
$p = $part ;
$parts [ $part ] = array ();
} else
$parts [ $p ][] = $part ;
break ;
case " by " :
continue ;
default :
if ( ! isset ( $p ))
throw new Doctrine_Query_Exception ( " Couldn't parse query. " );
$parts [ $p ][] = $part ;
}
}
2006-10-31 02:00:09 +03:00
return $parts ;
}
/**
* 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 parseQuery ( $query , $clear = true ) {
if ( $clear )
$this -> clear ();
$query = trim ( $query );
$query = str_replace ( " \n " , " " , $query );
$query = str_replace ( " \r " , " " , $query );
$parts = $this -> splitQuery ( $query );
2006-10-16 14:04:38 +04:00
foreach ( $parts as $k => $part ) {
$part = implode ( " " , $part );
switch ( strtoupper ( $k )) {
2006-10-18 21:37:20 +04:00
case 'DELETE' :
$this -> type = self :: DELETE ;
break ;
2006-10-18 23:55:14 +04:00
2006-10-18 21:37:20 +04:00
case 'SELECT' :
$this -> type = self :: SELECT ;
2006-10-16 14:04:38 +04:00
$this -> parseSelect ( $part );
break ;
2006-10-18 23:55:14 +04:00
case 'UPDATE' :
$this -> type = self :: UPDATE ;
$k = 'FROM' ;
2006-10-18 21:37:20 +04:00
case 'FROM' :
2006-10-18 23:55:14 +04:00
case 'SET' :
$class = 'Doctrine_Query_' . ucwords ( strtolower ( $k ));
2006-10-16 14:04:38 +04:00
$parser = new $class ( $this );
$parser -> parse ( $part );
break ;
2006-10-18 21:37:20 +04:00
case 'GROUP' :
case 'ORDER' :
2006-10-16 14:04:38 +04:00
$k .= " by " ;
2006-10-18 21:37:20 +04:00
case 'WHERE' :
case 'HAVING' :
2006-10-16 14:04:38 +04:00
$class = " Doctrine_Query_ " . ucwords ( strtolower ( $k ));
$parser = new $class ( $this );
$name = strtolower ( $k );
$this -> parts [ $name ][] = $parser -> parse ( $part );
break ;
2006-10-18 21:37:20 +04:00
case 'LIMIT' :
2006-10-16 14:04:38 +04:00
$this -> parts [ " limit " ] = trim ( $part );
break ;
2006-10-18 21:37:20 +04:00
case 'OFFSET' :
2006-10-16 14:04:38 +04:00
$this -> parts [ " offset " ] = trim ( $part );
break ;
}
}
return $this ;
}
/**
* DQL ORDER BY PARSER
* parses the order by part of the query string
*
* @ param string $str
* @ return void
*/
final public function parseOrderBy ( $str ) {
$parser = new Doctrine_Query_Part_Orderby ( $this );
return $parser -> parse ( $str );
}
/**
* returns Doctrine :: FETCH_ * constant
*
* @ param string $mode
* @ return integer
*/
final public function parseFetchMode ( $mode ) {
switch ( strtolower ( $mode )) :
case " i " :
case " immediate " :
$fetchmode = Doctrine :: FETCH_IMMEDIATE ;
break ;
case " b " :
case " batch " :
$fetchmode = Doctrine :: FETCH_BATCH ;
break ;
case " l " :
case " lazy " :
$fetchmode = Doctrine :: FETCH_LAZY ;
break ;
case " o " :
case " offset " :
$fetchmode = Doctrine :: FETCH_OFFSET ;
break ;
case " lo " :
case " lazyoffset " :
$fetchmode = Doctrine :: FETCH_LAZYOFFSET ;
default :
throw new Doctrine_Query_Exception ( " Unknown fetchmode ' $mode '. The availible fetchmodes are 'i', 'b' and 'l'. " );
endswitch ;
return $fetchmode ;
}
/**
* trims brackets
*
* @ param string $str
* @ param string $e1 the first bracket , usually '('
* @ param string $e2 the second bracket , usually ')'
*/
public static function bracketTrim ( $str , $e1 = '(' , $e2 = ')' ) {
if ( substr ( $str , 0 , 1 ) == $e1 && substr ( $str , - 1 ) == $e2 )
return substr ( $str , 1 , - 1 );
else
return $str ;
}
/**
* bracketExplode
2006-10-31 02:00:09 +03:00
*
* example :
*
* parameters :
* $str = ( age < 20 AND age > 18 ) AND email LIKE 'John@example.com'
* $d = ' AND '
* $e1 = '('
* $e2 = ')'
*
2006-10-16 14:04:38 +04:00
* would return an array :
2006-10-31 02:00:09 +03:00
* array ( " (age < 20 AND age > 18) " ,
* " email LIKE 'John@example.com' " )
2006-10-16 14:04:38 +04:00
*
* @ param string $str
* @ param string $d the delimeter which explodes the string
* @ param string $e1 the first bracket , usually '('
* @ param string $e2 the second bracket , usually ')'
*
*/
2006-10-31 02:00:09 +03:00
public static function bracketExplode ( $str , $d = ' ' , $e1 = '(' , $e2 = ')' ) {
2006-10-16 14:04:38 +04:00
if ( is_array ( $d )) {
$a = preg_split ( '/(' . implode ( '|' , $d ) . ')/' , $str );
$d = stripslashes ( $d [ 0 ]);
} else
$a = explode ( " $d " , $str );
$i = 0 ;
$term = array ();
foreach ( $a as $key => $val ) {
if ( empty ( $term [ $i ])) {
$term [ $i ] = trim ( $val );
2006-10-31 02:00:09 +03:00
$s1 = substr_count ( $term [ $i ], " $e1 " );
$s2 = substr_count ( $term [ $i ], " $e2 " );
2006-10-16 14:04:38 +04:00
if ( $s1 == $s2 ) $i ++ ;
} else {
$term [ $i ] .= " $d " . trim ( $val );
2006-10-31 02:00:09 +03:00
$c1 = substr_count ( $term [ $i ], " $e1 " );
$c2 = substr_count ( $term [ $i ], " $e2 " );
2006-10-16 14:04:38 +04:00
if ( $c1 == $c2 ) $i ++ ;
}
}
return $term ;
}
/**
* sqlExplode
*
* explodes a string into array using custom brackets and
* quote delimeters
*
2006-10-31 02:00:09 +03:00
*
* example :
*
* parameters :
* $str = " (age < 20 AND age > 18) AND name LIKE 'John Doe' "
* $d = ' '
* $e1 = '('
* $e2 = ')'
*
* would return an array :
* array ( '(age < 20 AND age > 18)' ,
* 'name' ,
* 'LIKE' ,
* 'John Doe' )
*
2006-10-16 14:04:38 +04:00
* @ param string $str
* @ param string $d the delimeter which explodes the string
* @ param string $e1 the first bracket , usually '('
* @ param string $e2 the second bracket , usually ')'
*
* @ return array
*/
2006-10-31 02:00:09 +03:00
public static function sqlExplode ( $str , $d = ' ' , $e1 = '(' , $e2 = ')' ) {
2006-10-16 14:04:38 +04:00
if ( is_array ( $d )) {
$str = preg_split ( '/(' . implode ( '|' , $d ) . ')/' , $str );
$d = stripslashes ( $d [ 0 ]);
} else
$str = explode ( " $d " , $str );
$i = 0 ;
$term = array ();
foreach ( $str as $key => $val ) {
if ( empty ( $term [ $i ])) {
$term [ $i ] = trim ( $val );
$s1 = substr_count ( $term [ $i ], " $e1 " );
$s2 = substr_count ( $term [ $i ], " $e2 " );
if ( substr ( $term [ $i ], 0 , 1 ) == " ( " ) {
if ( $s1 == $s2 )
$i ++ ;
} else {
if ( ! ( substr_count ( $term [ $i ], " ' " ) & 1 ) &&
! ( substr_count ( $term [ $i ], " \" " ) & 1 ) &&
! ( substr_count ( $term [ $i ], " <EFBFBD> " ) & 1 )
) $i ++ ;
}
} else {
$term [ $i ] .= " $d " . trim ( $val );
$c1 = substr_count ( $term [ $i ], " $e1 " );
$c2 = substr_count ( $term [ $i ], " $e2 " );
if ( substr ( $term [ $i ], 0 , 1 ) == " ( " ) {
if ( $c1 == $c2 )
$i ++ ;
} else {
if ( ! ( substr_count ( $term [ $i ], " ' " ) & 1 ) &&
! ( substr_count ( $term [ $i ], " \" " ) & 1 ) &&
! ( substr_count ( $term [ $i ], " <EFBFBD> " ) & 1 )
) $i ++ ;
}
}
}
return $term ;
}
/**
* generateAlias
*
* @ param string $tableName
* @ return string
*/
public function generateAlias ( $tableName ) {
if ( isset ( $this -> tableIndexes [ $tableName ])) {
return $tableName .++ $this -> tableIndexes [ $tableName ];
} else {
$this -> tableIndexes [ $tableName ] = 1 ;
return $tableName ;
}
}
/**
* loads a component
*
* @ param string $path the path of the loadable component
* @ param integer $fetchmode optional fetchmode , if not set the components default fetchmode will be used
* @ throws Doctrine_Query_Exception
* @ return Doctrine_Table
*/
final public function load ( $path , $loadFields = true ) {
$tmp = explode ( ' ' , $path );
$componentAlias = ( count ( $tmp ) > 1 ) ? end ( $tmp ) : false ;
$e = preg_split ( " /[.:]/ " , $tmp [ 0 ], - 1 );
if ( isset ( $this -> compAliases [ $e [ 0 ]])) {
$end = substr ( $tmp [ 0 ], strlen ( $e [ 0 ]));
$path = $this -> compAliases [ $e [ 0 ]] . $end ;
$e = preg_split ( " /[.:]/ " , $path , - 1 );
} else
$path = $tmp [ 0 ];
$index = 0 ;
$currPath = '' ;
$this -> tableStack = array ();
foreach ( $e as $key => $fullname ) {
try {
$e2 = preg_split ( " /[-(]/ " , $fullname );
$name = $e2 [ 0 ];
$currPath .= " . " . $name ;
if ( $key == 0 ) {
$currPath = substr ( $currPath , 1 );
$table = $this -> connection -> getTable ( $name );
$tname = $table -> getTableName ();
if ( ! isset ( $this -> tableAliases [ $currPath ]))
$this -> tableIndexes [ $tname ] = 1 ;
$this -> parts [ " from " ][ $tname ] = true ;
$this -> tableAliases [ $currPath ] = $tname ;
$tableName = $tname ;
} else {
$index += strlen ( $e [( $key - 1 )]) + 1 ;
// the mark here is either '.' or ':'
$mark = substr ( $path ,( $index - 1 ), 1 );
if ( isset ( $this -> tableAliases [ $prevPath ])) {
$tname = $this -> tableAliases [ $prevPath ];
} else
$tname = $table -> getTableName ();
$fk = $table -> getRelation ( $name );
$name = $fk -> getTable () -> getComponentName ();
$original = $fk -> getTable () -> getTableName ();
if ( isset ( $this -> tableAliases [ $currPath ])) {
$tname2 = $this -> tableAliases [ $currPath ];
} else
$tname2 = $this -> generateAlias ( $original );
if ( $original !== $tname2 )
$aliasString = $original . " AS " . $tname2 ;
else
$aliasString = $original ;
2006-10-17 21:21:21 +04:00
switch ( $mark ) {
2006-10-16 14:04:38 +04:00
case " : " :
$join = 'INNER JOIN ' ;
break ;
case " . " :
$join = 'LEFT JOIN ' ;
break ;
default :
throw new Doctrine_Exception ( " Unknown operator ' $mark ' " );
2006-10-17 21:21:21 +04:00
}
2006-10-16 14:04:38 +04:00
2006-10-17 21:21:21 +04:00
if ( ! $fk -> isOneToOne ()) {
2006-10-16 14:04:38 +04:00
if ( ! $loadFields ) {
$this -> subqueryAliases [] = $tname2 ;
}
$this -> needsSubquery = true ;
}
2006-10-17 21:21:21 +04:00
if ( $fk instanceof Doctrine_Relation_Association ) {
2006-10-16 14:04:38 +04:00
$asf = $fk -> getAssociationFactory ();
$assocTableName = $asf -> getTableName ();
if ( ! $loadFields ) {
$this -> subqueryAliases [] = $assocTableName ;
}
2006-10-17 21:21:21 +04:00
$this -> parts [ " join " ][ $tname ][ $assocTableName ] = $join . $assocTableName . ' ON ' . $tname . " . " . $table -> getIdentifier () . " = " . $assocTableName . " . " . $fk -> getLocal ();
$this -> parts [ " join " ][ $tname ][ $tname2 ] = $join . $aliasString . ' ON ' . $tname2 . " . " . $table -> getIdentifier () . " = " . $assocTableName . " . " . $fk -> getForeign ();
} else {
$this -> parts [ " join " ][ $tname ][ $tname2 ] = $join . $aliasString . ' ON ' . $tname . " . " . $fk -> getLocal () . " = " . $tname2 . " . " . $fk -> getForeign ();
2006-10-16 14:04:38 +04:00
}
$this -> joins [ $tname2 ] = $prevTable ;
$table = $fk -> getTable ();
$this -> tableAliases [ $currPath ] = $tname2 ;
$tableName = $tname2 ;
$this -> relationStack [] = $fk ;
}
2006-10-17 21:21:21 +04:00
$this -> components [ $currPath ] = $table ;
2006-10-16 14:04:38 +04:00
$this -> tableStack [] = $table ;
if ( ! isset ( $this -> tables [ $tableName ])) {
$this -> tables [ $tableName ] = $table ;
if ( $loadFields ) {
2006-10-17 21:21:21 +04:00
$skip = false ;
if ( $componentAlias ) {
$this -> compAliases [ $componentAlias ] = $currPath ;
if ( isset ( $this -> pendingFields [ $componentAlias ])) {
$this -> processPendingFields ( $componentAlias );
$skip = true ;
}
if ( isset ( $this -> pendingAggregates [ $componentAlias ])) {
$this -> processPendingAggregates ( $componentAlias );
$skip = true ;
}
}
if ( ! $skip )
$this -> parseFields ( $fullname , $tableName , $e2 , $currPath );
2006-10-16 14:04:38 +04:00
}
}
$prevPath = $currPath ;
$prevTable = $tableName ;
} catch ( Exception $e ) {
throw new Doctrine_Query_Exception ( $e -> __toString ());
}
}
if ( $componentAlias !== false ) {
$this -> compAliases [ $componentAlias ] = $currPath ;
}
return $table ;
}
/**
* parseFields
*
* @ param string $fullName
* @ param string $tableName
* @ param array $exploded
* @ param string $currPath
* @ return void
*/
final public function parseFields ( $fullName , $tableName , array $exploded , $currPath ) {
$table = $this -> tables [ $tableName ];
$fields = array ();
if ( strpos ( $fullName , " - " ) === false ) {
$fetchmode = $table -> getAttribute ( Doctrine :: ATTR_FETCHMODE );
if ( isset ( $exploded [ 1 ])) {
if ( count ( $exploded ) > 2 ) {
$fields = $this -> parseAggregateValues ( $fullName , $tableName , $exploded , $currPath );
} elseif ( count ( $exploded ) == 2 ) {
$fields = explode ( " , " , substr ( $exploded [ 1 ], 0 , - 1 ));
}
}
} else {
if ( isset ( $exploded [ 1 ])) {
$fetchmode = $this -> parseFetchMode ( $exploded [ 1 ]);
} else
$fetchmode = $table -> getAttribute ( Doctrine :: ATTR_FETCHMODE );
if ( isset ( $exploded [ 2 ])) {
if ( substr_count ( $exploded [ 2 ], " ) " ) > 1 ) {
} else {
$fields = explode ( " , " , substr ( $exploded [ 2 ], 0 , - 1 ));
}
}
}
if ( ! $this -> aggregate )
$this -> loadFields ( $table , $fetchmode , $fields , $currPath );
}
/**
* parseAggregateFunction
*
* @ param string $func
* @ param string $reference
* @ return string
*/
public function parseAggregateFunction ( $func , $reference ) {
$pos = strpos ( $func , " ( " );
if ( $pos !== false ) {
$funcs = array ();
$name = substr ( $func , 0 , $pos );
$func = substr ( $func , ( $pos + 1 ), - 1 );
$params = Doctrine_Query :: bracketExplode ( $func , " , " , " ( " , " ) " );
foreach ( $params as $k => $param ) {
$params [ $k ] = $this -> parseAggregateFunction ( $param , $reference );
}
$funcs = $name . " ( " . implode ( " , " , $params ) . " ) " ;
return $funcs ;
} else {
if ( ! is_numeric ( $func )) {
$func = $this -> getTableAlias ( $reference ) . " . " . $func ;
return $func ;
} else {
return $func ;
}
}
}
/**
* parseAggregateValues
*/
public function parseAggregateValues ( $fullName , $tableName , array $exploded , $currPath ) {
$this -> aggregate = true ;
$pos = strpos ( $fullName , " ( " );
$name = substr ( $fullName , 0 , $pos );
$string = substr ( $fullName , ( $pos + 1 ), - 1 );
$exploded = Doctrine_Query :: bracketExplode ( $string , ',' );
foreach ( $exploded as $k => $value ) {
$func = $this -> parseAggregateFunction ( $value , $currPath );
$exploded [ $k ] = $func ;
$this -> parts [ " select " ][] = $exploded [ $k ];
}
}
}
2006-10-17 21:21:21 +04:00