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