1
|
|
|
<?php defined('SYSPATH') or die('No direct access allowed.'); |
2
|
|
|
/* |
3
|
|
|
* Class: Database_PdoSqlite_Driver |
4
|
|
|
* Provides specific database items for Sqlite. |
5
|
|
|
* |
6
|
|
|
* Connection string should be, eg: "pdosqlite://path/to/database.db" |
7
|
|
|
* |
8
|
|
|
* Version 1.0 alpha |
9
|
|
|
* author - Doutu, updated by gregmac |
10
|
|
|
* copyright - (c) BSD |
11
|
|
|
* license - <no> |
12
|
|
|
*/ |
13
|
|
|
|
14
|
|
|
class Database_Pdosqlite_Driver extends Database_Driver |
15
|
|
|
{ |
16
|
|
|
|
17
|
|
|
// Database connection link |
18
|
|
|
protected $link; |
19
|
|
|
protected $db_config; |
20
|
|
|
|
21
|
|
|
/* |
22
|
|
|
* Constructor: __construct |
23
|
|
|
* Sets up the config for the class. |
24
|
|
|
* |
25
|
|
|
* Parameters: |
26
|
|
|
* config - database configuration |
27
|
|
|
* |
28
|
|
|
*/ |
29
|
|
|
public function __construct($config) |
30
|
|
|
{ |
31
|
|
|
$this->db_config = $config; |
32
|
|
|
|
33
|
|
|
Kohana::log('debug', 'PDO:Sqlite Database Driver Initialized'); |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
public function connect() |
37
|
|
|
{ |
38
|
|
|
// Import the connect variables |
39
|
|
|
extract($this->db_config['connection']); |
40
|
|
|
|
41
|
|
|
try { |
42
|
|
|
$this->link = new PDO('sqlite:'.$socket.$database, $user, $pass, |
43
|
|
|
array(PDO::ATTR_PERSISTENT => $this->db_config['persistent'])); |
44
|
|
|
|
45
|
|
|
$this->link->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); |
46
|
|
|
//$this->link->query('PRAGMA count_changes=1;'); |
47
|
|
|
|
48
|
|
|
if ($charset = $this->db_config['character_set']) { |
49
|
|
|
$this->set_charset($charset); |
50
|
|
|
} |
51
|
|
|
} catch (PDOException $e) { |
52
|
|
|
throw new Kohana_Database_Exception('database.error', $e->getMessage()); |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
// Clear password after successful connect |
56
|
|
|
$this->db_config['connection']['pass'] = null; |
57
|
|
|
|
58
|
|
|
return $this->link; |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* @param string $sql |
63
|
|
|
*/ |
64
|
|
|
public function query($sql) |
65
|
|
|
{ |
66
|
|
|
try { |
67
|
|
|
$sth = $this->link->prepare($sql); |
68
|
|
|
} catch (PDOException $e) { |
69
|
|
|
throw new Kohana_Database_Exception('database.error', $e->getMessage()); |
70
|
|
|
} |
71
|
|
|
return new Pdosqlite_Result($sth, $this->link, $this->db_config['object'], $sql); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
public function set_charset($charset) |
75
|
|
|
{ |
76
|
|
|
$this->link->query('PRAGMA encoding = '.$this->escape_str($charset)); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
View Code Duplication |
public function escape_table($table) |
|
|
|
|
80
|
|
|
{ |
81
|
|
|
if (! $this->db_config['escape']) { |
82
|
|
|
return $table; |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
return '`'.str_replace('.', '`.`', $table).'`'; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
View Code Duplication |
public function escape_column($column) |
|
|
|
|
89
|
|
|
{ |
90
|
|
|
if (! $this->db_config['escape']) { |
91
|
|
|
return $column; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
if ($column == '*') { |
95
|
|
|
return $column; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
// This matches any functions we support to SELECT. |
99
|
|
|
if (preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) { |
100
|
|
|
if (count($matches) == 3) { |
101
|
|
|
return $matches[1].'('.$this->escape_column($matches[2]).')'; |
102
|
|
|
} elseif (count($matches) == 5) { |
103
|
|
|
return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]); |
104
|
|
|
} |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
// This matches any modifiers we support to SELECT. |
108
|
|
|
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)) { |
109
|
|
|
if (stripos($column, ' AS ') !== false) { |
110
|
|
|
// Force 'AS' to uppercase |
111
|
|
|
$column = str_ireplace(' AS ', ' AS ', $column); |
112
|
|
|
|
113
|
|
|
// Runs escape_column on both sides of an AS statement |
114
|
|
|
$column = array_map(array($this, __FUNCTION__), explode(' AS ', $column)); |
115
|
|
|
|
116
|
|
|
// Re-create the AS statement |
117
|
|
|
return implode(' AS ', $column); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
return preg_replace('/[^.*]+/', '`$0`', $column); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
$parts = explode(' ', $column); |
124
|
|
|
$column = ''; |
125
|
|
|
|
126
|
|
|
for ($i = 0, $c = count($parts); $i < $c; $i++) { |
127
|
|
|
// The column is always last |
128
|
|
|
if ($i == ($c - 1)) { |
129
|
|
|
$column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]); |
130
|
|
|
} else { // otherwise, it's a modifier |
131
|
|
|
$column .= $parts[$i].' '; |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
return $column; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
public function limit($limit, $offset = 0) |
138
|
|
|
{ |
139
|
|
|
return 'LIMIT '.$offset.', '.$limit; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
View Code Duplication |
public function compile_select($database) |
|
|
|
|
143
|
|
|
{ |
144
|
|
|
$sql = ($database['distinct'] == true) ? 'SELECT DISTINCT ' : 'SELECT '; |
145
|
|
|
$sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*'; |
146
|
|
|
|
147
|
|
|
if (count($database['from']) > 0) { |
148
|
|
|
$sql .= "\nFROM "; |
149
|
|
|
$sql .= implode(', ', $database['from']); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
if (count($database['join']) > 0) { |
153
|
|
|
foreach ($database['join'] as $join) { |
154
|
|
|
$sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions']; |
155
|
|
|
} |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
if (count($database['where']) > 0) { |
159
|
|
|
$sql .= "\nWHERE "; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
$sql .= implode("\n", $database['where']); |
163
|
|
|
|
164
|
|
|
if (count($database['groupby']) > 0) { |
165
|
|
|
$sql .= "\nGROUP BY "; |
166
|
|
|
$sql .= implode(', ', $database['groupby']); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
if (count($database['having']) > 0) { |
170
|
|
|
$sql .= "\nHAVING "; |
171
|
|
|
$sql .= implode("\n", $database['having']); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
if (count($database['orderby']) > 0) { |
175
|
|
|
$sql .= "\nORDER BY "; |
176
|
|
|
$sql .= implode(', ', $database['orderby']); |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
if (is_numeric($database['limit'])) { |
180
|
|
|
$sql .= "\n"; |
181
|
|
|
$sql .= $this->limit($database['limit'], $database['offset']); |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
return $sql; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
public function escape_str($str) |
188
|
|
|
{ |
189
|
|
|
if (! $this->db_config['escape']) { |
190
|
|
|
return $str; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
if (function_exists('sqlite_escape_string')) { |
194
|
|
|
$res = sqlite_escape_string($str); |
195
|
|
|
} else { |
196
|
|
|
$res = str_replace("'", "''", $str); |
197
|
|
|
} |
198
|
|
|
return $res; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
public function list_tables() |
202
|
|
|
{ |
203
|
|
|
$sql = "SELECT `name` FROM `sqlite_master` WHERE `type`='table' ORDER BY `name`;"; |
204
|
|
|
try { |
205
|
|
|
$result = $this->query($sql)->result(false, PDO::FETCH_ASSOC); |
206
|
|
|
$tables = array(); |
207
|
|
|
foreach ($result as $row) { |
208
|
|
|
$tables[] = current($row); |
209
|
|
|
} |
210
|
|
|
} catch (PDOException $e) { |
211
|
|
|
throw new Kohana_Database_Exception('database.error', $e->getMessage()); |
212
|
|
|
} |
213
|
|
|
return $tables; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
public function show_error() |
217
|
|
|
{ |
218
|
|
|
$err = $this->link->errorInfo(); |
219
|
|
|
return isset($err[2]) ? $err[2] : 'Unknown error!'; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
public function list_fields($table, $query = false) |
223
|
|
|
{ |
224
|
|
|
static $tables; |
225
|
|
|
if (is_object($query)) { |
226
|
|
|
if (empty($tables[$table])) { |
227
|
|
|
$tables[$table] = array(); |
228
|
|
|
|
229
|
|
|
foreach ($query->result() as $row) { |
230
|
|
|
$tables[$table][] = $row->name; |
231
|
|
|
} |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
return $tables[$table]; |
235
|
|
|
} else { |
236
|
|
|
$result = $this->link->query('PRAGMA table_info('.$this->escape_table($table).')'); |
237
|
|
|
|
238
|
|
|
foreach ($result as $row) { |
239
|
|
|
$tables[$table][$row['name']] = $this->sql_type($row['type']); |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
return $tables[$table]; |
243
|
|
|
} |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
public function field_data($table) |
247
|
|
|
{ |
248
|
|
|
Kohana::log('error', 'This method is under developing'); |
249
|
|
|
} |
250
|
|
|
/** |
251
|
|
|
* Version number query string |
252
|
|
|
* |
253
|
|
|
* @access public |
254
|
|
|
* @return string |
255
|
|
|
*/ |
256
|
|
|
public function version() |
257
|
|
|
{ |
258
|
|
|
return $this->link->getAttribute(constant("PDO::ATTR_SERVER_VERSION")); |
259
|
|
|
} |
260
|
|
|
} // End Database_PdoSqlite_Driver Class |
261
|
|
|
|
262
|
|
|
/* |
263
|
|
|
* PDO-sqlite Result |
264
|
|
|
*/ |
265
|
|
|
class Pdosqlite_Result extends Database_Result |
266
|
|
|
{ |
267
|
|
|
|
268
|
|
|
// Data fetching types |
269
|
|
|
protected $fetch_type = PDO::FETCH_OBJ; |
270
|
|
|
protected $return_type = PDO::FETCH_ASSOC; |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* Sets up the result variables. |
274
|
|
|
* |
275
|
|
|
* @param resource query result |
276
|
|
|
* @param resource database link |
277
|
|
|
* @param boolean return objects or arrays |
278
|
|
|
* @param string SQL query that was run |
279
|
|
|
* @param PDOStatement $result |
280
|
|
|
* @param PDO $link |
281
|
|
|
*/ |
282
|
|
|
public function __construct($result, $link, $object = true, $sql) |
283
|
|
|
{ |
284
|
|
|
if (is_object($result) or $result = $link->prepare($sql)) { |
285
|
|
|
// run the query. Return true if success, false otherwise |
286
|
|
|
if (! $result->execute()) { |
287
|
|
|
// Throw Kohana Exception with error message. See PDOStatement errorInfo() method |
288
|
|
|
$arr_infos = $result->errorInfo(); |
289
|
|
|
throw new Kohana_Database_Exception('database.error', $arr_infos[2]); |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
if (preg_match('/^SELECT|PRAGMA|EXPLAIN/i', $sql)) { |
293
|
|
|
$this->result = $result; |
294
|
|
|
$this->current_row = 0; |
295
|
|
|
|
296
|
|
|
$this->total_rows = $this->sqlite_row_count(); |
297
|
|
|
|
298
|
|
|
$this->fetch_type = ($object === true) ? PDO::FETCH_OBJ : PDO::FETCH_ASSOC; |
299
|
|
|
} elseif (preg_match('/^DELETE|INSERT|UPDATE/i', $sql)) { |
300
|
|
|
$this->insert_id = $link->lastInsertId(); |
301
|
|
|
|
302
|
|
|
$this->total_rows = $result->rowCount(); |
303
|
|
|
} |
304
|
|
|
} else { |
305
|
|
|
// SQL error |
306
|
|
|
throw new Kohana_Database_Exception('database.error', $link->errorInfo().' - '.$sql); |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
// Set result type |
310
|
|
|
$this->result($object); |
311
|
|
|
|
312
|
|
|
// Store the SQL |
313
|
|
|
$this->sql = $sql; |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
private function sqlite_row_count() |
317
|
|
|
{ |
318
|
|
|
$count = 0; |
319
|
|
|
while ($this->result->fetch()) { |
|
|
|
|
320
|
|
|
$count++; |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
// The query must be re-fetched now. |
324
|
|
|
$this->result->execute(); |
|
|
|
|
325
|
|
|
|
326
|
|
|
return $count; |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/* |
330
|
|
|
* Destructor: __destruct |
331
|
|
|
* Magic __destruct function, frees the result. |
332
|
|
|
*/ |
333
|
|
|
public function __destruct() |
334
|
|
|
{ |
335
|
|
|
if (is_object($this->result)) { |
336
|
|
|
$this->result->closeCursor(); |
|
|
|
|
337
|
|
|
$this->result = null; |
338
|
|
|
} |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
public function result($object = true, $type = PDO::FETCH_BOTH) |
342
|
|
|
{ |
343
|
|
|
$this->fetch_type = (bool) $object ? PDO::FETCH_OBJ : PDO::FETCH_BOTH; |
344
|
|
|
|
345
|
|
|
if ($this->fetch_type == PDO::FETCH_OBJ) { |
346
|
|
|
$this->return_type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass'; |
347
|
|
|
} else { |
348
|
|
|
$this->return_type = $type; |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
return $this; |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
public function as_array($object = null, $type = PDO::FETCH_ASSOC) |
355
|
|
|
{ |
356
|
|
|
return $this->result_array($object, $type); |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
public function result_array($object = null, $type = PDO::FETCH_ASSOC) |
360
|
|
|
{ |
361
|
|
|
$rows = array(); |
362
|
|
|
|
363
|
|
|
if (is_string($object)) { |
364
|
|
|
$fetch = $object; |
365
|
|
|
} elseif (is_bool($object)) { |
366
|
|
|
if ($object === true) { |
367
|
|
|
$fetch = PDO::FETCH_OBJ; |
368
|
|
|
|
369
|
|
|
// NOTE - The class set by $type must be defined before fetching the result, |
370
|
|
|
// autoloading is disabled to save a lot of stupid overhead. |
371
|
|
|
$type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass'; |
|
|
|
|
372
|
|
|
} else { |
373
|
|
|
$fetch = PDO::FETCH_OBJ; |
374
|
|
|
} |
375
|
|
|
} else { |
376
|
|
|
// Use the default config values |
377
|
|
|
$fetch = $this->fetch_type; |
378
|
|
|
|
379
|
|
|
if ($fetch == PDO::FETCH_OBJ) { |
380
|
|
|
$type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass'; |
|
|
|
|
381
|
|
|
} |
382
|
|
|
} |
383
|
|
|
try { |
384
|
|
|
while ($row = $this->result->fetch($fetch)) { |
|
|
|
|
385
|
|
|
$rows[] = $row; |
386
|
|
|
} |
387
|
|
|
} catch (PDOException $e) { |
388
|
|
|
throw new Kohana_Database_Exception('database.error', $e->getMessage()); |
389
|
|
|
return false; |
|
|
|
|
390
|
|
|
} |
391
|
|
|
return $rows; |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
public function list_fields() |
395
|
|
|
{ |
396
|
|
|
$field_names = array(); |
397
|
|
|
for ($i = 0, $max = $this->result->columnCount(); $i < $max; $i++) { |
|
|
|
|
398
|
|
|
$info = $this->result->getColumnMeta($i); |
|
|
|
|
399
|
|
|
$field_names[] = $info['name']; |
400
|
|
|
} |
401
|
|
|
return $field_names; |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
/** |
405
|
|
|
* @param integer $offset |
406
|
|
|
*/ |
407
|
|
|
public function seek($offset) |
408
|
|
|
{ |
409
|
|
|
// To request a scrollable cursor for your PDOStatement object, you must |
410
|
|
|
// set the PDO::ATTR_CURSOR attribute to PDO::CURSOR_SCROLL when you |
411
|
|
|
// prepare the statement. |
412
|
|
|
Kohana::log('error', get_class($this).' does not support scrollable cursors, '.__FUNCTION__.' call ignored'); |
413
|
|
|
|
414
|
|
|
return false; |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
public function offsetGet($offset) |
418
|
|
|
{ |
419
|
|
|
try { |
420
|
|
|
return $this->result->fetch($this->fetch_type, PDO::FETCH_ORI_ABS, $offset); |
|
|
|
|
421
|
|
|
} catch (PDOException $e) { |
422
|
|
|
throw new Kohana_Database_Exception('database.error', $e->getMessage()); |
423
|
|
|
} |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
public function rewind() |
427
|
|
|
{ |
428
|
|
|
// Same problem that seek() has, see above. |
429
|
|
|
return $this->seek(0); |
|
|
|
|
430
|
|
|
} |
431
|
|
|
} // End PdoSqlite_Result Class |
432
|
|
|
|
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.