Commit | Line | Data |
---|---|---|
49926145 | 1 | <?php |
2 | ||
3 | // This file is part of Moodle - http://moodle.org/ | |
4 | // | |
5 | // Moodle is free software: you can redistribute it and/or modify | |
6 | // it under the terms of the GNU General Public License as published by | |
7 | // the Free Software Foundation, either version 3 of the License, or | |
8 | // (at your option) any later version. | |
9 | // | |
10 | // Moodle is distributed in the hope that it will be useful, | |
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | // GNU General Public License for more details. | |
14 | // | |
15 | // You should have received a copy of the GNU General Public License | |
16 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
17 | ||
18 | ||
19 | /** | |
20 | * Native pgsql class representing moodle database interface. | |
21 | * | |
66c0ee78 | 22 | * @package core |
a0eb2e97 | 23 | * @subpackage dml_driver |
49926145 | 24 | * @copyright 2008 Petr Skoda (http://skodak.org) |
25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
26 | */ | |
158622bd | 27 | |
f2ed3f05 PS |
28 | defined('MOODLE_INTERNAL') || die(); |
29 | ||
158622bd | 30 | require_once($CFG->libdir.'/dml/moodle_database.php'); |
31 | require_once($CFG->libdir.'/dml/pgsql_native_moodle_recordset.php'); | |
10f375aa | 32 | require_once($CFG->libdir.'/dml/pgsql_native_moodle_temptables.php'); |
158622bd | 33 | |
34 | /** | |
35 | * Native pgsql class representing moodle database interface. | |
a0eb2e97 AB |
36 | * |
37 | * @package core | |
38 | * @subpackage dml_driver | |
39 | * @copyright 2008 Petr Skoda (http://skodak.org) | |
40 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
158622bd | 41 | */ |
42 | class pgsql_native_moodle_database extends moodle_database { | |
43 | ||
db7aea38 | 44 | protected $pgsql = null; |
db7aea38 | 45 | protected $bytea_oid = null; |
158622bd | 46 | |
190ff43b | 47 | protected $last_error_reporting; // To handle pgsql driver default verbosity |
ba98912f | 48 | |
158622bd | 49 | /** |
50 | * Detects if all needed PHP stuff installed. | |
51 | * Note: can be used before connect() | |
52 | * @return mixed true if ok, string if something | |
53 | */ | |
54 | public function driver_installed() { | |
55 | if (!extension_loaded('pgsql')) { | |
56 | return get_string('pgsqlextensionisnotpresentinphp', 'install'); | |
57 | } | |
58 | return true; | |
59 | } | |
60 | ||
61 | /** | |
62 | * Returns database family type - describes SQL dialect | |
63 | * Note: can be used before connect() | |
64 | * @return string db family name (mysql, postgres, mssql, oracle, etc.) | |
65 | */ | |
66 | public function get_dbfamily() { | |
67 | return 'postgres'; | |
68 | } | |
69 | ||
70 | /** | |
71 | * Returns more specific database driver type | |
72 | * Note: can be used before connect() | |
4a6c2ab8 | 73 | * @return string db type mysqli, pgsql, oci, mssql, sqlsrv |
158622bd | 74 | */ |
75 | protected function get_dbtype() { | |
76 | return 'pgsql'; | |
77 | } | |
78 | ||
79 | /** | |
80 | * Returns general database library name | |
81 | * Note: can be used before connect() | |
eb123deb | 82 | * @return string db type pdo, native |
158622bd | 83 | */ |
84 | protected function get_dblibrary() { | |
85 | return 'native'; | |
86 | } | |
87 | ||
88 | /** | |
89 | * Returns localised database type name | |
90 | * Note: can be used before connect() | |
91 | * @return string | |
92 | */ | |
93 | public function get_name() { | |
bba50616 | 94 | return get_string('nativepgsql', 'install'); |
158622bd | 95 | } |
96 | ||
3b093310 | 97 | /** |
98 | * Returns localised database configuration help. | |
99 | * Note: can be used before connect() | |
100 | * @return string | |
101 | */ | |
102 | public function get_configuration_help() { | |
103 | return get_string('nativepgsqlhelp', 'install'); | |
104 | } | |
105 | ||
158622bd | 106 | /** |
107 | * Returns localised database description | |
108 | * Note: can be used before connect() | |
109 | * @return string | |
110 | */ | |
111 | public function get_configuration_hints() { | |
bba50616 | 112 | return get_string('databasesettingssub_postgres7', 'install'); |
158622bd | 113 | } |
114 | ||
115 | /** | |
116 | * Connect to db | |
117 | * Must be called before other methods. | |
6df26010 AB |
118 | * @param string $dbhost The database host. |
119 | * @param string $dbuser The database username. | |
120 | * @param string $dbpass The database username's password. | |
121 | * @param string $dbname The name of the database being connected to. | |
158622bd | 122 | * @param mixed $prefix string means moodle db prefix, false used for external databases where prefix not used |
123 | * @param array $dboptions driver specific options | |
ce152606 | 124 | * @return bool true |
125 | * @throws dml_connection_exception if error | |
158622bd | 126 | */ |
beaa43db | 127 | public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) { |
189f3ee9 | 128 | if ($prefix == '' and !$this->external) { |
129 | //Enforce prefixes for everybody but mysql | |
130 | throw new dml_exception('prefixcannotbeempty', $this->get_dbfamily()); | |
131 | } | |
132 | ||
ce152606 | 133 | $driverstatus = $this->driver_installed(); |
134 | ||
135 | if ($driverstatus !== true) { | |
136 | throw new dml_exception('dbdriverproblem', $driverstatus); | |
137 | } | |
138 | ||
beaa43db | 139 | $this->store_settings($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions); |
158622bd | 140 | |
dd2ce459 | 141 | $pass = addcslashes($this->dbpass, "'\\"); |
142 | ||
143 | // Unix socket connections should have lower overhead | |
e6b854a0 | 144 | if (!empty($this->dboptions['dbsocket']) and ($this->dbhost === 'localhost' or $this->dbhost === '127.0.0.1')) { |
dd2ce459 | 145 | $connection = "user='$this->dbuser' password='$pass' dbname='$this->dbname'"; |
3c1293fe PS |
146 | if (strpos($this->dboptions['dbsocket'], '/') !== false) { |
147 | $connection = $connection." host='".$this->dboptions['dbsocket']."'"; | |
148 | } | |
dd2ce459 | 149 | } else { |
3c1293fe | 150 | $this->dboptions['dbsocket'] = ''; |
e6b854a0 | 151 | if (empty($this->dbname)) { |
152 | // probably old style socket connection - do not add port | |
153 | $port = ""; | |
154 | } else if (empty($this->dboptions['dbport'])) { | |
155 | $port = "port ='5432'"; | |
d139b067 | 156 | } else { |
e6b854a0 | 157 | $port = "port ='".$this->dboptions['dbport']."'"; |
d139b067 | 158 | } |
e6b854a0 | 159 | $connection = "host='$this->dbhost' $port user='$this->dbuser' password='$pass' dbname='$this->dbname'"; |
dd2ce459 | 160 | } |
161 | ||
ce152606 | 162 | ob_start(); |
a0b7200d | 163 | if (empty($this->dboptions['dbpersist'])) { |
dd2ce459 | 164 | $this->pgsql = pg_connect($connection, PGSQL_CONNECT_FORCE_NEW); |
165 | } else { | |
166 | $this->pgsql = pg_pconnect($connection, PGSQL_CONNECT_FORCE_NEW); | |
167 | } | |
ce152606 | 168 | $dberr = ob_get_contents(); |
169 | ob_end_clean(); | |
117bd748 | 170 | |
158622bd | 171 | $status = pg_connection_status($this->pgsql); |
9a4f9e33 | 172 | |
173 | if ($status === false or $status === PGSQL_CONNECTION_BAD) { | |
158622bd | 174 | $this->pgsql = null; |
ce152606 | 175 | throw new dml_connection_exception($dberr); |
158622bd | 176 | } |
9a4f9e33 | 177 | |
1500142b | 178 | $this->query_start("--pg_set_client_encoding()", null, SQL_QUERY_AUX); |
158622bd | 179 | pg_set_client_encoding($this->pgsql, 'utf8'); |
1500142b | 180 | $this->query_end(true); |
181 | ||
db7aea38 | 182 | // find out the bytea oid |
7063c802 | 183 | $sql = "SELECT oid FROM pg_type WHERE typname = 'bytea'"; |
1500142b | 184 | $this->query_start($sql, null, SQL_QUERY_AUX); |
db7aea38 | 185 | $result = pg_query($this->pgsql, $sql); |
1500142b | 186 | $this->query_end($result); |
ce152606 | 187 | |
b6265b7e | 188 | $this->bytea_oid = pg_fetch_result($result, 0, 0); |
db7aea38 | 189 | pg_free_result($result); |
190 | if ($this->bytea_oid === false) { | |
ce152606 | 191 | $this->pgsql = null; |
192 | throw new dml_connection_exception('Can not read bytea type.'); | |
db7aea38 | 193 | } |
1500142b | 194 | |
b6265b7e | 195 | // Connection stabilised and configured, going to instantiate the temptables controller |
10f375aa EL |
196 | $this->temptables = new pgsql_native_moodle_temptables($this); |
197 | ||
158622bd | 198 | return true; |
199 | } | |
200 | ||
201 | /** | |
202 | * Close database connection and release all resources | |
203 | * and memory (especially circular memory references). | |
204 | * Do NOT use connect() again, create a new instance if needed. | |
205 | */ | |
206 | public function dispose() { | |
2434f624 | 207 | parent::dispose(); // Call parent dispose to write/close session and other common stuff before closing connection |
158622bd | 208 | if ($this->pgsql) { |
209 | pg_close($this->pgsql); | |
210 | $this->pgsql = null; | |
211 | } | |
158622bd | 212 | } |
213 | ||
ba98912f | 214 | |
215 | /** | |
216 | * Called before each db query. | |
217 | * @param string $sql | |
218 | * @param array array of parameters | |
219 | * @param int $type type of query | |
220 | * @param mixed $extrainfo driver specific extra information | |
221 | * @return void | |
222 | */ | |
223 | protected function query_start($sql, array $params=null, $type, $extrainfo=null) { | |
224 | parent::query_start($sql, $params, $type, $extrainfo); | |
225 | // pgsql driver tents to send debug to output, we do not need that ;-) | |
190ff43b | 226 | $this->last_error_reporting = error_reporting(0); |
ba98912f | 227 | } |
228 | ||
229 | /** | |
230 | * Called immediately after each db query. | |
231 | * @param mixed db specific result | |
232 | * @return void | |
233 | */ | |
234 | protected function query_end($result) { | |
190ff43b | 235 | // reset original debug level |
236 | error_reporting($this->last_error_reporting); | |
ba98912f | 237 | parent::query_end($result); |
238 | } | |
239 | ||
158622bd | 240 | /** |
241 | * Returns database server info array | |
6df26010 | 242 | * @return array Array containing 'description' and 'version' info |
158622bd | 243 | */ |
244 | public function get_server_info() { | |
245 | static $info; | |
246 | if (!$info) { | |
1500142b | 247 | $this->query_start("--pg_version()", null, SQL_QUERY_AUX); |
158622bd | 248 | $info = pg_version($this->pgsql); |
1500142b | 249 | $this->query_end(true); |
158622bd | 250 | } |
251 | return array('description'=>$info['server'], 'version'=>$info['server']); | |
252 | } | |
253 | ||
254 | protected function is_min_version($version) { | |
255 | $server = $this->get_server_info(); | |
256 | $server = $server['version']; | |
257 | return version_compare($server, $version, '>='); | |
258 | } | |
259 | ||
260 | /** | |
261 | * Returns supported query parameter types | |
6df26010 | 262 | * @return int bitmask of accepted SQL_PARAMS_* |
158622bd | 263 | */ |
264 | protected function allowed_param_types() { | |
265 | return SQL_PARAMS_DOLLAR; | |
266 | } | |
267 | ||
268 | /** | |
269 | * Returns last error reported by database engine. | |
9331d879 | 270 | * @return string error message |
158622bd | 271 | */ |
272 | public function get_last_error() { | |
273 | return pg_last_error($this->pgsql); | |
274 | } | |
275 | ||
276 | /** | |
6df26010 AB |
277 | * Return tables in database WITHOUT current prefix. |
278 | * @param bool $usecache if true, returns list of cached tables. | |
158622bd | 279 | * @return array of table names in lowercase and without prefix |
280 | */ | |
117679db | 281 | public function get_tables($usecache=true) { |
282 | if ($usecache and $this->tables !== null) { | |
283 | return $this->tables; | |
284 | } | |
285 | $this->tables = array(); | |
d5b3deec | 286 | $prefix = str_replace('_', '|_', $this->prefix); |
00511e21 EL |
287 | // Get them from information_schema instead of catalog as far as |
288 | // we want to get only own session temp objects (catalog returns all) | |
289 | $sql = "SELECT table_name | |
290 | FROM information_schema.tables | |
d5b3deec | 291 | WHERE table_name LIKE '$prefix%' ESCAPE '|' |
00511e21 | 292 | AND table_type IN ('BASE TABLE', 'LOCAL TEMPORARY')"; |
1500142b | 293 | $this->query_start($sql, null, SQL_QUERY_AUX); |
294 | $result = pg_query($this->pgsql, $sql); | |
295 | $this->query_end($result); | |
d139b067 | 296 | |
1500142b | 297 | if ($result) { |
158622bd | 298 | while ($row = pg_fetch_row($result)) { |
299 | $tablename = reset($row); | |
300 | if (strpos($tablename, $this->prefix) !== 0) { | |
301 | continue; | |
302 | } | |
303 | $tablename = substr($tablename, strlen($this->prefix)); | |
117679db | 304 | $this->tables[$tablename] = $tablename; |
158622bd | 305 | } |
306 | pg_free_result($result); | |
307 | } | |
117679db | 308 | return $this->tables; |
158622bd | 309 | } |
310 | ||
311 | /** | |
6df26010 AB |
312 | * Return table indexes - everything lowercased. |
313 | * @param string $table The table we want to get indexes from. | |
158622bd | 314 | * @return array of arrays |
315 | */ | |
316 | public function get_indexes($table) { | |
317 | $indexes = array(); | |
318 | $tablename = $this->prefix.$table; | |
319 | ||
320 | $sql = "SELECT * | |
321 | FROM pg_catalog.pg_indexes | |
322 | WHERE tablename = '$tablename'"; | |
1500142b | 323 | |
324 | $this->query_start($sql, null, SQL_QUERY_AUX); | |
325 | $result = pg_query($this->pgsql, $sql); | |
326 | $this->query_end($result); | |
327 | ||
328 | if ($result) { | |
158622bd | 329 | while ($row = pg_fetch_assoc($result)) { |
330 | if (!preg_match('/CREATE (|UNIQUE )INDEX ([^\s]+) ON '.$tablename.' USING ([^\s]+) \(([^\)]+)\)/i', $row['indexdef'], $matches)) { | |
331 | continue; | |
332 | } | |
333 | if ($matches[4] === 'id') { | |
334 | continue; | |
335 | } | |
80ffbad3 | 336 | $columns = explode(',', $matches[4]); |
d64514aa | 337 | $columns = array_map(array($this, 'trim_quotes'), $columns); |
471dc4d3 | 338 | $indexes[$row['indexname']] = array('unique'=>!empty($matches[1]), |
80ffbad3 | 339 | 'columns'=>$columns); |
158622bd | 340 | } |
341 | pg_free_result($result); | |
342 | } | |
343 | return $indexes; | |
344 | } | |
345 | ||
346 | /** | |
2434f624 | 347 | * Returns detailed information about columns in table. This information is cached internally. |
158622bd | 348 | * @param string $table name |
349 | * @param bool $usecache | |
350 | * @return array array of database_column_info objects indexed with column names | |
351 | */ | |
352 | public function get_columns($table, $usecache=true) { | |
353 | if ($usecache and isset($this->columns[$table])) { | |
354 | return $this->columns[$table]; | |
355 | } | |
356 | ||
357 | $this->columns[$table] = array(); | |
358 | ||
359 | $tablename = $this->prefix.$table; | |
360 | ||
361 | $sql = "SELECT a.attnum, a.attname AS field, t.typname AS type, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, d.adsrc | |
362 | FROM pg_catalog.pg_class c | |
9a4f9e33 | 363 | JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid |
158622bd | 364 | JOIN pg_catalog.pg_type t ON t.oid = a.atttypid |
365 | LEFT JOIN pg_catalog.pg_attrdef d ON (d.adrelid = c.oid AND d.adnum = a.attnum) | |
366 | WHERE relkind = 'r' AND c.relname = '$tablename' AND c.reltype > 0 AND a.attnum > 0 | |
367 | ORDER BY a.attnum"; | |
368 | ||
1500142b | 369 | $this->query_start($sql, null, SQL_QUERY_AUX); |
370 | $result = pg_query($this->pgsql, $sql); | |
371 | $this->query_end($result); | |
372 | ||
373 | if (!$result) { | |
158622bd | 374 | return array(); |
375 | } | |
376 | while ($rawcolumn = pg_fetch_object($result)) { | |
377 | ||
ac6f1a82 | 378 | $info = new stdClass(); |
158622bd | 379 | $info->name = $rawcolumn->field; |
380 | $matches = null; | |
381 | ||
382 | if ($rawcolumn->type === 'varchar') { | |
158622bd | 383 | $info->type = 'varchar'; |
384 | $info->meta_type = 'C'; | |
385 | $info->max_length = $rawcolumn->atttypmod - 4; | |
386 | $info->scale = null; | |
298d9250 | 387 | $info->not_null = ($rawcolumn->attnotnull === 't'); |
388 | $info->has_default = ($rawcolumn->atthasdef === 't'); | |
158622bd | 389 | if ($info->has_default) { |
390 | $parts = explode('::', $rawcolumn->adsrc); | |
391 | if (count($parts) > 1) { | |
392 | $info->default_value = reset($parts); | |
393 | $info->default_value = trim($info->default_value, "'"); | |
394 | } else { | |
395 | $info->default_value = $rawcolumn->adsrc; | |
396 | } | |
397 | } else { | |
398 | $info->default_value = null; | |
399 | } | |
400 | $info->primary_key = false; | |
401 | $info->binary = false; | |
402 | $info->unsigned = null; | |
403 | $info->auto_increment= false; | |
404 | $info->unique = null; | |
405 | ||
406 | } else if (preg_match('/int(\d)/i', $rawcolumn->type, $matches)) { | |
407 | $info->type = 'int'; | |
408 | if (strpos($rawcolumn->adsrc, 'nextval') === 0) { | |
409 | $info->primary_key = true; | |
410 | $info->meta_type = 'R'; | |
411 | $info->unique = true; | |
412 | $info->auto_increment= true; | |
413 | $info->has_default = false; | |
414 | } else { | |
415 | $info->primary_key = false; | |
416 | $info->meta_type = 'I'; | |
417 | $info->unique = null; | |
418 | $info->auto_increment= false; | |
298d9250 | 419 | $info->has_default = ($rawcolumn->atthasdef === 't'); |
158622bd | 420 | } |
421 | $info->max_length = $matches[1]; | |
422 | $info->scale = null; | |
298d9250 | 423 | $info->not_null = ($rawcolumn->attnotnull === 't'); |
158622bd | 424 | if ($info->has_default) { |
69857fa0 | 425 | $info->default_value = trim($rawcolumn->adsrc, '()'); |
158622bd | 426 | } else { |
427 | $info->default_value = null; | |
428 | } | |
429 | $info->binary = false; | |
430 | $info->unsigned = false; | |
431 | ||
432 | } else if ($rawcolumn->type === 'numeric') { | |
433 | $info->type = $rawcolumn->type; | |
434 | $info->meta_type = 'N'; | |
435 | $info->primary_key = false; | |
436 | $info->binary = false; | |
437 | $info->unsigned = null; | |
438 | $info->auto_increment= false; | |
439 | $info->unique = null; | |
298d9250 | 440 | $info->not_null = ($rawcolumn->attnotnull === 't'); |
441 | $info->has_default = ($rawcolumn->atthasdef === 't'); | |
158622bd | 442 | if ($info->has_default) { |
69857fa0 | 443 | $info->default_value = trim($rawcolumn->adsrc, '()'); |
158622bd | 444 | } else { |
445 | $info->default_value = null; | |
446 | } | |
447 | $info->max_length = $rawcolumn->atttypmod >> 16; | |
448 | $info->scale = ($rawcolumn->atttypmod & 0xFFFF) - 4; | |
449 | ||
450 | } else if (preg_match('/float(\d)/i', $rawcolumn->type, $matches)) { | |
451 | $info->type = 'float'; | |
452 | $info->meta_type = 'N'; | |
453 | $info->primary_key = false; | |
454 | $info->binary = false; | |
455 | $info->unsigned = null; | |
456 | $info->auto_increment= false; | |
457 | $info->unique = null; | |
298d9250 | 458 | $info->not_null = ($rawcolumn->attnotnull === 't'); |
459 | $info->has_default = ($rawcolumn->atthasdef === 't'); | |
158622bd | 460 | if ($info->has_default) { |
69857fa0 | 461 | $info->default_value = trim($rawcolumn->adsrc, '()'); |
158622bd | 462 | } else { |
463 | $info->default_value = null; | |
464 | } | |
465 | // just guess expected number of deciaml places :-( | |
466 | if ($matches[1] == 8) { | |
467 | // total 15 digits | |
468 | $info->max_length = 8; | |
469 | $info->scale = 7; | |
470 | } else { | |
471 | // total 6 digits | |
472 | $info->max_length = 4; | |
473 | $info->scale = 2; | |
474 | } | |
475 | ||
476 | } else if ($rawcolumn->type === 'text') { | |
477 | $info->type = $rawcolumn->type; | |
478 | $info->meta_type = 'X'; | |
479 | $info->max_length = -1; | |
480 | $info->scale = null; | |
298d9250 | 481 | $info->not_null = ($rawcolumn->attnotnull === 't'); |
482 | $info->has_default = ($rawcolumn->atthasdef === 't'); | |
158622bd | 483 | if ($info->has_default) { |
484 | $parts = explode('::', $rawcolumn->adsrc); | |
485 | if (count($parts) > 1) { | |
486 | $info->default_value = reset($parts); | |
487 | $info->default_value = trim($info->default_value, "'"); | |
488 | } else { | |
489 | $info->default_value = $rawcolumn->adsrc; | |
490 | } | |
491 | } else { | |
492 | $info->default_value = null; | |
493 | } | |
494 | $info->primary_key = false; | |
495 | $info->binary = false; | |
496 | $info->unsigned = null; | |
497 | $info->auto_increment= false; | |
498 | $info->unique = null; | |
499 | ||
500 | } else if ($rawcolumn->type === 'bytea') { | |
501 | $info->type = $rawcolumn->type; | |
502 | $info->meta_type = 'B'; | |
503 | $info->max_length = -1; | |
504 | $info->scale = null; | |
298d9250 | 505 | $info->not_null = ($rawcolumn->attnotnull === 't'); |
158622bd | 506 | $info->has_default = false; |
507 | $info->default_value = null; | |
508 | $info->primary_key = false; | |
509 | $info->binary = true; | |
510 | $info->unsigned = null; | |
511 | $info->auto_increment= false; | |
512 | $info->unique = null; | |
513 | ||
514 | } | |
515 | ||
516 | $this->columns[$table][$info->name] = new database_column_info($info); | |
517 | } | |
518 | ||
e4f9c142 | 519 | pg_free_result($result); |
520 | ||
158622bd | 521 | return $this->columns[$table]; |
522 | } | |
523 | ||
8fb501e9 EL |
524 | /** |
525 | * Normalise values based in RDBMS dependencies (booleans, LOBs...) | |
526 | * | |
527 | * @param database_column_info $column column metadata corresponding with the value we are going to normalise | |
528 | * @param mixed $value value we are going to normalise | |
529 | * @return mixed the normalised value | |
530 | */ | |
e3acc8af | 531 | protected function normalise_value($column, $value) { |
e618cdf3 PS |
532 | $this->detect_objects($value); |
533 | ||
8fb501e9 EL |
534 | if (is_bool($value)) { // Always, convert boolean to int |
535 | $value = (int)$value; | |
536 | ||
f3e317dc | 537 | } else if ($column->meta_type === 'B') { // BLOB detected, we return 'blob' array instead of raw value to allow |
8fb501e9 EL |
538 | if (!is_null($value)) { // binding/executing code later to know about its nature |
539 | $value = array('blob' => $value); | |
540 | } | |
541 | ||
542 | } else if ($value === '') { | |
f3e317dc | 543 | if ($column->meta_type === 'I' or $column->meta_type === 'F' or $column->meta_type === 'N') { |
8fb501e9 EL |
544 | $value = 0; // prevent '' problems in numeric fields |
545 | } | |
546 | } | |
547 | return $value; | |
548 | } | |
549 | ||
158622bd | 550 | /** |
551 | * Is db in unicode mode? | |
552 | * @return bool | |
553 | */ | |
554 | public function setup_is_unicodedb() { | |
555 | /// Get PostgreSQL server_encoding value | |
1500142b | 556 | $sql = "SHOW server_encoding"; |
557 | $this->query_start($sql, null, SQL_QUERY_AUX); | |
558 | $result = pg_query($this->pgsql, $sql); | |
559 | $this->query_end($result); | |
560 | ||
561 | if (!$result) { | |
158622bd | 562 | return false; |
563 | } | |
564 | $rawcolumn = pg_fetch_object($result); | |
565 | $encoding = $rawcolumn->server_encoding; | |
566 | pg_free_result($result); | |
567 | ||
568 | return (strtoupper($encoding) == 'UNICODE' || strtoupper($encoding) == 'UTF8'); | |
569 | } | |
570 | ||
158622bd | 571 | /** |
572 | * Do NOT use in code, to be used by database_manager only! | |
573 | * @param string $sql query | |
22d77567 | 574 | * @return bool true |
6df26010 | 575 | * @throws dml_exception A DML specific exception is thrown for any errors. |
158622bd | 576 | */ |
577 | public function change_database_structure($sql) { | |
117679db | 578 | $this->reset_caches(); |
1500142b | 579 | |
580 | $this->query_start($sql, null, SQL_QUERY_STRUCTURE); | |
581 | $result = pg_query($this->pgsql, $sql); | |
582 | $this->query_end($result); | |
583 | ||
e4f9c142 | 584 | pg_free_result($result); |
158622bd | 585 | return true; |
586 | } | |
587 | ||
588 | /** | |
589 | * Execute general sql query. Should be used only when no other method suitable. | |
a681b6c0 | 590 | * Do NOT use this to make changes in db structure, use database_manager methods instead! |
158622bd | 591 | * @param string $sql query |
592 | * @param array $params query parameters | |
22d77567 | 593 | * @return bool true |
6df26010 | 594 | * @throws dml_exception A DML specific exception is thrown for any errors. |
158622bd | 595 | */ |
596 | public function execute($sql, array $params=null) { | |
597 | list($sql, $params, $type) = $this->fix_sql_params($sql, $params); | |
598 | ||
599 | if (strpos($sql, ';') !== false) { | |
22d77567 | 600 | throw new coding_exception('moodle_database::execute() Multiple sql statements found or bound parameters not used properly in query!'); |
158622bd | 601 | } |
602 | ||
1500142b | 603 | $this->query_start($sql, $params, SQL_QUERY_UPDATE); |
158622bd | 604 | $result = pg_query_params($this->pgsql, $sql, $params); |
1500142b | 605 | $this->query_end($result); |
158622bd | 606 | |
e4f9c142 | 607 | pg_free_result($result); |
158622bd | 608 | return true; |
609 | } | |
610 | ||
611 | /** | |
612 | * Get a number of records as a moodle_recordset using a SQL statement. | |
613 | * | |
614 | * Since this method is a little less readable, use of it should be restricted to | |
615 | * code where it's possible there might be large datasets being returned. For known | |
616 | * small datasets use get_records_sql - it leads to simpler code. | |
617 | * | |
dafa20e8 AB |
618 | * The return type is like: |
619 | * @see function get_recordset. | |
158622bd | 620 | * |
621 | * @param string $sql the SQL select query to execute. | |
622 | * @param array $params array of sql parameters | |
623 | * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set). | |
624 | * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set). | |
5212b07f | 625 | * @return moodle_recordset instance |
6df26010 | 626 | * @throws dml_exception A DML specific exception is thrown for any errors. |
158622bd | 627 | */ |
628 | public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) { | |
088a4df8 | 629 | $limitfrom = (int)$limitfrom; |
630 | $limitnum = (int)$limitnum; | |
3ff8bf26 | 631 | $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom; |
632 | $limitnum = ($limitnum < 0) ? 0 : $limitnum; | |
158622bd | 633 | if ($limitfrom or $limitnum) { |
158622bd | 634 | if ($limitnum < 1) { |
72a3902f | 635 | $limitnum = "ALL"; |
4335aa29 PS |
636 | } else if (PHP_INT_MAX - $limitnum < $limitfrom) { |
637 | // this is a workaround for weird max int problem | |
638 | $limitnum = "ALL"; | |
158622bd | 639 | } |
640 | $sql .= " LIMIT $limitnum OFFSET $limitfrom"; | |
641 | } | |
642 | ||
643 | list($sql, $params, $type) = $this->fix_sql_params($sql, $params); | |
644 | ||
1500142b | 645 | $this->query_start($sql, $params, SQL_QUERY_SELECT); |
158622bd | 646 | $result = pg_query_params($this->pgsql, $sql, $params); |
1500142b | 647 | $this->query_end($result); |
158622bd | 648 | |
158622bd | 649 | return $this->create_recordset($result); |
650 | } | |
651 | ||
652 | protected function create_recordset($result) { | |
db7aea38 | 653 | return new pgsql_native_moodle_recordset($result, $this->bytea_oid); |
158622bd | 654 | } |
655 | ||
656 | /** | |
657 | * Get a number of records as an array of objects using a SQL statement. | |
658 | * | |
dafa20e8 AB |
659 | * Return value is like: |
660 | * @see function get_records. | |
158622bd | 661 | * |
662 | * @param string $sql the SQL select query to execute. The first column of this SELECT statement | |
663 | * must be a unique value (usually the 'id' field), as it will be used as the key of the | |
664 | * returned array. | |
665 | * @param array $params array of sql parameters | |
666 | * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set). | |
667 | * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set). | |
3503dcad | 668 | * @return array of objects, or empty array if no records were found |
6df26010 | 669 | * @throws dml_exception A DML specific exception is thrown for any errors. |
158622bd | 670 | */ |
671 | public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) { | |
088a4df8 | 672 | $limitfrom = (int)$limitfrom; |
673 | $limitnum = (int)$limitnum; | |
3ff8bf26 | 674 | $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom; |
675 | $limitnum = ($limitnum < 0) ? 0 : $limitnum; | |
158622bd | 676 | if ($limitfrom or $limitnum) { |
158622bd | 677 | if ($limitnum < 1) { |
72a3902f | 678 | $limitnum = "ALL"; |
4335aa29 PS |
679 | } else if (PHP_INT_MAX - $limitnum < $limitfrom) { |
680 | // this is a workaround for weird max int problem | |
681 | $limitnum = "ALL"; | |
158622bd | 682 | } |
683 | $sql .= " LIMIT $limitnum OFFSET $limitfrom"; | |
684 | } | |
685 | ||
686 | list($sql, $params, $type) = $this->fix_sql_params($sql, $params); | |
1500142b | 687 | $this->query_start($sql, $params, SQL_QUERY_SELECT); |
158622bd | 688 | $result = pg_query_params($this->pgsql, $sql, $params); |
1500142b | 689 | $this->query_end($result); |
158622bd | 690 | |
db7aea38 | 691 | // find out if there are any blobs |
692 | $numrows = pg_num_fields($result); | |
693 | $blobs = array(); | |
694 | for($i=0; $i<$numrows; $i++) { | |
695 | $type_oid = pg_field_type_oid($result, $i); | |
696 | if ($type_oid == $this->bytea_oid) { | |
697 | $blobs[] = pg_field_name($result, $i); | |
698 | } | |
699 | } | |
158622bd | 700 | |
701 | $rows = pg_fetch_all($result); | |
702 | pg_free_result($result); | |
703 | ||
704 | $return = array(); | |
705 | if ($rows) { | |
706 | foreach ($rows as $row) { | |
707 | $id = reset($row); | |
db7aea38 | 708 | if ($blobs) { |
709 | foreach ($blobs as $blob) { | |
cda6d44b | 710 | // note: in PostgreSQL 9.0 the returned blobs are hexencoded by default - see http://www.postgresql.org/docs/9.0/static/runtime-config-client.html#GUC-BYTEA-OUTPUT |
2b4cf032 | 711 | $row[$blob] = $row[$blob] !== null ? pg_unescape_bytea($row[$blob]) : null; |
db7aea38 | 712 | } |
713 | } | |
758ba89a | 714 | if (isset($return[$id])) { |
715 | $colname = key($row); | |
716 | debugging("Did you remember to make the first column something unique in your call to get_records? Duplicate value '$id' found in column '$colname'.", DEBUG_DEVELOPER); | |
717 | } | |
158622bd | 718 | $return[$id] = (object)$row; |
719 | } | |
720 | } | |
db7aea38 | 721 | |
158622bd | 722 | return $return; |
723 | } | |
724 | ||
725 | /** | |
726 | * Selects records and return values (first field) as an array using a SQL statement. | |
727 | * | |
728 | * @param string $sql The SQL query | |
729 | * @param array $params array of sql parameters | |
3503dcad | 730 | * @return array of values |
6df26010 | 731 | * @throws dml_exception A DML specific exception is thrown for any errors. |
158622bd | 732 | */ |
733 | public function get_fieldset_sql($sql, array $params=null) { | |
734 | list($sql, $params, $type) = $this->fix_sql_params($sql, $params); | |
735 | ||
1500142b | 736 | $this->query_start($sql, $params, SQL_QUERY_SELECT); |
158622bd | 737 | $result = pg_query_params($this->pgsql, $sql, $params); |
1500142b | 738 | $this->query_end($result); |
158622bd | 739 | |
158622bd | 740 | $return = pg_fetch_all_columns($result, 0); |
741 | pg_free_result($result); | |
9a4f9e33 | 742 | |
158622bd | 743 | return $return; |
744 | } | |
745 | ||
746 | /** | |
747 | * Insert new record into database, as fast as possible, no safety checks, lobs not supported. | |
748 | * @param string $table name | |
749 | * @param mixed $params data record as object or array | |
750 | * @param bool $returnit return it of inserted record | |
751 | * @param bool $bulk true means repeated inserts expected | |
752 | * @param bool $customsequence true if 'id' included in $params, disables $returnid | |
3503dcad | 753 | * @return bool|int true or new id |
6df26010 | 754 | * @throws dml_exception A DML specific exception is thrown for any errors. |
158622bd | 755 | */ |
756 | public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) { | |
757 | if (!is_array($params)) { | |
758 | $params = (array)$params; | |
759 | } | |
760 | ||
761 | $returning = ""; | |
762 | ||
763 | if ($customsequence) { | |
764 | if (!isset($params['id'])) { | |
22d77567 | 765 | throw new coding_exception('moodle_database::insert_record_raw() id field must be specified if custom sequences used.'); |
158622bd | 766 | } |
767 | $returnid = false; | |
768 | } else { | |
769 | if ($returnid) { | |
4b5732f9 | 770 | $returning = "RETURNING id"; |
771 | unset($params['id']); | |
158622bd | 772 | } else { |
773 | unset($params['id']); | |
774 | } | |
775 | } | |
776 | ||
777 | if (empty($params)) { | |
22d77567 | 778 | throw new coding_exception('moodle_database::insert_record_raw() no fields found.'); |
158622bd | 779 | } |
780 | ||
781 | $fields = implode(',', array_keys($params)); | |
782 | $values = array(); | |
e618cdf3 PS |
783 | $i = 1; |
784 | foreach ($params as $value) { | |
785 | $this->detect_objects($value); | |
786 | $values[] = "\$".$i++; | |
9a4f9e33 | 787 | } |
158622bd | 788 | $values = implode(',', $values); |
789 | ||
790 | $sql = "INSERT INTO {$this->prefix}$table ($fields) VALUES($values) $returning"; | |
1500142b | 791 | $this->query_start($sql, $params, SQL_QUERY_INSERT); |
158622bd | 792 | $result = pg_query_params($this->pgsql, $sql, $params); |
1500142b | 793 | $this->query_end($result); |
158622bd | 794 | |
158622bd | 795 | if ($returning !== "") { |
796 | $row = pg_fetch_assoc($result); | |
797 | $params['id'] = reset($row); | |
158622bd | 798 | } |
e4f9c142 | 799 | pg_free_result($result); |
158622bd | 800 | |
801 | if (!$returnid) { | |
802 | return true; | |
803 | } | |
804 | ||
805 | return (int)$params['id']; | |
806 | } | |
807 | ||
808 | /** | |
809 | * Insert a record into a table and return the "id" field if required. | |
810 | * | |
811 | * Some conversions and safety checks are carried out. Lobs are supported. | |
812 | * If the return ID isn't required, then this just reports success as true/false. | |
813 | * $data is an object containing needed data | |
814 | * @param string $table The database table to be inserted into | |
815 | * @param object $data A data object with values for one or more fields in the record | |
816 | * @param bool $returnid Should the id of the newly created record entry be returned? If this option is not requested then true/false is returned. | |
3503dcad | 817 | * @return bool|int true or new id |
6df26010 | 818 | * @throws dml_exception A DML specific exception is thrown for any errors. |
158622bd | 819 | */ |
820 | public function insert_record($table, $dataobject, $returnid=true, $bulk=false) { | |
d8fa8e40 | 821 | $dataobject = (array)$dataobject; |
158622bd | 822 | |
823 | $columns = $this->get_columns($table); | |
158622bd | 824 | $cleaned = array(); |
825 | $blobs = array(); | |
826 | ||
827 | foreach ($dataobject as $field=>$value) { | |
d8fa8e40 PS |
828 | if ($field === 'id') { |
829 | continue; | |
830 | } | |
158622bd | 831 | if (!isset($columns[$field])) { |
832 | continue; | |
833 | } | |
834 | $column = $columns[$field]; | |
8fb501e9 EL |
835 | $normalised_value = $this->normalise_value($column, $value); |
836 | if (is_array($normalised_value) && array_key_exists('blob', $normalised_value)) { | |
837 | $cleaned[$field] = '@#BLOB#@'; | |
838 | $blobs[$field] = $normalised_value['blob']; | |
839 | } else { | |
840 | $cleaned[$field] = $normalised_value; | |
158622bd | 841 | } |
158622bd | 842 | } |
843 | ||
158622bd | 844 | if (empty($blobs)) { |
845 | return $this->insert_record_raw($table, $cleaned, $returnid, $bulk); | |
846 | } | |
847 | ||
22d77567 | 848 | $id = $this->insert_record_raw($table, $cleaned, true, $bulk); |
158622bd | 849 | |
850 | foreach ($blobs as $key=>$value) { | |
158622bd | 851 | $value = pg_escape_bytea($this->pgsql, $value); |
852 | $sql = "UPDATE {$this->prefix}$table SET $key = '$value'::bytea WHERE id = $id"; | |
1500142b | 853 | $this->query_start($sql, NULL, SQL_QUERY_UPDATE); |
e4f9c142 | 854 | $result = pg_query($this->pgsql, $sql); |
1500142b | 855 | $this->query_end($result); |
e4f9c142 | 856 | if ($result !== false) { |
857 | pg_free_result($result); | |
858 | } | |
158622bd | 859 | } |
860 | ||
861 | return ($returnid ? $id : true); | |
862 | ||
863 | } | |
864 | ||
865 | /** | |
866 | * Import a record into a table, id field is required. | |
867 | * Safety checks are NOT carried out. Lobs are supported. | |
868 | * | |
869 | * @param string $table name of database table to be inserted into | |
870 | * @param object $dataobject A data object with values for one or more fields in the record | |
22d77567 | 871 | * @return bool true |
6df26010 | 872 | * @throws dml_exception A DML specific exception is thrown for any errors. |
158622bd | 873 | */ |
874 | public function import_record($table, $dataobject) { | |
d8fa8e40 | 875 | $dataobject = (array)$dataobject; |
158622bd | 876 | |
158622bd | 877 | $columns = $this->get_columns($table); |
878 | $cleaned = array(); | |
f3e317dc | 879 | $blobs = array(); |
158622bd | 880 | |
881 | foreach ($dataobject as $field=>$value) { | |
e618cdf3 | 882 | $this->detect_objects($value); |
158622bd | 883 | if (!isset($columns[$field])) { |
884 | continue; | |
885 | } | |
f3e317dc PS |
886 | if ($columns[$field]->meta_type === 'B') { |
887 | if (!is_null($value)) { | |
888 | $cleaned[$field] = '@#BLOB#@'; | |
889 | $blobs[$field] = $value; | |
890 | continue; | |
891 | } | |
892 | } | |
893 | ||
158622bd | 894 | $cleaned[$field] = $value; |
895 | } | |
896 | ||
f3e317dc | 897 | $this->insert_record_raw($table, $cleaned, false, true, true); |
992c19a7 | 898 | $id = $dataobject['id']; |
f3e317dc PS |
899 | |
900 | foreach ($blobs as $key=>$value) { | |
901 | $value = pg_escape_bytea($this->pgsql, $value); | |
902 | $sql = "UPDATE {$this->prefix}$table SET $key = '$value'::bytea WHERE id = $id"; | |
903 | $this->query_start($sql, NULL, SQL_QUERY_UPDATE); | |
904 | $result = pg_query($this->pgsql, $sql); | |
905 | $this->query_end($result); | |
906 | if ($result !== false) { | |
907 | pg_free_result($result); | |
908 | } | |
909 | } | |
910 | ||
911 | return true; | |
158622bd | 912 | } |
913 | ||
914 | /** | |
915 | * Update record in database, as fast as possible, no safety checks, lobs not supported. | |
916 | * @param string $table name | |
917 | * @param mixed $params data record as object or array | |
918 | * @param bool true means repeated updates expected | |
22d77567 | 919 | * @return bool true |
6df26010 | 920 | * @throws dml_exception A DML specific exception is thrown for any errors. |
158622bd | 921 | */ |
922 | public function update_record_raw($table, $params, $bulk=false) { | |
d8fa8e40 PS |
923 | $params = (array)$params; |
924 | ||
158622bd | 925 | if (!isset($params['id'])) { |
22d77567 | 926 | throw new coding_exception('moodle_database::update_record_raw() id field must be specified.'); |
158622bd | 927 | } |
928 | $id = $params['id']; | |
929 | unset($params['id']); | |
930 | ||
931 | if (empty($params)) { | |
22d77567 | 932 | throw new coding_exception('moodle_database::update_record_raw() no fields found.'); |
158622bd | 933 | } |
934 | ||
935 | $i = 1; | |
936 | ||
937 | $sets = array(); | |
938 | foreach ($params as $field=>$value) { | |
e618cdf3 | 939 | $this->detect_objects($value); |
158622bd | 940 | $sets[] = "$field = \$".$i++; |
941 | } | |
942 | ||
943 | $params[] = $id; // last ? in WHERE condition | |
944 | ||
945 | $sets = implode(',', $sets); | |
946 | $sql = "UPDATE {$this->prefix}$table SET $sets WHERE id=\$".$i; | |
947 | ||
1500142b | 948 | $this->query_start($sql, $params, SQL_QUERY_UPDATE); |
158622bd | 949 | $result = pg_query_params($this->pgsql, $sql, $params); |
1500142b | 950 | $this->query_end($result); |
158622bd | 951 | |
e4f9c142 | 952 | pg_free_result($result); |
158622bd | 953 | return true; |
954 | } | |
955 | ||
956 | /** | |
957 | * Update a record in a table | |
958 | * | |
959 | * $dataobject is an object containing needed data | |
960 | * Relies on $dataobject having a variable "id" to | |
961 | * specify the record to update | |
962 | * | |
963 | * @param string $table The database table to be checked against. | |
964 | * @param object $dataobject An object with contents equal to fieldname=>fieldvalue. Must have an entry for 'id' to map to the table specified. | |
965 | * @param bool true means repeated updates expected | |
22d77567 | 966 | * @return bool true |
6df26010 | 967 | * @throws dml_exception A DML specific exception is thrown for any errors. |
158622bd | 968 | */ |
969 | public function update_record($table, $dataobject, $bulk=false) { | |
d8fa8e40 | 970 | $dataobject = (array)$dataobject; |
158622bd | 971 | |
158622bd | 972 | $columns = $this->get_columns($table); |
973 | $cleaned = array(); | |
d246cdd2 | 974 | $blobs = array(); |
158622bd | 975 | |
976 | foreach ($dataobject as $field=>$value) { | |
977 | if (!isset($columns[$field])) { | |
978 | continue; | |
979 | } | |
d246cdd2 | 980 | $column = $columns[$field]; |
8fb501e9 EL |
981 | $normalised_value = $this->normalise_value($column, $value); |
982 | if (is_array($normalised_value) && array_key_exists('blob', $normalised_value)) { | |
983 | $cleaned[$field] = '@#BLOB#@'; | |
984 | $blobs[$field] = $normalised_value['blob']; | |
985 | } else { | |
986 | $cleaned[$field] = $normalised_value; | |
158622bd | 987 | } |
158622bd | 988 | } |
989 | ||
22d77567 | 990 | $this->update_record_raw($table, $cleaned, $bulk); |
d246cdd2 | 991 | |
992 | if (empty($blobs)) { | |
993 | return true; | |
994 | } | |
995 | ||
1045bec0 | 996 | $id = (int)$dataobject['id']; |
22d77567 | 997 | |
d246cdd2 | 998 | foreach ($blobs as $key=>$value) { |
d246cdd2 | 999 | $value = pg_escape_bytea($this->pgsql, $value); |
1000 | $sql = "UPDATE {$this->prefix}$table SET $key = '$value'::bytea WHERE id = $id"; | |
1500142b | 1001 | $this->query_start($sql, NULL, SQL_QUERY_UPDATE); |
d246cdd2 | 1002 | $result = pg_query($this->pgsql, $sql); |
1500142b | 1003 | $this->query_end($result); |
22d77567 | 1004 | |
d246cdd2 | 1005 | pg_free_result($result); |
1006 | } | |
1007 | ||
1008 | return true; | |
158622bd | 1009 | } |
1010 | ||
1011 | /** | |
1012 | * Set a single field in every table record which match a particular WHERE clause. | |
1013 | * | |
1014 | * @param string $table The database table to be checked against. | |
1015 | * @param string $newfield the field to set. | |
1016 | * @param string $newvalue the value to set the field to. | |
1017 | * @param string $select A fragment of SQL to be used in a where clause in the SQL call. | |
1018 | * @param array $params array of sql parameters | |
22d77567 | 1019 | * @return bool true |
6df26010 | 1020 | * @throws dml_exception A DML specific exception is thrown for any errors. |
158622bd | 1021 | */ |
1022 | public function set_field_select($table, $newfield, $newvalue, $select, array $params=null) { | |
762bd0df | 1023 | |
158622bd | 1024 | if ($select) { |
1025 | $select = "WHERE $select"; | |
1026 | } | |
1027 | if (is_null($params)) { | |
1028 | $params = array(); | |
1029 | } | |
1030 | list($select, $params, $type) = $this->fix_sql_params($select, $params); | |
1031 | $i = count($params)+1; | |
1032 | ||
762bd0df | 1033 | /// Get column metadata |
1034 | $columns = $this->get_columns($table); | |
1035 | $column = $columns[$newfield]; | |
1036 | ||
8fb501e9 EL |
1037 | $normalised_value = $this->normalise_value($column, $newvalue); |
1038 | if (is_array($normalised_value) && array_key_exists('blob', $normalised_value)) { | |
762bd0df | 1039 | /// Update BYTEA and return |
8fb501e9 EL |
1040 | $normalised_value = pg_escape_bytea($this->pgsql, $normalised_value['blob']); |
1041 | $sql = "UPDATE {$this->prefix}$table SET $newfield = '$normalised_value'::bytea $select"; | |
762bd0df | 1042 | $this->query_start($sql, NULL, SQL_QUERY_UPDATE); |
1043 | $result = pg_query_params($this->pgsql, $sql, $params); | |
1044 | $this->query_end($result); | |
1045 | pg_free_result($result); | |
1046 | return true; | |
1047 | } | |
1048 | ||
8fb501e9 | 1049 | if (is_null($normalised_value)) { |
158622bd | 1050 | $newfield = "$newfield = NULL"; |
1051 | } else { | |
1052 | $newfield = "$newfield = \$".$i; | |
8fb501e9 | 1053 | $params[] = $normalised_value; |
158622bd | 1054 | } |
1055 | $sql = "UPDATE {$this->prefix}$table SET $newfield $select"; | |
1056 | ||
1500142b | 1057 | $this->query_start($sql, $params, SQL_QUERY_UPDATE); |
158622bd | 1058 | $result = pg_query_params($this->pgsql, $sql, $params); |
1500142b | 1059 | $this->query_end($result); |
158622bd | 1060 | |
e4f9c142 | 1061 | pg_free_result($result); |
158622bd | 1062 | |
1063 | return true; | |
1064 | } | |
1065 | ||
1066 | /** | |
1067 | * Delete one or more records from a table which match a particular WHERE clause. | |
1068 | * | |
1069 | * @param string $table The database table to be checked against. | |
1070 | * @param string $select A fragment of SQL to be used in a where clause in the SQL call (used to define the selection criteria). | |
1071 | * @param array $params array of sql parameters | |
22d77567 | 1072 | * @return bool true |
6df26010 | 1073 | * @throws dml_exception A DML specific exception is thrown for any errors. |
158622bd | 1074 | */ |
1075 | public function delete_records_select($table, $select, array $params=null) { | |
1076 | if ($select) { | |
1077 | $select = "WHERE $select"; | |
1078 | } | |
1079 | $sql = "DELETE FROM {$this->prefix}$table $select"; | |
1080 | ||
1081 | list($sql, $params, $type) = $this->fix_sql_params($sql, $params); | |
1082 | ||
1500142b | 1083 | $this->query_start($sql, $params, SQL_QUERY_UPDATE); |
158622bd | 1084 | $result = pg_query_params($this->pgsql, $sql, $params); |
1500142b | 1085 | $this->query_end($result); |
158622bd | 1086 | |
e4f9c142 | 1087 | pg_free_result($result); |
158622bd | 1088 | |
1089 | return true; | |
1090 | } | |
1091 | ||
6055f89d PS |
1092 | /** |
1093 | * Returns 'LIKE' part of a query. | |
1094 | * | |
1095 | * @param string $fieldname usually name of the table column | |
1096 | * @param string $param usually bound query parameter (?, :named) | |
1097 | * @param bool $casesensitive use case sensitive search | |
1098 | * @param bool $accensensitive use accent sensitive search (not all databases support accent insensitive) | |
16114b9d | 1099 | * @param bool $notlike true means "NOT LIKE" |
6055f89d PS |
1100 | * @param string $escapechar escape char for '%' and '_' |
1101 | * @return string SQL code fragment | |
1102 | */ | |
16114b9d | 1103 | public function sql_like($fieldname, $param, $casesensitive = true, $accentsensitive = true, $notlike = false, $escapechar = '\\') { |
6055f89d PS |
1104 | if (strpos($param, '%') !== false) { |
1105 | debugging('Potential SQL injection detected, sql_ilike() expects bound parameters (? or :named)'); | |
1106 | } | |
1107 | $escapechar = pg_escape_string($this->pgsql, $escapechar); // prevents problems with C-style escapes of enclosing '\' | |
1108 | ||
1109 | // postgresql does not support accent insensitive text comparisons, sorry | |
1110 | if ($casesensitive) { | |
16114b9d | 1111 | $LIKE = $notlike ? 'NOT LIKE' : 'LIKE'; |
6055f89d | 1112 | } else { |
16114b9d | 1113 | $LIKE = $notlike ? 'NOT ILIKE' : 'ILIKE'; |
6055f89d | 1114 | } |
16114b9d | 1115 | return "$fieldname $LIKE $param ESCAPE '$escapechar'"; |
6055f89d PS |
1116 | } |
1117 | ||
158622bd | 1118 | public function sql_ilike() { |
2f8eea34 | 1119 | debugging('sql_ilike() is deprecated, please use sql_like() instead'); |
158622bd | 1120 | return 'ILIKE'; |
1121 | } | |
1122 | ||
1123 | public function sql_bitxor($int1, $int2) { | |
30899510 | 1124 | return '((' . $int1 . ') # (' . $int2 . '))'; |
158622bd | 1125 | } |
1126 | ||
1127 | public function sql_cast_char2int($fieldname, $text=false) { | |
1128 | return ' CAST(' . $fieldname . ' AS INT) '; | |
1129 | } | |
1130 | ||
1131 | public function sql_cast_char2real($fieldname, $text=false) { | |
1132 | return " $fieldname::real "; | |
1133 | } | |
1134 | ||
1135 | public function sql_concat() { | |
1136 | $arr = func_get_args(); | |
1137 | $s = implode(' || ', $arr); | |
1138 | if ($s === '') { | |
1139 | return " '' "; | |
1140 | } | |
4cf2e5d5 EL |
1141 | // Add always empty string element so integer-exclusive concats |
1142 | // will work without needing to cast each element explicity | |
1143 | return " '' || $s "; | |
158622bd | 1144 | } |
1145 | ||
1146 | public function sql_concat_join($separator="' '", $elements=array()) { | |
1147 | for ($n=count($elements)-1; $n > 0 ; $n--) { | |
1148 | array_splice($elements, $n, 0, $separator); | |
1149 | } | |
1150 | $s = implode(' || ', $elements); | |
1151 | if ($s === '') { | |
1152 | return " '' "; | |
1153 | } | |
1154 | return " $s "; | |
1155 | } | |
1156 | ||
158622bd | 1157 | public function sql_regex_supported() { |
1158 | return true; | |
1159 | } | |
1160 | ||
1161 | public function sql_regex($positivematch=true) { | |
1162 | return $positivematch ? '~*' : '!~*'; | |
1163 | } | |
1164 | ||
5e9dd017 | 1165 | /// session locking |
1166 | public function session_lock_supported() { | |
4b5732f9 | 1167 | return true; |
5e9dd017 | 1168 | } |
1169 | ||
2b0e3941 PS |
1170 | /** |
1171 | * Obtain session lock | |
1172 | * @param int $rowid id of the row with session record | |
1173 | * @param int $timeout max allowed time to wait for the lock in seconds | |
1174 | * @return bool success | |
1175 | */ | |
1176 | public function get_session_lock($rowid, $timeout) { | |
bba50616 | 1177 | // NOTE: there is a potential locking problem for database running |
5e9dd017 | 1178 | // multiple instances of moodle, we could try to use pg_advisory_lock(int, int), |
1179 | // luckily there is not a big chance that they would collide | |
1180 | if (!$this->session_lock_supported()) { | |
1181 | return; | |
1182 | } | |
1183 | ||
2b0e3941 PS |
1184 | parent::get_session_lock($rowid, $timeout); |
1185 | ||
1186 | $timeoutmilli = $timeout * 1000; | |
1187 | ||
1188 | $sql = "SET statement_timeout TO $timeoutmilli"; | |
1189 | $this->query_start($sql, null, SQL_QUERY_AUX); | |
1190 | $result = pg_query($this->pgsql, $sql); | |
1191 | $this->query_end($result); | |
1192 | ||
1193 | if ($result) { | |
1194 | pg_free_result($result); | |
1195 | } | |
1196 | ||
5e9dd017 | 1197 | $sql = "SELECT pg_advisory_lock($rowid)"; |
1198 | $this->query_start($sql, null, SQL_QUERY_AUX); | |
2b0e3941 PS |
1199 | $start = time(); |
1200 | $result = pg_query($this->pgsql, $sql); | |
1201 | $end = time(); | |
1202 | try { | |
1203 | $this->query_end($result); | |
1204 | } catch (dml_exception $ex) { | |
1205 | if ($end - $start >= $timeout) { | |
1206 | throw new dml_sessionwait_exception(); | |
1207 | } else { | |
1208 | throw $ex; | |
1209 | } | |
1210 | } | |
1211 | ||
1212 | if ($result) { | |
1213 | pg_free_result($result); | |
1214 | } | |
1215 | ||
1216 | $sql = "SET statement_timeout TO DEFAULT"; | |
1217 | $this->query_start($sql, null, SQL_QUERY_AUX); | |
5e9dd017 | 1218 | $result = pg_query($this->pgsql, $sql); |
1219 | $this->query_end($result); | |
1220 | ||
1221 | if ($result) { | |
1222 | pg_free_result($result); | |
1223 | } | |
1224 | } | |
1225 | ||
1226 | public function release_session_lock($rowid) { | |
1227 | if (!$this->session_lock_supported()) { | |
1228 | return; | |
1229 | } | |
1230 | parent::release_session_lock($rowid); | |
1231 | ||
1232 | $sql = "SELECT pg_advisory_unlock($rowid)"; | |
1233 | $this->query_start($sql, null, SQL_QUERY_AUX); | |
1234 | $result = pg_query($this->pgsql, $sql); | |
1235 | $this->query_end($result); | |
1236 | ||
1237 | if ($result) { | |
1238 | pg_free_result($result); | |
1239 | } | |
1240 | } | |
1241 | ||
fb76304b | 1242 | /// transactions |
1243 | /** | |
d5a8d9aa PS |
1244 | * Driver specific start of real database transaction, |
1245 | * this can not be used directly in code. | |
1246 | * @return void | |
fb76304b | 1247 | */ |
d5a8d9aa | 1248 | protected function begin_transaction() { |
1500142b | 1249 | $sql = "BEGIN ISOLATION LEVEL READ COMMITTED"; |
1250 | $this->query_start($sql, NULL, SQL_QUERY_AUX); | |
1251 | $result = pg_query($this->pgsql, $sql); | |
1252 | $this->query_end($result); | |
1253 | ||
fb76304b | 1254 | pg_free_result($result); |
fb76304b | 1255 | } |
1256 | ||
1257 | /** | |
d5a8d9aa PS |
1258 | * Driver specific commit of real database transaction, |
1259 | * this can not be used directly in code. | |
1260 | * @return void | |
fb76304b | 1261 | */ |
d5a8d9aa | 1262 | protected function commit_transaction() { |
1500142b | 1263 | $sql = "COMMIT"; |
1264 | $this->query_start($sql, NULL, SQL_QUERY_AUX); | |
1265 | $result = pg_query($this->pgsql, $sql); | |
1266 | $this->query_end($result); | |
1267 | ||
fb76304b | 1268 | pg_free_result($result); |
fb76304b | 1269 | } |
1270 | ||
1271 | /** | |
d5a8d9aa PS |
1272 | * Driver specific abort of real database transaction, |
1273 | * this can not be used directly in code. | |
1274 | * @return void | |
fb76304b | 1275 | */ |
d5a8d9aa | 1276 | protected function rollback_transaction() { |
1500142b | 1277 | $sql = "ROLLBACK"; |
1278 | $this->query_start($sql, NULL, SQL_QUERY_AUX); | |
1279 | $result = pg_query($this->pgsql, $sql); | |
1280 | $this->query_end($result); | |
1281 | ||
fb76304b | 1282 | pg_free_result($result); |
fb76304b | 1283 | } |
d64514aa | 1284 | |
1285 | /** | |
1286 | * Helper function trimming (whitespace + quotes) any string | |
1287 | * needed because PG uses to enclose with double quotes some | |
1288 | * fields in indexes definition and others | |
1289 | * | |
1290 | * @param string $str string to apply whitespace + quotes trim | |
1291 | * @return string trimmed string | |
1292 | */ | |
1293 | private function trim_quotes($str) { | |
1294 | return trim(trim($str), "'\""); | |
1295 | } | |
158622bd | 1296 | } |