1
|
|
|
<?php defined('SYSPATH') or die('No direct access allowed.'); |
2
|
|
|
/** |
3
|
|
|
* PostgreSQL 8.1+ Database Driver |
4
|
|
|
* |
5
|
|
|
* $Id: Pgsql.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_Pgsql_Driver extends Database_Driver |
13
|
|
|
{ |
14
|
|
|
|
15
|
|
|
// Database connection link |
16
|
|
|
protected $link; |
17
|
|
|
protected $db_config; |
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', 'PgSQL Database Driver Initialized'); |
29
|
|
|
} |
30
|
|
|
|
31
|
|
|
public function connect() |
32
|
|
|
{ |
33
|
|
|
// Check if link already exists |
34
|
|
|
if (is_resource($this->link)) { |
35
|
|
|
return $this->link; |
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
// Import the connect variables |
39
|
|
|
extract($this->db_config['connection']); |
40
|
|
|
|
41
|
|
|
// Persistent connections enabled? |
42
|
|
|
$connect = ($this->db_config['persistent'] == true) ? 'pg_pconnect' : 'pg_connect'; |
43
|
|
|
|
44
|
|
|
// Build the connection info |
45
|
|
|
$port = isset($port) ? 'port=\''.$port.'\'' : ''; |
46
|
|
|
$host = isset($host) ? 'host=\''.$host.'\' '.$port : ''; // if no host, connect with the socket |
47
|
|
|
|
48
|
|
|
$connection_string = $host.' dbname=\''.$database.'\' user=\''.$user.'\' password=\''.$pass.'\''; |
49
|
|
|
// Make the connection and select the database |
50
|
|
|
if ($this->link = $connect($connection_string)) { |
51
|
|
|
if ($charset = $this->db_config['character_set']) { |
52
|
|
|
echo $this->set_charset($charset); |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
// Clear password after successful connect |
56
|
|
|
$this->db_config['connection']['pass'] = null; |
57
|
|
|
|
58
|
|
|
return $this->link; |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
return false; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @param string $sql |
66
|
|
|
*/ |
67
|
|
View Code Duplication |
public function query($sql) |
|
|
|
|
68
|
|
|
{ |
69
|
|
|
// Only cache if it's turned on, and only cache if it's not a write statement |
70
|
|
|
if ($this->db_config['cache'] and ! preg_match('#\b(?:INSERT|UPDATE|SET)\b#i', $sql)) { |
71
|
|
|
$hash = $this->query_hash($sql); |
72
|
|
|
|
73
|
|
|
if (! isset($this->query_cache[$hash])) { |
74
|
|
|
// Set the cached object |
75
|
|
|
$this->query_cache[$hash] = new Pgsql_Result(pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql); |
76
|
|
|
} else { |
77
|
|
|
// Rewind cached result |
78
|
|
|
$this->query_cache[$hash]->rewind(); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
return $this->query_cache[$hash]; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
// Suppress warning triggered when a database error occurs (e.g., a constraint violation) |
85
|
|
|
return new Pgsql_Result(@pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
public function set_charset($charset) |
89
|
|
|
{ |
90
|
|
|
$this->query('SET client_encoding TO '.pg_escape_string($this->link, $charset)); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
View Code Duplication |
public function escape_table($table) |
|
|
|
|
94
|
|
|
{ |
95
|
|
|
if (!$this->db_config['escape']) { |
96
|
|
|
return $table; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
return '"'.str_replace('.', '"."', $table).'"'; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
View Code Duplication |
public function escape_column($column) |
|
|
|
|
103
|
|
|
{ |
104
|
|
|
if (!$this->db_config['escape']) { |
105
|
|
|
return $column; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
if ($column == '*') { |
109
|
|
|
return $column; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
// This matches any functions we support to SELECT. |
113
|
|
|
if (preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) { |
114
|
|
|
if (count($matches) == 3) { |
115
|
|
|
return $matches[1].'('.$this->escape_column($matches[2]).')'; |
116
|
|
|
} elseif (count($matches) == 5) { |
117
|
|
|
return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]); |
118
|
|
|
} |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
// This matches any modifiers we support to SELECT. |
122
|
|
|
if (! preg_match('/\b(?:all|distinct)\s/i', $column)) { |
123
|
|
|
if (stripos($column, ' AS ') !== false) { |
124
|
|
|
// Force 'AS' to uppercase |
125
|
|
|
$column = str_ireplace(' AS ', ' AS ', $column); |
126
|
|
|
|
127
|
|
|
// Runs escape_column on both sides of an AS statement |
128
|
|
|
$column = array_map(array($this, __FUNCTION__), explode(' AS ', $column)); |
129
|
|
|
|
130
|
|
|
// Re-create the AS statement |
131
|
|
|
return implode(' AS ', $column); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
return preg_replace('/[^.*]+/', '"$0"', $column); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
$parts = explode(' ', $column); |
138
|
|
|
$column = ''; |
139
|
|
|
|
140
|
|
|
for ($i = 0, $c = count($parts); $i < $c; $i++) { |
141
|
|
|
// The column is always last |
142
|
|
|
if ($i == ($c - 1)) { |
143
|
|
|
$column .= preg_replace('/[^.*]+/', '"$0"', $parts[$i]); |
144
|
|
|
} else { // otherwise, it's a modifier |
145
|
|
|
$column .= $parts[$i].' '; |
146
|
|
|
} |
147
|
|
|
} |
148
|
|
|
return $column; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
public function regex($field, $match, $type, $num_regexs) |
152
|
|
|
{ |
153
|
|
|
$prefix = ($num_regexs == 0) ? '' : $type; |
154
|
|
|
|
155
|
|
|
return $prefix.' '.$this->escape_column($field).' ~* \''.$this->escape_str($match).'\''; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
public function notregex($field, $match, $type, $num_regexs) |
159
|
|
|
{ |
160
|
|
|
$prefix = $num_regexs == 0 ? '' : $type; |
161
|
|
|
|
162
|
|
|
return $prefix.' '.$this->escape_column($field).' !~* \''.$this->escape_str($match) . '\''; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
public function limit($limit, $offset = 0) |
166
|
|
|
{ |
167
|
|
|
return 'LIMIT '.$limit.' OFFSET '.$offset; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
View Code Duplication |
public function compile_select($database) |
|
|
|
|
171
|
|
|
{ |
172
|
|
|
$sql = ($database['distinct'] == true) ? 'SELECT DISTINCT ' : 'SELECT '; |
173
|
|
|
$sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*'; |
174
|
|
|
|
175
|
|
|
if (count($database['from']) > 0) { |
176
|
|
|
$sql .= "\nFROM "; |
177
|
|
|
$sql .= implode(', ', $database['from']); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
if (count($database['join']) > 0) { |
181
|
|
|
foreach ($database['join'] as $join) { |
182
|
|
|
$sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions']; |
183
|
|
|
} |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
if (count($database['where']) > 0) { |
187
|
|
|
$sql .= "\nWHERE "; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
$sql .= implode("\n", $database['where']); |
191
|
|
|
|
192
|
|
|
if (count($database['groupby']) > 0) { |
193
|
|
|
$sql .= "\nGROUP BY "; |
194
|
|
|
$sql .= implode(', ', $database['groupby']); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
if (count($database['having']) > 0) { |
198
|
|
|
$sql .= "\nHAVING "; |
199
|
|
|
$sql .= implode("\n", $database['having']); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
if (count($database['orderby']) > 0) { |
203
|
|
|
$sql .= "\nORDER BY "; |
204
|
|
|
$sql .= implode(', ', $database['orderby']); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
if (is_numeric($database['limit'])) { |
208
|
|
|
$sql .= "\n"; |
209
|
|
|
$sql .= $this->limit($database['limit'], $database['offset']); |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
return $sql; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
View Code Duplication |
public function escape_str($str) |
|
|
|
|
216
|
|
|
{ |
217
|
|
|
if (!$this->db_config['escape']) { |
218
|
|
|
return $str; |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
is_resource($this->link) or $this->connect(); |
222
|
|
|
|
223
|
|
|
return pg_escape_string($this->link, $str); |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
public function list_tables() |
227
|
|
|
{ |
228
|
|
|
$sql = 'SELECT table_schema || \'.\' || table_name FROM information_schema.tables WHERE table_schema NOT IN (\'pg_catalog\', \'information_schema\')'; |
229
|
|
|
$result = $this->query($sql)->result(false, PGSQL_ASSOC); |
|
|
|
|
230
|
|
|
|
231
|
|
|
$retval = array(); |
232
|
|
|
foreach ($result as $row) { |
233
|
|
|
$retval[] = current($row); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
return $retval; |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
public function show_error() |
240
|
|
|
{ |
241
|
|
|
return pg_last_error($this->link); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
public function list_fields($table) |
245
|
|
|
{ |
246
|
|
|
$result = null; |
247
|
|
|
|
248
|
|
|
foreach ($this->field_data($table) as $row) { |
249
|
|
|
// Make an associative array |
250
|
|
|
$result[$row->column_name] = $this->sql_type($row->data_type); |
251
|
|
|
|
252
|
|
|
if (!strncmp($row->column_default, 'nextval(', 8)) { |
253
|
|
|
$result[$row->column_name]['sequenced'] = true; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
if ($row->is_nullable === 'YES') { |
257
|
|
|
$result[$row->column_name]['null'] = true; |
258
|
|
|
} |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
if (!isset($result)) { |
262
|
|
|
throw new Kohana_Database_Exception('database.table_not_found', $table); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
return $result; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
public function field_data($table) |
269
|
|
|
{ |
270
|
|
|
// http://www.postgresql.org/docs/8.3/static/infoschema-columns.html |
271
|
|
|
$result = $this->query(' |
272
|
|
|
SELECT column_name, column_default, is_nullable, data_type, udt_name, |
273
|
|
|
character_maximum_length, numeric_precision, numeric_precision_radix, numeric_scale |
274
|
|
|
FROM information_schema.columns |
275
|
|
|
WHERE table_name = \''. $this->escape_str($table) .'\' |
276
|
|
|
ORDER BY ordinal_position |
277
|
|
|
'); |
278
|
|
|
|
279
|
|
|
return $result->result_array(true); |
280
|
|
|
} |
281
|
|
|
} // End Database_Pgsql_Driver Class |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* PostgreSQL Result |
285
|
|
|
*/ |
286
|
|
|
class Pgsql_Result extends Database_Result |
287
|
|
|
{ |
288
|
|
|
|
289
|
|
|
// Data fetching types |
290
|
|
|
protected $fetch_type = 'pgsql_fetch_object'; |
291
|
|
|
protected $return_type = PGSQL_ASSOC; |
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* Sets up the result variables. |
295
|
|
|
* |
296
|
|
|
* @param resource query result |
297
|
|
|
* @param resource database link |
298
|
|
|
* @param boolean return objects or arrays |
299
|
|
|
* @param string SQL query that was run |
300
|
|
|
*/ |
301
|
|
|
public function __construct($result, $link, $object = true, $sql) |
302
|
|
|
{ |
303
|
|
|
$this->link = $link; |
|
|
|
|
304
|
|
|
$this->result = $result; |
305
|
|
|
|
306
|
|
|
// If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query |
307
|
|
|
if (is_resource($result)) { |
308
|
|
|
// Its an DELETE, INSERT, REPLACE, or UPDATE query |
309
|
|
|
if (preg_match('/^(?:delete|insert|replace|update)\b/iD', trim($sql), $matches)) { |
310
|
|
|
$this->insert_id = (strtolower($matches[0]) == 'insert') ? $this->insert_id() : false; |
311
|
|
|
$this->total_rows = pg_affected_rows($this->result); |
312
|
|
|
} else { |
313
|
|
|
$this->current_row = 0; |
314
|
|
|
$this->total_rows = pg_num_rows($this->result); |
315
|
|
|
$this->fetch_type = ($object === true) ? 'pg_fetch_object' : 'pg_fetch_array'; |
316
|
|
|
} |
317
|
|
|
} else { |
318
|
|
|
throw new Kohana_Database_Exception('database.error', pg_last_error().' - '.$sql); |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
// Set result type |
322
|
|
|
$this->result($object); |
323
|
|
|
|
324
|
|
|
// Store the SQL |
325
|
|
|
$this->sql = $sql; |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* Magic __destruct function, frees the result. |
330
|
|
|
*/ |
331
|
|
|
public function __destruct() |
332
|
|
|
{ |
333
|
|
|
if (is_resource($this->result)) { |
334
|
|
|
pg_free_result($this->result); |
335
|
|
|
} |
336
|
|
|
} |
337
|
|
|
|
338
|
|
View Code Duplication |
public function result($object = true, $type = PGSQL_ASSOC) |
|
|
|
|
339
|
|
|
{ |
340
|
|
|
$this->fetch_type = ((bool) $object) ? 'pg_fetch_object' : 'pg_fetch_array'; |
341
|
|
|
|
342
|
|
|
// This check has to be outside the previous statement, because we do not |
343
|
|
|
// know the state of fetch_type when $object = NULL |
344
|
|
|
// NOTE - The class set by $type must be defined before fetching the result, |
345
|
|
|
// autoloading is disabled to save a lot of stupid overhead. |
346
|
|
|
if ($this->fetch_type == 'pg_fetch_object') { |
347
|
|
|
$this->return_type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass'; |
348
|
|
|
} else { |
349
|
|
|
$this->return_type = $type; |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
return $this; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
public function as_array($object = null, $type = PGSQL_ASSOC) |
356
|
|
|
{ |
357
|
|
|
return $this->result_array($object, $type); |
358
|
|
|
} |
359
|
|
|
|
360
|
|
View Code Duplication |
public function result_array($object = null, $type = PGSQL_ASSOC) |
|
|
|
|
361
|
|
|
{ |
362
|
|
|
$rows = array(); |
363
|
|
|
|
364
|
|
|
if (is_string($object)) { |
365
|
|
|
$fetch = $object; |
366
|
|
|
} elseif (is_bool($object)) { |
367
|
|
|
if ($object === true) { |
368
|
|
|
$fetch = 'pg_fetch_object'; |
369
|
|
|
|
370
|
|
|
// NOTE - The class set by $type must be defined before fetching the result, |
371
|
|
|
// autoloading is disabled to save a lot of stupid overhead. |
372
|
|
|
$type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass'; |
373
|
|
|
} else { |
374
|
|
|
$fetch = 'pg_fetch_array'; |
375
|
|
|
} |
376
|
|
|
} else { |
377
|
|
|
// Use the default config values |
378
|
|
|
$fetch = $this->fetch_type; |
379
|
|
|
|
380
|
|
|
if ($fetch == 'pg_fetch_object') { |
381
|
|
|
$type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass'; |
382
|
|
|
} |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
if ($this->total_rows) { |
386
|
|
|
pg_result_seek($this->result, 0); |
387
|
|
|
|
388
|
|
|
while ($row = $fetch($this->result, null, $type)) { |
389
|
|
|
$rows[] = $row; |
390
|
|
|
} |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
return $rows; |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
public function insert_id() |
|
|
|
|
397
|
|
|
{ |
398
|
|
|
if ($this->insert_id === null) { |
399
|
|
|
$query = 'SELECT LASTVAL() AS insert_id'; |
400
|
|
|
|
401
|
|
|
// Disable error reporting for this, just to silence errors on |
402
|
|
|
// tables that have no serial column. |
403
|
|
|
$ER = error_reporting(0); |
|
|
|
|
404
|
|
|
|
405
|
|
|
$result = pg_query($this->link, $query); |
406
|
|
|
$insert_id = pg_fetch_array($result, null, PGSQL_ASSOC); |
407
|
|
|
|
408
|
|
|
$this->insert_id = $insert_id['insert_id']; |
409
|
|
|
|
410
|
|
|
// Reset error reporting |
411
|
|
|
error_reporting($ER); |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
return $this->insert_id; |
415
|
|
|
} |
416
|
|
|
|
417
|
|
View Code Duplication |
public function seek($offset) |
|
|
|
|
418
|
|
|
{ |
419
|
|
|
if ($this->offsetExists($offset) and pg_result_seek($this->result, $offset)) { |
420
|
|
|
// Set the current row to the offset |
421
|
|
|
$this->current_row = $offset; |
422
|
|
|
|
423
|
|
|
return true; |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
return false; |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
public function list_fields() |
430
|
|
|
{ |
431
|
|
|
$field_names = array(); |
432
|
|
|
|
433
|
|
|
$fields = pg_num_fields($this->result); |
434
|
|
|
for ($i = 0; $i < $fields; ++$i) { |
435
|
|
|
$field_names[] = pg_field_name($this->result, $i); |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
return $field_names; |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
/** |
442
|
|
|
* ArrayAccess: offsetGet |
443
|
|
|
*/ |
444
|
|
View Code Duplication |
public function offsetGet($offset) |
|
|
|
|
445
|
|
|
{ |
446
|
|
|
if (! $this->seek($offset)) { |
447
|
|
|
return false; |
448
|
|
|
} |
449
|
|
|
|
450
|
|
|
// Return the row by calling the defined fetching callback |
451
|
|
|
$fetch = $this->fetch_type; |
452
|
|
|
return $fetch($this->result, null, $this->return_type); |
453
|
|
|
} |
454
|
|
|
} // End Pgsql_Result Class |
455
|
|
|
|
456
|
|
|
/** |
457
|
|
|
* PostgreSQL Prepared Statement (experimental) |
458
|
|
|
*/ |
459
|
|
|
class Kohana_Pgsql_Statement |
460
|
|
|
{ |
461
|
|
|
protected $link = null; |
462
|
|
|
protected $stmt; |
463
|
|
|
|
464
|
|
|
public function __construct($sql, $link) |
465
|
|
|
{ |
466
|
|
|
$this->link = $link; |
467
|
|
|
|
468
|
|
|
$this->stmt = $this->link->prepare($sql); |
469
|
|
|
|
470
|
|
|
return $this; |
|
|
|
|
471
|
|
|
} |
472
|
|
|
|
473
|
|
|
public function __destruct() |
474
|
|
|
{ |
475
|
|
|
$this->stmt->close(); |
476
|
|
|
} |
477
|
|
|
|
478
|
|
|
// Sets the bind parameters |
479
|
|
|
public function bind_params() |
480
|
|
|
{ |
481
|
|
|
$argv = func_get_args(); |
|
|
|
|
482
|
|
|
return $this; |
483
|
|
|
} |
484
|
|
|
|
485
|
|
|
// sets the statement values to the bound parameters |
486
|
|
|
public function set_vals() |
487
|
|
|
{ |
488
|
|
|
return $this; |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
// Runs the statement |
492
|
|
|
public function execute() |
493
|
|
|
{ |
494
|
|
|
return $this; |
495
|
|
|
} |
496
|
|
|
} |
497
|
|
|
|
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.