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