1
|
|
|
<?php defined('SYSPATH') or die('No direct access allowed.'); |
2
|
|
|
/** |
3
|
|
|
* MySQL Database Driver |
4
|
|
|
* |
5
|
|
|
* $Id: Mysql.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
|
|
|
*/ |
12
|
|
|
class Database_Mysql_Driver extends Database_Driver |
13
|
|
|
{ |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Database connection link |
17
|
|
|
*/ |
18
|
|
|
protected $link; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Database configuration |
22
|
|
|
*/ |
23
|
|
|
protected $db_config; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Sets the config for the class. |
27
|
|
|
* |
28
|
|
|
* @param array database configuration |
29
|
|
|
*/ |
30
|
|
|
public function __construct($config) |
31
|
|
|
{ |
32
|
|
|
$this->db_config = $config; |
33
|
|
|
|
34
|
|
|
Kohana::log('debug', 'MySQL Database Driver Initialized'); |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Closes the database connection. |
39
|
|
|
*/ |
40
|
|
|
public function __destruct() |
41
|
|
|
{ |
42
|
|
|
is_resource($this->link) and mysql_close($this->link); |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
public function connect() |
46
|
|
|
{ |
47
|
|
|
// Check if link already exists |
48
|
|
|
if (is_resource($this->link)) { |
49
|
|
|
return $this->link; |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
// Import the connect variables |
53
|
|
|
extract($this->db_config['connection']); |
54
|
|
|
|
55
|
|
|
// Persistent connections enabled? |
56
|
|
|
$connect = ($this->db_config['persistent'] == true) ? 'mysql_pconnect' : 'mysql_connect'; |
57
|
|
|
|
58
|
|
|
// Build the connection info |
59
|
|
|
$host = isset($host) ? $host : $socket; |
60
|
|
|
$port = isset($port) ? ':'.$port : ''; |
61
|
|
|
|
62
|
|
|
// Make the connection and select the database |
63
|
|
|
if (($this->link = $connect($host.$port, $user, $pass, true)) and mysql_select_db($database, $this->link)) { |
64
|
|
|
if ($charset = $this->db_config['character_set']) { |
65
|
|
|
$this->set_charset($charset); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
// Clear password after successful connect |
69
|
|
|
$this->db_config['connection']['pass'] = null; |
70
|
|
|
|
71
|
|
|
return $this->link; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
return false; |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* @param string $sql |
79
|
|
|
*/ |
80
|
|
View Code Duplication |
public function query($sql) |
|
|
|
|
81
|
|
|
{ |
82
|
|
|
// Only cache if it's turned on, and only cache if it's not a write statement |
83
|
|
|
if ($this->db_config['cache'] and ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET|DELETE|TRUNCATE)\b#i', $sql)) { |
84
|
|
|
$hash = $this->query_hash($sql); |
85
|
|
|
|
86
|
|
|
if (! isset($this->query_cache[$hash])) { |
87
|
|
|
// Set the cached object |
88
|
|
|
$this->query_cache[$hash] = new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); |
89
|
|
|
} else { |
90
|
|
|
// Rewind cached result |
91
|
|
|
$this->query_cache[$hash]->rewind(); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
// Return the cached query |
95
|
|
|
return $this->query_cache[$hash]; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
return new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
public function set_charset($charset) |
102
|
|
|
{ |
103
|
|
|
$this->query('SET NAMES '.$this->escape_str($charset)); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
View Code Duplication |
public function escape_table($table) |
|
|
|
|
107
|
|
|
{ |
108
|
|
|
if (!$this->db_config['escape']) { |
109
|
|
|
return $table; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
if (stripos($table, ' AS ') !== false) { |
113
|
|
|
// Force 'AS' to uppercase |
114
|
|
|
$table = str_ireplace(' AS ', ' AS ', $table); |
115
|
|
|
|
116
|
|
|
// Runs escape_table on both sides of an AS statement |
117
|
|
|
$table = array_map(array($this, __FUNCTION__), explode(' AS ', $table)); |
118
|
|
|
|
119
|
|
|
// Re-create the AS statement |
120
|
|
|
return implode(' AS ', $table); |
121
|
|
|
} |
122
|
|
|
return '`'.str_replace('.', '`.`', $table).'`'; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
View Code Duplication |
public function escape_column($column) |
|
|
|
|
126
|
|
|
{ |
127
|
|
|
if (!$this->db_config['escape']) { |
128
|
|
|
return $column; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
if ($column == '*') { |
132
|
|
|
return $column; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
// This matches any functions we support to SELECT. |
136
|
|
|
if (preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) { |
137
|
|
|
if (count($matches) == 3) { |
138
|
|
|
return $matches[1].'('.$this->escape_column($matches[2]).')'; |
139
|
|
|
} elseif (count($matches) == 5) { |
140
|
|
|
return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]); |
141
|
|
|
} |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
// This matches any modifiers we support to SELECT. |
145
|
|
|
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)) { |
146
|
|
|
if (stripos($column, ' AS ') !== false) { |
147
|
|
|
// Force 'AS' to uppercase |
148
|
|
|
$column = str_ireplace(' AS ', ' AS ', $column); |
149
|
|
|
|
150
|
|
|
// Runs escape_column on both sides of an AS statement |
151
|
|
|
$column = array_map(array($this, __FUNCTION__), explode(' AS ', $column)); |
152
|
|
|
|
153
|
|
|
// Re-create the AS statement |
154
|
|
|
return implode(' AS ', $column); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
return preg_replace('/[^.*]+/', '`$0`', $column); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
$parts = explode(' ', $column); |
161
|
|
|
$column = ''; |
162
|
|
|
|
163
|
|
|
for ($i = 0, $c = count($parts); $i < $c; $i++) { |
164
|
|
|
// The column is always last |
165
|
|
|
if ($i == ($c - 1)) { |
166
|
|
|
$column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]); |
167
|
|
|
} else { // otherwise, it's a modifier |
168
|
|
|
$column .= $parts[$i].' '; |
169
|
|
|
} |
170
|
|
|
} |
171
|
|
|
return $column; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
public function regex($field, $match, $type, $num_regexs) |
175
|
|
|
{ |
176
|
|
|
$prefix = ($num_regexs == 0) ? '' : $type; |
177
|
|
|
|
178
|
|
|
return $prefix.' '.$this->escape_column($field).' REGEXP \''.$this->escape_str($match).'\''; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
public function notregex($field, $match, $type, $num_regexs) |
182
|
|
|
{ |
183
|
|
|
$prefix = $num_regexs == 0 ? '' : $type; |
184
|
|
|
|
185
|
|
|
return $prefix.' '.$this->escape_column($field).' NOT REGEXP \''.$this->escape_str($match) . '\''; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
View Code Duplication |
public function merge($table, $keys, $values) |
|
|
|
|
189
|
|
|
{ |
190
|
|
|
// Escape the column names |
191
|
|
|
foreach ($keys as $key => $value) { |
192
|
|
|
$keys[$key] = $this->escape_column($value); |
193
|
|
|
} |
194
|
|
|
return 'REPLACE INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')'; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
public function limit($limit, $offset = 0) |
198
|
|
|
{ |
199
|
|
|
return 'LIMIT '.$offset.', '.$limit; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
View Code Duplication |
public function compile_select($database) |
|
|
|
|
203
|
|
|
{ |
204
|
|
|
$sql = ($database['distinct'] == true) ? 'SELECT DISTINCT ' : 'SELECT '; |
205
|
|
|
$sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*'; |
206
|
|
|
|
207
|
|
|
if (count($database['from']) > 0) { |
208
|
|
|
// Escape the tables |
209
|
|
|
$froms = array(); |
210
|
|
|
foreach ($database['from'] as $from) { |
211
|
|
|
$froms[] = $this->escape_column($from); |
212
|
|
|
} |
213
|
|
|
$sql .= "\nFROM ("; |
214
|
|
|
$sql .= implode(', ', $froms).")"; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
if (count($database['join']) > 0) { |
218
|
|
|
foreach ($database['join'] as $join) { |
219
|
|
|
$sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions']; |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
if (count($database['where']) > 0) { |
224
|
|
|
$sql .= "\nWHERE "; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
$sql .= implode("\n", $database['where']); |
228
|
|
|
|
229
|
|
|
if (count($database['groupby']) > 0) { |
230
|
|
|
$sql .= "\nGROUP BY "; |
231
|
|
|
$sql .= implode(', ', $database['groupby']); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
if (count($database['having']) > 0) { |
235
|
|
|
$sql .= "\nHAVING "; |
236
|
|
|
$sql .= implode("\n", $database['having']); |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
if (count($database['orderby']) > 0) { |
240
|
|
|
$sql .= "\nORDER BY "; |
241
|
|
|
$sql .= implode(', ', $database['orderby']); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
if (is_numeric($database['limit'])) { |
245
|
|
|
$sql .= "\n"; |
246
|
|
|
$sql .= $this->limit($database['limit'], $database['offset']); |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
return $sql; |
250
|
|
|
} |
251
|
|
|
|
252
|
|
View Code Duplication |
public function escape_str($str) |
|
|
|
|
253
|
|
|
{ |
254
|
|
|
if (!$this->db_config['escape']) { |
255
|
|
|
return $str; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
is_resource($this->link) or $this->connect(); |
259
|
|
|
|
260
|
|
|
return mysql_real_escape_string($str, $this->link); |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
public function list_tables() |
264
|
|
|
{ |
265
|
|
|
$tables = array(); |
266
|
|
|
|
267
|
|
|
if ($query = $this->query('SHOW TABLES FROM '.$this->escape_table($this->db_config['connection']['database']))) { |
268
|
|
|
foreach ($query->result(false) as $row) { |
269
|
|
|
$tables[] = current($row); |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
return $tables; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
public function show_error() |
277
|
|
|
{ |
278
|
|
|
return mysql_error($this->link); |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
public function list_fields($table) |
282
|
|
|
{ |
283
|
|
|
$result = null; |
284
|
|
|
|
285
|
|
|
foreach ($this->field_data($table) as $row) { |
286
|
|
|
// Make an associative array |
287
|
|
|
$result[$row->Field] = $this->sql_type($row->Type); |
288
|
|
|
|
289
|
|
|
if ($row->Key === 'PRI' and $row->Extra === 'auto_increment') { |
290
|
|
|
// For sequenced (AUTO_INCREMENT) tables |
291
|
|
|
$result[$row->Field]['sequenced'] = true; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
if ($row->null === 'YES') { |
295
|
|
|
// Set NULL status |
296
|
|
|
$result[$row->Field]['null'] = true; |
297
|
|
|
} |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
if (!isset($result)) { |
301
|
|
|
throw new Kohana_Database_Exception('database.table_not_found', $table); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
return $result; |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
public function field_data($table) |
308
|
|
|
{ |
309
|
|
|
$result = $this->query('SHOW COLUMNS FROM '.$this->escape_table($table)); |
310
|
|
|
|
311
|
|
|
return $result->result_array(true); |
312
|
|
|
} |
313
|
|
|
} // End Database_Mysql_Driver Class |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* MySQL Result |
317
|
|
|
*/ |
318
|
|
|
class Mysql_Result extends Database_Result |
319
|
|
|
{ |
320
|
|
|
|
321
|
|
|
// Fetch function and return type |
322
|
|
|
protected $fetch_type = 'mysql_fetch_object'; |
323
|
|
|
protected $return_type = MYSQL_ASSOC; |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* Sets up the result variables. |
327
|
|
|
* |
328
|
|
|
* @param resource query result |
329
|
|
|
* @param resource database link |
330
|
|
|
* @param boolean return objects or arrays |
331
|
|
|
* @param string SQL query that was run |
332
|
|
|
* @param resource $result |
333
|
|
|
*/ |
334
|
|
|
public function __construct($result, $link, $object = true, $sql) |
335
|
|
|
{ |
336
|
|
|
$this->result = $result; |
337
|
|
|
|
338
|
|
|
// If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query |
339
|
|
|
if (is_resource($result)) { |
340
|
|
|
$this->current_row = 0; |
341
|
|
|
$this->total_rows = mysql_num_rows($this->result); |
342
|
|
|
$this->fetch_type = ($object === true) ? 'mysql_fetch_object' : 'mysql_fetch_array'; |
343
|
|
|
} elseif (is_bool($result)) { |
344
|
|
|
if ($result == false) { |
|
|
|
|
345
|
|
|
// SQL error |
346
|
|
|
throw new Kohana_Database_Exception('database.error', mysql_error($link).' - '.$sql); |
347
|
|
|
} else { |
348
|
|
|
// Its an DELETE, INSERT, REPLACE, or UPDATE query |
349
|
|
|
$this->insert_id = mysql_insert_id($link); |
350
|
|
|
$this->total_rows = mysql_affected_rows($link); |
351
|
|
|
} |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
// Set result type |
355
|
|
|
$this->result($object); |
356
|
|
|
|
357
|
|
|
// Store the SQL |
358
|
|
|
$this->sql = $sql; |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Destruct, the cleanup crew! |
363
|
|
|
*/ |
364
|
|
|
public function __destruct() |
365
|
|
|
{ |
366
|
|
|
if (is_resource($this->result)) { |
367
|
|
|
mysql_free_result($this->result); |
368
|
|
|
} |
369
|
|
|
} |
370
|
|
|
|
371
|
|
View Code Duplication |
public function result($object = true, $type = MYSQL_ASSOC) |
|
|
|
|
372
|
|
|
{ |
373
|
|
|
$this->fetch_type = ((bool) $object) ? 'mysql_fetch_object' : 'mysql_fetch_array'; |
374
|
|
|
|
375
|
|
|
// This check has to be outside the previous statement, because we do not |
376
|
|
|
// know the state of fetch_type when $object = NULL |
377
|
|
|
// NOTE - The class set by $type must be defined before fetching the result, |
378
|
|
|
// autoloading is disabled to save a lot of stupid overhead. |
379
|
|
|
if ($this->fetch_type == 'mysql_fetch_object' and $object === true) { |
380
|
|
|
$this->return_type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass'; |
381
|
|
|
} else { |
382
|
|
|
$this->return_type = $type; |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
return $this; |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
public function as_array($object = null, $type = MYSQL_ASSOC) |
389
|
|
|
{ |
390
|
|
|
return $this->result_array($object, $type); |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
public function result_array($object = null, $type = MYSQL_ASSOC) |
394
|
|
|
{ |
395
|
|
|
$rows = array(); |
396
|
|
|
|
397
|
|
|
if (is_string($object)) { |
398
|
|
|
$fetch = $object; |
399
|
|
|
} elseif (is_bool($object)) { |
400
|
|
|
if ($object === true) { |
401
|
|
|
$fetch = 'mysql_fetch_object'; |
402
|
|
|
|
403
|
|
|
$type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass'; |
404
|
|
|
} else { |
405
|
|
|
$fetch = 'mysql_fetch_array'; |
406
|
|
|
} |
407
|
|
|
} else { |
408
|
|
|
// Use the default config values |
409
|
|
|
$fetch = $this->fetch_type; |
410
|
|
|
|
411
|
|
|
if ($fetch == 'mysql_fetch_object') { |
412
|
|
|
$type = (is_string($this->return_type) and Kohana::auto_load($this->return_type)) ? $this->return_type : 'stdClass'; |
413
|
|
|
} |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
if (mysql_num_rows($this->result)) { |
417
|
|
|
// Reset the pointer location to make sure things work properly |
418
|
|
|
mysql_data_seek($this->result, 0); |
419
|
|
|
|
420
|
|
|
while ($row = $fetch($this->result, $type)) { |
421
|
|
|
$rows[] = $row; |
422
|
|
|
} |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
return isset($rows) ? $rows : array(); |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
public function list_fields() |
429
|
|
|
{ |
430
|
|
|
$field_names = array(); |
431
|
|
|
while ($field = mysql_fetch_field($this->result)) { |
432
|
|
|
$field_names[] = $field->name; |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
return $field_names; |
436
|
|
|
} |
437
|
|
|
|
438
|
|
View Code Duplication |
public function seek($offset) |
|
|
|
|
439
|
|
|
{ |
440
|
|
|
if ($this->offsetExists($offset) and mysql_data_seek($this->result, $offset)) { |
441
|
|
|
// Set the current row to the offset |
442
|
|
|
$this->current_row = $offset; |
443
|
|
|
|
444
|
|
|
return true; |
445
|
|
|
} else { |
446
|
|
|
return false; |
447
|
|
|
} |
448
|
|
|
} |
449
|
|
|
} // End Mysql_Result Class |
450
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.