db_config = $config; Kohana::log('debug', 'MySQL Database Driver Initialized'); } /** * Closes the database connection. */ public function __destruct() { is_resource($this->link) and mysql_close($this->link); } public function connect() { // Check if link already exists if (is_resource($this->link)) { return $this->link; } // Import the connect variables extract($this->db_config['connection']); // Persistent connections enabled? $connect = ($this->db_config['persistent'] == true) ? 'mysql_pconnect' : 'mysql_connect'; // Build the connection info $host = isset($host) ? $host : $socket; $port = isset($port) ? ':' . $port : ''; // Make the connection and select the database if (($this->link = $connect($host . $port, $user, $pass, true)) and mysql_select_db($database, $this->link)) { if ($charset = $this->db_config['character_set']) { $this->set_charset($charset); } // Clear password after successful connect $this->db_config['connection']['pass'] = null; return $this->link; } return false; } public function query($sql) { // Only cache if it's turned on, and only cache if it's not a write statement if ($this->db_config['cache'] and ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET|DELETE|TRUNCATE)\b#i', $sql)) { $hash = $this->query_hash($sql); if (! isset($this->query_cache[$hash])) { // Set the cached object $this->query_cache[$hash] = new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); } else { // Rewind cached result $this->query_cache[$hash]->rewind(); } // Return the cached query return $this->query_cache[$hash]; } return new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); } public function set_charset($charset) { $this->query('SET NAMES ' . $this->escape_str($charset)); } public function escape_table($table) { if (!$this->db_config['escape']) { return $table; } if (stripos($table, ' AS ') !== false) { // Force 'AS' to uppercase $table = str_ireplace(' AS ', ' AS ', $table); // Runs escape_table on both sides of an AS statement $table = array_map(array($this, __FUNCTION__), explode(' AS ', $table)); // Re-create the AS statement return implode(' AS ', $table); } return '`' . str_replace('.', '`.`', $table) . '`'; } public function escape_column($column) { if (!$this->db_config['escape']) { return $column; } if ($column == '*') { return $column; } // This matches any functions we support to SELECT. if (preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) { if (count($matches) == 3) { return $matches[1] . '(' . $this->escape_column($matches[2]) . ')'; } elseif (count($matches) == 5) { return $matches[1] . '(' . $this->escape_column($matches[2]) . ') AS ' . $this->escape_column($matches[2]); } } // This matches any modifiers we support to SELECT. if (! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column)) { if (stripos($column, ' AS ') !== false) { // Force 'AS' to uppercase $column = str_ireplace(' AS ', ' AS ', $column); // Runs escape_column on both sides of an AS statement $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column)); // Re-create the AS statement return implode(' AS ', $column); } return preg_replace('/[^.*]+/', '`$0`', $column); } $parts = explode(' ', $column); $column = ''; for ($i = 0, $c = count($parts); $i < $c; $i++) { // The column is always last if ($i == ($c - 1)) { $column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]); } else // otherwise, it's a modifier { $column .= $parts[$i] . ' '; } } return $column; } public function regex($field, $match, $type, $num_regexs) { $prefix = ($num_regexs == 0) ? '' : $type; return $prefix . ' ' . $this->escape_column($field) . ' REGEXP \'' . $this->escape_str($match) . '\''; } public function notregex($field, $match, $type, $num_regexs) { $prefix = $num_regexs == 0 ? '' : $type; return $prefix . ' ' . $this->escape_column($field) . ' NOT REGEXP \'' . $this->escape_str($match) . '\''; } public function merge($table, $keys, $values) { // Escape the column names foreach ($keys as $key => $value) { $keys[$key] = $this->escape_column($value); } return 'REPLACE INTO ' . $this->escape_table($table) . ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $values) . ')'; } public function limit($limit, $offset = 0) { return 'LIMIT ' . $offset . ', ' . $limit; } public function compile_select($database) { $sql = ($database['distinct'] == true) ? 'SELECT DISTINCT ' : 'SELECT '; $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*'; if (count($database['from']) > 0) { // Escape the tables $froms = array(); foreach ($database['from'] as $from) { $froms[] = $this->escape_column($from); } $sql .= "\nFROM ("; $sql .= implode(', ', $froms) . ")"; } if (count($database['join']) > 0) { foreach ($database['join'] as $join) { $sql .= "\n" . $join['type'] . 'JOIN ' . implode(', ', $join['tables']) . ' ON ' . $join['conditions']; } } if (count($database['where']) > 0) { $sql .= "\nWHERE "; } $sql .= implode("\n", $database['where']); if (count($database['groupby']) > 0) { $sql .= "\nGROUP BY "; $sql .= implode(', ', $database['groupby']); } if (count($database['having']) > 0) { $sql .= "\nHAVING "; $sql .= implode("\n", $database['having']); } if (count($database['orderby']) > 0) { $sql .= "\nORDER BY "; $sql .= implode(', ', $database['orderby']); } if (is_numeric($database['limit'])) { $sql .= "\n"; $sql .= $this->limit($database['limit'], $database['offset']); } return $sql; } public function escape_str($str) { if (!$this->db_config['escape']) { return $str; } is_resource($this->link) or $this->connect(); return mysql_real_escape_string($str, $this->link); } public function list_tables() { $tables = array(); if ($query = $this->query('SHOW TABLES FROM ' . $this->escape_table($this->db_config['connection']['database']))) { foreach ($query->result(false) as $row) { $tables[] = current($row); } } return $tables; } public function show_error() { return mysql_error($this->link); } public function list_fields($table) { $result = null; foreach ($this->field_data($table) as $row) { // Make an associative array $result[$row->Field] = $this->sql_type($row->Type); if ($row->Key === 'PRI' and $row->Extra === 'auto_increment') { // For sequenced (AUTO_INCREMENT) tables $result[$row->Field]['sequenced'] = true; } if ($row->Null === 'YES') { // Set NULL status $result[$row->Field]['null'] = true; } } if (!isset($result)) { throw new Kohana_Database_Exception('database.table_not_found', $table); } return $result; } public function field_data($table) { $result = $this->query('SHOW COLUMNS FROM ' . $this->escape_table($table)); return $result->result_array(true); } } // End Database_Mysql_Driver Class /** * MySQL Result */ class Mysql_Result extends Database_Result { // Fetch function and return type protected $fetch_type = 'mysql_fetch_object'; protected $return_type = MYSQL_ASSOC; /** * Sets up the result variables. * * @param resource query result * @param resource database link * @param boolean return objects or arrays * @param string SQL query that was run */ public function __construct($result, $link, $object = true, $sql = '') { $this->result = $result; // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query if (is_resource($result)) { $this->current_row = 0; $this->total_rows = mysql_num_rows($this->result); $this->fetch_type = ($object === true) ? 'mysql_fetch_object' : 'mysql_fetch_array'; } elseif (is_bool($result)) { if ($result == false) { // SQL error throw new Kohana_Database_Exception('database.error', mysql_error($link) . ' - ' . $sql); } else { // Its an DELETE, INSERT, REPLACE, or UPDATE query $this->insert_id = mysql_insert_id($link); $this->total_rows = mysql_affected_rows($link); } } // Set result type $this->result($object); // Store the SQL $this->sql = $sql; } /** * Destruct, the cleanup crew! */ public function __destruct() { if (is_resource($this->result)) { mysql_free_result($this->result); } } public function result($object = true, $type = MYSQL_ASSOC) { $this->fetch_type = ((bool) $object) ? 'mysql_fetch_object' : 'mysql_fetch_array'; // This check has to be outside the previous statement, because we do not // know the state of fetch_type when $object = NULL // NOTE - The class set by $type must be defined before fetching the result, // autoloading is disabled to save a lot of stupid overhead. if ($this->fetch_type == 'mysql_fetch_object' and $object === true) { $this->return_type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass'; } else { $this->return_type = $type; } return $this; } public function as_array($object = null, $type = MYSQL_ASSOC) { return $this->result_array($object, $type); } public function result_array($object = null, $type = MYSQL_ASSOC) { $rows = array(); if (is_string($object)) { $fetch = $object; } elseif (is_bool($object)) { if ($object === true) { $fetch = 'mysql_fetch_object'; $type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass'; } else { $fetch = 'mysql_fetch_array'; } } else { // Use the default config values $fetch = $this->fetch_type; if ($fetch == 'mysql_fetch_object') { $type = (is_string($this->return_type) and Kohana::auto_load($this->return_type)) ? $this->return_type : 'stdClass'; } } if (mysql_num_rows($this->result)) { // Reset the pointer location to make sure things work properly mysql_data_seek($this->result, 0); while ($row = $fetch($this->result, $type)) { $rows[] = $row; } } return isset($rows) ? $rows : array(); } public function list_fields() { $field_names = array(); while ($field = mysql_fetch_field($this->result)) { $field_names[] = $field->name; } return $field_names; } public function seek($offset) { if ($this->offsetExists($offset) and mysql_data_seek($this->result, $offset)) { // Set the current row to the offset $this->current_row = $offset; return true; } else { return false; } } } // End Mysql_Result Class