Query.php 27.8 KB
Newer Older
doctrine's avatar
doctrine committed
1
<?php
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/* 
 *  $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's avatar
doctrine committed
21
require_once("Hydrate.php");
doctrine's avatar
doctrine committed
22 23 24 25 26 27 28
/**
 * Doctrine_Query
 *
 * @package     Doctrine ORM
 * @url         www.phpdoctrine.com
 * @license     LGPL
 */
zYne's avatar
zYne committed
29
class Doctrine_Query extends Doctrine_Hydrate implements Countable {
zYne's avatar
zYne committed
30 31 32
    /**
     * @param array $subqueryAliases        the table aliases needed in some LIMIT subqueries
     */
33
    private $subqueryAliases  = array();
zYne's avatar
zYne committed
34 35 36
    /**
     * @param boolean $needsSubquery
     */
37 38 39 40 41
    private $needsSubquery    = false;
    /**
     * @param boolean $limitSubqueryUsed
     */
    private $limitSubqueryUsed = false;
doctrine's avatar
doctrine committed
42 43 44 45 46 47 48 49 50 51 52
	/**
 	 * count
     *
	 * @return integer
     */
	public function count(Doctrine_Table $table, $params = array()) {
		$this->remove('select');
		$join  = $this->join;
		$where = $this->where;
		$having = $this->having;
		
zYne's avatar
zYne committed
53
		$q = "SELECT COUNT(1) FROM ".$table->getTableName()." ";
doctrine's avatar
doctrine committed
54 55 56
		foreach($join as $j) {
			$q .= implode(" ",$j);
		}
57
        $string = $this->applyInheritance();
doctrine's avatar
doctrine committed
58 59 60 61 62 63 64 65 66 67 68 69

        if( ! empty($where)) {
            $q .= " WHERE ".implode(" AND ",$where);
            if( ! empty($string))
                $q .= " AND (".$string.")";
        } else {
            if( ! empty($string))
                $q .= " WHERE (".$string.")";
        }
			
		if( ! empty($having)) 
			$q .= " HAVING ".implode(' AND ',$having);
doctrine's avatar
doctrine committed
70

zYne's avatar
zYne committed
71
		$a = $this->getConnection()->execute($q, $params)->fetch(PDO::FETCH_NUM);
doctrine's avatar
doctrine committed
72 73
		return $a[0];		
	}
doctrine's avatar
doctrine committed
74 75 76 77 78 79 80 81
    /**
     * 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
doctrine's avatar
doctrine committed
82 83 84
     * @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)
doctrine's avatar
doctrine committed
85 86
     * @return void
     */
87
    protected function loadFields(Doctrine_Table $table, $fetchmode, array $names, $cpath) {
doctrine's avatar
doctrine committed
88 89 90 91 92 93 94
        $name = $table->getComponentName();

        switch($fetchmode):
            case Doctrine::FETCH_OFFSET:
                $this->limit = $table->getAttribute(Doctrine::ATTR_COLL_LIMIT);
            case Doctrine::FETCH_IMMEDIATE:
                if( ! empty($names))
95 96 97
                    $names = array_unique(array_merge($table->getPrimaryKeys(), $names));
                else
                    $names = $table->getColumnNames();
doctrine's avatar
doctrine committed
98 99 100 101 102
            break;
            case Doctrine::FETCH_LAZY_OFFSET:
                $this->limit = $table->getAttribute(Doctrine::ATTR_COLL_LIMIT);
            case Doctrine::FETCH_LAZY:
            case Doctrine::FETCH_BATCH:
103
                $names = array_unique(array_merge($table->getPrimaryKeys(), $names));
doctrine's avatar
doctrine committed
104 105 106 107
            break;
            default:
                throw new Doctrine_Exception("Unknown fetchmode.");
        endswitch;
108
        
109 110 111 112
        $component          = $table->getComponentName();
        $tablename          = $this->tableAliases[$cpath];

        $this->fetchModes[$tablename] = $fetchmode;
doctrine's avatar
doctrine committed
113 114

        $count = count($this->tables);
doctrine's avatar
doctrine committed
115

doctrine's avatar
doctrine committed
116 117
        foreach($names as $name) {
            if($count == 0) {
118
                $this->parts["select"][] = $tablename.".".$name;
doctrine's avatar
doctrine committed
119
            } else {
120
                $this->parts["select"][] = $tablename.".".$name." AS ".$tablename."__".$name;
doctrine's avatar
doctrine committed
121 122 123
            }
        }
    }
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
    /**
     * addFrom
     * 
     * @param strint $from
     */
    public function addFrom($from) {
        $class = "Doctrine_Query_From";
        $parser = new $class($this);
        $parser->parse($from);
    }
    /**
     * addWhere
     *
     * @param string $where
     */
    public function addWhere($where) {
        $class  = "Doctrine_Query_Where";
        $parser = new $class($this);
        $this->parts['where'][] = $parser->parse($where);
    }
    /**
doctrine's avatar
doctrine committed
145 146 147 148 149 150 151 152
     * sets a query part
     *
     * @param string $name
     * @param array $args
     * @return void
     */
    public function __call($name, $args) {
        $name = strtolower($name);
153
        
doctrine's avatar
doctrine committed
154 155 156
        if(isset($this->parts[$name])) {
            $method = "parse".ucwords($name);
            switch($name):
doctrine's avatar
doctrine committed
157 158
                case "from":
                    $this->parts['from']    = array();
159
                    $this->parts['select']  = array();
doctrine's avatar
doctrine committed
160 161 162 163 164 165 166 167 168 169 170 171
                    $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;
doctrine's avatar
doctrine committed
172
                case "where":
doctrine's avatar
doctrine committed
173 174 175 176 177 178 179
                case "having": 
                case "orderby":
                case "groupby":
                    $class = "Doctrine_Query_".ucwords($name);
                    $parser = new $class($this);

                    $this->parts[$name] = array($parser->parse($args[0]));
doctrine's avatar
doctrine committed
180 181 182 183 184 185 186 187 188 189 190 191
                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]);
            endswitch;
192 193
        } else 
            throw new Doctrine_Query_Exception("Unknown overload method");
doctrine's avatar
doctrine committed
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221

        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) {

        if(isset($this->parts[$name])) {
            $method = "parse".ucwords($name);
            switch($name):
                case "where":
222
                case "having":
doctrine's avatar
doctrine committed
223 224 225 226 227 228 229 230 231 232
                    $this->parts[$name] = array($this->$method($value));
                break;
                case "limit":
                case "offset": 
                    if($value == null)
                        $value = false;

                    $this->parts[$name] = $value;
                break;
                case "from":
233
                    $this->parts['select']  = array();
234
                    $this->parts['join']    = array();
doctrine's avatar
doctrine committed
235 236 237
                    $this->joins            = array();
                    $this->tables           = array();
                    $this->fetchModes       = array();
238 239
                    $this->tableIndexes     = array();
                    $this->tableAliases     = array();
doctrine's avatar
doctrine committed
240 241 242 243 244 245 246 247 248
                default:
                    $this->parts[$name] = array();
                    $this->$method($value);
            endswitch;
            
            return true;
        }
        return false;
    }
249 250 251 252
    /**
     * @return boolean
     */
    public function isLimitSubqueryUsed() {
zYne's avatar
zYne committed
253
        return $this->limitSubqueryUsed;
254
    }
doctrine's avatar
doctrine committed
255 256 257 258 259
    /**
     * returns the built sql query
     *
     * @return string
     */
260
    public function getQuery() {
261
        if(empty($this->parts["select"]) || empty($this->parts["from"]))
doctrine's avatar
doctrine committed
262
            return false;
263

zYne's avatar
zYne committed
264 265
        $needsSubQuery = false;
        $subquery = '';
266 267
        $k  = array_keys($this->tables);
        $table = $this->tables[$k[0]];
zYne's avatar
zYne committed
268

269
        if( ! empty($this->parts['limit']) && $this->needsSubquery && $table->getAttribute(Doctrine::ATTR_QUERY_LIMIT) == Doctrine::LIMIT_RECORDS) {
zYne's avatar
zYne committed
270
            $needsSubQuery = true;
271 272
            $this->limitSubqueryUsed = true;
        }
doctrine's avatar
doctrine committed
273 274

        // build the basic query
275
        $q = "SELECT ".implode(", ",$this->parts["select"]).
doctrine's avatar
doctrine committed
276
             " FROM ";
zYne's avatar
zYne committed
277

doctrine's avatar
doctrine committed
278 279 280 281
        foreach($this->parts["from"] as $tname => $bool) {
            $a[] = $tname;
        }
        $q .= implode(", ",$a);
zYne's avatar
zYne committed
282 283

        if($needsSubQuery)
zYne's avatar
zYne committed
284
            $subquery = 'SELECT DISTINCT '.$table->getTableName().".".$table->getIdentifier().
zYne's avatar
zYne committed
285 286
                        ' FROM '.$table->getTableName();

doctrine's avatar
doctrine committed
287 288 289 290
        if( ! empty($this->parts['join'])) {
            foreach($this->parts['join'] as $part) {
                $q .= " ".implode(' ', $part);
            }
zYne's avatar
zYne committed
291 292 293 294

            if($needsSubQuery) {
                foreach($this->parts['join'] as $parts) {
                    foreach($parts as $part) {
zYne's avatar
zYne committed
295 296 297 298 299 300 301 302
                        if(substr($part,0,9) === 'LEFT JOIN') {
                            $e = explode(' ', $part);

                            if( ! in_array($e[2],$this->subqueryAliases))
                                continue;
                        }

                        $subquery .= " ".$part;
zYne's avatar
zYne committed
303 304 305
                    }
                }
            }
doctrine's avatar
doctrine committed
306 307
        }

doctrine's avatar
doctrine committed
308 309
        $string = $this->applyInheritance();

zYne's avatar
zYne committed
310 311 312 313
        if( ! empty($string))
            $this->parts['where'][] = '('.$string.')';

        if($needsSubQuery) {
314
            // all conditions must be preserved in subquery
zYne's avatar
zYne committed
315 316 317
            $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"]):'';
doctrine's avatar
doctrine committed
318 319
        }

320
        $modifyLimit = true;
zYne's avatar
zYne committed
321
        if( ! empty($this->parts["limit"]) || ! empty($this->parts["offset"])) {
322

zYne's avatar
zYne committed
323
            if($needsSubQuery) {
zYne's avatar
zYne committed
324
                $subquery = $this->connection->modifyLimitQuery($subquery,$this->parts["limit"],$this->parts["offset"]);
zYne's avatar
zYne committed
325 326 327
    
                $field    = $table->getTableName().'.'.$table->getIdentifier();
                array_unshift($this->parts['where'], $field.' IN ('.$subquery.')');
328 329
                $modifyLimit = false;
            }   
zYne's avatar
zYne committed
330
        }
doctrine's avatar
doctrine committed
331

zYne's avatar
zYne committed
332
        $q .= ( ! empty($this->parts['where']))?" WHERE ".implode(" AND ",$this->parts["where"]):'';
doctrine's avatar
doctrine committed
333 334 335
        $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"]):'';
zYne's avatar
zYne committed
336
        if($modifyLimit)
zYne's avatar
zYne committed
337
            $q = $this->connection->modifyLimitQuery($q,$this->parts["limit"],$this->parts["offset"]);
doctrine's avatar
doctrine committed
338

zYne's avatar
zYne committed
339 340 341 342 343
        // return to the previous state
        if( ! empty($string))
            array_pop($this->parts['where']);
        if($needsSubQuery)
            array_shift($this->parts['where']);
doctrine's avatar
doctrine committed
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361

        return $q;
    }



    /**
     * 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();
zYne's avatar
zYne committed
362
            $stmt  = $this->tables[$keys[0]]->getConnection()->select($query,$this->parts["limit"],$this->parts["offset"]);
doctrine's avatar
doctrine committed
363 364 365 366 367 368 369 370 371 372 373 374
            $data  = $stmt->fetch(PDO::FETCH_ASSOC);
            if(count($data) == 1) {
                return current($data);
            } else {
                return $data;
            }
        } else {
            return $this->execute($params);
        }
    }
    /**
     * DQL PARSER
375 376 377
     * parses a DQL query
     * first splits the query in parts and then uses individual
     * parsers for each part
doctrine's avatar
doctrine committed
378 379 380 381
     *
     * @param string $query         DQL query
     * @return void
     */
382
    public function parseQuery($query) {
doctrine's avatar
doctrine committed
383
        $this->clear();
384
        $e = self::sqlExplode($query," ","(",")");
385 386


doctrine's avatar
doctrine committed
387 388 389 390 391 392 393 394
        $parts = array();
        foreach($e as $k=>$part):
            switch(strtolower($part)):
                case "select":
                case "from":
                case "where":
                case "limit":
                case "offset":
doctrine's avatar
doctrine committed
395
                case "having":
doctrine's avatar
doctrine committed
396 397 398 399
                    $p = $part;
                    $parts[$part] = array();
                break;
                case "order":
doctrine's avatar
doctrine committed
400 401 402 403
                case "group":
                    $i = ($k + 1);
                    if(isset($e[$i]) && strtolower($e[$i]) === "by") {
                        $p = $part;
doctrine's avatar
doctrine committed
404
                        $parts[$part] = array();
doctrine's avatar
doctrine committed
405 406
                    } else 
                        $parts[$p][] = $part;
doctrine's avatar
doctrine committed
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
                break;
                case "by":
                    continue;
                default:
                    $parts[$p][] = $part;
            endswitch;
        endforeach;

        foreach($parts as $k => $part) {
            $part = implode(" ",$part);
            switch(strtoupper($k)):
                case "SELECT":
                    $this->parseSelect($part);
                break;
                case "FROM":
doctrine's avatar
doctrine committed
422 423 424 425

                    $class  = "Doctrine_Query_".ucwords(strtolower($k));
                    $parser = new $class($this);
                    $parser->parse($part);
doctrine's avatar
doctrine committed
426
                break;
doctrine's avatar
doctrine committed
427 428 429
                case "GROUP":
                case "ORDER":
                    $k .= "by";
doctrine's avatar
doctrine committed
430
                case "WHERE":
doctrine's avatar
doctrine committed
431 432 433 434 435 436
                case "HAVING":
                    $class  = "Doctrine_Query_".ucwords(strtolower($k));
                    $parser = new $class($this);

                    $name = strtolower($k);
                    $this->parts[$name][] = $parser->parse($part);
doctrine's avatar
doctrine committed
437 438 439 440 441
                break;
                case "LIMIT":
                    $this->parts["limit"] = trim($part);
                break;
                case "OFFSET":
zYne's avatar
zYne committed
442
                    $this->parts["offset"] = trim($part);
doctrine's avatar
doctrine committed
443 444 445 446 447 448 449 450 451 452 453
                break;
            endswitch;
        }
    }
    /**
     * DQL ORDER BY PARSER
     * parses the order by part of the query string
     *
     * @param string $str
     * @return void
     */
doctrine's avatar
doctrine committed
454 455 456
    final public function parseOrderBy($str) {
        $parser = new Doctrine_Query_Part_Orderby($this);
        return $parser->parse($str);
doctrine's avatar
doctrine committed
457 458 459 460 461 462 463
    }
    /**
     * returns Doctrine::FETCH_* constant
     *
     * @param string $mode
     * @return integer
     */
doctrine's avatar
doctrine committed
464
    final public function parseFetchMode($mode) {
doctrine's avatar
doctrine committed
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
        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:
486
                throw new Doctrine_Query_Exception("Unknown fetchmode '$mode'. The availible fetchmodes are 'i', 'b' and 'l'.");
doctrine's avatar
doctrine committed
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
        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
     * usage:
     * $str = (age < 20 AND age > 18) AND email LIKE 'John@example.com'
     * now exploding $str with parameters $d = ' AND ', $e1 = '(' and $e2 = ')'
     * would return an array:
     * array("(age < 20 AND age > 18)", "email LIKE 'John@example.com'")
     *
     * @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 ')'
     *
     */
zYne's avatar
zYne committed
517
    public static function bracketExplode($str,$d,$e1 = '(',$e2 = ')') {
doctrine's avatar
doctrine committed
518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
        $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($s1 == $s2) $i++;
            } else {
                $term[$i] .= "$d".trim($val);
                $c1 = substr_count($term[$i],"$e1");
                $c2 = substr_count($term[$i],"$e2");
                    if($c1 == $c2) $i++;
            }
        }
        return $term;
    }
536 537 538 539 540 541 542 543 544 545 546 547 548
    /**
     * sqlExplode
     *
     * explodes a string into array using custom brackets and
     * quote delimeters
     *
     * @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
     */
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587
    public static function sqlExplode($str,$d = " ",$e1 = '(',$e2 = ')') {
        $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], "") & 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], "") & 1)
                        ) $i++;
                }
            }
        }
        return $term;
    }

doctrine's avatar
doctrine committed
588
    /**
589 590 591 592 593 594
     * generateAlias
     *
     * @param string $tableName
     * @return string
     */
    final public function generateAlias($tableName) {
595
        if(isset($this->tableIndexes[$tableName])) {
596
            return $tableName.++$this->tableIndexes[$tableName];
597
        } else {
598 599 600 601 602 603 604 605
            $this->tableIndexes[$tableName] = 1;
            return $tableName;
        }
    }

    /**
     * loads a component
     *
doctrine's avatar
doctrine committed
606 607
     * @param string $path              the path of the loadable component
     * @param integer $fetchmode        optional fetchmode, if not set the components default fetchmode will be used
608
     * @throws Doctrine_Query_Exception
609
     * @return Doctrine_Table
doctrine's avatar
doctrine committed
610 611 612 613
     */
    final public function load($path, $loadFields = true) {
        $e = preg_split("/[.:]/",$path);
        $index = 0;
614
        $currPath = '';
doctrine's avatar
doctrine committed
615 616 617

        foreach($e as $key => $fullname) {
            try {
618 619 620 621 622 623
                $copy  = $e;

                $e2    = preg_split("/[-(]/",$fullname);
                $name  = $e2[0];

                $currPath .= ".".$name;
doctrine's avatar
doctrine committed
624 625

                if($key == 0) {
626
                    $currPath = substr($currPath,1);
doctrine's avatar
doctrine committed
627

zYne's avatar
zYne committed
628
                    $table = $this->connection->getTable($name);
doctrine's avatar
doctrine committed
629 630

                    $tname = $table->getTableName();
zYne's avatar
zYne committed
631

632 633
                    if( ! isset($this->tableAliases[$currPath]))
                        $this->tableIndexes[$tname] = 1;
634
                    
doctrine's avatar
doctrine committed
635 636
                    $this->parts["from"][$tname] = true;

637 638 639
                    $this->tableAliases[$currPath] = $tname;
                    
                    $tableName = $tname;
doctrine's avatar
doctrine committed
640 641 642 643 644 645
                } else {

                    $index += strlen($e[($key - 1)]) + 1;
                    // the mark here is either '.' or ':'
                    $mark  = substr($path,($index - 1),1);

646 647
                    if(isset($this->tableAliases[$prevPath])) {
                        $tname = $this->tableAliases[$prevPath];
648 649
                    } else
                        $tname = $table->getTableName();
doctrine's avatar
doctrine committed
650 651


652 653 654
                    $fk       = $table->getForeignKey($name);
                    $name     = $fk->getTable()->getComponentName();
                    $original = $fk->getTable()->getTableName();
zYne's avatar
zYne committed
655 656 657



658 659 660 661
                    if(isset($this->tableAliases[$currPath])) {
                        $tname2 = $this->tableAliases[$currPath];
                    } else
                        $tname2 = $this->generateAlias($original);
doctrine's avatar
doctrine committed
662

663 664 665 666
                    if($original !== $tname2) 
                        $aliasString = $original." AS ".$tname2;
                    else
                        $aliasString = $original;
doctrine's avatar
doctrine committed
667

668 669 670 671 672 673 674 675 676 677 678
                    switch($mark):
                        case ":":
                            $join = 'INNER JOIN ';
                        break;
                        case ".":
                            $join = 'LEFT JOIN ';
                        break;
                        default:
                            throw new Doctrine_Exception("Unknown operator '$mark'");
                    endswitch;

zYne's avatar
zYne committed
679 680
                    if($fk->getType() == Doctrine_Relation::MANY_AGGREGATE ||
                       $fk->getType() == Doctrine_Relation::MANY_COMPOSITE) {
681
                        if( ! $loadFields) {
zYne's avatar
zYne committed
682
                            $this->subqueryAliases[] = $tname2;
683 684
                        }
                        
zYne's avatar
zYne committed
685 686
                        $this->needsSubquery = true;
                    }
687

doctrine's avatar
doctrine committed
688 689
                    if($fk instanceof Doctrine_ForeignKey ||
                       $fk instanceof Doctrine_LocalKey) {
690

doctrine's avatar
doctrine committed
691
                        $this->parts["join"][$tname][$tname2]         = $join.$aliasString." ON ".$tname.".".$fk->getLocal()." = ".$tname2.".".$fk->getForeign();
692

doctrine's avatar
doctrine committed
693 694 695
                    } elseif($fk instanceof Doctrine_Association) {
                        $asf = $fk->getAssociationFactory();

696
                        $assocTableName = $asf->getTableName();
697 698 699 700
                        
                        if( ! $loadFields) {
                            $this->subqueryAliases[] = $assocTableName;
                        }
701 702
                        $this->parts["join"][$tname][$assocTableName] = $join.$assocTableName." ON ".$tname.".id = ".$assocTableName.".".$fk->getLocal();
                        $this->parts["join"][$tname][$tname2]         = $join.$aliasString." ON ".$tname2.".id = ".$assocTableName.".".$fk->getForeign();
doctrine's avatar
doctrine committed
703 704
                    }

705
                    $this->joins[$tname2] = $prevTable;
706

707

doctrine's avatar
doctrine committed
708
                    $table = $fk->getTable();
709
                    $this->tableAliases[$currPath] = $tname2;
doctrine's avatar
doctrine committed
710

711
                    $tableName = $tname2;
doctrine's avatar
doctrine committed
712 713
                }

714 715
                if( ! isset($this->tables[$tableName])) {
                    $this->tables[$tableName] = $table;
doctrine's avatar
doctrine committed
716

717
                    if($loadFields) {
718
                        $this->parseFields($fullname, $tableName, $e2, $currPath);
doctrine's avatar
doctrine committed
719 720 721
                    }
                }

722

723 724
                $prevPath  = $currPath;
                $prevTable = $tableName;
doctrine's avatar
doctrine committed
725
            } catch(Exception $e) {
726
                throw new Doctrine_Query_Exception($e->__toString());
doctrine's avatar
doctrine committed
727 728
            }
        }
729
        return $table;
doctrine's avatar
doctrine committed
730
    }
731 732 733 734 735 736 737 738 739
    /**
     * parseFields
     *
     * @param string $fullName
     * @param string $tableName
     * @param array $exploded
     * @param string $currPath
     * @return void
     */
740
    final public function parseFields($fullName, $tableName, array $exploded, $currPath) {
741 742 743 744 745 746 747
        $table = $this->tables[$tableName];

        $fields = array();

        if(strpos($fullName, "-") === false) {
            $fetchmode = $table->getAttribute(Doctrine::ATTR_FETCHMODE);

748 749 750 751 752 753 754 755 756 757 758 759
            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);
760

761 762
            if(isset($exploded[2])) {
                if(substr_count($exploded[2], ")") > 1) {
763

764
                } else {
765
                    $fields = explode(",",substr($exploded[2],0,-1));
766
                }
767 768
            }

769 770
        }
        if( ! $this->aggregate)
771
            $this->loadFields($table, $fetchmode, $fields, $currPath);
772
    }
773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813
    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;
            }
        }
    }
    final 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) {
            $exploded[$k] = $this->parseAggregateFunction($value, $currPath);
            $this->parts["select"][] = $exploded[$k];
        }
    }
doctrine's avatar
doctrine committed
814 815
}
?>