Merge branch 'MDL-62945-master' of https://github.com/HuongNV13/moodle
[moodle.git] / lib / dml / pdo_moodle_database.php
blob7f719059bd96214bddc9c3f7416ffb038837015d
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * Experimental pdo database class
20 * @package core_dml
21 * @copyright 2008 Andrei Bautu
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 defined('MOODLE_INTERNAL') || die();
27 require_once(__DIR__.'/moodle_database.php');
28 require_once(__DIR__.'/pdo_moodle_recordset.php');
30 /**
31 * Experimental pdo database class
33 * @package core_dml
34 * @copyright 2008 Andrei Bautu
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37 abstract class pdo_moodle_database extends moodle_database {
39 protected $pdb;
40 protected $lastError = null;
42 /**
43 * Constructor - instantiates the database, specifying if it's external (connect to other systems) or no (Moodle DB)
44 * note this has effect to decide if prefix checks must be performed or no
45 * @param bool true means external database used
47 public function __construct($external=false) {
48 parent::__construct($external);
51 /**
52 * Connect to db
53 * Must be called before other methods.
54 * @param string $dbhost The database host.
55 * @param string $dbuser The database username.
56 * @param string $dbpass The database username's password.
57 * @param string $dbname The name of the database being connected to.
58 * @param mixed $prefix string means moodle db prefix, false used for external databases where prefix not used
59 * @param array $dboptions driver specific options
60 * @return bool success
62 public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) {
63 $driverstatus = $this->driver_installed();
65 if ($driverstatus !== true) {
66 throw new dml_exception('dbdriverproblem', $driverstatus);
69 $this->store_settings($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions);
71 try{
72 $this->pdb = new PDO($this->get_dsn(), $this->dbuser, $this->dbpass, $this->get_pdooptions());
73 // generic PDO settings to match adodb's default; subclasses can change this in configure_dbconnection
74 $this->pdb->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
75 $this->pdb->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
76 $this->configure_dbconnection();
77 return true;
78 } catch (PDOException $ex) {
79 throw new dml_connection_exception($ex->getMessage());
80 return false;
84 /**
85 * Returns the driver-dependent DSN for PDO based on members stored by connect.
86 * Must be called after connect (or after $dbname, $dbhost, etc. members have been set).
87 * @return string driver-dependent DSN
89 abstract protected function get_dsn();
91 /**
92 * Returns the driver-dependent connection attributes for PDO based on members stored by connect.
93 * Must be called after $dbname, $dbhost, etc. members have been set.
94 * @return array A key=>value array of PDO driver-specific connection options
96 protected function get_pdooptions() {
97 return array(PDO::ATTR_PERSISTENT => !empty($this->dboptions['dbpersist']));
100 protected function configure_dbconnection() {
101 //TODO: not needed preconfigure_dbconnection() stuff for PDO drivers?
105 * Returns general database library name
106 * Note: can be used before connect()
107 * @return string db type pdo, native
109 protected function get_dblibrary() {
110 return 'pdo';
114 * Returns localised database type name
115 * Note: can be used before connect()
116 * @return string
118 public function get_name() {
119 return get_string('pdo'.$this->get_dbtype(), 'install');
123 * Returns localised database configuration help.
124 * Note: can be used before connect()
125 * @return string
127 public function get_configuration_help() {
128 return get_string('pdo'.$this->get_dbtype().'help', 'install');
132 * Returns database server info array
133 * @return array Array containing 'description' and 'version' info
135 public function get_server_info() {
136 $result = array();
137 try {
138 $result['description'] = $this->pdb->getAttribute(PDO::ATTR_SERVER_INFO);
139 } catch(PDOException $ex) {}
140 try {
141 $result['version'] = $this->pdb->getAttribute(PDO::ATTR_SERVER_VERSION);
142 } catch(PDOException $ex) {}
143 return $result;
147 * Returns supported query parameter types
148 * @return int bitmask of accepted SQL_PARAMS_*
150 protected function allowed_param_types() {
151 return SQL_PARAMS_QM | SQL_PARAMS_NAMED;
155 * Returns last error reported by database engine.
156 * @return string error message
158 public function get_last_error() {
159 return $this->lastError;
163 * Function to print/save/ignore debugging messages related to SQL queries.
165 protected function debug_query($sql, $params = null) {
166 echo '<hr /> (', $this->get_dbtype(), '): ', htmlentities($sql, ENT_QUOTES, 'UTF-8');
167 if($params) {
168 echo ' (parameters ';
169 print_r($params);
170 echo ')';
172 echo '<hr />';
176 * Do NOT use in code, to be used by database_manager only!
177 * @param string|array $sql query
178 * @param array|null $tablenames an array of xmldb table names affected by this request.
179 * @return bool true
180 * @throws ddl_change_structure_exception A DDL specific exception is thrown for any errors.
182 public function change_database_structure($sql, $tablenames = null) {
183 $this->get_manager(); // Includes DDL exceptions classes ;-)
184 $sqls = (array)$sql;
186 try {
187 foreach ($sqls as $sql) {
188 $result = true;
189 $this->query_start($sql, null, SQL_QUERY_STRUCTURE);
191 try {
192 $this->pdb->exec($sql);
193 } catch (PDOException $ex) {
194 $this->lastError = $ex->getMessage();
195 $result = false;
197 $this->query_end($result);
199 } catch (ddl_change_structure_exception $e) {
200 $this->reset_caches($tablenames);
201 throw $e;
204 $this->reset_caches($tablenames);
205 return true;
208 public function delete_records_select($table, $select, array $params=null) {
209 $sql = "DELETE FROM {{$table}}";
210 if ($select) {
211 $sql .= " WHERE $select";
213 return $this->execute($sql, $params);
217 * Factory method that creates a recordset for return by a query. The generic pdo_moodle_recordset
218 * class should fit most cases, but pdo_moodle_database subclasses can override this method to return
219 * a subclass of pdo_moodle_recordset.
220 * @param object $sth instance of PDOStatement
221 * @return object instance of pdo_moodle_recordset
223 protected function create_recordset($sth) {
224 return new pdo_moodle_recordset($sth);
228 * Execute general sql query. Should be used only when no other method suitable.
229 * Do NOT use this to make changes in db structure, use database_manager methods instead!
230 * @param string $sql query
231 * @param array $params query parameters
232 * @return bool success
234 public function execute($sql, array $params=null) {
235 list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
237 $result = true;
238 $this->query_start($sql, $params, SQL_QUERY_UPDATE);
240 try {
241 $sth = $this->pdb->prepare($sql);
242 $sth->execute($params);
243 } catch (PDOException $ex) {
244 $this->lastError = $ex->getMessage();
245 $result = false;
248 $this->query_end($result);
249 return $result;
253 * Get a number of records as an moodle_recordset. $sql must be a complete SQL query.
254 * Since this method is a little less readable, use of it should be restricted to
255 * code where it's possible there might be large datasets being returned. For known
256 * small datasets use get_records_sql - it leads to simpler code.
258 * The return type is like:
259 * @see function get_recordset.
261 * @param string $sql the SQL select query to execute.
262 * @param array $params array of sql parameters
263 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
264 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
265 * @return moodle_recordset instance
267 public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
269 $result = true;
271 list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
272 $sql = $this->get_limit_clauses($sql, $limitfrom, $limitnum);
273 $this->query_start($sql, $params, SQL_QUERY_SELECT);
275 try {
276 $sth = $this->pdb->prepare($sql);
277 $sth->execute($params);
278 $result = $this->create_recordset($sth);
279 } catch (PDOException $ex) {
280 $this->lastError = $ex->getMessage();
281 $result = false;
284 $this->query_end($result);
285 return $result;
289 * Selects rows and return values of first column as array.
291 * @param string $sql The SQL query
292 * @param array $params array of sql parameters
293 * @return array of values
295 public function get_fieldset_sql($sql, array $params=null) {
296 $rs = $this->get_recordset_sql($sql, $params);
297 if (!$rs->valid()) {
298 $rs->close(); // Not going to iterate (but exit), close rs
299 return false;
301 $result = array();
302 foreach($rs as $value) {
303 $result[] = reset($value);
305 $rs->close();
306 return $result;
310 * Get a number of records as an array of objects.
312 * Return value is like:
313 * @see function get_records.
315 * @param string $sql the SQL select query to execute. The first column of this SELECT statement
316 * must be a unique value (usually the 'id' field), as it will be used as the key of the
317 * returned array.
318 * @param array $params array of sql parameters
319 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
320 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
321 * @return array of objects, or empty array if no records were found, or false if an error occurred.
323 public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
324 global $CFG;
326 $rs = $this->get_recordset_sql($sql, $params, $limitfrom, $limitnum);
327 if (!$rs->valid()) {
328 $rs->close(); // Not going to iterate (but exit), close rs
329 return false;
331 $objects = array();
332 foreach($rs as $value) {
333 $key = reset($value);
334 if ($CFG->debugdeveloper && array_key_exists($key, $objects)) {
335 debugging("Did you remember to make the first column something unique in your call to get_records? Duplicate value '$key' found in column first column of '$sql'.", DEBUG_DEVELOPER);
337 $objects[$key] = (object)$value;
339 $rs->close();
340 return $objects;
344 * Insert new record into database, as fast as possible, no safety checks, lobs not supported.
345 * @param string $table name
346 * @param mixed $params data record as object or array
347 * @param bool $returnit return it of inserted record
348 * @param bool $bulk true means repeated inserts expected
349 * @param bool $customsequence true if 'id' included in $params, disables $returnid
350 * @return bool|int true or new id
352 public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) {
353 if (!is_array($params)) {
354 $params = (array)$params;
357 if ($customsequence) {
358 if (!isset($params['id'])) {
359 throw new coding_exception('moodle_database::insert_record_raw() id field must be specified if custom sequences used.');
361 $returnid = false;
362 } else {
363 unset($params['id']);
366 if (empty($params)) {
367 throw new coding_exception('moodle_database::insert_record_raw() no fields found.');
370 $fields = implode(',', array_keys($params));
371 $qms = array_fill(0, count($params), '?');
372 $qms = implode(',', $qms);
374 $sql = "INSERT INTO {{$table}} ($fields) VALUES($qms)";
375 if (!$this->execute($sql, $params)) {
376 return false;
378 if (!$returnid) {
379 return true;
381 if ($id = $this->pdb->lastInsertId()) {
382 return (int)$id;
384 return false;
388 * Insert a record into a table and return the "id" field if required,
389 * Some conversions and safety checks are carried out. Lobs are supported.
390 * If the return ID isn't required, then this just reports success as true/false.
391 * $data is an object containing needed data
392 * @param string $table The database table to be inserted into
393 * @param object $data A data object with values for one or more fields in the record
394 * @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.
395 * @param bool $bulk true means repeated inserts expected
396 * @return bool|int true or new id
398 public function insert_record($table, $dataobject, $returnid=true, $bulk=false) {
399 $dataobject = (array)$dataobject;
401 $columns = $this->get_columns($table);
402 if (empty($columns)) {
403 throw new dml_exception('ddltablenotexist', $table);
406 $cleaned = array();
408 foreach ($dataobject as $field=>$value) {
409 if ($field === 'id') {
410 continue;
412 if (!isset($columns[$field])) {
413 continue;
415 $column = $columns[$field];
416 if (is_bool($value)) {
417 $value = (int)$value; // prevent "false" problems
419 $cleaned[$field] = $value;
422 if (empty($cleaned)) {
423 return false;
426 return $this->insert_record_raw($table, $cleaned, $returnid, $bulk);
430 * Update record in database, as fast as possible, no safety checks, lobs not supported.
431 * @param string $table name
432 * @param mixed $params data record as object or array
433 * @param bool true means repeated updates expected
434 * @return bool success
436 public function update_record_raw($table, $params, $bulk=false) {
437 $params = (array)$params;
439 if (!isset($params['id'])) {
440 throw new coding_exception('moodle_database::update_record_raw() id field must be specified.');
442 $id = $params['id'];
443 unset($params['id']);
445 if (empty($params)) {
446 throw new coding_exception('moodle_database::update_record_raw() no fields found.');
449 $sets = array();
450 foreach ($params as $field=>$value) {
451 $sets[] = "$field = ?";
454 $params[] = $id; // last ? in WHERE condition
456 $sets = implode(',', $sets);
457 $sql = "UPDATE {{$table}} SET $sets WHERE id=?";
458 return $this->execute($sql, $params);
462 * Update a record in a table
464 * $dataobject is an object containing needed data
465 * Relies on $dataobject having a variable "id" to
466 * specify the record to update
468 * @param string $table The database table to be checked against.
469 * @param object $dataobject An object with contents equal to fieldname=>fieldvalue. Must have an entry for 'id' to map to the table specified.
470 * @param bool true means repeated updates expected
471 * @return bool success
473 public function update_record($table, $dataobject, $bulk=false) {
474 $dataobject = (array)$dataobject;
476 $columns = $this->get_columns($table);
477 $cleaned = array();
479 foreach ($dataobject as $field=>$value) {
480 if (!isset($columns[$field])) {
481 continue;
483 if (is_bool($value)) {
484 $value = (int)$value; // prevent "false" problems
486 $cleaned[$field] = $value;
489 return $this->update_record_raw($table, $cleaned, $bulk);
493 * Set a single field in every table row where the select statement evaluates to true.
495 * @param string $table The database table to be checked against.
496 * @param string $newfield the field to set.
497 * @param string $newvalue the value to set the field to.
498 * @param string $select A fragment of SQL to be used in a where clause in the SQL call.
499 * @param array $params array of sql parameters
500 * @return bool success
502 public function set_field_select($table, $newfield, $newvalue, $select, array $params=null) {
503 if ($select) {
504 $select = "WHERE $select";
506 if (is_null($params)) {
507 $params = array();
509 list($select, $params, $type) = $this->fix_sql_params($select, $params);
511 if (is_bool($newvalue)) {
512 $newvalue = (int)$newvalue; // prevent "false" problems
514 if (is_null($newvalue)) {
515 $newfield = "$newfield = NULL";
516 } else {
517 // make sure SET and WHERE clauses use the same type of parameters,
518 // because we don't support different types in the same query
519 switch($type) {
520 case SQL_PARAMS_NAMED:
521 $newfield = "$newfield = :newvalueforupdate";
522 $params['newvalueforupdate'] = $newvalue;
523 break;
524 case SQL_PARAMS_QM:
525 $newfield = "$newfield = ?";
526 array_unshift($params, $newvalue);
527 break;
528 default:
529 $this->lastError = __FILE__ . ' LINE: ' . __LINE__ . '.';
530 print_error(unknowparamtype, 'error', '', $this->lastError);
533 $sql = "UPDATE {{$table}} SET $newfield $select";
534 return $this->execute($sql, $params);
537 public function sql_concat() {
538 print_error('TODO');
541 public function sql_concat_join($separator="' '", $elements=array()) {
542 print_error('TODO');
545 protected function begin_transaction() {
546 $this->query_start('', NULL, SQL_QUERY_AUX);
547 try {
548 $this->pdb->beginTransaction();
549 } catch(PDOException $ex) {
550 $this->lastError = $ex->getMessage();
552 $this->query_end($result);
555 protected function commit_transaction() {
556 $this->query_start('', NULL, SQL_QUERY_AUX);
558 try {
559 $this->pdb->commit();
560 } catch(PDOException $ex) {
561 $this->lastError = $ex->getMessage();
563 $this->query_end($result);
566 protected function rollback_transaction() {
567 $this->query_start('', NULL, SQL_QUERY_AUX);
569 try {
570 $this->pdb->rollBack();
571 } catch(PDOException $ex) {
572 $this->lastError = $ex->getMessage();
574 $this->query_end($result);
578 * Import a record into a table, id field is required.
579 * Basic safety checks only. Lobs are supported.
580 * @param string $table name of database table to be inserted into
581 * @param mixed $dataobject object or array with fields in the record
582 * @return bool success
584 public function import_record($table, $dataobject) {
585 $dataobject = (object)$dataobject;
587 $columns = $this->get_columns($table);
588 $cleaned = array();
589 foreach ($dataobject as $field=>$value) {
590 if (!isset($columns[$field])) {
591 continue;
593 $cleaned[$field] = $value;
596 return $this->insert_record_raw($table, $cleaned, false, true, true);
600 * Called before each db query.
602 * Overridden to ensure $this->lastErorr is reset each query
604 * @param string $sql
605 * @param array array of parameters
606 * @param int $type type of query
607 * @param mixed $extrainfo driver specific extra information
608 * @return void
610 protected function query_start($sql, array $params=null, $type, $extrainfo=null) {
611 $this->lastError = null;
612 parent::query_start($sql, $params, $type, $extrainfo);