FOSSology  4.5.1
Open Source License Compliance by Open Source Software
libschema.php
Go to the documentation of this file.
1 <?php
2 /*
3  SPDX-FileCopyrightText: © 2008-2014 Hewlett-Packard Development Company, L.P.
4  SPDX-FileCopyrightText: © 2014-2015, 2018 Siemens AG
5 
6  SPDX-License-Identifier: LGPL-2.1-only
7 */
8 
15 require_once(__DIR__ . '/../../vendor/autoload.php');
16 
21 use Monolog\Handler\ErrorLogHandler;
22 use Monolog\Logger;
23 
29 {
34  public $debug = false;
35 
40  private $dbman;
41 
46  private $schema = array();
47 
52  private $currSchema = array();
53 
58  function __construct(DbManager &$dbManager)
59  {
60  $this->dbman = $dbManager;
61  }
62 
67  function setDriver(Driver &$dbDriver)
68  {
69  $this->dbman->setDriver($dbDriver);
70  }
71 
72 
78  function applyOrEchoOnce($sql, $stmt = '')
79  {
80  if ($this->debug) {
81  print ("$sql\n");
82  } else {
83  $this->dbman->queryOnce($sql, $stmt);
84  }
85  }
86 
87 
96  function applySchema($filename = NULL, $debug = false, $catalog = 'fossology', $migrateColumns = array())
97  {
98  global $PG_CONN;
99 
100  // first check to make sure we don't already have the plpgsql language installed
101  $sql_statement = "select lanname from pg_language where lanname = 'plpgsql'";
102 
103  $result = pg_query($PG_CONN, $sql_statement);
104  if (!$result) {
105  throw new Exception("Could not check the database for plpgsql language");
106  }
107 
108  $plpgsql_already_installed = false;
109  if ( pg_fetch_row($result) ) {
110  $plpgsql_already_installed = true;
111  }
112 
113  // then create language plpgsql if not already created
114  if ($plpgsql_already_installed == false) {
115  $sql_statement = "CREATE LANGUAGE plpgsql";
116  $result = pg_query($PG_CONN, $sql_statement);
117  if (!$result) {
118  throw new Exception("Could not create plpgsql language in the database");
119  }
120  }
121 
122  $sql_statement = "select extname from pg_extension where extname = 'uuid-ossp'";
123 
124  $result = pg_query($PG_CONN, $sql_statement);
125  if (!$result) {
126  throw new Exception("Could not check the database for uuid-ossp extension");
127  }
128 
129  $uuid_already_installed = false;
130  if ( pg_fetch_row($result) ) {
131  $uuid_already_installed = true;
132  }
133 
134  // then create extension uuid-ossp if not already created
135  if ( $uuid_already_installed == false ) {
136  $sql_statement = 'CREATE EXTENSION "uuid-ossp";';
137  $result = pg_query($PG_CONN, $sql_statement);
138  if (!$result) {
139  throw new Exception("Could not create uuid-ossp extension in the database");
140  }
141  }
142 
143  $this->debug = $debug;
144  if (!file_exists($filename)) {
145  return "$filename does not exist.";
146  }
147  $Schema = array(); /* will be filled in next line */
148  require($filename); /* this cause Fatal Error if the file does not exist. */
149  $this->schema = $Schema;
150 
151  /* Very basic sanity check (so we don't delete everything!) */
152  if ((count($this->schema['TABLE']) < 5) || (count($this->schema['SEQUENCE']) < 5)
153  || (count($this->schema['INDEX']) < 5) || (count($this->schema['CONSTRAINT']) < 5)
154  ) {
155  return "Schema from '$filename' appears invalid.";
156  }
157 
158  if (!$debug) {
159  $result = $this->dbman->getSingleRow("show statement_timeout", array(), $stmt = __METHOD__ . '.getTimeout');
160  $statementTimeout = $result['statement_timeout'];
161  $this->dbman->queryOnce("SET statement_timeout = 0", $stmt = __METHOD__ . '.setTimeout');
162  }
163 
164  $this->applyOrEchoOnce('BEGIN');
165  $this->getCurrSchema();
166  $errlev = error_reporting(E_ERROR | E_WARNING | E_PARSE);
167  $this->dropViews($catalog);
168  $this->dropConstraints();
169  $this->applySequences();
170  $this->applyTables();
171  $this->applyInheritedRelations();
172  $this->getCurrSchema(); /* New tables created, recheck */
173  $this->applyTables(true);
174  $this->updateSequences();
175  $this->applyViews();
176  $this->dropConstraints();
177  /* Reload current since the CASCADE may have changed things */
178  $this->getCurrSchema(); /* constraints and indexes are linked, recheck */
179  $this->dropIndexes();
180  $this->applyIndexes();
181  $this->applyConstraints();
182  error_reporting($errlev); /* return to previous error reporting level */
183  $this->makeFunctions();
184  $this->applyClusters();
185  /* Reload current since CASCADE during migration may have changed things */
186  $this->getCurrSchema();
187  $this->dropViews($catalog);
188  foreach ($this->currSchema['TABLE'] as $table => $columns) {
189  $skipColumns = array_key_exists($table, $migrateColumns) ? $migrateColumns[$table] : array();
190  $dropColumns = array_diff(array_keys($columns), $skipColumns);
191  $this->dropColumnsFromTable($dropColumns, $table);
192  }
193  $this->applyOrEchoOnce('COMMIT');
194  flush();
196  if (!$debug) {
197  $this->dbman->getSingleRow("SET statement_timeout = $statementTimeout", array(), $stmt = __METHOD__ . '.resetTimeout');
198  print "DB schema has been updated for $catalog.\n";
199  } else {
200  print "These queries could update DB schema for $catalog.\n";
201  }
202  return false;
203  }
204 
211  function applySequences()
212  {
213  if (empty($this->schema['SEQUENCE'])) {
214  return;
215  }
216  foreach ($this->schema['SEQUENCE'] as $name => $import) {
217  if (empty($name)) {
218  continue;
219  }
220 
221  if (!array_key_exists('SEQUENCE', $this->currSchema)
222  || !array_key_exists($name, $this->currSchema['SEQUENCE'])) {
223  $createSql = is_string($import) ? $import : $import['CREATE'];
224  $this->applyOrEchoOnce($createSql, $stmt = __METHOD__ . "." . $name . ".CREATE");
225  }
226  }
227  }
234  function applyClusters()
235  {
236  if (empty($this->schema['CLUSTER'])) {
237  return;
238  }
239  foreach ($this->schema['CLUSTER'] as $name => $sql) {
240  if (empty($name)) {
241  continue;
242  }
243 
244  if (!array_key_exists('CLUSTER', $this->currSchema)
245  || !array_key_exists($name, $this->currSchema['CLUSTER'])) {
246  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . "." . $name . ".CREATE");
247  }
248  }
249  }
250 
258  function updateSequences()
259  {
260  if (empty($this->schema['SEQUENCE']) ||
261  !(array_key_exists('SEQUENCE', $this->currSchema))) {
262  return;
263  }
264  foreach ($this->schema['SEQUENCE'] as $name => $import) {
265  if (empty($name)) {
266  continue;
267  }
268 
269  if (is_array($import) && array_key_exists('UPDATE', $import)) {
270  $this->applyOrEchoOnce($import['UPDATE'], $stmt = __METHOD__ . "." . $name);
271  }
272  }
273  }
274 
281  function applyTables($inherits=false)
282  {
283  if (empty($this->schema['TABLE'])) {
284  return;
285  }
286  foreach ($this->schema['TABLE'] as $table => $columns) {
287  if (empty($table) || $inherits^array_key_exists($table,$this->schema['INHERITS']) ) {
288  continue;
289  }
290  $newTable = false;
291  if (!DB_TableExists($table)) {
292  $sql = "CREATE TABLE IF NOT EXISTS \"$table\" ()";
293  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . $table);
294  $newTable = true;
295  } elseif (!array_key_exists($table, $this->currSchema['TABLE'])) {
296  $newTable = true;
297  }
298  foreach ($columns as $column => $modification) {
299  if (!$newTable && !array_key_exists($column, $this->currSchema['TABLE'][$table])) {
300  $colNewTable = true;
301  } else {
302  $colNewTable = $newTable;
303  }
304  if ($colNewTable ||
305  $this->currSchema['TABLE'][$table][$column]['ADD'] != $modification['ADD']) {
306  $rename = "";
307  if (DB_ColExists($table, $column)) {
308  /* The column exists, but it looks different!
309  Solution: Delete the column! */
310  $rename = $column . '_old';
311  $sql = "ALTER TABLE \"$table\" RENAME COLUMN \"$column\" TO \"$rename\"";
312  $this->applyOrEchoOnce($sql);
313  }
314 
315  $sql = $modification['ADD'];
316  if ($this->debug) {
317  print "$sql\n";
318  } else {
319  // Add the new column which sets the default value
320  $this->dbman->queryOnce($sql);
321  }
322  if (!empty($rename)) {
323  /* copy over the old data */
324  $this->applyOrEchoOnce($sql = "UPDATE \"$table\" SET \"$column\" = \"$rename\"");
325  $this->applyOrEchoOnce($sql = "ALTER TABLE \"$table\" DROP COLUMN \"$rename\"");
326  }
327  }
328  if ($colNewTable ||
329  $this->currSchema['TABLE'][$table][$column]['ALTER'] != $modification['ALTER'] && isset($modification['ALTER'])) {
330  $sql = $modification['ALTER'];
331  if ($this->debug) {
332  print "$sql\n";
333  } else if (!empty ($sql)) {
334  $this->dbman->queryOnce($sql);
335  }
336  }
337  if ($colNewTable ||
338  $this->currSchema['TABLE'][$table][$column]['DESC'] != $modification['DESC']) {
339  $sql = empty($modification['DESC']) ? "COMMENT ON COLUMN \"$table\".\"$column\" IS ''" : $modification['DESC'];
340  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . "$table.$column.comment");
341  }
342  }
343  }
344  }
345 
349  function applyViews()
350  {
351  if (empty($this->schema['VIEW'])) {
352  return;
353  }
354  $newViews = !array_key_exists('VIEW', $this->currSchema);
355  foreach ($this->schema['VIEW'] as $name => $sql) {
356  if (empty($name) || (!$newViews &&
357  $this->currSchema['VIEW'][$name] == $sql)) {
358  continue;
359  }
360  if (!$newViews && !empty($this->currSchema['VIEW'][$name])) {
361  $sqlDropView = "DROP VIEW IF EXISTS $name";
362  $this->applyOrEchoOnce($sqlDropView);
363  }
364  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . $name);
365  }
366  }
367 
373  function dropConstraints()
374  {
375  if (!array_key_exists('CONSTRAINT', $this->currSchema) || // Empty DB
376  empty($this->currSchema['CONSTRAINT'])) {
377  return;
378  }
379  foreach ($this->currSchema['CONSTRAINT'] as $name => $sql) {
380  // skip if constraint name is empty or does not exist
381  if (empty($name) || !array_key_exists($name, $this->schema['CONSTRAINT'])
382  || ($this->schema['CONSTRAINT'][$name] == $sql)
383  || !DB_ConstraintExists($name)) {
384  continue;
385  }
386 
387  /* Only process tables that I know about */
388  $table = preg_replace("/^ALTER TABLE \"(.*)\" ADD CONSTRAINT.*/", '${1}', $sql);
389  $TableFk = preg_replace("/^.*FOREIGN KEY .* REFERENCES \"(.*)\" \‍(.*/", '${1}', $sql);
390  if ($TableFk == $sql) {
391  $TableFk = $table;
392  }
393  /* If I don't know the primary or foreign table... */
394  if (empty($this->schema['TABLE'][$table]) && empty($this->schema['TABLE'][$TableFk])) {
395  continue;
396  }
397  $sql = "ALTER TABLE \"$table\" DROP CONSTRAINT \"$name\" CASCADE";
398  $this->applyOrEchoOnce($sql);
399  }
400  }
401 
405  function dropIndexes()
406  {
407  if (!array_key_exists('INDEX', $this->currSchema) ||
408  empty($this->currSchema['INDEX'])) {
409  return;
410  }
411  foreach ($this->currSchema['INDEX'] as $table => $IndexInfo) {
412  if (empty($table) || (empty($this->schema['TABLE'][$table]) && empty($this->schema['INHERITS'][$table]))) {
413  continue;
414  }
415  foreach ($IndexInfo as $name => $sql) {
416  if (empty($name) || $this->schema['INDEX'][$table][$name] == $sql) {
417  continue;
418  }
419  $sql = "DROP INDEX \"$name\"";
420  $this->applyOrEchoOnce($sql);
421  }
422  }
423  }
424 
428  function applyIndexes()
429  {
430  if (empty($this->schema['INDEX'])) {
431  return;
432  }
433  foreach ($this->schema['INDEX'] as $table => $indexInfo) {
434  if (empty($table)) {
435  continue;
436  }
437  if (!array_key_exists($table, $this->schema["TABLE"]) && !array_key_exists($table, $this->schema['INHERITS'])) {
438  echo "skipping orphan table: $table\n";
439  continue;
440  }
441  $newIndexes = false;
442  if (!array_key_exists('INDEX', $this->currSchema) ||
443  !array_key_exists($table, $this->currSchema['INDEX'])) {
444  $newIndexes = true;
445  }
446  foreach ($indexInfo as $name => $sql) {
447  if (empty($name) || (!$newIndexes &&
448  array_key_exists($name, $this->currSchema['INDEX'][$table]) &&
449  $this->currSchema['INDEX'][$table][$name] == $sql)) {
450  continue;
451  }
452  $this->applyOrEchoOnce($sql);
453  $sql = "REINDEX INDEX \"$name\"";
454  $this->applyOrEchoOnce($sql);
455  }
456  }
457  }
458 
462  function applyConstraints()
463  {
464  $this->currSchema = $this->getCurrSchema(); /* constraints and indexes are linked, recheck */
465  if (empty($this->schema['CONSTRAINT'])) {
466  return;
467  }
468  /* Constraints must be added in the correct order! */
469  $orderedConstraints = array('primary' => array(), 'unique' => array(), 'foreign' => array(), 'other' => array());
470  foreach ($this->schema['CONSTRAINT'] as $Name => $sql) {
471  $newConstraint = false;
472  if (!array_key_exists('CONSTRAINT', $this->currSchema) ||
473  !array_key_exists($Name, $this->currSchema['CONSTRAINT'])) {
474  $newConstraint = true;
475  }
476  if (empty($Name) || (!$newConstraint &&
477  $this->currSchema['CONSTRAINT'][$Name] == $sql)) {
478  continue;
479  }
480  if (preg_match("/PRIMARY KEY/", $sql)) {
481  $orderedConstraints['primary'][] = $sql;
482  } elseif (preg_match("/UNIQUE/", $sql)) {
483  $orderedConstraints['unique'][] = $sql;
484  } elseif (preg_match("/FOREIGN KEY/", $sql)) {
485  $orderedConstraints['foreign'][] = $sql;
486  } else {
487  $orderedConstraints['other'][] = $sql;
488  }
489  }
490  foreach ($orderedConstraints as $type => $constraints) {
491  foreach ($constraints as $sql) {
492  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . ".constraint.$type");
493  }
494  }
495  }
496 
506  function dropViews($catalog)
507  {
508  $sql = "SELECT view_name,vcs.table_name,column_name
509  FROM information_schema.view_column_usage AS vcs
510  INNER JOIN information_schema.views AS v
511  ON vcs.view_name = v.table_name
512  WHERE vcs.table_catalog='$catalog'
513  AND v.table_schema = 'public'
514  ORDER BY view_name,vcs.table_name,column_name;";
515  $stmt = __METHOD__;
516  $this->dbman->prepare($stmt, $sql);
517  $result = $this->dbman->execute($stmt);
518  while ($row = $this->dbman->fetchArray($result)) {
519  $View = $row['view_name'];
520  $table = $row['table_name'];
521  $column = $row['column_name'];
522  if (empty($this->schema['TABLE'][$table]) || empty($this->schema['TABLE'][$table][$column])) {
523  $sql = "DROP VIEW IF EXISTS \"$View\";";
524  $this->applyOrEchoOnce($sql);
525  }
526  }
527  $result = $this->dbman->freeResult($result);
528  }
529 
535  function dropColumnsFromTable($columns, $table)
536  {
537  if (empty($table) || empty($this->schema['TABLE'][$table])) {
538  return;
539  }
540  foreach ($columns as $column) {
541  if (empty($column)) {
542  continue;
543  }
544  if (empty($this->schema['TABLE'][$table][$column])) {
545  $sql = "ALTER TABLE \"$table\" DROP COLUMN \"$column\";";
546  $this->applyOrEchoOnce($sql);
547  }
548  }
549  }
550 
551 
555  function getCurrSchema()
556  {
557  global $SysConf;
558  $this->currSchema = array();
559  $this->addInheritedRelations();
560  $referencedSequencesInTableColumns = $this->addTables();
561  $this->addViews($viewowner = $SysConf['DBCONF']['user']);
562  $this->addSequences($referencedSequencesInTableColumns);
563  $this->addConstraints();
564  $this->addIndexes();
565  unset($this->currSchema['TABLEID']);
566  return $this->currSchema;
567  }
568 
573  {
574  $sql = "SELECT class.relname AS \"table\", daddy.relname AS inherits_from
575  FROM pg_class AS class
576  INNER JOIN pg_catalog.pg_inherits ON pg_inherits.inhrelid = class.oid
577  INNER JOIN pg_class daddy ON pg_inherits.inhparent = daddy.oid";
578  $this->dbman->prepare($stmt=__METHOD__, $sql);
579  $res = $this->dbman->execute($stmt);
580  $relations = array();
581  while ($row=$this->dbman->fetchArray($res)) {
582  $relations[$row['table']] = $row['inherits_from'];
583  }
584  $this->dbman->freeResult($res);
585  $this->currSchema['INHERITS'] = $relations;
586  }
587 
591  function addTables()
592  {
593  $referencedSequencesInTableColumns = array();
594 
595  $sql = "SELECT
596  table_name AS \"table\", ordinal_position AS ordinal, column_name,
597  udt_name AS type, character_maximum_length AS modifier,
598  CASE is_nullable WHEN 'YES' THEN false WHEN 'NO' THEN true END AS \"notnull\",
599  column_default AS \"default\",
600  col_description(table_name::regclass, ordinal_position) AS description
601  FROM information_schema.columns
602  WHERE table_schema = 'public'
603  ORDER BY table_name, ordinal_position;";
604  $stmt = __METHOD__;
605  $this->dbman->prepare($stmt, $sql);
606  $result = $this->dbman->execute($stmt);
607  while ($R = $this->dbman->fetchArray($result)) {
608  $Table = $R['table'];
609  $Column = $R['column_name'];
610  if (array_key_exists($Table, $this->currSchema['INHERITS'])) {
611  $this->currSchema['TABLEID'][$Table][$R['ordinal']] = $Column;
612  continue;
613  }
614  $Type = $R['type'];
615  if ($Type == 'bpchar') {
616  $Type = "char";
617  }
618  if ($R['modifier'] > 0) {
619  $Type .= '(' . $R['modifier'] . ')';
620  }
621  if (!empty($R['description'])) {
622  $Desc = str_replace("'", "''", $R['description']);
623  } else {
624  $Desc = "";
625  }
626  $this->currSchema['TABLEID'][$Table][$R['ordinal']] = $Column;
627  if (!empty($Desc)) {
628  $this->currSchema['TABLE'][$Table][$Column]['DESC'] = "COMMENT ON COLUMN \"$Table\".\"$Column\" IS '$Desc'";
629  } else {
630  $this->currSchema['TABLE'][$Table][$Column]['DESC'] = "";
631  }
632  $this->currSchema['TABLE'][$Table][$Column]['ADD'] = "ALTER TABLE \"$Table\" ADD COLUMN \"$Column\" $Type";
633  $this->currSchema['TABLE'][$Table][$Column]['ALTER'] = "ALTER TABLE \"$Table\"";
634  $Alter = "ALTER COLUMN \"$Column\"";
635  if ($R['notnull'] == 't') {
636  $this->currSchema['TABLE'][$Table][$Column]['ALTER'] .= " $Alter SET NOT NULL";
637  } else {
638  $this->currSchema['TABLE'][$Table][$Column]['ALTER'] .= " $Alter DROP NOT NULL";
639  }
640  if ($R['default'] != '') {
641  $R['default'] = preg_replace("/::bpchar/", "::char", $R['default']);
642  $R['default'] = str_replace("public.", "", $R['default']);
643  $this->currSchema['TABLE'][$Table][$Column]['ALTER'] .= ", $Alter SET DEFAULT " . $R['default'];
644  $this->currSchema['TABLE'][$Table][$Column]['ADD'] .= " DEFAULT " . $R['default'];
645 
646  $rgx = "/nextval\‍('([a-z_]*)'.*\‍)/";
647  $matches = array();
648  if (preg_match($rgx, $R['default'], $matches)) {
649  $sequence = $matches[1];
650  $referencedSequencesInTableColumns[$sequence] = array("table" => $Table, "column" => $Column);
651  }
652  }
653  }
654  $this->dbman->freeResult($result);
655 
656  return $referencedSequencesInTableColumns;
657  }
658 
663  function addViews($viewowner)
664  {
665  $sql = "SELECT viewname,definition FROM pg_views WHERE viewowner = $1";
666  $stmt = __METHOD__;
667  $this->dbman->prepare($stmt, $sql);
668  $result = $this->dbman->execute($stmt, array($viewowner));
669  while ($row = $this->dbman->fetchArray($result)) {
670  $sql = "CREATE VIEW \"" . $row['viewname'] . "\" AS " . $row['definition'];
671  $this->currSchema['VIEW'][$row['viewname']] = $sql;
672  }
673  $this->dbman->freeResult($result);
674  }
675 
680  function addSequences($referencedSequencesInTableColumns)
681  {
682  $sql = "SELECT relname
683  FROM pg_class
684  WHERE relkind = 'S'
685  AND relnamespace IN (
686  SELECT oid FROM pg_namespace WHERE nspname NOT LIKE 'pg_%' AND nspname != 'information_schema'
687  )";
688 
689  $stmt = __METHOD__;
690  $this->dbman->prepare($stmt, $sql);
691  $result = $this->dbman->execute($stmt);
692 
693  while ($row = $this->dbman->fetchArray($result)) {
694  $sequence = $row['relname'];
695  if (empty($sequence)) {
696  continue;
697  }
698 
699  $sqlCreate = "CREATE SEQUENCE \"" . $sequence . "\"";
700  $this->currSchema['SEQUENCE'][$sequence]['CREATE'] = $sqlCreate;
701 
702  if (array_key_exists($sequence, $referencedSequencesInTableColumns)) {
703  $table = $referencedSequencesInTableColumns[$sequence]['table'];
704  $column = $referencedSequencesInTableColumns[$sequence]['column'];
705 
706  $sqlUpdate = "SELECT setval('$sequence',(SELECT greatest(1,max($column)) val FROM $table))";
707  $this->currSchema['SEQUENCE'][$sequence]['UPDATE'] = $sqlUpdate;
708  }
709  }
710 
711  $this->dbman->freeResult($result);
712  }
713 
717  function addConstraints()
718  {
719  $sql = "SELECT c.conname AS constraint_name,
720  CASE c.contype
721  WHEN 'c' THEN 'CHECK'
722  WHEN 'f' THEN 'FOREIGN KEY'
723  WHEN 'p' THEN 'PRIMARY KEY'
724  WHEN 'u' THEN 'UNIQUE'
725  END AS type,
726  CASE WHEN c.condeferrable = 'f' THEN 0 ELSE 1 END AS is_deferrable,
727  CASE WHEN c.condeferred = 'f' THEN 0 ELSE 1 END AS is_deferred,
728  t.relname AS table_name, array_to_string(c.conkey, ' ') AS constraint_key,
729  CASE confupdtype
730  WHEN 'a' THEN 'NO ACTION'
731  WHEN 'r' THEN 'RESTRICT'
732  WHEN 'c' THEN 'CASCADE'
733  WHEN 'n' THEN 'SET NULL'
734  WHEN 'd' THEN 'SET DEFAULT'
735  END AS on_update,
736  CASE confdeltype
737  WHEN 'a' THEN 'NO ACTION'
738  WHEN 'r' THEN 'RESTRICT'
739  WHEN 'c' THEN 'CASCADE'
740  WHEN 'n' THEN 'SET NULL'
741  WHEN 'd' THEN 'SET DEFAULT' END AS on_delete,
742  CASE confmatchtype
743  WHEN 'u' THEN 'UNSPECIFIED'
744  WHEN 'f' THEN 'FULL'
745  WHEN 'p' THEN 'PARTIAL'
746  END AS match_type,
747  t2.relname AS references_table,
748  array_to_string(c.confkey, ' ') AS fk_constraint_key
749  FROM pg_constraint AS c
750  LEFT JOIN pg_class AS t ON c.conrelid = t.oid
751  INNER JOIN information_schema.tables AS tab ON t.relname = tab.table_name
752  LEFT JOIN pg_class AS t2 ON c.confrelid = t2.oid
753  ORDER BY constraint_name,table_name
754  ";
755  $stmt = __METHOD__;
756  $this->dbman->prepare($stmt, $sql);
757  $result = $this->dbman->execute($stmt);
758  $Results = $this->dbman->fetchAll($result);
759  $this->dbman->freeResult($result);
760  /* Constraints use indexes into columns. Covert those to column names. */
761  for ($i = 0; !empty($Results[$i]['constraint_name']); $i++) {
762  $Key = "";
763  $Keys = explode(" ", $Results[$i]['constraint_key']);
764  foreach ($Keys as $K) {
765  if (empty($K)) {
766  continue;
767  }
768  if (!empty($Key)) {
769  $Key .= ",";
770  }
771  if (!empty($this->currSchema['TABLEID'][$Results[$i]['table_name']][$K])) {
772  $Key .= '"' . $this->currSchema['TABLEID'][$Results[$i]['table_name']][$K] . '"';
773  }
774  }
775  $Results[$i]['constraint_key'] = $Key;
776  $Key = "";
777  if (!empty($Results[$i]['fk_constraint_key'])) {
778  $Keys = explode(" ", $Results[$i]['fk_constraint_key']);
779  } else {
780  $Keys = [];
781  }
782  foreach ($Keys as $K) {
783  if (empty($K)) {
784  continue;
785  }
786  if (!empty($Key)) {
787  $Key .= ",";
788  }
789  $Key .= '"' . $this->currSchema['TABLEID'][$Results[$i]['references_table']][$K] . '"';
790  }
791  $Results[$i]['fk_constraint_key'] = $Key;
792  }
793  /* Save the constraint */
794  /* There are different types of constraints that must be stored in order */
795  /* CONSTRAINT: PRIMARY KEY */
796  for ($i = 0; !empty($Results[$i]['constraint_name']); $i++) {
797  if ($Results[$i]['type'] != 'PRIMARY KEY') {
798  continue;
799  }
800  $sql = "ALTER TABLE \"" . $Results[$i]['table_name'] . "\"";
801  $sql .= " ADD CONSTRAINT \"" . $Results[$i]['constraint_name'] . '"';
802  $sql .= " " . $Results[$i]['type'];
803  $sql .= " (" . $Results[$i]['constraint_key'] . ")";
804  if (!empty($Results[$i]['references_table'])) {
805  $sql .= " REFERENCES \"" . $Results[$i]['references_table'] . "\"";
806  $sql .= " (" . $Results[$i]['fk_constraint_key'] . ")";
807  }
808  $sql .= ";";
809  $this->currSchema['CONSTRAINT'][$Results[$i]['constraint_name']] = $sql;
810  $Results[$i]['processed'] = 1;
811  }
812  /* CONSTRAINT: UNIQUE */
813  for ($i = 0; !empty($Results[$i]['constraint_name']); $i++) {
814  if ($Results[$i]['type'] != 'UNIQUE') {
815  continue;
816  }
817  $sql = "ALTER TABLE \"" . $Results[$i]['table_name'] . "\"";
818  $sql .= " ADD CONSTRAINT \"" . $Results[$i]['constraint_name'] . '"';
819  $sql .= " " . $Results[$i]['type'];
820  $sql .= " (" . $Results[$i]['constraint_key'] . ")";
821  if (!empty($Results[$i]['references_table'])) {
822  $sql .= " REFERENCES \"" . $Results[$i]['references_table'] . "\"";
823  $sql .= " (" . $Results[$i]['fk_constraint_key'] . ")";
824  }
825  $sql .= ";";
826  $this->currSchema['CONSTRAINT'][$Results[$i]['constraint_name']] = $sql;
827  $Results[$i]['processed'] = 1;
828  }
829 
830  /* CONSTRAINT: FOREIGN KEY */
831  for ($i = 0; !empty($Results[$i]['constraint_name']); $i++) {
832  if ($Results[$i]['type'] != 'FOREIGN KEY') {
833  continue;
834  }
835  $sql = "ALTER TABLE \"" . $Results[$i]['table_name'] . "\"";
836  $sql .= " ADD CONSTRAINT \"" . $Results[$i]['constraint_name'] . '"';
837  $sql .= " " . $Results[$i]['type'];
838  $sql .= " (" . $Results[$i]['constraint_key'] . ")";
839  if (!empty($Results[$i]['references_table'])) {
840  $sql .= " REFERENCES \"" . $Results[$i]['references_table'] . "\"";
841  $sql .= " (" . $Results[$i]['fk_constraint_key'] . ")";
842  }
843 
844  if (!empty($Results[$i]['on_update'])) {
845  $sql .= " ON UPDATE " . $Results[$i]['on_update'];
846  }
847  if (!empty($Results[$i]['on_delete'])) {
848  $sql .= " ON DELETE " . $Results[$i]['on_delete'];
849  }
850 
851  $sql .= ";";
852  $this->currSchema['CONSTRAINT'][$Results[$i]['constraint_name']] = $sql;
853  $Results[$i]['processed'] = 1;
854  }
855 
856  /* CONSTRAINT: ALL OTHERS */
857  for ($i = 0; !empty($Results[$i]['constraint_name']); $i++) {
858  if (!empty($Results[$i]['processed']) && $Results[$i]['processed'] == 1) {
859  continue;
860  }
861 
862  $sql = "ALTER TABLE \"" . $Results[$i]['table_name'] . "\"";
863  $sql .= " ADD CONSTRAINT \"" . $Results[$i]['constraint_name'] . '"';
864  $sql .= " " . $Results[$i]['type'];
865  $sql .= " (" . $Results[$i]['constraint_key'] . ")";
866  if (!empty($Results[$i]['references_table'])) {
867  $sql .= " REFERENCES \"" . $Results[$i]['references_table'] . "\"";
868  $sql .= " (" . $Results[$i]['fk_constraint_key'] . ")";
869  }
870  $sql .= ";";
871  $this->currSchema['CONSTRAINT'][$Results[$i]['constraint_name']] = $sql;
872  $Results[$i]['processed'] = 1;
873  }
874  }
875 
879  function addIndexes()
880  {
881  $sql = "SELECT tablename AS \"table\", indexname AS index, indexdef AS define
882  FROM pg_indexes
883  INNER JOIN information_schema.tables ON table_name = tablename
884  AND table_type = 'BASE TABLE'
885  AND table_schema = 'public'
886  AND schemaname = 'public'
887  ORDER BY tablename,indexname;
888  ";
889  $stmt = __METHOD__;
890  $this->dbman->prepare($stmt, $sql);
891  $result = $this->dbman->execute($stmt);
892  while ($row = $this->dbman->fetchArray($result)) {
893  /* UNIQUE constraints also include indexes. */
894  if (empty($this->currSchema['CONSTRAINT'][$row['index']])) {
895  $this->currSchema['INDEX'][$row['table']][$row['index']] = str_replace("public.", "", $row['define']) . ";";
896  }
897  }
898  $this->dbman->freeResult($result);
899  }
900 
907  {
908  // prosrc
909  // proretset == setof
910  $sql = "SELECT proname AS name,
911  pronargs AS input_num,
912  proargnames AS input_names,
913  proargtypes AS input_type,
914  proargmodes AS input_modes,
915  proretset AS setof,
916  prorettype AS output_type
917  FROM pg_proc AS proc
918  INNER JOIN pg_language AS lang ON proc.prolang = lang.oid
919  WHERE lang.lanname = 'plpgsql'
920  ORDER BY proname;";
921  $stmt = __METHOD__;
922  $this->dbman->prepare($stmt, $sql);
923  $result = $this->dbman->execute($stmt);
924  while ($row = $this->dbman->fetchArray($result)) {
925  $sql = "CREATE or REPLACE function " . $row['proname'] . "()";
926  $sql .= ' RETURNS ' . "TBD" . ' AS $$';
927  $sql .= " " . $row['prosrc'];
928  $schema['FUNCTION'][$row['proname']] = $sql;
929  }
930  $this->dbman->freeResult($result);
931  return $schema;
932  }
933 
941  function writeArrayEntries($fout, $key, $value, $varname)
942  {
943  $varname .= '["' . str_replace('"', '\"', $key) . '"]';
944  if (!is_array($value)) {
945  $value = str_replace('"', '\"', $value);
946  fwrite($fout, "$varname = \"$value\";\n");
947  return;
948  }
949  foreach ($value as $k => $v) {
950  $this->writeArrayEntries($fout, $k, $v, $varname);
951  }
952  fwrite($fout, "\n");
953  }
954 
963  function exportSchema($filename = NULL)
964  {
965  global $PG_CONN;
966 
967  /* set driver */
968  $dbDriver = $this->dbman->getDriver();
969  if (empty($dbDriver)) {
970  $pgDrive = new Postgres($PG_CONN);
971  $this->dbman->setDriver($pgDrive);
972  }
973 
974  if (empty($filename)) {
975  $filename = 'php://stdout';
976  }
977  $Schema = $this->getCurrSchema();
978  $fout = fopen($filename, "w");
979  if (!$fout) {
980  return ("Failed to write to $filename\n");
981  }
982  global $Name;
983  fwrite($fout, "<?php\n");
984  fwrite($fout, "/* This file is generated by " . $Name . " */\n");
985  fwrite($fout, "/* Do not manually edit this file */\n\n");
986  fwrite($fout, ' $Schema=array();' . "\n\n");
987  foreach ($Schema as $K1 => $V1) {
988  $this->writeArrayEntries($fout, $K1, $V1, ' $Schema');
989  }
990  fclose($fout);
991  return false;
992  }
993 
994 
998  function makeFunctions()
999  {
1000  print " Applying database functions\n";
1001  flush();
1002  /* *******************************************
1003  * uploadtree2path is a DB function that returns the non-artifact parents of an uploadtree_pk.
1004  * drop and recreate to change the return type.
1005  */
1006  $sql = 'drop function if exists uploadtree2path(integer);';
1007  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . '.uploadtree2path.drop');
1008 
1009  $sql = '
1010  CREATE function uploadtree2path(uploadtree_pk_in int) returns setof uploadtree as $$
1011  DECLARE
1012  UTrec uploadtree;
1013  UTpk integer;
1014  sql varchar;
1015  BEGIN
1016  UTpk := uploadtree_pk_in;
1017  WHILE UTpk > 0 LOOP
1018  sql := ' . "'" . 'select * from uploadtree where uploadtree_pk=' . "'" . ' || UTpk;
1019  execute sql into UTrec;
1020  IF ((UTrec.ufile_mode & (1<<28)) = 0) THEN RETURN NEXT UTrec; END IF;
1021  UTpk := UTrec.parent;
1022  END LOOP;
1023  RETURN;
1024  END;
1025  $$
1026  LANGUAGE plpgsql;
1027  ';
1028  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . '.uploadtree2path.create');
1029 
1030  /*
1031  * getItemParent is a DB function that returns the non-artifact parent of an uploadtree_pk.
1032  * drop and recreate to change the return type.
1033  */
1034  $sql = 'drop function if exists getItemParent(integer);';
1035  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . '.getItemParent.drop');
1036 
1037  $sql = '
1038  CREATE OR REPLACE FUNCTION getItemParent(itemId Integer) RETURNS Integer AS $$
1039  WITH RECURSIVE file_tree(uploadtree_pk, parent, jump, path, cycle) AS (
1040  SELECT ut.uploadtree_pk, ut.parent,
1041  true,
1042  ARRAY[ut.uploadtree_pk],
1043  false
1044  FROM uploadtree ut
1045  WHERE ut.uploadtree_pk = $1
1046  UNION ALL
1047  SELECT ut.uploadtree_pk, ut.parent,
1048  ut.ufile_mode & (1<<28) != 0,
1049  path || ut.uploadtree_pk,
1050  ut.uploadtree_pk = ANY(path)
1051  FROM uploadtree ut, file_tree ft
1052  WHERE ut.uploadtree_pk = ft.parent AND jump AND NOT cycle
1053  )
1054  SELECT uploadtree_pk from file_tree ft WHERE NOT jump
1055  $$
1056  LANGUAGE SQL
1057  STABLE
1058  RETURNS NULL ON NULL INPUT
1059  ';
1060  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . '.getItemParent.create');
1061  return;
1062  }
1063 
1068  {
1069  if (empty($this->schema['INHERITS'])) {
1070  return;
1071  }
1072  foreach ($this->schema['INHERITS'] as $table => $fromTable) {
1073  if (empty($table)) {
1074  continue;
1075  }
1076  if (!$this->dbman->existsTable($table) && $this->dbman->existsTable($fromTable)) {
1077  $sql = "CREATE TABLE \"$table\" () INHERITS (\"$fromTable\")";
1078  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . $table);
1079  }
1080  }
1081  }
1082 
1083  // MakeFunctions()
1084 }
1085 
1086 if (empty($dbManager) || !($dbManager instanceof DbManager)) {
1087  $logLevel = Logger::INFO;
1088  $logger = new Logger(__FILE__);
1089  $logger->pushHandler(new ErrorLogHandler(ErrorLogHandler::OPERATING_SYSTEM, $logLevel));
1090  $dbManager = new ModernDbManager($logger);
1091  $pgDriver = new Postgres($GLOBALS['PG_CONN']);
1092  $dbManager->setDriver($pgDriver);
1093 }
1094 /* simulate the old functions*/
1095 $libschema = new fo_libschema($dbManager);
1103 function ApplySchema($Filename = NULL, $Debug = false, $Catalog = 'fossology')
1104 {
1105  global $libschema;
1106  return $libschema->applySchema($Filename, $Debug, $Catalog);
1107 }
1108 
1112 function GetSchema()
1113 {
1114  global $libschema;
1115  return $libschema->getCurrSchema();
1116 }
1117 
1124 function ExportSchema($filename = NULL)
1125 {
1126  global $libschema;
1127  return $libschema->exportSchema($filename);
1128 }
1129 
1133 function MakeFunctions($Debug)
1134 {
1135  global $libschema;
1136  $libschema->makeFunctions($Debug);
1137 }
int debug
Definition: buckets.c:57
Class to handle database schema.
Definition: libschema.php:29
writeArrayEntries($fout, $key, $value, $varname)
Definition: libschema.php:941
applyTables($inherits=false)
Add tables/columns (dependent on sequences for default values)
Definition: libschema.php:281
addFunctions($schema)
Definition: libschema.php:906
applySchema($filename=NULL, $debug=false, $catalog='fossology', $migrateColumns=array())
Make schema match $Filename. This is a single transaction.
Definition: libschema.php:96
exportSchema($filename=NULL)
Export the schema of the connected ($PG_CONN) database to a file in the format readable by GetSchema(...
Definition: libschema.php:963
__construct(DbManager &$dbManager)
Definition: libschema.php:58
addViews($viewowner)
Definition: libschema.php:663
addInheritedRelations()
Definition: libschema.php:572
applySequences()
Add sequences to the database.
Definition: libschema.php:211
applyClusters()
Add clusters.
Definition: libschema.php:234
applyOrEchoOnce($sql, $stmt='')
Definition: libschema.php:78
dropColumnsFromTable($columns, $table)
Definition: libschema.php:535
dropViews($catalog)
Delete views.
Definition: libschema.php:506
updateSequences()
Add sequences.
Definition: libschema.php:258
addSequences($referencedSequencesInTableColumns)
Definition: libschema.php:680
dropConstraints()
Delete constraints.
Definition: libschema.php:373
makeFunctions()
Create any required DB functions.
Definition: libschema.php:998
getCurrSchema()
Load the schema from the db into an array.
Definition: libschema.php:555
setDriver(Driver &$dbDriver)
Definition: libschema.php:67
applyInheritedRelations()
Definition: libschema.php:1067
ReportCachePurgeAll()
Purge all records from the report cache.
DB_ColExists($tableName, $colName, $DBName='fossology')
Check if a column exists.
Definition: common-db.php:240
DB_ConstraintExists($ConstraintName, $DBName='fossology')
Check if a constraint exists.
Definition: common-db.php:264
DB_TableExists($tableName)
Check if table exists.
Definition: common-db.php:214
ExportSchema($filename=NULL)
Export the schema of the connected ($PG_CONN) database to a file in the format readable by GetSchema(...
Definition: libschema.php:1124
GetSchema()
Load the schema from the db into an array.
Definition: libschema.php:1112
MakeFunctions($Debug)
Create any required DB functions.
Definition: libschema.php:1133
ApplySchema($Filename=NULL, $Debug=false, $Catalog='fossology')
Make schema match $Filename. This is a single transaction.
Definition: libschema.php:1103
foreach($Options as $Option=> $OptVal) if(0==$reference_flag &&0==$nomos_flag) $PG_CONN