├── .gitignore ├── .travis.yml ├── README.md ├── classes ├── Config │ ├── Database.php │ └── Database │ │ ├── Reader.php │ │ └── Writer.php ├── DB.php ├── Database.php ├── Database │ ├── Exception.php │ ├── Expression.php │ ├── MySQL.php │ ├── MySQL │ │ └── Result.php │ ├── MySQLi.php │ ├── MySQLi │ │ └── Result.php │ ├── PDO.php │ ├── Query.php │ ├── Query │ │ ├── Builder.php │ │ └── Builder │ │ │ ├── Delete.php │ │ │ ├── Insert.php │ │ │ ├── Join.php │ │ │ ├── Select.php │ │ │ ├── Update.php │ │ │ └── Where.php │ ├── Result.php │ └── Result │ │ └── Cached.php ├── Kohana │ ├── Config │ │ ├── Database.php │ │ └── Database │ │ │ ├── Reader.php │ │ │ └── Writer.php │ ├── DB.php │ ├── Database.php │ ├── Database │ │ ├── Exception.php │ │ ├── Expression.php │ │ ├── MySQL.php │ │ ├── MySQL │ │ │ └── Result.php │ │ ├── MySQLi.php │ │ ├── MySQLi │ │ │ └── Result.php │ │ ├── PDO.php │ │ ├── Query.php │ │ ├── Query │ │ │ ├── Builder.php │ │ │ └── Builder │ │ │ │ ├── Delete.php │ │ │ │ ├── Insert.php │ │ │ │ ├── Join.php │ │ │ │ ├── Select.php │ │ │ │ ├── Update.php │ │ │ │ └── Where.php │ │ ├── Result.php │ │ └── Result │ │ │ └── Cached.php │ ├── Model │ │ └── Database.php │ └── Session │ │ └── Database.php ├── Model │ └── Database.php └── Session │ └── Database.php ├── composer.json ├── config ├── database.php ├── session.php └── userguide.php ├── guide └── database │ ├── config.md │ ├── examples.md │ ├── index.md │ ├── menu.md │ ├── query.md │ ├── query │ ├── builder.md │ └── parameterized.md │ └── results.md └── koharness.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor/* 3 | koharness_bootstrap.php 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: php 4 | 5 | # Only build the main develop/master branches - feature branches will be covered by PRs 6 | branches: 7 | only: 8 | - /^[0-9\.]+\/(develop|master)$/ 9 | 10 | cache: 11 | directories: 12 | - $HOME/.composer/cache/files 13 | 14 | php: 15 | - 5.3 16 | - 5.4 17 | - 5.5 18 | - 5.6 19 | - 7.0 20 | - hhvm 21 | 22 | matrix: 23 | include: 24 | - php: 5.3 25 | env: 'COMPOSER_PHPUNIT="lowest"' 26 | 27 | before_script: 28 | - composer self-update 29 | - composer install --prefer-dist --no-interaction 30 | - if [ "$COMPOSER_PHPUNIT" = "lowest" ]; then composer update --prefer-lowest --with-dependencies --prefer-dist --no-interaction phpunit/phpunit; fi; 31 | - vendor/bin/koharness 32 | 33 | script: 34 | - cd /tmp/koharness && ./vendor/bin/phpunit --bootstrap=modules/unittest/bootstrap.php modules/unittest/tests.php 35 | 36 | notifications: 37 | email: false 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kohana - database access module 2 | 3 | | ver | Stable | Develop | 4 | |-------|--------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------| 5 | | 3.3.x | [![Build Status - 3.3/master](https://travis-ci.org/kohana/database.svg?branch=3.3%2Fmaster)](https://travis-ci.org/kohana/database) | [![Build Status - 3.3/develop](https://travis-ci.org/kohana/database.svg?branch=3.3%2Fdevelop)](https://travis-ci.org/kohana/database) | 6 | | 3.4.x | [![Build Status - 3.4/master](https://travis-ci.org/kohana/database.svg?branch=3.4%2Fmaster)](https://travis-ci.org/kohana/database) | [![Build Status - 3.4/develop](https://travis-ci.org/kohana/database.svg?branch=3.4%2Fdevelop)](https://travis-ci.org/kohana/database) | 7 | -------------------------------------------------------------------------------- /classes/Config/Database.php: -------------------------------------------------------------------------------- 1 | _db_instance = $config['instance']; 28 | } 29 | elseif ($this->_db_instance === NULL) 30 | { 31 | $this->_db_instance = Database::$default; 32 | } 33 | 34 | if (isset($config['table_name'])) 35 | { 36 | $this->_table_name = $config['table_name']; 37 | } 38 | } 39 | 40 | /** 41 | * Tries to load the specificed configuration group 42 | * 43 | * Returns FALSE if group does not exist or an array if it does 44 | * 45 | * @param string $group Configuration group 46 | * @return boolean|array 47 | */ 48 | public function load($group) 49 | { 50 | /** 51 | * Prevents the catch-22 scenario where the database config reader attempts to load the 52 | * database connections details from the database. 53 | * 54 | * @link http://dev.kohanaframework.org/issues/4316 55 | */ 56 | if ($group === 'database') 57 | return FALSE; 58 | 59 | $query = DB::select('config_key', 'config_value') 60 | ->from($this->_table_name) 61 | ->where('group_name', '=', $group) 62 | ->execute($this->_db_instance); 63 | 64 | return count($query) ? array_map('unserialize', $query->as_array('config_key', 'config_value')) : FALSE; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /classes/Kohana/Config/Database/Writer.php: -------------------------------------------------------------------------------- 1 | _loaded_keys[$group] = array_combine(array_keys($config), array_keys($config)); 40 | } 41 | 42 | return $config; 43 | } 44 | 45 | /** 46 | * Writes the passed config for $group 47 | * 48 | * Returns chainable instance on success or throws 49 | * Kohana_Config_Exception on failure 50 | * 51 | * @param string $group The config group 52 | * @param string $key The config key to write to 53 | * @param array $config The configuration to write 54 | * @return boolean 55 | */ 56 | public function write($group, $key, $config) 57 | { 58 | $config = serialize($config); 59 | 60 | // Check to see if we've loaded the config from the table already 61 | if (isset($this->_loaded_keys[$group][$key])) 62 | { 63 | $this->_update($group, $key, $config); 64 | } 65 | else 66 | { 67 | // Attempt to run an insert query 68 | // This may fail if the config key already exists in the table 69 | // and we don't know about it 70 | try 71 | { 72 | $this->_insert($group, $key, $config); 73 | } 74 | catch (Database_Exception $e) 75 | { 76 | // Attempt to run an update instead 77 | $this->_update($group, $key, $config); 78 | } 79 | } 80 | 81 | return TRUE; 82 | } 83 | 84 | /** 85 | * Insert the config values into the table 86 | * 87 | * @param string $group The config group 88 | * @param string $key The config key to write to 89 | * @param array $config The serialized configuration to write 90 | * @return boolean 91 | */ 92 | protected function _insert($group, $key, $config) 93 | { 94 | DB::insert($this->_table_name, array('group_name', 'config_key', 'config_value')) 95 | ->values(array($group, $key, $config)) 96 | ->execute($this->_db_instance); 97 | 98 | return $this; 99 | } 100 | 101 | /** 102 | * Update the config values in the table 103 | * 104 | * @param string $group The config group 105 | * @param string $key The config key to write to 106 | * @param array $config The serialized configuration to write 107 | * @return boolean 108 | */ 109 | protected function _update($group, $key, $config) 110 | { 111 | DB::update($this->_table_name) 112 | ->set(array('config_value' => $config)) 113 | ->where('group_name', '=', $group) 114 | ->where('config_key', '=', $key) 115 | ->execute($this->_db_instance); 116 | 117 | return $this; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /classes/Kohana/DB.php: -------------------------------------------------------------------------------- 1 | [`DB::select_array()`](#select_array) | [Database_Query_Builder_Select] 10 | * [`DB::update()`](#update) | [Database_Query_Builder_Update] 11 | * [`DB::delete()`](#delete) | [Database_Query_Builder_Delete] 12 | * [`DB::expr()`](#expr) | [Database_Expression] 13 | * 14 | * You pass the same parameters to these functions as you pass to the objects they return. 15 | * 16 | * @package Kohana/Database 17 | * @category Base 18 | * @author Kohana Team 19 | * @copyright (c) 2009 Kohana Team 20 | * @license http://kohanaphp.com/license 21 | */ 22 | class Kohana_DB { 23 | 24 | /** 25 | * Create a new [Database_Query] of the given type. 26 | * 27 | * // Create a new SELECT query 28 | * $query = DB::query(Database::SELECT, 'SELECT * FROM users'); 29 | * 30 | * // Create a new DELETE query 31 | * $query = DB::query(Database::DELETE, 'DELETE FROM users WHERE id = 5'); 32 | * 33 | * Specifying the type changes the returned result. When using 34 | * `Database::SELECT`, a [Database_Query_Result] will be returned. 35 | * `Database::INSERT` queries will return the insert id and number of rows. 36 | * For all other queries, the number of affected rows is returned. 37 | * 38 | * @param integer $type type: Database::SELECT, Database::UPDATE, etc 39 | * @param string $sql SQL statement 40 | * @return Database_Query 41 | */ 42 | public static function query($type, $sql) 43 | { 44 | return new Database_Query($type, $sql); 45 | } 46 | 47 | /** 48 | * Create a new [Database_Query_Builder_Select]. Each argument will be 49 | * treated as a column. To generate a `foo AS bar` alias, use an array. 50 | * 51 | * // SELECT id, username 52 | * $query = DB::select('id', 'username'); 53 | * 54 | * // SELECT id AS user_id 55 | * $query = DB::select(array('id', 'user_id')); 56 | * 57 | * @param mixed $columns column name or array($column, $alias) or object 58 | * @return Database_Query_Builder_Select 59 | */ 60 | public static function select($columns = NULL) 61 | { 62 | return new Database_Query_Builder_Select(func_get_args()); 63 | } 64 | 65 | /** 66 | * Create a new [Database_Query_Builder_Select] from an array of columns. 67 | * 68 | * // SELECT id, username 69 | * $query = DB::select_array(array('id', 'username')); 70 | * 71 | * @param array $columns columns to select 72 | * @return Database_Query_Builder_Select 73 | */ 74 | public static function select_array(array $columns = NULL) 75 | { 76 | return new Database_Query_Builder_Select($columns); 77 | } 78 | 79 | /** 80 | * Create a new [Database_Query_Builder_Insert]. 81 | * 82 | * // INSERT INTO users (id, username) 83 | * $query = DB::insert('users', array('id', 'username')); 84 | * 85 | * @param string $table table to insert into 86 | * @param array $columns list of column names or array($column, $alias) or object 87 | * @return Database_Query_Builder_Insert 88 | */ 89 | public static function insert($table = NULL, array $columns = NULL) 90 | { 91 | return new Database_Query_Builder_Insert($table, $columns); 92 | } 93 | 94 | /** 95 | * Create a new [Database_Query_Builder_Update]. 96 | * 97 | * // UPDATE users 98 | * $query = DB::update('users'); 99 | * 100 | * @param string $table table to update 101 | * @return Database_Query_Builder_Update 102 | */ 103 | public static function update($table = NULL) 104 | { 105 | return new Database_Query_Builder_Update($table); 106 | } 107 | 108 | /** 109 | * Create a new [Database_Query_Builder_Delete]. 110 | * 111 | * // DELETE FROM users 112 | * $query = DB::delete('users'); 113 | * 114 | * @param string $table table to delete from 115 | * @return Database_Query_Builder_Delete 116 | */ 117 | public static function delete($table = NULL) 118 | { 119 | return new Database_Query_Builder_Delete($table); 120 | } 121 | 122 | /** 123 | * Create a new [Database_Expression] which is not escaped. An expression 124 | * is the only way to use SQL functions within query builders. 125 | * 126 | * $expression = DB::expr('COUNT(users.id)'); 127 | * $query = DB::update('users')->set(array('login_count' => DB::expr('login_count + 1')))->where('id', '=', $id); 128 | * $users = ORM::factory('user')->where(DB::expr("BINARY `hash`"), '=', $hash)->find(); 129 | * 130 | * @param string $string expression 131 | * @param array parameters 132 | * @return Database_Expression 133 | */ 134 | public static function expr($string, $parameters = array()) 135 | { 136 | return new Database_Expression($string, $parameters); 137 | } 138 | 139 | } // End DB 140 | -------------------------------------------------------------------------------- /classes/Kohana/Database.php: -------------------------------------------------------------------------------- 1 | load('database')->$name; 66 | } 67 | 68 | if ( ! isset($config['type'])) 69 | { 70 | throw new Kohana_Exception('Database type not defined in :name configuration', 71 | array(':name' => $name)); 72 | } 73 | 74 | // Set the driver class name 75 | $driver = 'Database_'.ucfirst($config['type']); 76 | 77 | // Create the database connection instance 78 | $driver = new $driver($name, $config); 79 | 80 | // Store the database instance 81 | Database::$instances[$name] = $driver; 82 | } 83 | 84 | return Database::$instances[$name]; 85 | } 86 | 87 | /** 88 | * @var string the last query executed 89 | */ 90 | public $last_query; 91 | 92 | // Character that is used to quote identifiers 93 | protected $_identifier = '"'; 94 | 95 | // Instance name 96 | protected $_instance; 97 | 98 | // Raw server connection 99 | protected $_connection; 100 | 101 | // Configuration array 102 | protected $_config; 103 | 104 | /** 105 | * Stores the database configuration locally and name the instance. 106 | * 107 | * [!!] This method cannot be accessed directly, you must use [Database::instance]. 108 | * 109 | * @return void 110 | */ 111 | public function __construct($name, array $config) 112 | { 113 | // Set the instance name 114 | $this->_instance = $name; 115 | 116 | // Store the config locally 117 | $this->_config = $config; 118 | 119 | if (empty($this->_config['table_prefix'])) 120 | { 121 | $this->_config['table_prefix'] = ''; 122 | } 123 | } 124 | 125 | /** 126 | * Disconnect from the database when the object is destroyed. 127 | * 128 | * // Destroy the database instance 129 | * unset(Database::instances[(string) $db], $db); 130 | * 131 | * [!!] Calling `unset($db)` is not enough to destroy the database, as it 132 | * will still be stored in `Database::$instances`. 133 | * 134 | * @return void 135 | */ 136 | public function __destruct() 137 | { 138 | $this->disconnect(); 139 | } 140 | 141 | /** 142 | * Returns the database instance name. 143 | * 144 | * echo (string) $db; 145 | * 146 | * @return string 147 | */ 148 | public function __toString() 149 | { 150 | return $this->_instance; 151 | } 152 | 153 | /** 154 | * Connect to the database. This is called automatically when the first 155 | * query is executed. 156 | * 157 | * $db->connect(); 158 | * 159 | * @throws Database_Exception 160 | * @return void 161 | */ 162 | abstract public function connect(); 163 | 164 | /** 165 | * Disconnect from the database. This is called automatically by [Database::__destruct]. 166 | * Clears the database instance from [Database::$instances]. 167 | * 168 | * $db->disconnect(); 169 | * 170 | * @return boolean 171 | */ 172 | public function disconnect() 173 | { 174 | unset(Database::$instances[$this->_instance]); 175 | 176 | return TRUE; 177 | } 178 | 179 | /** 180 | * Set the connection character set. This is called automatically by [Database::connect]. 181 | * 182 | * $db->set_charset('utf8'); 183 | * 184 | * @throws Database_Exception 185 | * @param string $charset character set name 186 | * @return void 187 | */ 188 | abstract public function set_charset($charset); 189 | 190 | /** 191 | * Perform an SQL query of the given type. 192 | * 193 | * // Make a SELECT query and use objects for results 194 | * $db->query(Database::SELECT, 'SELECT * FROM groups', TRUE); 195 | * 196 | * // Make a SELECT query and use "Model_User" for the results 197 | * $db->query(Database::SELECT, 'SELECT * FROM users LIMIT 1', 'Model_User'); 198 | * 199 | * @param integer $type Database::SELECT, Database::INSERT, etc 200 | * @param string $sql SQL query 201 | * @param mixed $as_object result object class string, TRUE for stdClass, FALSE for assoc array 202 | * @param array $params object construct parameters for result class 203 | * @return object Database_Result for SELECT queries 204 | * @return array list (insert id, row count) for INSERT queries 205 | * @return integer number of affected rows for all other queries 206 | */ 207 | abstract public function query($type, $sql, $as_object = FALSE, array $params = NULL); 208 | 209 | /** 210 | * Start a SQL transaction 211 | * 212 | * // Start the transactions 213 | * $db->begin(); 214 | * 215 | * try { 216 | * DB::insert('users')->values($user1)... 217 | * DB::insert('users')->values($user2)... 218 | * // Insert successful commit the changes 219 | * $db->commit(); 220 | * } 221 | * catch (Database_Exception $e) 222 | * { 223 | * // Insert failed. Rolling back changes... 224 | * $db->rollback(); 225 | * } 226 | * 227 | * @param string $mode transaction mode 228 | * @return boolean 229 | */ 230 | abstract public function begin($mode = NULL); 231 | 232 | /** 233 | * Commit the current transaction 234 | * 235 | * // Commit the database changes 236 | * $db->commit(); 237 | * 238 | * @return boolean 239 | */ 240 | abstract public function commit(); 241 | 242 | /** 243 | * Abort the current transaction 244 | * 245 | * // Undo the changes 246 | * $db->rollback(); 247 | * 248 | * @return boolean 249 | */ 250 | abstract public function rollback(); 251 | 252 | /** 253 | * Count the number of records in a table. 254 | * 255 | * // Get the total number of records in the "users" table 256 | * $count = $db->count_records('users'); 257 | * 258 | * @param mixed $table table name string or array(query, alias) 259 | * @return integer 260 | */ 261 | public function count_records($table) 262 | { 263 | // Quote the table name 264 | $table = $this->quote_table($table); 265 | 266 | return $this->query(Database::SELECT, 'SELECT COUNT(*) AS total_row_count FROM '.$table, FALSE) 267 | ->get('total_row_count'); 268 | } 269 | 270 | /** 271 | * Returns a normalized array describing the SQL data type 272 | * 273 | * $db->datatype('char'); 274 | * 275 | * @param string $type SQL data type 276 | * @return array 277 | */ 278 | public function datatype($type) 279 | { 280 | static $types = array 281 | ( 282 | // SQL-92 283 | 'bit' => array('type' => 'string', 'exact' => TRUE), 284 | 'bit varying' => array('type' => 'string'), 285 | 'char' => array('type' => 'string', 'exact' => TRUE), 286 | 'char varying' => array('type' => 'string'), 287 | 'character' => array('type' => 'string', 'exact' => TRUE), 288 | 'character varying' => array('type' => 'string'), 289 | 'date' => array('type' => 'string'), 290 | 'dec' => array('type' => 'float', 'exact' => TRUE), 291 | 'decimal' => array('type' => 'float', 'exact' => TRUE), 292 | 'double precision' => array('type' => 'float'), 293 | 'float' => array('type' => 'float'), 294 | 'int' => array('type' => 'int', 'min' => '-2147483648', 'max' => '2147483647'), 295 | 'integer' => array('type' => 'int', 'min' => '-2147483648', 'max' => '2147483647'), 296 | 'interval' => array('type' => 'string'), 297 | 'national char' => array('type' => 'string', 'exact' => TRUE), 298 | 'national char varying' => array('type' => 'string'), 299 | 'national character' => array('type' => 'string', 'exact' => TRUE), 300 | 'national character varying' => array('type' => 'string'), 301 | 'nchar' => array('type' => 'string', 'exact' => TRUE), 302 | 'nchar varying' => array('type' => 'string'), 303 | 'numeric' => array('type' => 'float', 'exact' => TRUE), 304 | 'real' => array('type' => 'float'), 305 | 'smallint' => array('type' => 'int', 'min' => '-32768', 'max' => '32767'), 306 | 'time' => array('type' => 'string'), 307 | 'time with time zone' => array('type' => 'string'), 308 | 'timestamp' => array('type' => 'string'), 309 | 'timestamp with time zone' => array('type' => 'string'), 310 | 'varchar' => array('type' => 'string'), 311 | 312 | // SQL:1999 313 | 'binary large object' => array('type' => 'string', 'binary' => TRUE), 314 | 'blob' => array('type' => 'string', 'binary' => TRUE), 315 | 'boolean' => array('type' => 'bool'), 316 | 'char large object' => array('type' => 'string'), 317 | 'character large object' => array('type' => 'string'), 318 | 'clob' => array('type' => 'string'), 319 | 'national character large object' => array('type' => 'string'), 320 | 'nchar large object' => array('type' => 'string'), 321 | 'nclob' => array('type' => 'string'), 322 | 'time without time zone' => array('type' => 'string'), 323 | 'timestamp without time zone' => array('type' => 'string'), 324 | 325 | // SQL:2003 326 | 'bigint' => array('type' => 'int', 'min' => '-9223372036854775808', 'max' => '9223372036854775807'), 327 | 328 | // SQL:2008 329 | 'binary' => array('type' => 'string', 'binary' => TRUE, 'exact' => TRUE), 330 | 'binary varying' => array('type' => 'string', 'binary' => TRUE), 331 | 'varbinary' => array('type' => 'string', 'binary' => TRUE), 332 | ); 333 | 334 | if (isset($types[$type])) 335 | return $types[$type]; 336 | 337 | return array(); 338 | } 339 | 340 | /** 341 | * List all of the tables in the database. Optionally, a LIKE string can 342 | * be used to search for specific tables. 343 | * 344 | * // Get all tables in the current database 345 | * $tables = $db->list_tables(); 346 | * 347 | * // Get all user-related tables 348 | * $tables = $db->list_tables('user%'); 349 | * 350 | * @param string $like table to search for 351 | * @return array 352 | */ 353 | abstract public function list_tables($like = NULL); 354 | 355 | /** 356 | * Lists all of the columns in a table. Optionally, a LIKE string can be 357 | * used to search for specific fields. 358 | * 359 | * // Get all columns from the "users" table 360 | * $columns = $db->list_columns('users'); 361 | * 362 | * // Get all name-related columns 363 | * $columns = $db->list_columns('users', '%name%'); 364 | * 365 | * // Get the columns from a table that doesn't use the table prefix 366 | * $columns = $db->list_columns('users', NULL, FALSE); 367 | * 368 | * @param string $table table to get columns from 369 | * @param string $like column to search for 370 | * @param boolean $add_prefix whether to add the table prefix automatically or not 371 | * @return array 372 | */ 373 | abstract public function list_columns($table, $like = NULL, $add_prefix = TRUE); 374 | 375 | /** 376 | * Extracts the text between parentheses, if any. 377 | * 378 | * // Returns: array('CHAR', '6') 379 | * list($type, $length) = $db->_parse_type('CHAR(6)'); 380 | * 381 | * @param string $type 382 | * @return array list containing the type and length, if any 383 | */ 384 | protected function _parse_type($type) 385 | { 386 | if (($open = strpos($type, '(')) === FALSE) 387 | { 388 | // No length specified 389 | return array($type, NULL); 390 | } 391 | 392 | // Closing parenthesis 393 | $close = strrpos($type, ')', $open); 394 | 395 | // Length without parentheses 396 | $length = substr($type, $open + 1, $close - 1 - $open); 397 | 398 | // Type without the length 399 | $type = substr($type, 0, $open).substr($type, $close + 1); 400 | 401 | return array($type, $length); 402 | } 403 | 404 | /** 405 | * Return the table prefix defined in the current configuration. 406 | * 407 | * $prefix = $db->table_prefix(); 408 | * 409 | * @return string 410 | */ 411 | public function table_prefix() 412 | { 413 | return $this->_config['table_prefix']; 414 | } 415 | 416 | /** 417 | * Quote a value for an SQL query. 418 | * 419 | * $db->quote(NULL); // 'NULL' 420 | * $db->quote(10); // 10 421 | * $db->quote('fred'); // 'fred' 422 | * 423 | * Objects passed to this function will be converted to strings. 424 | * [Database_Expression] objects will be compiled. 425 | * [Database_Query] objects will be compiled and converted to a sub-query. 426 | * All other objects will be converted using the `__toString` method. 427 | * 428 | * @param mixed $value any value to quote 429 | * @return string 430 | * @uses Database::escape 431 | */ 432 | public function quote($value) 433 | { 434 | if ($value === NULL) 435 | { 436 | return 'NULL'; 437 | } 438 | elseif ($value === TRUE) 439 | { 440 | return "'1'"; 441 | } 442 | elseif ($value === FALSE) 443 | { 444 | return "'0'"; 445 | } 446 | elseif (is_object($value)) 447 | { 448 | if ($value instanceof Database_Query) 449 | { 450 | // Create a sub-query 451 | return '('.$value->compile($this).')'; 452 | } 453 | elseif ($value instanceof Database_Expression) 454 | { 455 | // Compile the expression 456 | return $value->compile($this); 457 | } 458 | else 459 | { 460 | // Convert the object to a string 461 | return $this->quote( (string) $value); 462 | } 463 | } 464 | elseif (is_array($value)) 465 | { 466 | return '('.implode(', ', array_map(array($this, __FUNCTION__), $value)).')'; 467 | } 468 | elseif (is_int($value)) 469 | { 470 | return (int) $value; 471 | } 472 | elseif (is_float($value)) 473 | { 474 | // Convert to non-locale aware float to prevent possible commas 475 | return sprintf('%F', $value); 476 | } 477 | 478 | return $this->escape($value); 479 | } 480 | 481 | /** 482 | * Quote a database column name and add the table prefix if needed. 483 | * 484 | * $column = $db->quote_column($column); 485 | * 486 | * You can also use SQL methods within identifiers. 487 | * 488 | * $column = $db->quote_column(DB::expr('COUNT(`column`)')); 489 | * 490 | * Objects passed to this function will be converted to strings. 491 | * [Database_Expression] objects will be compiled. 492 | * [Database_Query] objects will be compiled and converted to a sub-query. 493 | * All other objects will be converted using the `__toString` method. 494 | * 495 | * @param mixed $column column name or array(column, alias) 496 | * @return string 497 | * @uses Database::quote_identifier 498 | * @uses Database::table_prefix 499 | */ 500 | public function quote_column($column) 501 | { 502 | // Identifiers are escaped by repeating them 503 | $escaped_identifier = $this->_identifier.$this->_identifier; 504 | 505 | if (is_array($column)) 506 | { 507 | list($column, $alias) = $column; 508 | $alias = str_replace($this->_identifier, $escaped_identifier, $alias); 509 | } 510 | 511 | if ($column instanceof Database_Query) 512 | { 513 | // Create a sub-query 514 | $column = '('.$column->compile($this).')'; 515 | } 516 | elseif ($column instanceof Database_Expression) 517 | { 518 | // Compile the expression 519 | $column = $column->compile($this); 520 | } 521 | else 522 | { 523 | // Convert to a string 524 | $column = (string) $column; 525 | 526 | $column = str_replace($this->_identifier, $escaped_identifier, $column); 527 | 528 | if ($column === '*') 529 | { 530 | return $column; 531 | } 532 | elseif (strpos($column, '.') !== FALSE) 533 | { 534 | $parts = explode('.', $column); 535 | 536 | if ($prefix = $this->table_prefix()) 537 | { 538 | // Get the offset of the table name, 2nd-to-last part 539 | $offset = count($parts) - 2; 540 | 541 | // Add the table prefix to the table name 542 | $parts[$offset] = $prefix.$parts[$offset]; 543 | } 544 | 545 | foreach ($parts as & $part) 546 | { 547 | if ($part !== '*') 548 | { 549 | // Quote each of the parts 550 | $part = $this->_identifier.$part.$this->_identifier; 551 | } 552 | } 553 | 554 | $column = implode('.', $parts); 555 | } 556 | else 557 | { 558 | $column = $this->_identifier.$column.$this->_identifier; 559 | } 560 | } 561 | 562 | if (isset($alias)) 563 | { 564 | $column .= ' AS '.$this->_identifier.$alias.$this->_identifier; 565 | } 566 | 567 | return $column; 568 | } 569 | 570 | /** 571 | * Quote a database table name and adds the table prefix if needed. 572 | * 573 | * $table = $db->quote_table($table); 574 | * 575 | * Objects passed to this function will be converted to strings. 576 | * [Database_Expression] objects will be compiled. 577 | * [Database_Query] objects will be compiled and converted to a sub-query. 578 | * All other objects will be converted using the `__toString` method. 579 | * 580 | * @param mixed $table table name or array(table, alias) 581 | * @return string 582 | * @uses Database::quote_identifier 583 | * @uses Database::table_prefix 584 | */ 585 | public function quote_table($table) 586 | { 587 | // Identifiers are escaped by repeating them 588 | $escaped_identifier = $this->_identifier.$this->_identifier; 589 | 590 | if (is_array($table)) 591 | { 592 | list($table, $alias) = $table; 593 | $alias = str_replace($this->_identifier, $escaped_identifier, $alias); 594 | } 595 | 596 | if ($table instanceof Database_Query) 597 | { 598 | // Create a sub-query 599 | $table = '('.$table->compile($this).')'; 600 | } 601 | elseif ($table instanceof Database_Expression) 602 | { 603 | // Compile the expression 604 | $table = $table->compile($this); 605 | } 606 | else 607 | { 608 | // Convert to a string 609 | $table = (string) $table; 610 | 611 | $table = str_replace($this->_identifier, $escaped_identifier, $table); 612 | 613 | if (strpos($table, '.') !== FALSE) 614 | { 615 | $parts = explode('.', $table); 616 | 617 | if ($prefix = $this->table_prefix()) 618 | { 619 | // Get the offset of the table name, last part 620 | $offset = count($parts) - 1; 621 | 622 | // Add the table prefix to the table name 623 | $parts[$offset] = $prefix.$parts[$offset]; 624 | } 625 | 626 | foreach ($parts as & $part) 627 | { 628 | // Quote each of the parts 629 | $part = $this->_identifier.$part.$this->_identifier; 630 | } 631 | 632 | $table = implode('.', $parts); 633 | } 634 | else 635 | { 636 | // Add the table prefix 637 | $table = $this->_identifier.$this->table_prefix().$table.$this->_identifier; 638 | } 639 | } 640 | 641 | if (isset($alias)) 642 | { 643 | // Attach table prefix to alias 644 | $table .= ' AS '.$this->_identifier.$this->table_prefix().$alias.$this->_identifier; 645 | } 646 | 647 | return $table; 648 | } 649 | 650 | /** 651 | * Quote a database identifier 652 | * 653 | * Objects passed to this function will be converted to strings. 654 | * [Database_Expression] objects will be compiled. 655 | * [Database_Query] objects will be compiled and converted to a sub-query. 656 | * All other objects will be converted using the `__toString` method. 657 | * 658 | * @param mixed $value any identifier 659 | * @return string 660 | */ 661 | public function quote_identifier($value) 662 | { 663 | // Identifiers are escaped by repeating them 664 | $escaped_identifier = $this->_identifier.$this->_identifier; 665 | 666 | if (is_array($value)) 667 | { 668 | list($value, $alias) = $value; 669 | $alias = str_replace($this->_identifier, $escaped_identifier, $alias); 670 | } 671 | 672 | if ($value instanceof Database_Query) 673 | { 674 | // Create a sub-query 675 | $value = '('.$value->compile($this).')'; 676 | } 677 | elseif ($value instanceof Database_Expression) 678 | { 679 | // Compile the expression 680 | $value = $value->compile($this); 681 | } 682 | else 683 | { 684 | // Convert to a string 685 | $value = (string) $value; 686 | 687 | $value = str_replace($this->_identifier, $escaped_identifier, $value); 688 | 689 | if (strpos($value, '.') !== FALSE) 690 | { 691 | $parts = explode('.', $value); 692 | 693 | foreach ($parts as & $part) 694 | { 695 | // Quote each of the parts 696 | $part = $this->_identifier.$part.$this->_identifier; 697 | } 698 | 699 | $value = implode('.', $parts); 700 | } 701 | else 702 | { 703 | $value = $this->_identifier.$value.$this->_identifier; 704 | } 705 | } 706 | 707 | if (isset($alias)) 708 | { 709 | $value .= ' AS '.$this->_identifier.$alias.$this->_identifier; 710 | } 711 | 712 | return $value; 713 | } 714 | 715 | /** 716 | * Sanitize a string by escaping characters that could cause an SQL 717 | * injection attack. 718 | * 719 | * $value = $db->escape('any string'); 720 | * 721 | * @param string $value value to quote 722 | * @return string 723 | */ 724 | abstract public function escape($value); 725 | 726 | } // End Database_Connection 727 | -------------------------------------------------------------------------------- /classes/Kohana/Database/Exception.php: -------------------------------------------------------------------------------- 1 | _value = $value; 40 | $this->_parameters = $parameters; 41 | } 42 | 43 | /** 44 | * Bind a variable to a parameter. 45 | * 46 | * @param string $param parameter key to replace 47 | * @param mixed $var variable to use 48 | * @return $this 49 | */ 50 | public function bind($param, & $var) 51 | { 52 | $this->_parameters[$param] =& $var; 53 | 54 | return $this; 55 | } 56 | 57 | /** 58 | * Set the value of a parameter. 59 | * 60 | * @param string $param parameter key to replace 61 | * @param mixed $value value to use 62 | * @return $this 63 | */ 64 | public function param($param, $value) 65 | { 66 | $this->_parameters[$param] = $value; 67 | 68 | return $this; 69 | } 70 | 71 | /** 72 | * Add multiple parameter values. 73 | * 74 | * @param array $params list of parameter values 75 | * @return $this 76 | */ 77 | public function parameters(array $params) 78 | { 79 | $this->_parameters = $params + $this->_parameters; 80 | 81 | return $this; 82 | } 83 | 84 | /** 85 | * Get the expression value as a string. 86 | * 87 | * $sql = $expression->value(); 88 | * 89 | * @return string 90 | */ 91 | public function value() 92 | { 93 | return (string) $this->_value; 94 | } 95 | 96 | /** 97 | * Return the value of the expression as a string. 98 | * 99 | * echo $expression; 100 | * 101 | * @return string 102 | * @uses Database_Expression::value 103 | */ 104 | public function __toString() 105 | { 106 | return $this->value(); 107 | } 108 | 109 | /** 110 | * Compile the SQL expression and return it. Replaces any parameters with 111 | * their given values. 112 | * 113 | * @param mixed Database instance or name of instance 114 | * @return string 115 | */ 116 | public function compile($db = NULL) 117 | { 118 | if ( ! is_object($db)) 119 | { 120 | // Get the database instance 121 | $db = Database::instance($db); 122 | } 123 | 124 | $value = $this->value(); 125 | 126 | if ( ! empty($this->_parameters)) 127 | { 128 | // Quote all of the parameter values 129 | $params = array_map(array($db, 'quote'), $this->_parameters); 130 | 131 | // Replace the values in the expression 132 | $value = strtr($value, $params); 133 | } 134 | 135 | return $value; 136 | } 137 | 138 | } // End Database_Expression 139 | -------------------------------------------------------------------------------- /classes/Kohana/Database/MySQL.php: -------------------------------------------------------------------------------- 1 | _connection) 28 | return; 29 | 30 | if (Database_MySQL::$_set_names === NULL) 31 | { 32 | // Determine if we can use mysql_set_charset(), which is only 33 | // available on PHP 5.2.3+ when compiled against MySQL 5.0+ 34 | Database_MySQL::$_set_names = ! function_exists('mysql_set_charset'); 35 | } 36 | 37 | // Extract the connection parameters, adding required variabels 38 | extract($this->_config['connection'] + array( 39 | 'database' => '', 40 | 'hostname' => '', 41 | 'username' => '', 42 | 'password' => '', 43 | 'persistent' => FALSE, 44 | )); 45 | 46 | // Prevent this information from showing up in traces 47 | unset($this->_config['connection']['username'], $this->_config['connection']['password']); 48 | 49 | try 50 | { 51 | if ($persistent) 52 | { 53 | // Create a persistent connection 54 | $this->_connection = mysql_pconnect($hostname, $username, $password); 55 | } 56 | else 57 | { 58 | // Create a connection and force it to be a new link 59 | $this->_connection = mysql_connect($hostname, $username, $password, TRUE); 60 | } 61 | } 62 | catch (Exception $e) 63 | { 64 | // No connection exists 65 | $this->_connection = NULL; 66 | 67 | throw new Database_Exception(':error', 68 | array(':error' => $e->getMessage()), 69 | $e->getCode()); 70 | } 71 | 72 | // \xFF is a better delimiter, but the PHP driver uses underscore 73 | $this->_connection_id = sha1($hostname.'_'.$username.'_'.$password); 74 | 75 | $this->_select_db($database); 76 | 77 | if ( ! empty($this->_config['charset'])) 78 | { 79 | // Set the character set 80 | $this->set_charset($this->_config['charset']); 81 | } 82 | 83 | if ( ! empty($this->_config['connection']['variables'])) 84 | { 85 | // Set session variables 86 | $variables = array(); 87 | 88 | foreach ($this->_config['connection']['variables'] as $var => $val) 89 | { 90 | $variables[] = 'SESSION '.$var.' = '.$this->quote($val); 91 | } 92 | 93 | mysql_query('SET '.implode(', ', $variables), $this->_connection); 94 | } 95 | } 96 | 97 | /** 98 | * Select the database 99 | * 100 | * @param string $database Database 101 | * @return void 102 | */ 103 | protected function _select_db($database) 104 | { 105 | if ( ! mysql_select_db($database, $this->_connection)) 106 | { 107 | // Unable to select database 108 | throw new Database_Exception(':error', 109 | array(':error' => mysql_error($this->_connection)), 110 | mysql_errno($this->_connection)); 111 | } 112 | 113 | Database_MySQL::$_current_databases[$this->_connection_id] = $database; 114 | } 115 | 116 | public function disconnect() 117 | { 118 | try 119 | { 120 | // Database is assumed disconnected 121 | $status = TRUE; 122 | 123 | if (is_resource($this->_connection)) 124 | { 125 | if ($status = mysql_close($this->_connection)) 126 | { 127 | // Clear the connection 128 | $this->_connection = NULL; 129 | 130 | // Clear the instance 131 | parent::disconnect(); 132 | } 133 | } 134 | } 135 | catch (Exception $e) 136 | { 137 | // Database is probably not disconnected 138 | $status = ! is_resource($this->_connection); 139 | } 140 | 141 | return $status; 142 | } 143 | 144 | public function set_charset($charset) 145 | { 146 | // Make sure the database is connected 147 | $this->_connection or $this->connect(); 148 | 149 | if (Database_MySQL::$_set_names === TRUE) 150 | { 151 | // PHP is compiled against MySQL 4.x 152 | $status = (bool) mysql_query('SET NAMES '.$this->quote($charset), $this->_connection); 153 | } 154 | else 155 | { 156 | // PHP is compiled against MySQL 5.x 157 | $status = mysql_set_charset($charset, $this->_connection); 158 | } 159 | 160 | if ($status === FALSE) 161 | { 162 | throw new Database_Exception(':error', 163 | array(':error' => mysql_error($this->_connection)), 164 | mysql_errno($this->_connection)); 165 | } 166 | } 167 | 168 | public function query($type, $sql, $as_object = FALSE, array $params = NULL) 169 | { 170 | // Make sure the database is connected 171 | $this->_connection or $this->connect(); 172 | 173 | if (Kohana::$profiling) 174 | { 175 | // Benchmark this query for the current instance 176 | $benchmark = Profiler::start("Database ({$this->_instance})", $sql); 177 | } 178 | 179 | if ( ! empty($this->_config['connection']['persistent']) AND $this->_config['connection']['database'] !== Database_MySQL::$_current_databases[$this->_connection_id]) 180 | { 181 | // Select database on persistent connections 182 | $this->_select_db($this->_config['connection']['database']); 183 | } 184 | 185 | // Execute the query 186 | if (($result = mysql_query($sql, $this->_connection)) === FALSE) 187 | { 188 | if (isset($benchmark)) 189 | { 190 | // This benchmark is worthless 191 | Profiler::delete($benchmark); 192 | } 193 | 194 | throw new Database_Exception(':error [ :query ]', 195 | array(':error' => mysql_error($this->_connection), ':query' => $sql), 196 | mysql_errno($this->_connection)); 197 | } 198 | 199 | if (isset($benchmark)) 200 | { 201 | Profiler::stop($benchmark); 202 | } 203 | 204 | // Set the last query 205 | $this->last_query = $sql; 206 | 207 | if ($type === Database::SELECT) 208 | { 209 | // Return an iterator of results 210 | return new Database_MySQL_Result($result, $sql, $as_object, $params); 211 | } 212 | elseif ($type === Database::INSERT) 213 | { 214 | // Return a list of insert id and rows created 215 | return array( 216 | mysql_insert_id($this->_connection), 217 | mysql_affected_rows($this->_connection), 218 | ); 219 | } 220 | else 221 | { 222 | // Return the number of rows affected 223 | return mysql_affected_rows($this->_connection); 224 | } 225 | } 226 | 227 | public function datatype($type) 228 | { 229 | static $types = array 230 | ( 231 | 'blob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '65535'), 232 | 'bool' => array('type' => 'bool'), 233 | 'bigint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '18446744073709551615'), 234 | 'datetime' => array('type' => 'string'), 235 | 'decimal unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), 236 | 'double' => array('type' => 'float'), 237 | 'double precision unsigned' => array('type' => 'float', 'min' => '0'), 238 | 'double unsigned' => array('type' => 'float', 'min' => '0'), 239 | 'enum' => array('type' => 'string'), 240 | 'fixed' => array('type' => 'float', 'exact' => TRUE), 241 | 'fixed unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), 242 | 'float unsigned' => array('type' => 'float', 'min' => '0'), 243 | 'geometry' => array('type' => 'string', 'binary' => TRUE), 244 | 'int unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'), 245 | 'integer unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'), 246 | 'longblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '4294967295'), 247 | 'longtext' => array('type' => 'string', 'character_maximum_length' => '4294967295'), 248 | 'mediumblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '16777215'), 249 | 'mediumint' => array('type' => 'int', 'min' => '-8388608', 'max' => '8388607'), 250 | 'mediumint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '16777215'), 251 | 'mediumtext' => array('type' => 'string', 'character_maximum_length' => '16777215'), 252 | 'national varchar' => array('type' => 'string'), 253 | 'numeric unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), 254 | 'nvarchar' => array('type' => 'string'), 255 | 'point' => array('type' => 'string', 'binary' => TRUE), 256 | 'real unsigned' => array('type' => 'float', 'min' => '0'), 257 | 'set' => array('type' => 'string'), 258 | 'smallint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '65535'), 259 | 'text' => array('type' => 'string', 'character_maximum_length' => '65535'), 260 | 'tinyblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '255'), 261 | 'tinyint' => array('type' => 'int', 'min' => '-128', 'max' => '127'), 262 | 'tinyint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '255'), 263 | 'tinytext' => array('type' => 'string', 'character_maximum_length' => '255'), 264 | 'year' => array('type' => 'string'), 265 | ); 266 | 267 | $type = str_replace(' zerofill', '', $type); 268 | 269 | if (isset($types[$type])) 270 | return $types[$type]; 271 | 272 | return parent::datatype($type); 273 | } 274 | 275 | /** 276 | * Start a SQL transaction 277 | * 278 | * @link http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html 279 | * 280 | * @param string $mode Isolation level 281 | * @return boolean 282 | */ 283 | public function begin($mode = NULL) 284 | { 285 | // Make sure the database is connected 286 | $this->_connection or $this->connect(); 287 | 288 | if ($mode AND ! mysql_query("SET TRANSACTION ISOLATION LEVEL $mode", $this->_connection)) 289 | { 290 | throw new Database_Exception(':error', 291 | array(':error' => mysql_error($this->_connection)), 292 | mysql_errno($this->_connection)); 293 | } 294 | 295 | return (bool) mysql_query('START TRANSACTION', $this->_connection); 296 | } 297 | 298 | /** 299 | * Commit a SQL transaction 300 | * 301 | * @return boolean 302 | */ 303 | public function commit() 304 | { 305 | // Make sure the database is connected 306 | $this->_connection or $this->connect(); 307 | 308 | return (bool) mysql_query('COMMIT', $this->_connection); 309 | } 310 | 311 | /** 312 | * Rollback a SQL transaction 313 | * 314 | * @return boolean 315 | */ 316 | public function rollback() 317 | { 318 | // Make sure the database is connected 319 | $this->_connection or $this->connect(); 320 | 321 | return (bool) mysql_query('ROLLBACK', $this->_connection); 322 | } 323 | 324 | public function list_tables($like = NULL) 325 | { 326 | if (is_string($like)) 327 | { 328 | // Search for table names 329 | $result = $this->query(Database::SELECT, 'SHOW TABLES LIKE '.$this->quote($like), FALSE); 330 | } 331 | else 332 | { 333 | // Find all table names 334 | $result = $this->query(Database::SELECT, 'SHOW TABLES', FALSE); 335 | } 336 | 337 | $tables = array(); 338 | foreach ($result as $row) 339 | { 340 | $tables[] = reset($row); 341 | } 342 | 343 | return $tables; 344 | } 345 | 346 | public function list_columns($table, $like = NULL, $add_prefix = TRUE) 347 | { 348 | // Quote the table name 349 | $table = ($add_prefix === TRUE) ? $this->quote_table($table) : $table; 350 | 351 | if (is_string($like)) 352 | { 353 | // Search for column names 354 | $result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table.' LIKE '.$this->quote($like), FALSE); 355 | } 356 | else 357 | { 358 | // Find all column names 359 | $result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table, FALSE); 360 | } 361 | 362 | $count = 0; 363 | $columns = array(); 364 | foreach ($result as $row) 365 | { 366 | list($type, $length) = $this->_parse_type($row['Type']); 367 | 368 | $column = $this->datatype($type); 369 | 370 | $column['column_name'] = $row['Field']; 371 | $column['column_default'] = $row['Default']; 372 | $column['data_type'] = $type; 373 | $column['is_nullable'] = ($row['Null'] == 'YES'); 374 | $column['ordinal_position'] = ++$count; 375 | 376 | switch ($column['type']) 377 | { 378 | case 'float': 379 | if (isset($length)) 380 | { 381 | list($column['numeric_precision'], $column['numeric_scale']) = explode(',', $length); 382 | } 383 | break; 384 | case 'int': 385 | if (isset($length)) 386 | { 387 | // MySQL attribute 388 | $column['display'] = $length; 389 | } 390 | break; 391 | case 'string': 392 | switch ($column['data_type']) 393 | { 394 | case 'binary': 395 | case 'varbinary': 396 | $column['character_maximum_length'] = $length; 397 | break; 398 | case 'char': 399 | case 'varchar': 400 | $column['character_maximum_length'] = $length; 401 | case 'text': 402 | case 'tinytext': 403 | case 'mediumtext': 404 | case 'longtext': 405 | $column['collation_name'] = $row['Collation']; 406 | break; 407 | case 'enum': 408 | case 'set': 409 | $column['collation_name'] = $row['Collation']; 410 | $column['options'] = explode('\',\'', substr($length, 1, -1)); 411 | break; 412 | } 413 | break; 414 | } 415 | 416 | // MySQL attributes 417 | $column['comment'] = $row['Comment']; 418 | $column['extra'] = $row['Extra']; 419 | $column['key'] = $row['Key']; 420 | $column['privileges'] = $row['Privileges']; 421 | 422 | $columns[$row['Field']] = $column; 423 | } 424 | 425 | return $columns; 426 | } 427 | 428 | public function escape($value) 429 | { 430 | // Make sure the database is connected 431 | $this->_connection or $this->connect(); 432 | 433 | if (($value = mysql_real_escape_string( (string) $value, $this->_connection)) === FALSE) 434 | { 435 | throw new Database_Exception(':error', 436 | array(':error' => mysql_error($this->_connection)), 437 | mysql_errno($this->_connection)); 438 | } 439 | 440 | // SQL standard is to use single-quotes for all values 441 | return "'$value'"; 442 | } 443 | 444 | } // End Database_MySQL 445 | -------------------------------------------------------------------------------- /classes/Kohana/Database/MySQL/Result.php: -------------------------------------------------------------------------------- 1 | _total_rows = mysql_num_rows($result); 21 | } 22 | 23 | public function __destruct() 24 | { 25 | if (is_resource($this->_result)) 26 | { 27 | mysql_free_result($this->_result); 28 | } 29 | } 30 | 31 | public function seek($offset) 32 | { 33 | if ($this->offsetExists($offset) AND mysql_data_seek($this->_result, $offset)) 34 | { 35 | // Set the current row to the offset 36 | $this->_current_row = $this->_internal_row = $offset; 37 | 38 | return TRUE; 39 | } 40 | else 41 | { 42 | return FALSE; 43 | } 44 | } 45 | 46 | public function current() 47 | { 48 | if ($this->_current_row !== $this->_internal_row AND ! $this->seek($this->_current_row)) 49 | return NULL; 50 | 51 | // Increment internal row for optimization assuming rows are fetched in order 52 | $this->_internal_row++; 53 | 54 | // FIXME mysql_fetch_object has been deprecated as of php 5.5! 55 | // Please use mysqli_fetch_object or PDOStatement::fetch(PDO::FETCH_OBJ) instead. 56 | 57 | if ($this->_as_object === TRUE) 58 | { 59 | // Return an stdClass 60 | return mysql_fetch_object($this->_result); 61 | } 62 | elseif (is_string($this->_as_object)) 63 | { 64 | /* The second and third argument for mysql_fetch_object are optional, but do 65 | * not have default values defined. Passing _object_params with a non-array value results 66 | * in undefined behavior that varies by PHP version. For example, if NULL is supplied on 67 | * PHP 5.3, the resulting behavior is identical to calling with array(), which results in the 68 | * classes __construct function being called with no arguments. This is only an issue when 69 | * the _as_object class does not have an explicit __construct method resulting in the 70 | * cryptic error "Class %s does not have a constructor hence you cannot use ctor_params." 71 | * In contrast, the same function call on PHP 5.5 will 'functionally' interpret 72 | * _object_params == NULL as an omission of the third argument, resulting in the original 73 | * intended functionally. 74 | * 75 | * Because the backing code for the mysql_fetch_object has not changed between 5.3 and 5.5, 76 | * I suspect this discrepancy is due to the way the classes are instantiated on a boarder 77 | * level. Additionally, mysql_fetch_object has been deprecated in 5.5 and should probably be 78 | * replaced by mysqli_fetch_object or PDOStatement::fetch(PDO::FETCH_OBJ) in Kohana 3.4. 79 | */ 80 | if ($this->_object_params !== NULL) 81 | { 82 | // Return an object of given class name with constructor params 83 | return mysql_fetch_object($this->_result, $this->_as_object, $this->_object_params); 84 | } 85 | else 86 | { 87 | // Return an object of given class name without constructor params 88 | return mysql_fetch_object($this->_result, $this->_as_object); 89 | } 90 | } 91 | else 92 | { 93 | // Return an array of the row 94 | return mysql_fetch_assoc($this->_result); 95 | } 96 | } 97 | 98 | } // End Database_MySQL_Result_Select 99 | -------------------------------------------------------------------------------- /classes/Kohana/Database/MySQLi.php: -------------------------------------------------------------------------------- 1 | _connection) 28 | return; 29 | 30 | if (Database_MySQLi::$_set_names === NULL) 31 | { 32 | // Determine if we can use mysqli_set_charset(), which is only 33 | // available on PHP 5.2.3+ when compiled against MySQL 5.0+ 34 | Database_MySQLi::$_set_names = ! function_exists('mysqli_set_charset'); 35 | } 36 | 37 | // Extract the connection parameters, adding required variabels 38 | extract($this->_config['connection'] + array( 39 | 'database' => '', 40 | 'hostname' => '', 41 | 'username' => '', 42 | 'password' => '', 43 | 'socket' => '', 44 | 'port' => 3306, 45 | 'ssl' => NULL, 46 | )); 47 | 48 | // Prevent this information from showing up in traces 49 | unset($this->_config['connection']['username'], $this->_config['connection']['password']); 50 | 51 | try 52 | { 53 | if(is_array($ssl)) 54 | { 55 | $this->_connection = mysqli_init(); 56 | $this->_connection->ssl_set( 57 | Arr::get($ssl, 'client_key_path'), 58 | Arr::get($ssl, 'client_cert_path'), 59 | Arr::get($ssl, 'ca_cert_path'), 60 | Arr::get($ssl, 'ca_dir_path'), 61 | Arr::get($ssl, 'cipher') 62 | ); 63 | $this->_connection->real_connect($hostname, $username, $password, $database, $port, $socket, MYSQLI_CLIENT_SSL); 64 | } 65 | else 66 | { 67 | $this->_connection = new mysqli($hostname, $username, $password, $database, $port, $socket); 68 | } 69 | } 70 | catch (Exception $e) 71 | { 72 | // No connection exists 73 | $this->_connection = NULL; 74 | 75 | throw new Database_Exception(':error', array(':error' => $e->getMessage()), $e->getCode()); 76 | } 77 | 78 | // \xFF is a better delimiter, but the PHP driver uses underscore 79 | $this->_connection_id = sha1($hostname.'_'.$username.'_'.$password); 80 | 81 | if ( ! empty($this->_config['charset'])) 82 | { 83 | // Set the character set 84 | $this->set_charset($this->_config['charset']); 85 | } 86 | 87 | if ( ! empty($this->_config['connection']['variables'])) 88 | { 89 | // Set session variables 90 | $variables = array(); 91 | 92 | foreach ($this->_config['connection']['variables'] as $var => $val) 93 | { 94 | $variables[] = 'SESSION '.$var.' = '.$this->quote($val); 95 | } 96 | 97 | $this->_connection->query('SET '.implode(', ', $variables)); 98 | } 99 | } 100 | 101 | public function disconnect() 102 | { 103 | try 104 | { 105 | // Database is assumed disconnected 106 | $status = TRUE; 107 | 108 | if (is_resource($this->_connection)) 109 | { 110 | if ($status = $this->_connection->close()) 111 | { 112 | // Clear the connection 113 | $this->_connection = NULL; 114 | 115 | // Clear the instance 116 | parent::disconnect(); 117 | } 118 | } 119 | } 120 | catch (Exception $e) 121 | { 122 | // Database is probably not disconnected 123 | $status = ! is_resource($this->_connection); 124 | } 125 | 126 | return $status; 127 | } 128 | 129 | public function set_charset($charset) 130 | { 131 | // Make sure the database is connected 132 | $this->_connection or $this->connect(); 133 | 134 | if (Database_MySQLi::$_set_names === TRUE) 135 | { 136 | // PHP is compiled against MySQL 4.x 137 | $status = (bool) $this->_connection->query('SET NAMES '.$this->quote($charset)); 138 | } 139 | else 140 | { 141 | // PHP is compiled against MySQL 5.x 142 | $status = $this->_connection->set_charset($charset); 143 | } 144 | 145 | if ($status === FALSE) 146 | { 147 | throw new Database_Exception(':error', array(':error' => $this->_connection->error), $this->_connection->errno); 148 | } 149 | } 150 | 151 | public function query($type, $sql, $as_object = FALSE, array $params = NULL) 152 | { 153 | // Make sure the database is connected 154 | $this->_connection or $this->connect(); 155 | 156 | if (Kohana::$profiling) 157 | { 158 | // Benchmark this query for the current instance 159 | $benchmark = Profiler::start("Database ({$this->_instance})", $sql); 160 | } 161 | 162 | // Execute the query 163 | if (($result = $this->_connection->query($sql)) === FALSE) 164 | { 165 | if (isset($benchmark)) 166 | { 167 | // This benchmark is worthless 168 | Profiler::delete($benchmark); 169 | } 170 | 171 | throw new Database_Exception(':error [ :query ]', array( 172 | ':error' => $this->_connection->error, 173 | ':query' => $sql 174 | ), $this->_connection->errno); 175 | } 176 | 177 | if (isset($benchmark)) 178 | { 179 | Profiler::stop($benchmark); 180 | } 181 | 182 | // Set the last query 183 | $this->last_query = $sql; 184 | 185 | if ($type === Database::SELECT) 186 | { 187 | // Return an iterator of results 188 | return new Database_MySQLi_Result($result, $sql, $as_object, $params); 189 | } 190 | elseif ($type === Database::INSERT) 191 | { 192 | // Return a list of insert id and rows created 193 | return array( 194 | $this->_connection->insert_id, 195 | $this->_connection->affected_rows, 196 | ); 197 | } 198 | else 199 | { 200 | // Return the number of rows affected 201 | return $this->_connection->affected_rows; 202 | } 203 | } 204 | 205 | public function datatype($type) 206 | { 207 | static $types = array 208 | ( 209 | 'blob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '65535'), 210 | 'bool' => array('type' => 'bool'), 211 | 'bigint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '18446744073709551615'), 212 | 'datetime' => array('type' => 'string'), 213 | 'decimal unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), 214 | 'double' => array('type' => 'float'), 215 | 'double precision unsigned' => array('type' => 'float', 'min' => '0'), 216 | 'double unsigned' => array('type' => 'float', 'min' => '0'), 217 | 'enum' => array('type' => 'string'), 218 | 'fixed' => array('type' => 'float', 'exact' => TRUE), 219 | 'fixed unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), 220 | 'float unsigned' => array('type' => 'float', 'min' => '0'), 221 | 'geometry' => array('type' => 'string', 'binary' => TRUE), 222 | 'int unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'), 223 | 'integer unsigned' => array('type' => 'int', 'min' => '0', 'max' => '4294967295'), 224 | 'longblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '4294967295'), 225 | 'longtext' => array('type' => 'string', 'character_maximum_length' => '4294967295'), 226 | 'mediumblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '16777215'), 227 | 'mediumint' => array('type' => 'int', 'min' => '-8388608', 'max' => '8388607'), 228 | 'mediumint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '16777215'), 229 | 'mediumtext' => array('type' => 'string', 'character_maximum_length' => '16777215'), 230 | 'national varchar' => array('type' => 'string'), 231 | 'numeric unsigned' => array('type' => 'float', 'exact' => TRUE, 'min' => '0'), 232 | 'nvarchar' => array('type' => 'string'), 233 | 'point' => array('type' => 'string', 'binary' => TRUE), 234 | 'real unsigned' => array('type' => 'float', 'min' => '0'), 235 | 'set' => array('type' => 'string'), 236 | 'smallint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '65535'), 237 | 'text' => array('type' => 'string', 'character_maximum_length' => '65535'), 238 | 'tinyblob' => array('type' => 'string', 'binary' => TRUE, 'character_maximum_length' => '255'), 239 | 'tinyint' => array('type' => 'int', 'min' => '-128', 'max' => '127'), 240 | 'tinyint unsigned' => array('type' => 'int', 'min' => '0', 'max' => '255'), 241 | 'tinytext' => array('type' => 'string', 'character_maximum_length' => '255'), 242 | 'year' => array('type' => 'string'), 243 | ); 244 | 245 | $type = str_replace(' zerofill', '', $type); 246 | 247 | if (isset($types[$type])) 248 | return $types[$type]; 249 | 250 | return parent::datatype($type); 251 | } 252 | 253 | /** 254 | * Start a SQL transaction 255 | * 256 | * @link http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html 257 | * 258 | * @param string $mode Isolation level 259 | * @return boolean 260 | */ 261 | public function begin($mode = NULL) 262 | { 263 | // Make sure the database is connected 264 | $this->_connection or $this->connect(); 265 | 266 | if ($mode AND ! $this->_connection->query("SET TRANSACTION ISOLATION LEVEL $mode")) 267 | { 268 | throw new Database_Exception(':error', array( 269 | ':error' => $this->_connection->error 270 | ), $this->_connection->errno); 271 | } 272 | 273 | return (bool) $this->_connection->query('START TRANSACTION'); 274 | } 275 | 276 | /** 277 | * Commit a SQL transaction 278 | * 279 | * @return boolean 280 | */ 281 | public function commit() 282 | { 283 | // Make sure the database is connected 284 | $this->_connection or $this->connect(); 285 | 286 | return (bool) $this->_connection->query('COMMIT'); 287 | } 288 | 289 | /** 290 | * Rollback a SQL transaction 291 | * 292 | * @return boolean 293 | */ 294 | public function rollback() 295 | { 296 | // Make sure the database is connected 297 | $this->_connection or $this->connect(); 298 | 299 | return (bool) $this->_connection->query('ROLLBACK'); 300 | } 301 | 302 | public function list_tables($like = NULL) 303 | { 304 | if (is_string($like)) 305 | { 306 | // Search for table names 307 | $result = $this->query(Database::SELECT, 'SHOW TABLES LIKE '.$this->quote($like), FALSE); 308 | } 309 | else 310 | { 311 | // Find all table names 312 | $result = $this->query(Database::SELECT, 'SHOW TABLES', FALSE); 313 | } 314 | 315 | $tables = array(); 316 | foreach ($result as $row) 317 | { 318 | $tables[] = reset($row); 319 | } 320 | 321 | return $tables; 322 | } 323 | 324 | public function list_columns($table, $like = NULL, $add_prefix = TRUE) 325 | { 326 | // Quote the table name 327 | $table = ($add_prefix === TRUE) ? $this->quote_table($table) : $table; 328 | 329 | if (is_string($like)) 330 | { 331 | // Search for column names 332 | $result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table.' LIKE '.$this->quote($like), FALSE); 333 | } 334 | else 335 | { 336 | // Find all column names 337 | $result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table, FALSE); 338 | } 339 | 340 | $count = 0; 341 | $columns = array(); 342 | foreach ($result as $row) 343 | { 344 | list($type, $length) = $this->_parse_type($row['Type']); 345 | 346 | $column = $this->datatype($type); 347 | 348 | $column['column_name'] = $row['Field']; 349 | $column['column_default'] = $row['Default']; 350 | $column['data_type'] = $type; 351 | $column['is_nullable'] = ($row['Null'] == 'YES'); 352 | $column['ordinal_position'] = ++$count; 353 | 354 | switch ($column['type']) 355 | { 356 | case 'float': 357 | if (isset($length)) 358 | { 359 | list($column['numeric_precision'], $column['numeric_scale']) = explode(',', $length); 360 | } 361 | break; 362 | case 'int': 363 | if (isset($length)) 364 | { 365 | // MySQL attribute 366 | $column['display'] = $length; 367 | } 368 | break; 369 | case 'string': 370 | switch ($column['data_type']) 371 | { 372 | case 'binary': 373 | case 'varbinary': 374 | $column['character_maximum_length'] = $length; 375 | break; 376 | case 'char': 377 | case 'varchar': 378 | $column['character_maximum_length'] = $length; 379 | case 'text': 380 | case 'tinytext': 381 | case 'mediumtext': 382 | case 'longtext': 383 | $column['collation_name'] = $row['Collation']; 384 | break; 385 | case 'enum': 386 | case 'set': 387 | $column['collation_name'] = $row['Collation']; 388 | $column['options'] = explode('\',\'', substr($length, 1, -1)); 389 | break; 390 | } 391 | break; 392 | } 393 | 394 | // MySQL attributes 395 | $column['comment'] = $row['Comment']; 396 | $column['extra'] = $row['Extra']; 397 | $column['key'] = $row['Key']; 398 | $column['privileges'] = $row['Privileges']; 399 | 400 | $columns[$row['Field']] = $column; 401 | } 402 | 403 | return $columns; 404 | } 405 | 406 | public function escape($value) 407 | { 408 | // Make sure the database is connected 409 | $this->_connection or $this->connect(); 410 | 411 | if (($value = $this->_connection->real_escape_string( (string) $value)) === FALSE) 412 | { 413 | throw new Database_Exception(':error', array( 414 | ':error' => $this->_connection->error, 415 | ), $this->_connection->errno); 416 | } 417 | 418 | // SQL standard is to use single-quotes for all values 419 | return "'$value'"; 420 | } 421 | 422 | } // End Database_MySQLi 423 | -------------------------------------------------------------------------------- /classes/Kohana/Database/MySQLi/Result.php: -------------------------------------------------------------------------------- 1 | _total_rows = $result->num_rows; 21 | } 22 | 23 | public function __destruct() 24 | { 25 | if (is_resource($this->_result)) 26 | { 27 | $this->_result->free(); 28 | } 29 | } 30 | 31 | public function seek($offset) 32 | { 33 | if ($this->offsetExists($offset) AND $this->_result->data_seek($offset)) 34 | { 35 | // Set the current row to the offset 36 | $this->_current_row = $this->_internal_row = $offset; 37 | 38 | return TRUE; 39 | } 40 | else 41 | { 42 | return FALSE; 43 | } 44 | } 45 | 46 | public function current() 47 | { 48 | if ($this->_current_row !== $this->_internal_row AND ! $this->seek($this->_current_row)) 49 | return NULL; 50 | 51 | // Increment internal row for optimization assuming rows are fetched in order 52 | $this->_internal_row++; 53 | 54 | if ($this->_as_object === TRUE) 55 | { 56 | // Return an stdClass 57 | return $this->_result->fetch_object(); 58 | } 59 | elseif (is_string($this->_as_object)) 60 | { 61 | // Return an object of given class name 62 | return $this->_result->fetch_object($this->_as_object, (array) $this->_object_params); 63 | } 64 | else 65 | { 66 | // Return an array of the row 67 | return $this->_result->fetch_assoc(); 68 | } 69 | } 70 | 71 | } // End Database_MySQLi_Result_Select 72 | -------------------------------------------------------------------------------- /classes/Kohana/Database/PDO.php: -------------------------------------------------------------------------------- 1 | _config['identifier'])) 21 | { 22 | // Allow the identifier to be overloaded per-connection 23 | $this->_identifier = (string) $this->_config['identifier']; 24 | } 25 | } 26 | 27 | public function connect() 28 | { 29 | if ($this->_connection) 30 | return; 31 | 32 | // Extract the connection parameters, adding required variabels 33 | extract($this->_config['connection'] + array( 34 | 'dsn' => '', 35 | 'username' => NULL, 36 | 'password' => NULL, 37 | 'persistent' => FALSE, 38 | )); 39 | 40 | // Clear the connection parameters for security 41 | unset($this->_config['connection']); 42 | 43 | // Force PDO to use exceptions for all errors 44 | $options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION; 45 | 46 | if ( ! empty($persistent)) 47 | { 48 | // Make the connection persistent 49 | $options[PDO::ATTR_PERSISTENT] = TRUE; 50 | } 51 | 52 | try 53 | { 54 | // Create a new PDO connection 55 | $this->_connection = new PDO($dsn, $username, $password, $options); 56 | } 57 | catch (PDOException $e) 58 | { 59 | throw new Database_Exception(':error', 60 | array(':error' => $e->getMessage()), 61 | $e->getCode()); 62 | } 63 | 64 | if ( ! empty($this->_config['charset'])) 65 | { 66 | // Set the character set 67 | $this->set_charset($this->_config['charset']); 68 | } 69 | } 70 | 71 | /** 72 | * Create or redefine a SQL aggregate function. 73 | * 74 | * [!!] Works only with SQLite 75 | * 76 | * @link http://php.net/manual/function.pdo-sqlitecreateaggregate 77 | * 78 | * @param string $name Name of the SQL function to be created or redefined 79 | * @param callback $step Called for each row of a result set 80 | * @param callback $final Called after all rows of a result set have been processed 81 | * @param integer $arguments Number of arguments that the SQL function takes 82 | * 83 | * @return boolean 84 | */ 85 | public function create_aggregate($name, $step, $final, $arguments = -1) 86 | { 87 | $this->_connection or $this->connect(); 88 | 89 | return $this->_connection->sqliteCreateAggregate( 90 | $name, $step, $final, $arguments 91 | ); 92 | } 93 | 94 | /** 95 | * Create or redefine a SQL function. 96 | * 97 | * [!!] Works only with SQLite 98 | * 99 | * @link http://php.net/manual/function.pdo-sqlitecreatefunction 100 | * 101 | * @param string $name Name of the SQL function to be created or redefined 102 | * @param callback $callback Callback which implements the SQL function 103 | * @param integer $arguments Number of arguments that the SQL function takes 104 | * 105 | * @return boolean 106 | */ 107 | public function create_function($name, $callback, $arguments = -1) 108 | { 109 | $this->_connection or $this->connect(); 110 | 111 | return $this->_connection->sqliteCreateFunction( 112 | $name, $callback, $arguments 113 | ); 114 | } 115 | 116 | public function disconnect() 117 | { 118 | // Destroy the PDO object 119 | $this->_connection = NULL; 120 | 121 | return parent::disconnect(); 122 | } 123 | 124 | public function set_charset($charset) 125 | { 126 | // Make sure the database is connected 127 | $this->_connection OR $this->connect(); 128 | 129 | // This SQL-92 syntax is not supported by all drivers 130 | $this->_connection->exec('SET NAMES '.$this->quote($charset)); 131 | } 132 | 133 | public function query($type, $sql, $as_object = FALSE, array $params = NULL) 134 | { 135 | // Make sure the database is connected 136 | $this->_connection or $this->connect(); 137 | 138 | if (Kohana::$profiling) 139 | { 140 | // Benchmark this query for the current instance 141 | $benchmark = Profiler::start("Database ({$this->_instance})", $sql); 142 | } 143 | 144 | try 145 | { 146 | $result = $this->_connection->query($sql); 147 | } 148 | catch (Exception $e) 149 | { 150 | if (isset($benchmark)) 151 | { 152 | // This benchmark is worthless 153 | Profiler::delete($benchmark); 154 | } 155 | 156 | // Convert the exception in a database exception 157 | throw new Database_Exception(':error [ :query ]', 158 | array( 159 | ':error' => $e->getMessage(), 160 | ':query' => $sql 161 | ), 162 | $e->getCode()); 163 | } 164 | 165 | if (isset($benchmark)) 166 | { 167 | Profiler::stop($benchmark); 168 | } 169 | 170 | // Set the last query 171 | $this->last_query = $sql; 172 | 173 | if ($type === Database::SELECT) 174 | { 175 | // Convert the result into an array, as PDOStatement::rowCount is not reliable 176 | if ($as_object === FALSE) 177 | { 178 | $result->setFetchMode(PDO::FETCH_ASSOC); 179 | } 180 | elseif (is_string($as_object)) 181 | { 182 | $result->setFetchMode(PDO::FETCH_CLASS, $as_object, $params); 183 | } 184 | else 185 | { 186 | $result->setFetchMode(PDO::FETCH_CLASS, 'stdClass'); 187 | } 188 | 189 | $result = $result->fetchAll(); 190 | 191 | // Return an iterator of results 192 | return new Database_Result_Cached($result, $sql, $as_object, $params); 193 | } 194 | elseif ($type === Database::INSERT) 195 | { 196 | // Return a list of insert id and rows created 197 | return array( 198 | $this->_connection->lastInsertId(), 199 | $result->rowCount(), 200 | ); 201 | } 202 | else 203 | { 204 | // Return the number of rows affected 205 | return $result->rowCount(); 206 | } 207 | } 208 | 209 | public function begin($mode = NULL) 210 | { 211 | // Make sure the database is connected 212 | $this->_connection or $this->connect(); 213 | 214 | return $this->_connection->beginTransaction(); 215 | } 216 | 217 | public function commit() 218 | { 219 | // Make sure the database is connected 220 | $this->_connection or $this->connect(); 221 | 222 | return $this->_connection->commit(); 223 | } 224 | 225 | public function rollback() 226 | { 227 | // Make sure the database is connected 228 | $this->_connection or $this->connect(); 229 | 230 | return $this->_connection->rollBack(); 231 | } 232 | 233 | public function list_tables($like = NULL) 234 | { 235 | throw new Kohana_Exception('Database method :method is not supported by :class', 236 | array(':method' => __FUNCTION__, ':class' => __CLASS__)); 237 | } 238 | 239 | public function list_columns($table, $like = NULL, $add_prefix = TRUE) 240 | { 241 | throw new Kohana_Exception('Database method :method is not supported by :class', 242 | array(':method' => __FUNCTION__, ':class' => __CLASS__)); 243 | } 244 | 245 | public function escape($value) 246 | { 247 | // Make sure the database is connected 248 | $this->_connection or $this->connect(); 249 | 250 | return $this->_connection->quote($value); 251 | } 252 | 253 | } // End Database_PDO 254 | -------------------------------------------------------------------------------- /classes/Kohana/Database/Query.php: -------------------------------------------------------------------------------- 1 | _type = $type; 44 | $this->_sql = $sql; 45 | } 46 | 47 | /** 48 | * Return the SQL query string. 49 | * 50 | * @return string 51 | */ 52 | public function __toString() 53 | { 54 | try 55 | { 56 | // Return the SQL string 57 | return $this->compile(Database::instance()); 58 | } 59 | catch (Exception $e) 60 | { 61 | return Kohana_Exception::text($e); 62 | } 63 | } 64 | 65 | /** 66 | * Get the type of the query. 67 | * 68 | * @return integer 69 | */ 70 | public function type() 71 | { 72 | return $this->_type; 73 | } 74 | 75 | /** 76 | * Enables the query to be cached for a specified amount of time. 77 | * 78 | * @param integer $lifetime number of seconds to cache, 0 deletes it from the cache 79 | * @param boolean whether or not to execute the query during a cache hit 80 | * @return $this 81 | * @uses Kohana::$cache_life 82 | */ 83 | public function cached($lifetime = NULL, $force = FALSE) 84 | { 85 | if ($lifetime === NULL) 86 | { 87 | // Use the global setting 88 | $lifetime = Kohana::$cache_life; 89 | } 90 | 91 | $this->_force_execute = $force; 92 | $this->_lifetime = $lifetime; 93 | 94 | return $this; 95 | } 96 | 97 | /** 98 | * Returns results as associative arrays 99 | * 100 | * @return $this 101 | */ 102 | public function as_assoc() 103 | { 104 | $this->_as_object = FALSE; 105 | 106 | $this->_object_params = array(); 107 | 108 | return $this; 109 | } 110 | 111 | /** 112 | * Returns results as objects 113 | * 114 | * @param string $class classname or TRUE for stdClass 115 | * @param array $params 116 | * @return $this 117 | */ 118 | public function as_object($class = TRUE, array $params = NULL) 119 | { 120 | $this->_as_object = $class; 121 | 122 | if ($params) 123 | { 124 | // Add object parameters 125 | $this->_object_params = $params; 126 | } 127 | 128 | return $this; 129 | } 130 | 131 | /** 132 | * Set the value of a parameter in the query. 133 | * 134 | * @param string $param parameter key to replace 135 | * @param mixed $value value to use 136 | * @return $this 137 | */ 138 | public function param($param, $value) 139 | { 140 | // Add or overload a new parameter 141 | $this->_parameters[$param] = $value; 142 | 143 | return $this; 144 | } 145 | 146 | /** 147 | * Bind a variable to a parameter in the query. 148 | * 149 | * @param string $param parameter key to replace 150 | * @param mixed $var variable to use 151 | * @return $this 152 | */ 153 | public function bind($param, & $var) 154 | { 155 | // Bind a value to a variable 156 | $this->_parameters[$param] =& $var; 157 | 158 | return $this; 159 | } 160 | 161 | /** 162 | * Add multiple parameters to the query. 163 | * 164 | * @param array $params list of parameters 165 | * @return $this 166 | */ 167 | public function parameters(array $params) 168 | { 169 | // Merge the new parameters in 170 | $this->_parameters = $params + $this->_parameters; 171 | 172 | return $this; 173 | } 174 | 175 | /** 176 | * Compile the SQL query and return it. Replaces any parameters with their 177 | * given values. 178 | * 179 | * @param mixed $db Database instance or name of instance 180 | * @return string 181 | */ 182 | public function compile($db = NULL) 183 | { 184 | if ( ! is_object($db)) 185 | { 186 | // Get the database instance 187 | $db = Database::instance($db); 188 | } 189 | 190 | // Import the SQL locally 191 | $sql = $this->_sql; 192 | 193 | if ( ! empty($this->_parameters)) 194 | { 195 | // Quote all of the values 196 | $values = array_map(array($db, 'quote'), $this->_parameters); 197 | 198 | // Replace the values in the SQL 199 | $sql = strtr($sql, $values); 200 | } 201 | 202 | return $sql; 203 | } 204 | 205 | /** 206 | * Execute the current query on the given database. 207 | * 208 | * @param mixed $db Database instance or name of instance 209 | * @param string result object classname, TRUE for stdClass or FALSE for array 210 | * @param array result object constructor arguments 211 | * @return object Database_Result for SELECT queries 212 | * @return mixed the insert id for INSERT queries 213 | * @return integer number of affected rows for all other queries 214 | */ 215 | public function execute($db = NULL, $as_object = NULL, $object_params = NULL) 216 | { 217 | if ( ! is_object($db)) 218 | { 219 | // Get the database instance 220 | $db = Database::instance($db); 221 | } 222 | 223 | if ($as_object === NULL) 224 | { 225 | $as_object = $this->_as_object; 226 | } 227 | 228 | if ($object_params === NULL) 229 | { 230 | $object_params = $this->_object_params; 231 | } 232 | 233 | // Compile the SQL query 234 | $sql = $this->compile($db); 235 | 236 | if ($this->_lifetime !== NULL AND $this->_type === Database::SELECT) 237 | { 238 | // Set the cache key based on the database instance name and SQL 239 | $cache_key = 'Database::query("'.$db.'", "'.$sql.'")'; 240 | 241 | // Read the cache first to delete a possible hit with lifetime <= 0 242 | if (($result = Kohana::cache($cache_key, NULL, $this->_lifetime)) !== NULL 243 | AND ! $this->_force_execute) 244 | { 245 | // Return a cached result 246 | return new Database_Result_Cached($result, $sql, $as_object, $object_params); 247 | } 248 | } 249 | 250 | // Execute the query 251 | $result = $db->query($this->_type, $sql, $as_object, $object_params); 252 | 253 | if (isset($cache_key) AND $this->_lifetime > 0) 254 | { 255 | // Cache the result array 256 | Kohana::cache($cache_key, $result->as_array(), $this->_lifetime); 257 | } 258 | 259 | return $result; 260 | } 261 | 262 | } // End Database_Query 263 | -------------------------------------------------------------------------------- /classes/Kohana/Database/Query/Builder.php: -------------------------------------------------------------------------------- 1 | compile($db); 28 | } 29 | 30 | return implode(' ', $statements); 31 | } 32 | 33 | /** 34 | * Compiles an array of conditions into an SQL partial. Used for WHERE 35 | * and HAVING. 36 | * 37 | * @param object $db Database instance 38 | * @param array $conditions condition statements 39 | * @return string 40 | */ 41 | protected function _compile_conditions(Database $db, array $conditions) 42 | { 43 | $last_condition = NULL; 44 | 45 | $sql = ''; 46 | foreach ($conditions as $group) 47 | { 48 | // Process groups of conditions 49 | foreach ($group as $logic => $condition) 50 | { 51 | if ($condition === '(') 52 | { 53 | if ( ! empty($sql) AND $last_condition !== '(') 54 | { 55 | // Include logic operator 56 | $sql .= ' '.$logic.' '; 57 | } 58 | 59 | $sql .= '('; 60 | } 61 | elseif ($condition === ')') 62 | { 63 | $sql .= ')'; 64 | } 65 | else 66 | { 67 | if ( ! empty($sql) AND $last_condition !== '(') 68 | { 69 | // Add the logic operator 70 | $sql .= ' '.$logic.' '; 71 | } 72 | 73 | // Split the condition 74 | list($column, $op, $value) = $condition; 75 | 76 | if ($value === NULL) 77 | { 78 | if ($op === '=') 79 | { 80 | // Convert "val = NULL" to "val IS NULL" 81 | $op = 'IS'; 82 | } 83 | elseif ($op === '!=' OR $op === '<>') 84 | { 85 | // Convert "val != NULL" to "valu IS NOT NULL" 86 | $op = 'IS NOT'; 87 | } 88 | } 89 | 90 | // Database operators are always uppercase 91 | $op = strtoupper($op); 92 | 93 | if ($op === 'BETWEEN' AND is_array($value)) 94 | { 95 | // BETWEEN always has exactly two arguments 96 | list($min, $max) = $value; 97 | 98 | if ((is_string($min) AND array_key_exists($min, $this->_parameters)) === FALSE) 99 | { 100 | // Quote the value, it is not a parameter 101 | $min = $db->quote($min); 102 | } 103 | 104 | if ((is_string($max) AND array_key_exists($max, $this->_parameters)) === FALSE) 105 | { 106 | // Quote the value, it is not a parameter 107 | $max = $db->quote($max); 108 | } 109 | 110 | // Quote the min and max value 111 | $value = $min.' AND '.$max; 112 | } 113 | elseif ((is_string($value) AND array_key_exists($value, $this->_parameters)) === FALSE) 114 | { 115 | // Quote the value, it is not a parameter 116 | $value = $db->quote($value); 117 | } 118 | 119 | if ($column) 120 | { 121 | if (is_array($column)) 122 | { 123 | // Use the column name 124 | $column = $db->quote_identifier(reset($column)); 125 | } 126 | else 127 | { 128 | // Apply proper quoting to the column 129 | $column = $db->quote_column($column); 130 | } 131 | } 132 | 133 | // Append the statement to the query 134 | $sql .= trim($column.' '.$op.' '.$value); 135 | } 136 | 137 | $last_condition = $condition; 138 | } 139 | } 140 | 141 | return $sql; 142 | } 143 | 144 | /** 145 | * Compiles an array of set values into an SQL partial. Used for UPDATE. 146 | * 147 | * @param object $db Database instance 148 | * @param array $values updated values 149 | * @return string 150 | */ 151 | protected function _compile_set(Database $db, array $values) 152 | { 153 | $set = array(); 154 | foreach ($values as $group) 155 | { 156 | // Split the set 157 | list ($column, $value) = $group; 158 | 159 | // Quote the column name 160 | $column = $db->quote_column($column); 161 | 162 | if ((is_string($value) AND array_key_exists($value, $this->_parameters)) === FALSE) 163 | { 164 | // Quote the value, it is not a parameter 165 | $value = $db->quote($value); 166 | } 167 | 168 | $set[$column] = $column.' = '.$value; 169 | } 170 | 171 | return implode(', ', $set); 172 | } 173 | 174 | /** 175 | * Compiles an array of GROUP BY columns into an SQL partial. 176 | * 177 | * @param object $db Database instance 178 | * @param array $columns 179 | * @return string 180 | */ 181 | protected function _compile_group_by(Database $db, array $columns) 182 | { 183 | $group = array(); 184 | 185 | foreach ($columns as $column) 186 | { 187 | if (is_array($column)) 188 | { 189 | // Use the column alias 190 | $column = $db->quote_identifier(end($column)); 191 | } 192 | else 193 | { 194 | // Apply proper quoting to the column 195 | $column = $db->quote_column($column); 196 | } 197 | 198 | $group[] = $column; 199 | } 200 | 201 | return 'GROUP BY '.implode(', ', $group); 202 | } 203 | 204 | /** 205 | * Compiles an array of ORDER BY statements into an SQL partial. 206 | * 207 | * @param object $db Database instance 208 | * @param array $columns sorting columns 209 | * @return string 210 | */ 211 | protected function _compile_order_by(Database $db, array $columns) 212 | { 213 | $sort = array(); 214 | foreach ($columns as $group) 215 | { 216 | list ($column, $direction) = $group; 217 | 218 | if (is_array($column)) 219 | { 220 | // Use the column alias 221 | $column = $db->quote_identifier(end($column)); 222 | } 223 | else 224 | { 225 | // Apply proper quoting to the column 226 | $column = $db->quote_column($column); 227 | } 228 | 229 | if ($direction) 230 | { 231 | // Make the direction uppercase 232 | $direction = ' '.strtoupper($direction); 233 | } 234 | 235 | $sort[] = $column.$direction; 236 | } 237 | 238 | return 'ORDER BY '.implode(', ', $sort); 239 | } 240 | 241 | /** 242 | * Reset the current builder status. 243 | * 244 | * @return $this 245 | */ 246 | abstract public function reset(); 247 | 248 | } // End Database_Query_Builder 249 | -------------------------------------------------------------------------------- /classes/Kohana/Database/Query/Builder/Delete.php: -------------------------------------------------------------------------------- 1 | _table = $table; 28 | } 29 | 30 | // Start the query with no SQL 31 | return parent::__construct(Database::DELETE, ''); 32 | } 33 | 34 | /** 35 | * Sets the table to delete from. 36 | * 37 | * @param mixed $table table name or array($table, $alias) or object 38 | * @return $this 39 | */ 40 | public function table($table) 41 | { 42 | $this->_table = $table; 43 | 44 | return $this; 45 | } 46 | 47 | /** 48 | * Compile the SQL query and return it. 49 | * 50 | * @param mixed $db Database instance or name of instance 51 | * @return string 52 | */ 53 | public function compile($db = NULL) 54 | { 55 | if ( ! is_object($db)) 56 | { 57 | // Get the database instance 58 | $db = Database::instance($db); 59 | } 60 | 61 | // Start a deletion query 62 | $query = 'DELETE FROM '.$db->quote_table($this->_table); 63 | 64 | if ( ! empty($this->_where)) 65 | { 66 | // Add deletion conditions 67 | $query .= ' WHERE '.$this->_compile_conditions($db, $this->_where); 68 | } 69 | 70 | if ( ! empty($this->_order_by)) 71 | { 72 | // Add sorting 73 | $query .= ' '.$this->_compile_order_by($db, $this->_order_by); 74 | } 75 | 76 | if ($this->_limit !== NULL) 77 | { 78 | // Add limiting 79 | $query .= ' LIMIT '.$this->_limit; 80 | } 81 | 82 | $this->_sql = $query; 83 | 84 | return parent::compile($db); 85 | } 86 | 87 | public function reset() 88 | { 89 | $this->_table = NULL; 90 | $this->_where = array(); 91 | 92 | $this->_parameters = array(); 93 | 94 | $this->_sql = NULL; 95 | 96 | return $this; 97 | } 98 | 99 | } // End Database_Query_Builder_Delete 100 | -------------------------------------------------------------------------------- /classes/Kohana/Database/Query/Builder/Insert.php: -------------------------------------------------------------------------------- 1 | table($table); 35 | } 36 | 37 | if ($columns) 38 | { 39 | // Set the column names 40 | $this->_columns = $columns; 41 | } 42 | 43 | // Start the query with no SQL 44 | return parent::__construct(Database::INSERT, ''); 45 | } 46 | 47 | /** 48 | * Sets the table to insert into. 49 | * 50 | * @param string $table table name 51 | * @return $this 52 | */ 53 | public function table($table) 54 | { 55 | if ( ! is_string($table)) 56 | throw new Kohana_Exception('INSERT INTO syntax does not allow table aliasing'); 57 | 58 | $this->_table = $table; 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * Set the columns that will be inserted. 65 | * 66 | * @param array $columns column names 67 | * @return $this 68 | */ 69 | public function columns(array $columns) 70 | { 71 | $this->_columns = $columns; 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * Adds or overwrites values. Multiple value sets can be added. 78 | * 79 | * @param array $values values list 80 | * @param ... 81 | * @return $this 82 | */ 83 | public function values(array $values) 84 | { 85 | if ( ! is_array($this->_values)) 86 | { 87 | throw new Kohana_Exception('INSERT INTO ... SELECT statements cannot be combined with INSERT INTO ... VALUES'); 88 | } 89 | 90 | // Get all of the passed values 91 | $values = func_get_args(); 92 | 93 | foreach ($values as $value) 94 | { 95 | $this->_values[] = $value; 96 | } 97 | 98 | return $this; 99 | } 100 | 101 | /** 102 | * Use a sub-query to for the inserted values. 103 | * 104 | * @param object $query Database_Query of SELECT type 105 | * @return $this 106 | */ 107 | public function select(Database_Query $query) 108 | { 109 | if ($query->type() !== Database::SELECT) 110 | { 111 | throw new Kohana_Exception('Only SELECT queries can be combined with INSERT queries'); 112 | } 113 | 114 | $this->_values = $query; 115 | 116 | return $this; 117 | } 118 | 119 | /** 120 | * Compile the SQL query and return it. 121 | * 122 | * @param mixed $db Database instance or name of instance 123 | * @return string 124 | */ 125 | public function compile($db = NULL) 126 | { 127 | if ( ! is_object($db)) 128 | { 129 | // Get the database instance 130 | $db = Database::instance($db); 131 | } 132 | 133 | // Start an insertion query 134 | $query = 'INSERT INTO '.$db->quote_table($this->_table); 135 | 136 | // Add the column names 137 | $query .= ' ('.implode(', ', array_map(array($db, 'quote_column'), $this->_columns)).') '; 138 | 139 | if (is_array($this->_values)) 140 | { 141 | // Callback for quoting values 142 | $quote = array($db, 'quote'); 143 | 144 | $groups = array(); 145 | foreach ($this->_values as $group) 146 | { 147 | foreach ($group as $offset => $value) 148 | { 149 | if ((is_string($value) AND array_key_exists($value, $this->_parameters)) === FALSE) 150 | { 151 | // Quote the value, it is not a parameter 152 | $group[$offset] = $db->quote($value); 153 | } 154 | } 155 | 156 | $groups[] = '('.implode(', ', $group).')'; 157 | } 158 | 159 | // Add the values 160 | $query .= 'VALUES '.implode(', ', $groups); 161 | } 162 | else 163 | { 164 | // Add the sub-query 165 | $query .= (string) $this->_values; 166 | } 167 | 168 | $this->_sql = $query; 169 | 170 | return parent::compile($db);; 171 | } 172 | 173 | public function reset() 174 | { 175 | $this->_table = NULL; 176 | 177 | $this->_columns = 178 | $this->_values = array(); 179 | 180 | $this->_parameters = array(); 181 | 182 | $this->_sql = NULL; 183 | 184 | return $this; 185 | } 186 | 187 | } // End Database_Query_Builder_Insert 188 | -------------------------------------------------------------------------------- /classes/Kohana/Database/Query/Builder/Join.php: -------------------------------------------------------------------------------- 1 | _table = $table; 37 | 38 | if ($type !== NULL) 39 | { 40 | // Set the JOIN type 41 | $this->_type = (string) $type; 42 | } 43 | } 44 | 45 | /** 46 | * Adds a new condition for joining. 47 | * 48 | * @param mixed $c1 column name or array($column, $alias) or object 49 | * @param string $op logic operator 50 | * @param mixed $c2 column name or array($column, $alias) or object 51 | * @return $this 52 | */ 53 | public function on($c1, $op, $c2) 54 | { 55 | if ( ! empty($this->_using)) 56 | { 57 | throw new Kohana_Exception('JOIN ... ON ... cannot be combined with JOIN ... USING ...'); 58 | } 59 | 60 | $this->_on[] = array($c1, $op, $c2); 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * Adds a new condition for joining. 67 | * 68 | * @param string $columns column name 69 | * @return $this 70 | */ 71 | public function using($columns) 72 | { 73 | if ( ! empty($this->_on)) 74 | { 75 | throw new Kohana_Exception('JOIN ... ON ... cannot be combined with JOIN ... USING ...'); 76 | } 77 | 78 | $columns = func_get_args(); 79 | 80 | $this->_using = array_merge($this->_using, $columns); 81 | 82 | return $this; 83 | } 84 | 85 | /** 86 | * Compile the SQL partial for a JOIN statement and return it. 87 | * 88 | * @param mixed $db Database instance or name of instance 89 | * @return string 90 | */ 91 | public function compile($db = NULL) 92 | { 93 | if ( ! is_object($db)) 94 | { 95 | // Get the database instance 96 | $db = Database::instance($db); 97 | } 98 | 99 | if ($this->_type) 100 | { 101 | $sql = strtoupper($this->_type).' JOIN'; 102 | } 103 | else 104 | { 105 | $sql = 'JOIN'; 106 | } 107 | 108 | // Quote the table name that is being joined 109 | $sql .= ' '.$db->quote_table($this->_table); 110 | 111 | if ( ! empty($this->_using)) 112 | { 113 | // Quote and concat the columns 114 | $sql .= ' USING ('.implode(', ', array_map(array($db, 'quote_column'), $this->_using)).')'; 115 | } 116 | else 117 | { 118 | $conditions = array(); 119 | foreach ($this->_on as $condition) 120 | { 121 | // Split the condition 122 | list($c1, $op, $c2) = $condition; 123 | 124 | if ($op) 125 | { 126 | // Make the operator uppercase and spaced 127 | $op = ' '.strtoupper($op); 128 | } 129 | 130 | // Quote each of the columns used for the condition 131 | $conditions[] = $db->quote_column($c1).$op.' '.$db->quote_column($c2); 132 | } 133 | 134 | // Concat the conditions "... AND ..." 135 | $sql .= ' ON ('.implode(' AND ', $conditions).')'; 136 | } 137 | 138 | return $sql; 139 | } 140 | 141 | public function reset() 142 | { 143 | $this->_type = 144 | $this->_table = NULL; 145 | 146 | $this->_on = array(); 147 | } 148 | 149 | } // End Database_Query_Builder_Join 150 | -------------------------------------------------------------------------------- /classes/Kohana/Database/Query/Builder/Select.php: -------------------------------------------------------------------------------- 1 | _select = $columns; 52 | } 53 | 54 | // Start the query with no actual SQL statement 55 | parent::__construct(Database::SELECT, ''); 56 | } 57 | 58 | /** 59 | * Enables or disables selecting only unique columns using "SELECT DISTINCT" 60 | * 61 | * @param boolean $value enable or disable distinct columns 62 | * @return $this 63 | */ 64 | public function distinct($value) 65 | { 66 | $this->_distinct = (bool) $value; 67 | 68 | return $this; 69 | } 70 | 71 | /** 72 | * Choose the columns to select from. 73 | * 74 | * @param mixed $columns column name or array($column, $alias) or object 75 | * @return $this 76 | */ 77 | public function select($columns = NULL) 78 | { 79 | $columns = func_get_args(); 80 | 81 | $this->_select = array_merge($this->_select, $columns); 82 | 83 | return $this; 84 | } 85 | 86 | /** 87 | * Choose the columns to select from, using an array. 88 | * 89 | * @param array $columns list of column names or aliases 90 | * @return $this 91 | */ 92 | public function select_array(array $columns) 93 | { 94 | $this->_select = array_merge($this->_select, $columns); 95 | 96 | return $this; 97 | } 98 | 99 | /** 100 | * Choose the tables to select "FROM ..." 101 | * 102 | * @param mixed $table table name or array($table, $alias) or object 103 | * @return $this 104 | */ 105 | public function from($tables) 106 | { 107 | $tables = func_get_args(); 108 | 109 | $this->_from = array_merge($this->_from, $tables); 110 | 111 | return $this; 112 | } 113 | 114 | /** 115 | * Adds addition tables to "JOIN ...". 116 | * 117 | * @param mixed $table column name or array($column, $alias) or object 118 | * @param string $type join type (LEFT, RIGHT, INNER, etc) 119 | * @return $this 120 | */ 121 | public function join($table, $type = NULL) 122 | { 123 | $this->_join[] = $this->_last_join = new Database_Query_Builder_Join($table, $type); 124 | 125 | return $this; 126 | } 127 | 128 | /** 129 | * Adds "ON ..." conditions for the last created JOIN statement. 130 | * 131 | * @param mixed $c1 column name or array($column, $alias) or object 132 | * @param string $op logic operator 133 | * @param mixed $c2 column name or array($column, $alias) or object 134 | * @return $this 135 | */ 136 | public function on($c1, $op, $c2) 137 | { 138 | $this->_last_join->on($c1, $op, $c2); 139 | 140 | return $this; 141 | } 142 | 143 | /** 144 | * Adds "USING ..." conditions for the last created JOIN statement. 145 | * 146 | * @param string $columns column name 147 | * @return $this 148 | */ 149 | public function using($columns) 150 | { 151 | $columns = func_get_args(); 152 | 153 | call_user_func_array(array($this->_last_join, 'using'), $columns); 154 | 155 | return $this; 156 | } 157 | 158 | /** 159 | * Creates a "GROUP BY ..." filter. 160 | * 161 | * @param mixed $columns column name or array($column, $alias) or object 162 | * @return $this 163 | */ 164 | public function group_by($columns) 165 | { 166 | $columns = func_get_args(); 167 | 168 | $this->_group_by = array_merge($this->_group_by, $columns); 169 | 170 | return $this; 171 | } 172 | 173 | /** 174 | * Alias of and_having() 175 | * 176 | * @param mixed $column column name or array($column, $alias) or object 177 | * @param string $op logic operator 178 | * @param mixed $value column value 179 | * @return $this 180 | */ 181 | public function having($column, $op, $value = NULL) 182 | { 183 | return $this->and_having($column, $op, $value); 184 | } 185 | 186 | /** 187 | * Creates a new "AND HAVING" condition for the query. 188 | * 189 | * @param mixed $column column name or array($column, $alias) or object 190 | * @param string $op logic operator 191 | * @param mixed $value column value 192 | * @return $this 193 | */ 194 | public function and_having($column, $op, $value = NULL) 195 | { 196 | $this->_having[] = array('AND' => array($column, $op, $value)); 197 | 198 | return $this; 199 | } 200 | 201 | /** 202 | * Creates a new "OR HAVING" condition for the query. 203 | * 204 | * @param mixed $column column name or array($column, $alias) or object 205 | * @param string $op logic operator 206 | * @param mixed $value column value 207 | * @return $this 208 | */ 209 | public function or_having($column, $op, $value = NULL) 210 | { 211 | $this->_having[] = array('OR' => array($column, $op, $value)); 212 | 213 | return $this; 214 | } 215 | 216 | /** 217 | * Alias of and_having_open() 218 | * 219 | * @return $this 220 | */ 221 | public function having_open() 222 | { 223 | return $this->and_having_open(); 224 | } 225 | 226 | /** 227 | * Opens a new "AND HAVING (...)" grouping. 228 | * 229 | * @return $this 230 | */ 231 | public function and_having_open() 232 | { 233 | $this->_having[] = array('AND' => '('); 234 | 235 | return $this; 236 | } 237 | 238 | /** 239 | * Opens a new "OR HAVING (...)" grouping. 240 | * 241 | * @return $this 242 | */ 243 | public function or_having_open() 244 | { 245 | $this->_having[] = array('OR' => '('); 246 | 247 | return $this; 248 | } 249 | 250 | /** 251 | * Closes an open "AND HAVING (...)" grouping. 252 | * 253 | * @return $this 254 | */ 255 | public function having_close() 256 | { 257 | return $this->and_having_close(); 258 | } 259 | 260 | /** 261 | * Closes an open "AND HAVING (...)" grouping. 262 | * 263 | * @return $this 264 | */ 265 | public function and_having_close() 266 | { 267 | $this->_having[] = array('AND' => ')'); 268 | 269 | return $this; 270 | } 271 | 272 | /** 273 | * Closes an open "OR HAVING (...)" grouping. 274 | * 275 | * @return $this 276 | */ 277 | public function or_having_close() 278 | { 279 | $this->_having[] = array('OR' => ')'); 280 | 281 | return $this; 282 | } 283 | 284 | /** 285 | * Adds an other UNION clause. 286 | * 287 | * @param mixed $select if string, it must be the name of a table. Else 288 | * must be an instance of Database_Query_Builder_Select 289 | * @param boolean $all decides if it's an UNION or UNION ALL clause 290 | * @return $this 291 | */ 292 | public function union($select, $all = TRUE) 293 | { 294 | if (is_string($select)) 295 | { 296 | $select = DB::select()->from($select); 297 | } 298 | if ( ! $select instanceof Database_Query_Builder_Select) 299 | throw new Kohana_Exception('first parameter must be a string or an instance of Database_Query_Builder_Select'); 300 | $this->_union []= array('select' => $select, 'all' => $all); 301 | return $this; 302 | } 303 | 304 | /** 305 | * Start returning results after "OFFSET ..." 306 | * 307 | * @param integer $number starting result number or NULL to reset 308 | * @return $this 309 | */ 310 | public function offset($number) 311 | { 312 | $this->_offset = ($number === NULL) ? NULL : (int) $number; 313 | 314 | return $this; 315 | } 316 | 317 | /** 318 | * Compile the SQL query and return it. 319 | * 320 | * @param mixed $db Database instance or name of instance 321 | * @return string 322 | */ 323 | public function compile($db = NULL) 324 | { 325 | if ( ! is_object($db)) 326 | { 327 | // Get the database instance 328 | $db = Database::instance($db); 329 | } 330 | 331 | // Callback to quote columns 332 | $quote_column = array($db, 'quote_column'); 333 | 334 | // Callback to quote tables 335 | $quote_table = array($db, 'quote_table'); 336 | 337 | // Start a selection query 338 | $query = 'SELECT '; 339 | 340 | if ($this->_distinct === TRUE) 341 | { 342 | // Select only unique results 343 | $query .= 'DISTINCT '; 344 | } 345 | 346 | if (empty($this->_select)) 347 | { 348 | // Select all columns 349 | $query .= '*'; 350 | } 351 | else 352 | { 353 | // Select all columns 354 | $query .= implode(', ', array_unique(array_map($quote_column, $this->_select))); 355 | } 356 | 357 | if ( ! empty($this->_from)) 358 | { 359 | // Set tables to select from 360 | $query .= ' FROM '.implode(', ', array_unique(array_map($quote_table, $this->_from))); 361 | } 362 | 363 | if ( ! empty($this->_join)) 364 | { 365 | // Add tables to join 366 | $query .= ' '.$this->_compile_join($db, $this->_join); 367 | } 368 | 369 | if ( ! empty($this->_where)) 370 | { 371 | // Add selection conditions 372 | $query .= ' WHERE '.$this->_compile_conditions($db, $this->_where); 373 | } 374 | 375 | if ( ! empty($this->_group_by)) 376 | { 377 | // Add grouping 378 | $query .= ' '.$this->_compile_group_by($db, $this->_group_by); 379 | } 380 | 381 | if ( ! empty($this->_having)) 382 | { 383 | // Add filtering conditions 384 | $query .= ' HAVING '.$this->_compile_conditions($db, $this->_having); 385 | } 386 | 387 | if ( ! empty($this->_order_by)) 388 | { 389 | // Add sorting 390 | $query .= ' '.$this->_compile_order_by($db, $this->_order_by); 391 | } 392 | 393 | if ($this->_limit !== NULL) 394 | { 395 | // Add limiting 396 | $query .= ' LIMIT '.$this->_limit; 397 | } 398 | 399 | if ($this->_offset !== NULL) 400 | { 401 | // Add offsets 402 | $query .= ' OFFSET '.$this->_offset; 403 | } 404 | 405 | if ( ! empty($this->_union)) 406 | { 407 | $query = '('.$query.')'; 408 | foreach ($this->_union as $u) { 409 | $query .= ' UNION '; 410 | if ($u['all'] === TRUE) 411 | { 412 | $query .= 'ALL '; 413 | } 414 | $query .= '('.$u['select']->compile($db).')'; 415 | } 416 | } 417 | 418 | $this->_sql = $query; 419 | 420 | return parent::compile($db); 421 | } 422 | 423 | public function reset() 424 | { 425 | $this->_select = 426 | $this->_from = 427 | $this->_join = 428 | $this->_where = 429 | $this->_group_by = 430 | $this->_having = 431 | $this->_order_by = 432 | $this->_union = array(); 433 | 434 | $this->_distinct = FALSE; 435 | 436 | $this->_limit = 437 | $this->_offset = 438 | $this->_last_join = NULL; 439 | 440 | $this->_parameters = array(); 441 | 442 | $this->_sql = NULL; 443 | 444 | return $this; 445 | } 446 | 447 | } // End Database_Query_Select 448 | -------------------------------------------------------------------------------- /classes/Kohana/Database/Query/Builder/Update.php: -------------------------------------------------------------------------------- 1 | _table = $table; 31 | } 32 | 33 | // Start the query with no SQL 34 | return parent::__construct(Database::UPDATE, ''); 35 | } 36 | 37 | /** 38 | * Sets the table to update. 39 | * 40 | * @param mixed $table table name or array($table, $alias) or object 41 | * @return $this 42 | */ 43 | public function table($table) 44 | { 45 | $this->_table = $table; 46 | 47 | return $this; 48 | } 49 | 50 | /** 51 | * Set the values to update with an associative array. 52 | * 53 | * @param array $pairs associative (column => value) list 54 | * @return $this 55 | */ 56 | public function set(array $pairs) 57 | { 58 | foreach ($pairs as $column => $value) 59 | { 60 | $this->_set[] = array($column, $value); 61 | } 62 | 63 | return $this; 64 | } 65 | 66 | /** 67 | * Set the value of a single column. 68 | * 69 | * @param mixed $column table name or array($table, $alias) or object 70 | * @param mixed $value column value 71 | * @return $this 72 | */ 73 | public function value($column, $value) 74 | { 75 | $this->_set[] = array($column, $value); 76 | 77 | return $this; 78 | } 79 | 80 | /** 81 | * Compile the SQL query and return it. 82 | * 83 | * @param mixed $db Database instance or name of instance 84 | * @return string 85 | */ 86 | public function compile($db = NULL) 87 | { 88 | if ( ! is_object($db)) 89 | { 90 | // Get the database instance 91 | $db = Database::instance($db); 92 | } 93 | 94 | // Start an update query 95 | $query = 'UPDATE '.$db->quote_table($this->_table); 96 | 97 | // Add the columns to update 98 | $query .= ' SET '.$this->_compile_set($db, $this->_set); 99 | 100 | if ( ! empty($this->_where)) 101 | { 102 | // Add selection conditions 103 | $query .= ' WHERE '.$this->_compile_conditions($db, $this->_where); 104 | } 105 | 106 | if ( ! empty($this->_order_by)) 107 | { 108 | // Add sorting 109 | $query .= ' '.$this->_compile_order_by($db, $this->_order_by); 110 | } 111 | 112 | if ($this->_limit !== NULL) 113 | { 114 | // Add limiting 115 | $query .= ' LIMIT '.$this->_limit; 116 | } 117 | 118 | $this->_sql = $query; 119 | 120 | return parent::compile($db); 121 | } 122 | 123 | public function reset() 124 | { 125 | $this->_table = NULL; 126 | 127 | $this->_set = 128 | $this->_where = array(); 129 | 130 | $this->_limit = NULL; 131 | 132 | $this->_parameters = array(); 133 | 134 | $this->_sql = NULL; 135 | 136 | return $this; 137 | } 138 | 139 | 140 | } // End Database_Query_Builder_Update 141 | -------------------------------------------------------------------------------- /classes/Kohana/Database/Query/Builder/Where.php: -------------------------------------------------------------------------------- 1 | and_where($column, $op, $value); 33 | } 34 | 35 | /** 36 | * Creates a new "AND WHERE" condition for the query. 37 | * 38 | * @param mixed $column column name or array($column, $alias) or object 39 | * @param string $op logic operator 40 | * @param mixed $value column value 41 | * @return $this 42 | */ 43 | public function and_where($column, $op, $value) 44 | { 45 | $this->_where[] = array('AND' => array($column, $op, $value)); 46 | 47 | return $this; 48 | } 49 | 50 | /** 51 | * Creates a new "OR WHERE" condition for the query. 52 | * 53 | * @param mixed $column column name or array($column, $alias) or object 54 | * @param string $op logic operator 55 | * @param mixed $value column value 56 | * @return $this 57 | */ 58 | public function or_where($column, $op, $value) 59 | { 60 | $this->_where[] = array('OR' => array($column, $op, $value)); 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * Alias of and_where_open() 67 | * 68 | * @return $this 69 | */ 70 | public function where_open() 71 | { 72 | return $this->and_where_open(); 73 | } 74 | 75 | /** 76 | * Opens a new "AND WHERE (...)" grouping. 77 | * 78 | * @return $this 79 | */ 80 | public function and_where_open() 81 | { 82 | $this->_where[] = array('AND' => '('); 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * Opens a new "OR WHERE (...)" grouping. 89 | * 90 | * @return $this 91 | */ 92 | public function or_where_open() 93 | { 94 | $this->_where[] = array('OR' => '('); 95 | 96 | return $this; 97 | } 98 | 99 | /** 100 | * Closes an open "WHERE (...)" grouping. 101 | * 102 | * @return $this 103 | */ 104 | public function where_close() 105 | { 106 | return $this->and_where_close(); 107 | } 108 | 109 | /** 110 | * Closes an open "WHERE (...)" grouping or removes the grouping when it is 111 | * empty. 112 | * 113 | * @return $this 114 | */ 115 | public function where_close_empty() 116 | { 117 | $group = end($this->_where); 118 | 119 | if ($group AND reset($group) === '(') 120 | { 121 | array_pop($this->_where); 122 | 123 | return $this; 124 | } 125 | 126 | return $this->where_close(); 127 | } 128 | 129 | /** 130 | * Closes an open "WHERE (...)" grouping. 131 | * 132 | * @return $this 133 | */ 134 | public function and_where_close() 135 | { 136 | $this->_where[] = array('AND' => ')'); 137 | 138 | return $this; 139 | } 140 | 141 | /** 142 | * Closes an open "WHERE (...)" grouping. 143 | * 144 | * @return $this 145 | */ 146 | public function or_where_close() 147 | { 148 | $this->_where[] = array('OR' => ')'); 149 | 150 | return $this; 151 | } 152 | 153 | /** 154 | * Applies sorting with "ORDER BY ..." 155 | * 156 | * @param mixed $column column name or array($column, $alias) or object 157 | * @param string $direction direction of sorting 158 | * @return $this 159 | */ 160 | public function order_by($column, $direction = NULL) 161 | { 162 | $this->_order_by[] = array($column, $direction); 163 | 164 | return $this; 165 | } 166 | 167 | /** 168 | * Return up to "LIMIT ..." results 169 | * 170 | * @param integer $number maximum results to return or NULL to reset 171 | * @return $this 172 | */ 173 | public function limit($number) 174 | { 175 | $this->_limit = ($number === NULL) ? NULL : (int) $number; 176 | 177 | return $this; 178 | } 179 | 180 | } // End Database_Query_Builder_Where 181 | -------------------------------------------------------------------------------- /classes/Kohana/Database/Result.php: -------------------------------------------------------------------------------- 1 | _result = $result; 42 | 43 | // Store the SQL locally 44 | $this->_query = $sql; 45 | 46 | if (is_object($as_object)) 47 | { 48 | // Get the object class name 49 | $as_object = get_class($as_object); 50 | } 51 | 52 | // Results as objects or associative arrays 53 | $this->_as_object = $as_object; 54 | 55 | if ($params) 56 | { 57 | // Object constructor params 58 | $this->_object_params = $params; 59 | } 60 | } 61 | 62 | /** 63 | * Result destruction cleans up all open result sets. 64 | * 65 | * @return void 66 | */ 67 | abstract public function __destruct(); 68 | 69 | /** 70 | * Get a cached database result from the current result iterator. 71 | * 72 | * $cachable = serialize($result->cached()); 73 | * 74 | * @return Database_Result_Cached 75 | * @since 3.0.5 76 | */ 77 | public function cached() 78 | { 79 | return new Database_Result_Cached($this->as_array(), $this->_query, $this->_as_object); 80 | } 81 | 82 | /** 83 | * Return all of the rows in the result as an array. 84 | * 85 | * // Indexed array of all rows 86 | * $rows = $result->as_array(); 87 | * 88 | * // Associative array of rows by "id" 89 | * $rows = $result->as_array('id'); 90 | * 91 | * // Associative array of rows, "id" => "name" 92 | * $rows = $result->as_array('id', 'name'); 93 | * 94 | * @param string $key column for associative keys 95 | * @param string $value column for values 96 | * @return array 97 | */ 98 | public function as_array($key = NULL, $value = NULL) 99 | { 100 | $results = array(); 101 | 102 | if ($key === NULL AND $value === NULL) 103 | { 104 | // Indexed rows 105 | 106 | foreach ($this as $row) 107 | { 108 | $results[] = $row; 109 | } 110 | } 111 | elseif ($key === NULL) 112 | { 113 | // Indexed columns 114 | 115 | if ($this->_as_object) 116 | { 117 | foreach ($this as $row) 118 | { 119 | $results[] = $row->$value; 120 | } 121 | } 122 | else 123 | { 124 | foreach ($this as $row) 125 | { 126 | $results[] = $row[$value]; 127 | } 128 | } 129 | } 130 | elseif ($value === NULL) 131 | { 132 | // Associative rows 133 | 134 | if ($this->_as_object) 135 | { 136 | foreach ($this as $row) 137 | { 138 | $results[$row->$key] = $row; 139 | } 140 | } 141 | else 142 | { 143 | foreach ($this as $row) 144 | { 145 | $results[$row[$key]] = $row; 146 | } 147 | } 148 | } 149 | else 150 | { 151 | // Associative columns 152 | 153 | if ($this->_as_object) 154 | { 155 | foreach ($this as $row) 156 | { 157 | $results[$row->$key] = $row->$value; 158 | } 159 | } 160 | else 161 | { 162 | foreach ($this as $row) 163 | { 164 | $results[$row[$key]] = $row[$value]; 165 | } 166 | } 167 | } 168 | 169 | $this->rewind(); 170 | 171 | return $results; 172 | } 173 | 174 | /** 175 | * Return the named column from the current row. 176 | * 177 | * // Get the "id" value 178 | * $id = $result->get('id'); 179 | * 180 | * @param string $name column to get 181 | * @param mixed $default default value if the column does not exist 182 | * @return mixed 183 | */ 184 | public function get($name, $default = NULL) 185 | { 186 | $row = $this->current(); 187 | 188 | if ($this->_as_object) 189 | { 190 | if (isset($row->$name)) 191 | return $row->$name; 192 | } 193 | else 194 | { 195 | if (isset($row[$name])) 196 | return $row[$name]; 197 | } 198 | 199 | return $default; 200 | } 201 | 202 | /** 203 | * Implements [Countable::count], returns the total number of rows. 204 | * 205 | * echo count($result); 206 | * 207 | * @return integer 208 | */ 209 | public function count() 210 | { 211 | return $this->_total_rows; 212 | } 213 | 214 | /** 215 | * Implements [ArrayAccess::offsetExists], determines if row exists. 216 | * 217 | * if (isset($result[10])) 218 | * { 219 | * // Row 10 exists 220 | * } 221 | * 222 | * @param int $offset 223 | * @return boolean 224 | */ 225 | public function offsetExists($offset) 226 | { 227 | return ($offset >= 0 AND $offset < $this->_total_rows); 228 | } 229 | 230 | /** 231 | * Implements [ArrayAccess::offsetGet], gets a given row. 232 | * 233 | * $row = $result[10]; 234 | * 235 | * @param int $offset 236 | * @return mixed 237 | */ 238 | public function offsetGet($offset) 239 | { 240 | if ( ! $this->seek($offset)) 241 | return NULL; 242 | 243 | return $this->current(); 244 | } 245 | 246 | /** 247 | * Implements [ArrayAccess::offsetSet], throws an error. 248 | * 249 | * [!!] You cannot modify a database result. 250 | * 251 | * @param int $offset 252 | * @param mixed $value 253 | * @return void 254 | * @throws Kohana_Exception 255 | */ 256 | final public function offsetSet($offset, $value) 257 | { 258 | throw new Kohana_Exception('Database results are read-only'); 259 | } 260 | 261 | /** 262 | * Implements [ArrayAccess::offsetUnset], throws an error. 263 | * 264 | * [!!] You cannot modify a database result. 265 | * 266 | * @param int $offset 267 | * @return void 268 | * @throws Kohana_Exception 269 | */ 270 | final public function offsetUnset($offset) 271 | { 272 | throw new Kohana_Exception('Database results are read-only'); 273 | } 274 | 275 | /** 276 | * Implements [Iterator::key], returns the current row number. 277 | * 278 | * echo key($result); 279 | * 280 | * @return integer 281 | */ 282 | public function key() 283 | { 284 | return $this->_current_row; 285 | } 286 | 287 | /** 288 | * Implements [Iterator::next], moves to the next row. 289 | * 290 | * next($result); 291 | * 292 | * @return $this 293 | */ 294 | public function next() 295 | { 296 | ++$this->_current_row; 297 | return $this; 298 | } 299 | 300 | /** 301 | * Implements [Iterator::prev], moves to the previous row. 302 | * 303 | * prev($result); 304 | * 305 | * @return $this 306 | */ 307 | public function prev() 308 | { 309 | --$this->_current_row; 310 | return $this; 311 | } 312 | 313 | /** 314 | * Implements [Iterator::rewind], sets the current row to zero. 315 | * 316 | * rewind($result); 317 | * 318 | * @return $this 319 | */ 320 | public function rewind() 321 | { 322 | $this->_current_row = 0; 323 | return $this; 324 | } 325 | 326 | /** 327 | * Implements [Iterator::valid], checks if the current row exists. 328 | * 329 | * [!!] This method is only used internally. 330 | * 331 | * @return boolean 332 | */ 333 | public function valid() 334 | { 335 | return $this->offsetExists($this->_current_row); 336 | } 337 | 338 | } // End Database_Result 339 | -------------------------------------------------------------------------------- /classes/Kohana/Database/Result/Cached.php: -------------------------------------------------------------------------------- 1 | _total_rows = count($result); 19 | } 20 | 21 | public function __destruct() 22 | { 23 | // Cached results do not use resources 24 | } 25 | 26 | public function cached() 27 | { 28 | return $this; 29 | } 30 | 31 | public function seek($offset) 32 | { 33 | if ($this->offsetExists($offset)) 34 | { 35 | $this->_current_row = $offset; 36 | 37 | return TRUE; 38 | } 39 | else 40 | { 41 | return FALSE; 42 | } 43 | } 44 | 45 | public function current() 46 | { 47 | // Return an array of the row 48 | return $this->valid() ? $this->_result[$this->_current_row] : NULL; 49 | } 50 | 51 | } // End Database_Result_Cached 52 | -------------------------------------------------------------------------------- /classes/Kohana/Model/Database.php: -------------------------------------------------------------------------------- 1 | _db = $db; 49 | } 50 | elseif ( ! $this->_db) 51 | { 52 | // Use the default name 53 | $this->_db = Database::$default; 54 | } 55 | 56 | if (is_string($this->_db)) 57 | { 58 | // Load the database 59 | $this->_db = Database::instance($this->_db); 60 | } 61 | } 62 | 63 | } // End Model 64 | -------------------------------------------------------------------------------- /classes/Kohana/Session/Database.php: -------------------------------------------------------------------------------- 1 | 'session_id', 32 | 'last_active' => 'last_active', 33 | 'contents' => 'contents' 34 | ); 35 | 36 | // Garbage collection requests 37 | protected $_gc = 500; 38 | 39 | // The current session id 40 | protected $_session_id; 41 | 42 | // The old session id 43 | protected $_update_id; 44 | 45 | public function __construct(array $config = NULL, $id = NULL) 46 | { 47 | if ( ! isset($config['group'])) 48 | { 49 | // Use the default group 50 | $config['group'] = Database::$default; 51 | } 52 | 53 | // Load the database 54 | $this->_db = Database::instance($config['group']); 55 | 56 | if (isset($config['table'])) 57 | { 58 | // Set the table name 59 | $this->_table = (string) $config['table']; 60 | } 61 | 62 | if (isset($config['gc'])) 63 | { 64 | // Set the gc chance 65 | $this->_gc = (int) $config['gc']; 66 | } 67 | 68 | if (isset($config['columns'])) 69 | { 70 | // Overload column names 71 | $this->_columns = $config['columns']; 72 | } 73 | 74 | parent::__construct($config, $id); 75 | 76 | if (mt_rand(0, $this->_gc) === $this->_gc) 77 | { 78 | // Run garbage collection 79 | // This will average out to run once every X requests 80 | $this->_gc(); 81 | } 82 | } 83 | 84 | public function id() 85 | { 86 | return $this->_session_id; 87 | } 88 | 89 | protected function _read($id = NULL) 90 | { 91 | if ($id OR $id = Cookie::get($this->_name)) 92 | { 93 | $result = DB::select(array($this->_columns['contents'], 'contents')) 94 | ->from($this->_table) 95 | ->where($this->_columns['session_id'], '=', ':id') 96 | ->limit(1) 97 | ->param(':id', $id) 98 | ->execute($this->_db); 99 | 100 | if ($result->count()) 101 | { 102 | // Set the current session id 103 | $this->_session_id = $this->_update_id = $id; 104 | 105 | // Return the contents 106 | return $result->get('contents'); 107 | } 108 | } 109 | 110 | // Create a new session id 111 | $this->_regenerate(); 112 | 113 | return NULL; 114 | } 115 | 116 | protected function _regenerate() 117 | { 118 | // Create the query to find an ID 119 | $query = DB::select($this->_columns['session_id']) 120 | ->from($this->_table) 121 | ->where($this->_columns['session_id'], '=', ':id') 122 | ->limit(1) 123 | ->bind(':id', $id); 124 | 125 | do 126 | { 127 | // Create a new session id 128 | $id = str_replace('.', '-', uniqid(NULL, TRUE)); 129 | 130 | // Get the the id from the database 131 | $result = $query->execute($this->_db); 132 | } 133 | while ($result->count()); 134 | 135 | return $this->_session_id = $id; 136 | } 137 | 138 | protected function _write() 139 | { 140 | if ($this->_update_id === NULL) 141 | { 142 | // Insert a new row 143 | $query = DB::insert($this->_table, $this->_columns) 144 | ->values(array(':new_id', ':active', ':contents')); 145 | } 146 | else 147 | { 148 | // Update the row 149 | $query = DB::update($this->_table) 150 | ->value($this->_columns['last_active'], ':active') 151 | ->value($this->_columns['contents'], ':contents') 152 | ->where($this->_columns['session_id'], '=', ':old_id'); 153 | 154 | if ($this->_update_id !== $this->_session_id) 155 | { 156 | // Also update the session id 157 | $query->value($this->_columns['session_id'], ':new_id'); 158 | } 159 | } 160 | 161 | $query 162 | ->param(':new_id', $this->_session_id) 163 | ->param(':old_id', $this->_update_id) 164 | ->param(':active', $this->_data['last_active']) 165 | ->param(':contents', $this->__toString()); 166 | 167 | // Execute the query 168 | $query->execute($this->_db); 169 | 170 | // The update and the session id are now the same 171 | $this->_update_id = $this->_session_id; 172 | 173 | // Update the cookie with the new session id 174 | Cookie::set($this->_name, $this->_session_id, $this->_lifetime); 175 | 176 | return TRUE; 177 | } 178 | 179 | /** 180 | * @return bool 181 | */ 182 | protected function _restart() 183 | { 184 | $this->_regenerate(); 185 | 186 | return TRUE; 187 | } 188 | 189 | protected function _destroy() 190 | { 191 | if ($this->_update_id === NULL) 192 | { 193 | // Session has not been created yet 194 | return TRUE; 195 | } 196 | 197 | // Delete the current session 198 | $query = DB::delete($this->_table) 199 | ->where($this->_columns['session_id'], '=', ':id') 200 | ->param(':id', $this->_update_id); 201 | 202 | try 203 | { 204 | // Execute the query 205 | $query->execute($this->_db); 206 | 207 | // Delete the old session id 208 | $this->_update_id = NULL; 209 | 210 | // Delete the cookie 211 | Cookie::delete($this->_name); 212 | } 213 | catch (Exception $e) 214 | { 215 | // An error occurred, the session has not been deleted 216 | return FALSE; 217 | } 218 | 219 | return TRUE; 220 | } 221 | 222 | protected function _gc() 223 | { 224 | if ($this->_lifetime) 225 | { 226 | // Expire sessions when their lifetime is up 227 | $expires = $this->_lifetime; 228 | } 229 | else 230 | { 231 | // Expire sessions after one month 232 | $expires = Date::MONTH; 233 | } 234 | 235 | // Delete all sessions that have expired 236 | DB::delete($this->_table) 237 | ->where($this->_columns['last_active'], '<', ':time') 238 | ->param(':time', time() - $expires) 239 | ->execute($this->_db); 240 | } 241 | 242 | } // End Session_Database 243 | -------------------------------------------------------------------------------- /classes/Model/Database.php: -------------------------------------------------------------------------------- 1 | =3.3", 25 | "php": ">=5.3.6" 26 | }, 27 | "require-dev": { 28 | "kohana/core": "3.3.*@dev", 29 | "kohana/unittest": "3.3.*@dev", 30 | "kohana/koharness": "*@dev" 31 | }, 32 | "suggest": { 33 | "ext-mysql": "*", 34 | "ext-pdo": "*" 35 | }, 36 | "extra": { 37 | "branch-alias": { 38 | "dev-3.3/develop": "3.3.x-dev", 39 | "dev-3.4/develop": "3.4.x-dev" 40 | }, 41 | "installer-paths": { 42 | "vendor/{$vendor}/{$name}": ["type:kohana-module"] 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /config/database.php: -------------------------------------------------------------------------------- 1 | array 6 | ( 7 | 'type' => 'MySQL', 8 | 'connection' => array( 9 | /** 10 | * The following options are available for MySQL: 11 | * 12 | * string hostname server hostname, or socket 13 | * string database database name 14 | * string username database username 15 | * string password database password 16 | * boolean persistent use persistent connections? 17 | * array variables system variables as "key => value" pairs 18 | * 19 | * Ports and sockets may be appended to the hostname. 20 | */ 21 | 'hostname' => 'localhost', 22 | 'database' => 'kohana', 23 | 'username' => FALSE, 24 | 'password' => FALSE, 25 | 'persistent' => FALSE, 26 | ), 27 | 'table_prefix' => '', 28 | 'charset' => 'utf8', 29 | 'caching' => FALSE, 30 | ), 31 | 'alternate' => array( 32 | 'type' => 'PDO', 33 | 'connection' => array( 34 | /** 35 | * The following options are available for PDO: 36 | * 37 | * string dsn Data Source Name 38 | * string username database username 39 | * string password database password 40 | * boolean persistent use persistent connections? 41 | */ 42 | 'dsn' => 'mysql:host=localhost;dbname=kohana', 43 | 'username' => 'root', 44 | 'password' => 'r00tdb', 45 | 'persistent' => FALSE, 46 | ), 47 | /** 48 | * The following extra options are available for PDO: 49 | * 50 | * string identifier set the escaping identifier 51 | */ 52 | 'table_prefix' => '', 53 | 'charset' => 'utf8', 54 | 'caching' => FALSE, 55 | ), 56 | /** 57 | * MySQLi driver config information 58 | * 59 | * The following options are available for MySQLi: 60 | * 61 | * string hostname server hostname, or socket 62 | * string database database name 63 | * string username database username 64 | * string password database password 65 | * boolean persistent use persistent connections? 66 | * array ssl ssl parameters as "key => value" pairs. 67 | * Available keys: client_key_path, client_cert_path, ca_cert_path, ca_dir_path, cipher 68 | * array variables system variables as "key => value" pairs 69 | * 70 | * Ports and sockets may be appended to the hostname. 71 | * 72 | * MySQLi driver config example: 73 | * 74 | */ 75 | // 'alternate_mysqli' => array 76 | // ( 77 | // 'type' => 'MySQLi', 78 | // 'connection' => array( 79 | // 'hostname' => 'localhost', 80 | // 'database' => 'kohana', 81 | // 'username' => FALSE, 82 | // 'password' => FALSE, 83 | // 'persistent' => FALSE, 84 | // 'ssl' => NULL, 85 | // ), 86 | // 'table_prefix' => '', 87 | // 'charset' => 'utf8', 88 | // 'caching' => FALSE, 89 | // ), 90 | ); 91 | -------------------------------------------------------------------------------- /config/session.php: -------------------------------------------------------------------------------- 1 | array( 5 | /** 6 | * Database settings for session storage. 7 | * 8 | * string group configuation group name 9 | * string table session table name 10 | * integer gc number of requests before gc is invoked 11 | * columns array custom column names 12 | */ 13 | 'group' => 'default', 14 | 'table' => 'sessions', 15 | 'gc' => 500, 16 | 'columns' => array( 17 | /** 18 | * session_id: session identifier 19 | * last_active: timestamp of the last activity 20 | * contents: serialized session data 21 | */ 22 | 'session_id' => 'session_id', 23 | 'last_active' => 'last_active', 24 | 'contents' => 'contents' 25 | ), 26 | ), 27 | ); 28 | -------------------------------------------------------------------------------- /config/userguide.php: -------------------------------------------------------------------------------- 1 | array( 6 | 7 | // This should be the path to this modules userguide pages, without the 'guide/'. Ex: '/guide/modulename/' would be 'modulename' 8 | 'database' => array( 9 | 10 | // Whether this modules userguide pages should be shown 11 | 'enabled' => TRUE, 12 | 13 | // The name that should show up on the userguide index page 14 | 'name' => 'Database', 15 | 16 | // A short description of this module, shown on the index page 17 | 'description' => 'Database agnostic querying and result management.', 18 | 19 | // Copyright message, shown in the footer for this module 20 | 'copyright' => '© 2008–2012 Kohana Team', 21 | ) 22 | ) 23 | ); -------------------------------------------------------------------------------- /guide/database/config.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | The default config file is located in `MODPATH/database/config/database.php`. You should copy this file to `APPPATH/config/database.php` and make changes there, in keeping with the [cascading filesystem](../kohana/files). 4 | 5 | The database configuration file contains an array of configuration groups. The structure of each database configuration group, called an "instance", looks like this: 6 | 7 | string INSTANCE_NAME => array( 8 | 'type' => string DATABASE_TYPE, 9 | 'connection' => array CONNECTION_ARRAY, 10 | 'table_prefix' => string TABLE_PREFIX, 11 | 'charset' => string CHARACTER_SET, 12 | ), 13 | 14 | 15 | Understanding each of these settings is important. 16 | 17 | INSTANCE_NAME 18 | : Connections can be named anything you want, but you should always have at least one connection called "default". 19 | 20 | DATABASE_TYPE 21 | : One of the installed database drivers. Kohana comes with "MySQL", "MySQLi", and "PDO" drivers. Drivers must extend the Database class. This parameter is case sensitive. Note the mysql php extension used by the MySQL driver is deprecated as of PHP 5.5 and you should look to use an alternative driver. 22 | 23 | CONNECTION_ARRAY 24 | : Specific driver options for connecting to your database. (Driver options are explained [below](#connection-settings).) 25 | 26 | TABLE_PREFIX 27 | : Prefix that will be added to all table names by the [query builder](#query_building). 28 | 29 | CHARACTER_SET 30 | : The character set to use for the connection with the database. 31 | 32 | [!!] Setting Character Set won't work for PDO based connections because of incompatibility with PHP prior to 5.3.6. Use the DSN or options config instead. Example Below: 33 | 34 | return array 35 | ( 36 | 'default' => array 37 | ( 38 | 'type' => 'PDO', 39 | 'connection' => array( 40 | 'options' => array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"), 41 | ), 42 | ), 43 | ); 44 | 45 | ## Example 46 | 47 | The example file below shows 2 MySQL connections, one local and one remote. 48 | 49 | return array 50 | ( 51 | 'default' => array 52 | ( 53 | 'type' => 'MySQL', 54 | 'connection' => array( 55 | 'hostname' => 'localhost', 56 | 'username' => 'dbuser', 57 | 'password' => 'mypassword', 58 | 'persistent' => FALSE, 59 | 'database' => 'my_db_name', 60 | ), 61 | 'table_prefix' => '', 62 | 'charset' => 'utf8', 63 | ), 64 | 'remote' => array( 65 | 'type' => 'MySQL', 66 | 'connection' => array( 67 | 'hostname' => '55.55.55.55', 68 | 'username' => 'remote_user', 69 | 'password' => 'mypassword', 70 | 'persistent' => FALSE, 71 | 'database' => 'my_remote_db_name', 72 | ), 73 | 'table_prefix' => '', 74 | 'charset' => 'utf8', 75 | ), 76 | ); 77 | 78 | [!!] Note that the 'type' parameter is case sensitive (eg 'MySQL', 'PDO'). 79 | 80 | ## Connections and Instances 81 | 82 | Each configuration group is referred to as a database instance. Each instance can be accessed by calling [Database::instance]. If you don't provide a parameter, the default instance is used. 83 | 84 | // This would connect to the database defined as 'default' 85 | $default = Database::instance(); 86 | 87 | // This would connect to the database defined as 'remote' 88 | $remote = Database::instance('remote'); 89 | 90 | To disconnect the database, simply destroy the object: 91 | 92 | unset($default) 93 | 94 | // Or 95 | 96 | unset(Database::$instances['default']); 97 | 98 | If you want to disconnect all of the database instances at once: 99 | 100 | Database::$instances = array(); 101 | 102 | ## Connection Settings 103 | 104 | Every database driver has different connection settings. 105 | 106 | ### MySQL 107 | 108 | A [MySQL database](http://www.php.net/manual/en/book.mysql.php) can accept the following options in the `connection` array: 109 | 110 | Type | Option | Description | Default value 111 | ----------|------------|----------------------------| ------------------------- 112 | `string` | hostname | Hostname of the database | `localhost` 113 | `integer` | port | Port number | `NULL` 114 | `string` | socket | UNIX socket | `NULL` 115 | `string` | username | Database username | `NULL` 116 | `string` | password | Database password | `NULL` 117 | `boolean` | persistent | Persistent connections | `FALSE` 118 | `string` | database | Database name | `kohana` 119 | 120 | ### MySQLi 121 | 122 | A [MySQL database](http://php.net/manual/en/book.mysqli.php) can accept the following options in the `connection` array: 123 | 124 | Type | Option | Description | Default value 125 | ----------|------------|----------------------------| ------------------------- 126 | `string` | hostname | Hostname of the database | `localhost` 127 | `integer` | port | Port number | `NULL` 128 | `string` | socket | UNIX socket | `NULL` 129 | `string` | username | Database username | `NULL` 130 | `string` | password | Database password | `NULL` 131 | `boolean` | persistent | Persistent connections | `FALSE` 132 | `string` | database | Database name | `kohana` 133 | `array` | ssl | SSL parameters | `NULL` 134 | 135 | SSL parameters should be specified as `key` => `value` pairs. 136 | Available keys: client_key_path, client_cert_path, ca_cert_path, ca_dir_path, cipher 137 | 138 | ### PDO 139 | 140 | A [PDO database](http://php.net/manual/en/book.pdo.php) can accept these options in the `connection` array: 141 | 142 | Type | Option | Description | Default value 143 | ----------|------------|----------------------------| ------------------------- 144 | `string` | dsn | PDO data source identifier | `localhost` 145 | `array` | options | Driver-specific options | none 146 | `string` | username | Database username | `NULL` 147 | `string` | password | Database password | `NULL` 148 | `boolean` | persistent | Persistent connections | `FALSE` 149 | 150 | The connection character set should be configured using the DSN string or `options` array. 151 | 152 | [!!] If you are using PDO and are not sure what to use for the `dsn` option, review [PDO::__construct](http://php.net/pdo.construct). 153 | -------------------------------------------------------------------------------- /guide/database/examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Here are some "real world" examples of using the database library to construct your queries and use the results. 4 | 5 | ## Examples of Parameterized Statements 6 | 7 | TODO: 4-6 examples of parameterized statements of varying complexity, including a good bind() example. 8 | 9 | ## Pagination and search/filter 10 | 11 | In this example, we loop through an array of whitelisted input fields and for each allowed non-empty value we add it to the search query. We make a clone of the query and then execute that query to count the total number of results. The count is then passed to the [Pagination](../pagination) class to determine the search offset. The last few lines search with Pagination's items_per_page and offset values to return a page of results based on the current page the user is on. 12 | 13 | $query = DB::select()->from('users'); 14 | 15 | //only search for these fields 16 | $form_inputs = array('first_name', 'last_name', 'email'); 17 | foreach ($form_inputs as $name) 18 | { 19 | $value = Arr::get($_GET, $name, FALSE); 20 | if ($value !== FALSE AND $value != '') 21 | { 22 | $query->where($name, 'like', '%'.$value.'%'); 23 | } 24 | } 25 | 26 | //copy the query & execute it 27 | $pagination_query = clone $query; 28 | $count = $pagination_query->select(DB::expr('COUNT(*) AS mycount'))->execute()->get('mycount'); 29 | 30 | //pass the total item count to Pagination 31 | $config = Kohana::$config->load('pagination'); 32 | $pagination = Pagination::factory(array( 33 | 'total_items' => $count, 34 | 'current_page' => array('source' => 'route', 'key' => 'page'), 35 | 'items_per_page' => 20, 36 | 'view' => 'pagination/pretty', 37 | 'auto_hide' => TRUE, 38 | )); 39 | $page_links = $pagination->render(); 40 | 41 | //search for results starting at the offset calculated by the Pagination class 42 | $query->order_by('last_name', 'asc') 43 | ->order_by('first_name', 'asc') 44 | ->limit($pagination->items_per_page) 45 | ->offset($pagination->offset); 46 | $results = $query->execute()->as_array(); 47 | 48 | ## Having 49 | 50 | TODO: example goes here 51 | 52 | [!!] We could use more examples on this page. 53 | -------------------------------------------------------------------------------- /guide/database/index.md: -------------------------------------------------------------------------------- 1 | # Database 2 | 3 | Kohana 3.0 comes with a robust module for working with databases. By default, the database module supports drivers for [MySQL](http://php.net/mysql) and [PDO](http://php.net/pdo), but new drivers can be made for other database servers. 4 | 5 | The database module is included with the Kohana 3.0 install, but needs to be enabled before you can use it. To enable, open your `application/bootstrap.php` file and modify the call to [Kohana::modules] by including the database module like so: 6 | 7 | Kohana::modules(array( 8 | ... 9 | 'database' => MODPATH.'database', 10 | ... 11 | )); 12 | 13 | Next, you will then need to [configure](config) the database module to connect to your database. 14 | 15 | Once that is done then you can make [queries](query) and use the [results](results). 16 | 17 | The database module also provides a [config driver](../api/Kohana_Config_Database) (for storing [configuration](../kohana/files/config) in the database) and a [session driver](Session_Database). 18 | -------------------------------------------------------------------------------- /guide/database/menu.md: -------------------------------------------------------------------------------- 1 | ## [Database]() 2 | - [Configuration](config) 3 | - [Querying](query) 4 | - [Parameterized Statements](query/parameterized) 5 | - [Query Builder](query/builder) 6 | - [Results](results) 7 | - [Examples](examples) -------------------------------------------------------------------------------- /guide/database/query.md: -------------------------------------------------------------------------------- 1 | # Making Queries 2 | 3 | There are two different ways to make queries. The simplest way to make a query is to use [Database_Query], via [DB::query], to manually create queries. These queries are called [parameterized statements](query/parameterized) and allow you to set query parameters which are automatically escaped. The second way to make a query is by building the query using method calls. This is done using the [query builder](query/builder). 4 | 5 | [!!] All queries are run using the `execute` method, which accepts a [Database] object or instance name. See [Database_Query::execute] for more information. -------------------------------------------------------------------------------- /guide/database/query/builder.md: -------------------------------------------------------------------------------- 1 | # Query Builder 2 | 3 | Creating queries dynamically using objects and methods allows queries to be written very quickly in an agnostic way. Query building also adds identifier (table and column name) quoting, as well as value quoting. 4 | 5 | ## Select 6 | 7 | Each type of database query is represented by a different class, each with their own methods. For instance, to create a SELECT query, we use [DB::select] which is a shortcut to return a new [Database_Query_Builder_Select] object: 8 | 9 | $query = DB::select(); 10 | 11 | Query Builder methods return a reference to itself so that method chaining may be used. Select queries ussually require a table and they are referenced using the `from()` method. The `from()` method takes one parameter which can be the table name (string), an array of two strings (table name and alias), or an object (See Subqueries in the Advanced Queries section below). 12 | 13 | $query = DB::select()->from('users'); 14 | 15 | Limiting the results of queries is done using the `where()`, `and_where()` and `or_where()` methods. These methods take three parameters: a column, an operator, and a value. 16 | 17 | $query = DB::select()->from('users')->where('username', '=', 'john'); 18 | 19 | Multiple `where()` methods may be used to string together multiple clauses connected by the boolean operator in the method's prefix. The `where()` method is a wrapper that just calls `and_where()`. 20 | 21 | $query = DB::select()->from('users')->where('username', '=', 'john')->or_where('username', '=', 'jane'); 22 | 23 | You can use any operator you want. Examples include `IN`, `BETWEEN`, `>`, `=<`, `!=`, etc. Use an array for operators that require more than one value. 24 | 25 | $query = DB::select()->from('users')->where('logins', '<=', 1); 26 | 27 | $query = DB::select()->from('users')->where('logins', '>', 50); 28 | 29 | $query = DB::select()->from('users')->where('username', 'IN', array('john','mark','matt')); 30 | 31 | $query = DB::select()->from('users')->where('joindate', 'BETWEEN', array($then, $now)); 32 | 33 | By default, [DB::select] will select all columns (`SELECT * ...`), but you can also specify which columns you want returned by passing parameters to [DB::select]: 34 | 35 | $query = DB::select('username', 'password')->from('users')->where('username', '=', 'john'); 36 | 37 | Now take a minute to look at what this method chain is doing. First, we create a new selection object using the [DB::select] method. Next, we set table(s) using the `from()` method. Last, we search for a specific records using the `where()` method. We can display the SQL that will be executed by casting the query to a string: 38 | 39 | echo Debug::vars((string) $query); 40 | // Should display: 41 | // SELECT `username`, `password` FROM `users` WHERE `username` = 'john' 42 | 43 | Notice how the column and table names are automatically escaped, as well as the values? This is one of the key benefits of using the query builder. 44 | 45 | ### Select - AS (column aliases) 46 | 47 | It is also possible to create `AS` aliases when selecting, by passing an array as each parameter to [DB::select]: 48 | 49 | $query = DB::select(array('username', 'u'), array('password', 'p'))->from('users'); 50 | 51 | This query would generate the following SQL: 52 | 53 | SELECT `username` AS `u`, `password` AS `p` FROM `users` 54 | 55 | ### Select - DISTINCT 56 | 57 | Unique column values may be turned on or off (default) by passing TRUE or FALSE, respectively, to the `distinct()` method. 58 | 59 | $query = DB::select('username')->distinct(TRUE)->from('posts'); 60 | 61 | This query would generate the following SQL: 62 | 63 | SELECT DISTINCT `username` FROM `posts` 64 | 65 | ### Select - LIMIT & OFFSET 66 | 67 | When querying large sets of data, it is often better to limit the results and page through the data one chunk at a time. This is done using the `limit()` and `offset()` methods. 68 | 69 | $query = DB::select()->from(`posts`)->limit(10)->offset(30); 70 | 71 | This query would generate the following SQL: 72 | 73 | SELECT * FROM `posts` LIMIT 10 OFFSET 30 74 | 75 | ### Select - ORDER BY 76 | 77 | Often you will want the results in a particular order and rather than sorting the results, it's better to have the results returned to you in the correct order. You can do this by using the order_by() method. It takes the column name and an optional direction string as the parameters. Multiple `order_by()` methods can be used to add additional sorting capability. 78 | 79 | $query = DB::select()->from('posts')->order_by('published', 'DESC'); 80 | 81 | This query would generate the following SQL: 82 | 83 | SELECT * FROM `posts` ORDER BY `published` DESC 84 | 85 | [!!] For a complete list of methods available while building a select query see [Database_Query_Builder_Select]. 86 | 87 | ## Insert 88 | 89 | To create records into the database, use [DB::insert] to create an INSERT query, using `values()` to pass in the data: 90 | 91 | $query = DB::insert('users', array('username', 'password'))->values(array('fred', 'p@5sW0Rd')); 92 | 93 | This query would generate the following SQL: 94 | 95 | INSERT INTO `users` (`username`, `password`) VALUES ('fred', 'p@5sW0Rd') 96 | 97 | [!!] For a complete list of methods available while building an insert query see [Database_Query_Builder_Insert]. 98 | 99 | ## Update 100 | 101 | To modify an existing record, use [DB::update] to create an UPDATE query: 102 | 103 | $query = DB::update('users')->set(array('username' => 'jane'))->where('username', '=', 'john'); 104 | 105 | This query would generate the following SQL: 106 | 107 | UPDATE `users` SET `username` = 'jane' WHERE `username` = 'john' 108 | 109 | [!!] For a complete list of methods available while building an update query see [Database_Query_Builder_Update]. 110 | 111 | ## Delete 112 | 113 | To remove an existing record, use [DB::delete] to create a DELETE query: 114 | 115 | $query = DB::delete('users')->where('username', 'IN', array('john', 'jane')); 116 | 117 | This query would generate the following SQL: 118 | 119 | DELETE FROM `users` WHERE `username` IN ('john', 'jane') 120 | 121 | [!!] For a complete list of methods available while building a delete query see [Database_Query_Builder_Delete]. 122 | 123 | ## Advanced Queries 124 | 125 | ### Joins 126 | 127 | Multiple tables can be joined using the `join()` and `on()` methods. The `join()` method takes two parameters. The first is either a table name, an array containing the table and alias, or an object (subquery or expression). The second parameter is the join type: LEFT, RIGHT, INNER, etc. 128 | 129 | The `on()` method sets the conditions for the previous `join()` method and is very similar to the `where()` method in that it takes three parameters; left column (name or object), an operator, and the right column (name or object). Multiple `on()` methods may be used to supply multiple conditions and they will be appended with an 'AND' operator. 130 | 131 | // This query will find all the posts related to "smith" with JOIN 132 | $query = DB::select('authors.name', 'posts.content')->from('authors')->join('posts')->on('authors.id', '=', 'posts.author_id')->where('authors.name', '=', 'smith'); 133 | 134 | This query would generate the following SQL: 135 | 136 | SELECT `authors`.`name`, `posts`.`content` FROM `authors` JOIN `posts` ON (`authors`.`id` = `posts`.`author_id`) WHERE `authors`.`name` = 'smith' 137 | 138 | If you want to do a LEFT, RIGHT or INNER JOIN you would do it like this `join('colum_name', 'type_of_join')`: 139 | 140 | // This query will find all the posts related to "smith" with LEFT JOIN 141 | $query = DB::select()->from('authors')->join('posts', 'LEFT')->on('authors.id', '=', 'posts.author_id')->where('authors.name', '=', 'smith'); 142 | 143 | This query would generate the following SQL: 144 | 145 | SELECT `authors`.`name`, `posts`.`content` FROM `authors` LEFT JOIN `posts` ON (`authors`.`id` = `posts`.`author_id`) WHERE `authors`.`name` = 'smith' 146 | 147 | [!!] When joining multiple tables with similar column names, it's best to prefix the columns with the table name or table alias to avoid errors. Ambiguous column names should also be aliased so that they can be referenced easier. 148 | 149 | ### Database Functions 150 | 151 | Eventually you will probably run into a situation where you need to call `COUNT` or some other database function within your query. The query builder supports these functions using the `Database_Expression` class: 152 | 153 | $query = DB::select(array(DB::expr('COUNT(`username`)'), 'total_users'))->from('users'); 154 | 155 | This looks almost exactly the same as a standard `AS` alias, but note how the column name is put in a call to `DB::expr()`. Any time `DB::expr()` is used, the column name will **not** be escaped. This query would generate the following SQL: 156 | 157 | SELECT COUNT(`username`) AS `total_users` FROM `users` 158 | 159 | [!!] When building complex queries and you need to get a count of the total rows that will be returned, build the expression with an empty column list first. Then clone the query and add the COUNT function to one copy and the columns list to the other. This will cut down on the total lines of code and make updating the query easier. 160 | 161 | $query = DB::select()->from('users') 162 | ->join('posts')->on('posts.username', '=', 'users.username') 163 | ->where('users.active', '=', TRUE) 164 | ->where('posts.created', '>=', $yesterday); 165 | 166 | $total = clone $query; 167 | $total->select(array(DB::expr('COUNT( DISTINCT `username`)'), 'unique_users')); 168 | $query->select('posts.username')->distinct(); 169 | 170 | ### Aggregate Functions 171 | 172 | Aggregate functions like `COUNT()`, `SUM()`, `AVG()`, etc. will most likely be used with the `group_by()` and possibly the `having()` methods in order to group and filter the results on a set of columns. 173 | 174 | $query = DB::select('username', array(DB::expr('COUNT(`id`)'), 'total_posts') 175 | ->from('posts')->group_by('username')->having('total_posts', '>=', 10); 176 | 177 | This will generate the following query: 178 | 179 | SELECT `username`, COUNT(`id`) AS `total_posts` FROM `posts` GROUP BY `username` HAVING `total_posts` >= 10 180 | 181 | ### Subqueries 182 | 183 | Query Builder objects can be passed as parameters to many of the methods to create subqueries. Let's take the previous example query and pass it to a new query. 184 | 185 | $sub = DB::select('username', array(DB::expr('COUNT(`id`)'), 'total_posts') 186 | ->from('posts')->group_by('username')->having('total_posts', '>=', 10); 187 | 188 | $query = DB::select('profiles.*', 'posts.total_posts')->from('profiles') 189 | ->join(array($sub, 'posts'), 'INNER')->on('profiles.username', '=', 'posts.username'); 190 | 191 | This will generate the following query: 192 | 193 | SELECT `profiles`.*, `posts`.`total_posts` FROM `profiles` INNER JOIN 194 | ( SELECT `username`, COUNT(`id`) AS `total_posts` FROM `posts` GROUP BY `username` HAVING `total_posts` >= 10 ) AS posts 195 | ON `profiles`.`username` = `posts`.`username` 196 | 197 | Insert queries can also use a select query for the input values 198 | 199 | $sub = DB::select('username', array(DB::expr('COUNT(`id`)'), 'total_posts') 200 | ->from('posts')->group_by('username')->having('total_posts', '>=', 10); 201 | 202 | $query = DB::insert('post_totals', array('username', 'posts'))->select($sub); 203 | 204 | This will generate the following query: 205 | 206 | INSERT INTO `post_totals` (`username`, `posts`) 207 | SELECT `username`, COUNT(`id`) AS `total_posts` FROM `posts` GROUP BY `username` HAVING `total_posts` >= 10 208 | 209 | ### Boolean Operators and Nested Clauses 210 | 211 | Multiple Where and Having clauses are added to the query with Boolean operators connecting each expression. The default operator for both methods is AND which is the same as the and_ prefixed method. The OR operator can be specified by prefixing the methods with or_. Where and Having clauses can be nested or grouped by post fixing either method with _open and then followed by a method with a _close. 212 | 213 | $query = DB::select()->from('users') 214 | ->where_open() 215 | ->or_where('id', 'IN', $expired) 216 | ->and_where_open() 217 | ->where('last_login', '<=', $last_month) 218 | ->or_where('last_login', 'IS', NULL) 219 | ->and_where_close() 220 | ->where_close() 221 | ->and_where('removed','IS', NULL); 222 | 223 | This will generate the following query: 224 | 225 | SELECT * FROM `users` WHERE ( `id` IN (1, 2, 3, 5) OR ( `last_login` <= 1276020805 OR `last_login` IS NULL ) ) AND `removed` IS NULL 226 | 227 | ### Database Expressions 228 | 229 | There are cases were you need a complex expression or other database functions, which you don't want the Query Builder to try and escape. In these cases, you will need to use a database expression created with [DB::expr]. **A database expression is taken as direct input and no escaping is performed.** 230 | 231 | $query = DB::update('users')->set(array('login_count' => DB::expr('login_count + 1')))->where('id', '=', $id); 232 | 233 | This will generate the following query, assuming `$id = 45`: 234 | 235 | UPDATE `users` SET `login_count` = `login_count` + 1 WHERE `id` = 45 236 | 237 | Another example to calculate the distance of two geographical points: 238 | 239 | $query = DB::select(array(DB::expr('degrees(acos(sin(radians('.$lat.')) * sin(radians(`latitude`)) + cos(radians('.$lat.')) * cos(radians(`latitude`)) * cos(radians(abs('.$lng.' - `longitude`))))) * 69.172'), 'distance'))->from('locations'); 240 | 241 | [!!] You must validate or escape any user input inside of DB::expr as it will obviously not be escaped it for you. 242 | 243 | ## Executing 244 | 245 | Once you are done building, you can execute the query using `execute()` and use [the results](results). 246 | 247 | $result = $query->execute(); 248 | 249 | To use a different database [config group](config) pass either the name or the config object to `execute()`. 250 | 251 | $result = $query->execute('config_name') 252 | -------------------------------------------------------------------------------- /guide/database/query/parameterized.md: -------------------------------------------------------------------------------- 1 | # Parameterized Statements 2 | 3 | Using parameterized statements allows you to write SQL queries manually while still escaping the query values automatically to prevent [SQL injection](http://wikipedia.org/wiki/SQL_Injection). Creating a query is simple: 4 | 5 | $query = DB::query(Database::SELECT, 'SELECT * FROM users WHERE username = :user'); 6 | 7 | The [DB::query] method is just a shortcut that creates a new [Database_Query] class for us, to allow method chaining. The query contains a `:user` parameter, which we will get to in a second. 8 | 9 | The first parameter of [DB::query] is the type of query. It should be `Database::SELECT`, `Database::INSERT`, `Database::UPDATE`, or `Database::DELETE`. This is done for compatibility reasons for drivers, and to easily determine what `execute()` should return. 10 | 11 | The second parameter is the query itself. Rather than trying to concatenate your query and variables together, you should make use of [Database_Query::param]. This will make your queries much easier to mantain, and will escape the values to prevent [SQL injection](http://wikipedia.org/wiki/SQL_Injection). 12 | 13 | ## Parameters 14 | 15 | Our example query earlier contains a `:user` parameter, which we can assign to a value using [Database_Query::param] like so: 16 | 17 | $query->param(':user', 'john'); 18 | 19 | [!!] Parameter names can be any unique string, as they are replaced using [strtr](http://php.net/strtr). It is highly recommended to **not** use dollars signs as parameter names to prevent confusion. Colons are commonly used. 20 | 21 | You can also update the `:user` parameter by calling [Database_Query::param] again: 22 | 23 | $query->param(':user', $_GET['search']); 24 | 25 | If you want to set multiple parameters at once, you can use [Database_Query::parameters]. 26 | 27 | $query = DB::query(Database::SELECT, 'SELECT * FROM users WHERE username = :user AND status = :status'); 28 | 29 | $query->parameters(array( 30 | ':user' => 'john', 31 | ':status' => 'active', 32 | )); 33 | 34 | It is also possible to bind a parameter to a variable, using a [variable reference]((http://php.net/language.references.whatdo)). This can be extremely useful when running the same query many times: 35 | 36 | $query = DB::query(Database::INSERT, 'INSERT INTO users (username, password) VALUES (:user, :pass)') 37 | ->bind(':user', $username) 38 | ->bind(':pass', $password); 39 | 40 | foreach ($new_users as $username => $password) 41 | { 42 | $query->execute(); 43 | } 44 | 45 | In the above example, the variables `$username` and `$password` are changed for every loop of the `foreach` statement. When the parameter changes, it effectively changes the `:user` and `:pass` query parameters. Careful parameter binding can save a lot of code when it is used properly. 46 | 47 | The only difference between `param()` and `bind()` is that `bind()` passes the variable by reference rather than by assignment (copied), so future changes to the variable can be "seen" by the query. 48 | 49 | [!!] Although all parameters are escaped to prevent SQL injection, it is still a good idea to validate/sanitize your input. 50 | 51 | ## Display the raw query 52 | 53 | If you want to display the SQL that will be executed, you can simply echo the query: 54 | 55 | echo $query; 56 | // Should display: 57 | // SELECT * FROM users WHERE username = 'john' 58 | 59 | ## Executing 60 | 61 | Once you have assigned something to each of the parameters, you can execute the query using `execute()` and use [the results](results). 62 | 63 | $result = $query->execute(); 64 | 65 | To use a different database [config group](config) pass either the name or the config object to `execute()`. 66 | 67 | $result = $query->execute('config_name') -------------------------------------------------------------------------------- /guide/database/results.md: -------------------------------------------------------------------------------- 1 | # Results 2 | 3 | ## Execute 4 | 5 | Once you have a query object built, either through a parameterized statement or through the builder, you must then `execute()` the query and retrieve the results. Depending on the query type used, the results returned will vary. 6 | 7 | ## Select 8 | 9 | [DB::select] will return a [Database_Result] object which you can then iterate over. This example shows how you can iterate through the [Database_Result] using a foreach. 10 | 11 | $results = DB::select()->from('users')->where('verified', '=', 0)->execute(); 12 | foreach($results as $user) 13 | { 14 | // Send reminder email to $user['email'] 15 | echo $user['email']." needs to verify his/her account\n"; 16 | } 17 | 18 | ### Select - `as_object()` and `as_assoc()` 19 | 20 | When iterating over a result set, the default type will be an associative array with the column names or aliases as the keys. As an option, before calling `execute()`, you can specify to return the result rows as an object by using the `as_object()` method. The `as_object()` method takes one parameter, the name of the class of your choice, but will default to TRUE which uses the `stdClass`. Here is the example again using `stdClass`. 21 | 22 | $results = DB::select()->from('users')->where('verified', '=', 0)->as_object()->execute(); 23 | foreach($results as $user) 24 | { 25 | // Send reminder email to $user->email 26 | echo $user->email." needs to verify his/her account\n"; 27 | } 28 | 29 | [!!] The method `as_assoc()` will remove the object name and return the results set back to an associative array. Since this is the default, this method is seldom required. 30 | 31 | ### Select - `as_array()` 32 | 33 | Sometimes you will require the results as a pure array rather than as an object. The `Database_Result` method `as_array()` will return an array of all rows. 34 | 35 | $results = DB::select('id', 'email')->from('users')->execute(); 36 | $users = $results->as_array(); 37 | foreach($users as $user) 38 | { 39 | echo 'User ID: '.$user['id']; 40 | echo 'User Email: '.$user['email']; 41 | } 42 | 43 | It also accepts two parameters that can be very helpful: `$key` and `$value`. When passing a value to `$key` you will index the resulting array by the column specified. 44 | 45 | $results = DB::select('id', 'email')->from('users')->execute(); 46 | $users = $results->as_array('id'); 47 | foreach($users as $id => $user) 48 | { 49 | echo 'User ID: '.$id; 50 | echo 'User Email: '.$user['email']; 51 | } 52 | 53 | The second parameter, `$value`, will reference the column specified and return that value rather than the whole row. This is particularly useful when making `