ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilDBPdo.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
23
29abstract class ilDBPdo implements ilDBInterface, ilDBPdoInterface
30{
31 public array $options = [];
32 public const FEATURE_TRANSACTIONS = 'transactions';
33 public const FEATURE_FULLTEXT = 'fulltext';
34 public const FEATURE_SLAVE = 'slave';
35 protected string $host = '';
36 protected string $dbname = '';
37 protected string $charset = 'utf8';
38 protected string $username = '';
39 protected string $password = '';
40 protected int $port = 3306;
41 protected ?PDO $pdo = null;
44 protected ?int $limit = null;
45 protected ?int $offset = null;
46 protected string $storage_engine = 'InnoDB';
47 protected string $dsn = '';
51 protected array $attributes = [
52 // PDO::ATTR_EMULATE_PREPARES => true,
53 PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
54 ];
55 protected string $db_type = '';
56 protected int $error_code = 0;
58
62 public function connect(bool $return_false_for_error = false): ?bool
63 {
64 $this->generateDSN();
65 try {
66 $options = $this->getAttributes();
67 $this->pdo = new PDO($this->getDSN(), $this->getUsername(), $this->getPassword(), $options);
68 $this->initHelpers();
69 $this->initSQLMode();
70 } catch (Exception $e) {
71 $this->error_code = $e->getCode();
72 if ($return_false_for_error) {
73 return false;
74 }
75 throw $e;
76 }
77
78 return ($this->pdo->errorCode() === PDO::ERR_NONE);
79 }
80
81 abstract public function initHelpers(): void;
82
83 protected function initSQLMode(): void
84 {
85 }
86
87 protected function getAttributes(): array
88 {
90 foreach ($this->getAdditionalAttributes() as $k => $v) {
91 $options[$k] = $v;
92 }
93
94 return $options;
95 }
96
97 protected function getAdditionalAttributes(): array
98 {
99 return [];
100 }
101
103 {
105 }
106
108 {
109 $this->field_definition = $field_definition;
110 }
111
112 public function createDatabase(string $a_name, string $a_charset = "utf8", string $a_collation = ""): bool
113 {
114 $this->setDbname('');
115 $this->generateDSN();
116 $this->connect(true);
117 try {
118 $this->query($this->manager->getQueryUtils()->createDatabase($a_name, $a_charset, $a_collation));
119 return true;
120 } catch (PDOException) {
121 return false;
122 }
123 }
124
128 public function getLastErrorCode()
129 {
130 if ($this->pdo instanceof PDO) {
131 return $this->pdo->errorCode();
132 }
133
134 return $this->error_code;
135 }
136
137 public function initFromIniFile(?ilIniFile $ini = null): void
138 {
139 global $DIC;
140
141 if ($ini instanceof ilIniFile) {
142 $clientIniFile = $ini;
143 } elseif ($DIC->offsetExists('ilClientIniFile')) {
144 $clientIniFile = $DIC['ilClientIniFile'];
145 } else {
146 throw new InvalidArgumentException('$tmpClientIniFile is not an instance of ilIniFile');
147 }
148
149 $this->setUsername($clientIniFile->readVariable("db", "user"));
150 $this->setHost($clientIniFile->readVariable("db", "host"));
151 $this->setPort((int) $clientIniFile->readVariable("db", "port"));
152 $this->setPassword((string) $clientIniFile->readVariable("db", "pass"));
153 $this->setDbname($clientIniFile->readVariable("db", "name"));
154 $this->setDBType($clientIniFile->readVariable("db", "type"));
155
156 $this->generateDSN();
157 }
158
159 public function generateDSN(): void
160 {
161 $port = $this->getPort() !== 0 ? ";port=" . $this->getPort() : "";
162 $dbname = $this->getDbname() !== '' ? ';dbname=' . $this->getDbname() : '';
163 $host = $this->getHost();
164 $charset = ';charset=' . $this->getCharset();
165 $this->dsn = 'mysql:host=' . $host . $port . $dbname . $charset;
166 }
167
168 public function quoteIdentifier(string $identifier, bool $check_option = false): string
169 {
170 return '`' . preg_replace('/[^a-zA-Z0-9_$]/', '', $identifier) . '`';
171 }
172
176 abstract public function nextId(string $table_name): int;
177
181 public function createTable(
182 string $table_name,
183 array $fields,
184 bool $drop_table = false,
185 bool $ignore_erros = false
186 ): bool {
187 // check table name
188 if (!$ignore_erros && !$this->checkTableName($table_name)) {
189 throw new ilDatabaseException("ilDB Error: createTable(" . $table_name . ")");
190 }
191
192 // check definition array
193 if (!$ignore_erros && !$this->checkTableColumns($fields)) {
194 throw new ilDatabaseException("ilDB Error: createTable(" . $table_name . ")");
195 }
196
197 if ($drop_table) {
198 $this->dropTable($table_name, false);
199 }
200
201 return $this->manager->createTable($table_name, $fields, []);
202 }
203
204 protected function checkTableColumns(array $a_cols): bool
205 {
206 foreach ($a_cols as $col => $def) {
207 if (!$this->checkColumn($col, $def)) {
208 return false;
209 }
210 }
211
212 return true;
213 }
214
215 protected function checkColumn(string $a_col, array $a_def): bool
216 {
217 if (!$this->checkColumnName($a_col)) {
218 return false;
219 }
220 return $this->checkColumnDefinition($a_def);
221 }
222
223 protected function checkColumnDefinition(array $a_def, bool $a_modify_mode = false): bool
224 {
225 return $this->field_definition->checkColumnDefinition($a_def);
226 }
227
228 public function checkColumnName(string $a_name): bool
229 {
230 return $this->field_definition->checkColumnName($a_name);
231 }
232
236 public function addPrimaryKey(string $table_name, array $primary_keys): bool
237 {
238 assert(is_array($primary_keys));
239
240 $fields = [];
241 foreach ($primary_keys as $f) {
242 $fields[$f] = [];
243 }
244 $definition = [
245 'primary' => true,
246 'fields' => $fields,
247 ];
248 $this->manager->createConstraint(
249 $table_name,
250 $this->constraintName($table_name, $this->getPrimaryKeyIdentifier()),
251 $definition
252 );
253
254 return true;
255 }
256
260 public function dropIndexByFields(string $table_name, array $fields): bool
261 {
262 foreach ($this->manager->listTableIndexes($table_name) as $idx_name) {
263 $def = $this->reverse->getTableIndexDefinition($table_name, $idx_name);
264 $idx_fields = array_keys($def['fields']);
265
266 if ($idx_fields === $fields) {
267 return $this->dropIndex($table_name, $idx_name);
268 }
269 }
270
271 return false;
272 }
273
274 public function getPrimaryKeyIdentifier(): string
275 {
276 return "PRIMARY";
277 }
278
279 public function createSequence(string $table_name, int $start = 1): bool
280 {
281 $this->manager->createSequence($table_name, $start);
282 return true;
283 }
284
285 public function tableExists(string $table_name): bool
286 {
287 $result = $this->pdo->prepare("SHOW TABLES LIKE :table_name");
288 $result->execute(['table_name' => $table_name]);
289 $return = $result->rowCount();
290 $result->closeCursor();
291
292 return $return > 0;
293 }
294
295 public function tableColumnExists(string $table_name, string $column_name): bool
296 {
297 $fields = $this->loadModule(ilDBConstants::MODULE_MANAGER)->listTableFields($table_name);
298
299 return in_array($column_name, $fields, true);
300 }
301
305 public function addTableColumn(string $table_name, string $column_name, array $attributes): bool
306 {
307 if (!$this->checkColumnName($column_name)) {
308 throw new ilDatabaseException("ilDB Error: addTableColumn(" . $table_name . ", " . $column_name . ")");
309 }
310 if (!$this->checkColumnDefinition($attributes)) {
311 throw new ilDatabaseException("ilDB Error: addTableColumn(" . $table_name . ", " . $column_name . ")");
312 }
313
314 $changes = [
315 "add" => [
316 $column_name => $attributes,
317 ],
318 ];
319
320 return $this->manager->alterTable($table_name, $changes, false);
321 }
322
326 public function dropTable(string $table_name, bool $error_if_not_existing = true): bool
327 {
328 $ilDBPdoManager = $this->loadModule(ilDBConstants::MODULE_MANAGER);
329 $tables = $ilDBPdoManager->listTables();
330 $table_exists = in_array($table_name, $tables);
331 if (!$table_exists && $error_if_not_existing) {
332 throw new ilDatabaseException("Table $table_name does not exist");
333 }
334
335 // drop sequence
336 $sequences = $ilDBPdoManager->listSequences();
337 if (in_array($table_name, $sequences)) {
338 $ilDBPdoManager->dropSequence($table_name);
339 }
340
341 // drop table
342 if ($table_exists) {
343 $ilDBPdoManager->dropTable($table_name);
344 }
345
346 return true;
347 }
348
352 public function query(string $query): ilDBStatement
353 {
354 global $DIC;
355 $ilBench = $DIC['ilBench'] ?? null;
356
357 $query = $this->appendLimit($query);
358
359 try {
360 if ($ilBench instanceof ilBenchmark) {
361 $ilBench->startDbBench($query);
362 }
363 $res = $this->pdo->query($query);
364 if ($ilBench instanceof ilBenchmark) {
365 $ilBench->stopDbBench();
366 }
367 } catch (PDOException $e) {
368 throw new ilDatabaseException($e->getMessage() . ' QUERY: ' . $query, (int) $e->getCode(), $e);
369 }
370
371 $err = $this->pdo->errorCode();
372 if ($err !== PDO::ERR_NONE) {
373 $info = $this->pdo->errorInfo();
374 $info_message = $info[2];
375 throw new ilDatabaseException($info_message . ' QUERY: ' . $query);
376 }
377
378 return new ilPDOStatement($res);
379 }
380
381 public function fetchAll(ilDBStatement $statement, int $fetch_mode = ilDBConstants::FETCHMODE_ASSOC): array
382 {
383 $return = [];
384 while ($data = $statement->fetch($fetch_mode)) {
385 $return[] = $data;
386 }
387
388 return $return;
389 }
390
391 public function dropSequence(string $table_name): bool
392 {
393 $this->manager->dropSequence($table_name);
394 return true;
395 }
396
400 public function dropTableColumn(string $table_name, string $column_name): bool
401 {
402 $changes = [
403 "remove" => [
404 $column_name => [],
405 ],
406 ];
407
408 return $this->manager->alterTable($table_name, $changes, false);
409 }
410
414 public function renameTableColumn(string $table_name, string $column_old_name, string $column_new_name): bool
415 {
416 // check table name
417 if (!$this->checkColumnName($column_new_name)) {
418 throw new ilDatabaseException("ilDB Error: renameTableColumn(" . $table_name . "," . $column_old_name . "," . $column_new_name . ")");
419 }
420
421 $def = $this->reverse->getTableFieldDefinition($table_name, $column_old_name);
422
423 $analyzer = new ilDBAnalyzer($this);
424 $best_alt = $analyzer->getBestDefinitionAlternative($def);
425 $def = $def[$best_alt];
426 unset($def["nativetype"]);
427 unset($def["mdb2type"]);
428
429 $f["definition"] = $def;
430 $f["name"] = $column_new_name;
431
432 $changes = [
433 "rename" => [
434 $column_old_name => $f,
435 ],
436 ];
437
438 return $this->manager->alterTable($table_name, $changes, false);
439 }
440
441 public function insert(string $table_name, array $values): int
442 {
443 $real = [];
444 $fields = [];
445 foreach ($values as $key => $val) {
446 $real[] = $this->quote($val[1], $val[0]);
447 $fields[] = $this->quoteIdentifier($key);
448 }
449 $values_string = implode(",", $real);
450 $fields_string = implode(",", $fields);
451 $query = "INSERT INTO " . $this->quoteIdentifier($table_name) . " (" . $fields_string . ") VALUES (" . $values_string . ")";
452
453 $query = $this->sanitizeMB4StringIfNotSupported($query);
454
455 return (int) $this->pdo->exec($query);
456 }
457
458 public function fetchObject(ilDBStatement $query_result): ?stdClass
459 {
460 $res = $query_result->fetchObject();
461 if ($res === null) {
462 $query_result->closeCursor();
463
464 return null;
465 }
466
467 return $res;
468 }
469
470 public function update(string $table_name, array $columns, array $where): int
471 {
472 $fields = [];
473 $field_values = [];
474 $placeholders = [];
475 $placeholders_full = [];
476 $types = [];
477 $values = [];
478 $lobs = false;
479 $lob = [];
480 foreach ($columns as $k => $col) {
481 $field_value = $col[1];
482 $fields[] = $k;
483 $placeholders[] = "%s";
484 $placeholders_full[] = ":$k";
485 $types[] = $col[0];
486
487 if (($col[0] === "blob" || $col[0] === "clob" || $col[0] === 'text') && is_string($field_value)) {
488 $field_value = $this->sanitizeMB4StringIfNotSupported($field_value);
489 }
490
491 // integer auto-typecast (this casts bool values to integer)
492 if ($col[0] === 'integer' && !is_null($field_value)) {
493 $field_value = (int) $field_value;
494 }
495
496 $values[] = $field_value;
497 $field_values[$k] = $field_value;
498 if ($col[0] === "blob" || $col[0] === "clob") {
499 $lobs = true;
500 }
501 }
502
503 if ($lobs) {
504 $q = "UPDATE " . $this->quoteIdentifier($table_name) . " SET ";
505 $lim = "";
506 foreach ($fields as $k => $field) {
507 $q .= $lim . $this->quoteIdentifier($field) . " = " . $placeholders_full[$k];
508 $lim = ", ";
509 }
510 $q .= " WHERE ";
511 $lim = "";
512 foreach ($where as $k => $col) {
513 $q .= $lim . $this->quoteIdentifier($k) . " = " . $this->quote($col[1], $col[0]);
514 $lim = " AND ";
515 }
516
517 $r = $this->prepareManip($q, $types);
518 $this->execute($r, $field_values);
519
520 $num_affected_rows = $r->rowCount();
521
522 $this->free($r);
523 } else {
524 foreach ($where as $k => $col) {
525 $types[] = $col[0];
526 $values[] = $col[1];
527 $field_values[$k] = $col;
528 }
529 $q = "UPDATE " . $this->quoteIdentifier($table_name) . " SET ";
530 $lim = "";
531 foreach ($fields as $k => $field) {
532 $q .= $lim . $this->quoteIdentifier($field) . " = " . $placeholders[$k];
533 $lim = ", ";
534 }
535 $q .= " WHERE ";
536 $lim = "";
537 foreach (array_keys($where) as $k) {
538 $q .= $lim . $this->quoteIdentifier($k) . " = %s";
539 $lim = " AND ";
540 }
541
542 $num_affected_rows = $this->manipulateF($q, $types, $values);
543 }
544
545 return $num_affected_rows;
546 }
547
551 public function manipulate(string $query): int
552 {
553 global $DIC;
554 $ilBench = $DIC['ilBench'] ?? null;
555 try {
556 $query = $this->sanitizeMB4StringIfNotSupported($query);
557 if ($ilBench instanceof ilBenchmark) {
558 $ilBench->startDbBench($query);
559 }
560 $num_affected_rows = $this->pdo->exec($query);
561 if ($ilBench instanceof ilBenchmark) {
562 $ilBench->stopDbBench();
563 }
564 } catch (PDOException $e) {
565 throw new ilDatabaseException($e->getMessage() . ' QUERY: ' . $query, (int) $e->getCode(), $e);
566 }
567
568 return (int) $num_affected_rows;
569 }
570
571 public function fetchAssoc(ilDBStatement $statement): ?array
572 {
573 $res = $statement->fetch(PDO::FETCH_ASSOC);
574 if ($res === null || $res === false) {
575 $statement->closeCursor();
576
577 return null;
578 }
579
580 return $res;
581 }
582
583 public function numRows(ilDBStatement $statement): int
584 {
585 return $statement->rowCount();
586 }
587
588 public function quote($value, ?string $type = null): string
589 {
590 if ($value === null) {
591 return 'NULL';
592 }
593
594 $pdo_type = PDO::PARAM_STR;
595 switch ($type) {
599 if ($value === '') {
600 return 'NULL';
601 }
602 if ($value === $this->now()) {
603 return $value;
604 }
605 $value = (string) $value;
606 break;
608 return (string) (int) $value;
610 $pdo_type = PDO::PARAM_INT;
611 $value = (string) $value;
612 break;
614 default:
615 $value = (string) $value;
616 $pdo_type = PDO::PARAM_STR;
617 break;
618 }
619
620 return $this->pdo->quote((string) $value, $pdo_type);
621 }
622
623 public function indexExistsByFields(string $table_name, array $fields): bool
624 {
625 foreach ($this->manager->listTableIndexes($table_name) as $idx_name) {
626 $def = $this->reverse->getTableIndexDefinition($table_name, $idx_name);
627 $idx_fields = array_keys($def['fields']);
628
629 if ($idx_fields === $fields) {
630 return true;
631 }
632 }
633
634 return false;
635 }
636
637 public function addIndex(string $table_name, array $fields, string $index_name = '', bool $fulltext = false): bool
638 {
639 assert(is_array($fields));
640 $this->field_definition->checkIndexName($index_name);
641
642 $definition_fields = [];
643 foreach ($fields as $f) {
644 $definition_fields[$f] = [];
645 }
646 $definition = [
647 'fields' => $definition_fields,
648 ];
649
650 if (!$fulltext) {
651 $this->manager->createIndex($table_name, $this->constraintName($table_name, $index_name), $definition);
652 } elseif ($this->supportsFulltext()) {
653 $this->addFulltextIndex($table_name, $fields, $index_name);
654 // TODO
655 }
656
657 return true;
658 }
659
663 public function addFulltextIndex(string $table, array $fields, string $a_name = "in"): bool
664 {
665 $i_name = $this->constraintName($table, $a_name) . "_idx";
666 $f_str = implode(",", $fields);
667 $q = "ALTER TABLE $table ADD FULLTEXT $i_name ($f_str)";
668 $this->query($q);
669 return true;
670 }
671
675 public function dropFulltextIndex(string $a_table, string $a_name): bool
676 {
677 $i_name = $this->constraintName($a_table, $a_name) . "_idx";
678 $this->query("ALTER TABLE $a_table DROP FULLTEXT $i_name");
679 return true;
680 }
681
685 public function isFulltextIndex(string $a_table, string $a_name): bool
686 {
687 $set = $this->query("SHOW INDEX FROM " . $a_table);
688 while ($rec = $this->fetchAssoc($set)) {
689 if ($rec["Key_name"] === $a_name && $rec["Index_type"] === "FULLTEXT") {
690 return true;
691 }
692 }
693
694 return false;
695 }
696
697 public function getIndexName(string $index_name_base): string
698 {
699 return sprintf(ilDBPdoFieldDefinition::INDEX_FORMAT, preg_replace('/[^a-z0-9_\$]/i', '_', $index_name_base));
700 }
701
702 public function getSequenceName(string $table_name): string
703 {
704 return sprintf(ilDBPdoFieldDefinition::SEQUENCE_FORMAT, preg_replace('/[^a-z0-9_\$.]/i', '_', $table_name));
705 }
706
711 public function constraintName(string $a_table, string $a_constraint): string
712 {
713 return $a_constraint;
714 }
715
716 public function getDSN(): string
717 {
718 return $this->dsn;
719 }
720
721 public function getDBType(): string
722 {
723 return $this->db_type;
724 }
725
726 public function setDBType(string $type): void
727 {
728 $this->db_type = $type;
729 }
730
736 public static function getReservedWords(): array
737 {
738 global $DIC;
739 $ilDB = $DIC->database();
740
744 $fd = $ilDB->getFieldDefinition();
745 if ($fd !== null) {
746 return $fd->getReservedMysql();
747 }
748 return [];
749 }
750
754 public function lockTables(array $tables): void
755 {
756 assert(is_array($tables));
757 $lock = $this->manager->getQueryUtils()->lock($tables);
758 $this->pdo->exec($lock);
759 }
760
765 public function unlockTables(): void
766 {
767 $this->pdo->exec($this->manager->getQueryUtils()->unlock());
768 }
769
770 public function in(string $field, array $values, bool $negate = false, string $type = ""): string
771 {
772 return $this->manager->getQueryUtils()->in($field, $values, $negate, $type);
773 }
774
779 public function queryF(string $query, array $types, array $values): ilDBStatement
780 {
781 if (!is_array($types) || !is_array($values) || count($types) !== count($values)) {
782 throw new ilDatabaseException("ilDB::queryF: Types and values must be arrays of same size. ($query)");
783 }
784 $quoted_values = [];
785 foreach ($types as $k => $t) {
786 $quoted_values[] = $this->quote($values[$k], $t);
787 }
788 $query = vsprintf($query, $quoted_values);
789
790 return $this->query($query);
791 }
792
797 public function manipulateF(string $query, array $types, array $values): int
798 {
799 if (!is_array($types) || !is_array($values) || count($types) !== count($values)) {
800 throw new ilDatabaseException("ilDB::manipulateF: types and values must be arrays of same size. ($query)");
801 }
802 $quoted_values = [];
803 foreach ($types as $k => $t) {
804 $quoted_values[] = $this->quote($values[$k], $t);
805 }
806 $query = vsprintf($query, $quoted_values);
807
808 return $this->manipulate($query);
809 }
810
814 public function useSlave(bool $bool): bool
815 {
816 return false;
817 }
818
822 public function setLimit(int $limit, int $offset = 0): void
823 {
824 $this->limit = $limit;
825 $this->offset = $offset;
826 }
827
831 public function like(string $column, string $type, string $value = "?", bool $case_insensitive = true): string
832 {
833 return $this->manager->getQueryUtils()->like($column, $type, $value, $case_insensitive);
834 }
835
839 public function now(): string
840 {
841 return $this->manager->getQueryUtils()->now();
842 }
843
844 public function replace(string $table, array $primary_keys, array $other_columns): int
845 {
846 $a_columns = array_merge($primary_keys, $other_columns);
847 $fields = [];
848 $placeholders = [];
849 $types = [];
850 $values = [];
851
852 foreach ($a_columns as $k => $col) {
853 $fields[] = $this->quoteIdentifier($k);
854 $placeholders[] = "%s";
855 $placeholders2[] = ":$k";
856 $types[] = $col[0];
857
858 // integer auto-typecast (this casts bool values to integer)
859 if ($col[0] === 'integer' && !is_null($col[1])) {
860 $col[1] = (int) $col[1];
861 }
862
863 $values[] = $col[1];
864 }
865
866 $q = "REPLACE INTO " . $table . " (" . implode(",", $fields) . ") VALUES (" . implode(",", $placeholders) . ")";
867
868 return $this->manipulateF($q, $types, $values);
869 }
870
874 public function equals(string $columns, $value, string $type, bool $emptyOrNull = false): string
875 {
876 if (!$emptyOrNull || $value != "") {
877 return $columns . " = " . $this->quote($value, $type);
878 }
879
880 return "(" . $columns . " = '' OR $columns IS NULL)";
881 }
882
883 public function getHost(): string
884 {
885 return $this->host;
886 }
887
888 public function setHost(string $host): void
889 {
890 $this->host = $host;
891 }
892
893 public function getDbname(): string
894 {
895 return $this->dbname;
896 }
897
898 public function setDbname(string $dbname): void
899 {
900 $this->dbname = $dbname;
901 }
902
903 public function getCharset(): string
904 {
905 return $this->charset;
906 }
907
908 public function setCharset(string $charset): void
909 {
910 $this->charset = $charset;
911 }
912
913 public function getUsername(): string
914 {
915 return $this->username;
916 }
917
918 public function setUsername(string $username): void
919 {
920 $this->username = $username;
921 }
922
923 public function getPassword(): string
924 {
925 return $this->password;
926 }
927
928 public function setPassword(string $password): void
929 {
930 $this->password = $password;
931 }
932
933 public function getPort(): int
934 {
935 return $this->port;
936 }
937
938 public function setPort(int $port): void
939 {
940 $this->port = $port;
941 }
942
943 public function setDBUser(string $user): void
944 {
945 $this->setUsername($user);
946 }
947
948 public function setDBPort(int $port): void
949 {
950 $this->setPort($port);
951 }
952
953 public function setDBPassword(string $password): void
954 {
955 $this->setPassword($password);
956 }
957
958 public function setDBHost(string $host): void
959 {
960 $this->setHost($host);
961 }
962
966 public function upper(string $expression): string
967 {
968 return " UPPER(" . $expression . ") ";
969 }
970
974 public function lower(string $expression): string
975 {
976 return " LOWER(" . $expression . ") ";
977 }
978
979 public function substr(string $a_exp, int $a_pos = 1, int $a_len = -1): string
980 {
981 $lenstr = "";
982 if ($a_len > -1) {
983 $lenstr = ", " . $a_len;
984 }
985 return " SUBSTR(" . $a_exp . ", " . $a_pos . $lenstr . ") ";
986 }
987
988 public function prepareManip(string $query, ?array $types = null): ilDBStatement
989 {
990 return new ilPDOStatement($this->pdo->prepare($query));
991 }
992
993 public function prepare(string $query, ?array $types = null, ?array $result_types = null): ilDBStatement
994 {
995 return new ilPDOStatement($this->pdo->prepare($query));
996 }
997
998 public function enableResultBuffering(bool $a_status): void
999 {
1000 $this->pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $a_status);
1001 }
1002
1006 public function execute(ilDBStatement $stmt, array $data = []): ilDBStatement
1007 {
1011 $result = $stmt->execute($data);
1012 if ($result === false) {//This may not work since execute returns an object
1013 throw new ilDatabaseException(implode(', ', $stmt->errorInfo()), (int) $stmt->errorCode());
1014 }
1015 return $stmt;
1016 }
1017
1018 public function supportsSlave(): bool
1019 {
1020 return false;
1021 }
1022
1023 public function supportsFulltext(): bool
1024 {
1025 return false;
1026 }
1027
1028 public function supportsTransactions(): bool
1029 {
1030 return false;
1031 }
1032
1033 public function supports(string $feature): bool
1034 {
1035 return match ($feature) {
1036 self::FEATURE_TRANSACTIONS => $this->supportsTransactions(),
1037 self::FEATURE_FULLTEXT => $this->supportsFulltext(),
1038 self::FEATURE_SLAVE => $this->supportsSlave(),
1039 default => false,
1040 };
1041 }
1042
1046 public function listTables(): array
1047 {
1048 return $this->manager->listTables();
1049 }
1050
1054 public function loadModule(string $module)
1055 {
1056 return match ($module) {
1057 ilDBConstants::MODULE_MANAGER => $this->manager,
1058 ilDBConstants::MODULE_REVERSE => $this->reverse,
1059 default => throw new LogicException('module "' . $module . '" not available'),
1060 };
1061 }
1062
1066 public function getAllowedAttributes(): array
1067 {
1068 return $this->field_definition->getAllowedAttributes();
1069 }
1070
1071 public function sequenceExists(string $sequence): bool
1072 {
1073 return in_array($sequence, $this->listSequences(), true);
1074 }
1075
1076 public function listSequences(): array
1077 {
1078 return $this->manager->listSequences();
1079 }
1080
1081 public function concat(array $values, bool $allow_null = true): string
1082 {
1083 return $this->manager->getQueryUtils()->concat($values, $allow_null);
1084 }
1085
1086 protected function appendLimit(string $query): string
1087 {
1088 if ($this->limit !== null && $this->offset !== null) {
1089 $query .= ' LIMIT ' . $this->offset . ', ' . $this->limit;
1090 $this->limit = null;
1091 $this->offset = null;
1092
1093 return $query;
1094 }
1095
1096 return $query;
1097 }
1098
1099 public function locate(string $needle, string $string, int $start_pos = 1): string
1100 {
1101 return $this->manager->getQueryUtils()->locate($needle, $string, $start_pos);
1102 }
1103
1107 public function modifyTableColumn(string $table, string $column, array $attributes): bool
1108 {
1109 $def = $this->reverse->getTableFieldDefinition($table, $column);
1110
1111 $analyzer = new ilDBAnalyzer($this);
1112 $best_alt = $analyzer->getBestDefinitionAlternative($def);
1113 $def = $def[$best_alt];
1114 unset($def["nativetype"], $def["mdb2type"]);
1115
1116 // check attributes
1117 $ilDBPdoFieldDefinition = $this->field_definition;
1118
1119 $type = $attributes["type"] ?? $def["type"];
1120
1121 foreach (array_keys($def) as $k) {
1122 if ($k !== "type" && !$ilDBPdoFieldDefinition->isAllowedAttribute($k, $type)) {
1123 unset($def[$k]);
1124 }
1125 }
1126 $check_array = $def;
1127 foreach ($attributes as $k => $v) {
1128 $check_array[$k] = $v;
1129 }
1130 if (!$this->checkColumnDefinition($check_array, true)) {
1131 throw new ilDatabaseException("ilDB Error: modifyTableColumn(" . $table . ", " . $column . ")");
1132 }
1133
1134 foreach ($attributes as $a => $v) {
1135 $def[$a] = $v;
1136 }
1137
1138 $attributes["definition"] = $def;
1139
1140 $changes = [
1141 "change" => [
1142 $column => $attributes,
1143 ],
1144 ];
1145
1146 return $this->manager->alterTable($table, $changes, false);
1147 }
1148
1149 public function free(ilDBStatement $a_st): void
1150 {
1151 $a_st->closeCursor();
1152 }
1153
1157 public function renameTable(string $name, string $new_name): bool
1158 {
1159 // check table name
1160 try {
1161 $this->checkTableName($new_name);
1162 } catch (ilDatabaseException $e) {
1163 throw new ilDatabaseException("ilDB Error: renameTable(" . $name . "," . $new_name . ")<br />" . $e->getMessage(), $e->getCode(), $e);
1164 }
1165
1166 $this->manager->alterTable($name, ["name" => $new_name], false);
1167 if ($this->sequenceExists($name)) {
1168 $this->manager->alterTable(
1169 $this->getSequenceName($name),
1170 ["name" => $this->getSequenceName($new_name)],
1171 false
1172 );
1173 }
1174 // The abstraction_progress is no longer used in ILIAS, see http://www.ilias.de/mantis/view.php?id=19513
1175 // $query = "UPDATE abstraction_progress " . "SET table_name = " . $this->quote($a_new_name, 'text') . " " . "WHERE table_name = "
1176 // . $this->quote($a_name, 'text');
1177 // $this->pdo->query($query);
1178
1179 return true;
1180 }
1181
1185 public function checkTableName(string $a_name): bool
1186 {
1187 return $this->field_definition->checkTableName($a_name);
1188 }
1189
1190 public static function isReservedWord(string $a_word): bool
1191 {
1192 global $DIC;
1193 return (new ilDBPdoMySQLFieldDefinition($DIC->database()))->isReserved($a_word);
1194 }
1195
1199 public function beginTransaction(): bool
1200 {
1201 if (!$this->supports(self::FEATURE_TRANSACTIONS)) {
1202 throw new ilDatabaseException("ilDB::beginTransaction: Transactions are not supported.");
1203 }
1204
1205 return $this->pdo->beginTransaction();
1206 }
1207
1211 public function commit(): bool
1212 {
1213 if (!$this->supports(self::FEATURE_TRANSACTIONS)) {
1214 throw new ilDatabaseException("ilDB::beginTransaction: Transactions are not supported.");
1215 }
1216
1217 return $this->pdo->commit();
1218 }
1219
1223 public function rollback(): bool
1224 {
1225 if (!$this->supports(self::FEATURE_TRANSACTIONS)) {
1226 throw new ilDatabaseException("ilDB::beginTransaction: Transactions are not supported.");
1227 }
1228
1229 return $this->pdo->rollBack();
1230 }
1231
1232 public function dropIndex(string $a_table, string $a_name = "i1"): bool
1233 {
1234 return $this->manager->dropIndex($a_table, $a_name);
1235 }
1236
1237 public function setStorageEngine(string $storage_engine): void
1238 {
1239 $this->storage_engine = $storage_engine;
1240 }
1241
1242 public function getStorageEngine(): string
1243 {
1244 return $this->storage_engine;
1245 }
1246
1247 public function queryCol(string $query, int $type = PDO::FETCH_ASSOC, int $colnum = 0): array
1248 {
1249 $type = match ($type) {
1250 ilDBConstants::FETCHMODE_ASSOC => PDO::FETCH_ASSOC,
1251 ilDBConstants::FETCHMODE_OBJECT => PDO::FETCH_OBJ,
1252 default => PDO::FETCH_ASSOC,
1253 };
1254
1255 return $this->pdo->query($query, PDO::FETCH_ASSOC)->fetchAll(PDO::FETCH_COLUMN, $colnum);
1256 }
1257
1258 public function queryRow(
1259 string $query,
1260 ?array $types = null,
1261 int $fetchmode = ilDBConstants::FETCHMODE_DEFAULT
1262 ): array {
1263 $type = match ($fetchmode) {
1264 ilDBConstants::FETCHMODE_ASSOC => PDO::FETCH_ASSOC,
1265 ilDBConstants::FETCHMODE_OBJECT => PDO::FETCH_OBJ,
1266 default => PDO::FETCH_ASSOC,
1267 };
1268
1269 return $this->pdo->query($query, $type)->fetch();
1270 }
1271
1272 public function getServerVersion(bool $native = false): int
1273 {
1274 return $this->pdo->query('SELECT VERSION()')->fetchColumn();
1275 }
1276
1277 public function escape(string $value, bool $escape_wildcards = false): string
1278 {
1279 return $value;
1280 }
1281
1282 public function escapePattern(string $text): string
1283 {
1284 return $text;
1285 }
1286
1287 public function migrateAllTablesToEngine(string $engine = ilDBConstants::MYSQL_ENGINE_INNODB): array
1288 {
1289 return [];
1290 }
1291
1296 {
1297 return [];
1298 }
1299
1303 public function supportsCollationMigration(): bool
1304 {
1305 return false;
1306 }
1307
1308 public function supportsEngineMigration(): bool
1309 {
1310 return false;
1311 }
1312
1316 public function checkIndexName(string $name): bool
1317 {
1318 $fd = $this->getFieldDefinition();
1319 if ($fd !== null) {
1320 return $fd->checkIndexName($name);
1321 }
1322 return false;
1323 }
1324
1328 public function addUniqueConstraint(string $table, array $fields, string $name = "con"): bool
1329 {
1330 assert(is_array($fields));
1331 $manager = $this->manager;
1332
1333 // check index name
1334 if (!$this->checkIndexName($name)) {
1335 throw new ilDatabaseException("ilDB Error: addUniqueConstraint(" . $table . "," . $name . ")");
1336 }
1337
1338 $fields_corrected = [];
1339 foreach ($fields as $f) {
1340 $fields_corrected[$f] = [];
1341 }
1342 $definition = [
1343 'unique' => true,
1344 'fields' => $fields_corrected,
1345 ];
1346
1347 return $manager->createConstraint($table, $this->constraintName($table, $name), $definition);
1348 }
1349
1350 public function dropUniqueConstraint(string $table, string $name = "con"): bool
1351 {
1352 return $this->manager->dropConstraint($table, $this->constraintName($table, $name), false);
1353 }
1354
1355 public function dropUniqueConstraintByFields(string $table, array $fields): bool
1356 {
1357 $analyzer = new ilDBAnalyzer();
1358 $cons = $analyzer->getConstraintsInformation($table);
1359 foreach ($cons as $c) {
1360 if ($c["type"] === "unique" && count($fields) === count($c["fields"])) {
1361 $all_in = true;
1362 foreach ($fields as $f) {
1363 if (!isset($c["fields"][$f])) {
1364 $all_in = false;
1365 }
1366 }
1367 if ($all_in) {
1368 return $this->dropUniqueConstraint($table, $c['name']);
1369 }
1370 }
1371 }
1372
1373 return false;
1374 }
1375
1376 public function getLastInsertId(): int
1377 {
1378 return (int) $this->pdo->lastInsertId();
1379 }
1380
1381 public function buildAtomQuery(): ilAtomQuery
1382 {
1383 return new ilAtomQueryLock($this);
1384 }
1385
1386 public function uniqueConstraintExists(string $table, array $fields): bool
1387 {
1388 $analyzer = new ilDBAnalyzer();
1389 $cons = $analyzer->getConstraintsInformation($table);
1390 foreach ($cons as $c) {
1391 if ($c["type"] === "unique" && count($fields) === count($c["fields"])) {
1392 $all_in = true;
1393 foreach ($fields as $f) {
1394 if (!isset($c["fields"][$f])) {
1395 $all_in = false;
1396 }
1397 }
1398 if ($all_in) {
1399 return true;
1400 }
1401 }
1402 }
1403
1404 return false;
1405 }
1406
1407 public function dropPrimaryKey(string $table_name): bool
1408 {
1409 return $this->manager->dropConstraint($table_name, "PRIMARY", true);
1410 }
1411
1412 public function executeMultiple(ilDBStatement $stmt, array $data): array
1413 {
1414 foreach ($data as $set) {
1415 $this->execute($stmt, $set);
1416 }
1417 return [];
1418 }
1419
1420 public function fromUnixtime(string $expr, bool $to_text = true): string
1421 {
1422 return "FROM_UNIXTIME(" . $expr . ")";
1423 }
1424
1425 public function unixTimestamp(): string
1426 {
1427 return "UNIX_TIMESTAMP()";
1428 }
1429
1430
1434 public function getDBVersion(): string
1435 {
1436 $d = $this->fetchObject($this->query("SELECT VERSION() AS version"));
1437
1438 if ($d !== null && $d->version) {
1439 return $d->version;
1440 }
1441 return 'Unknown';
1442 }
1443
1447 public function sanitizeMB4StringIfNotSupported(string $query): string
1448 {
1449 if (!$this->doesCollationSupportMB4Strings()) {
1450 $query_replaced = preg_replace(
1451 '/[\x{10000}-\x{10FFFF}]/u',
1453 $query
1454 );
1455 if (!empty($query_replaced)) {
1456 return $query_replaced;
1457 }
1458 }
1459
1460 return $query;
1461 }
1462
1466 public function doesCollationSupportMB4Strings(): bool
1467 {
1468 return false;
1469 }
1470
1474 public function groupConcat(string $a_field_name, string $a_seperator = ",", ?string $a_order = null): string
1475 {
1476 return $this->manager->getQueryUtils()->groupConcat($a_field_name, $a_seperator, $a_order);
1477 }
1478
1482 public function cast(string $a_field_name, string $a_dest_type): string
1483 {
1484 return $this->manager->getQueryUtils()->cast($a_field_name, $a_dest_type);
1485 }
1486
1487 public function addForeignKey(
1488 string $foreign_key_name,
1489 array $field_names,
1490 string $table_name,
1491 array $reference_field_names,
1492 string $reference_table,
1493 ?ForeignKeyConstraints $on_update = null,
1494 ?ForeignKeyConstraints $on_delete = null
1495 ): bool {
1496 return $this->manager->addForeignKey($foreign_key_name, $field_names, $table_name, $reference_field_names, $reference_table, $on_update, $on_delete);
1497 }
1498
1499 public function dropForeignKey(string $foreign_key_name, string $table_name): bool
1500 {
1501 return $this->manager->dropForeignKey($foreign_key_name, $table_name);
1502 }
1503
1504 public function foreignKeyExists(string $foreign_key_name, string $table_name): bool
1505 {
1506 return $this->manager->foreignKeyExists($foreign_key_name, $table_name);
1507 }
1508
1510 {
1511 return new Integrity($this);
1512 }
1513
1514 public function primaryExistsByFields(string $table_name, array $fields): bool
1515 {
1516 $constraints = $this->manager->listTableConstraints($table_name);
1517
1518 if (in_array('primary', $constraints)) {
1519 $definitions = $this->reverse->getTableConstraintDefinition($table_name, 'primary');
1520 $primary_fields = array_keys($definitions['fields']);
1521 sort($primary_fields);
1522 sort($fields);
1523
1524 return $primary_fields === $fields;
1525 }
1526 return false;
1527 }
1528}
Class ilAtomQueryLock.
Class ilBenchmark.
This class gives all kind of DB information using the database manager and reverse module.
Class ilDBPdoFieldDefinition.
Class ilDBPdoManager.
createConstraint(string $table, string $name, array $definition)
Class ilDBPdoMySQLFieldDefinition.
Class ilDBPdoReverse.
Class pdoDB.
string $db_type
renameTable(string $name, string $new_name)
fetchAll(ilDBStatement $statement, int $fetch_mode=ilDBConstants::FETCHMODE_ASSOC)
addForeignKey(string $foreign_key_name, array $field_names, string $table_name, array $reference_field_names, string $reference_table, ?ForeignKeyConstraints $on_update=null, ?ForeignKeyConstraints $on_delete=null)
setUsername(string $username)
dropUniqueConstraintByFields(string $table, array $fields)
supportsTransactions()
checkColumnName(string $a_name)
string $dbname
setLimit(int $limit, int $offset=0)
Set the Limit for the next Query.
createTable(string $table_name, array $fields, bool $drop_table=false, bool $ignore_erros=false)
in(string $field, array $values, bool $negate=false, string $type="")
tableExists(string $table_name)
getFieldDefinition()
manipulateF(string $query, array $types, array $values)
string $storage_engine
dropTable(string $table_name, bool $error_if_not_existing=true)
string $username
sanitizeMB4StringIfNotSupported(string $query)
string sanitized query
isFulltextIndex(string $a_table, string $a_name)
Is index a fulltext index?
numRows(ilDBStatement $statement)
setStorageEngine(string $storage_engine)
escapePattern(string $text)
free(ilDBStatement $a_st)
connect(bool $return_false_for_error=false)
insert(string $table_name, array $values)
fromUnixtime(string $expr, bool $to_text=true)
setPort(int $port)
groupConcat(string $a_field_name, string $a_seperator=",", ?string $a_order=null)
enableResultBuffering(bool $a_status)
checkIndexName(string $name)
dropIndex(string $a_table, string $a_name="i1")
addPrimaryKey(string $table_name, array $primary_keys)
dropForeignKey(string $foreign_key_name, string $table_name)
substr(string $a_exp, int $a_pos=1, int $a_len=-1)
queryF(string $query, array $types, array $values)
string $dsn
getLastErrorCode()
supportsEngineMigration()
string $host
query(string $query)
getPrimaryKeyIdentifier()
executeMultiple(ilDBStatement $stmt, array $data)
string $password
getAllowedAttributes()
string[]
checkTableName(string $a_name)
setDBPassword(string $password)
setHost(string $host)
addFulltextIndex(string $table, array $fields, string $a_name="in")
sequenceExists(string $sequence)
getDBType()
Get DSN.
array $options
escape(string $value, bool $escape_wildcards=false)
ilDBPdoReverse $reverse
buildIntegrityAnalyser()
getServerVersion(bool $native=false)
modifyTableColumn(string $table, string $column, array $attributes)
string $charset
addUniqueConstraint(string $table, array $fields, string $name="con")
migrateAllTablesToCollation(string $collation=ilDBConstants::MYSQL_COLLATION_UTF8MB4)
@inheritDoc
setCharset(string $charset)
dropFulltextIndex(string $a_table, string $a_name)
Drop fulltext index.
getAdditionalAttributes()
dropUniqueConstraint(string $table, string $name="con")
addIndex(string $table_name, array $fields, string $index_name='', bool $fulltext=false)
getSequenceName(string $table_name)
setFieldDefinition(\ilDBPdoFieldDefinition $field_definition)
createSequence(string $table_name, int $start=1)
dropPrimaryKey(string $table_name)
const FEATURE_SLAVE
supportsCollationMigration()
@inheritDoc
appendLimit(string $query)
prepareManip(string $query, ?array $types=null)
loadModule(string $module)
setDBHost(string $host)
queryCol(string $query, int $type=PDO::FETCH_ASSOC, int $colnum=0)
setDBType(string $type)
setDbname(string $dbname)
indexExistsByFields(string $table_name, array $fields)
concat(array $values, bool $allow_null=true)
renameTableColumn(string $table_name, string $column_old_name, string $column_new_name)
equals(string $columns, $value, string $type, bool $emptyOrNull=false)
lockTables(array $tables)
checkColumn(string $a_col, array $a_def)
migrateAllTablesToEngine(string $engine=ilDBConstants::MYSQL_ENGINE_INNODB)
primaryExistsByFields(string $table_name, array $fields)
prepare(string $query, ?array $types=null, ?array $result_types=null)
Prepare a query (SELECT) statement to be used with execute.
ilDBPdoFieldDefinition $field_definition
array $attributes
int $error_code
ilDBPdoManager $manager
like(string $column, string $type, string $value="?", bool $case_insensitive=true)
dropIndexByFields(string $table_name, array $fields)
quoteIdentifier(string $identifier, bool $check_option=false)
doesCollationSupportMB4Strings()
@inheritDoc
const FEATURE_TRANSACTIONS
dropTableColumn(string $table_name, string $column_name)
createDatabase(string $a_name, string $a_charset="utf8", string $a_collation="")
getIndexName(string $index_name_base)
manipulate(string $query)
addTableColumn(string $table_name, string $column_name, array $attributes)
constraintName(string $a_table, string $a_constraint)
Determine contraint name by table name and constraint name.
useSlave(bool $bool)
TODO.
fetchAssoc(ilDBStatement $statement)
checkTableColumns(array $a_cols)
replace(string $table, array $primary_keys, array $other_columns)
Replace into method.
locate(string $needle, string $string, int $start_pos=1)
checkColumnDefinition(array $a_def, bool $a_modify_mode=false)
upper(string $expression)
setDBUser(string $user)
const FEATURE_FULLTEXT
setDBPort(int $port)
foreignKeyExists(string $foreign_key_name, string $table_name)
dropSequence(string $table_name)
uniqueConstraintExists(string $table, array $fields)
nextId(string $table_name)
getDSN()
Get DSN.
cast(string $a_field_name, string $a_dest_type)
fetchObject(ilDBStatement $query_result)
update(string $table_name, array $columns, array $where)
@description $where MUST contain existing columns only.
static isReservedWord(string $a_word)
supports(string $feature)
setPassword(string $password)
tableColumnExists(string $table_name, string $column_name)
initHelpers()
queryRow(string $query, ?array $types=null, int $fetchmode=ilDBConstants::FETCHMODE_DEFAULT)
initFromIniFile(?ilIniFile $ini=null)
quote($value, ?string $type=null)
lower(string $expression)
Class ilDatabaseException.
INIFile Parser Early access in init proceess! Avoid further dependencies like logging or other servic...
Class ilPDOStatement is a Wrapper Class for PDOStatement.
$c
Definition: deliver.php:25
return['delivery_method'=> 'php',]
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$info
Definition: entry_point.php:21
Interface ilAtomQuery Use ilAtomQuery to fire Database-Actions which have to be done without beeing i...
Interface ilDBInterface.
Interface ilDBPdoInterface.
Interface ilDBStatement.
execute(?array $a_data=null)
fetch(int $fetch_mode=ilDBConstants::FETCHMODE_ASSOC)
$res
Definition: ltiservices.php:69
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples
$ini
Definition: raiseError.php:20
if(!file_exists('../ilias.ini.php'))
global $DIC
Definition: shib_login.php:26
$q
Definition: shib_logout.php:23