ILIAS  release_10 Revision v10.1-43-ga1241a92c2f
class.ilDBPdo.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
23 
29 abstract 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 = array(
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  {
89  $options = $this->attributes;
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 array();
100  }
101 
103  {
105  }
106 
107  public function setFieldDefinition(\ilDBPdoFieldDefinition $field_definition): void
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 $e) {
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()
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, array());
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 = array();
241  foreach ($primary_keys as $f) {
242  $fields[$f] = array();
243  }
244  $definition = array(
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(array('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 = array(
315  "add" => array(
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());
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 = array(
403  "remove" => array(
404  $column_name => array(),
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 = array(
433  "rename" => array(
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 = array();
444  $fields = array();
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 = array();
473  $field_values = array();
474  $placeholders = array();
475  $placeholders_full = array();
476  $types = array();
477  $values = array();
478  $lobs = false;
479  $lob = array();
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());
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 = array();
643  foreach ($fields as $f) {
644  $definition_fields[$f] = array();
645  }
646  $definition = array(
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 = array();
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 = array();
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  switch ($feature) {
1036  case self::FEATURE_TRANSACTIONS:
1037  return $this->supportsTransactions();
1038  case self::FEATURE_FULLTEXT:
1039  return $this->supportsFulltext();
1040  case self::FEATURE_SLAVE:
1041  return $this->supportsSlave();
1042  default:
1043  return false;
1044  }
1045  }
1046 
1050  public function listTables(): array
1051  {
1052  return $this->manager->listTables();
1053  }
1054 
1058  public function loadModule(string $module)
1059  {
1060  switch ($module) {
1062  return $this->manager;
1064  return $this->reverse;
1065  }
1066  throw new LogicException('module "' . $module . '" not available');
1067  }
1068 
1072  public function getAllowedAttributes(): array
1073  {
1074  return $this->field_definition->getAllowedAttributes();
1075  }
1076 
1077  public function sequenceExists(string $sequence): bool
1078  {
1079  return in_array($sequence, $this->listSequences(), true);
1080  }
1081 
1082  public function listSequences(): array
1083  {
1084  return $this->manager->listSequences();
1085  }
1086 
1087  public function concat(array $values, bool $allow_null = true): string
1088  {
1089  return $this->manager->getQueryUtils()->concat($values, $allow_null);
1090  }
1091 
1092  protected function appendLimit(string $query): string
1093  {
1094  if ($this->limit !== null && $this->offset !== null) {
1095  $query .= ' LIMIT ' . $this->offset . ', ' . $this->limit;
1096  $this->limit = null;
1097  $this->offset = null;
1098 
1099  return $query;
1100  }
1101 
1102  return $query;
1103  }
1104 
1105  public function locate(string $needle, string $string, int $start_pos = 1): string
1106  {
1107  return $this->manager->getQueryUtils()->locate($needle, $string, $start_pos);
1108  }
1109 
1113  public function modifyTableColumn(string $table, string $column, array $attributes): bool
1114  {
1115  $def = $this->reverse->getTableFieldDefinition($table, $column);
1116 
1117  $analyzer = new ilDBAnalyzer($this);
1118  $best_alt = $analyzer->getBestDefinitionAlternative($def);
1119  $def = $def[$best_alt];
1120  unset($def["nativetype"], $def["mdb2type"]);
1121 
1122  // check attributes
1123  $ilDBPdoFieldDefinition = $this->field_definition;
1124 
1125  $type = $attributes["type"] ?? $def["type"];
1126 
1127  foreach (array_keys($def) as $k) {
1128  if ($k !== "type" && !$ilDBPdoFieldDefinition->isAllowedAttribute($k, $type)) {
1129  unset($def[$k]);
1130  }
1131  }
1132  $check_array = $def;
1133  foreach ($attributes as $k => $v) {
1134  $check_array[$k] = $v;
1135  }
1136  if (!$this->checkColumnDefinition($check_array, true)) {
1137  throw new ilDatabaseException("ilDB Error: modifyTableColumn(" . $table . ", " . $column . ")");
1138  }
1139 
1140  foreach ($attributes as $a => $v) {
1141  $def[$a] = $v;
1142  }
1143 
1144  $attributes["definition"] = $def;
1145 
1146  $changes = array(
1147  "change" => array(
1148  $column => $attributes,
1149  ),
1150  );
1151 
1152  return $this->manager->alterTable($table, $changes, false);
1153  }
1154 
1155  public function free(ilDBStatement $a_st): void
1156  {
1157  $a_st->closeCursor();
1158  }
1159 
1163  public function renameTable(string $name, string $new_name): bool
1164  {
1165  // check table name
1166  try {
1167  $this->checkTableName($new_name);
1168  } catch (ilDatabaseException $e) {
1169  throw new ilDatabaseException(
1170  "ilDB Error: renameTable(" . $name . "," . $new_name . ")<br />" . $e->getMessage(),
1171  $e->getCode()
1172  );
1173  }
1174 
1175  $this->manager->alterTable($name, ["name" => $new_name], false);
1176  if ($this->sequenceExists($name)) {
1177  $this->manager->alterTable(
1178  $this->getSequenceName($name),
1179  ["name" => $this->getSequenceName($new_name)],
1180  false
1181  );
1182  }
1183  // The abstraction_progress is no longer used in ILIAS, see http://www.ilias.de/mantis/view.php?id=19513
1184  // $query = "UPDATE abstraction_progress " . "SET table_name = " . $this->quote($a_new_name, 'text') . " " . "WHERE table_name = "
1185  // . $this->quote($a_name, 'text');
1186  // $this->pdo->query($query);
1187 
1188  return true;
1189  }
1190 
1194  public function checkTableName(string $a_name): bool
1195  {
1196  return $this->field_definition->checkTableName($a_name);
1197  }
1198 
1199  public static function isReservedWord(string $a_word): bool
1200  {
1201  global $DIC;
1202  return (new ilDBPdoMySQLFieldDefinition($DIC->database()))->isReserved($a_word);
1203  }
1204 
1208  public function beginTransaction(): bool
1209  {
1210  if (!$this->supports(self::FEATURE_TRANSACTIONS)) {
1211  throw new ilDatabaseException("ilDB::beginTransaction: Transactions are not supported.");
1212  }
1213 
1214  return $this->pdo->beginTransaction();
1215  }
1216 
1220  public function commit(): bool
1221  {
1222  if (!$this->supports(self::FEATURE_TRANSACTIONS)) {
1223  throw new ilDatabaseException("ilDB::beginTransaction: Transactions are not supported.");
1224  }
1225 
1226  return $this->pdo->commit();
1227  }
1228 
1232  public function rollback(): bool
1233  {
1234  if (!$this->supports(self::FEATURE_TRANSACTIONS)) {
1235  throw new ilDatabaseException("ilDB::beginTransaction: Transactions are not supported.");
1236  }
1237 
1238  return $this->pdo->rollBack();
1239  }
1240 
1241  public function dropIndex(string $a_table, string $a_name = "i1"): bool
1242  {
1243  return $this->manager->dropIndex($a_table, $a_name);
1244  }
1245 
1246  public function setStorageEngine(string $storage_engine): void
1247  {
1248  $this->storage_engine = $storage_engine;
1249  }
1250 
1251  public function getStorageEngine(): string
1252  {
1253  return $this->storage_engine;
1254  }
1255 
1256  public function queryCol(string $query, int $type = PDO::FETCH_ASSOC, int $colnum = 0): array
1257  {
1258  switch ($type) {
1260  $type = PDO::FETCH_ASSOC;
1261  break;
1263  $type = PDO::FETCH_OBJ;
1264  break;
1265  default:
1266  $type = PDO::FETCH_ASSOC;
1267  break;
1268  }
1269 
1270  return $this->pdo->query($query, PDO::FETCH_ASSOC)->fetchAll(PDO::FETCH_COLUMN, $colnum);
1271  }
1272 
1273  public function queryRow(
1274  string $query,
1275  ?array $types = null,
1276  int $fetchmode = ilDBConstants::FETCHMODE_DEFAULT
1277  ): array {
1278  switch ($fetchmode) {
1280  $type = PDO::FETCH_ASSOC;
1281  break;
1283  $type = PDO::FETCH_OBJ;
1284  break;
1285  default:
1286  $type = PDO::FETCH_ASSOC;
1287  break;
1288  }
1289 
1290  return $this->pdo->query($query, $type)->fetch();
1291  }
1292 
1293  public function getServerVersion(bool $native = false): int
1294  {
1295  return $this->pdo->query('SELECT VERSION()')->fetchColumn();
1296  }
1297 
1298  public function escape(string $value, bool $escape_wildcards = false): string
1299  {
1300  return $value;
1301  }
1302 
1303  public function escapePattern(string $text): string
1304  {
1305  return $text;
1306  }
1307 
1308  public function migrateAllTablesToEngine(string $engine = ilDBConstants::MYSQL_ENGINE_INNODB): array
1309  {
1310  return array();
1311  }
1312 
1316  public function migrateAllTablesToCollation(string $collation = ilDBConstants::MYSQL_COLLATION_UTF8MB4): array
1317  {
1318  return array();
1319  }
1320 
1324  public function supportsCollationMigration(): bool
1325  {
1326  return false;
1327  }
1328 
1329  public function supportsEngineMigration(): bool
1330  {
1331  return false;
1332  }
1333 
1337  public function checkIndexName(string $name): bool
1338  {
1339  $fd = $this->getFieldDefinition();
1340  if ($fd !== null) {
1341  return $fd->checkIndexName($name);
1342  }
1343  return false;
1344  }
1345 
1349  public function addUniqueConstraint(string $table, array $fields, string $name = "con"): bool
1350  {
1351  assert(is_array($fields));
1352  $manager = $this->manager;
1353 
1354  // check index name
1355  if (!$this->checkIndexName($name)) {
1356  throw new ilDatabaseException("ilDB Error: addUniqueConstraint(" . $table . "," . $name . ")");
1357  }
1358 
1359  $fields_corrected = array();
1360  foreach ($fields as $f) {
1361  $fields_corrected[$f] = array();
1362  }
1363  $definition = array(
1364  'unique' => true,
1365  'fields' => $fields_corrected,
1366  );
1367 
1368  return $manager->createConstraint($table, $this->constraintName($table, $name), $definition);
1369  }
1370 
1371  public function dropUniqueConstraint(string $table, string $name = "con"): bool
1372  {
1373  return $this->manager->dropConstraint($table, $this->constraintName($table, $name), false);
1374  }
1375 
1376  public function dropUniqueConstraintByFields(string $table, array $fields): bool
1377  {
1378  $analyzer = new ilDBAnalyzer();
1379  $cons = $analyzer->getConstraintsInformation($table);
1380  foreach ($cons as $c) {
1381  if ($c["type"] === "unique" && count($fields) === count($c["fields"])) {
1382  $all_in = true;
1383  foreach ($fields as $f) {
1384  if (!isset($c["fields"][$f])) {
1385  $all_in = false;
1386  }
1387  }
1388  if ($all_in) {
1389  return $this->dropUniqueConstraint($table, $c['name']);
1390  }
1391  }
1392  }
1393 
1394  return false;
1395  }
1396 
1397  public function getLastInsertId(): int
1398  {
1399  return (int) $this->pdo->lastInsertId();
1400  }
1401 
1402  public function buildAtomQuery(): ilAtomQuery
1403  {
1404  return new ilAtomQueryLock($this);
1405  }
1406 
1407  public function uniqueConstraintExists(string $table, array $fields): bool
1408  {
1409  $analyzer = new ilDBAnalyzer();
1410  $cons = $analyzer->getConstraintsInformation($table);
1411  foreach ($cons as $c) {
1412  if ($c["type"] === "unique" && count($fields) === count($c["fields"])) {
1413  $all_in = true;
1414  foreach ($fields as $f) {
1415  if (!isset($c["fields"][$f])) {
1416  $all_in = false;
1417  }
1418  }
1419  if ($all_in) {
1420  return true;
1421  }
1422  }
1423  }
1424 
1425  return false;
1426  }
1427 
1428  public function dropPrimaryKey(string $table_name): bool
1429  {
1430  return $this->manager->dropConstraint($table_name, "PRIMARY", true);
1431  }
1432 
1433  public function executeMultiple(ilDBStatement $stmt, array $data): array
1434  {
1435  foreach ($data as $set) {
1436  $this->execute($stmt, $set);
1437  }
1438  return [];
1439  }
1440 
1441  public function fromUnixtime(string $expr, bool $to_text = true): string
1442  {
1443  return "FROM_UNIXTIME(" . $expr . ")";
1444  }
1445 
1446  public function unixTimestamp(): string
1447  {
1448  return "UNIX_TIMESTAMP()";
1449  }
1450 
1451 
1455  public function getDBVersion(): string
1456  {
1457  $d = $this->fetchObject($this->query("SELECT VERSION() AS version"));
1458 
1459  if ($d !== null && $d->version) {
1460  return $d->version;
1461  }
1462  return 'Unknown';
1463  }
1464 
1468  public function sanitizeMB4StringIfNotSupported(string $query): string
1469  {
1470  if (!$this->doesCollationSupportMB4Strings()) {
1471  $query_replaced = preg_replace(
1472  '/[\x{10000}-\x{10FFFF}]/u',
1474  $query
1475  );
1476  if (!empty($query_replaced)) {
1477  return $query_replaced;
1478  }
1479  }
1480 
1481  return $query;
1482  }
1483 
1487  public function doesCollationSupportMB4Strings(): bool
1488  {
1489  return false;
1490  }
1491 
1495  public function groupConcat(string $a_field_name, string $a_seperator = ",", ?string $a_order = null): string
1496  {
1497  return $this->manager->getQueryUtils()->groupConcat($a_field_name, $a_seperator, $a_order);
1498  }
1499 
1503  public function cast(string $a_field_name, string $a_dest_type): string
1504  {
1505  return $this->manager->getQueryUtils()->cast($a_field_name, $a_dest_type);
1506  }
1507 
1508  public function addForeignKey(
1509  string $foreign_key_name,
1510  array $field_names,
1511  string $table_name,
1512  array $reference_field_names,
1513  string $reference_table,
1514  ?ForeignKeyConstraints $on_update = null,
1515  ?ForeignKeyConstraints $on_delete = null
1516  ): bool {
1517  return $this->manager->addForeignKey($foreign_key_name, $field_names, $table_name, $reference_field_names, $reference_table, $on_update, $on_delete);
1518  }
1519 
1520  public function dropForeignKey(string $foreign_key_name, string $table_name): bool
1521  {
1522  return $this->manager->dropForeignKey($foreign_key_name, $table_name);
1523  }
1524 
1525  public function foreignKeyExists(string $foreign_key_name, string $table_name): bool
1526  {
1527  return $this->manager->foreignKeyExists($foreign_key_name, $table_name);
1528  }
1529 
1531  {
1532  return new Integrity($this);
1533  }
1534 
1535  public function primaryExistsByFields(string $table_name, array $fields): bool
1536  {
1537  $constraints = $this->manager->listTableConstraints($table_name);
1538 
1539  if (in_array('primary', $constraints)) {
1540  $definitions = $this->reverse->getTableConstraintDefinition($table_name, 'primary');
1541  $primary_fields = array_keys($definitions['fields']);
1542  sort($primary_fields);
1543  sort($fields);
1544 
1545  return $primary_fields === $fields;
1546  }
1547  return false;
1548  }
1549 }
setHost(string $host)
manipulateF(string $query, array $types, array $values)
groupConcat(string $a_field_name, string $a_seperator=",", ?string $a_order=null)
$res
Definition: ltiservices.php:69
supportsTransactions()
addTableColumn(string $table_name, string $column_name, array $attributes)
tableColumnExists(string $table_name, string $column_name)
migrateAllTablesToCollation(string $collation=ilDBConstants::MYSQL_COLLATION_UTF8MB4)
checkTableColumns(array $a_cols)
dropUniqueConstraint(string $table, string $name="con")
lower(string $expression)
string $dsn
string $storage_engine
static getReservedWords()
Get reserved words.
fetchObject(ilDBStatement $query_result)
doesCollationSupportMB4Strings()
addIndex(string $table_name, array $fields, string $index_name='', bool $fulltext=false)
free(ilDBStatement $a_st)
query(string $query)
string $host
createDatabase(string $a_name, string $a_charset="utf8", string $a_collation="")
Class ilPDOStatement is a Wrapper Class for PDOStatement.
setUsername(string $username)
setDBHost(string $host)
indexExistsByFields(string $table_name, array $fields)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
substr(string $a_exp, int $a_pos=1, int $a_len=-1)
uniqueConstraintExists(string $table, array $fields)
dropPrimaryKey(string $table_name)
fetchAll(ilDBStatement $statement, int $fetch_mode=ilDBConstants::FETCHMODE_ASSOC)
string $db_type
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
supportsCollationMigration()
dropIndex(string $a_table, string $a_name="i1")
setFieldDefinition(\ilDBPdoFieldDefinition $field_definition)
checkTableName(string $a_name)
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)
getIndexName(string $index_name_base)
escape(string $value, bool $escape_wildcards=false)
queryF(string $query, array $types, array $values)
getSequenceName(string $table_name)
const FEATURE_FULLTEXT
dropFulltextIndex(string $a_table, string $a_name)
Drop fulltext index.
$c
Definition: deliver.php:9
initHelpers()
appendLimit(string $query)
setPassword(string $password)
useSlave(bool $bool)
TODO.
migrateAllTablesToEngine(string $engine=ilDBConstants::MYSQL_ENGINE_INNODB)
sort()
description: > Example for rendering a Sort Glyph.
Definition: sort.php:25
foreignKeyExists(string $foreign_key_name, string $table_name)
checkColumnDefinition(array $a_def, bool $a_modify_mode=false)
addUniqueConstraint(string $table, array $fields, string $name="con")
getAdditionalAttributes()
Class pdoDB.
dropTable(string $table_name, bool $error_if_not_existing=true)
dropForeignKey(string $foreign_key_name, string $table_name)
string $password
execute(ilDBStatement $stmt, array $data=[])
manipulate(string $query)
supports(string $feature)
string $charset
dropSequence(string $table_name)
quote($value, ?string $type=null)
setDbname(string $dbname)
fetchAssoc(ilDBStatement $statement)
cast(string $a_field_name, string $a_dest_type)
fetch(int $fetch_mode=ilDBConstants::FETCHMODE_ASSOC)
array $attributes
const FEATURE_SLAVE
numRows(ilDBStatement $statement)
execute(array $a_data=null)
setStorageEngine(string $storage_engine)
lockTables(array $tables)
constraintName(string $a_table, string $a_constraint)
Determine contraint name by table name and constraint name.
getFieldDefinition()
checkColumn(string $a_col, array $a_def)
modifyTableColumn(string $table, string $column, array $attributes)
update(string $table_name, array $columns, array $where)
$where MUST contain existing columns only.
loadModule(string $module)
$text
Definition: xapiexit.php:21
createSequence(string $table_name, int $start=1)
dropIndexByFields(string $table_name, array $fields)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
dropUniqueConstraintByFields(string $table, array $fields)
upper(string $expression)
string $username
global $DIC
Definition: shib_login.php:25
like(string $column, string $type, string $value="?", bool $case_insensitive=true)
getLastErrorCode()
string $dbname
supportsEngineMigration()
addPrimaryKey(string $table_name, array $primary_keys)
setPort(int $port)
renameTable(string $name, string $new_name)
isFulltextIndex(string $a_table, string $a_name)
Is index a fulltext index?
ilDBPdoManager $manager
prepare(string $query, ?array $types=null, ?array $result_types=null)
ilDBPdoReverse $reverse
setCharset(string $charset)
setDBPassword(string $password)
tableExists(string $table_name)
in(string $field, array $values, bool $negate=false, string $type="")
renameTableColumn(string $table_name, string $column_old_name, string $column_new_name)
ilDBPdoFieldDefinition $field_definition
checkIndexName(string $name)
enableResultBuffering(bool $a_status)
buildIntegrityAnalyser()
getPrimaryKeyIdentifier()
setLimit(int $limit, int $offset=0)
Set the Limit for the next Query.
quoteIdentifier(string $identifier, bool $check_option=false)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
createConstraint(string $table, string $name, array $definition)
getAllowedAttributes()
string[]
escapePattern(string $text)
insert(string $table_name, array $values)
static isReservedWord(string $a_word)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
equals(string $columns, $value, string $type, bool $emptyOrNull=false)
const FEATURE_TRANSACTIONS
initFromIniFile(?ilIniFile $ini=null)
Class ilDBPdoManager.
setDBPort(int $port)
concat(array $values, bool $allow_null=true)
getServerVersion(bool $native=false)
$q
Definition: shib_logout.php:18
sanitizeMB4StringIfNotSupported(string $query)
string to sanitize, all MB4-Characters like emojis will re replaced with ??? string sanitized query ...
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples
executeMultiple(ilDBStatement $stmt, array $data)
getDSN()
Get DSN.
Class ilBenchmark.
primaryExistsByFields(string $table_name, array $fields)
prepareManip(string $query, ?array $types=null)
addFulltextIndex(string $table, array $fields, string $a_name="in")
checkColumnName(string $a_name)
int $error_code
This class gives all kind of DB information using the database manager and reverse module...
queryCol(string $query, int $type=PDO::FETCH_ASSOC, int $colnum=0)
createTable(string $table_name, array $fields, bool $drop_table=false, bool $ignore_erros=false)
setDBType(string $type)
setDBUser(string $user)
dropTableColumn(string $table_name, string $column_name)
connect(bool $return_false_for_error=false)
array $options
locate(string $needle, string $string, int $start_pos=1)
replace(string $table, array $primary_keys, array $other_columns)
Replace into method.
nextId(string $table_name)
$ini
Definition: raiseError.php:4
getDBType()
Get DSN.
fromUnixtime(string $expr, bool $to_text=true)
queryRow(string $query, ?array $types=null, int $fetchmode=ilDBConstants::FETCHMODE_DEFAULT)
$r
sequenceExists(string $sequence)