1<?php defined('SYSPATH') OR die('No direct access allowed.'); 2/** 3 * Provides database access in a platform agnostic way, using simple query building blocks. 4 * 5 * $Id: Database.php 4342 2009-05-08 16:56:01Z jheathco $ 6 * 7 * @package Core 8 * @author Kohana Team 9 * @copyright (c) 2007-2008 Kohana Team 10 * @license http://kohanaphp.com/license.html 11 */ 12class Database_Core { 13 14 // Database instances 15 public static $instances = array(); 16 17 // Global benchmark 18 public static $benchmarks = array(); 19 20 // Configuration 21 protected $config = array 22 ( 23 'benchmark' => TRUE, 24 'persistent' => FALSE, 25 'connection' => '', 26 'character_set' => 'utf8', 27 'table_prefix' => '', 28 'object' => TRUE, 29 'cache' => FALSE, 30 'escape' => TRUE, 31 ); 32 33 // Database driver object 34 protected $driver; 35 protected $link; 36 37 // Un-compiled parts of the SQL query 38 protected $select = array(); 39 protected $set = array(); 40 protected $from = array(); 41 protected $join = array(); 42 protected $where = array(); 43 protected $orderby = array(); 44 protected $order = array(); 45 protected $groupby = array(); 46 protected $having = array(); 47 protected $distinct = FALSE; 48 protected $limit = FALSE; 49 protected $offset = FALSE; 50 protected $last_query = ''; 51 52 // Stack of queries for push/pop 53 protected $query_history = array(); 54 55 /** 56 * Returns a singleton instance of Database. 57 * 58 * @param mixed configuration array or DSN 59 * @return Database_Core 60 */ 61 public static function & instance($name = 'default', $config = NULL) 62 { 63 if ( ! isset(Database::$instances[$name])) 64 { 65 // Create a new instance 66 Database::$instances[$name] = new Database($config === NULL ? $name : $config); 67 } 68 69 return Database::$instances[$name]; 70 } 71 72 /** 73 * Returns the name of a given database instance. 74 * 75 * @param Database instance of Database 76 * @return string 77 */ 78 public static function instance_name(Database $db) 79 { 80 return array_search($db, Database::$instances, TRUE); 81 } 82 83 /** 84 * Sets up the database configuration, loads the Database_Driver. 85 * 86 * @throws Kohana_Database_Exception 87 */ 88 public function __construct($config = array()) 89 { 90 if (empty($config)) 91 { 92 // Load the default group 93 $config = Kohana::config('database.default'); 94 } 95 elseif (is_array($config) AND count($config) > 0) 96 { 97 if ( ! array_key_exists('connection', $config)) 98 { 99 $config = array('connection' => $config); 100 } 101 } 102 elseif (is_string($config)) 103 { 104 // The config is a DSN string 105 if (strpos($config, '://') !== FALSE) 106 { 107 $config = array('connection' => $config); 108 } 109 // The config is a group name 110 else 111 { 112 $name = $config; 113 114 // Test the config group name 115 if (($config = Kohana::config('database.'.$config)) === NULL) 116 throw new Kohana_Database_Exception('database.undefined_group', $name); 117 } 118 } 119 120 // Merge the default config with the passed config 121 $this->config = array_merge($this->config, $config); 122 123 if (is_string($this->config['connection'])) 124 { 125 // Make sure the connection is valid 126 if (strpos($this->config['connection'], '://') === FALSE) 127 throw new Kohana_Database_Exception('database.invalid_dsn', $this->config['connection']); 128 129 // Parse the DSN, creating an array to hold the connection parameters 130 $db = array 131 ( 132 'type' => FALSE, 133 'user' => FALSE, 134 'pass' => FALSE, 135 'host' => FALSE, 136 'port' => FALSE, 137 'socket' => FALSE, 138 'database' => FALSE 139 ); 140 141 // Get the protocol and arguments 142 list ($db['type'], $connection) = explode('://', $this->config['connection'], 2); 143 144 if (strpos($connection, '@') !== FALSE) 145 { 146 // Get the username and password 147 list ($db['pass'], $connection) = explode('@', $connection, 2); 148 // Check if a password is supplied 149 $logindata = explode(':', $db['pass'], 2); 150 $db['pass'] = (count($logindata) > 1) ? $logindata[1] : ''; 151 $db['user'] = $logindata[0]; 152 153 // Prepare for finding the database 154 $connection = explode('/', $connection); 155 156 // Find the database name 157 $db['database'] = array_pop($connection); 158 159 // Reset connection string 160 $connection = implode('/', $connection); 161 162 // Find the socket 163 if (preg_match('/^unix\([^)]++\)/', $connection)) 164 { 165 // This one is a little hairy: we explode based on the end of 166 // the socket, removing the 'unix(' from the connection string 167 list ($db['socket'], $connection) = explode(')', substr($connection, 5), 2); 168 } 169 elseif (strpos($connection, ':') !== FALSE) 170 { 171 // Fetch the host and port name 172 list ($db['host'], $db['port']) = explode(':', $connection, 2); 173 } 174 else 175 { 176 $db['host'] = $connection; 177 } 178 } 179 else 180 { 181 // File connection 182 $connection = explode('/', $connection); 183 184 // Find database file name 185 $db['database'] = array_pop($connection); 186 187 // Find database directory name 188 $db['socket'] = implode('/', $connection).'/'; 189 } 190 191 // Reset the connection array to the database config 192 $this->config['connection'] = $db; 193 } 194 // Set driver name 195 $driver = 'Database_'.ucfirst($this->config['connection']['type']).'_Driver'; 196 197 // Load the driver 198 if ( ! Kohana::auto_load($driver)) 199 throw new Kohana_Database_Exception('core.driver_not_found', $this->config['connection']['type'], get_class($this)); 200 201 // Initialize the driver 202 $this->driver = new $driver($this->config); 203 204 // Validate the driver 205 if ( ! ($this->driver instanceof Database_Driver)) 206 throw new Kohana_Database_Exception('core.driver_implements', $this->config['connection']['type'], get_class($this), 'Database_Driver'); 207 208 Kohana::log('debug', 'Database Library initialized'); 209 } 210 211 /** 212 * Simple connect method to get the database queries up and running. 213 * 214 * @return void 215 */ 216 public function connect() 217 { 218 // A link can be a resource or an object 219 if ( ! is_resource($this->link) AND ! is_object($this->link)) 220 { 221 $this->link = $this->driver->connect(); 222 if ( ! is_resource($this->link) AND ! is_object($this->link)) 223 throw new Kohana_Database_Exception('database.connection', $this->driver->show_error()); 224 225 // Clear password after successful connect 226 $this->config['connection']['pass'] = NULL; 227 } 228 } 229 230 /** 231 * Runs a query into the driver and returns the result. 232 * 233 * @param string SQL query to execute 234 * @return Database_Result 235 */ 236 public function query($sql = '') 237 { 238 if ($sql == '') return FALSE; 239 240 // No link? Connect! 241 $this->link or $this->connect(); 242 243 // Start the benchmark 244 $start = microtime(TRUE); 245 246 if (func_num_args() > 1) //if we have more than one argument ($sql) 247 { 248 $argv = func_get_args(); 249 $binds = (is_array(next($argv))) ? current($argv) : array_slice($argv, 1); 250 } 251 252 // Compile binds if needed 253 if (isset($binds)) 254 { 255 $sql = $this->compile_binds($sql, $binds); 256 } 257 258 // Fetch the result 259 $result = $this->driver->query($this->last_query = $sql); 260 261 // Stop the benchmark 262 $stop = microtime(TRUE); 263 264 if ($this->config['benchmark'] == TRUE) 265 { 266 // Benchmark the query 267 Database::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result)); 268 } 269 270 return $result; 271 } 272 273 /** 274 * Selects the column names for a database query. 275 * 276 * @param string string or array of column names to select 277 * @return Database_Core This Database object. 278 */ 279 public function select($sql = '*') 280 { 281 if (func_num_args() > 1) 282 { 283 $sql = func_get_args(); 284 } 285 elseif (is_string($sql)) 286 { 287 $sql = explode(',', $sql); 288 } 289 else 290 { 291 $sql = (array) $sql; 292 } 293 294 foreach ($sql as $val) 295 { 296 if (($val = trim($val)) === '') continue; 297 298 if (strpos($val, '(') === FALSE AND $val !== '*') 299 { 300 if (preg_match('/^DISTINCT\s++(.+)$/i', $val, $matches)) 301 { 302 // Only prepend with table prefix if table name is specified 303 $val = (strpos($matches[1], '.') !== FALSE) ? $this->config['table_prefix'].$matches[1] : $matches[1]; 304 305 $this->distinct = TRUE; 306 } 307 else 308 { 309 $val = (strpos($val, '.') !== FALSE) ? $this->config['table_prefix'].$val : $val; 310 } 311 312 $val = $this->driver->escape_column($val); 313 } 314 315 $this->select[] = $val; 316 } 317 318 return $this; 319 } 320 321 /** 322 * Selects the from table(s) for a database query. 323 * 324 * @param string string or array of tables to select 325 * @return Database_Core This Database object. 326 */ 327 public function from($sql) 328 { 329 if (func_num_args() > 1) 330 { 331 $sql = func_get_args(); 332 } 333 elseif (is_string($sql)) 334 { 335 $sql = explode(',', $sql); 336 } 337 else 338 { 339 $sql = array($sql); 340 } 341 342 foreach ($sql as $val) 343 { 344 if (is_string($val)) 345 { 346 if (($val = trim($val)) === '') continue; 347 348 // TODO: Temporary solution, this should be moved to database driver (AS is checked for twice) 349 if (stripos($val, ' AS ') !== FALSE) 350 { 351 $val = str_ireplace(' AS ', ' AS ', $val); 352 353 list($table, $alias) = explode(' AS ', $val); 354 355 // Attach prefix to both sides of the AS 356 $val = $this->config['table_prefix'].$table.' AS '.$this->config['table_prefix'].$alias; 357 } 358 else 359 { 360 $val = $this->config['table_prefix'].$val; 361 } 362 } 363 364 $this->from[] = $val; 365 } 366 367 return $this; 368 } 369 370 /** 371 * Generates the JOIN portion of the query. 372 * 373 * @param string table name 374 * @param string|array where key or array of key => value pairs 375 * @param string where value 376 * @param string type of join 377 * @return Database_Core This Database object. 378 */ 379 public function join($table, $key, $value = NULL, $type = '') 380 { 381 $join = array(); 382 383 if ( ! empty($type)) 384 { 385 $type = strtoupper(trim($type)); 386 387 if ( ! in_array($type, array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'), TRUE)) 388 { 389 $type = ''; 390 } 391 else 392 { 393 $type .= ' '; 394 } 395 } 396 397 $cond = array(); 398 $keys = is_array($key) ? $key : array($key => $value); 399 foreach ($keys as $key => $value) 400 { 401 $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key; 402 403 if (is_string($value)) 404 { 405 // Only escape if it's a string 406 $value = $this->driver->escape_column($this->config['table_prefix'].$value); 407 } 408 409 $cond[] = $this->driver->where($key, $value, 'AND ', count($cond), FALSE); 410 } 411 412 if ( ! is_array($this->join)) 413 { 414 $this->join = array(); 415 } 416 417 if ( ! is_array($table)) 418 { 419 $table = array($table); 420 } 421 422 foreach ($table as $t) 423 { 424 if (is_string($t)) 425 { 426 // TODO: Temporary solution, this should be moved to database driver (AS is checked for twice) 427 if (stripos($t, ' AS ') !== FALSE) 428 { 429 $t = str_ireplace(' AS ', ' AS ', $t); 430 431 list($table, $alias) = explode(' AS ', $t); 432 433 // Attach prefix to both sides of the AS 434 $t = $this->config['table_prefix'].$table.' AS '.$this->config['table_prefix'].$alias; 435 } 436 else 437 { 438 $t = $this->config['table_prefix'].$t; 439 } 440 } 441 442 $join['tables'][] = $this->driver->escape_column($t); 443 } 444 445 $join['conditions'] = '('.trim(implode(' ', $cond)).')'; 446 $join['type'] = $type; 447 448 $this->join[] = $join; 449 450 return $this; 451 } 452 453 454 /** 455 * Selects the where(s) for a database query. 456 * 457 * @param string|array key name or array of key => value pairs 458 * @param string value to match with key 459 * @param boolean disable quoting of WHERE clause 460 * @return Database_Core This Database object. 461 */ 462 public function where($key, $value = NULL, $quote = TRUE) 463 { 464 $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote; 465 if (is_object($key)) 466 { 467 $keys = array((string) $key => ''); 468 } 469 elseif ( ! is_array($key)) 470 { 471 $keys = array($key => $value); 472 } 473 else 474 { 475 $keys = $key; 476 } 477 478 foreach ($keys as $key => $value) 479 { 480 $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key; 481 $this->where[] = $this->driver->where($key, $value, 'AND ', count($this->where), $quote); 482 } 483 484 return $this; 485 } 486 487 /** 488 * Selects the or where(s) for a database query. 489 * 490 * @param string|array key name or array of key => value pairs 491 * @param string value to match with key 492 * @param boolean disable quoting of WHERE clause 493 * @return Database_Core This Database object. 494 */ 495 public function orwhere($key, $value = NULL, $quote = TRUE) 496 { 497 $quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote; 498 if (is_object($key)) 499 { 500 $keys = array((string) $key => ''); 501 } 502 elseif ( ! is_array($key)) 503 { 504 $keys = array($key => $value); 505 } 506 else 507 { 508 $keys = $key; 509 } 510 511 foreach ($keys as $key => $value) 512 { 513 $key = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key; 514 $this->where[] = $this->driver->where($key, $value, 'OR ', count($this->where), $quote); 515 } 516 517 return $this; 518 } 519 520 /** 521 * Selects the like(s) for a database query. 522 * 523 * @param string|array field name or array of field => match pairs 524 * @param string like value to match with field 525 * @param boolean automatically add starting and ending wildcards 526 * @return Database_Core This Database object. 527 */ 528 public function like($field, $match = '', $auto = TRUE) 529 { 530 $fields = is_array($field) ? $field : array($field => $match); 531 532 foreach ($fields as $field => $match) 533 { 534 $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; 535 $this->where[] = $this->driver->like($field, $match, $auto, 'AND ', count($this->where)); 536 } 537 538 return $this; 539 } 540 541 /** 542 * Selects the or like(s) for a database query. 543 * 544 * @param string|array field name or array of field => match pairs 545 * @param string like value to match with field 546 * @param boolean automatically add starting and ending wildcards 547 * @return Database_Core This Database object. 548 */ 549 public function orlike($field, $match = '', $auto = TRUE) 550 { 551 $fields = is_array($field) ? $field : array($field => $match); 552 553 foreach ($fields as $field => $match) 554 { 555 $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; 556 $this->where[] = $this->driver->like($field, $match, $auto, 'OR ', count($this->where)); 557 } 558 559 return $this; 560 } 561 562 /** 563 * Selects the not like(s) for a database query. 564 * 565 * @param string|array field name or array of field => match pairs 566 * @param string like value to match with field 567 * @param boolean automatically add starting and ending wildcards 568 * @return Database_Core This Database object. 569 */ 570 public function notlike($field, $match = '', $auto = TRUE) 571 { 572 $fields = is_array($field) ? $field : array($field => $match); 573 574 foreach ($fields as $field => $match) 575 { 576 $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; 577 $this->where[] = $this->driver->notlike($field, $match, $auto, 'AND ', count($this->where)); 578 } 579 580 return $this; 581 } 582 583 /** 584 * Selects the or not like(s) for a database query. 585 * 586 * @param string|array field name or array of field => match pairs 587 * @param string like value to match with field 588 * @return Database_Core This Database object. 589 */ 590 public function ornotlike($field, $match = '', $auto = TRUE) 591 { 592 $fields = is_array($field) ? $field : array($field => $match); 593 594 foreach ($fields as $field => $match) 595 { 596 $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; 597 $this->where[] = $this->driver->notlike($field, $match, $auto, 'OR ', count($this->where)); 598 } 599 600 return $this; 601 } 602 603 /** 604 * Selects the like(s) for a database query. 605 * 606 * @param string|array field name or array of field => match pairs 607 * @param string like value to match with field 608 * @return Database_Core This Database object. 609 */ 610 public function regex($field, $match = '') 611 { 612 $fields = is_array($field) ? $field : array($field => $match); 613 614 foreach ($fields as $field => $match) 615 { 616 $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; 617 $this->where[] = $this->driver->regex($field, $match, 'AND ', count($this->where)); 618 } 619 620 return $this; 621 } 622 623 /** 624 * Selects the or like(s) for a database query. 625 * 626 * @param string|array field name or array of field => match pairs 627 * @param string like value to match with field 628 * @return Database_Core This Database object. 629 */ 630 public function orregex($field, $match = '') 631 { 632 $fields = is_array($field) ? $field : array($field => $match); 633 634 foreach ($fields as $field => $match) 635 { 636 $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; 637 $this->where[] = $this->driver->regex($field, $match, 'OR ', count($this->where)); 638 } 639 640 return $this; 641 } 642 643 /** 644 * Selects the not regex(s) for a database query. 645 * 646 * @param string|array field name or array of field => match pairs 647 * @param string regex value to match with field 648 * @return Database_Core This Database object. 649 */ 650 public function notregex($field, $match = '') 651 { 652 $fields = is_array($field) ? $field : array($field => $match); 653 654 foreach ($fields as $field => $match) 655 { 656 $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; 657 $this->where[] = $this->driver->notregex($field, $match, 'AND ', count($this->where)); 658 } 659 660 return $this; 661 } 662 663 /** 664 * Selects the or not regex(s) for a database query. 665 * 666 * @param string|array field name or array of field => match pairs 667 * @param string regex value to match with field 668 * @return Database_Core This Database object. 669 */ 670 public function ornotregex($field, $match = '') 671 { 672 $fields = is_array($field) ? $field : array($field => $match); 673 674 foreach ($fields as $field => $match) 675 { 676 $field = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field; 677 $this->where[] = $this->driver->notregex($field, $match, 'OR ', count($this->where)); 678 } 679 680 return $this; 681 } 682 683 /** 684 * Chooses the column to group by in a select query. 685 * 686 * @param string column name to group by 687 * @return Database_Core This Database object. 688 */ 689 public function groupby($by) 690 { 691 if ( ! is_array($by)) 692 { 693 $by = explode(',', (string) $by); 694 } 695 696 foreach ($by as $val) 697 { 698 $val = trim($val); 699 700 if ($val != '') 701 { 702 // Add the table prefix if we are using table.column names 703 if(strpos($val, '.')) 704 { 705 $val = $this->config['table_prefix'].$val; 706 } 707 708 $this->groupby[] = $this->driver->escape_column($val); 709 } 710 } 711 712 return $this; 713 } 714 715 /** 716 * Selects the having(s) for a database query. 717 * 718 * @param string|array key name or array of key => value pairs 719 * @param string value to match with key 720 * @param boolean disable quoting of WHERE clause 721 * @return Database_Core This Database object. 722 */ 723 public function having($key, $value = '', $quote = TRUE) 724 { 725 $this->having[] = $this->driver->where($key, $value, 'AND', count($this->having), TRUE); 726 return $this; 727 } 728 729 /** 730 * Selects the or having(s) for a database query. 731 * 732 * @param string|array key name or array of key => value pairs 733 * @param string value to match with key 734 * @param boolean disable quoting of WHERE clause 735 * @return Database_Core This Database object. 736 */ 737 public function orhaving($key, $value = '', $quote = TRUE) 738 { 739 $this->having[] = $this->driver->where($key, $value, 'OR', count($this->having), TRUE); 740 return $this; 741 } 742 743 /** 744 * Chooses which column(s) to order the select query by. 745 * 746 * @param string|array column(s) to order on, can be an array, single column, or comma seperated list of columns 747 * @param string direction of the order 748 * @return Database_Core This Database object. 749 */ 750 public function orderby($orderby, $direction = NULL) 751 { 752 if ( ! is_array($orderby)) 753 { 754 $orderby = array($orderby => $direction); 755 } 756 757 foreach ($orderby as $column => $direction) 758 { 759 $direction = strtoupper(trim($direction)); 760 761 // Add a direction if the provided one isn't valid 762 if ( ! in_array($direction, array('ASC', 'DESC', 'RAND()', 'RANDOM()', 'NULL'))) 763 { 764 $direction = 'ASC'; 765 } 766 767 // Add the table prefix if a table.column was passed 768 if (strpos($column, '.')) 769 { 770 $column = $this->config['table_prefix'].$column; 771 } 772 773 $this->orderby[] = $this->driver->escape_column($column).' '.$direction; 774 } 775 776 return $this; 777 } 778 779 /** 780 * Selects the limit section of a query. 781 * 782 * @param integer number of rows to limit result to 783 * @param integer offset in result to start returning rows from 784 * @return Database_Core This Database object. 785 */ 786 public function limit($limit, $offset = NULL) 787 { 788 $this->limit = (int) $limit; 789 790 if ($offset !== NULL OR ! is_int($this->offset)) 791 { 792 $this->offset($offset); 793 } 794 795 return $this; 796 } 797 798 /** 799 * Sets the offset portion of a query. 800 * 801 * @param integer offset value 802 * @return Database_Core This Database object. 803 */ 804 public function offset($value) 805 { 806 $this->offset = (int) $value; 807 808 return $this; 809 } 810 811 /** 812 * Allows key/value pairs to be set for inserting or updating. 813 * 814 * @param string|array key name or array of key => value pairs 815 * @param string value to match with key 816 * @return Database_Core This Database object. 817 */ 818 public function set($key, $value = '') 819 { 820 if ( ! is_array($key)) 821 { 822 $key = array($key => $value); 823 } 824 825 foreach ($key as $k => $v) 826 { 827 // Add a table prefix if the column includes the table. 828 if (strpos($k, '.')) 829 $k = $this->config['table_prefix'].$k; 830 831 $this->set[$k] = $this->driver->escape($v); 832 } 833 834 return $this; 835 } 836 837 /** 838 * Compiles the select statement based on the other functions called and runs the query. 839 * 840 * @param string table name 841 * @param string limit clause 842 * @param string offset clause 843 * @return Database_Result 844 */ 845 public function get($table = '', $limit = NULL, $offset = NULL) 846 { 847 if ($table != '') 848 { 849 $this->from($table); 850 } 851 852 if ( ! is_null($limit)) 853 { 854 $this->limit($limit, $offset); 855 } 856 857 $sql = $this->driver->compile_select(get_object_vars($this)); 858 859 $this->reset_select(); 860 861 $result = $this->query($sql); 862 863 $this->last_query = $sql; 864 865 return $result; 866 } 867 868 /** 869 * Compiles the select statement based on the other functions called and runs the query. 870 * 871 * @param string table name 872 * @param array where clause 873 * @param string limit clause 874 * @param string offset clause 875 * @return Database_Core This Database object. 876 */ 877 public function getwhere($table = '', $where = NULL, $limit = NULL, $offset = NULL) 878 { 879 if ($table != '') 880 { 881 $this->from($table); 882 } 883 884 if ( ! is_null($where)) 885 { 886 $this->where($where); 887 } 888 889 if ( ! is_null($limit)) 890 { 891 $this->limit($limit, $offset); 892 } 893 894 $sql = $this->driver->compile_select(get_object_vars($this)); 895 896 $this->reset_select(); 897 898 $result = $this->query($sql); 899 900 return $result; 901 } 902 903 /** 904 * Compiles the select statement based on the other functions called and returns the query string. 905 * 906 * @param string table name 907 * @param string limit clause 908 * @param string offset clause 909 * @return string sql string 910 */ 911 public function compile($table = '', $limit = NULL, $offset = NULL) 912 { 913 if ($table != '') 914 { 915 $this->from($table); 916 } 917 918 if ( ! is_null($limit)) 919 { 920 $this->limit($limit, $offset); 921 } 922 923 $sql = $this->driver->compile_select(get_object_vars($this)); 924 925 $this->reset_select(); 926 927 return $sql; 928 } 929 930 /** 931 * Compiles an insert string and runs the query. 932 * 933 * @param string table name 934 * @param array array of key/value pairs to insert 935 * @return Database_Result Query result 936 */ 937 public function insert($table = '', $set = NULL) 938 { 939 if ( ! is_null($set)) 940 { 941 $this->set($set); 942 } 943 944 if ($this->set == NULL) 945 throw new Kohana_Database_Exception('database.must_use_set'); 946 947 if ($table == '') 948 { 949 if ( ! isset($this->from[0])) 950 throw new Kohana_Database_Exception('database.must_use_table'); 951 952 $table = $this->from[0]; 953 } 954 955 // If caching is enabled, clear the cache before inserting 956 ($this->config['cache'] === TRUE) and $this->clear_cache(); 957 958 $sql = $this->driver->insert($this->config['table_prefix'].$table, array_keys($this->set), array_values($this->set)); 959 960 $this->reset_write(); 961 962 return $this->query($sql); 963 } 964 965 /** 966 * Adds an "IN" condition to the where clause 967 * 968 * @param string Name of the column being examined 969 * @param mixed An array or string to match against 970 * @param bool Generate a NOT IN clause instead 971 * @return Database_Core This Database object. 972 */ 973 public function in($field, $values, $not = FALSE) 974 { 975 if (is_array($values)) 976 { 977 $escaped_values = array(); 978 foreach ($values as $v) 979 { 980 if (is_numeric($v)) 981 { 982 $escaped_values[] = $v; 983 } 984 else 985 { 986 $escaped_values[] = "'".$this->driver->escape_str($v)."'"; 987 } 988 } 989 $values = implode(",", $escaped_values); 990 } 991 992 $where = $this->driver->escape_column(((strpos($field,'.') !== FALSE) ? $this->config['table_prefix'] : ''). $field).' '.($not === TRUE ? 'NOT ' : '').'IN ('.$values.')'; 993 $this->where[] = $this->driver->where($where, '', 'AND ', count($this->where), -1); 994 995 return $this; 996 } 997 998 /** 999 * Adds a "NOT IN" condition to the where clause 1000 * 1001 * @param string Name of the column being examined 1002 * @param mixed An array or string to match against 1003 * @return Database_Core This Database object. 1004 */ 1005 public function notin($field, $values) 1006 { 1007 return $this->in($field, $values, TRUE); 1008 } 1009 1010 /** 1011 * Compiles a merge string and runs the query. 1012 * 1013 * @param string table name 1014 * @param array array of key/value pairs to merge 1015 * @return Database_Result Query result 1016 */ 1017 public function merge($table = '', $set = NULL) 1018 { 1019 if ( ! is_null($set)) 1020 { 1021 $this->set($set); 1022 } 1023 1024 if ($this->set == NULL) 1025 throw new Kohana_Database_Exception('database.must_use_set'); 1026 1027 if ($table == '') 1028 { 1029 if ( ! isset($this->from[0])) 1030 throw new Kohana_Database_Exception('database.must_use_table'); 1031 1032 $table = $this->from[0]; 1033 } 1034 1035 $sql = $this->driver->merge($this->config['table_prefix'].$table, array_keys($this->set), array_values($this->set)); 1036 1037 $this->reset_write(); 1038 return $this->query($sql); 1039 } 1040 1041 /** 1042 * Compiles an update string and runs the query. 1043 * 1044 * @param string table name 1045 * @param array associative array of update values 1046 * @param array where clause 1047 * @return Database_Result Query result 1048 */ 1049 public function update($table = '', $set = NULL, $where = NULL) 1050 { 1051 if ( is_array($set)) 1052 { 1053 $this->set($set); 1054 } 1055 1056 if ( ! is_null($where)) 1057 { 1058 $this->where($where); 1059 } 1060 1061 if ($this->set == FALSE) 1062 throw new Kohana_Database_Exception('database.must_use_set'); 1063 1064 if ($table == '') 1065 { 1066 if ( ! isset($this->from[0])) 1067 throw new Kohana_Database_Exception('database.must_use_table'); 1068 1069 $table = $this->from[0]; 1070 } 1071 1072 $sql = $this->driver->update($this->config['table_prefix'].$table, $this->set, $this->where); 1073 1074 $this->reset_write(); 1075 return $this->query($sql); 1076 } 1077 1078 /** 1079 * Compiles a delete string and runs the query. 1080 * 1081 * @param string table name 1082 * @param array where clause 1083 * @return Database_Result Query result 1084 */ 1085 public function delete($table = '', $where = NULL) 1086 { 1087 if ($table == '') 1088 { 1089 if ( ! isset($this->from[0])) 1090 throw new Kohana_Database_Exception('database.must_use_table'); 1091 1092 $table = $this->from[0]; 1093 } 1094 else 1095 { 1096 $table = $this->config['table_prefix'].$table; 1097 } 1098 1099 if (! is_null($where)) 1100 { 1101 $this->where($where); 1102 } 1103 1104 if (count($this->where) < 1) 1105 throw new Kohana_Database_Exception('database.must_use_where'); 1106 1107 $sql = $this->driver->delete($table, $this->where); 1108 1109 $this->reset_write(); 1110 return $this->query($sql); 1111 } 1112 1113 /** 1114 * Returns the last query run. 1115 * 1116 * @return string SQL 1117 */ 1118 public function last_query() 1119 { 1120 return $this->last_query; 1121 } 1122 1123 /** 1124 * Count query records. 1125 * 1126 * @param string table name 1127 * @param array where clause 1128 * @return integer 1129 */ 1130 public function count_records($table = FALSE, $where = NULL) 1131 { 1132 if (count($this->from) < 1) 1133 { 1134 if ($table == FALSE) 1135 throw new Kohana_Database_Exception('database.must_use_table'); 1136 1137 $this->from($table); 1138 } 1139 1140 if ($where !== NULL) 1141 { 1142 $this->where($where); 1143 } 1144 1145 $query = $this->select('COUNT(*) AS '.$this->escape_column('records_found'))->get()->result(TRUE); 1146 1147 return (int) $query->current()->records_found; 1148 } 1149 1150 /** 1151 * Resets all private select variables. 1152 * 1153 * @return void 1154 */ 1155 protected function reset_select() 1156 { 1157 $this->select = array(); 1158 $this->from = array(); 1159 $this->join = array(); 1160 $this->where = array(); 1161 $this->orderby = array(); 1162 $this->groupby = array(); 1163 $this->having = array(); 1164 $this->distinct = FALSE; 1165 $this->limit = FALSE; 1166 $this->offset = FALSE; 1167 } 1168 1169 /** 1170 * Resets all private insert and update variables. 1171 * 1172 * @return void 1173 */ 1174 protected function reset_write() 1175 { 1176 $this->set = array(); 1177 $this->from = array(); 1178 $this->where = array(); 1179 } 1180 1181 /** 1182 * Lists all the tables in the current database. 1183 * 1184 * @return array 1185 */ 1186 public function list_tables() 1187 { 1188 $this->link or $this->connect(); 1189 1190 return $this->driver->list_tables(); 1191 } 1192 1193 /** 1194 * See if a table exists in the database. 1195 * 1196 * @param string table name 1197 * @param boolean True to attach table prefix 1198 * @return boolean 1199 */ 1200 public function table_exists($table_name, $prefix = TRUE) 1201 { 1202 if ($prefix) 1203 return in_array($this->config['table_prefix'].$table_name, $this->list_tables()); 1204 else 1205 return in_array($table_name, $this->list_tables()); 1206 } 1207 1208 /** 1209 * Combine a SQL statement with the bind values. Used for safe queries. 1210 * 1211 * @param string query to bind to the values 1212 * @param array array of values to bind to the query 1213 * @return string 1214 */ 1215 public function compile_binds($sql, $binds) 1216 { 1217 foreach ((array) $binds as $val) 1218 { 1219 // If the SQL contains no more bind marks ("?"), we're done. 1220 if (($next_bind_pos = strpos($sql, '?')) === FALSE) 1221 break; 1222 1223 // Properly escape the bind value. 1224 $val = $this->driver->escape($val); 1225 1226 // Temporarily replace possible bind marks ("?"), in the bind value itself, with a placeholder. 1227 $val = str_replace('?', '{%B%}', $val); 1228 1229 // Replace the first bind mark ("?") with its corresponding value. 1230 $sql = substr($sql, 0, $next_bind_pos).$val.substr($sql, $next_bind_pos + 1); 1231 } 1232 1233 // Restore placeholders. 1234 return str_replace('{%B%}', '?', $sql); 1235 } 1236 1237 /** 1238 * Get the field data for a database table, along with the field's attributes. 1239 * 1240 * @param string table name 1241 * @return array 1242 */ 1243 public function field_data($table = '') 1244 { 1245 $this->link or $this->connect(); 1246 1247 return $this->driver->field_data($this->config['table_prefix'].$table); 1248 } 1249 1250 /** 1251 * Get the field data for a database table, along with the field's attributes. 1252 * 1253 * @param string table name 1254 * @return array 1255 */ 1256 public function list_fields($table = '') 1257 { 1258 $this->link or $this->connect(); 1259 1260 return $this->driver->list_fields($this->config['table_prefix'].$table); 1261 } 1262 1263 /** 1264 * Escapes a value for a query. 1265 * 1266 * @param mixed value to escape 1267 * @return string 1268 */ 1269 public function escape($value) 1270 { 1271 return $this->driver->escape($value); 1272 } 1273 1274 /** 1275 * Escapes a string for a query. 1276 * 1277 * @param string string to escape 1278 * @return string 1279 */ 1280 public function escape_str($str) 1281 { 1282 return $this->driver->escape_str($str); 1283 } 1284 1285 /** 1286 * Escapes a table name for a query. 1287 * 1288 * @param string string to escape 1289 * @return string 1290 */ 1291 public function escape_table($table) 1292 { 1293 return $this->driver->escape_table($table); 1294 } 1295 1296 /** 1297 * Escapes a column name for a query. 1298 * 1299 * @param string string to escape 1300 * @return string 1301 */ 1302 public function escape_column($table) 1303 { 1304 return $this->driver->escape_column($table); 1305 } 1306 1307 /** 1308 * Returns table prefix of current configuration. 1309 * 1310 * @return string 1311 */ 1312 public function table_prefix() 1313 { 1314 return $this->config['table_prefix']; 1315 } 1316 1317 /** 1318 * Clears the query cache. 1319 * 1320 * @param string|TRUE clear cache by SQL statement or TRUE for last query 1321 * @return Database_Core This Database object. 1322 */ 1323 public function clear_cache($sql = NULL) 1324 { 1325 if ($sql === TRUE) 1326 { 1327 $this->driver->clear_cache($this->last_query); 1328 } 1329 elseif (is_string($sql)) 1330 { 1331 $this->driver->clear_cache($sql); 1332 } 1333 else 1334 { 1335 $this->driver->clear_cache(); 1336 } 1337 1338 return $this; 1339 } 1340 1341 /** 1342 * Pushes existing query space onto the query stack. Use push 1343 * and pop to prevent queries from clashing before they are 1344 * executed 1345 * 1346 * @return Database_Core This Databaes object 1347 */ 1348 public function push() 1349 { 1350 array_push($this->query_history, array( 1351 $this->select, 1352 $this->from, 1353 $this->join, 1354 $this->where, 1355 $this->orderby, 1356 $this->order, 1357 $this->groupby, 1358 $this->having, 1359 $this->distinct, 1360 $this->limit, 1361 $this->offset 1362 )); 1363 1364 $this->reset_select(); 1365 1366 return $this; 1367 } 1368 1369 /** 1370 * Pops from query stack into the current query space. 1371 * 1372 * @return Database_Core This Databaes object 1373 */ 1374 public function pop() 1375 { 1376 if (count($this->query_history) == 0) 1377 { 1378 // No history 1379 return $this; 1380 } 1381 1382 list( 1383 $this->select, 1384 $this->from, 1385 $this->join, 1386 $this->where, 1387 $this->orderby, 1388 $this->order, 1389 $this->groupby, 1390 $this->having, 1391 $this->distinct, 1392 $this->limit, 1393 $this->offset 1394 ) = array_pop($this->query_history); 1395 1396 return $this; 1397 } 1398 1399 /** 1400 * Count the number of records in the last query, without LIMIT or OFFSET applied. 1401 * 1402 * @return integer 1403 */ 1404 public function count_last_query() 1405 { 1406 if ($sql = $this->last_query()) 1407 { 1408 if (stripos($sql, 'LIMIT') !== FALSE) 1409 { 1410 // Remove LIMIT from the SQL 1411 $sql = preg_replace('/\sLIMIT\s+[^a-z]+/i', ' ', $sql); 1412 } 1413 1414 if (stripos($sql, 'OFFSET') !== FALSE) 1415 { 1416 // Remove OFFSET from the SQL 1417 $sql = preg_replace('/\sOFFSET\s+\d+/i', '', $sql); 1418 } 1419 1420 // Get the total rows from the last query executed 1421 $result = $this->query 1422 ( 1423 'SELECT COUNT(*) AS '.$this->escape_column('total_rows').' '. 1424 'FROM ('.trim($sql).') AS '.$this->escape_table('counted_results') 1425 ); 1426 1427 // Return the total number of rows from the query 1428 return (int) $result->current()->total_rows; 1429 } 1430 1431 return FALSE; 1432 } 1433 1434} // End Database Class 1435 1436 1437/** 1438 * Sets the code for a Database exception. 1439 */ 1440class Kohana_Database_Exception extends Kohana_Exception { 1441 1442 protected $code = E_DATABASE_ERROR; 1443 1444} // End Kohana Database Exception 1445