1<?php defined('SYSPATH') OR die('No direct access allowed.');
2/**
3 * MySQLi Database Driver
4 *
5 * $Id: Mysqli.php 4344 2009-05-11 16:41:39Z zombor $
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_Mysqli_Driver extends Database_Mysql_Driver {
13
14	// Database connection link
15	protected $link;
16	protected $db_config;
17	protected $statements = array();
18
19	/**
20	 * Sets the config for the class.
21	 *
22	 * @param  array  database configuration
23	 */
24	public function __construct($config)
25	{
26		$this->db_config = $config;
27
28		Kohana::log('debug', 'MySQLi Database Driver Initialized');
29	}
30
31	/**
32	 * Closes the database connection.
33	 */
34	public function __destruct()
35	{
36		is_object($this->link) and $this->link->close();
37	}
38
39	public function connect()
40	{
41		// Check if link already exists
42		if (is_object($this->link))
43			return $this->link;
44
45		// Import the connect variables
46		extract($this->db_config['connection']);
47
48		// Build the connection info
49		$host = isset($host) ? $host : $socket;
50
51		// Make the connection and select the database
52		if ($this->link = new mysqli($host, $user, $pass, $database, $port))
53		{
54			if ($charset = $this->db_config['character_set'])
55			{
56				$this->set_charset($charset);
57			}
58
59			// Clear password after successful connect
60			$this->db_config['connection']['pass'] = NULL;
61
62			return $this->link;
63		}
64
65		return FALSE;
66	}
67
68	public function query($sql)
69	{
70		// Only cache if it's turned on, and only cache if it's not a write statement
71		if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET|DELETE|TRUNCATE)\b#i', $sql))
72		{
73			$hash = $this->query_hash($sql);
74
75			if ( ! isset($this->query_cache[$hash]))
76			{
77				// Set the cached object
78				$this->query_cache[$hash] = new Kohana_Mysqli_Result($this->link, $this->db_config['object'], $sql);
79			}
80			else
81			{
82				// Rewind cached result
83				$this->query_cache[$hash]->rewind();
84			}
85
86			// Return the cached query
87			return $this->query_cache[$hash];
88		}
89
90		return new Kohana_Mysqli_Result($this->link, $this->db_config['object'], $sql);
91	}
92
93	public function set_charset($charset)
94	{
95		if ($this->link->set_charset($charset) === FALSE)
96			throw new Kohana_Database_Exception('database.error', $this->show_error());
97	}
98
99	public function escape_str($str)
100	{
101		if (!$this->db_config['escape'])
102			return $str;
103
104		is_object($this->link) or $this->connect();
105
106		return $this->link->real_escape_string($str);
107	}
108
109	public function show_error()
110	{
111		return $this->link->error;
112	}
113
114} // End Database_Mysqli_Driver Class
115
116/**
117 * MySQLi Result
118 */
119class Kohana_Mysqli_Result extends Database_Result {
120
121	// Database connection
122	protected $link;
123
124	// Data fetching types
125	protected $fetch_type  = 'mysqli_fetch_object';
126	protected $return_type = MYSQLI_ASSOC;
127
128	/**
129	 * Sets up the result variables.
130	 *
131	 * @param  object    database link
132	 * @param  boolean   return objects or arrays
133	 * @param  string    SQL query that was run
134	 */
135	public function __construct($link, $object = TRUE, $sql)
136	{
137		$this->link = $link;
138
139		if ( ! $this->link->multi_query($sql))
140		{
141			// SQL error
142			throw new Kohana_Database_Exception('database.error', $this->link->error.' - '.$sql);
143		}
144		else
145		{
146			$this->result = $this->link->store_result();
147
148			// If the query is an object, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
149			if (is_object($this->result))
150			{
151				$this->current_row = 0;
152				$this->total_rows  = $this->result->num_rows;
153				$this->fetch_type = ($object === TRUE) ? 'fetch_object' : 'fetch_array';
154			}
155			elseif ($this->link->error)
156			{
157				// SQL error
158				throw new Kohana_Database_Exception('database.error', $this->link->error.' - '.$sql);
159			}
160			else
161			{
162				// Its an DELETE, INSERT, REPLACE, or UPDATE query
163				$this->insert_id  = $this->link->insert_id;
164				$this->total_rows = $this->link->affected_rows;
165			}
166		}
167
168		// Set result type
169		$this->result($object);
170
171		// Store the SQL
172		$this->sql = $sql;
173	}
174
175	/**
176	 * Magic __destruct function, frees the result.
177	 */
178	public function __destruct()
179	{
180		if (is_object($this->result))
181		{
182			$this->result->free_result();
183
184			// this is kinda useless, but needs to be done to avoid the "Commands out of sync; you
185			// can't run this command now" error. Basically, we get all results after the first one
186			// (the one we actually need) and free them.
187			if (is_resource($this->link) AND $this->link->more_results())
188			{
189				do
190				{
191					if ($result = $this->link->store_result())
192					{
193						$result->free_result();
194					}
195				} while ($this->link->next_result());
196			}
197		}
198	}
199
200	public function result($object = TRUE, $type = MYSQLI_ASSOC)
201	{
202		$this->fetch_type = ((bool) $object) ? 'fetch_object' : 'fetch_array';
203
204		// This check has to be outside the previous statement, because we do not
205		// know the state of fetch_type when $object = NULL
206		// NOTE - The class set by $type must be defined before fetching the result,
207		// autoloading is disabled to save a lot of stupid overhead.
208		if ($this->fetch_type == 'fetch_object')
209		{
210			$this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
211		}
212		else
213		{
214			$this->return_type = $type;
215		}
216
217		return $this;
218	}
219
220	public function as_array($object = NULL, $type = MYSQLI_ASSOC)
221	{
222		return $this->result_array($object, $type);
223	}
224
225	public function result_array($object = NULL, $type = MYSQLI_ASSOC)
226	{
227		$rows = array();
228
229		if (is_string($object))
230		{
231			$fetch = $object;
232		}
233		elseif (is_bool($object))
234		{
235			if ($object === TRUE)
236			{
237				$fetch = 'fetch_object';
238
239				// NOTE - The class set by $type must be defined before fetching the result,
240				// autoloading is disabled to save a lot of stupid overhead.
241				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
242			}
243			else
244			{
245				$fetch = 'fetch_array';
246			}
247		}
248		else
249		{
250			// Use the default config values
251			$fetch = $this->fetch_type;
252
253			if ($fetch == 'fetch_object')
254			{
255				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
256			}
257		}
258
259		if ($this->result->num_rows)
260		{
261			// Reset the pointer location to make sure things work properly
262			$this->result->data_seek(0);
263
264			while ($row = $this->result->$fetch($type))
265			{
266				$rows[] = $row;
267			}
268		}
269
270		return isset($rows) ? $rows : array();
271	}
272
273	public function list_fields()
274	{
275		$field_names = array();
276		while ($field = $this->result->fetch_field())
277		{
278			$field_names[] = $field->name;
279		}
280
281		return $field_names;
282	}
283
284	public function seek($offset)
285	{
286		if ($this->offsetExists($offset) AND $this->result->data_seek($offset))
287		{
288			// Set the current row to the offset
289			$this->current_row = $offset;
290
291			return TRUE;
292		}
293
294		return FALSE;
295	}
296
297	public function offsetGet($offset)
298	{
299		if ( ! $this->seek($offset))
300			return FALSE;
301
302		// Return the row
303		$fetch = $this->fetch_type;
304		return $this->result->$fetch($this->return_type);
305	}
306
307} // End Mysqli_Result Class
308
309/**
310 * MySQLi Prepared Statement (experimental)
311 */
312class Kohana_Mysqli_Statement {
313
314	protected $link = NULL;
315	protected $stmt;
316	protected $var_names = array();
317	protected $var_values = array();
318
319	public function __construct($sql, $link)
320	{
321		$this->link = $link;
322
323		$this->stmt = $this->link->prepare($sql);
324
325		return $this;
326	}
327
328	public function __destruct()
329	{
330		$this->stmt->close();
331	}
332
333	// Sets the bind parameters
334	public function bind_params($param_types, $params)
335	{
336		$this->var_names = array_keys($params);
337		$this->var_values = array_values($params);
338		call_user_func_array(array($this->stmt, 'bind_param'), array_merge($param_types, $var_names));
339
340		return $this;
341	}
342
343	public function bind_result($params)
344	{
345		call_user_func_array(array($this->stmt, 'bind_result'), $params);
346	}
347
348	// Runs the statement
349	public function execute()
350	{
351		foreach ($this->var_names as $key => $name)
352		{
353			$$name = $this->var_values[$key];
354		}
355		$this->stmt->execute();
356		return $this->stmt;
357	}
358}
359