7466a42f |
1 | <?php //$Id$ |
2 | |
3 | require_once($CFG->libdir.'/dml/moodle_database.php'); |
4 | require_once($CFG->libdir.'/dml/mysqli_native_moodle_recordset.php'); |
5 | |
6 | /** |
7 | * Native mysqli class representing moodle database interface. |
8 | * @package dml |
9 | */ |
10 | class mysqli_native_moodle_database extends moodle_database { |
11 | |
0487f9e2 |
12 | protected $mysqli = null; |
13 | protected $debug = false; |
7466a42f |
14 | |
15 | /** |
16 | * Detects if all needed PHP stuff installed. |
17 | * Note: can be used before connect() |
18 | * @return mixed true if ok, string if something |
19 | */ |
20 | public function driver_installed() { |
21 | if (!extension_loaded('mysqli')) { |
22 | return get_string('mysqliextensionisnotpresentinphp', 'install'); |
23 | } |
24 | return true; |
25 | } |
26 | |
27 | /** |
28 | * Returns database family type - describes SQL dialect |
29 | * Note: can be used before connect() |
30 | * @return string db family name (mysql, postgres, mssql, oracle, etc.) |
31 | */ |
32 | public function get_dbfamily() { |
33 | return 'mysql'; |
34 | } |
35 | |
36 | /** |
37 | * Returns more specific database driver type |
38 | * Note: can be used before connect() |
39 | * @return string db type mysql, mysqli, postgres7 |
40 | */ |
41 | protected function get_dbtype() { |
42 | return 'mysqli'; |
43 | } |
44 | |
45 | /** |
46 | * Returns general database library name |
47 | * Note: can be used before connect() |
48 | * @return string db type adodb, pdo, native |
49 | */ |
50 | protected function get_dblibrary() { |
51 | return 'native'; |
52 | } |
53 | |
54 | /** |
55 | * Returns localised database type name |
56 | * Note: can be used before connect() |
57 | * @return string |
58 | */ |
59 | public function get_name() { |
2aeb3bcb |
60 | return get_string('nativemysqli', 'install'); |
7466a42f |
61 | } |
62 | |
63 | /** |
64 | * Returns localised database description |
65 | * Note: can be used before connect() |
66 | * @return string |
67 | */ |
68 | public function get_configuration_hints() { |
2aeb3bcb |
69 | return get_string('databasesettingssub_mysqli', 'install'); |
7466a42f |
70 | } |
71 | |
72 | /** |
73 | * Connect to db |
74 | * Must be called before other methods. |
75 | * @param string $dbhost |
76 | * @param string $dbuser |
77 | * @param string $dbpass |
78 | * @param string $dbname |
7466a42f |
79 | * @param mixed $prefix string means moodle db prefix, false used for external databases where prefix not used |
80 | * @param array $dboptions driver specific options |
81 | * @return bool success |
82 | */ |
beaa43db |
83 | public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) { |
7466a42f |
84 | global $CFG; |
85 | |
beaa43db |
86 | $this->store_settings($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions); |
7466a42f |
87 | |
88 | $this->mysqli = new mysqli($dbhost, $dbuser, $dbpass, $dbname); |
89 | if ($this->mysqli->connect_error) { |
90 | return false; |
91 | } |
a6283e91 |
92 | $this->query_start("--set_charset()", null, SQL_QUERY_AUX); |
7466a42f |
93 | $this->mysqli->set_charset('utf8'); |
a6283e91 |
94 | $this->query_end(true); |
95 | |
7466a42f |
96 | return true; |
97 | } |
98 | |
99 | /** |
100 | * Close database connection and release all resources |
101 | * and memory (especially circular memory references). |
102 | * Do NOT use connect() again, create a new instance if needed. |
103 | */ |
104 | public function dispose() { |
105 | if ($this->mysqli) { |
106 | $this->mysqli->close(); |
107 | $this->mysqli = null; |
108 | } |
109 | parent::dispose(); |
110 | } |
111 | |
112 | /** |
113 | * Returns database server info array |
114 | * @return array |
115 | */ |
116 | public function get_server_info() { |
117 | return array('description'=>$this->mysqli->server_info, 'version'=>$this->mysqli->server_info); |
118 | } |
119 | |
120 | /** |
121 | * Returns supported query parameter types |
122 | * @return bitmask |
123 | */ |
124 | protected function allowed_param_types() { |
125 | return SQL_PARAMS_QM; |
126 | } |
127 | |
128 | /** |
129 | * Returns last error reported by database engine. |
130 | */ |
131 | public function get_last_error() { |
132 | return $this->mysqli->error; |
133 | } |
134 | |
135 | /** |
136 | * Return tables in database WITHOUT current prefix |
137 | * @return array of table names in lowercase and without prefix |
138 | */ |
139 | public function get_tables() { |
7466a42f |
140 | $tables = array(); |
a6283e91 |
141 | $sql = "SHOW TABLES"; |
142 | $this->query_start($sql, null, SQL_QUERY_AUX); |
143 | $result = $this->mysqli->query($sql); |
144 | $this->query_end($result); |
145 | if ($result) { |
7466a42f |
146 | while ($arr = $result->fetch_assoc()) { |
147 | $tablename = reset($arr); |
148 | if (strpos($tablename, $this->prefix) !== 0) { |
149 | continue; |
150 | } |
151 | $tablename = substr($tablename, strlen($this->prefix)); |
152 | $tables[$tablename] = $tablename; |
153 | } |
154 | $result->close(); |
155 | } |
156 | return $tables; |
157 | } |
158 | |
159 | /** |
160 | * Return table indexes - everything lowercased |
161 | * @return array of arrays |
162 | */ |
163 | public function get_indexes($table) { |
164 | $preflen = strlen($this->prefix); |
165 | $indexes = array(); |
a6283e91 |
166 | $sql = "SHOW INDEXES FROM {$this->prefix}$table"; |
167 | $this->query_start($sql, null, SQL_QUERY_AUX); |
168 | $result = $this->mysqli->query($sql); |
169 | $this->query_end($result); |
170 | if ($result) { |
7466a42f |
171 | while ($res = $result->fetch_object()) { |
172 | if ($res->Key_name === 'PRIMARY') { |
173 | continue; |
174 | } |
175 | if (!isset($indexes[$res->Key_name])) { |
176 | $indexes[$res->Key_name] = array('unique'=>empty($res->Non_unique), 'columns'=>array()); |
177 | } |
178 | $indexes[$res->Key_name]['columns'][$res->Seq_in_index-1] = $res->Column_name; |
179 | } |
180 | $result->close(); |
181 | } |
182 | return $indexes; |
183 | } |
184 | |
185 | /** |
186 | * Returns datailed information about columns in table. This information is cached internally. |
187 | * @param string $table name |
188 | * @param bool $usecache |
189 | * @return array array of database_column_info objects indexed with column names |
190 | */ |
191 | public function get_columns($table, $usecache=true) { |
192 | if ($usecache and isset($this->columns[$table])) { |
193 | return $this->columns[$table]; |
194 | } |
195 | |
196 | $this->columns[$table] = array(); |
197 | |
a6283e91 |
198 | $sql = "SHOW COLUMNS FROM {$this->prefix}$table"; |
199 | $this->query_start($sql, null, SQL_QUERY_AUX); |
200 | $result = $this->mysqli->query($sql); |
201 | $this->query_end($result); |
202 | |
203 | if ($result === false) { |
7466a42f |
204 | return array(); |
205 | } |
206 | |
a6283e91 |
207 | while ($rawcolumn = $result->fetch_assoc()) { |
208 | $rawcolumn = (object)array_change_key_case($rawcolumn, CASE_LOWER); |
209 | |
7466a42f |
210 | $info = new object(); |
211 | $info->name = $rawcolumn->field; |
212 | $matches = null; |
213 | |
214 | if (preg_match('/varchar\((\d+)\)/i', $rawcolumn->type, $matches)) { |
215 | $info->type = 'varchar'; |
216 | $info->meta_type = 'C'; |
217 | $info->max_length = $matches[1]; |
218 | $info->scale = null; |
219 | $info->not_null = ($rawcolumn->null === 'NO'); |
220 | $info->default_value = $rawcolumn->default; |
221 | $info->has_default = is_null($info->default_value) ? false : true; |
222 | $info->primary_key = ($rawcolumn->key === 'PRI'); |
223 | $info->binary = false; |
224 | $info->unsigned = null; |
225 | $info->auto_increment= false; |
226 | $info->unique = null; |
227 | |
228 | } else if (preg_match('/([a-z]*int[a-z]*)\((\d+)\)/i', $rawcolumn->type, $matches)) { |
229 | $info->type = $matches[1]; |
230 | $info->primary_key = ($rawcolumn->key === 'PRI'); |
231 | if ($info->primary_key) { |
232 | $info->meta_type = 'R'; |
233 | $info->max_length = $matches[2]; |
234 | $info->scale = null; |
235 | $info->not_null = ($rawcolumn->null === 'NO'); |
236 | $info->default_value = $rawcolumn->default; |
237 | $info->has_default = is_null($info->default_value) ? false : true; |
238 | $info->binary = false; |
239 | $info->unsigned = (stripos($rawcolumn->type, 'unsigned') !== false); |
240 | $info->auto_increment= true; |
241 | $info->unique = true; |
242 | } else { |
243 | $info->meta_type = 'I'; |
244 | $info->max_length = $matches[2]; |
245 | $info->scale = null; |
246 | $info->not_null = ($rawcolumn->null === 'NO'); |
247 | $info->default_value = $rawcolumn->default; |
248 | $info->has_default = is_null($info->default_value) ? false : true; |
249 | $info->binary = false; |
250 | $info->unsigned = (stripos($rawcolumn->type, 'unsigned') !== false); |
251 | $info->auto_increment= false; |
252 | $info->unique = null; |
253 | } |
254 | |
c65845cc |
255 | } else if (preg_match('/(decimal|double|float)\((\d+),(\d+)\)/i', $rawcolumn->type, $matches)) { |
256 | $info->type = $matches[1]; |
7466a42f |
257 | $info->meta_type = 'N'; |
c65845cc |
258 | $info->max_length = $matches[2]; |
259 | $info->scale = $matches[3]; |
7466a42f |
260 | $info->not_null = ($rawcolumn->null === 'NO'); |
261 | $info->default_value = $rawcolumn->default; |
262 | $info->has_default = is_null($info->default_value) ? false : true; |
263 | $info->primary_key = ($rawcolumn->key === 'PRI'); |
264 | $info->binary = false; |
265 | $info->unsigned = null; |
266 | $info->auto_increment= false; |
267 | $info->unique = null; |
268 | |
269 | } else if (preg_match('/([a-z]*text)/i', $rawcolumn->type, $matches)) { |
270 | $info->type = $matches[1]; |
271 | $info->meta_type = 'X'; |
272 | $info->max_length = -1; |
273 | $info->scale = null; |
274 | $info->not_null = ($rawcolumn->null === 'NO'); |
275 | $info->default_value = $rawcolumn->default; |
276 | $info->has_default = is_null($info->default_value) ? false : true; |
277 | $info->primary_key = ($rawcolumn->key === 'PRI'); |
278 | $info->binary = false; |
279 | $info->unsigned = null; |
280 | $info->auto_increment= false; |
281 | $info->unique = null; |
282 | |
75dfa4a3 |
283 | } else if (preg_match('/([a-z]*blob)/i', $rawcolumn->type, $matches)) { |
284 | $info->type = $matches[1]; |
285 | $info->meta_type = 'B'; |
286 | $info->max_length = -1; |
287 | $info->scale = null; |
288 | $info->not_null = ($rawcolumn->null === 'NO'); |
289 | $info->default_value = $rawcolumn->default; |
290 | $info->has_default = is_null($info->default_value) ? false : true; |
291 | $info->primary_key = false; |
292 | $info->binary = true; |
293 | $info->unsigned = null; |
294 | $info->auto_increment= false; |
295 | $info->unique = null; |
296 | |
7466a42f |
297 | } else if (preg_match('/enum\((.*)\)/i', $rawcolumn->type, $matches)) { |
298 | $info->type = 'enum'; |
299 | $info->meta_type = 'C'; |
300 | $info->enums = array(); |
301 | $info->max_length = 0; |
302 | $values = $matches[1]; |
303 | $values = explode(',', $values); |
304 | $textlib = textlib_get_instance(); |
305 | foreach ($values as $val) { |
306 | $val = trim($val, "'"); |
307 | $length = $textlib->strlen($val); |
308 | $info->enums[] = $val; |
309 | $info->max_length = ($info->max_length < $length) ? $length : $info->max_length; |
310 | } |
311 | $info->scale = null; |
312 | $info->not_null = ($rawcolumn->null === 'NO'); |
313 | $info->default_value = $rawcolumn->default; |
314 | $info->has_default = is_null($info->default_value) ? false : true; |
315 | $info->primary_key = ($rawcolumn->key === 'PRI'); |
316 | $info->binary = false; |
317 | $info->unsigned = null; |
318 | $info->auto_increment= false; |
319 | $info->unique = null; |
320 | } |
321 | |
322 | $this->columns[$table][$info->name] = new database_column_info($info); |
323 | } |
324 | |
a6283e91 |
325 | $result->close(); |
326 | |
7466a42f |
327 | return $this->columns[$table]; |
328 | } |
329 | |
330 | /** |
331 | * Reset a sequence to the id field of a table. |
332 | * @param string $table name of table |
333 | * @return success |
334 | */ |
335 | public function reset_sequence($table) { |
336 | // From http://dev.mysql.com/doc/refman/5.0/en/alter-table.html |
337 | if (!$this->get_manager()->table_exists($table)) { |
338 | return false; |
339 | } |
340 | $value = (int)$this->get_field_sql('SELECT MAX(id) FROM {'.$table.'}'); |
341 | $value++; |
342 | return $this->change_database_structure("ALTER TABLE $this->prefix$table AUTO_INCREMENT = $value"); |
343 | } |
344 | |
345 | /** |
346 | * Is db in unicode mode? |
347 | * @return bool |
348 | */ |
349 | public function setup_is_unicodedb() { |
a6283e91 |
350 | $sql = "SHOW LOCAL VARIABLES LIKE 'character_set_database'"; |
351 | $this->query_start($sql, null, SQL_QUERY_AUX); |
352 | $result = $this->mysqli->query($sql); |
353 | $this->query_end($result); |
354 | |
355 | if ($result) { |
7466a42f |
356 | $result->close(); |
357 | return true; |
358 | } |
359 | return false; |
360 | } |
361 | |
362 | /** |
363 | * Enable/disable very detailed debugging |
7466a42f |
364 | * @param bool $state |
365 | */ |
366 | public function set_debug($state) { |
0487f9e2 |
367 | $this->debug = $state; |
7466a42f |
368 | } |
369 | |
370 | /** |
371 | * Returns debug status |
372 | * @return bool $state |
373 | */ |
374 | public function get_debug() { |
0487f9e2 |
375 | return $this->debug; |
7466a42f |
376 | } |
377 | |
378 | /** |
379 | * Enable/disable detailed sql logging |
7466a42f |
380 | * @param bool $state |
381 | */ |
382 | public function set_logging($state) { |
383 | //TODO |
384 | } |
385 | |
386 | /** |
387 | * Do NOT use in code, to be used by database_manager only! |
388 | * @param string $sql query |
389 | * @return bool success |
390 | */ |
391 | public function change_database_structure($sql) { |
12e09c6b |
392 | $this->reset_columns(); |
a6283e91 |
393 | |
394 | $this->query_start($sql, null, SQL_QUERY_STRUCTURE); |
395 | $result = $this->mysqli->query($sql); |
396 | $this->query_end($result); |
397 | |
12e09c6b |
398 | if ($result === false) { |
399 | $this->report_error($sql); |
400 | return false; |
401 | } |
402 | return true; |
7466a42f |
403 | } |
404 | |
405 | /** |
406 | * Very ugly hack which emulates bound parameters in queries |
407 | * because prepared statements do not use query cache. |
408 | */ |
409 | protected function emulate_bound_params($sql, array $params=null) { |
410 | if (empty($params)) { |
411 | return $sql; |
412 | } |
413 | /// ok, we have verified sql statement with ? and correct number of params |
414 | $return = strtok($sql, '?'); |
415 | foreach ($params as $param) { |
416 | if (is_bool($param)) { |
417 | $return .= (int)$param; |
418 | } else if (is_null($param)) { |
419 | $return .= 'NULL'; |
420 | } else if (is_numeric($param)) { |
421 | $return .= $param; |
422 | } else { |
423 | $param = $this->mysqli->real_escape_string($param); |
424 | $return .= "'$param'"; |
425 | } |
426 | $return .= strtok('?'); |
427 | } |
428 | return $return; |
429 | } |
430 | |
431 | /** |
432 | * Execute general sql query. Should be used only when no other method suitable. |
433 | * Do NOT use this to make changes in db structure, use database_manager::execute_sql() instead! |
434 | * @param string $sql query |
435 | * @param array $params query parameters |
436 | * @return bool success |
437 | */ |
438 | public function execute($sql, array $params=null) { |
439 | list($sql, $params, $type) = $this->fix_sql_params($sql, $params); |
440 | |
441 | if (strpos($sql, ';') !== false) { |
442 | debugging('Error: Multiple sql statements found or bound parameters not used properly in query!'); |
443 | return false; |
444 | } |
445 | |
446 | $rawsql = $this->emulate_bound_params($sql, $params); |
447 | |
a6283e91 |
448 | $this->query_start($sql, $params, SQL_QUERY_UPDATE); |
7466a42f |
449 | $result = $this->mysqli->query($rawsql); |
a6283e91 |
450 | $this->query_end($result); |
7466a42f |
451 | |
452 | if ($result === false) { |
453 | $this->report_error($sql, $params); |
454 | return false; |
455 | |
456 | } else if ($result === true) { |
457 | return true; |
458 | |
459 | } else { |
460 | $result->close(); |
461 | return true; |
462 | } |
463 | } |
464 | |
465 | /** |
466 | * Get a number of records as a moodle_recordset using a SQL statement. |
467 | * |
468 | * Since this method is a little less readable, use of it should be restricted to |
469 | * code where it's possible there might be large datasets being returned. For known |
470 | * small datasets use get_records_sql - it leads to simpler code. |
471 | * |
472 | * The return type is as for @see function get_recordset. |
473 | * |
474 | * @param string $sql the SQL select query to execute. |
475 | * @param array $params array of sql parameters |
476 | * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set). |
477 | * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set). |
478 | * @return mixed an moodle_recorset object, or false if an error occured. |
479 | */ |
480 | public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) { |
7466a42f |
481 | if ($limitfrom or $limitnum) { |
482 | $limitfrom = (int)$limitfrom; |
483 | $limitnum = (int)$limitnum; |
484 | if ($limitnum < 1) { |
485 | $limitnum = "18446744073709551615"; |
486 | } |
0487f9e2 |
487 | $sql .= " LIMIT $limitfrom, $limitnum"; |
7466a42f |
488 | } |
489 | |
0487f9e2 |
490 | list($sql, $params, $type) = $this->fix_sql_params($sql, $params); |
491 | $rawsql = $this->emulate_bound_params($sql, $params); |
492 | |
a6283e91 |
493 | $this->query_start($sql, $params, SQL_QUERY_SELECT); |
7466a42f |
494 | // no MYSQLI_USE_RESULT here, it would block write ops on affected tables |
495 | $result = $this->mysqli->query($rawsql, MYSQLI_STORE_RESULT); |
a6283e91 |
496 | $this->query_end($result); |
7466a42f |
497 | |
498 | if ($result === false) { |
499 | $this->report_error($sql, $params); |
500 | return false; |
501 | } |
502 | |
503 | return $this->create_recordset($result); |
504 | } |
505 | |
506 | protected function create_recordset($result) { |
507 | return new mysqli_native_moodle_recordset($result); |
508 | } |
509 | |
510 | /** |
511 | * Get a number of records as an array of objects using a SQL statement. |
512 | * |
513 | * Return value as for @see function get_records. |
514 | * |
515 | * @param string $sql the SQL select query to execute. The first column of this SELECT statement |
516 | * must be a unique value (usually the 'id' field), as it will be used as the key of the |
517 | * returned array. |
518 | * @param array $params array of sql parameters |
519 | * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set). |
520 | * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set). |
521 | * @return mixed an array of objects, or empty array if no records were found, or false if an error occured. |
522 | */ |
523 | public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) { |
7466a42f |
524 | if ($limitfrom or $limitnum) { |
525 | $limitfrom = (int)$limitfrom; |
526 | $limitnum = (int)$limitnum; |
527 | if ($limitnum < 1) { |
528 | $limitnum = "18446744073709551615"; |
529 | } |
0487f9e2 |
530 | $sql .= " LIMIT $limitfrom, $limitnum"; |
7466a42f |
531 | } |
532 | |
0487f9e2 |
533 | list($sql, $params, $type) = $this->fix_sql_params($sql, $params); |
534 | $rawsql = $this->emulate_bound_params($sql, $params); |
535 | |
a6283e91 |
536 | $this->query_start($sql, $params, SQL_QUERY_SELECT); |
7466a42f |
537 | $result = $this->mysqli->query($rawsql, MYSQLI_STORE_RESULT); |
a6283e91 |
538 | $this->query_end($result); |
7466a42f |
539 | |
540 | if ($result === false) { |
541 | $this->report_error($sql, $params); |
542 | return false; |
543 | } |
544 | |
545 | $return = array(); |
a6283e91 |
546 | |
7466a42f |
547 | while($row = $result->fetch_assoc()) { |
548 | $row = array_change_key_case($row, CASE_LOWER); |
549 | $id = reset($row); |
550 | $return[$id] = (object)$row; |
551 | } |
552 | $result->close(); |
a6283e91 |
553 | |
7466a42f |
554 | return $return; |
555 | } |
556 | |
557 | /** |
558 | * Selects records and return values (first field) as an array using a SQL statement. |
559 | * |
560 | * @param string $sql The SQL query |
561 | * @param array $params array of sql parameters |
562 | * @return mixed array of values or false if an error occured |
563 | */ |
564 | public function get_fieldset_sql($sql, array $params=null) { |
565 | list($sql, $params, $type) = $this->fix_sql_params($sql, $params); |
566 | $rawsql = $this->emulate_bound_params($sql, $params); |
567 | |
a6283e91 |
568 | $this->query_start($sql, $params, SQL_QUERY_SELECT); |
7466a42f |
569 | $result = $this->mysqli->query($rawsql, MYSQLI_STORE_RESULT); |
a6283e91 |
570 | $this->query_end($result); |
7466a42f |
571 | |
572 | if ($result === false) { |
573 | $this->report_error($sql, $params); |
574 | return false; |
575 | } |
576 | |
577 | $return = array(); |
a6283e91 |
578 | |
7466a42f |
579 | while($row = $result->fetch_assoc()) { |
580 | $return[] = reset($row); |
581 | } |
582 | $result->close(); |
a6283e91 |
583 | |
7466a42f |
584 | return $return; |
585 | } |
586 | |
587 | /** |
588 | * Insert new record into database, as fast as possible, no safety checks, lobs not supported. |
589 | * @param string $table name |
590 | * @param mixed $params data record as object or array |
591 | * @param bool $returnit return it of inserted record |
592 | * @param bool $bulk true means repeated inserts expected |
593 | * @param bool $customsequence true if 'id' included in $params, disables $returnid |
594 | * @return mixed success or new id |
595 | */ |
596 | public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) { |
597 | if (!is_array($params)) { |
598 | $params = (array)$params; |
599 | } |
600 | |
601 | if ($customsequence) { |
602 | if (!isset($params['id'])) { |
603 | return false; |
604 | } |
605 | $returnid = false; |
606 | } else { |
607 | unset($params['id']); |
608 | } |
609 | |
610 | if (empty($params)) { |
611 | return false; |
612 | } |
613 | |
7466a42f |
614 | $fields = implode(',', array_keys($params)); |
615 | $qms = array_fill(0, count($params), '?'); |
616 | $qms = implode(',', $qms); |
617 | |
618 | $sql = "INSERT INTO {$this->prefix}$table ($fields) VALUES($qms)"; |
619 | $rawsql = $this->emulate_bound_params($sql, $params); |
620 | |
a6283e91 |
621 | $this->query_start($sql, $params, SQL_QUERY_INSERT); |
7466a42f |
622 | $result = $this->mysqli->query($rawsql); |
a6283e91 |
623 | $this->query_end($result); |
7466a42f |
624 | |
625 | if ($result === false) { |
626 | $this->report_error($sql, $params); |
627 | return false; |
628 | } |
629 | |
630 | if (!$id = $this->mysqli->insert_id) { |
631 | return false; |
632 | } |
633 | |
634 | if (!$returnid) { |
635 | return true; |
636 | } else { |
637 | return (int)$id; |
638 | } |
639 | } |
640 | |
641 | /** |
642 | * Insert a record into a table and return the "id" field if required. |
643 | * |
644 | * Some conversions and safety checks are carried out. Lobs are supported. |
645 | * If the return ID isn't required, then this just reports success as true/false. |
646 | * $data is an object containing needed data |
647 | * @param string $table The database table to be inserted into |
648 | * @param object $data A data object with values for one or more fields in the record |
649 | * @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. |
650 | * @return mixed success or new ID |
651 | */ |
652 | public function insert_record($table, $dataobject, $returnid=true, $bulk=false) { |
653 | if (!is_object($dataobject)) { |
654 | $dataobject = (object)$dataobject; |
655 | } |
656 | |
657 | $columns = $this->get_columns($table); |
658 | |
659 | unset($dataobject->id); |
660 | $cleaned = array(); |
661 | |
662 | foreach ($dataobject as $field=>$value) { |
663 | if (!isset($columns[$field])) { |
664 | continue; |
665 | } |
666 | $column = $columns[$field]; |
667 | if (is_bool($value)) { |
668 | $value = (int)$value; // prevent "false" problems |
669 | } |
670 | if (!empty($column->enums)) { |
671 | // workaround for problem with wrong enums in mysql |
672 | if (is_null($value) and !$column->not_null) { |
673 | // ok - nulls allowed |
674 | } else { |
675 | if (!in_array((string)$value, $column->enums)) { |
676 | debugging('Enum value '.s($value).' not allowed in field '.$field.' table '.$table.'.'); |
677 | return false; |
678 | } |
679 | } |
680 | } |
681 | $cleaned[$field] = $value; |
682 | } |
683 | |
684 | if (empty($cleaned)) { |
685 | return false; |
686 | } |
687 | |
688 | return $this->insert_record_raw($table, $cleaned, $returnid, $bulk); |
689 | } |
690 | |
691 | /** |
692 | * Import a record into a table, id field is required. |
693 | * Safety checks are NOT carried out. Lobs are supported. |
694 | * |
695 | * @param string $table name of database table to be inserted into |
696 | * @param object $dataobject A data object with values for one or more fields in the record |
697 | * @return bool success |
698 | */ |
699 | public function import_record($table, $dataobject) { |
700 | $dataobject = (object)$dataobject; |
701 | |
702 | if (empty($dataobject->id)) { |
703 | return false; |
704 | } |
705 | |
706 | $columns = $this->get_columns($table); |
707 | $cleaned = array(); |
708 | |
709 | foreach ($dataobject as $field=>$value) { |
710 | if (!isset($columns[$field])) { |
711 | continue; |
712 | } |
713 | $cleaned[$field] = $value; |
714 | } |
715 | |
716 | return $this->insert_record_raw($table, $cleaned, false, true, true); |
717 | } |
718 | |
719 | /** |
720 | * Update record in database, as fast as possible, no safety checks, lobs not supported. |
721 | * @param string $table name |
722 | * @param mixed $params data record as object or array |
723 | * @param bool true means repeated updates expected |
724 | * @return bool success |
725 | */ |
726 | public function update_record_raw($table, $params, $bulk=false) { |
727 | if (!is_array($params)) { |
728 | $params = (array)$params; |
729 | } |
730 | if (!isset($params['id'])) { |
731 | return false; |
732 | } |
733 | $id = $params['id']; |
734 | unset($params['id']); |
735 | |
736 | if (empty($params)) { |
737 | return false; |
738 | } |
739 | |
7466a42f |
740 | $sets = array(); |
741 | foreach ($params as $field=>$value) { |
742 | $sets[] = "$field = ?"; |
743 | } |
744 | |
745 | $params[] = $id; // last ? in WHERE condition |
746 | |
747 | $sets = implode(',', $sets); |
748 | $sql = "UPDATE {$this->prefix}$table SET $sets WHERE id=?"; |
749 | $rawsql = $this->emulate_bound_params($sql, $params); |
750 | |
a6283e91 |
751 | $this->query_start($sql, $params, SQL_QUERY_UPDATE); |
7466a42f |
752 | $result = $this->mysqli->query($rawsql); |
a6283e91 |
753 | $this->query_end($result); |
7466a42f |
754 | |
755 | if ($result === false) { |
756 | $this->report_error($sql, $params); |
757 | return false; |
758 | } |
759 | |
760 | return true; |
761 | } |
762 | |
763 | /** |
764 | * Update a record in a table |
765 | * |
766 | * $dataobject is an object containing needed data |
767 | * Relies on $dataobject having a variable "id" to |
768 | * specify the record to update |
769 | * |
770 | * @param string $table The database table to be checked against. |
771 | * @param object $dataobject An object with contents equal to fieldname=>fieldvalue. Must have an entry for 'id' to map to the table specified. |
772 | * @param bool true means repeated updates expected |
773 | * @return bool success |
774 | */ |
775 | public function update_record($table, $dataobject, $bulk=false) { |
776 | if (!is_object($dataobject)) { |
777 | $dataobject = (object)$dataobject; |
778 | } |
779 | |
780 | if (!isset($dataobject->id) ) { |
781 | return false; |
782 | } |
783 | |
784 | $columns = $this->get_columns($table); |
785 | $cleaned = array(); |
786 | |
787 | foreach ($dataobject as $field=>$value) { |
788 | if (!isset($columns[$field])) { |
789 | continue; |
790 | } |
791 | if (is_bool($value)) { |
792 | $value = (int)$value; // prevent "false" problems |
793 | } |
794 | $cleaned[$field] = $value; |
795 | } |
796 | |
797 | return $this->update_record_raw($table, $cleaned, $bulk); |
798 | } |
799 | |
800 | /** |
801 | * Set a single field in every table record which match a particular WHERE clause. |
802 | * |
803 | * @param string $table The database table to be checked against. |
804 | * @param string $newfield the field to set. |
805 | * @param string $newvalue the value to set the field to. |
806 | * @param string $select A fragment of SQL to be used in a where clause in the SQL call. |
807 | * @param array $params array of sql parameters |
808 | * @return bool success |
809 | */ |
810 | public function set_field_select($table, $newfield, $newvalue, $select, array $params=null) { |
811 | if ($select) { |
812 | $select = "WHERE $select"; |
813 | } |
814 | if (is_null($params)) { |
815 | $params = array(); |
816 | } |
817 | list($select, $params, $type) = $this->fix_sql_params($select, $params); |
818 | |
819 | if (is_bool($newvalue)) { |
820 | $newvalue = (int)$newvalue; // prevent "false" problems |
821 | } |
822 | if (is_null($newvalue)) { |
823 | $newfield = "$newfield = NULL"; |
824 | } else { |
825 | $newfield = "$newfield = ?"; |
826 | array_unshift($params, $newvalue); |
827 | } |
828 | $sql = "UPDATE {$this->prefix}$table SET $newfield $select"; |
829 | $rawsql = $this->emulate_bound_params($sql, $params); |
830 | |
a6283e91 |
831 | $this->query_start($sql, $params, SQL_QUERY_UPDATE); |
7466a42f |
832 | $result = $this->mysqli->query($rawsql); |
a6283e91 |
833 | $this->query_end($result); |
7466a42f |
834 | |
835 | if ($result === false) { |
836 | $this->report_error($sql, $params); |
837 | return false; |
838 | } |
839 | |
840 | return true; |
841 | } |
842 | |
843 | /** |
844 | * Delete one or more records from a table which match a particular WHERE clause. |
845 | * |
846 | * @param string $table The database table to be checked against. |
847 | * @param string $select A fragment of SQL to be used in a where clause in the SQL call (used to define the selection criteria). |
848 | * @param array $params array of sql parameters |
849 | * @return returns success. |
850 | */ |
851 | public function delete_records_select($table, $select, array $params=null) { |
852 | if ($select) { |
853 | $select = "WHERE $select"; |
854 | } |
855 | $sql = "DELETE FROM {$this->prefix}$table $select"; |
856 | |
857 | list($sql, $params, $type) = $this->fix_sql_params($sql, $params); |
858 | $rawsql = $this->emulate_bound_params($sql, $params); |
859 | |
a6283e91 |
860 | $this->query_start($sql, $params, SQL_QUERY_UPDATE); |
7466a42f |
861 | $result = $this->mysqli->query($rawsql); |
a6283e91 |
862 | $this->query_end($result); |
7466a42f |
863 | |
864 | if ($result === false) { |
865 | $this->report_error($sql, $params); |
866 | return false; |
867 | } |
868 | |
869 | return true; |
870 | } |
871 | |
872 | public function sql_cast_char2int($fieldname, $text=false) { |
873 | return ' CAST(' . $fieldname . ' AS SIGNED) '; |
874 | } |
875 | |
876 | public function sql_concat() { |
877 | $arr = func_get_args(); |
878 | $s = implode(',', $arr); |
879 | if ($s === '') { |
d5d0890c |
880 | return "''"; |
7466a42f |
881 | } |
882 | return "CONCAT($s)"; |
883 | } |
884 | |
885 | public function sql_concat_join($separator="' '", $elements=array()) { |
886 | for ($n=count($elements)-1; $n > 0 ; $n--) { |
887 | array_splice($elements, $n, 0, $separator); |
888 | } |
889 | $s = implode(',', $elements); |
890 | if ($s === '') { |
d5d0890c |
891 | return "''"; |
7466a42f |
892 | } |
893 | return "CONCAT ($s)"; |
894 | } |
895 | |
7466a42f |
896 | /** |
897 | * Does this driver suppoer regex syntax when searching |
898 | */ |
899 | public function sql_regex_supported() { |
900 | return true; |
901 | } |
902 | |
903 | /** |
904 | * Return regex positive or negative match sql |
905 | * @param bool $positivematch |
906 | * @return string or empty if not supported |
907 | */ |
908 | public function sql_regex($positivematch=true) { |
909 | return $positivematch ? 'REGEXP' : 'NOT REGEXP'; |
910 | } |
fa76662b |
911 | |
912 | /// transactions |
913 | /** |
914 | * on DBs that support it, switch to transaction mode and begin a transaction |
915 | * you'll need to ensure you call commit_sql() or your changes *will* be lost. |
916 | * |
917 | * this is _very_ useful for massive updates |
918 | */ |
919 | public function begin_sql() { |
a6283e91 |
920 | $sql = "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"; |
921 | $this->query_start($sql, NULL, SQL_QUERY_AUX); |
922 | $result = $this->mysqli->query($sql); |
923 | $this->query_end($result); |
924 | |
fa76662b |
925 | if ($result === false) { |
926 | return false; |
927 | } |
a6283e91 |
928 | |
929 | $sql = "BEGIN"; |
930 | $this->query_start($sql, NULL, SQL_QUERY_AUX); |
931 | $result = $this->mysqli->query($sql); |
932 | $this->query_end($result); |
933 | |
fa76662b |
934 | if ($result === false) { |
935 | return false; |
936 | } |
937 | return true; |
938 | } |
939 | |
940 | /** |
941 | * on DBs that support it, commit the transaction |
942 | */ |
943 | public function commit_sql() { |
a6283e91 |
944 | $sql = "COMMIT"; |
945 | $this->query_start($sql, NULL, SQL_QUERY_AUX); |
946 | $result = $this->mysqli->query($sql); |
947 | $this->query_end($result); |
948 | |
fa76662b |
949 | if ($result === false) { |
950 | return false; |
951 | } |
952 | return true; |
953 | } |
954 | |
955 | /** |
956 | * on DBs that support it, rollback the transaction |
957 | */ |
958 | public function rollback_sql() { |
a6283e91 |
959 | $sql = "ROLLBACK"; |
960 | $this->query_start($sql, NULL, SQL_QUERY_AUX); |
961 | $result = $this->mysqli->query($sql); |
962 | $this->query_end($result); |
963 | |
fa76662b |
964 | if ($result === false) { |
965 | return false; |
966 | } |
967 | return true; |
968 | } |
7466a42f |
969 | } |