File indexing completed on 2025-01-19 05:21:03
0001 <?php 0002 /** 0003 * Zend Framework 0004 * 0005 * LICENSE 0006 * 0007 * This source file is subject to the new BSD license that is bundled 0008 * with this package in the file LICENSE.txt. 0009 * It is also available through the world-wide-web at this URL: 0010 * http://framework.zend.com/license/new-bsd 0011 * If you did not receive a copy of the license and are unable to 0012 * obtain it through the world-wide-web, please send an email 0013 * to license@zend.com so we can send you a copy immediately. 0014 * 0015 * @category Zend 0016 * @package Zend_Db 0017 * @subpackage Select 0018 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0019 * @license http://framework.zend.com/license/new-bsd New BSD License 0020 * @version $Id$ 0021 */ 0022 0023 0024 /** 0025 * @see Zend_Db_Adapter_Abstract 0026 */ 0027 // require_once 'Zend/Db/Adapter/Abstract.php'; 0028 0029 /** 0030 * @see Zend_Db_Expr 0031 */ 0032 // require_once 'Zend/Db/Expr.php'; 0033 0034 0035 /** 0036 * Class for SQL SELECT generation and results. 0037 * 0038 * @category Zend 0039 * @package Zend_Db 0040 * @subpackage Select 0041 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0042 * @license http://framework.zend.com/license/new-bsd New BSD License 0043 */ 0044 class Zend_Db_Select 0045 { 0046 0047 const DISTINCT = 'distinct'; 0048 const COLUMNS = 'columns'; 0049 const FROM = 'from'; 0050 const UNION = 'union'; 0051 const WHERE = 'where'; 0052 const GROUP = 'group'; 0053 const HAVING = 'having'; 0054 const ORDER = 'order'; 0055 const LIMIT_COUNT = 'limitcount'; 0056 const LIMIT_OFFSET = 'limitoffset'; 0057 const FOR_UPDATE = 'forupdate'; 0058 0059 const INNER_JOIN = 'inner join'; 0060 const LEFT_JOIN = 'left join'; 0061 const RIGHT_JOIN = 'right join'; 0062 const FULL_JOIN = 'full join'; 0063 const CROSS_JOIN = 'cross join'; 0064 const NATURAL_JOIN = 'natural join'; 0065 0066 const SQL_WILDCARD = '*'; 0067 const SQL_SELECT = 'SELECT'; 0068 const SQL_UNION = 'UNION'; 0069 const SQL_UNION_ALL = 'UNION ALL'; 0070 const SQL_FROM = 'FROM'; 0071 const SQL_WHERE = 'WHERE'; 0072 const SQL_DISTINCT = 'DISTINCT'; 0073 const SQL_GROUP_BY = 'GROUP BY'; 0074 const SQL_ORDER_BY = 'ORDER BY'; 0075 const SQL_HAVING = 'HAVING'; 0076 const SQL_FOR_UPDATE = 'FOR UPDATE'; 0077 const SQL_AND = 'AND'; 0078 const SQL_AS = 'AS'; 0079 const SQL_OR = 'OR'; 0080 const SQL_ON = 'ON'; 0081 const SQL_ASC = 'ASC'; 0082 const SQL_DESC = 'DESC'; 0083 0084 const REGEX_COLUMN_EXPR = '/^([\w]*\s*\(([^\(\)]|(?1))*\))$/'; 0085 const REGEX_COLUMN_EXPR_ORDER = '/^([\w]+\s*\(([^\(\)]|(?1))*\))$/'; 0086 const REGEX_COLUMN_EXPR_GROUP = '/^([\w]+\s*\(([^\(\)]|(?1))*\))$/'; 0087 0088 // @see http://stackoverflow.com/a/13823184/2028814 0089 const REGEX_SQL_COMMENTS = '@ 0090 (([\'"]).*?[^\\\]\2) # $1 : Skip single & double quoted expressions 0091 |( # $3 : Match comments 0092 (?:\#|--).*?$ # - Single line comments 0093 | # - Multi line (nested) comments 0094 /\* # . comment open marker 0095 (?: [^/*] # . non comment-marker characters 0096 |/(?!\*) # . ! not a comment open 0097 |\*(?!/) # . ! not a comment close 0098 |(?R) # . recursive case 0099 )* # . repeat eventually 0100 \*\/ # . comment close marker 0101 )\s* # Trim after comments 0102 |(?<=;)\s+ # Trim after semi-colon 0103 @msx'; 0104 0105 /** 0106 * Bind variables for query 0107 * 0108 * @var array 0109 */ 0110 protected $_bind = array(); 0111 0112 /** 0113 * Zend_Db_Adapter_Abstract object. 0114 * 0115 * @var Zend_Db_Adapter_Abstract 0116 */ 0117 protected $_adapter; 0118 0119 /** 0120 * The initial values for the $_parts array. 0121 * NOTE: It is important for the 'FOR_UPDATE' part to be last to ensure 0122 * meximum compatibility with database adapters. 0123 * 0124 * @var array 0125 */ 0126 protected static $_partsInit = array( 0127 self::DISTINCT => false, 0128 self::COLUMNS => array(), 0129 self::UNION => array(), 0130 self::FROM => array(), 0131 self::WHERE => array(), 0132 self::GROUP => array(), 0133 self::HAVING => array(), 0134 self::ORDER => array(), 0135 self::LIMIT_COUNT => null, 0136 self::LIMIT_OFFSET => null, 0137 self::FOR_UPDATE => false 0138 ); 0139 0140 /** 0141 * Specify legal join types. 0142 * 0143 * @var array 0144 */ 0145 protected static $_joinTypes = array( 0146 self::INNER_JOIN, 0147 self::LEFT_JOIN, 0148 self::RIGHT_JOIN, 0149 self::FULL_JOIN, 0150 self::CROSS_JOIN, 0151 self::NATURAL_JOIN, 0152 ); 0153 0154 /** 0155 * Specify legal union types. 0156 * 0157 * @var array 0158 */ 0159 protected static $_unionTypes = array( 0160 self::SQL_UNION, 0161 self::SQL_UNION_ALL 0162 ); 0163 0164 /** 0165 * The component parts of a SELECT statement. 0166 * Initialized to the $_partsInit array in the constructor. 0167 * 0168 * @var array 0169 */ 0170 protected $_parts = array(); 0171 0172 /** 0173 * Tracks which columns are being select from each table and join. 0174 * 0175 * @var array 0176 */ 0177 protected $_tableCols = array(); 0178 0179 /** 0180 * Class constructor 0181 * 0182 * @param Zend_Db_Adapter_Abstract $adapter 0183 */ 0184 public function __construct(Zend_Db_Adapter_Abstract $adapter) 0185 { 0186 $this->_adapter = $adapter; 0187 $this->_parts = self::$_partsInit; 0188 } 0189 0190 /** 0191 * Get bind variables 0192 * 0193 * @return array 0194 */ 0195 public function getBind() 0196 { 0197 return $this->_bind; 0198 } 0199 0200 /** 0201 * Set bind variables 0202 * 0203 * @param mixed $bind 0204 * @return Zend_Db_Select 0205 */ 0206 public function bind($bind) 0207 { 0208 $this->_bind = $bind; 0209 0210 return $this; 0211 } 0212 0213 /** 0214 * Makes the query SELECT DISTINCT. 0215 * 0216 * @param bool $flag Whether or not the SELECT is DISTINCT (default true). 0217 * @return Zend_Db_Select This Zend_Db_Select object. 0218 */ 0219 public function distinct($flag = true) 0220 { 0221 $this->_parts[self::DISTINCT] = (bool) $flag; 0222 return $this; 0223 } 0224 0225 /** 0226 * Adds a FROM table and optional columns to the query. 0227 * 0228 * The first parameter $name can be a simple string, in which case the 0229 * correlation name is generated automatically. If you want to specify 0230 * the correlation name, the first parameter must be an associative 0231 * array in which the key is the correlation name, and the value is 0232 * the physical table name. For example, array('alias' => 'table'). 0233 * The correlation name is prepended to all columns fetched for this 0234 * table. 0235 * 0236 * The second parameter can be a single string or Zend_Db_Expr object, 0237 * or else an array of strings or Zend_Db_Expr objects. 0238 * 0239 * The first parameter can be null or an empty string, in which case 0240 * no correlation name is generated or prepended to the columns named 0241 * in the second parameter. 0242 * 0243 * @param array|string|Zend_Db_Expr $name The table name or an associative array 0244 * relating correlation name to table name. 0245 * @param array|string|Zend_Db_Expr $cols The columns to select from this table. 0246 * @param string $schema The schema name to specify, if any. 0247 * @return Zend_Db_Select This Zend_Db_Select object. 0248 */ 0249 public function from($name, $cols = '*', $schema = null) 0250 { 0251 return $this->_join(self::FROM, $name, null, $cols, $schema); 0252 } 0253 0254 /** 0255 * Specifies the columns used in the FROM clause. 0256 * 0257 * The parameter can be a single string or Zend_Db_Expr object, 0258 * or else an array of strings or Zend_Db_Expr objects. 0259 * 0260 * @param array|string|Zend_Db_Expr $cols The columns to select from this table. 0261 * @param string $correlationName Correlation name of target table. OPTIONAL 0262 * @return Zend_Db_Select This Zend_Db_Select object. 0263 */ 0264 public function columns($cols = '*', $correlationName = null) 0265 { 0266 if ($correlationName === null && count($this->_parts[self::FROM])) { 0267 $correlationNameKeys = array_keys($this->_parts[self::FROM]); 0268 $correlationName = current($correlationNameKeys); 0269 } 0270 0271 if (!array_key_exists($correlationName, $this->_parts[self::FROM])) { 0272 /** 0273 * @see Zend_Db_Select_Exception 0274 */ 0275 // require_once 'Zend/Db/Select/Exception.php'; 0276 throw new Zend_Db_Select_Exception("No table has been specified for the FROM clause"); 0277 } 0278 0279 $this->_tableCols($correlationName, $cols); 0280 0281 return $this; 0282 } 0283 0284 /** 0285 * Adds a UNION clause to the query. 0286 * 0287 * The first parameter has to be an array of Zend_Db_Select or 0288 * sql query strings. 0289 * 0290 * <code> 0291 * $sql1 = $db->select(); 0292 * $sql2 = "SELECT ..."; 0293 * $select = $db->select() 0294 * ->union(array($sql1, $sql2)) 0295 * ->order("id"); 0296 * </code> 0297 * 0298 * @param array $select Array of select clauses for the union. 0299 * @return Zend_Db_Select This Zend_Db_Select object. 0300 */ 0301 public function union($select = array(), $type = self::SQL_UNION) 0302 { 0303 if (!is_array($select)) { 0304 // require_once 'Zend/Db/Select/Exception.php'; 0305 throw new Zend_Db_Select_Exception( 0306 "union() only accepts an array of Zend_Db_Select instances of sql query strings." 0307 ); 0308 } 0309 0310 if (!in_array($type, self::$_unionTypes)) { 0311 // require_once 'Zend/Db/Select/Exception.php'; 0312 throw new Zend_Db_Select_Exception("Invalid union type '{$type}'"); 0313 } 0314 0315 foreach ($select as $target) { 0316 $this->_parts[self::UNION][] = array($target, $type); 0317 } 0318 0319 return $this; 0320 } 0321 0322 /** 0323 * Adds a JOIN table and columns to the query. 0324 * 0325 * The $name and $cols parameters follow the same logic 0326 * as described in the from() method. 0327 * 0328 * @param array|string|Zend_Db_Expr $name The table name. 0329 * @param string $cond Join on this condition. 0330 * @param array|string $cols The columns to select from the joined table. 0331 * @param string $schema The database name to specify, if any. 0332 * @return Zend_Db_Select This Zend_Db_Select object. 0333 */ 0334 public function join($name, $cond, $cols = self::SQL_WILDCARD, $schema = null) 0335 { 0336 return $this->joinInner($name, $cond, $cols, $schema); 0337 } 0338 0339 /** 0340 * Add an INNER JOIN table and colums to the query 0341 * Rows in both tables are matched according to the expression 0342 * in the $cond argument. The result set is comprised 0343 * of all cases where rows from the left table match 0344 * rows from the right table. 0345 * 0346 * The $name and $cols parameters follow the same logic 0347 * as described in the from() method. 0348 * 0349 * @param array|string|Zend_Db_Expr $name The table name. 0350 * @param string $cond Join on this condition. 0351 * @param array|string $cols The columns to select from the joined table. 0352 * @param string $schema The database name to specify, if any. 0353 * @return Zend_Db_Select This Zend_Db_Select object. 0354 */ 0355 public function joinInner($name, $cond, $cols = self::SQL_WILDCARD, $schema = null) 0356 { 0357 return $this->_join(self::INNER_JOIN, $name, $cond, $cols, $schema); 0358 } 0359 0360 /** 0361 * Add a LEFT OUTER JOIN table and colums to the query 0362 * All rows from the left operand table are included, 0363 * matching rows from the right operand table included, 0364 * and the columns from the right operand table are filled 0365 * with NULLs if no row exists matching the left table. 0366 * 0367 * The $name and $cols parameters follow the same logic 0368 * as described in the from() method. 0369 * 0370 * @param array|string|Zend_Db_Expr $name The table name. 0371 * @param string $cond Join on this condition. 0372 * @param array|string $cols The columns to select from the joined table. 0373 * @param string $schema The database name to specify, if any. 0374 * @return Zend_Db_Select This Zend_Db_Select object. 0375 */ 0376 public function joinLeft($name, $cond, $cols = self::SQL_WILDCARD, $schema = null) 0377 { 0378 return $this->_join(self::LEFT_JOIN, $name, $cond, $cols, $schema); 0379 } 0380 0381 /** 0382 * Add a RIGHT OUTER JOIN table and colums to the query. 0383 * Right outer join is the complement of left outer join. 0384 * All rows from the right operand table are included, 0385 * matching rows from the left operand table included, 0386 * and the columns from the left operand table are filled 0387 * with NULLs if no row exists matching the right table. 0388 * 0389 * The $name and $cols parameters follow the same logic 0390 * as described in the from() method. 0391 * 0392 * @param array|string|Zend_Db_Expr $name The table name. 0393 * @param string $cond Join on this condition. 0394 * @param array|string $cols The columns to select from the joined table. 0395 * @param string $schema The database name to specify, if any. 0396 * @return Zend_Db_Select This Zend_Db_Select object. 0397 */ 0398 public function joinRight($name, $cond, $cols = self::SQL_WILDCARD, $schema = null) 0399 { 0400 return $this->_join(self::RIGHT_JOIN, $name, $cond, $cols, $schema); 0401 } 0402 0403 /** 0404 * Add a FULL OUTER JOIN table and colums to the query. 0405 * A full outer join is like combining a left outer join 0406 * and a right outer join. All rows from both tables are 0407 * included, paired with each other on the same row of the 0408 * result set if they satisfy the join condition, and otherwise 0409 * paired with NULLs in place of columns from the other table. 0410 * 0411 * The $name and $cols parameters follow the same logic 0412 * as described in the from() method. 0413 * 0414 * @param array|string|Zend_Db_Expr $name The table name. 0415 * @param string $cond Join on this condition. 0416 * @param array|string $cols The columns to select from the joined table. 0417 * @param string $schema The database name to specify, if any. 0418 * @return Zend_Db_Select This Zend_Db_Select object. 0419 */ 0420 public function joinFull($name, $cond, $cols = self::SQL_WILDCARD, $schema = null) 0421 { 0422 return $this->_join(self::FULL_JOIN, $name, $cond, $cols, $schema); 0423 } 0424 0425 /** 0426 * Add a CROSS JOIN table and colums to the query. 0427 * A cross join is a cartesian product; there is no join condition. 0428 * 0429 * The $name and $cols parameters follow the same logic 0430 * as described in the from() method. 0431 * 0432 * @param array|string|Zend_Db_Expr $name The table name. 0433 * @param array|string $cols The columns to select from the joined table. 0434 * @param string $schema The database name to specify, if any. 0435 * @return Zend_Db_Select This Zend_Db_Select object. 0436 */ 0437 public function joinCross($name, $cols = self::SQL_WILDCARD, $schema = null) 0438 { 0439 return $this->_join(self::CROSS_JOIN, $name, null, $cols, $schema); 0440 } 0441 0442 /** 0443 * Add a NATURAL JOIN table and colums to the query. 0444 * A natural join assumes an equi-join across any column(s) 0445 * that appear with the same name in both tables. 0446 * Only natural inner joins are supported by this API, 0447 * even though SQL permits natural outer joins as well. 0448 * 0449 * The $name and $cols parameters follow the same logic 0450 * as described in the from() method. 0451 * 0452 * @param array|string|Zend_Db_Expr $name The table name. 0453 * @param array|string $cols The columns to select from the joined table. 0454 * @param string $schema The database name to specify, if any. 0455 * @return Zend_Db_Select This Zend_Db_Select object. 0456 */ 0457 public function joinNatural($name, $cols = self::SQL_WILDCARD, $schema = null) 0458 { 0459 return $this->_join(self::NATURAL_JOIN, $name, null, $cols, $schema); 0460 } 0461 0462 /** 0463 * Adds a WHERE condition to the query by AND. 0464 * 0465 * If a value is passed as the second param, it will be quoted 0466 * and replaced into the condition wherever a question-mark 0467 * appears. Array values are quoted and comma-separated. 0468 * 0469 * <code> 0470 * // simplest but non-secure 0471 * $select->where("id = $id"); 0472 * 0473 * // secure (ID is quoted but matched anyway) 0474 * $select->where('id = ?', $id); 0475 * 0476 * // alternatively, with named binding 0477 * $select->where('id = :id'); 0478 * </code> 0479 * 0480 * Note that it is more correct to use named bindings in your 0481 * queries for values other than strings. When you use named 0482 * bindings, don't forget to pass the values when actually 0483 * making a query: 0484 * 0485 * <code> 0486 * $db->fetchAll($select, array('id' => 5)); 0487 * </code> 0488 * 0489 * @param string $cond The WHERE condition. 0490 * @param mixed $value OPTIONAL The value to quote into the condition. 0491 * @param int $type OPTIONAL The type of the given value 0492 * @return Zend_Db_Select This Zend_Db_Select object. 0493 */ 0494 public function where($cond, $value = null, $type = null) 0495 { 0496 $this->_parts[self::WHERE][] = $this->_where($cond, $value, $type, true); 0497 0498 return $this; 0499 } 0500 0501 /** 0502 * Adds a WHERE condition to the query by OR. 0503 * 0504 * Otherwise identical to where(). 0505 * 0506 * @param string $cond The WHERE condition. 0507 * @param mixed $value OPTIONAL The value to quote into the condition. 0508 * @param int $type OPTIONAL The type of the given value 0509 * @return Zend_Db_Select This Zend_Db_Select object. 0510 * 0511 * @see where() 0512 */ 0513 public function orWhere($cond, $value = null, $type = null) 0514 { 0515 $this->_parts[self::WHERE][] = $this->_where($cond, $value, $type, false); 0516 0517 return $this; 0518 } 0519 0520 /** 0521 * Adds grouping to the query. 0522 * 0523 * @param array|string $spec The column(s) to group by. 0524 * @return Zend_Db_Select This Zend_Db_Select object. 0525 */ 0526 public function group($spec) 0527 { 0528 if (!is_array($spec)) { 0529 $spec = array($spec); 0530 } 0531 0532 foreach ($spec as $val) { 0533 // Remove comments from SQL statement 0534 $noComments = preg_replace(self::REGEX_SQL_COMMENTS, '$1', (string) $val); 0535 if (preg_match(self::REGEX_COLUMN_EXPR_GROUP, $noComments)) { 0536 $val = new Zend_Db_Expr($val); 0537 } 0538 $this->_parts[self::GROUP][] = $val; 0539 } 0540 0541 return $this; 0542 } 0543 0544 /** 0545 * Adds a HAVING condition to the query by AND. 0546 * 0547 * If a value is passed as the second param, it will be quoted 0548 * and replaced into the condition wherever a question-mark 0549 * appears. See {@link where()} for an example 0550 * 0551 * @param string $cond The HAVING condition. 0552 * @param mixed $value OPTIONAL The value to quote into the condition. 0553 * @param int $type OPTIONAL The type of the given value 0554 * @return Zend_Db_Select This Zend_Db_Select object. 0555 */ 0556 public function having($cond, $value = null, $type = null) 0557 { 0558 if ($value !== null) { 0559 $cond = $this->_adapter->quoteInto($cond, $value, $type); 0560 } 0561 0562 if ($this->_parts[self::HAVING]) { 0563 $this->_parts[self::HAVING][] = self::SQL_AND . " ($cond)"; 0564 } else { 0565 $this->_parts[self::HAVING][] = "($cond)"; 0566 } 0567 0568 return $this; 0569 } 0570 0571 /** 0572 * Adds a HAVING condition to the query by OR. 0573 * 0574 * Otherwise identical to orHaving(). 0575 * 0576 * @param string $cond The HAVING condition. 0577 * @param mixed $value OPTIONAL The value to quote into the condition. 0578 * @param int $type OPTIONAL The type of the given value 0579 * @return Zend_Db_Select This Zend_Db_Select object. 0580 * 0581 * @see having() 0582 */ 0583 public function orHaving($cond, $value = null, $type = null) 0584 { 0585 if ($value !== null) { 0586 $cond = $this->_adapter->quoteInto($cond, $value, $type); 0587 } 0588 0589 if ($this->_parts[self::HAVING]) { 0590 $this->_parts[self::HAVING][] = self::SQL_OR . " ($cond)"; 0591 } else { 0592 $this->_parts[self::HAVING][] = "($cond)"; 0593 } 0594 0595 return $this; 0596 } 0597 0598 /** 0599 * Adds a row order to the query. 0600 * 0601 * @param mixed $spec The column(s) and direction to order by. 0602 * @return Zend_Db_Select This Zend_Db_Select object. 0603 */ 0604 public function order($spec) 0605 { 0606 if (!is_array($spec)) { 0607 $spec = array($spec); 0608 } 0609 0610 // force 'ASC' or 'DESC' on each order spec, default is ASC. 0611 foreach ($spec as $val) { 0612 if ($val instanceof Zend_Db_Expr) { 0613 $expr = $val->__toString(); 0614 if (empty($expr)) { 0615 continue; 0616 } 0617 $this->_parts[self::ORDER][] = $val; 0618 } else { 0619 if (empty($val)) { 0620 continue; 0621 } 0622 $direction = self::SQL_ASC; 0623 if (preg_match('/(.*\W)(' . self::SQL_ASC . '|' . self::SQL_DESC . ')\b/si', $val, $matches)) { 0624 $val = trim($matches[1]); 0625 $direction = $matches[2]; 0626 } 0627 // Remove comments from SQL statement 0628 $noComments = preg_replace(self::REGEX_SQL_COMMENTS, '$1', (string) $val); 0629 if (preg_match(self::REGEX_COLUMN_EXPR_ORDER, $noComments)) { 0630 $val = new Zend_Db_Expr($val); 0631 } 0632 $this->_parts[self::ORDER][] = array($val, $direction); 0633 } 0634 } 0635 0636 return $this; 0637 } 0638 0639 /** 0640 * Sets a limit count and offset to the query. 0641 * 0642 * @param int $count OPTIONAL The number of rows to return. 0643 * @param int $offset OPTIONAL Start returning after this many rows. 0644 * @return Zend_Db_Select This Zend_Db_Select object. 0645 */ 0646 public function limit($count = null, $offset = null) 0647 { 0648 $this->_parts[self::LIMIT_COUNT] = (int) $count; 0649 $this->_parts[self::LIMIT_OFFSET] = (int) $offset; 0650 return $this; 0651 } 0652 0653 /** 0654 * Sets the limit and count by page number. 0655 * 0656 * @param int $page Limit results to this page number. 0657 * @param int $rowCount Use this many rows per page. 0658 * @return Zend_Db_Select This Zend_Db_Select object. 0659 */ 0660 public function limitPage($page, $rowCount) 0661 { 0662 $page = ($page > 0) ? $page : 1; 0663 $rowCount = ($rowCount > 0) ? $rowCount : 1; 0664 $this->_parts[self::LIMIT_COUNT] = (int) $rowCount; 0665 $this->_parts[self::LIMIT_OFFSET] = (int) $rowCount * ($page - 1); 0666 return $this; 0667 } 0668 0669 /** 0670 * Makes the query SELECT FOR UPDATE. 0671 * 0672 * @param bool $flag Whether or not the SELECT is FOR UPDATE (default true). 0673 * @return Zend_Db_Select This Zend_Db_Select object. 0674 */ 0675 public function forUpdate($flag = true) 0676 { 0677 $this->_parts[self::FOR_UPDATE] = (bool) $flag; 0678 return $this; 0679 } 0680 0681 /** 0682 * Get part of the structured information for the current query. 0683 * 0684 * @param string $part 0685 * @return mixed 0686 * @throws Zend_Db_Select_Exception 0687 */ 0688 public function getPart($part) 0689 { 0690 $part = strtolower($part); 0691 if (!array_key_exists($part, $this->_parts)) { 0692 // require_once 'Zend/Db/Select/Exception.php'; 0693 throw new Zend_Db_Select_Exception("Invalid Select part '$part'"); 0694 } 0695 return $this->_parts[$part]; 0696 } 0697 0698 /** 0699 * Executes the current select object and returns the result 0700 * 0701 * @param integer $fetchMode OPTIONAL 0702 * @param mixed $bind An array of data to bind to the placeholders. 0703 * @return PDO_Statement|Zend_Db_Statement 0704 */ 0705 public function query($fetchMode = null, $bind = array()) 0706 { 0707 if (!empty($bind)) { 0708 $this->bind($bind); 0709 } 0710 0711 $stmt = $this->_adapter->query($this); 0712 if ($fetchMode == null) { 0713 $fetchMode = $this->_adapter->getFetchMode(); 0714 } 0715 $stmt->setFetchMode($fetchMode); 0716 return $stmt; 0717 } 0718 0719 /** 0720 * Converts this object to an SQL SELECT string. 0721 * 0722 * @return string|null This object as a SELECT string. (or null if a string cannot be produced.) 0723 */ 0724 public function assemble() 0725 { 0726 $sql = self::SQL_SELECT; 0727 foreach (array_keys(self::$_partsInit) as $part) { 0728 $method = '_render' . ucfirst($part); 0729 if (method_exists($this, $method)) { 0730 $sql = $this->$method($sql); 0731 } 0732 } 0733 return $sql; 0734 } 0735 0736 /** 0737 * Clear parts of the Select object, or an individual part. 0738 * 0739 * @param string $part OPTIONAL 0740 * @return Zend_Db_Select 0741 */ 0742 public function reset($part = null) 0743 { 0744 if ($part == null) { 0745 $this->_parts = self::$_partsInit; 0746 } elseif (array_key_exists($part, self::$_partsInit)) { 0747 $this->_parts[$part] = self::$_partsInit[$part]; 0748 } 0749 return $this; 0750 } 0751 0752 /** 0753 * Gets the Zend_Db_Adapter_Abstract for this 0754 * particular Zend_Db_Select object. 0755 * 0756 * @return Zend_Db_Adapter_Abstract 0757 */ 0758 public function getAdapter() 0759 { 0760 return $this->_adapter; 0761 } 0762 0763 /** 0764 * Populate the {@link $_parts} 'join' key 0765 * 0766 * Does the dirty work of populating the join key. 0767 * 0768 * The $name and $cols parameters follow the same logic 0769 * as described in the from() method. 0770 * 0771 * @param null|string $type Type of join; inner, left, and null are currently supported 0772 * @param array|string|Zend_Db_Expr $name Table name 0773 * @param string $cond Join on this condition 0774 * @param array|string $cols The columns to select from the joined table 0775 * @param string $schema The database name to specify, if any. 0776 * @return Zend_Db_Select This Zend_Db_Select object 0777 * @throws Zend_Db_Select_Exception 0778 */ 0779 protected function _join($type, $name, $cond, $cols, $schema = null) 0780 { 0781 if (!in_array($type, self::$_joinTypes) && $type != self::FROM) { 0782 /** 0783 * @see Zend_Db_Select_Exception 0784 */ 0785 // require_once 'Zend/Db/Select/Exception.php'; 0786 throw new Zend_Db_Select_Exception("Invalid join type '$type'"); 0787 } 0788 0789 if (count($this->_parts[self::UNION])) { 0790 // require_once 'Zend/Db/Select/Exception.php'; 0791 throw new Zend_Db_Select_Exception("Invalid use of table with " . self::SQL_UNION); 0792 } 0793 0794 if (empty($name)) { 0795 $correlationName = $tableName = ''; 0796 } elseif (is_array($name)) { 0797 // Must be array($correlationName => $tableName) or array($ident, ...) 0798 foreach ($name as $_correlationName => $_tableName) { 0799 if (is_string($_correlationName)) { 0800 // We assume the key is the correlation name and value is the table name 0801 $tableName = $_tableName; 0802 $correlationName = $_correlationName; 0803 } else { 0804 // We assume just an array of identifiers, with no correlation name 0805 $tableName = $_tableName; 0806 $correlationName = $this->_uniqueCorrelation($tableName); 0807 } 0808 break; 0809 } 0810 } elseif ($name instanceof Zend_Db_Expr|| $name instanceof Zend_Db_Select) { 0811 $tableName = $name; 0812 $correlationName = $this->_uniqueCorrelation('t'); 0813 } elseif (preg_match('/^(.+)\s+AS\s+(.+)$/i', $name, $m)) { 0814 $tableName = $m[1]; 0815 $correlationName = $m[2]; 0816 } else { 0817 $tableName = $name; 0818 $correlationName = $this->_uniqueCorrelation($tableName); 0819 } 0820 0821 // Schema from table name overrides schema argument 0822 if (!is_object($tableName) && false !== strpos($tableName, '.')) { 0823 list($schema, $tableName) = explode('.', $tableName); 0824 } 0825 0826 $lastFromCorrelationName = null; 0827 if (!empty($correlationName)) { 0828 if (array_key_exists($correlationName, $this->_parts[self::FROM])) { 0829 /** 0830 * @see Zend_Db_Select_Exception 0831 */ 0832 // require_once 'Zend/Db/Select/Exception.php'; 0833 throw new Zend_Db_Select_Exception("You cannot define a correlation name '$correlationName' more than once"); 0834 } 0835 0836 if ($type == self::FROM) { 0837 // append this from after the last from joinType 0838 $tmpFromParts = $this->_parts[self::FROM]; 0839 $this->_parts[self::FROM] = array(); 0840 // move all the froms onto the stack 0841 while ($tmpFromParts) { 0842 $currentCorrelationName = key($tmpFromParts); 0843 if ($tmpFromParts[$currentCorrelationName]['joinType'] != self::FROM) { 0844 break; 0845 } 0846 $lastFromCorrelationName = $currentCorrelationName; 0847 $this->_parts[self::FROM][$currentCorrelationName] = array_shift($tmpFromParts); 0848 } 0849 } else { 0850 $tmpFromParts = array(); 0851 } 0852 $this->_parts[self::FROM][$correlationName] = array( 0853 'joinType' => $type, 0854 'schema' => $schema, 0855 'tableName' => $tableName, 0856 'joinCondition' => $cond 0857 ); 0858 while ($tmpFromParts) { 0859 $currentCorrelationName = key($tmpFromParts); 0860 $this->_parts[self::FROM][$currentCorrelationName] = array_shift($tmpFromParts); 0861 } 0862 } 0863 0864 // add to the columns from this joined table 0865 if ($type == self::FROM && $lastFromCorrelationName == null) { 0866 $lastFromCorrelationName = true; 0867 } 0868 $this->_tableCols($correlationName, $cols, $lastFromCorrelationName); 0869 0870 return $this; 0871 } 0872 0873 /** 0874 * Handle JOIN... USING... syntax 0875 * 0876 * This is functionality identical to the existing JOIN methods, however 0877 * the join condition can be passed as a single column name. This method 0878 * then completes the ON condition by using the same field for the FROM 0879 * table and the JOIN table. 0880 * 0881 * <code> 0882 * $select = $db->select()->from('table1') 0883 * ->joinUsing('table2', 'column1'); 0884 * 0885 * // SELECT * FROM table1 JOIN table2 ON table1.column1 = table2.column2 0886 * </code> 0887 * 0888 * These joins are called by the developer simply by adding 'Using' to the 0889 * method name. E.g. 0890 * * joinUsing 0891 * * joinInnerUsing 0892 * * joinFullUsing 0893 * * joinRightUsing 0894 * * joinLeftUsing 0895 * 0896 * @return Zend_Db_Select This Zend_Db_Select object. 0897 */ 0898 public function _joinUsing($type, $name, $cond, $cols = '*', $schema = null) 0899 { 0900 if (empty($this->_parts[self::FROM])) { 0901 // require_once 'Zend/Db/Select/Exception.php'; 0902 throw new Zend_Db_Select_Exception("You can only perform a joinUsing after specifying a FROM table"); 0903 } 0904 0905 $join = $this->_adapter->quoteIdentifier(key($this->_parts[self::FROM]), true); 0906 $from = $this->_adapter->quoteIdentifier($this->_uniqueCorrelation($name), true); 0907 0908 $joinCond = array(); 0909 foreach ((array)$cond as $fieldName) { 0910 $cond1 = $from . '.' . $fieldName; 0911 $cond2 = $join . '.' . $fieldName; 0912 $joinCond[] = $cond1 . ' = ' . $cond2; 0913 } 0914 $cond = implode(' '.self::SQL_AND.' ', $joinCond); 0915 0916 return $this->_join($type, $name, $cond, $cols, $schema); 0917 } 0918 0919 /** 0920 * Generate a unique correlation name 0921 * 0922 * @param string|array $name A qualified identifier. 0923 * @return string A unique correlation name. 0924 */ 0925 private function _uniqueCorrelation($name) 0926 { 0927 if (is_array($name)) { 0928 $k = key($name); 0929 $c = is_string($k) ? $k : end($name); 0930 } else { 0931 // Extract just the last name of a qualified table name 0932 $dot = strrpos($name,'.'); 0933 $c = ($dot === false) ? $name : substr($name, $dot+1); 0934 } 0935 for ($i = 2; array_key_exists($c, $this->_parts[self::FROM]); ++$i) { 0936 $c = $name . '_' . (string) $i; 0937 } 0938 return $c; 0939 } 0940 0941 /** 0942 * Adds to the internal table-to-column mapping array. 0943 * 0944 * @param string $tbl The table/join the columns come from. 0945 * @param array|string $cols The list of columns; preferably as 0946 * an array, but possibly as a string containing one column. 0947 * @param bool|string True if it should be prepended, a correlation name if it should be inserted 0948 * @return void 0949 */ 0950 protected function _tableCols($correlationName, $cols, $afterCorrelationName = null) 0951 { 0952 if (!is_array($cols)) { 0953 $cols = array($cols); 0954 } 0955 0956 if ($correlationName == null) { 0957 $correlationName = ''; 0958 } 0959 0960 $columnValues = array(); 0961 0962 foreach (array_filter($cols) as $alias => $col) { 0963 $currentCorrelationName = $correlationName; 0964 if (is_string($col)) { 0965 // Check for a column matching "<column> AS <alias>" and extract the alias name 0966 $col = trim(str_replace("\n", ' ', $col)); 0967 if (preg_match('/^(.+)\s+' . self::SQL_AS . '\s+(.+)$/i', $col, $m)) { 0968 $col = $m[1]; 0969 $alias = $m[2]; 0970 } 0971 // Check for columns that look like functions and convert to Zend_Db_Expr 0972 if (preg_match(self::REGEX_COLUMN_EXPR, (string) $col)) { 0973 $col = new Zend_Db_Expr($col); 0974 } elseif (preg_match('/(.+)\.(.+)/', $col, $m)) { 0975 $currentCorrelationName = $m[1]; 0976 $col = $m[2]; 0977 } 0978 } 0979 $columnValues[] = array($currentCorrelationName, $col, is_string($alias) ? $alias : null); 0980 } 0981 0982 if ($columnValues) { 0983 0984 // should we attempt to prepend or insert these values? 0985 if ($afterCorrelationName === true || is_string($afterCorrelationName)) { 0986 $tmpColumns = $this->_parts[self::COLUMNS]; 0987 $this->_parts[self::COLUMNS] = array(); 0988 } else { 0989 $tmpColumns = array(); 0990 } 0991 0992 // find the correlation name to insert after 0993 if (is_string($afterCorrelationName)) { 0994 while ($tmpColumns) { 0995 $this->_parts[self::COLUMNS][] = $currentColumn = array_shift($tmpColumns); 0996 if ($currentColumn[0] == $afterCorrelationName) { 0997 break; 0998 } 0999 } 1000 } 1001 1002 // apply current values to current stack 1003 foreach ($columnValues as $columnValue) { 1004 array_push($this->_parts[self::COLUMNS], $columnValue); 1005 } 1006 1007 // finish ensuring that all previous values are applied (if they exist) 1008 while ($tmpColumns) { 1009 array_push($this->_parts[self::COLUMNS], array_shift($tmpColumns)); 1010 } 1011 } 1012 } 1013 1014 /** 1015 * Internal function for creating the where clause 1016 * 1017 * @param string $condition 1018 * @param mixed $value optional 1019 * @param string $type optional 1020 * @param boolean $bool true = AND, false = OR 1021 * @return string clause 1022 */ 1023 protected function _where($condition, $value = null, $type = null, $bool = true) 1024 { 1025 if (count($this->_parts[self::UNION])) { 1026 // require_once 'Zend/Db/Select/Exception.php'; 1027 throw new Zend_Db_Select_Exception("Invalid use of where clause with " . self::SQL_UNION); 1028 } 1029 1030 if ($value !== null) { 1031 $condition = $this->_adapter->quoteInto($condition, $value, $type); 1032 } 1033 1034 $cond = ""; 1035 if ($this->_parts[self::WHERE]) { 1036 if ($bool === true) { 1037 $cond = self::SQL_AND . ' '; 1038 } else { 1039 $cond = self::SQL_OR . ' '; 1040 } 1041 } 1042 1043 return $cond . "($condition)"; 1044 } 1045 1046 /** 1047 * @return array 1048 */ 1049 protected function _getDummyTable() 1050 { 1051 return array(); 1052 } 1053 1054 /** 1055 * Return a quoted schema name 1056 * 1057 * @param string $schema The schema name OPTIONAL 1058 * @return string|null 1059 */ 1060 protected function _getQuotedSchema($schema = null) 1061 { 1062 if ($schema === null) { 1063 return null; 1064 } 1065 return $this->_adapter->quoteIdentifier($schema, true) . '.'; 1066 } 1067 1068 /** 1069 * Return a quoted table name 1070 * 1071 * @param string $tableName The table name 1072 * @param string $correlationName The correlation name OPTIONAL 1073 * @return string 1074 */ 1075 protected function _getQuotedTable($tableName, $correlationName = null) 1076 { 1077 return $this->_adapter->quoteTableAs($tableName, $correlationName, true); 1078 } 1079 1080 /** 1081 * Render DISTINCT clause 1082 * 1083 * @param string $sql SQL query 1084 * @return string 1085 */ 1086 protected function _renderDistinct($sql) 1087 { 1088 if ($this->_parts[self::DISTINCT]) { 1089 $sql .= ' ' . self::SQL_DISTINCT; 1090 } 1091 1092 return $sql; 1093 } 1094 1095 /** 1096 * Render DISTINCT clause 1097 * 1098 * @param string $sql SQL query 1099 * @return string|null 1100 */ 1101 protected function _renderColumns($sql) 1102 { 1103 if (!count($this->_parts[self::COLUMNS])) { 1104 return null; 1105 } 1106 1107 $columns = array(); 1108 foreach ($this->_parts[self::COLUMNS] as $columnEntry) { 1109 list($correlationName, $column, $alias) = $columnEntry; 1110 if ($column instanceof Zend_Db_Expr) { 1111 $columns[] = $this->_adapter->quoteColumnAs($column, $alias, true); 1112 } else { 1113 if ($column == self::SQL_WILDCARD) { 1114 $column = new Zend_Db_Expr(self::SQL_WILDCARD); 1115 $alias = null; 1116 } 1117 if (empty($correlationName)) { 1118 $columns[] = $this->_adapter->quoteColumnAs($column, $alias, true); 1119 } else { 1120 $columns[] = $this->_adapter->quoteColumnAs(array($correlationName, $column), $alias, true); 1121 } 1122 } 1123 } 1124 1125 return $sql . ' ' . implode(', ', $columns); 1126 } 1127 1128 /** 1129 * Render FROM clause 1130 * 1131 * @param string $sql SQL query 1132 * @return string 1133 */ 1134 protected function _renderFrom($sql) 1135 { 1136 /* 1137 * If no table specified, use RDBMS-dependent solution 1138 * for table-less query. e.g. DUAL in Oracle. 1139 */ 1140 if (empty($this->_parts[self::FROM])) { 1141 $this->_parts[self::FROM] = $this->_getDummyTable(); 1142 } 1143 1144 $from = array(); 1145 1146 foreach ($this->_parts[self::FROM] as $correlationName => $table) { 1147 $tmp = ''; 1148 1149 $joinType = ($table['joinType'] == self::FROM) ? self::INNER_JOIN : $table['joinType']; 1150 1151 // Add join clause (if applicable) 1152 if (! empty($from)) { 1153 $tmp .= ' ' . strtoupper($joinType) . ' '; 1154 } 1155 1156 $tmp .= $this->_getQuotedSchema($table['schema']); 1157 $tmp .= $this->_getQuotedTable($table['tableName'], $correlationName); 1158 1159 // Add join conditions (if applicable) 1160 if (!empty($from) && ! empty($table['joinCondition'])) { 1161 $tmp .= ' ' . self::SQL_ON . ' ' . $table['joinCondition']; 1162 } 1163 1164 // Add the table name and condition add to the list 1165 $from[] = $tmp; 1166 } 1167 1168 // Add the list of all joins 1169 if (!empty($from)) { 1170 $sql .= ' ' . self::SQL_FROM . ' ' . implode("\n", $from); 1171 } 1172 1173 return $sql; 1174 } 1175 1176 /** 1177 * Render UNION query 1178 * 1179 * @param string $sql SQL query 1180 * @return string 1181 */ 1182 protected function _renderUnion($sql) 1183 { 1184 if ($this->_parts[self::UNION]) { 1185 $parts = count($this->_parts[self::UNION]); 1186 foreach ($this->_parts[self::UNION] as $cnt => $union) { 1187 list($target, $type) = $union; 1188 if ($target instanceof Zend_Db_Select) { 1189 $target = $target->assemble(); 1190 } 1191 $sql .= $target; 1192 if ($cnt < $parts - 1) { 1193 $sql .= ' ' . $type . ' '; 1194 } 1195 } 1196 } 1197 1198 return $sql; 1199 } 1200 1201 /** 1202 * Render WHERE clause 1203 * 1204 * @param string $sql SQL query 1205 * @return string 1206 */ 1207 protected function _renderWhere($sql) 1208 { 1209 if ($this->_parts[self::FROM] && $this->_parts[self::WHERE]) { 1210 $sql .= ' ' . self::SQL_WHERE . ' ' . implode(' ', $this->_parts[self::WHERE]); 1211 } 1212 1213 return $sql; 1214 } 1215 1216 /** 1217 * Render GROUP clause 1218 * 1219 * @param string $sql SQL query 1220 * @return string 1221 */ 1222 protected function _renderGroup($sql) 1223 { 1224 if ($this->_parts[self::FROM] && $this->_parts[self::GROUP]) { 1225 $group = array(); 1226 foreach ($this->_parts[self::GROUP] as $term) { 1227 $group[] = $this->_adapter->quoteIdentifier($term, true); 1228 } 1229 $sql .= ' ' . self::SQL_GROUP_BY . ' ' . implode(",\n\t", $group); 1230 } 1231 1232 return $sql; 1233 } 1234 1235 /** 1236 * Render HAVING clause 1237 * 1238 * @param string $sql SQL query 1239 * @return string 1240 */ 1241 protected function _renderHaving($sql) 1242 { 1243 if ($this->_parts[self::FROM] && $this->_parts[self::HAVING]) { 1244 $sql .= ' ' . self::SQL_HAVING . ' ' . implode(' ', $this->_parts[self::HAVING]); 1245 } 1246 1247 return $sql; 1248 } 1249 1250 /** 1251 * Render ORDER clause 1252 * 1253 * @param string $sql SQL query 1254 * @return string 1255 */ 1256 protected function _renderOrder($sql) 1257 { 1258 if ($this->_parts[self::ORDER]) { 1259 $order = array(); 1260 foreach ($this->_parts[self::ORDER] as $term) { 1261 if (is_array($term)) { 1262 if(is_numeric($term[0]) && strval(intval($term[0])) == $term[0]) { 1263 $order[] = (int)trim($term[0]) . ' ' . $term[1]; 1264 } else { 1265 $order[] = $this->_adapter->quoteIdentifier($term[0], true) . ' ' . $term[1]; 1266 } 1267 } elseif (is_numeric($term) && strval(intval($term)) == $term) { 1268 $order[] = (int)trim($term); 1269 } else { 1270 $order[] = $this->_adapter->quoteIdentifier($term, true); 1271 } 1272 } 1273 $sql .= ' ' . self::SQL_ORDER_BY . ' ' . implode(', ', $order); 1274 } 1275 1276 return $sql; 1277 } 1278 1279 /** 1280 * Render LIMIT OFFSET clause 1281 * 1282 * @param string $sql SQL query 1283 * @return string 1284 */ 1285 protected function _renderLimitoffset($sql) 1286 { 1287 $count = 0; 1288 $offset = 0; 1289 1290 if (!empty($this->_parts[self::LIMIT_OFFSET])) { 1291 $offset = (int) $this->_parts[self::LIMIT_OFFSET]; 1292 $count = PHP_INT_MAX; 1293 } 1294 1295 if (!empty($this->_parts[self::LIMIT_COUNT])) { 1296 $count = (int) $this->_parts[self::LIMIT_COUNT]; 1297 } 1298 1299 /* 1300 * Add limits clause 1301 */ 1302 if ($count > 0) { 1303 $sql = trim($this->_adapter->limit($sql, $count, $offset)); 1304 } 1305 1306 return $sql; 1307 } 1308 1309 /** 1310 * Render FOR UPDATE clause 1311 * 1312 * @param string $sql SQL query 1313 * @return string 1314 */ 1315 protected function _renderForupdate($sql) 1316 { 1317 if ($this->_parts[self::FOR_UPDATE]) { 1318 $sql .= ' ' . self::SQL_FOR_UPDATE; 1319 } 1320 1321 return $sql; 1322 } 1323 1324 /** 1325 * Turn magic function calls into non-magic function calls 1326 * for joinUsing syntax 1327 * 1328 * @param string $method 1329 * @param array $args OPTIONAL Zend_Db_Table_Select query modifier 1330 * @return Zend_Db_Select 1331 * @throws Zend_Db_Select_Exception If an invalid method is called. 1332 */ 1333 public function __call($method, array $args) 1334 { 1335 $matches = array(); 1336 1337 /** 1338 * Recognize methods for Has-Many cases: 1339 * findParent<Class>() 1340 * findParent<Class>By<Rule>() 1341 * Use the non-greedy pattern repeat modifier e.g. \w+? 1342 */ 1343 if (preg_match('/^join([a-zA-Z]*?)Using$/', $method, $matches)) { 1344 $type = strtolower($matches[1]); 1345 if ($type) { 1346 $type .= ' join'; 1347 if (!in_array($type, self::$_joinTypes)) { 1348 // require_once 'Zend/Db/Select/Exception.php'; 1349 throw new Zend_Db_Select_Exception("Unrecognized method '$method()'"); 1350 } 1351 if (in_array($type, array(self::CROSS_JOIN, self::NATURAL_JOIN))) { 1352 // require_once 'Zend/Db/Select/Exception.php'; 1353 throw new Zend_Db_Select_Exception("Cannot perform a joinUsing with method '$method()'"); 1354 } 1355 } else { 1356 $type = self::INNER_JOIN; 1357 } 1358 array_unshift($args, $type); 1359 return call_user_func_array(array($this, '_joinUsing'), $args); 1360 } 1361 1362 // require_once 'Zend/Db/Select/Exception.php'; 1363 throw new Zend_Db_Select_Exception("Unrecognized method '$method()'"); 1364 } 1365 1366 /** 1367 * Implements magic method. 1368 * 1369 * @return string This object as a SELECT string. 1370 */ 1371 public function __toString() 1372 { 1373 try { 1374 $sql = $this->assemble(); 1375 } catch (Exception $e) { 1376 trigger_error($e->getMessage(), E_USER_WARNING); 1377 $sql = ''; 1378 } 1379 return (string)$sql; 1380 } 1381 1382 }