├── BasicQuery.php ├── CREDITS ├── DeleteQuery.php ├── InsertQuery.php ├── LICENSE ├── README.md ├── SelectQuery.php ├── UpdateQuery.php ├── additional.php ├── autoload.php └── tests ├── AllTests.php ├── DeleteQueryTest.php ├── InsertQueryTest.php ├── OtherTest.php ├── SelectQueryTest.php └── UpdateQueryTest.php /BasicQuery.php: -------------------------------------------------------------------------------- 1 | 10 | Copyright © 2005-2006 Konstantin Sedov 11 | 12 | This library is free software; you can redistribute it and/or 13 | modify it under the terms of the GNU Lesser General Public 14 | License as published by the Free Software Foundation; either 15 | version 2.1 of the License, or (at your option) any later version. 16 | 17 | This library is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | Lesser General Public License for more details. 21 | 22 | You should have received a copy of the GNU Lesser General Public 23 | License along with this library; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 25 | */ 26 | 27 | /** 28 | * This class contains all the common logic shared by other query-classes 29 | * 30 | * @package mysql-query-builder 31 | * @author Alexey Zakhlestin 32 | */ 33 | abstract class BasicQuery 34 | { 35 | private $conditions = null; 36 | private $parameters; 37 | private $sql = null; 38 | private $orderby; 39 | private $orderdirection; 40 | 41 | /** 42 | * contains QBTable objects related to current query 43 | * 44 | * @var array 45 | */ 46 | protected $from = array(); 47 | 48 | /** 49 | * Constructor provides common logic (all queries are done on tables), but does not direct instantiation of BasicQuery 50 | * 51 | * @param mixed $tables 52 | */ 53 | protected function __construct($tables) 54 | { 55 | $this->setTables($tables); 56 | } 57 | 58 | /** 59 | * Sets which table(s) the query will be applied to 60 | * 61 | * @param mixed $tables Can be either string, QBTable instance or array of strings/QBTables 62 | * @return void 63 | * @throws InvalidArgumentException, LogicException 64 | */ 65 | public function setTables($tables) 66 | { 67 | if (is_string($tables) or $tables instanceof QBTable) 68 | $tables = array($tables); 69 | 70 | if (!is_array($tables)) 71 | throw new InvalidArgumentException('table(s) should be specified as a string, or array of strings'); 72 | 73 | if (count($tables) == 0) 74 | throw new InvalidArgumentException('there were no tables, specified'); 75 | 76 | $this->from = array(); 77 | foreach ($tables as $table) { 78 | if (is_string($table)) { 79 | $this->from[] = new QBTable($table); 80 | } elseif ($table instanceof QBTable) { 81 | $this->from[] = $table; 82 | } else { 83 | throw new LogicException("Invalid object is provided as a table"); 84 | } 85 | } 86 | 87 | $this->reset(); 88 | } 89 | 90 | /** 91 | * Sets where-condition, which will be applied to query 92 | * The most typical objects to use as parameters are Condition and AndOp 93 | * 94 | * @param MQB_Condition $conditions 95 | * @return void 96 | * @author Jimi Dini 97 | */ 98 | public function setWhere(MQB_Condition $conditions = null) 99 | { 100 | if (null === $conditions) { 101 | $this->conditions = null; 102 | } elseif ($conditions instanceof MQB_Condition) { 103 | $this->conditions = clone $conditions; 104 | } 105 | 106 | $this->reset(); 107 | } 108 | 109 | /** 110 | * setup "ORDER BY" clause of Query. 111 | * $orderlist is supposed to be array of objects implementing MQB_Field (most-probably, Field objects). 112 | * $orderdirectionlist is supposed to be array of booleans, where TRUE means DESC and FALSE means ASC. 113 | * if number of elements of $orderdirectionlist is smaller that number of elements of $orderlist array, then ASC is applied to the tail-objects 114 | * 115 | * @param array $orderlist 116 | * @param array $orderdirectionlist 117 | * @return void 118 | * @throws InvalidArgumentException 119 | */ 120 | public function setOrderby(array $orderlist, array $orderdirectionlist = array()) 121 | { 122 | foreach ($orderlist as $field) 123 | if (!($field instanceof MQB_Field)) 124 | throw new InvalidArgumentException('Only object implementing MQB_Field can be used in setOrderBy'); 125 | 126 | $this->orderby = $orderlist; 127 | $this->orderdirection = $orderdirectionlist; 128 | 129 | $this->reset(); 130 | } 131 | 132 | /** 133 | * accessor, which returns array of table-names used in Query. 134 | * 135 | * @return array 136 | */ 137 | public function showTables() 138 | { 139 | $res = array(); 140 | foreach ($this->from as $table) { 141 | $res[] = $table->getTable(); 142 | } 143 | 144 | return $res; 145 | } 146 | 147 | /** 148 | * accessor, which returns current-querys condition 149 | * 150 | * @return MQB_Condition 151 | */ 152 | public function showConditions() 153 | { 154 | return $this->conditions; 155 | } 156 | 157 | // internal stuff 158 | 159 | /** 160 | * This method should be overridden by descendents 161 | * 162 | * @param array $parameters 163 | * @return void 164 | * @throws LogicException 165 | */ 166 | protected function getSql(array &$parameters) 167 | { 168 | throw new LogicException(); 169 | } 170 | 171 | /** 172 | * Returns "FROM" clause which can be used in various queries 173 | * 174 | * @param array $parameters 175 | * @return void 176 | */ 177 | protected function getFrom(array &$parameters) 178 | { 179 | $froms = array(); 180 | for ($i = 0; $i < count($this->from); $i++) { 181 | $froms[] = $this->from[$i]->__toString().' AS `t'.$i.'`'; 182 | } 183 | 184 | $sql = ' FROM '.implode(", ", $froms); 185 | 186 | return $sql; 187 | } 188 | 189 | /** 190 | * Returns "WHERE" clause which can be used in various queries 191 | * 192 | * @param array $parameters 193 | * @return void 194 | */ 195 | protected function getWhere(array &$parameters) 196 | { 197 | if (null === $this->conditions) 198 | return ""; 199 | 200 | $sql = $this->conditions->getSql($parameters); 201 | 202 | if (empty($sql)) 203 | return ""; 204 | 205 | return " WHERE ".$sql; 206 | } 207 | 208 | /** 209 | * Returns "ORDER BY" clause which can be used in various queries 210 | * 211 | * @param array $parameters 212 | * @return void 213 | */ 214 | protected function getOrderby(array &$parameters) 215 | { 216 | if (!$this->orderby || !is_array($this->orderby)) 217 | return ""; 218 | 219 | foreach ($this->orderby as $i => $field) { 220 | if (array_key_exists($i, $this->orderdirection) && $this->orderdirection[$i]) 221 | $direction = ' DESC'; 222 | else 223 | $direction = ' ASC'; 224 | 225 | if (null !== $alias = $field->getAlias()) 226 | $sqls[] = $alias.$direction; 227 | else 228 | $sqls[] = $field->getSql($parameters).$direction; 229 | } 230 | 231 | return " ORDER BY ".implode(", ", $sqls); 232 | } 233 | 234 | /** 235 | * resets internal cache-structures, which are used for generation of sql-string and parameters-array 236 | * 237 | * @return void 238 | */ 239 | protected function reset() 240 | { 241 | $this->parameters = array(); 242 | $this->sql = null; 243 | } 244 | 245 | /** 246 | * rebuilds (if needed) and returns SQL-string, which can be used for "prepared" query 247 | * 248 | * @return string 249 | */ 250 | public function sql(array &$parameters = null) 251 | { 252 | if (null === $this->sql) { 253 | if (is_array($parameters)) { 254 | $this->parameters = &$parameters; 255 | } else { 256 | $this->parameters = array(); 257 | } 258 | $this->sql = $this->getSql($this->parameters); 259 | } 260 | 261 | return $this->sql; 262 | } 263 | 264 | /** 265 | * returns array of parameters, which can be used with SQL-string from ->sql() method. 266 | * WARNING: this method does not rebuild SQL-Query. Be sure to call ->sql() before using it. 267 | * 268 | * @return array 269 | * @author Jimi Dini 270 | */ 271 | public function parameters() 272 | { 273 | if (null === $this->sql) { 274 | throw new LogicException('->sql() method should be called, before calling ->parameters() method'); 275 | } 276 | 277 | return $this->parameters; 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | MySQL Query Builder 2 | 3 | Major contributions: 4 | Alexey Zakhlestine 5 | Konstantin Sedov 6 | 7 | Subquery support: 8 | Igor Vasilcovsky 9 | Maxim Ponomariov -------------------------------------------------------------------------------- /DeleteQuery.php: -------------------------------------------------------------------------------- 1 | 10 | Copyright © 2005-2006 Konstantin Sedov 11 | 12 | This library is free software; you can redistribute it and/or 13 | modify it under the terms of the GNU Lesser General Public 14 | License as published by the Free Software Foundation; either 15 | version 2.1 of the License, or (at your option) any later version. 16 | 17 | This library is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | Lesser General Public License for more details. 21 | 22 | You should have received a copy of the GNU Lesser General Public 23 | License along with this library; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 25 | */ 26 | 27 | /** 28 | * This class contains logic of "DELETE" queries 29 | * 30 | * @package mysql-query-builder 31 | * @author Alexey Zakhlestin 32 | */ 33 | class DeleteQuery extends BasicQuery 34 | { 35 | private $del_limit = null; 36 | private $del_tables = null; 37 | 38 | public function __construct($tables, array $del_tables = array(0)) 39 | { 40 | parent::__construct($tables); 41 | $this->del_tables = $del_tables; 42 | } 43 | 44 | /** 45 | * wrapper around BasicQuery::setOrderBy, which additionally checks if it is allowed, to apply order to the query. 46 | * it is allowed only for single-table queries 47 | * 48 | * @param array $orderlist 49 | * @param array $orderdirectionlist 50 | * @return void 51 | * @throws LogicException 52 | */ 53 | public function setOrderby(array $orderlist, array $orderdirectionlist = array()) 54 | { 55 | if (count($this->from) != 1) { 56 | throw new LogicException("setOrderby is allowed only in single-table delete queries"); 57 | } 58 | 59 | parent::setOrderby($orderlist, $orderdirectionlist); 60 | } 61 | 62 | /** 63 | * Sets maximum number of rows, the DELETE query will be applied to. 64 | * MySQL does not allow to specify offset, so, it is just a single number 65 | * 66 | * @param integer $limit 67 | * @return void 68 | * @throws LogicException, InvalidArgumentException 69 | */ 70 | public function setLimit($limit) 71 | { 72 | if (count($this->from) != 1) { 73 | throw new LogicException("setLimit is allowed only in single-table delete queries"); 74 | } 75 | 76 | if (!is_numeric($limit) or $limit < 1) 77 | throw new InvalidArgumentException('positive number should be used as a limit'); 78 | 79 | $this->del_limit = (string)$limit; 80 | } 81 | 82 | protected function getSql(array &$parameters) 83 | { 84 | $sql = $this->getDelete($parameters); 85 | $sql .= $this->getUsing($parameters); 86 | $sql .= $this->getWhere($parameters); 87 | $sql .= $this->getOrderby($parameters); 88 | $sql .= $this->getLimit($parameters); 89 | 90 | if (count($this->from) == 1) { 91 | $sql = str_replace('`t0`', $this->from[0]->__toString(), $sql); 92 | } 93 | 94 | return $sql; 95 | } 96 | 97 | private function getDelete(&$parameters) 98 | { 99 | if (count($this->from) == 1) 100 | return 'DELETE FROM '.$this->from[0]->__toString(); 101 | else { 102 | $sql = 'DELETE FROM'; 103 | 104 | $first = true; 105 | foreach ($this->del_tables as $tbl) { 106 | if ($first) { 107 | $first = false; 108 | } else { 109 | $sql .= ','; 110 | } 111 | 112 | $sql .= ' `t'.$tbl.'`'; 113 | } 114 | 115 | return $sql; 116 | } 117 | } 118 | 119 | protected function getUsing(&$parameters) 120 | { 121 | if (count($this->from) == 1) 122 | return ''; 123 | 124 | $froms = array(); 125 | for ($i = 0; $i < count($this->from); $i++) { 126 | $froms[] = $this->from[$i]->__toString().' AS `t'.$i.'`'; 127 | } 128 | 129 | return " USING ".implode(", ", $froms); 130 | } 131 | 132 | protected function getLimit() 133 | { 134 | return (null == $this->del_limit) ? '' : ' LIMIT '.$this->del_limit; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /InsertQuery.php: -------------------------------------------------------------------------------- 1 | 10 | Copyright © 2005-2006 Konstantin Sedov 11 | 12 | This library is free software; you can redistribute it and/or 13 | modify it under the terms of the GNU Lesser General Public 14 | License as published by the Free Software Foundation; either 15 | version 2.1 of the License, or (at your option) any later version. 16 | 17 | This library is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | Lesser General Public License for more details. 21 | 22 | You should have received a copy of the GNU Lesser General Public 23 | License along with this library; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 25 | */ 26 | 27 | /** 28 | * This class contains logic of "INSERT" queries 29 | * 30 | * @package mysql-query-builder 31 | * @author Alexey Zakhlestin 32 | */ 33 | class InsertQuery extends BasicQuery 34 | { 35 | private $values; 36 | private $on_duplicate_update = false; 37 | 38 | /** 39 | * Constructor of INSERT query. 40 | * WARNING: INSERT can be applied only to the single table. You can use array as the first parameter of constructor, but it should be array of 1 element 41 | * 42 | * @param mixed $tables 43 | * @param bool $on_duplicate_update 44 | * @throws InvalidArgumentException 45 | */ 46 | public function __construct($tables, $on_duplicate_update = false) 47 | { 48 | parent::__construct($tables); 49 | 50 | if (count($this->from) != 1) 51 | throw new InvalidArgumentException('INSERT can be used only on the single table'); 52 | 53 | $this->on_duplicate_update = $on_duplicate_update; 54 | } 55 | 56 | /** 57 | * magic accessor, which lets setting parts of "SET …" clause with simple "$obj->field = 'value';" statements 58 | * 59 | * @param string $key 60 | * @param mixed $value 61 | * @return void 62 | */ 63 | public function __set($key, $value) 64 | { 65 | $this->values[$key] = new Parameter($value); 66 | $this->reset(); 67 | } 68 | 69 | /** 70 | * sets "SET …" clause of query to the new value. Array is supposed to be in the following format: 71 | * [field_name => value, field2 => value2, …] 72 | * 73 | * @param array $values 74 | * @return void 75 | */ 76 | public function setValues(array $values) 77 | { 78 | $this->values = array(); 79 | 80 | foreach ($values as $key => $value) { 81 | $this->values[$key] = new Parameter($value); 82 | } 83 | 84 | $this->reset(); 85 | } 86 | 87 | protected function getSql(array &$parameters) 88 | { 89 | $sql = $this->getInsert($parameters); 90 | $sql .= $this->getValues($parameters); 91 | 92 | if (true === $this->on_duplicate_update) { 93 | $sql .= $this->getUpdate($parameters); 94 | } 95 | 96 | return $sql; 97 | } 98 | 99 | private function getInsert(&$parameters) 100 | { 101 | $inserts = array(); 102 | foreach (array_keys($this->values) as $key) { 103 | $inserts[] = '`'.$key.'`'; 104 | } 105 | 106 | $sql = "INSERT INTO ".$this->from[0]->__toString()." (".implode(", ", $inserts).")"; 107 | 108 | return $sql; 109 | } 110 | 111 | private function getValues(&$parameters) 112 | { 113 | $values = array(); 114 | foreach ($this->values as $k => $v) { 115 | $values[] = $v->getSql($parameters); 116 | } 117 | $sql = " VALUES (".implode(", ", $values).")"; 118 | 119 | return $sql; 120 | } 121 | 122 | private function getUpdate(&$parameters) 123 | { 124 | // if (!isset($this->values['id'])) 125 | // throw new LogicException("id field is required for ON DUPLICATE KEY UPDATE functionality"); 126 | 127 | $values = array(); 128 | foreach ($this->values as $k => $v) { 129 | if ('id' == $k) { // FIXME: не всегда первичным ключом является id 130 | $values[] = '`id` = LAST_INSERT_ID(`id`)'; 131 | } else { 132 | $values[] = '`'.$k.'` = VALUES(`'.$k.'`)'; 133 | } 134 | } 135 | 136 | $sql = " ON DUPLICATE KEY UPDATE ".implode(", ", $values); 137 | 138 | return $sql; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 489 | 490 | Also add information on how to contact you by electronic and paper mail. 491 | 492 | You should also get your employer (if you work as a programmer) or your 493 | school, if any, to sign a "copyright disclaimer" for the library, if 494 | necessary. Here is a sample; alter the names: 495 | 496 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 497 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 498 | 499 | , 1 April 1990 500 | Ty Coon, President of Vice 501 | 502 | That's all there is to it! 503 | 504 | 505 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Object-oriented API for PHP5, for composing MySQL5 queries. Primarily targeted to [PDO_MYSQL](http://docs.php.net/pdo_mysql). 2 | 3 | Not actively developed. 4 | -------------------------------------------------------------------------------- /SelectQuery.php: -------------------------------------------------------------------------------- 1 | 10 | Copyright © 2005-2006 Konstantin Sedov 11 | 12 | This library is free software; you can redistribute it and/or 13 | modify it under the terms of the GNU Lesser General Public 14 | License as published by the Free Software Foundation; either 15 | version 2.1 of the License, or (at your option) any later version. 16 | 17 | This library is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | Lesser General Public License for more details. 21 | 22 | You should have received a copy of the GNU Lesser General Public 23 | License along with this library; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 25 | */ 26 | 27 | /** 28 | * This class contains logic of "SELECT" queries 29 | * 30 | * @package mysql-query-builder 31 | * @author Alexey Zakhlestin 32 | */ 33 | class SelectQuery extends BasicQuery 34 | { 35 | private $limit = null; 36 | private $selects = null; 37 | private $groupby = null; 38 | private $havings = null; 39 | private $indices = null; 40 | 41 | private $distinct = false; 42 | 43 | /** 44 | * Creates new SELECT-query object. 45 | * By default, it is equivalent of "SELECT t0.* FROM t0, t1, t2, tN", where t0-tN are tables given to this constructor 46 | * 47 | * @param mixed $tables 48 | */ 49 | public function __construct($tables) 50 | { 51 | parent::__construct($tables); 52 | $this->setSelect(array(new AllFields())); 53 | } 54 | 55 | /** 56 | * Specifies, which columns should be selected. 57 | * $_select can be instance of AllFields, MQB_Field or array of such instances. 58 | * If $distinct is set to TRUE, 'SELECT DISTINCT …' query shall be used 59 | * 60 | * @param mixed $_selects 61 | * @param bool $distinct 62 | * @return void 63 | * @throws InvalidArgumentException, RangeException 64 | */ 65 | public function setSelect($_selects, $distinct = false) 66 | { 67 | if (!is_array($_selects)) { 68 | $_selects = array($_selects); 69 | } 70 | 71 | if (count($_selects) == 0) { 72 | throw new InvalidArgumentException('Nothing to select'); 73 | } 74 | 75 | if (!is_bool($distinct)) { 76 | throw new InvalidArgumentException('"distinct" parameter should be boolean'); 77 | } 78 | 79 | foreach ($_selects as $s) { 80 | if (!($s instanceof MQB_Field) and !($s instanceof AllFields)) 81 | throw new RangeException('Allowed values are objects of the following classes: Field, AllFields, sqlFunction and Aggregate'); 82 | } 83 | 84 | $this->selects = $_selects; 85 | $this->distinct = $distinct; 86 | $this->reset(); 87 | } 88 | 89 | /** 90 | * Specifies, which index(es) should be preferred for the first table of query 91 | * $indices should be either string or array of strings 92 | * 93 | * @param mixed $indices 94 | * @return void 95 | */ 96 | public function setIndices($indices) 97 | { 98 | if (!is_array($indices)) { 99 | $indices = array($indices); 100 | } 101 | 102 | $this->indices = $indices; 103 | } 104 | 105 | /** 106 | * Specifies, if the query should use "GROUP BY" clause. 107 | * MQB_Field of array of MQB_Fields is allowed as parameter 108 | * 109 | * @param mixed $orderlist 110 | * @return void 111 | * @throws InvalidArgumentException 112 | */ 113 | public function setGroupby($orderlist) 114 | { 115 | if (!is_array($orderlist)) 116 | $orderlist = array($orderlist); 117 | 118 | foreach ($orderlist as $field) 119 | if (!($field instanceof MQB_Field)) 120 | throw new InvalidArgumentException('setGroupBy takes only [array of] MQB_Fields as parameter'); 121 | 122 | $this->groupby = $orderlist; 123 | $this->reset(); 124 | } 125 | 126 | /** 127 | * setup "LIMIT" clause of Query. 128 | * 129 | * @param integer $limit 130 | * @param integer $offset 131 | * @return void 132 | * @throws InvalidArgumentException 133 | */ 134 | public function setLimit($limit, $offset=0) 135 | { 136 | if (!is_numeric($limit) or !is_numeric($offset)) 137 | throw new InvalidArgumentException('Limit should be specified using numerics'); 138 | 139 | $this->limit = array($limit, $offset); 140 | } 141 | 142 | /** 143 | * Accessor, which returns internal "GROUP BY" array 144 | * 145 | * @return array 146 | */ 147 | public function showGroupBy() 148 | { 149 | return $this->groupby; 150 | } 151 | 152 | protected function getSql(array &$parameters) 153 | { 154 | return $this->getSelect($parameters). 155 | $this->getFrom($parameters). 156 | // $this->getIndices(). 157 | $this->getWhere($parameters). 158 | $this->getGroupby($parameters). 159 | $this->getHaving($parameters). 160 | $this->getOrderby($parameters). 161 | $this->getLimit($parameters); 162 | } 163 | 164 | /** 165 | * Returns "LIMIT" clause which can be used in various queries 166 | * 167 | * @param array $parameters 168 | * @return void 169 | */ 170 | protected function getLimit(array &$parameters) 171 | { 172 | if (null === $this->limit) 173 | return ""; 174 | 175 | return " LIMIT ".$this->limit[0].' OFFSET '.$this->limit[1]; 176 | } 177 | 178 | /** 179 | * Returns number of columns, which will be returned by query 180 | * 181 | * @return integer 182 | */ 183 | public function countSelects() 184 | { 185 | return count($this->selects); 186 | } 187 | 188 | private function getSelect(&$parameters) 189 | { 190 | $res = 'SELECT '; 191 | 192 | if (true === $this->distinct) { 193 | $res .= 'DISTINCT '; 194 | } 195 | 196 | $sqls = array(); 197 | foreach ($this->selects as $s) { 198 | $sqls[] = $s->getSql($parameters, true); 199 | } 200 | 201 | return $res.implode(", ", $sqls); 202 | } 203 | 204 | protected function getFrom(array &$parameters) 205 | { 206 | $froms = array(); 207 | for ($i = 0; $i < count($this->from); $i++) { 208 | $_str = $this->from[$i]->__toString().' AS `t'.$i.'`'; 209 | if (0 == $i) 210 | $_str .= $this->getIndices(); 211 | $froms[] = $_str; 212 | } 213 | 214 | $sql = ' FROM '.implode(", ", $froms); 215 | 216 | return $sql; 217 | } 218 | 219 | private function getGroupby(&$parameters) 220 | { 221 | if ($this->groupby === null) 222 | return ""; 223 | 224 | foreach ($this->groupby as $groupby) { 225 | if (null !== $alias = $groupby->getAlias()) 226 | $sqls[] = $alias; 227 | else 228 | $sqls[] = $groupby->getSql($parameters); 229 | } 230 | 231 | return " GROUP BY ".implode(", ", $sqls); 232 | } 233 | 234 | /** 235 | * Specifies "HAVING" clause of query 236 | * 237 | * @param MQB_Condition $conditions 238 | * @return void 239 | * @author Jimi Dini 240 | */ 241 | public function setHaving(MQB_Condition $conditions = null) 242 | { 243 | if (null === $conditions) { 244 | $this->havings = null; 245 | } elseif ($conditions instanceof MQB_Condition) { 246 | $this->havings = clone $conditions; 247 | } 248 | 249 | $this->reset(); 250 | } 251 | 252 | protected function getHaving(&$parameters) 253 | { 254 | if (null == $this->havings) 255 | return ""; 256 | 257 | return " HAVING ".$this->havings->getSql($parameters); 258 | } 259 | 260 | protected function getIndices() 261 | { 262 | if (null === $this->indices) 263 | return ''; 264 | 265 | $res = ' USE INDEX ('; 266 | $first = true; 267 | foreach ($this->indices as $idx) { 268 | if (true === $first) 269 | $first = false; 270 | else 271 | $res .= ', '; 272 | 273 | $res .= '`'.$idx.'`'; 274 | } 275 | $res .= ')'; 276 | 277 | return $res; 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /UpdateQuery.php: -------------------------------------------------------------------------------- 1 | 10 | Copyright © 2005-2006 Konstantin Sedov 11 | 12 | This library is free software; you can redistribute it and/or 13 | modify it under the terms of the GNU Lesser General Public 14 | License as published by the Free Software Foundation; either 15 | version 2.1 of the License, or (at your option) any later version. 16 | 17 | This library is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | Lesser General Public License for more details. 21 | 22 | You should have received a copy of the GNU Lesser General Public 23 | License along with this library; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 25 | */ 26 | 27 | /** 28 | * This class contains logic of "UPDATE" queries 29 | * 30 | * @package mysql-query-builder 31 | * @author Alexey Zakhlestin 32 | */ 33 | class UpdateQuery extends BasicQuery 34 | { 35 | private $set_fields = null; 36 | private $set_values = null; 37 | private $up_limit = null; 38 | 39 | /** 40 | * Creates new UPDATE-query object. 41 | * By default, it is equivalent of "UPDATE t0, t1, t2, tN SET …", where t0-tN are tables given to this constructor 42 | * Be sure to specify some fields to be updated, or query will fail to be generated 43 | * 44 | * @param mixed $tables 45 | */ 46 | public function __construct($tables) 47 | { 48 | parent::__construct($tables); 49 | 50 | $this->set_fields = array(); 51 | $this->set_values = array(); 52 | } 53 | 54 | /** 55 | * magic accessor, which lets setting parts of "SET …" clause with simple "$obj->field = 'value';" statements 56 | * 57 | * @param string $key 58 | * @param mixed $value 59 | * @return void 60 | */ 61 | public function __set($key, $value) 62 | { 63 | $this->set_fields[] = new Field($key); 64 | $this->set_values[] = new Parameter($value); 65 | $this->reset(); 66 | } 67 | 68 | /** 69 | * sets "SET …" clause of query to the new value. Array is supposed to be in one of the following formats: 70 | * 1) [field_name => value, field2 => value2, …] 71 | * 2) [[field_name, value], [field2, value2], …] 72 | * 73 | * @param array $sets 74 | * @return void 75 | */ 76 | public function setValues(array $sets) 77 | { 78 | $this->set_fields = array(); 79 | $this->set_values = array(); 80 | 81 | if (count($sets) == 0) 82 | return; 83 | 84 | foreach ($sets as $set => $value) { 85 | if (is_array($value)) { 86 | // nested arrays. $value[0] is Field, $value[1] is Parameter 87 | if (is_string($value[0])) 88 | $value[0] = new Field($value[0]); 89 | 90 | $this->set_fields[] = $value[0]; 91 | $this->set_values[] = new Parameter($value[1]); 92 | } else { 93 | // key-value pairs 94 | $this->set_fields[] = new Field($set); 95 | $this->set_values[] = new Parameter($value); 96 | } 97 | } 98 | 99 | $this->reset(); 100 | } 101 | 102 | /** 103 | * Sets maximum number of rows, the UPDATE query will be applied to. 104 | * MySQL does not allow to specify offset, so, it is just a single number 105 | * 106 | * @param integer $limit 107 | * @return void 108 | * @throws LogicException, InvalidArgumentException 109 | */ 110 | public function setLimit($limit) 111 | { 112 | if (count($this->from) != 1) { 113 | throw new LogicException("setLimit is allowed only in single-table update queries"); 114 | } 115 | 116 | if (!is_numeric($limit) or $limit < 1) 117 | throw new InvalidArgumentException('setLimit takes as single numeric greater than zero'); 118 | 119 | $this->up_limit = (string)$limit; 120 | } 121 | 122 | /** 123 | * wrapper around BasicQuery::setOrderBy, which additionally checks if it is allowed, to apply order to the query. 124 | * it is allowed only for single-table queries 125 | * 126 | * @param array $orderlist 127 | * @param array $orderdirectionlist 128 | * @return void 129 | * @throws LogicException 130 | */ 131 | public function setOrderby(array $orderlist, array $orderdirectionlist = array()) 132 | { 133 | if (count($this->from) != 1) { 134 | throw new LogicException("setOrderby is allowed only in single-table update queries"); 135 | } 136 | 137 | parent::setOrderby($orderlist, $orderdirectionlist); 138 | } 139 | 140 | protected function getSql(array &$parameters) 141 | { 142 | if (null === $this->set_fields) 143 | throw new LogicException("Nothing is specified to be set. Can't produce valid MySQL query"); 144 | 145 | return $this->getUpdate($parameters). 146 | $this->getSet($parameters). 147 | $this->getWhere($parameters). 148 | $this->getOrderby($parameters). 149 | $this->getLimit(); 150 | } 151 | 152 | protected function getLimit() 153 | { 154 | return (null == $this->up_limit) ? '' : ' LIMIT '.$this->up_limit; 155 | } 156 | 157 | private function getUpdate(&$parameters) 158 | { 159 | $sql = 'UPDATE '; 160 | 161 | for ($i = 0; $i < count($this->from); $i++) { 162 | if ($i > 0) 163 | $sql .= ', '; 164 | $sql .= $this->from[$i]->__toString().' AS `t'.$i.'`'; 165 | } 166 | 167 | return $sql; 168 | } 169 | 170 | protected function getSet(&$parameters) 171 | { 172 | if (null === $this->set_fields or 0 == count($this->set_fields)) 173 | throw new LogicException("Empty update-queries are forbidden"); 174 | 175 | $sqls = array(); 176 | foreach ($this->set_fields as $i => $value) { 177 | $sqls[] = $value->getSql($parameters).' = '.$this->set_values[$i]->getSql($parameters); 178 | } 179 | 180 | return " SET ".implode(", ", $sqls); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /additional.php: -------------------------------------------------------------------------------- 1 | 10 | Copyright © 2005-2006 Konstantin Sedov 11 | 12 | This library is free software; you can redistribute it and/or 13 | modify it under the terms of the GNU Lesser General Public 14 | License as published by the Free Software Foundation; either 15 | version 2.1 of the License, or (at your option) any later version. 16 | 17 | This library is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | Lesser General Public License for more details. 21 | 22 | You should have received a copy of the GNU Lesser General Public 23 | License along with this library; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 25 | */ 26 | 27 | /** 28 | * Defines set of methods, which must be implemented by any "CONDITION" object 29 | * 30 | * @package mysql-query-builder 31 | */ 32 | interface MQB_Condition 33 | { 34 | /** 35 | * used for generation of "prepared" SQL-queries. Supposed to be used recursively, and add parameters to the end of $parameters stack 36 | * 37 | * @param array $parameters 38 | * @return string 39 | */ 40 | public function getSql(array &$parameters); 41 | } 42 | 43 | /** 44 | * Defines set of methods, which must be implemented by any object, which is used as a Field of result-set row 45 | * 46 | * @package mysql-query-builder 47 | */ 48 | interface MQB_Field 49 | { 50 | /** 51 | * used for generation of "prepared" SQL-queries. Supposed to be used recursively, and add parameters to the end of $parameters stack 52 | * 53 | * @param array $parameters 54 | * @return string 55 | */ 56 | public function getSql(array &$parameters); 57 | 58 | /** 59 | * returns "alias" name of field 60 | * 61 | * @return string 62 | * @author Jimi Dini 63 | */ 64 | public function getAlias(); 65 | } 66 | 67 | 68 | 69 | /** 70 | * Represents the table-entity of SQL-query 71 | * 72 | * @package mysql-query-builder 73 | */ 74 | class QBTable 75 | { 76 | private $table_name = null; 77 | private $db_name = null; 78 | 79 | /** 80 | * Designated constructor of table-object. 81 | * 82 | * @param string $table_name 83 | * @param string $db_name 84 | */ 85 | public function __construct($table_name, $db_name = null) 86 | { 87 | $this->table_name = $table_name; 88 | $this->db_name = $db_name; 89 | } 90 | 91 | /** 92 | * accessor, which returns sql-friendly (escaped) string-representation of table 93 | * 94 | * @return string 95 | */ 96 | public function __toString() 97 | { 98 | $res = ''; 99 | 100 | if (null !== $this->db_name) { 101 | $res .= '`'.$this->db_name.'`.'; 102 | } 103 | 104 | $res .= '`'.$this->table_name.'`'; 105 | 106 | return $res; 107 | } 108 | 109 | /** 110 | * accessor, which returns raw table-name (without database-name) 111 | * 112 | * @return string 113 | */ 114 | public function getTable() 115 | { 116 | return $this->table_name; 117 | } 118 | } 119 | 120 | /** 121 | * generic Operator class. You can't instantiate this directly 122 | * 123 | * @package mysql-query-builder 124 | */ 125 | class Operator implements MQB_Condition 126 | { 127 | private $content = array(); 128 | protected $startSql; 129 | protected $implodeSql; 130 | protected $endSql; 131 | 132 | protected function __construct(array $content) 133 | { 134 | $this->setContent($content); 135 | } 136 | 137 | /** 138 | * Specifies array of MQB_Conditions, which should be used as the content of Operator 139 | * 140 | * @param array $content 141 | * @return void 142 | * @throws InvalidArgumentException 143 | */ 144 | public function setContent(array $content) 145 | { 146 | foreach ($content as $c) { 147 | if (!is_object($c) or !($c instanceof MQB_Condition)) { 148 | throw new InvalidArgumentException("Operators should be given valid Operators or Conditions as parameters"); 149 | } 150 | } 151 | 152 | $this->content = $content; 153 | } 154 | 155 | /** 156 | * accessor, which returns internal content-array 157 | * 158 | * @return array 159 | */ 160 | public function getContent() 161 | { 162 | return $this->content; 163 | } 164 | 165 | public function getSql(array &$parameters) 166 | { 167 | $sqlparts = array(); 168 | 169 | foreach ($this->content as $c) { 170 | $sqlparts[] = $c->getSql($parameters); 171 | } 172 | 173 | $parts = implode($this->implodeSql, $sqlparts); 174 | 175 | if (empty($parts)) 176 | return ''; 177 | 178 | return $this->startSql.$parts.$this->endSql; 179 | } 180 | } 181 | 182 | /** 183 | * Class, which implements SQLs "NOT()" operator 184 | * 185 | * @package mysql-query-builder 186 | */ 187 | class NotOp extends Operator 188 | { 189 | private $my_content = null; 190 | 191 | /** 192 | * designated constructor. takes either MQB_Condition or array consisting of the single MQB_Condition 193 | * 194 | * @param mixed $content 195 | * @throws InvalidArgumentException 196 | */ 197 | public function __construct($content) 198 | { 199 | if (is_array($content)) { 200 | // compatibility with "legacy" API 201 | if (count($content) != 1) 202 | throw new InvalidArgumentException("NotOp takes an array of exactly one Condition or Operator"); 203 | 204 | $content = $content[0]; 205 | } 206 | 207 | parent::__construct(array($content)); 208 | } 209 | 210 | public function getSql(array &$parameters) 211 | { 212 | $content = $this->getContent(); 213 | return 'NOT ('.$content[0]->getSql($parameters).')'; 214 | } 215 | } 216 | 217 | /** 218 | * Class, which implements SQLs "AND" operator 219 | * 220 | * @package mysql-query-builder 221 | */ 222 | class AndOp extends Operator 223 | { 224 | /** 225 | * Designated constructor. 226 | * Takes either single parameter — array of MQB_Conditions or several parameters-MQB_Conditions 227 | * 228 | * @param string|array $content,... 229 | */ 230 | public function __construct($content) 231 | { 232 | if (func_num_args() > 1) 233 | parent::__construct(func_get_args()); 234 | else 235 | parent::__construct($content); 236 | 237 | $this->startSql = "("; 238 | $this->implodeSql = " AND "; 239 | $this->endSql = ")"; 240 | } 241 | 242 | public function getSql(array &$parameters) 243 | { 244 | $content = $this->getContent(); 245 | 246 | // shortcut 247 | if (count($content) == 1) 248 | return $content[0]->getSql($parameters); 249 | 250 | return parent::getSql($parameters); 251 | } 252 | } 253 | 254 | /** 255 | * Class, which implements SQLs "OR" operator 256 | * 257 | * @package mysql-query-builder 258 | */ 259 | class OrOp extends Operator 260 | { 261 | /** 262 | * Designated constructor. 263 | * Takes either single parameter — array of MQB_Conditions or several parameters-MQB_Conditions 264 | * 265 | * @param string|array $content,... 266 | */ 267 | public function __construct($content) 268 | { 269 | if (func_num_args() > 1) 270 | parent::__construct(func_get_args()); 271 | else 272 | parent::__construct($content); 273 | 274 | $this->startSql = "("; 275 | $this->implodeSql = " OR "; 276 | $this->endSql = ")"; 277 | } 278 | 279 | public function getSql(array &$parameters) 280 | { 281 | $content = $this->getContent(); 282 | 283 | // shortcut 284 | if (count($content) == 1) 285 | return $content[0]->getSql($parameters); 286 | 287 | return parent::getSql($parameters); 288 | } 289 | } 290 | 291 | /** 292 | * Class, which implements SQLs "XOR()" operator 293 | * 294 | * @package mysql-query-builder 295 | */ 296 | class XorOp extends Operator 297 | { 298 | /** 299 | * Designated constructor. 300 | * Takes either single parameter — array of MQB_Conditions or several parameters-MQB_Conditions 301 | * 302 | * @param string|array $content,... 303 | */ 304 | public function __construct($content) 305 | { 306 | if (func_num_args() > 1) 307 | parent::__construct(func_get_args()); 308 | else 309 | parent::__construct($content); 310 | 311 | $this->startSql = "("; 312 | $this->implodeSql = " XOR "; 313 | $this->endSql = ")"; 314 | } 315 | 316 | public function getSql(array &$parameters) 317 | { 318 | $content = $this->getContent(); 319 | 320 | // shortcut 321 | if (count($content) == 1) 322 | return $content[0]->getSql($parameters); 323 | 324 | return parent::getSql($parameters); 325 | } 326 | } 327 | 328 | /** 329 | * Class, which implements generic Condition. Actually, almost any condition can be implemented using it 330 | * 331 | * @package mysql-query-builder 332 | */ 333 | class Condition implements MQB_Condition 334 | { 335 | private $content = array(); 336 | private $validConditions = array("=", "<>", "<", ">", ">=", "<=", "like", "is null", "find_in_set", "and", "or", "xor", "in"); 337 | private $validSingulars = array("is null"); 338 | 339 | /** 340 | * Designated constructor. 341 | * First parameter should be one of the allowed comparator-strings 342 | * Second parameter should be some MQB_Field-compliant object 343 | * Third parameter is the value, which is compared against second-parameter. It should be either scalar-value, or another MQB_Field 344 | * 345 | * @param string $comparison 346 | * @param MQB_Field $left 347 | * @param mixed $right 348 | * @throws RangeException, InvalidArgumentException 349 | */ 350 | public function __construct($comparison, MQB_Field $left, $right = null) 351 | { 352 | $comparison = strtolower($comparison); 353 | 354 | if (!in_array($comparison, $this->validConditions)) 355 | throw new RangeException('invalid comparator-function'); 356 | 357 | if ($comparison == 'in') { 358 | if (is_array($right)) { 359 | // IN (1,2,3,4) 360 | foreach ($right as $value) { 361 | if (!is_numeric($value)) { 362 | throw new InvalidArgumentException('Right-op has to be array consisting of NUMERIC VALUES, if comparison is "in"'); 363 | } 364 | } 365 | } else { 366 | // IN (SELECT …) 367 | if (!is_object($right) or !($right instanceof SelectQuery)) { 368 | throw new InvalidArgumentException('Right-op has to be object of class SelectQuery, if comparison is "in"'); 369 | } 370 | 371 | if ($right->countSelects() != 1) { 372 | throw new InvalidArgumentException('Right-op has to be query with one field, if comparison is "in"'); 373 | } 374 | } 375 | } elseif (!in_array($comparison, $this->validSingulars)) { 376 | if (is_scalar($right)) { 377 | $right = new Parameter($right); 378 | } elseif (null !== $right and !($right instanceof Parameter) and !($right instanceof MQB_Field)) { 379 | throw new InvalidArgumentException('Right-op has to be Parameter or MQB_Field. Got '.get_class($right).' instead'); 380 | } 381 | } 382 | 383 | $this->content = array($comparison, $left, $right); 384 | } 385 | 386 | public function getSql(array &$parameters) 387 | { 388 | $comparison = $this->content[0]; 389 | $leftpart = $this->content[1]->getSql($parameters); 390 | 391 | if ($comparison == 'is null' or ($comparison == '=' and null === $this->content[2])) { 392 | return $leftpart." IS NULL"; 393 | } elseif ($comparison == '<>' and null === $this->content[2]) { 394 | return $leftpart." IS NOT NULL"; 395 | } elseif ($comparison == 'in') { 396 | $right = $this->content[2]; 397 | 398 | if (is_array($right)) { 399 | return $leftpart." IN (".implode(', ', $right).")"; 400 | } elseif ($right instanceof SelectQuery) { 401 | return $leftpart.' IN ('.$right->sql($parameters).')'; 402 | } 403 | } else { 404 | $rightpart = $this->content[2]->getSql($parameters); 405 | 406 | if ($comparison == "find_in_set") 407 | return $comparison."(".$rightpart.",".$leftpart.")"; 408 | 409 | return $leftpart." ".$comparison." ".$rightpart; 410 | } 411 | } 412 | 413 | /** 414 | * accessor which returns comparator 415 | * 416 | * @return string 417 | */ 418 | public function getComparison() 419 | { 420 | return $this->content[0]; 421 | } 422 | 423 | /** 424 | * accessor which returns left-parameter of comparison 425 | * 426 | * @return MQB_Field 427 | */ 428 | public function getLeft() 429 | { 430 | return $this->content[1]; 431 | } 432 | 433 | /** 434 | * accessor which returns right-parameter of comparison 435 | * 436 | * @return mixed 437 | */ 438 | public function getRight() 439 | { 440 | return $this->content[2]; 441 | } 442 | } 443 | 444 | /** 445 | * Representation of the Field of N-th Table in Query 446 | * 447 | * @package mysql-query-builder 448 | */ 449 | class Field implements MQB_Field 450 | { 451 | private $name; 452 | private $table; 453 | private $alias; 454 | 455 | /** 456 | * Designated constructor. Used to create representation of 'tN.field as alias' construction, which can be referred from various parts of query 457 | * 458 | * @param string $name 459 | * @param integer $table 460 | * @param string $alias 461 | * @throws RangeException 462 | */ 463 | public function __construct($name, $table = 0, $alias = null) 464 | { 465 | if (!$name) 466 | throw new RangeException('Name of the field is not specified'); 467 | 468 | if (!is_string($name) or !is_numeric($table)) 469 | throw new InvalidArgumentException(); 470 | 471 | $this->table = $table; 472 | $this->name = $name; 473 | $this->alias = $alias; 474 | } 475 | 476 | public function getSql(array &$parameters, $full = false) 477 | { 478 | if (true === $full or null === $this->alias) { 479 | $res = '`t'.$this->table."`.`".$this->name.'`'; 480 | 481 | if (null !== $this->alias) { 482 | $res .= ' AS `'.$this->alias.'`'; 483 | } 484 | } else { 485 | $res = '`'.$this->alias.'`'; 486 | } 487 | 488 | return $res; 489 | } 490 | 491 | /** 492 | * accessor for internal "number of table in query" property 493 | * 494 | * @return integer 495 | */ 496 | public function getTable() 497 | { 498 | return $this->table; 499 | } 500 | 501 | /** 502 | * accessor for internal "name of the field" property 503 | * 504 | * @return string 505 | */ 506 | public function getName() 507 | { 508 | return $this->name; 509 | } 510 | 511 | /** 512 | * accessor for internal "number of alias" property. returns NULL, if alias is not set 513 | * 514 | * @return string|null 515 | */ 516 | public function getAlias() 517 | { 518 | if (null === $this->alias) 519 | return null; 520 | 521 | return '`'.$this->alias.'`'; 522 | } 523 | } 524 | 525 | /** 526 | * Class, which represents 'table.*' concept 527 | * 528 | * @package mysql-query-builder 529 | */ 530 | class AllFields 531 | { 532 | private $table; 533 | 534 | /** 535 | * Designated constructor. Takes "number of table in query" as the parameter 536 | * 537 | * @param integer $table 538 | */ 539 | public function __construct($table = 0) 540 | { 541 | $this->table = $table; 542 | } 543 | 544 | public function getSql(array &$parameters) 545 | { 546 | return '`t'.$this->table."`.*"; 547 | } 548 | 549 | /** 550 | * accessor for internal "number of table in query" property 551 | * 552 | * @return integer 553 | */ 554 | public function getTable() 555 | { 556 | return $this->table; 557 | } 558 | } 559 | 560 | /** 561 | * Class, which represents SQL-Functions used as columns in query 562 | * 563 | * @package mysql-query-builder 564 | */ 565 | class SqlFunction implements MQB_Field 566 | { 567 | private $name; 568 | private $values; 569 | private $alias; 570 | 571 | private $validNames = array('substring', 'year', 'month', 'day', 'date', 'length'); 572 | 573 | /** 574 | * Designated constructor, which generates representation of '$name($value1, $value2, ... $valueN) as $alias' sql-construct 575 | * $values can either be literal, MQB_Field or array of literals and MQB_Fields 576 | * 577 | * @param string $name 578 | * @param mixed $values 579 | * @param string $alias 580 | * @throws InvalidArgumentException 581 | */ 582 | public function __construct($name, $values, $alias = null) 583 | { 584 | if (!is_string($name) or !in_array($name, $this->validNames)) 585 | throw new InvalidArgumentException('Invalid sql-function: '.$name); 586 | 587 | if (!is_array($values)) 588 | $values = array($values); 589 | 590 | foreach ($values as $v) { 591 | if (is_object($v) and !($v instanceof MQB_Field)) 592 | throw new InvalidArgumentException("Something wrong passed as a parameter"); 593 | } 594 | 595 | $this->name = $name; 596 | $this->values = $values; 597 | $this->alias = $alias; 598 | } 599 | 600 | public function getSql(array &$parameters) 601 | { 602 | $result = strtoupper($this->name)."("; 603 | 604 | $first = true; 605 | foreach ($this->values as $v) { 606 | if ($first) { 607 | $first = false; 608 | } else { 609 | $result .= ', '; 610 | } 611 | 612 | if (is_object($v)) { 613 | $result .= $v->getSql($parameters); 614 | } else { 615 | $result .= $v; 616 | } 617 | } 618 | 619 | $result .= ')'; 620 | 621 | if (null !== $this->alias) { 622 | $result .= ' AS '.$this->getAlias(); 623 | } 624 | 625 | return $result; 626 | } 627 | 628 | /** 629 | * accessor for internal "number of alias" property. returns NULL, if alias is not set 630 | * 631 | * @return string|null 632 | */ 633 | public function getAlias() 634 | { 635 | if (null === $this->alias) 636 | return null; 637 | 638 | return '`'.$this->alias.'`'; 639 | } 640 | 641 | /** 642 | * accessor for internal "name of the function" property 643 | * 644 | * @return string 645 | */ 646 | public function getName() 647 | { 648 | return $this->name; 649 | } 650 | } 651 | 652 | /** 653 | * Class, which represents SQLs aggregate-functions used as columns in query 654 | * 655 | * @package mysql-query-builder 656 | */ 657 | class Aggregate implements MQB_Field 658 | { 659 | private $aggregate; 660 | private $distinct; 661 | private $name; 662 | private $table; 663 | private $alias; 664 | private $validAggregates = array("sum", "count", "min", "max", "avg"); 665 | private $field = null; 666 | 667 | /** 668 | * Creates representation of SQLs aggregate-function. 669 | * $field can be null, if $aggregate is 'count' — this would result in COUNT(*) query. 670 | * If $distinct is set to true, then something like the following will appear: COUNT(DISTINCT `foo`) AS `alias` 671 | * 672 | * @param string $aggregate 673 | * @param string $field 674 | * @param bool $distinct 675 | * @param string $alias 676 | * @throws RangeException, InvalidArgumentException 677 | */ 678 | public function __construct($aggregate, $field = null, $distinct=false, $alias=null) 679 | { 680 | $aggregate = strtolower($aggregate); 681 | 682 | if (!in_array($aggregate, $this->validAggregates)) 683 | throw new RangeException('Invalid aggregate function: '.$aggregate); 684 | 685 | if (($field instanceof MQB_Field) or (null === $field and $aggregate == 'count')) { 686 | $this->aggregate = $aggregate; 687 | $this->distinct = ($distinct === true); 688 | $this->alias = $alias; 689 | $this->field = $field; 690 | } else { 691 | throw new InvalidArgumentException('field should be MQB_Field'); 692 | } 693 | 694 | } 695 | 696 | public function getSql(array &$parameters, $full = false) 697 | { 698 | if (true === $full or null === $this->alias) { 699 | if (null === $this->field) { 700 | $field_sql = '*'; 701 | } else { 702 | $field_sql = $this->field->getSql($parameters); 703 | } 704 | 705 | if ($this->distinct) { 706 | $field_sql = 'DISTINCT '.$field_sql; 707 | } 708 | 709 | $field_sql = strtoupper($this->aggregate).'('.$field_sql.')'; 710 | 711 | if (null !== $this->alias) { 712 | $field_sql .= ' AS `'.$this->alias.'`'; 713 | } 714 | } else { 715 | $field_sql = '`'.$this->alias.'`'; 716 | } 717 | 718 | return $field_sql; 719 | } 720 | 721 | /** 722 | * accessor for internal "number of alias" property. returns NULL, if alias is not set 723 | * 724 | * @return string|null 725 | */ 726 | public function getAlias() 727 | { 728 | if (null === $this->alias) 729 | return null; 730 | 731 | return '`'.$this->alias.'`'; 732 | } 733 | } 734 | 735 | /** 736 | * Class, which represents literal parameter in SQL-queries. 737 | * value is never directly used in query, instead, it is put array, which is later used for executing prepared SQL-statement 738 | * 739 | * @package mysql-query-builder 740 | */ 741 | class Parameter 742 | { 743 | private $content; 744 | 745 | /** 746 | * Creates representation of literal-value, passed as the single parameter 747 | * 748 | * @param string|integer|bool|null $content 749 | */ 750 | public function __construct($content) 751 | { 752 | $this->content = $content; 753 | } 754 | 755 | public function getSql(array &$parameters) 756 | { 757 | $number = count($parameters) + 1; 758 | 759 | $parameters[":p".$number] = $this->content; 760 | 761 | return ":p".$number; 762 | } 763 | 764 | /** 765 | * accessor, which returns value of parameter 766 | * 767 | * @return mixed 768 | */ 769 | public function getParameters() 770 | { 771 | return $this->content; 772 | } 773 | } 774 | -------------------------------------------------------------------------------- /autoload.php: -------------------------------------------------------------------------------- 1 | 10 | Copyright © 2005-2006 Konstantin Sedov 11 | 12 | This library is free software; you can redistribute it and/or 13 | modify it under the terms of the GNU Lesser General Public 14 | License as published by the Free Software Foundation; either 15 | version 2.1 of the License, or (at your option) any later version. 16 | 17 | This library is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 | Lesser General Public License for more details. 21 | 22 | You should have received a copy of the GNU Lesser General Public 23 | License along with this library; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 25 | */ 26 | 27 | function mqb_autoload($class_name) 28 | { 29 | static $prefix = null; 30 | static $classes = null; 31 | 32 | if (null === $prefix) { 33 | $prefix = dirname(__FILE__).DIRECTORY_SEPARATOR; 34 | } 35 | 36 | if (null === $classes) { 37 | $classes = array( 38 | 'BasicQuery' => 'BasicQuery.php', 39 | 'DeleteQuery' => 'DeleteQuery.php', 40 | 'InsertQuery' => 'InsertQuery.php', 41 | 'SelectQuery' => 'SelectQuery.php', 42 | 'UpdateQuery' => 'UpdateQuery.php', 43 | 'QBTable' => 'additional.php', 44 | 'Operator' => 'additional.php', 45 | 'NotOp' => 'additional.php', 46 | 'AndOp' => 'additional.php', 47 | 'OrOp' => 'additional.php', 48 | 'XorOp' => 'additional.php', 49 | 'Condition' => 'additional.php', 50 | 'AllFields' => 'additional.php', 51 | 'Field' => 'additional.php', 52 | 'sqlFunction' => 'additional.php', 53 | 'Aggregate' => 'additional.php', 54 | 'Parameter' => 'additional.php' 55 | ); 56 | } 57 | 58 | if (isset($classes[$class_name])) 59 | require $prefix.$classes[$class_name]; 60 | } 61 | 62 | spl_autoload_register('mqb_autoload'); -------------------------------------------------------------------------------- /tests/AllTests.php: -------------------------------------------------------------------------------- 1 | addTestSuite('SelectQueryTest'); 27 | $suite->addTestSuite('UpdateQueryTest'); 28 | $suite->addTestSuite('DeleteQueryTest'); 29 | $suite->addTestSuite('InsertQueryTest'); 30 | $suite->addTestSuite('OtherTest'); 31 | 32 | return $suite; 33 | } 34 | } 35 | 36 | if (PHPUnit_MAIN_METHOD == 'AllTests::main') { 37 | AllTests::main(); 38 | } 39 | -------------------------------------------------------------------------------- /tests/DeleteQueryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('DELETE FROM `test`', $q->sql()); // aliases are NOT supported in one-table delete queries 13 | } 14 | 15 | public function testOneOfMultiple() 16 | { 17 | $q = new DeleteQuery(array('test', 'test2', 'test3')); 18 | 19 | $this->assertEquals('DELETE FROM `t0` USING `test` AS `t0`, `test2` AS `t1`, `test3` AS `t2`', $q->sql()); 20 | } 21 | 22 | public function testSeveralOfMultiple() 23 | { 24 | $q = new DeleteQuery(array('test', 'test2', 'test3'), array(0, 2)); 25 | 26 | $this->assertEquals('DELETE FROM `t0`, `t2` USING `test` AS `t0`, `test2` AS `t1`, `test3` AS `t2`', $q->sql()); 27 | } 28 | 29 | public function testWhere() 30 | { 31 | $q = new DeleteQuery(array('test')); 32 | $q->setWhere(new AndOp(array( 33 | new Condition('=', new Field('group'), 'test'), 34 | new Condition('=', new Field('author'), null) 35 | ))); 36 | 37 | $this->assertEquals('DELETE FROM `test` WHERE (`test`.`group` = :p1 AND `test`.`author` IS NULL)', $q->sql()); 38 | } 39 | 40 | public function testOrderLimit() 41 | { 42 | $q = new DeleteQuery(array('test')); 43 | $q->setLimit(10); 44 | $q->setOrderBy(array(new Field('field1'))); 45 | 46 | $this->assertEquals('DELETE FROM `test` ORDER BY `test`.`field1` ASC LIMIT 10', $q->sql()); 47 | } 48 | 49 | public function testOrderLimitOnMultiple() 50 | { 51 | try { 52 | $q = new DeleteQuery(array('test', 'test2', 'test3')); 53 | $q->setLimit(10); 54 | $this->fail('LIMIT should not be allowed on multi-table queries'); 55 | } catch (LogicException $e) { 56 | } 57 | 58 | try { 59 | $q = new DeleteQuery(array('test', 'test2', 'test3')); 60 | $q->setOrderBy(array(new Field('field1'))); 61 | $this->fail('ORDER BY should not be allowed on multi-table queries'); 62 | } catch (LogicException $e) { 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/InsertQueryTest.php: -------------------------------------------------------------------------------- 1 | setValues(array( 12 | 'field1' => 'value1', 13 | 'field2' => 'value2' 14 | )); 15 | 16 | $this->assertEquals('INSERT INTO `test` (`field1`, `field2`) VALUES (:p1, :p2)', $q->sql()); 17 | $params = $q->parameters(); 18 | 19 | $this->assertEquals('value1', $params[':p1']); 20 | $this->assertEquals('value2', $params[':p2']); 21 | 22 | // shorthand 23 | $q = new InsertQuery('test'); 24 | $q->field1 = 'value1'; 25 | $q->field2 = 'value2'; 26 | 27 | $this->assertEquals('INSERT INTO `test` (`field1`, `field2`) VALUES (:p1, :p2)', $q->sql()); 28 | $params = $q->parameters(); 29 | 30 | $this->assertEquals('value1', $params[':p1']); 31 | $this->assertEquals('value2', $params[':p2']); 32 | } 33 | 34 | public function testMultitable() 35 | { 36 | try { 37 | $q = new InsertQuery(array('test', 'test2')); 38 | $this->assertEquals(true, false); 39 | } catch (InvalidArgumentException $e) { 40 | } 41 | } 42 | 43 | public function testOnDuplicate() 44 | { 45 | $q = new InsertQuery(array('test'), true); 46 | $q->setValues(array( 47 | 'id' => '35', 48 | 'field1' => 'value1', 49 | 'field2' => 'value2' 50 | )); 51 | 52 | $this->assertEquals('INSERT INTO `test` (`id`, `field1`, `field2`) VALUES (:p1, :p2, :p3) ON DUPLICATE KEY UPDATE `id` = LAST_INSERT_ID(`id`), `field1` = VALUES(`field1`), `field2` = VALUES(`field2`)', $q->sql()); 53 | $params = $q->parameters(); 54 | 55 | $this->assertEquals('35', $params[':p1']); 56 | $this->assertEquals('value1', $params[':p2']); 57 | $this->assertEquals('value2', $params[':p3']); 58 | $this->assertEquals(3, count($params)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/OtherTest.php: -------------------------------------------------------------------------------- 1 | fail("invalid input accepted"); 13 | } catch (InvalidArgumentException $e) { 14 | } 15 | 16 | try { 17 | $op = new NotOp(array(new Field('qq'))); 18 | $this->fail("invalid input accepted"); 19 | } catch (InvalidArgumentException $e) { 20 | } 21 | 22 | $params = array(); 23 | $op = new NotOp(array(new Condition('=', new Field('test'), '1'))); 24 | $this->assertEquals('NOT (`t0`.`test` = :p1)', $op->getSql($params)); 25 | 26 | 27 | $params = array(); 28 | $op = new XorOp(array(new Condition('=', new Field('test'), '1'), new Condition('=', new Field('test'), '2'))); 29 | $this->assertEquals('(`t0`.`test` = :p1 XOR `t0`.`test` = :p2)', $op->getSql($params)); 30 | 31 | // testing shortcuts 32 | $params = array(); 33 | $op = new AndOp(array(new Condition('=', new Field('foo'), 'bar'))); 34 | $this->assertEquals('`t0`.`foo` = :p1', $op->getSql($params)); 35 | 36 | $params = array(); 37 | $op = new OrOp(array(new Condition('=', new Field('foo'), 'bar'))); 38 | $this->assertEquals('`t0`.`foo` = :p1', $op->getSql($params)); 39 | 40 | $params = array(); 41 | $op = new XorOp(array(new Condition('=', new Field('foo'), 'bar'))); 42 | $this->assertEquals('`t0`.`foo` = :p1', $op->getSql($params)); 43 | } 44 | 45 | public function testCondition() 46 | { 47 | $params = array(); 48 | 49 | $f = new Field('test'); 50 | $arr = array(1,2,3,4,5); 51 | $c = new Condition('in', $f, $arr); 52 | 53 | $this->assertEquals('`t0`.`test` IN (1, 2, 3, 4, 5)', $c->getSql($params)); 54 | $this->assertEquals('in', $c->getComparison()); 55 | $this->assertEquals(var_export($f, true), var_export($c->getLeft(), true)); 56 | $this->assertEquals(var_export($arr, true), var_export($c->getRight(), true)); 57 | 58 | 59 | try { 60 | $c = new Condition('in', new Field('test'), 'error'); 61 | $this->fail('second parameter shoud be array-only, but string passed'); 62 | } catch (InvalidArgumentException $e) { 63 | } 64 | 65 | try { 66 | $c = new Condition('in', new Field('test'), array('a')); 67 | $this->fail('second parameter shoud be array of integets, but array of strings passed'); 68 | } catch (InvalidArgumentException $e) { 69 | } 70 | } 71 | 72 | public function testField() 73 | { 74 | $f = new Field('test', 1); 75 | $this->assertEquals('test', $f->getName()); 76 | $this->assertEquals(1, $f->getTable()); 77 | 78 | $f = new AllFields(); 79 | $this->assertEquals(0, $f->getTable()); 80 | 81 | $f = new AllFields(2); 82 | $this->assertEquals(2, $f->getTable()); 83 | 84 | try { 85 | $f = new Field(array('1')); 86 | $this->fail('first parameter should be string (array is not allowed)'); 87 | } catch (InvalidArgumentException $e) { 88 | } 89 | 90 | try { 91 | $f = new Field('foo', 'bar'); 92 | $this->fail('second parameter has to be numeric'); 93 | } catch (InvalidArgumentException $e) { 94 | } 95 | } 96 | 97 | public function testSqlFunctions() 98 | { 99 | $f = new SqlFunction('substring', array(new Field('name'), 5, 2)); 100 | 101 | $params = array(); 102 | $this->assertEquals('SUBSTRING(`t0`.`name`, 5, 2)', $f->getSql($params)); 103 | $this->assertEquals('substring', $f->getName()); 104 | 105 | try { 106 | $f = new SqlFunction('there_is_no_such_function', new Field('name')); 107 | $this->fail(); 108 | } catch (InvalidArgumentException $e) { 109 | } 110 | 111 | try { 112 | $f = new SqlFunction('substring', new AndOp(array())); 113 | $this->fail('wrong value accepted'); 114 | } catch (InvalidArgumentException $e) { 115 | } 116 | 117 | $q = new SelectQuery('test'); 118 | $q->setWhere(new Condition('>', new SqlFunction('length', new Field('foo')), 5)); 119 | 120 | $params = array(); 121 | $this->assertEquals('SELECT `t0`.* FROM `test` AS `t0` WHERE LENGTH(`t0`.`foo`) > :p1', $q->sql()); 122 | $parameters = $q->parameters(); 123 | $this->assertEquals('5', $parameters[':p1']); 124 | } 125 | 126 | public function testParameter() 127 | { 128 | $p = new Parameter(123); 129 | $this->assertEquals(123, $p->getParameters()); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /tests/SelectQueryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('SELECT `t0`.* FROM `test` AS `t0`', $q->sql()); 13 | $this->assertEquals(0, count($q->parameters())); 14 | 15 | 16 | $q = new SelectQuery(array('test')); 17 | $q->setSelect(array(new AllFields()), true); 18 | 19 | $this->assertEquals('SELECT DISTINCT `t0`.* FROM `test` AS `t0`', $q->sql()); 20 | $this->assertEquals(0, count($q->parameters())); 21 | 22 | 23 | $q = new SelectQuery(array(new QBTable('test'))); 24 | 25 | $this->assertEquals('SELECT `t0`.* FROM `test` AS `t0`', $q->sql()); 26 | $this->assertEquals(0, count($q->parameters())); 27 | } 28 | 29 | public function testSelectSomeFromOneTable() 30 | { 31 | $q = new SelectQuery(array('test')); 32 | $q->setWhere(new Condition('=', new Field('somefield'), 35)); 33 | $q->setLimit(10, 2); 34 | 35 | $this->assertEquals('SELECT `t0`.* FROM `test` AS `t0` WHERE `t0`.`somefield` = :p1 LIMIT 10 OFFSET 2', $q->sql()); 36 | 37 | $params = $q->parameters(); 38 | $this->assertEquals(35, $params[':p1']); 39 | } 40 | 41 | public function testNestedConditions() 42 | { 43 | $q = new SelectQuery(array('test')); 44 | $q->setWhere(new AndOp(array( 45 | new Condition('>', new Field('id'), 12), 46 | new OrOp(array( 47 | new Condition('=', new Field('status'), 'demolished'), 48 | new NotOp( 49 | new Condition('<', new Field('age'), 5) 50 | ) 51 | )) 52 | ))); 53 | 54 | $this->assertEquals('SELECT `t0`.* FROM `test` AS `t0` WHERE (`t0`.`id` > :p1 AND (`t0`.`status` = :p2 OR NOT (`t0`.`age` < :p3)))', $q->sql()); 55 | } 56 | 57 | public function testNotOp() 58 | { 59 | try { 60 | new NotOp(array( 61 | new Condition('=', new Field('test'), 1), 62 | new Condition('=', new Field('test'), 2), 63 | )); 64 | fail(); // exception should happen 65 | } catch (InvalidArgumentException $e) { 66 | } 67 | } 68 | 69 | public function testInCondition() 70 | { 71 | $q = new SelectQuery(array('test')); 72 | $q->setWhere(new Condition('in', new Field('id'), array(1, 3, 5))); 73 | 74 | $this->assertEquals('SELECT `t0`.* FROM `test` AS `t0` WHERE `t0`.`id` IN (1, 3, 5)', $q->sql()); 75 | } 76 | 77 | public function testSelectSpecificFields() 78 | { 79 | $q = new SelectQuery(array('test', 'test2')); 80 | $q->setSelect(array(new AllFields(), new Field('id', 1))); 81 | 82 | $this->assertEquals('SELECT `t0`.*, `t1`.`id` FROM `test` AS `t0`, `test2` AS `t1`', $q->sql()); 83 | } 84 | 85 | public function testAlias() 86 | { 87 | $field1 = new Field('id', 0, 'test'); 88 | 89 | $q = new SelectQuery(array('test', 'test2')); 90 | $q->setSelect(array($field1, new AllFields(1))); 91 | $q->setWhere(new Condition('=', $field1, '2')); 92 | 93 | $this->assertEquals('SELECT `t0`.`id` AS `test`, `t1`.* FROM `test` AS `t0`, `test2` AS `t1` WHERE `test` = :p1', $q->sql()); 94 | } 95 | 96 | public function testWhereNull() 97 | { 98 | $q = new SelectQuery(array('test')); 99 | $q->setWhere(new AndOp(array( 100 | new Condition('=', new Field('a'), null), 101 | new Condition('<>', new Field('b'), null), 102 | ))); 103 | 104 | $this->assertEquals('SELECT `t0`.* FROM `test` AS `t0` WHERE (`t0`.`a` IS NULL AND `t0`.`b` IS NOT NULL)', $q->sql()); 105 | 106 | $q->setWhere(); 107 | $this->assertEquals('SELECT `t0`.* FROM `test` AS `t0`', $q->sql()); 108 | } 109 | 110 | public function testSelectWrongs() 111 | { 112 | try { 113 | $q = new SelectQuery('test'); 114 | $q->setSelect(array()); 115 | $this->fail("noone is allowed to select nothing!"); 116 | } catch (InvalidArgumentException $e) { 117 | } 118 | 119 | try { 120 | $q = new SelectQuery('test'); 121 | $q->setSelect(array('field1'), 'test'); 122 | $this->fail("second params should be boolean!"); 123 | } catch (InvalidArgumentException $e) { 124 | } 125 | 126 | try { 127 | $q = new SelectQuery(array(123)); 128 | $this->fail("tables param should be either string or QBTable!"); 129 | } catch (LogicException $e) { 130 | } 131 | } 132 | 133 | public function testAggregate() 134 | { 135 | $q = new SelectQuery('test'); 136 | $q->setSelect(new Aggregate('count')); 137 | 138 | $this->assertEquals('SELECT COUNT(*) FROM `test` AS `t0`', $q->sql()); 139 | 140 | // This should throw exception, as count accept only '*' not 't0.*' 141 | try { 142 | new Aggregate('count', new AllFields()); 143 | fail(); 144 | } catch (InvalidArgumentException $e) { 145 | } 146 | } 147 | 148 | public function testGroupBy() 149 | { 150 | $group_by = array(new Field('year')); 151 | 152 | $q = new SelectQuery(array('test')); 153 | $q->setGroupby($group_by); 154 | 155 | $this->assertEquals('SELECT `t0`.* FROM `test` AS `t0` GROUP BY `t0`.`year`', $q->sql()); 156 | 157 | $q->setHaving(new Condition('>', new Aggregate('count', new Field('commit')), 20)); 158 | $this->assertEquals('SELECT `t0`.* FROM `test` AS `t0` GROUP BY `t0`.`year` HAVING COUNT(`t0`.`commit`) > :p1', $q->sql()); 159 | 160 | $q->setHaving(); 161 | $this->assertEquals('SELECT `t0`.* FROM `test` AS `t0` GROUP BY `t0`.`year`', $q->sql()); 162 | 163 | $this->assertEquals(var_export($group_by, true), var_export($q->showGroupBy(), true)); // check required by BebopCMS(tm) 164 | } 165 | 166 | public function testGroupByAggregate() 167 | { 168 | $group_by = new Aggregate('count', new Field('user'), true, 'c'); 169 | $field = new Field('very_long_identifier', 0, 'url'); 170 | 171 | $q = new SelectQuery('test'); 172 | $q->setSelect(array($group_by, $field)); 173 | $q->setGroupby(array($group_by)); 174 | $q->setOrderBy(array($field)); 175 | 176 | $this->assertEquals('SELECT COUNT(DISTINCT `t0`.`user`) AS `c`, `t0`.`very_long_identifier` AS `url` FROM `test` AS `t0` GROUP BY `c` ORDER BY `url` ASC', $q->sql()); 177 | } 178 | 179 | public function testOrderByFunction() 180 | { 181 | $f = new SqlFunction('year', new Field('stamp'), 'year'); 182 | 183 | $q = new SelectQuery('test'); 184 | $q->setSelect(array($f, new Field('profit'))); 185 | $q->setOrderBy(array($f)); 186 | 187 | $this->assertEquals('SELECT YEAR(`t0`.`stamp`) AS `year`, `t0`.`profit` FROM `test` AS `t0` ORDER BY `year` ASC', $q->sql()); 188 | } 189 | 190 | public function testOrderBy() 191 | { 192 | $q = new SelectQuery('test'); 193 | $q->setOrderBy(array(new Field('id')), array(true)); 194 | 195 | $this->assertEquals('SELECT `t0`.* FROM `test` AS `t0` ORDER BY `t0`.`id` DESC', $q->sql()); 196 | } 197 | 198 | public function testInfo() 199 | { 200 | $tbls = array('test1', 'test2', 'test3'); 201 | $q = new SelectQuery($tbls); 202 | 203 | $this->assertEquals(var_export($tbls, true), var_export($q->showTables(), true)); 204 | 205 | $condition = new Condition('=', new Field('id'), 1); 206 | $q->setWhere($condition); 207 | 208 | $this->assertEquals(var_export($condition, true), var_export($q->showConditions(), true)); 209 | } 210 | 211 | public function testMultiSchema() 212 | { 213 | $q = new SelectQuery(new QBTable('test', 'db2')); 214 | 215 | $this->assertEquals('SELECT `t0`.* FROM `db2`.`test` AS `t0`', $q->sql()); 216 | } 217 | 218 | public function testUseIndex() 219 | { 220 | $q = new SelectQuery('test'); 221 | $q->setIndices(array('abc', 'def')); 222 | 223 | $this->assertEquals('SELECT `t0`.* FROM `test` AS `t0` USE INDEX (`abc`, `def`)', $q->sql()); 224 | } 225 | 226 | public function testSelectWithSubquery() 227 | { 228 | $field1 = new Field('id', 0); 229 | $subq = new SelectQuery(array('sub_test')); 230 | $subq->setSelect(array($field1)); 231 | 232 | $q = new SelectQuery(array('test')); 233 | $q->setWhere(new Condition('in', new Field('id'), $subq)); 234 | 235 | $this->assertEquals('SELECT `t0`.* FROM `test` AS `t0` WHERE `t0`.`id` IN (SELECT `t0`.`id` FROM `sub_test` AS `t0`)', $q->sql()); 236 | } 237 | 238 | /** 239 | * testing nested subquery (query, which uses subquery, which uses subquery) and checking, that conditions are correct (for use in prepared queries) 240 | */ 241 | public function testSubqueryWithConditions() 242 | { 243 | $field1 = new Field('id', 0); 244 | 245 | $subq2 = new SelectQuery(array('bans')); 246 | $subq2->setSelect(array($field1)); 247 | $subq2->setWhere(new Condition('=', new Field('abc'), 10)); 248 | 249 | $subq = new SelectQuery(array('users')); 250 | $subq->setSelect(array($field1)); 251 | $subq->setWhere(new AndOp( 252 | new Condition('>', new Field('age'), 14), 253 | new Condition('in', new Field('id'), $subq2), 254 | new Condition('=', new Field('regdt'), '2009-01-01') 255 | )); 256 | 257 | $q = new SelectQuery(array('posts')); 258 | $q->setWhere(new AndOp( 259 | new Condition('>', new Field('comment'), 1), 260 | new Condition('in', new Field('user_id'), $subq), 261 | new Condition('=', new Field('funny'), 1) 262 | )); 263 | 264 | $this->assertEquals('SELECT `t0`.* FROM `posts` AS `t0` WHERE (`t0`.`comment` > :p1 AND `t0`.`user_id` IN (SELECT `t0`.`id` FROM `users` AS `t0` WHERE (`t0`.`age` > :p2 AND `t0`.`id` IN (SELECT `t0`.`id` FROM `bans` AS `t0` WHERE `t0`.`abc` = :p3) AND `t0`.`regdt` = :p4)) AND `t0`.`funny` = :p5)', $q->sql()); 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /tests/UpdateQueryTest.php: -------------------------------------------------------------------------------- 1 | sql(); 13 | $this->fail("allowed to generate empty query"); 14 | } catch (LogicException $e) { 15 | } 16 | } 17 | 18 | public function testFullUpdate() 19 | { 20 | $q = new UpdateQuery(array('test')); 21 | $q->setValues(array( 22 | 'qwe' => 'qweqwe' 23 | )); 24 | 25 | $this->assertEquals('UPDATE `test` AS `t0` SET `t0`.`qwe` = :p1', $q->sql()); 26 | 27 | $params = $q->parameters(); 28 | $this->assertEquals('qweqwe', $params[':p1']); 29 | 30 | // shortcut 31 | $q = new UpdateQuery('test'); 32 | $q->qwe = 'qweqwe'; 33 | 34 | $this->assertEquals('UPDATE `test` AS `t0` SET `t0`.`qwe` = :p1', $q->sql()); 35 | 36 | $params = $q->parameters(); 37 | $this->assertEquals('qweqwe', $params[':p1']); 38 | } 39 | 40 | public function testConditionalUpdate() 41 | { 42 | $q = new UpdateQuery(array('test')); 43 | $q->setValues(array( 44 | 'qwe' => 'qweqwe' 45 | )); 46 | $q->setWhere(new Condition('=', new Field('a'), 'b')); 47 | 48 | $this->assertEquals('UPDATE `test` AS `t0` SET `t0`.`qwe` = :p1 WHERE `t0`.`a` = :p2', $q->sql()); 49 | 50 | $params = $q->parameters(); 51 | $this->assertEquals('qweqwe', $params[':p1']); 52 | $this->assertEquals('b', $params[':p2']); 53 | } 54 | 55 | public function testMultitableUpdate() 56 | { 57 | $q = new UpdateQuery(array('test', 'test2')); 58 | $q->setValues(array( 59 | array(new Field('field1'), 'value1'), 60 | array(new Field('field2', 1), 'value2') 61 | )); 62 | 63 | $this->assertEquals('UPDATE `test` AS `t0`, `test2` AS `t1` SET `t0`.`field1` = :p1, `t1`.`field2` = :p2', $q->sql()); 64 | 65 | $params = $q->parameters(); 66 | $this->assertEquals('value1', $params[':p1']); 67 | $this->assertEquals('value2', $params[':p2']); 68 | } 69 | 70 | public function testConditionalMultitableUpdate() 71 | { 72 | $q = new UpdateQuery(array('test', 'test2')); 73 | $q->setValues(array( 74 | array(new Field('field1'), 'value1'), 75 | array(new Field('field2', 1), 'value2') 76 | )); 77 | $q->setWhere(new Condition('<', new Field('date', 1), '2004-10-11')); 78 | 79 | $this->assertEquals('UPDATE `test` AS `t0`, `test2` AS `t1` SET `t0`.`field1` = :p1, `t1`.`field2` = :p2 WHERE `t1`.`date` < :p3', $q->sql()); 80 | 81 | $params = $q->parameters(); 82 | $this->assertEquals('value1', $params[':p1']); 83 | $this->assertEquals('value2', $params[':p2']); 84 | $this->assertEquals('2004-10-11', $params[':p3']); 85 | } 86 | 87 | public function testLimit() 88 | { 89 | $q = new UpdateQuery('test'); 90 | $q->setValues(array( 91 | 'qwe' => 'qweqwe' 92 | )); 93 | $q->setLimit(10); 94 | 95 | $this->assertEquals('UPDATE `test` AS `t0` SET `t0`.`qwe` = :p1 LIMIT 10', $q->sql()); 96 | 97 | $params = $q->parameters(); 98 | $this->assertEquals('qweqwe', $params[':p1']); 99 | } 100 | 101 | public function testOrderBy() 102 | { 103 | $q = new UpdateQuery(array('test')); 104 | $q->setValues(array( 105 | 'qwe' => 'qweqwe' 106 | )); 107 | $q->setLimit(10); 108 | $q->setOrderBy(array(new Field('date'))); 109 | 110 | $this->assertEquals('UPDATE `test` AS `t0` SET `t0`.`qwe` = :p1 ORDER BY `t0`.`date` ASC LIMIT 10', $q->sql()); 111 | 112 | $params = $q->parameters(); 113 | $this->assertEquals('qweqwe', $params[':p1']); 114 | } 115 | 116 | public function testOrderLimitOnMultiple() 117 | { 118 | try { 119 | $q = new UpdateQuery(array('test', 'test2', 'test3')); 120 | $q->setLimit(10); 121 | $this->fail('LIMIT should not be allowed on multi-table queries'); 122 | } catch (LogicException $e) { 123 | } 124 | 125 | try { 126 | $q = new UpdateQuery(array('test', 'test2', 'test3')); 127 | $q->setOrderBy(array(new Field('field1'))); 128 | $this->fail('ORDER BY should not be allowed on multi-table queries'); 129 | } catch (LogicException $e) { 130 | } 131 | } 132 | 133 | public function testResettingOfSETClause() 134 | { 135 | $q = new UpdateQuery('test'); 136 | $q->setValues(array('foo' => 'bar')); 137 | 138 | $sql = $q->sql(); 139 | $this->assertEquals('UPDATE `test` AS `t0` SET `t0`.`foo` = :p1', $sql); 140 | 141 | $q->setValues(array()); 142 | $q->baz = 'bar'; 143 | 144 | $sql = $q->sql(); 145 | $this->assertEquals('UPDATE `test` AS `t0` SET `t0`.`baz` = :p1', $sql); 146 | } 147 | 148 | public function testAlternateSetSyntax() 149 | { 150 | $q = new UpdateQuery('test'); 151 | $q->setValues(array(array('foo', 'bar'), array('baz', 'bar'))); 152 | 153 | $sql = $q->sql(); 154 | 155 | $this->assertEquals('UPDATE `test` AS `t0` SET `t0`.`foo` = :p1, `t0`.`baz` = :p2', $sql); 156 | } 157 | } --------------------------------------------------------------------------------