FOSSology  4.7.0-rc1
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  }
330 
331  $sql = $modification['ADD'];
332  if ($this->debug) {
333  print "$sql\n";
334  } else {
335  // Add the new column which sets the default value
336  $this->dbman->queryOnce($sql);
337  }
338  if (!empty($rename)) {
339  /* copy over the old data */
340  $this->applyOrEchoOnce($sql = "UPDATE \"$table\" SET \"$column\" = \"$rename\"");
341  $this->applyOrEchoOnce($sql = "ALTER TABLE \"$table\" DROP COLUMN \"$rename\"");
342  }
343  }
344  if ($colNewTable ||
345  $this->currSchema['TABLE'][$table][$column]['ALTER'] != $modification['ALTER'] && isset($modification['ALTER'])) {
346  $sql = $modification['ALTER'];
347  if ($this->debug) {
348  print "$sql\n";
349  } else if (!empty ($sql)) {
350  $this->dbman->queryOnce($sql);
351  }
352  }
353  if ($colNewTable ||
354  $this->currSchema['TABLE'][$table][$column]['DESC'] != $modification['DESC']) {
355  $sql = empty($modification['DESC']) ? "COMMENT ON COLUMN \"$table\".\"$column\" IS ''" : $modification['DESC'];
356  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . "$table.$column.comment");
357  }
358  }
359  }
360  }
361 
365  function applyViews()
366  {
367  if (empty($this->schema['VIEW'])) {
368  return;
369  }
370  $newViews = !array_key_exists('VIEW', $this->currSchema);
371  foreach ($this->schema['VIEW'] as $name => $sql) {
372  if (empty($name) || (!$newViews &&
373  $this->currSchema['VIEW'][$name] == $sql)) {
374  continue;
375  }
376  if (!$newViews && !empty($this->currSchema['VIEW'][$name])) {
377  $sqlDropView = "DROP VIEW IF EXISTS $name";
378  $this->applyOrEchoOnce($sqlDropView);
379  }
380  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . $name);
381  }
382  }
383 
389  function dropConstraints()
390  {
391  if (!array_key_exists('CONSTRAINT', $this->currSchema) || // Empty DB
392  empty($this->currSchema['CONSTRAINT'])) {
393  return;
394  }
395  foreach ($this->currSchema['CONSTRAINT'] as $name => $sql) {
396  // skip if constraint name is empty or does not exist
397  if (empty($name) || !array_key_exists($name, $this->schema['CONSTRAINT'])
398  || ($this->schema['CONSTRAINT'][$name] == $sql)
399  || !DB_ConstraintExists($name)) {
400  continue;
401  }
402 
403  /* Only process tables that I know about */
404  $table = preg_replace("/^ALTER TABLE \"(.*)\" ADD CONSTRAINT.*/", '${1}', $sql);
405  $TableFk = preg_replace("/^.*FOREIGN KEY .* REFERENCES \"(.*)\" \‍(.*/", '${1}', $sql);
406  if ($TableFk == $sql) {
407  $TableFk = $table;
408  }
409  /* If I don't know the primary or foreign table... */
410  if (empty($this->schema['TABLE'][$table]) && empty($this->schema['TABLE'][$TableFk])) {
411  continue;
412  }
413  $sql = "ALTER TABLE \"$table\" DROP CONSTRAINT \"$name\" CASCADE";
414  $this->applyOrEchoOnce($sql);
415  }
416  }
417 
421  function dropIndexes()
422  {
423  if (!array_key_exists('INDEX', $this->currSchema) ||
424  empty($this->currSchema['INDEX'])) {
425  return;
426  }
427  foreach ($this->currSchema['INDEX'] as $table => $IndexInfo) {
428  if (empty($table) || (empty($this->schema['TABLE'][$table]) && empty($this->schema['INHERITS'][$table]))) {
429  continue;
430  }
431  foreach ($IndexInfo as $name => $sql) {
432  if (empty($name) || $this->schema['INDEX'][$table][$name] == $sql) {
433  continue;
434  }
435  $sql = "DROP INDEX \"$name\"";
436  $this->applyOrEchoOnce($sql);
437  }
438  }
439  }
440 
444  function applyIndexes()
445  {
446  if (empty($this->schema['INDEX'])) {
447  return;
448  }
449  foreach ($this->schema['INDEX'] as $table => $indexInfo) {
450  if (empty($table)) {
451  continue;
452  }
453  if (!array_key_exists($table, $this->schema["TABLE"]) && !array_key_exists($table, $this->schema['INHERITS'])) {
454  echo "skipping orphan table: $table\n";
455  continue;
456  }
457  $newIndexes = false;
458  if (!array_key_exists('INDEX', $this->currSchema) ||
459  !array_key_exists($table, $this->currSchema['INDEX'])) {
460  $newIndexes = true;
461  }
462  foreach ($indexInfo as $name => $sql) {
463  if (empty($name) || (!$newIndexes &&
464  array_key_exists($name, $this->currSchema['INDEX'][$table]) &&
465  $this->currSchema['INDEX'][$table][$name] == $sql)) {
466  continue;
467  }
468  $this->applyOrEchoOnce($sql);
469  $sql = "REINDEX INDEX \"$name\"";
470  $this->applyOrEchoOnce($sql);
471  }
472  }
473  }
474 
478  function applyConstraints()
479  {
480  $this->currSchema = $this->getCurrSchema(); /* constraints and indexes are linked, recheck */
481  if (empty($this->schema['CONSTRAINT'])) {
482  return;
483  }
484  /* Constraints must be added in the correct order! */
485  $orderedConstraints = array('primary' => array(), 'unique' => array(), 'foreign' => array(), 'other' => array());
486  foreach ($this->schema['CONSTRAINT'] as $Name => $sql) {
487  $newConstraint = false;
488  if (!array_key_exists('CONSTRAINT', $this->currSchema) ||
489  !array_key_exists($Name, $this->currSchema['CONSTRAINT'])) {
490  $newConstraint = true;
491  }
492  if (empty($Name) || (!$newConstraint &&
493  $this->currSchema['CONSTRAINT'][$Name] == $sql)) {
494  continue;
495  }
496  if (preg_match("/PRIMARY KEY/", $sql)) {
497  $orderedConstraints['primary'][] = $sql;
498  } elseif (preg_match("/UNIQUE/", $sql)) {
499  $orderedConstraints['unique'][] = $sql;
500  } elseif (preg_match("/FOREIGN KEY/", $sql)) {
501  $orderedConstraints['foreign'][] = $sql;
502  } else {
503  $orderedConstraints['other'][] = $sql;
504  }
505  }
506  foreach ($orderedConstraints as $type => $constraints) {
507  foreach ($constraints as $sql) {
508  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . ".constraint.$type");
509  }
510  }
511  }
512 
522  function dropViews($catalog)
523  {
524  $sql = "SELECT view_name,vcs.table_name,column_name
525  FROM information_schema.view_column_usage AS vcs
526  INNER JOIN information_schema.views AS v
527  ON vcs.view_name = v.table_name
528  WHERE vcs.table_catalog='$catalog'
529  AND v.table_schema = 'public'
530  ORDER BY view_name,vcs.table_name,column_name;";
531  $stmt = __METHOD__;
532  $this->dbman->prepare($stmt, $sql);
533  $result = $this->dbman->execute($stmt);
534  while ($row = $this->dbman->fetchArray($result)) {
535  $View = $row['view_name'];
536  $table = $row['table_name'];
537  $column = $row['column_name'];
538  if (empty($this->schema['TABLE'][$table]) || empty($this->schema['TABLE'][$table][$column])) {
539  $sql = "DROP VIEW IF EXISTS \"$View\";";
540  $this->applyOrEchoOnce($sql);
541  }
542  }
543  $result = $this->dbman->freeResult($result);
544  }
545 
551  function dropColumnsFromTable($columns, $table)
552  {
553  if (empty($table) || empty($this->schema['TABLE'][$table])) {
554  return;
555  }
556  foreach ($columns as $column) {
557  if (empty($column)) {
558  continue;
559  }
560  if (empty($this->schema['TABLE'][$table][$column])) {
561  $sql = "ALTER TABLE \"$table\" DROP COLUMN \"$column\";";
562  $this->applyOrEchoOnce($sql);
563  }
564  }
565  }
566 
567 
571  function getCurrSchema()
572  {
573  $this->ensureDriver();
574  global $SysConf, $PG_CONN;
575  $this->currSchema = array();
576  $this->addInheritedRelations();
577  $referencedSequencesInTableColumns = $this->addTables();
578  if (!empty($SysConf['DBCONF']['user'])) {
579  $viewowner = $SysConf['DBCONF']['user'];
580  } elseif (!empty($PG_CONN)) {
581  $viewowner = pg_parameter_status($PG_CONN, 'session_authorization');
582  }
583  if (empty($viewowner)) {
584  throw new \Exception(
585  "Unable to load schema views: could not determine the view owner. " .
586  "Ensure \$SysConf['DBCONF']['user'] is set or a valid \$PG_CONN is available."
587  );
588  }
589  $this->addViews($viewowner);
590  $this->addSequences($referencedSequencesInTableColumns);
591  $this->addConstraints();
592  $this->addIndexes();
593  unset($this->currSchema['TABLEID']);
594  return $this->currSchema;
595  }
596 
601  {
602  $sql = "SELECT class.relname AS \"table\", daddy.relname AS inherits_from
603  FROM pg_class AS class
604  INNER JOIN pg_catalog.pg_inherits ON pg_inherits.inhrelid = class.oid
605  INNER JOIN pg_class daddy ON pg_inherits.inhparent = daddy.oid";
606  $this->dbman->prepare($stmt=__METHOD__, $sql);
607  $res = $this->dbman->execute($stmt);
608  $relations = array();
609  while ($row=$this->dbman->fetchArray($res)) {
610  $relations[$row['table']] = $row['inherits_from'];
611  }
612  $this->dbman->freeResult($res);
613  $this->currSchema['INHERITS'] = $relations;
614  }
615 
619  function addTables()
620  {
621  $referencedSequencesInTableColumns = array();
622 
623  $sql = "SELECT
624  table_name AS \"table\", ordinal_position AS ordinal, column_name,
625  udt_name AS type, character_maximum_length AS modifier,
626  CASE is_nullable WHEN 'YES' THEN false WHEN 'NO' THEN true END AS \"notnull\",
627  column_default AS \"default\",
628  col_description(table_name::regclass, ordinal_position) AS description
629  FROM information_schema.columns
630  WHERE table_schema = 'public'
631  ORDER BY table_name, ordinal_position;";
632  $stmt = __METHOD__;
633  $this->dbman->prepare($stmt, $sql);
634  $result = $this->dbman->execute($stmt);
635  while ($R = $this->dbman->fetchArray($result)) {
636  $Table = $R['table'];
637  $Column = $R['column_name'];
638  if (array_key_exists($Table, $this->currSchema['INHERITS'])) {
639  $this->currSchema['TABLEID'][$Table][$R['ordinal']] = $Column;
640  continue;
641  }
642  $Type = $R['type'];
643  if ($Type == 'bpchar') {
644  $Type = "char";
645  }
646  if ($R['modifier'] > 0) {
647  $Type .= '(' . $R['modifier'] . ')';
648  }
649  if (!empty($R['description'])) {
650  $Desc = str_replace("'", "''", $R['description']);
651  } else {
652  $Desc = "";
653  }
654  $this->currSchema['TABLEID'][$Table][$R['ordinal']] = $Column;
655  if (!empty($Desc)) {
656  $this->currSchema['TABLE'][$Table][$Column]['DESC'] = "COMMENT ON COLUMN \"$Table\".\"$Column\" IS '$Desc'";
657  } else {
658  $this->currSchema['TABLE'][$Table][$Column]['DESC'] = "";
659  }
660  $this->currSchema['TABLE'][$Table][$Column]['ADD'] = "ALTER TABLE \"$Table\" ADD COLUMN \"$Column\" $Type";
661  $this->currSchema['TABLE'][$Table][$Column]['ALTER'] = "ALTER TABLE \"$Table\"";
662  $Alter = "ALTER COLUMN \"$Column\"";
663  if ($R['notnull'] == 't') {
664  $this->currSchema['TABLE'][$Table][$Column]['ALTER'] .= " $Alter SET NOT NULL";
665  } else {
666  $this->currSchema['TABLE'][$Table][$Column]['ALTER'] .= " $Alter DROP NOT NULL";
667  }
668  if ($R['default'] != '') {
669  $R['default'] = preg_replace("/::bpchar/", "::char", $R['default']);
670  $R['default'] = str_replace("public.", "", $R['default']);
671  $this->currSchema['TABLE'][$Table][$Column]['ALTER'] .= ", $Alter SET DEFAULT " . $R['default'];
672  $this->currSchema['TABLE'][$Table][$Column]['ADD'] .= " DEFAULT " . $R['default'];
673 
674  $rgx = "/nextval\‍('([a-z_]*)'.*\‍)/";
675  $matches = array();
676  if (preg_match($rgx, $R['default'], $matches)) {
677  $sequence = $matches[1];
678  $referencedSequencesInTableColumns[$sequence] = array("table" => $Table, "column" => $Column);
679  }
680  }
681  }
682  $this->dbman->freeResult($result);
683 
684  return $referencedSequencesInTableColumns;
685  }
686 
691  function addViews($viewowner)
692  {
693  $sql = "SELECT viewname,definition FROM pg_views WHERE viewowner = $1";
694  $stmt = __METHOD__;
695  $this->dbman->prepare($stmt, $sql);
696  $result = $this->dbman->execute($stmt, array($viewowner));
697  while ($row = $this->dbman->fetchArray($result)) {
698  $sql = "CREATE VIEW \"" . $row['viewname'] . "\" AS " . $row['definition'];
699  $this->currSchema['VIEW'][$row['viewname']] = $sql;
700  }
701  $this->dbman->freeResult($result);
702  }
703 
708  function addSequences($referencedSequencesInTableColumns)
709  {
710  $sql = "SELECT relname
711  FROM pg_class
712  WHERE relkind = 'S'
713  AND relnamespace IN (
714  SELECT oid FROM pg_namespace WHERE nspname NOT LIKE 'pg_%' AND nspname != 'information_schema'
715  )";
716 
717  $stmt = __METHOD__;
718  $this->dbman->prepare($stmt, $sql);
719  $result = $this->dbman->execute($stmt);
720 
721  while ($row = $this->dbman->fetchArray($result)) {
722  $sequence = $row['relname'];
723  if (empty($sequence)) {
724  continue;
725  }
726 
727  $sqlCreate = "CREATE SEQUENCE \"" . $sequence . "\"";
728  $this->currSchema['SEQUENCE'][$sequence]['CREATE'] = $sqlCreate;
729 
730  if (array_key_exists($sequence, $referencedSequencesInTableColumns)) {
731  $table = $referencedSequencesInTableColumns[$sequence]['table'];
732  $column = $referencedSequencesInTableColumns[$sequence]['column'];
733 
734  $sqlUpdate = "SELECT setval('$sequence',(SELECT greatest(1,max($column)) val FROM $table))";
735  $this->currSchema['SEQUENCE'][$sequence]['UPDATE'] = $sqlUpdate;
736  }
737  }
738 
739  $this->dbman->freeResult($result);
740  }
741 
745  function addConstraints()
746  {
747  $sql = "SELECT c.conname AS constraint_name,
748  CASE c.contype
749  WHEN 'c' THEN 'CHECK'
750  WHEN 'f' THEN 'FOREIGN KEY'
751  WHEN 'p' THEN 'PRIMARY KEY'
752  WHEN 'u' THEN 'UNIQUE'
753  END AS type,
754  CASE WHEN c.condeferrable = 'f' THEN 0 ELSE 1 END AS is_deferrable,
755  CASE WHEN c.condeferred = 'f' THEN 0 ELSE 1 END AS is_deferred,
756  t.relname AS table_name, array_to_string(c.conkey, ' ') AS constraint_key,
757  CASE confupdtype
758  WHEN 'a' THEN 'NO ACTION'
759  WHEN 'r' THEN 'RESTRICT'
760  WHEN 'c' THEN 'CASCADE'
761  WHEN 'n' THEN 'SET NULL'
762  WHEN 'd' THEN 'SET DEFAULT'
763  END AS on_update,
764  CASE confdeltype
765  WHEN 'a' THEN 'NO ACTION'
766  WHEN 'r' THEN 'RESTRICT'
767  WHEN 'c' THEN 'CASCADE'
768  WHEN 'n' THEN 'SET NULL'
769  WHEN 'd' THEN 'SET DEFAULT' END AS on_delete,
770  CASE confmatchtype
771  WHEN 'u' THEN 'UNSPECIFIED'
772  WHEN 'f' THEN 'FULL'
773  WHEN 'p' THEN 'PARTIAL'
774  END AS match_type,
775  t2.relname AS references_table,
776  array_to_string(c.confkey, ' ') AS fk_constraint_key
777  FROM pg_constraint AS c
778  LEFT JOIN pg_class AS t ON c.conrelid = t.oid
779  INNER JOIN information_schema.tables AS tab ON t.relname = tab.table_name
780  LEFT JOIN pg_class AS t2 ON c.confrelid = t2.oid
781  ORDER BY constraint_name,table_name
782  ";
783  $stmt = __METHOD__;
784  $this->dbman->prepare($stmt, $sql);
785  $result = $this->dbman->execute($stmt);
786  $Results = $this->dbman->fetchAll($result);
787  $this->dbman->freeResult($result);
788  /* Constraints use indexes into columns. Covert those to column names. */
789  for ($i = 0; !empty($Results[$i]['constraint_name']); $i++) {
790  $Key = "";
791  $Keys = explode(" ", $Results[$i]['constraint_key']);
792  foreach ($Keys as $K) {
793  if (empty($K)) {
794  continue;
795  }
796  if (!empty($Key)) {
797  $Key .= ",";
798  }
799  if (!empty($this->currSchema['TABLEID'][$Results[$i]['table_name']][$K])) {
800  $Key .= '"' . $this->currSchema['TABLEID'][$Results[$i]['table_name']][$K] . '"';
801  }
802  }
803  $Results[$i]['constraint_key'] = $Key;
804  $Key = "";
805  if (!empty($Results[$i]['fk_constraint_key'])) {
806  $Keys = explode(" ", $Results[$i]['fk_constraint_key']);
807  } else {
808  $Keys = [];
809  }
810  foreach ($Keys as $K) {
811  if (empty($K)) {
812  continue;
813  }
814  if (!empty($Key)) {
815  $Key .= ",";
816  }
817  $Key .= '"' . $this->currSchema['TABLEID'][$Results[$i]['references_table']][$K] . '"';
818  }
819  $Results[$i]['fk_constraint_key'] = $Key;
820  }
821  /* Save the constraint */
822  /* There are different types of constraints that must be stored in order */
823  /* CONSTRAINT: PRIMARY KEY */
824  for ($i = 0; !empty($Results[$i]['constraint_name']); $i++) {
825  if ($Results[$i]['type'] != 'PRIMARY KEY') {
826  continue;
827  }
828  $sql = "ALTER TABLE \"" . $Results[$i]['table_name'] . "\"";
829  $sql .= " ADD CONSTRAINT \"" . $Results[$i]['constraint_name'] . '"';
830  $sql .= " " . $Results[$i]['type'];
831  $sql .= " (" . $Results[$i]['constraint_key'] . ")";
832  if (!empty($Results[$i]['references_table'])) {
833  $sql .= " REFERENCES \"" . $Results[$i]['references_table'] . "\"";
834  $sql .= " (" . $Results[$i]['fk_constraint_key'] . ")";
835  }
836  $sql .= ";";
837  $this->currSchema['CONSTRAINT'][$Results[$i]['constraint_name']] = $sql;
838  $Results[$i]['processed'] = 1;
839  }
840  /* CONSTRAINT: UNIQUE */
841  for ($i = 0; !empty($Results[$i]['constraint_name']); $i++) {
842  if ($Results[$i]['type'] != 'UNIQUE') {
843  continue;
844  }
845  $sql = "ALTER TABLE \"" . $Results[$i]['table_name'] . "\"";
846  $sql .= " ADD CONSTRAINT \"" . $Results[$i]['constraint_name'] . '"';
847  $sql .= " " . $Results[$i]['type'];
848  $sql .= " (" . $Results[$i]['constraint_key'] . ")";
849  if (!empty($Results[$i]['references_table'])) {
850  $sql .= " REFERENCES \"" . $Results[$i]['references_table'] . "\"";
851  $sql .= " (" . $Results[$i]['fk_constraint_key'] . ")";
852  }
853  $sql .= ";";
854  $this->currSchema['CONSTRAINT'][$Results[$i]['constraint_name']] = $sql;
855  $Results[$i]['processed'] = 1;
856  }
857 
858  /* CONSTRAINT: FOREIGN KEY */
859  for ($i = 0; !empty($Results[$i]['constraint_name']); $i++) {
860  if ($Results[$i]['type'] != 'FOREIGN KEY') {
861  continue;
862  }
863  $sql = "ALTER TABLE \"" . $Results[$i]['table_name'] . "\"";
864  $sql .= " ADD CONSTRAINT \"" . $Results[$i]['constraint_name'] . '"';
865  $sql .= " " . $Results[$i]['type'];
866  $sql .= " (" . $Results[$i]['constraint_key'] . ")";
867  if (!empty($Results[$i]['references_table'])) {
868  $sql .= " REFERENCES \"" . $Results[$i]['references_table'] . "\"";
869  $sql .= " (" . $Results[$i]['fk_constraint_key'] . ")";
870  }
871 
872  if (!empty($Results[$i]['on_update'])) {
873  $sql .= " ON UPDATE " . $Results[$i]['on_update'];
874  }
875  if (!empty($Results[$i]['on_delete'])) {
876  $sql .= " ON DELETE " . $Results[$i]['on_delete'];
877  }
878 
879  $sql .= ";";
880  $this->currSchema['CONSTRAINT'][$Results[$i]['constraint_name']] = $sql;
881  $Results[$i]['processed'] = 1;
882  }
883 
884  /* CONSTRAINT: ALL OTHERS */
885  for ($i = 0; !empty($Results[$i]['constraint_name']); $i++) {
886  if (!empty($Results[$i]['processed']) && $Results[$i]['processed'] == 1) {
887  continue;
888  }
889 
890  $sql = "ALTER TABLE \"" . $Results[$i]['table_name'] . "\"";
891  $sql .= " ADD CONSTRAINT \"" . $Results[$i]['constraint_name'] . '"';
892  $sql .= " " . $Results[$i]['type'];
893  $sql .= " (" . $Results[$i]['constraint_key'] . ")";
894  if (!empty($Results[$i]['references_table'])) {
895  $sql .= " REFERENCES \"" . $Results[$i]['references_table'] . "\"";
896  $sql .= " (" . $Results[$i]['fk_constraint_key'] . ")";
897  }
898  $sql .= ";";
899  $this->currSchema['CONSTRAINT'][$Results[$i]['constraint_name']] = $sql;
900  $Results[$i]['processed'] = 1;
901  }
902  }
903 
907  function addIndexes()
908  {
909  $sql = "SELECT tablename AS \"table\", indexname AS index, indexdef AS define
910  FROM pg_indexes
911  INNER JOIN information_schema.tables ON table_name = tablename
912  AND table_type = 'BASE TABLE'
913  AND table_schema = 'public'
914  AND schemaname = 'public'
915  ORDER BY tablename,indexname;
916  ";
917  $stmt = __METHOD__;
918  $this->dbman->prepare($stmt, $sql);
919  $result = $this->dbman->execute($stmt);
920  while ($row = $this->dbman->fetchArray($result)) {
921  /* UNIQUE constraints also include indexes. */
922  if (empty($this->currSchema['CONSTRAINT'][$row['index']])) {
923  $this->currSchema['INDEX'][$row['table']][$row['index']] = str_replace("public.", "", $row['define']) . ";";
924  }
925  }
926  $this->dbman->freeResult($result);
927  }
928 
935  {
936  // prosrc
937  // proretset == setof
938  $sql = "SELECT proname AS name,
939  pronargs AS input_num,
940  proargnames AS input_names,
941  proargtypes AS input_type,
942  proargmodes AS input_modes,
943  proretset AS setof,
944  prorettype AS output_type
945  FROM pg_proc AS proc
946  INNER JOIN pg_language AS lang ON proc.prolang = lang.oid
947  WHERE lang.lanname = 'plpgsql'
948  ORDER BY proname;";
949  $stmt = __METHOD__;
950  $this->dbman->prepare($stmt, $sql);
951  $result = $this->dbman->execute($stmt);
952  while ($row = $this->dbman->fetchArray($result)) {
953  $sql = "CREATE or REPLACE function " . $row['proname'] . "()";
954  $sql .= ' RETURNS ' . "TBD" . ' AS $$';
955  $sql .= " " . $row['prosrc'];
956  $schema['FUNCTION'][$row['proname']] = $sql;
957  }
958  $this->dbman->freeResult($result);
959  return $schema;
960  }
961 
969  function writeArrayEntries($fout, $key, $value, $varname)
970  {
971  $varname .= '["' . str_replace('"', '\"', $key) . '"]';
972  if (!is_array($value)) {
973  $value = str_replace('"', '\"', $value);
974  fwrite($fout, "$varname = \"$value\";\n");
975  return;
976  }
977  foreach ($value as $k => $v) {
978  $this->writeArrayEntries($fout, $k, $v, $varname);
979  }
980  fwrite($fout, "\n");
981  }
982 
991  function exportSchema($filename = NULL)
992  {
993  if (empty($filename)) {
994  $filename = 'php://stdout';
995  }
996  $Schema = $this->getCurrSchema();
997  $fout = fopen($filename, "w");
998  if (!$fout) {
999  return ("Failed to write to $filename\n");
1000  }
1001  global $Name;
1002  fwrite($fout, "<?php\n");
1003  fwrite($fout, "/* This file is generated by " . $Name . " */\n");
1004  fwrite($fout, "/* Do not manually edit this file */\n\n");
1005  fwrite($fout, ' $Schema=array();' . "\n\n");
1006  foreach ($Schema as $K1 => $V1) {
1007  $this->writeArrayEntries($fout, $K1, $V1, ' $Schema');
1008  }
1009  fclose($fout);
1010  return false;
1011  }
1012 
1013 
1017  function makeFunctions()
1018  {
1019  print " Applying database functions\n";
1020  flush();
1021  /* *******************************************
1022  * uploadtree2path is a DB function that returns the non-artifact parents of an uploadtree_pk.
1023  * drop and recreate to change the return type.
1024  */
1025  $sql = 'drop function if exists uploadtree2path(integer);';
1026  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . '.uploadtree2path.drop');
1027 
1028  $sql = '
1029  CREATE function uploadtree2path(uploadtree_pk_in int) returns setof uploadtree as $$
1030  DECLARE
1031  UTrec uploadtree;
1032  UTpk integer;
1033  sql varchar;
1034  BEGIN
1035  UTpk := uploadtree_pk_in;
1036  WHILE UTpk > 0 LOOP
1037  sql := ' . "'" . 'select * from uploadtree where uploadtree_pk=' . "'" . ' || UTpk;
1038  execute sql into UTrec;
1039  IF ((UTrec.ufile_mode & (1<<28)) = 0) THEN RETURN NEXT UTrec; END IF;
1040  UTpk := UTrec.parent;
1041  END LOOP;
1042  RETURN;
1043  END;
1044  $$
1045  LANGUAGE plpgsql;
1046  ';
1047  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . '.uploadtree2path.create');
1048 
1049  /*
1050  * getItemParent is a DB function that returns the non-artifact parent of an uploadtree_pk.
1051  * drop and recreate to change the return type.
1052  */
1053  $sql = 'drop function if exists getItemParent(integer);';
1054  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . '.getItemParent.drop');
1055 
1056  $sql = '
1057  CREATE OR REPLACE FUNCTION getItemParent(itemId Integer) RETURNS Integer AS $$
1058  WITH RECURSIVE file_tree(uploadtree_pk, parent, jump, path, cycle) AS (
1059  SELECT ut.uploadtree_pk, ut.parent,
1060  true,
1061  ARRAY[ut.uploadtree_pk],
1062  false
1063  FROM uploadtree ut
1064  WHERE ut.uploadtree_pk = $1
1065  UNION ALL
1066  SELECT ut.uploadtree_pk, ut.parent,
1067  ut.ufile_mode & (1<<28) != 0,
1068  path || ut.uploadtree_pk,
1069  ut.uploadtree_pk = ANY(path)
1070  FROM uploadtree ut, file_tree ft
1071  WHERE ut.uploadtree_pk = ft.parent AND jump AND NOT cycle
1072  )
1073  SELECT uploadtree_pk from file_tree ft WHERE NOT jump
1074  $$
1075  LANGUAGE SQL
1076  STABLE
1077  RETURNS NULL ON NULL INPUT
1078  ';
1079  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . '.getItemParent.create');
1080  return;
1081  }
1082 
1087  {
1088  if (empty($this->schema['INHERITS'])) {
1089  return;
1090  }
1091  foreach ($this->schema['INHERITS'] as $table => $fromTable) {
1092  if (empty($table)) {
1093  continue;
1094  }
1095  if (!$this->dbman->existsTable($table) && $this->dbman->existsTable($fromTable)) {
1096  $sql = "CREATE TABLE \"$table\" () INHERITS (\"$fromTable\")";
1097  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . $table);
1098  }
1099  }
1100  }
1101 
1102  // MakeFunctions()
1103 }
1104 
1105 if (empty($dbManager) || !($dbManager instanceof DbManager)) {
1106  $logLevel = Logger::INFO;
1107  $logger = new Logger(__FILE__);
1108  $logger->pushHandler(new ErrorLogHandler(ErrorLogHandler::OPERATING_SYSTEM, $logLevel));
1109  $dbManager = new ModernDbManager($logger);
1110 }
1111 global $PG_CONN;
1112 if (empty($PG_CONN)) {
1113  $sysconfdir = getenv('SYSCONFDIR');
1114  if (empty($sysconfdir)) {
1115  $sysconfdir = "/usr/local/etc/fossology";
1116  }
1117  $foConf = $sysconfdir . "/fossology.conf";
1118  if (file_exists($foConf)) {
1119  require_once(__DIR__ . "/common-db.php");
1120  $PG_CONN = DBconnect($sysconfdir);
1121  $GLOBALS['PG_CONN'] = $PG_CONN;
1122  if (!isset($GLOBALS['SysConf']) && isset($SysConf)) {
1123  $GLOBALS['SysConf'] = $SysConf;
1124  }
1125  }
1126 }
1127 /* simulate the old functions*/
1128 $libschema = new fo_libschema($dbManager);
1136 function ApplySchema($Filename = NULL, $Debug = false, $Catalog = 'fossology')
1137 {
1138  global $libschema;
1139  return $libschema->applySchema($Filename, $Debug, $Catalog);
1140 }
1141 
1145 function GetSchema()
1146 {
1147  global $libschema;
1148  return $libschema->getCurrSchema();
1149 }
1150 
1157 function ExportSchema($filename = NULL)
1158 {
1159  global $libschema;
1160  return $libschema->exportSchema($filename);
1161 }
1162 
1166 function MakeFunctions($Debug)
1167 {
1168  global $libschema;
1169  $libschema->makeFunctions($Debug);
1170 }
int debug
Definition: buckets.c:57
Class to handle database schema.
Definition: libschema.php:29
writeArrayEntries($fout, $key, $value, $varname)
Definition: libschema.php:969
applyTables($inherits=false)
Add tables/columns (dependent on sequences for default values)
Definition: libschema.php:284
addFunctions($schema)
Definition: libschema.php:934
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:991
__construct(DbManager &$dbManager)
Definition: libschema.php:58
addViews($viewowner)
Definition: libschema.php:691
addInheritedRelations()
Definition: libschema.php:600
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:551
dropViews($catalog)
Delete views.
Definition: libschema.php:522
updateSequences()
Add sequences.
Definition: libschema.php:261
addSequences($referencedSequencesInTableColumns)
Definition: libschema.php:708
dropConstraints()
Delete constraints.
Definition: libschema.php:389
makeFunctions()
Create any required DB functions.
Definition: libschema.php:1017
getCurrSchema()
Load the schema from the db into an array.
Definition: libschema.php:571
setDriver(Driver &$dbDriver)
Definition: libschema.php:67
applyInheritedRelations()
Definition: libschema.php:1086
ReportCachePurgeAll()
Purge all records from the report cache.
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:1157
GetSchema()
Load the schema from the db into an array.
Definition: libschema.php:1145
MakeFunctions($Debug)
Create any required DB functions.
Definition: libschema.php:1166
ApplySchema($Filename=NULL, $Debug=false, $Catalog='fossology')
Make schema match $Filename. This is a single transaction.
Definition: libschema.php:1136
foreach($Options as $Option=> $OptVal) if(0==$reference_flag &&0==$nomos_flag) $PG_CONN