ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilDBGenerator.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
28class ilDBGenerator
29{
30 protected string $target_encoding = 'UTF-8';
31 protected array $whitelist = [];
32 protected array $blacklist = [];
33 protected array $tables = [];
34 protected array $filter = [];
35 protected ilDBManager $manager;
36 protected ilDBReverse $reverse;
37 protected ilDBInterface $il_db;
38 protected ilDBAnalyzer $analyzer;
39 protected array $allowed_attributes = [];
40 protected array $fields = [];
41
45 public function __construct()
46 {
47 global $DIC;
48 $ilDB = $DIC->database();
49
50 $this->manager = $ilDB->loadModule(ilDBConstants::MODULE_MANAGER);
51 $this->reverse = $ilDB->loadModule(ilDBConstants::MODULE_REVERSE);
52 $this->il_db = $ilDB;
53 $this->analyzer = new ilDBAnalyzer();
54 $this->allowed_attributes = $ilDB->getAllowedAttributes();
55 }
56
61 public static function lookupAbstractedTables(): array
62 {
63 global $DIC;
64 $ilDB = $DIC->database();
65
66 $query = "SELECT DISTINCT(table_name) FROM abstraction_progress ";
67 $res = $ilDB->query($query);
68 $names = [];
69 while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
70 $names[] = $row->table_name;
71 }
72
73 // tables that have been already created in an abstracted
74 // way or tables that have been renamed after being abstracted
75 // (see db_update script)
76 $abs_tables = array_merge($names, [
77 'acc_access_key',
78 'acc_user_access_key',
79 'ldap_rg_mapping',
80 'page_anchor',
81 'qpl_question_orderinghorizontal',
82 'qpl_question_fileupload',
83 'chat_smilies',
84 'style_color',
85 'style_template_class',
86 'style_template',
87 'page_style_usage',
88 'style_setting',
89 'page_editor_settings',
90 'mep_data',
91 'license_data',
92 'loginname_history',
93 'mep_item',
94 'qpl_a_cloze',
95 'qpl_a_imagemap',
96 'qpl_a_matching',
97 'qpl_num_range',
98 'qpl_qst_cloze',
99 'qpl_qst_essay',
100 'qpl_qst_fileupload',
101 'qpl_qst_flash',
102 'qpl_qst_horder',
103 'qpl_qst_imagemap',
104 'qpl_qst_javaapplet',
105 'qpl_qst_matching',
106 'qpl_qst_mc',
107 'qpl_qst_numeric',
108 'qpl_qst_ordering',
109 'qpl_qst_sc',
110 'qpl_qst_textsubset',
111 'qpl_qst_type',
112 'qpl_sol_sug',
113 'udf_text',
114 'udf_clob',
115 'xmlnestedsettmp',
116 'cache_text',
117 'cache_clob',
118 'qpl_a_errortext',
119 'qpl_qst_errortext',
120 'tst_rnd_cpy',
121 'tst_rnd_qpl_title',
122 'qpl_a_mdef',
123 ]);
124
125 return $abs_tables;
126 }
127
142 public function setTargetEncoding(string $a_encoding): void
143 {
144 $this->target_encoding = $a_encoding;
145 }
146
147 public function getTargetEncoding(): string
148 {
149 return $this->target_encoding;
150 }
151
157 public function setBlackList(array $a_blacklist): void
158 {
159 $this->blacklist = $a_blacklist;
160 }
161
162 public function getBlackList(): array
163 {
164 return $this->blacklist;
165 }
166
173 public function setWhiteList(array $a_whitelist): void
174 {
175 $this->whitelist = $a_whitelist;
176 }
177
178 public function getWhiteList(): array
179 {
180 return $this->whitelist;
181 }
182
183 public function setFilter(string $a_filter, string $a_value): void
184 {
185 $this->filter[$a_filter] = $a_value;
186 }
187
191 public function getTables(): array
192 {
193 return $this->tables = $this->manager->listTables();
194 }
195
199 public function checkProcessing(string $a_table): bool
200 {
201 // check black list
202 if (in_array($a_table, $this->blacklist, true)) {
203 return false;
204 }
205 // check white list
206 return !($this->whitelist !== [] && !in_array($a_table, $this->whitelist, true));
207 }
208
212 protected function openFile(string $a_path)
213 {
214 $start = '';
215 $file = fopen($a_path, 'wb');
216 $start .= ' global $ilDB;' . "\n\n";
217 fwrite($file, $start);
218
219 return $file;
220 }
221
225 protected function closeFile($fp): void
226 {
227 fclose($fp);
228 }
229
234 public function buildDBGenerationScript(string $a_filename = ""): void
235 {
236 if (@is_dir($a_filename)) {
237 $is_dir = true;
238 $path = $a_filename;
239 } else {
240 $is_dir = false;
241 $path = '';
242 }
243
244 $file = "";
245 if ($a_filename !== "" && !$is_dir) {
246 $file = fopen($a_filename, 'wb');
247
248 $start = '<?php' . "\n" . 'function setupILIASDatabase()' . "\n{\n";
249 $start .= ' global $ilDB;' . "\n\n";
250 fwrite($file, $start);
251 } else {
252 echo "<pre>";
253 }
254
255 foreach ($this->getTables() as $table) {
256 if ($this->checkProcessing($table)) {
257 if ($a_filename !== "") {
258 flush();
259 }
260
261 if ($is_dir) {
262 $file = $this->openFile($path . '/' . $table);
263 }
264
265 // create table statement
266 $this->buildCreateTableStatement($table, $file);
267
268 // primary key
269 $this->buildAddPrimaryKeyStatement($table, $file);
270
271 // indices
272 $this->buildAddIndexStatements($table, $file);
273
274 // constraints (currently unique keys)
275 $this->buildAddUniqueConstraintStatements($table, $file);
276
277 // auto increment sequence
278 $this->buildCreateSequenceStatement($table, $file);
279
280 if (in_array($table, ['usr_session_stats', 'usr_session_raw', 'il_plugin'])) {
281 continue;
282 }
283
284 // inserts
285 if ($is_dir) {
286 $this->buildInsertStatement($table, $path);
287 #$this->buildInsertStatementsXML($table,$path);
288 } else {
289 $this->buildInsertStatements($table, $file);
290 }
291
292 if ($is_dir) {
293 $this->closeFile($file);
294 }
295 } elseif ($a_filename !== "") {
296 echo "<br><b>missing: " . $table . "</b>";
297 flush();
298 }
299 }
300
301 // sequence(s) without table (of same name)
302 $this->buildSingularSequenceStatement($file);
303
304 if ($a_filename === "") {
305 echo "</pre>";
306 } elseif (!$is_dir) {
307 $end = "\n}\n?>\n";
308 $ok = fwrite($file, $end);
309 fclose($file);
310 }
311 }
312
318 public function buildCreateTableStatement(string $a_table, $a_file = null): void
319 {
320 $fields = $this->analyzer->getFieldInformation($a_table, true);
321 $this->fields = $fields;
322 $create_st = "\n\n//\n// " . $a_table . "\n//\n";
323 $create_st .= '$fields = array (' . "\n";
324 $f_sep = "";
325 foreach ($fields as $f => $def) {
326 $create_st .= "\t" . $f_sep . '"' . $f . '" => array (' . "\n";
327 $f_sep = ",";
328 $a_sep = "";
329 foreach ($def as $k => $v) {
330 if ($k !== "nativetype" && $k !== "alt_types" && $k !== "autoincrement" && !is_null($v)) {
331 switch ($k) {
332 case "notnull":
333 case "unsigned":
334 case "fixed":
335 $v = $v ? "true" : "false";
336 break;
337
338 case "default":
339 case "type":
340 $v = '"' . $v . '"';
341 break;
342
343 default:
344 break;
345 }
346 $create_st .= "\t\t" . $a_sep . '"' . $k . '" => ' . $v . "\n";
347 $a_sep = ",";
348 }
349 }
350 $create_st .= ' )' . "\n";
351 }
352 $create_st .= ');' . "\n";
353 $create_st .= '$ilDB->createTable("' . $a_table . '", $fields);' . "\n";
354
355 if ($a_file === null) {
356 echo $create_st;
357 } else {
358 fwrite($a_file, $create_st);
359 }
360 }
361
365 public function buildAddPrimaryKeyStatement(string $a_table, $a_file = null): void
366 {
367 $pk = $this->analyzer->getPrimaryKeyInformation($a_table);
368
369 if (isset($pk["fields"]) && is_array($pk["fields"]) && $pk["fields"] !== []) {
370 $pk_st = "\n" . '$pk_fields = array(';
371 $sep = "";
372 foreach (array_keys($pk["fields"]) as $f) {
373 $pk_st .= $sep . '"' . $f . '"';
374 $sep = ",";
375 }
376 $pk_st .= ");\n";
377 $pk_st .= '$ilDB->addPrimaryKey("' . $a_table . '", $pk_fields);' . "\n";
378
379 if ($a_file === null) {
380 echo $pk_st;
381 } else {
382 fwrite($a_file, $pk_st);
383 }
384 }
385 }
386
390 public function buildAddIndexStatements(string $a_table, $a_file = null): void
391 {
392 $ind = $this->analyzer->getIndicesInformation($a_table, true);
393
394 if (is_array($ind)) {
395 foreach ($ind as $i) {
396 $ft = $i["fulltext"] ? ", true" : ", false";
397 $in_st = "\n" . '$in_fields = array(';
398 $sep = "";
399 foreach ($i["fields"] as $f => $pos) {
400 $in_st .= $sep . '"' . $f . '"';
401 $sep = ",";
402 }
403 $in_st .= ");\n";
404 $in_st .= '$ilDB->addIndex("' . $a_table . '", $in_fields, "' . $i["name"] . '"' . $ft . ');' . "\n";
405
406 if ($a_file === null) {
407 echo $in_st;
408 } else {
409 fwrite($a_file, $in_st);
410 }
411 }
412 }
413 }
414
418 private function printOrWrite(string $string, $file_handle = null): void
419 {
420 if ($file_handle === null) {
421 echo $string;
422 } else {
423 fwrite($file_handle, $string);
424 }
425 }
426
430 public function buildAddUniqueConstraintStatements(string $a_table, $file_handle = null): void
431 {
432 $con = $this->analyzer->getConstraintsInformation($a_table, true);
433
434 if (is_array($con)) {
435 $in_st = '';
436 foreach ($con as $i) {
437 $in_st = "\n" . '$in_fields = array(';
438 $sep = "";
439 foreach ($i["fields"] as $f => $pos) {
440 $in_st .= $sep . '"' . $f . '"';
441 $sep = ",";
442 }
443 $in_st .= ");\n";
444 $in_st .= '$ilDB->addUniqueConstraint("' . $a_table . '", $in_fields, "' . $i["name"] . '");' . "\n";
445 }
446 $this->printOrWrite($in_st, $file_handle);
447 }
448 }
449
454 public function buildCreateSequenceStatement(string $a_table, $file_handle = null): void
455 {
456 $seq = $this->analyzer->hasSequence($a_table);
457 if ($seq !== false) {
458 $seq_st = "\n" . '$ilDB->createSequence("' . $a_table . '", ' . (int) $seq . ');' . "\n";
459
460 $this->printOrWrite($seq_st, $file_handle);
461 }
462 }
463
467 public function buildSingularSequenceStatement($file_handle = null): void
468 {
469 $r = $this->manager->listSequences();
470
471 foreach ($r as $seq) {
472 if (!in_array($seq, $this->tables, true)) {
473 // 12570
474 if ($seq === "sahs_sc13_seq") {
475 continue;
476 }
477
478 $create_st = "\n" . '$ilDB->createSequence("' . $seq . '");' . "\n";
479
480 $this->printOrWrite($create_st, $file_handle);
481 }
482 }
483 }
484
488 public function buildInsertStatement(string $a_table, string $a_basedir): bool
489 {
490 global $DIC;
491 $ilLogger = $DIC->logger()->root();
492
493 $ilLogger->log('Starting export of:' . $a_table);
494
495 $set = $this->il_db->query("SELECT * FROM " . $this->il_db->quoteIdentifier($a_table));
496 $row = 0;
497
498 umask(0000);
499 if (!mkdir(
500 $concurrentDirectory = $a_basedir . '/' . $a_table . '_inserts',
501 fileperms($a_basedir)
502 ) && !is_dir($concurrentDirectory)) {
503 throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
504 }
505
506 $filenum = 1;
507 while ($rec = $this->il_db->fetchAssoc($set)) {
508 $values = [];
509 foreach ($rec as $f => $v) {
510 if ($this->fields[$f]['type'] === 'text' && $this->fields[$f]['length'] >= 1000) {
511 $v = $this->shortenText($a_table, $f, $v, $this->fields[$f]['length']);
512 }
513
514 $values[$f] = [
515 $this->fields[$f]['type'],
516 $v,
517 ];
518 }
519
520 $rows[$a_table][$row++] = $values;
521
522 if ($row >= 1000) {
523 $ilLogger->log('Writing insert statements after 1000 lines...');
524 $fp = fopen($a_basedir . '/' . $a_table . '_inserts/' . $filenum++ . '.data', 'wb');
525 fwrite($fp, serialize($rows));
526 fclose($fp);
527
528 $row = 0;
529 unset($rows);
530 }
531 }
532 if (isset($rows)) {
533 $fp = fopen($a_basedir . '/' . $a_table . '_inserts/' . $filenum++ . '.data', 'wb');
534 fwrite($fp, serialize($rows) . "\n");
535 fclose($fp);
536 }
537
538 $ilLogger->log('Finished export of: ' . $a_table);
539 if (function_exists('memory_get_usage')) {
540 $ilLogger->log('Memory usage: ' . memory_get_usage(true));
541 }
542
543 return true;
544 }
545
549 public function buildInsertStatements(string $a_table, $file_handle = null): void
550 {
551 if ($a_table === "lng_data") {
552 return;
553 }
554
555 $set = $this->il_db->query("SELECT * FROM " . $this->il_db->quoteIdentifier($a_table));
556 $ins_st = "";
557 while ($rec = $this->il_db->fetchAssoc($set)) {
558 $fields = [];
559 $types = [];
560 $values = [];
561 $i_str = [];
562 foreach ($rec as $f => $v) {
563 $v = str_replace('\\', '\\\\', $v);
564 $i_str[] = "'" . $f . "' => array('" . $this->fields[$f]["type"] . "', '" . str_replace(
565 "'",
566 "\'",
567 $v
568 ) . "')";
569 }
570 $ins_st = "\n" . '$ilDB->insert("' . $a_table . '", array(' . "\n";
571 $ins_st .= implode(", ", $i_str) . "));\n";
572
573 $this->printOrWrite($ins_st, $file_handle);
574 $ins_st = "";
575 }
576 }
577
581 protected function shortenText(string $table, string $field, string $a_value, int $a_size): string
582 {
583 global $DIC;
584 $ilLogger = $DIC->logger()->root();
585
586 if ($this->getTargetEncoding() === 'UTF-8') {
587 return $a_value;
588 }
589 // Convert to target encoding
590 $shortened = mb_convert_encoding($a_value, $this->getTargetEncoding(), 'UTF-8');
591 // Shorten
592 $shortened = ilStr::shortenText($shortened, 0, $a_size, $this->getTargetEncoding());
593 // Convert back to UTF-8
594 $shortened = mb_convert_encoding($shortened, 'UTF-8', $this->getTargetEncoding());
595
596 if (strlen($a_value) !== strlen($shortened)) {
597 $ilLogger->log('Table : ' . $table);
598 $ilLogger->log('Field : ' . $field);
599 $ilLogger->log('Type : ' . $this->fields[$field]['type']);
600 $ilLogger->log('Length : ' . $this->fields[$field]['length']);
601 $ilLogger->log('Before : ' . $a_value);
602 $ilLogger->log('Shortened : ' . $shortened);
603 $ilLogger->log('Strlen Before: ' . strlen($a_value));
604 $ilLogger->log('Strlen After : ' . strlen($shortened));
605 }
606
607 return $shortened;
608 }
609}
This class gives all kind of DB information using the database manager and reverse module.
static shortenText(string $a_string, int $a_start_pos, int $a_num_bytes, string $a_encoding='UTF-8')
Shorten text to the given number of bytes.
Definition: class.ilStr.php:99
Interface ilDBInterface.
Interface ilDBManager.
Interface ilDBReverse.
$path
Definition: ltiservices.php:30
$res
Definition: ltiservices.php:69
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
filter(string $filter_id, $class_path, string $cmd, bool $activated=true, bool $expanded=true)
global $DIC
Definition: shib_login.php:26