FOSSology  3.2.0rc1
Open Source License Compliance by Open Source Software
libschema.php
Go to the documentation of this file.
1 <?php
2 /*
3  Copyright (C) 2008-2014 Hewlett-Packard Development Company, L.P.
4  Copyright (C) 2014-2015, 2018 Siemens AG
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Lesser General Public
8  License version 2.1 as published by the Free Software Foundation.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  Lesser General Public License for more details.
14 
15  You should have received a copy of the GNU Lesser General Public License
16  along with this library; if not, write to the Free Software Foundation, Inc.0
17  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18  */
19 
26 require_once(__DIR__ . '/../../vendor/autoload.php');
27 
33 use Monolog\Logger;
34 
40 {
45  public $debug = false;
46 
51  private $dbman;
52 
57  private $schema = array();
58 
63  private $currSchema = array();
64 
69  function __construct(DbManager &$dbManager)
70  {
71  $this->dbman = $dbManager;
72  }
73 
78  function setDriver(Driver &$dbDriver)
79  {
80  $this->dbman->setDriver($dbDriver);
81  }
82 
83 
90  function applyOrEchoOnce($sql, $stmt = '')
91  {
92  if ($this->debug) {
93  print ("$sql\n");
94  } else {
95  return $this->dbman->queryOnce($sql, $stmt);
96  }
97  }
98 
99 
108  function applySchema($filename = NULL, $debug = false, $catalog = 'fossology', $migrateColumns = array())
109  {
110  global $PG_CONN;
111 
112  // first check to make sure we don't already have the plpgsql language installed
113  $sql_statement = "select lanname from pg_language where lanname = 'plpgsql'";
114 
115  $result = pg_query($PG_CONN, $sql_statement);
116  if (!$result) {
117  throw new Exception("Could not check the database for plpgsql language");
118  }
119 
120  $plpgsql_already_installed = false;
121  if ( pg_fetch_row($result) ) {
122  $plpgsql_already_installed = true;
123  }
124 
125  // then create language plpgsql if not already created
126  if ($plpgsql_already_installed == false) {
127  $sql_statement = "CREATE LANGUAGE plpgsql";
128  $result = pg_query($PG_CONN, $sql_statement);
129  if (!$result) {
130  throw new Exception("Could not create plpgsql language in the database");
131  }
132  }
133 
134  $this->debug = $debug;
135  if (!file_exists($filename)) {
136  $errMsg = "$filename does not exist.";
137  return $errMsg;
138  }
139  $Schema = array(); /* will be filled in next line */
140  require($filename); /* this cause Fatal Error if the file does not exist. */
141  $this->schema = $Schema;
142 
143  /* Very basic sanity check (so we don't delete everything!) */
144  if ((count($this->schema['TABLE']) < 5) || (count($this->schema['SEQUENCE']) < 5)
145  || (count($this->schema['INDEX']) < 5) || (count($this->schema['CONSTRAINT']) < 5)
146  ) {
147  $errMsg = "Schema from '$filename' appears invalid.";
148  return $errMsg;
149  }
150 
151  if (!$debug) {
152  $result = $this->dbman->getSingleRow("show statement_timeout", array(), $stmt = __METHOD__ . '.getTimeout');
153  $statementTimeout = $result['statement_timeout'];
154  $this->dbman->queryOnce("SET statement_timeout = 0", $stmt = __METHOD__ . '.setTimeout');
155  }
156 
157  $this->applyOrEchoOnce('BEGIN');
158  $this->getCurrSchema();
159  $errlev = error_reporting(E_ERROR | E_WARNING | E_PARSE);
160  $this->applySequences();
161  $this->applyTables();
162  $this->applyInheritedRelations();
163  $this->applyTables(true);
164  $this->updateSequences();
165  $this->applyViews();
166  $this->dropConstraints();
167  /* Reload current since the CASCADE may have changed things */
168  $this->getCurrSchema(); /* constraints and indexes are linked, recheck */
169  $this->dropIndexes();
170  $this->applyIndexes();
171  $this->applyConstraints();
172  error_reporting($errlev); /* return to previous error reporting level */
173  $this->makeFunctions();
174  $this->applyClusters();
175  /* Reload current since CASCADE during migration may have changed things */
176  $this->getCurrSchema();
177  $this->dropViews($catalog);
178  foreach ($this->currSchema['TABLE'] as $table => $columns) {
179  $skipColumns = array_key_exists($table, $migrateColumns) ? $migrateColumns[$table] : array();
180  $dropColumns = array_diff(array_keys($columns), $skipColumns);
181  $this->dropColumnsFromTable($dropColumns, $table);
182  }
183  $this->applyOrEchoOnce('COMMIT');
184  flush();
186  if (!$debug) {
187  $this->dbman->getSingleRow("SET statement_timeout = $statementTimeout", array(), $stmt = __METHOD__ . '.resetTimeout');
188  print "DB schema has been updated for $catalog.\n";
189  } else {
190  print "These queries could update DB schema for $catalog.\n";
191  }
192  return false;
193  }
194 
201  function applySequences()
202  {
203  if (empty($this->schema['SEQUENCE'])) {
204  return;
205  }
206  foreach ($this->schema['SEQUENCE'] as $name => $import) {
207  if (empty($name)) {
208  continue;
209  }
210 
211  if (!array_key_exists('SEQUENCE', $this->currSchema)
212  || !array_key_exists($name, $this->currSchema['SEQUENCE'])) {
213  $createSql = is_string($import) ? $import : $import['CREATE'];
214  $this->applyOrEchoOnce($createSql, $stmt = __METHOD__ . "." . $name . ".CREATE");
215  }
216  }
217  }
224  function applyClusters()
225  {
226  if (empty($this->schema['CLUSTER'])) {
227  return;
228  }
229  foreach ($this->schema['CLUSTER'] as $name => $sql) {
230  if (empty($name)) {
231  continue;
232  }
233 
234  if (!array_key_exists('CLUSTER', $this->currSchema)
235  || !array_key_exists($name, $this->currSchema['CLUSTER'])) {
236  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . "." . $name . ".CREATE");
237  }
238  }
239  }
240 
248  function updateSequences()
249  {
250  if (empty($this->schema['SEQUENCE'])) {
251  return;
252  }
253  foreach ($this->schema['SEQUENCE'] as $name => $import) {
254  if (empty($name)) {
255  continue;
256  }
257 
258  if (is_array($import) && array_key_exists('UPDATE', $import)) {
259  $this->applyOrEchoOnce($import['UPDATE'], $stmt = __METHOD__ . "." . $name);
260  }
261  }
262  }
263 
270  function applyTables($inherits=false)
271  {
272  if (empty($this->schema['TABLE'])) {
273  return;
274  }
275  foreach ($this->schema['TABLE'] as $table => $columns) {
276  if (empty($table) || $inherits^array_key_exists($table,$this->schema['INHERITS']) ) {
277  continue;
278  }
279  if (!DB_TableExists($table)) {
280  $sql = "CREATE TABLE IF NOT EXISTS \"$table\" ()";
281  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . $table);
282  }
283  foreach ($columns as $column => $modification) {
284  if ($this->currSchema['TABLE'][$table][$column]['ADD'] != $modification['ADD']) {
285  $rename = "";
286  if (DB_ColExists($table, $column)) {
287  /* The column exists, but it looks different!
288  Solution: Delete the column! */
289  $rename = $column . '_old';
290  $sql = "ALTER TABLE \"$table\" RENAME COLUMN \"$column\" TO \"$rename\"";
291  $this->applyOrEchoOnce($sql);
292  }
293 
294  $sql = $modification['ADD'];
295  if ($this->debug) {
296  print "$sql\n";
297  } else {
298  // Add the new column which sets the default value
299  $this->dbman->queryOnce($sql);
300  }
301  if (!empty($rename)) {
302  /* copy over the old data */
303  $this->applyOrEchoOnce($sql = "UPDATE \"$table\" SET \"$column\" = \"$rename\"");
304  $this->applyOrEchoOnce($sql = "ALTER TABLE \"$table\" DROP COLUMN \"$rename\"");
305  }
306  }
307  if ($this->currSchema['TABLE'][$table][$column]['ALTER'] != $modification['ALTER'] && isset($modification['ALTER'])) {
308  $sql = $modification['ALTER'];
309  if ($this->debug) {
310  print "$sql\n";
311  } else if (!empty ($sql)) {
312  $this->dbman->queryOnce($sql);
313  }
314  }
315  if ($this->currSchema['TABLE'][$table][$column]['DESC'] != $modification['DESC']) {
316  $sql = empty($modification['DESC']) ? "COMMENT ON COLUMN \"$table\".\"$column\" IS ''" : $modification['DESC'];
317  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . "$table.$column.comment");
318  }
319  }
320  }
321  }
322 
326  function applyViews()
327  {
328  if (empty($this->schema['VIEW'])) {
329  return;
330  }
331  foreach ($this->schema['VIEW'] as $name => $sql) {
332  if (empty($name) || $this->currSchema['VIEW'][$name] == $sql) {
333  continue;
334  }
335  if (!empty($this->currSchema['VIEW'][$name])) {
336  $sqlDropView = "DROP VIEW IF EXISTS $name";
337  $this->applyOrEchoOnce($sqlDropView);
338  }
339  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . $name);
340  }
341  }
342 
348  function dropConstraints()
349  {
350  if (empty($this->currSchema['CONSTRAINT'])) {
351  return;
352  }
353  foreach ($this->currSchema['CONSTRAINT'] as $name => $sql) {
354  // skip if constraint name is empty or does not exist
355  if (empty($name) || ($this->schema['CONSTRAINT'][$name] == $sql)
356  || (DB_ConstraintExists($name) == false)) {
357  continue;
358  }
359 
360  /* Only process tables that I know about */
361  $table = preg_replace("/^ALTER TABLE \"(.*)\" ADD CONSTRAINT.*/", '${1}', $sql);
362  $TableFk = preg_replace("/^.*FOREIGN KEY .* REFERENCES \"(.*)\" \(.*/", '${1}', $sql);
363  if ($TableFk == $sql) {
364  $TableFk = $table;
365  }
366  /* If I don't know the primary or foreign table... */
367  if (empty($this->schema['TABLE'][$table]) && empty($this->schema['TABLE'][$TableFk])) {
368  continue;
369  }
370  $sql = "ALTER TABLE \"$table\" DROP CONSTRAINT \"$name\" CASCADE";
371  $this->applyOrEchoOnce($sql);
372  }
373  }
374 
378  function dropIndexes()
379  {
380  if (empty($this->currSchema['INDEX'])) {
381  return;
382  }
383  foreach ($this->currSchema['INDEX'] as $table => $IndexInfo) {
384  if (empty($table) || (empty($this->schema['TABLE'][$table]) && empty($this->schema['INHERITS'][$table]))) {
385  continue;
386  }
387  foreach ($IndexInfo as $name => $sql) {
388  if (empty($name) || $this->schema['INDEX'][$table][$name] == $sql) {
389  continue;
390  }
391  $sql = "DROP INDEX \"$name\"";
392  $this->applyOrEchoOnce($sql);
393  }
394  }
395  }
396 
400  function applyIndexes()
401  {
402  if (empty($this->schema['INDEX'])) {
403  return;
404  }
405  foreach ($this->schema['INDEX'] as $table => $indexInfo) {
406  if (empty($table)) {
407  continue;
408  }
409  if (!array_key_exists($table, $this->schema["TABLE"]) && !array_key_exists($table, $this->schema['INHERITS'])) {
410  echo "skipping orphan table: $table\n";
411  continue;
412  }
413  foreach ($indexInfo as $name => $sql) {
414  if (empty($name) || $this->currSchema['INDEX'][$table][$name] == $sql) {
415  continue;
416  }
417  $this->applyOrEchoOnce($sql);
418  $sql = "REINDEX INDEX \"$name\"";
419  $this->applyOrEchoOnce($sql);
420  }
421  }
422  }
423 
427  function applyConstraints()
428  {
429  $this->currSchema = $this->getCurrSchema(); /* constraints and indexes are linked, recheck */
430  if (empty($this->schema['CONSTRAINT'])) {
431  return;
432  }
433  /* Constraints must be added in the correct order! */
434  $orderedConstraints = array('primary' => array(), 'unique' => array(), 'foreign' => array(), 'other' => array());
435  foreach ($this->schema['CONSTRAINT'] as $Name => $sql) {
436  if (empty($Name) || $this->currSchema['CONSTRAINT'][$Name] == $sql) {
437  continue;
438  }
439  if (preg_match("/PRIMARY KEY/", $sql)) {
440  $orderedConstraints['primary'][] = $sql;
441  } elseif (preg_match("/UNIQUE/", $sql)) {
442  $orderedConstraints['unique'][] = $sql;
443  } elseif (preg_match("/FOREIGN KEY/", $sql)) {
444  $orderedConstraints['foreign'][] = $sql;
445  } else {
446  $orderedConstraints['other'][] = $sql;
447  }
448  }
449  foreach ($orderedConstraints as $type => $constraints) {
450  foreach ($constraints as $sql) {
451  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . ".constraint.$type");
452  }
453  }
454  }
455 
465  function dropViews($catalog)
466  {
467  $sql = "SELECT view_name,table_name,column_name
468  FROM information_schema.view_column_usage
469  WHERE table_catalog='$catalog'
470  ORDER BY view_name,table_name,column_name";
471  $stmt = __METHOD__;
472  $this->dbman->prepare($stmt, $sql);
473  $result = $this->dbman->execute($stmt);
474  while ($row = $this->dbman->fetchArray($result)) {
475  $View = $row['view_name'];
476  $table = $row['table_name'];
477  if (empty($this->schema['TABLE'][$table])) {
478  continue;
479  }
480  $column = $row['column_name'];
481  if (empty($this->schema['TABLE'][$table][$column])) {
482  $sql = "DROP VIEW \"$View\";";
483  $this->applyOrEchoOnce($sql);
484  }
485  }
486  $result = $this->dbman->freeResult($result);
487  }
488 
494  function dropColumnsFromTable($columns, $table)
495  {
496  if (empty($table) || empty($this->schema['TABLE'][$table])) {
497  return;
498  }
499  foreach ($columns as $column) {
500  if (empty($column)) {
501  continue;
502  }
503  if (empty($this->schema['TABLE'][$table][$column])) {
504  $sql = "ALTER TABLE \"$table\" DROP COLUMN \"$column\";";
505  $this->applyOrEchoOnce($sql);
506  }
507  }
508  }
509 
510 
514  function getCurrSchema()
515  {
516  global $SysConf;
517  $this->currSchema = array();
518  $this->addInheritedRelations();
519  $referencedSequencesInTableColumns = $this->addTables();
520  $this->addViews($viewowner = $SysConf['DBCONF']['user']);
521  $this->addSequences($referencedSequencesInTableColumns);
522  $this->addConstraints();
523  $this->addIndexes();
524  unset($this->currSchema['TABLEID']);
525  return $this->currSchema;
526  }
527 
532  {
533  $sql = "SELECT class.relname AS table, daddy.relname AS inherits_from
534  FROM pg_class AS class
535  INNER JOIN pg_catalog.pg_inherits ON pg_inherits.inhrelid = class.oid
536  INNER JOIN pg_class daddy ON pg_inherits.inhparent = daddy.oid";
537  $this->dbman->prepare($stmt=__METHOD__, $sql);
538  $res = $this->dbman->execute($stmt);
539  $relations = array();
540  while ($row=$this->dbman->fetchArray($res)) {
541  $relations[$row['table']] = $row['inherits_from'];
542  }
543  $this->dbman->freeResult($res);
544  $this->currSchema['INHERITS'] = $relations;
545  }
546 
550  function addTables()
551  {
552  $referencedSequencesInTableColumns = array();
553 
554  $sql = "SELECT
555  table_name AS table, ordinal_position AS ordinal, column_name,
556  udt_name AS type, character_maximum_length AS modifier,
557  CASE is_nullable WHEN 'YES' THEN false WHEN 'NO' THEN true END AS notnull,
558  column_default AS default,
559  col_description(table_name::regclass, ordinal_position) AS description
560  FROM information_schema.columns
561  WHERE table_schema = 'public'
562  ORDER BY table_name, ordinal_position;";
563  $stmt = __METHOD__;
564  $this->dbman->prepare($stmt, $sql);
565  $result = $this->dbman->execute($stmt);
566  while ($R = $this->dbman->fetchArray($result)) {
567  $Table = $R['table'];
568  $Column = $R['column_name'];
569  if (array_key_exists($Table, $this->currSchema['INHERITS'])) {
570  $this->currSchema['TABLEID'][$Table][$R['ordinal']] = $Column;
571  continue;
572  }
573  $Type = $R['type'];
574  if ($Type == 'bpchar') {
575  $Type = "char";
576  }
577  if ($R['modifier'] > 0) {
578  $Type .= '(' . $R['modifier'] . ')';
579  }
580  $Desc = str_replace("'", "''", $R['description']);
581  $this->currSchema['TABLEID'][$Table][$R['ordinal']] = $Column;
582  if (!empty($Desc)) {
583  $this->currSchema['TABLE'][$Table][$Column]['DESC'] = "COMMENT ON COLUMN \"$Table\".\"$Column\" IS '$Desc'";
584  } else {
585  $this->currSchema['TABLE'][$Table][$Column]['DESC'] = "";
586  }
587  $this->currSchema['TABLE'][$Table][$Column]['ADD'] = "ALTER TABLE \"$Table\" ADD COLUMN \"$Column\" $Type";
588  $this->currSchema['TABLE'][$Table][$Column]['ALTER'] = "ALTER TABLE \"$Table\"";
589  $Alter = "ALTER COLUMN \"$Column\"";
590  if ($R['notnull'] == 't') {
591  $this->currSchema['TABLE'][$Table][$Column]['ALTER'] .= " $Alter SET NOT NULL";
592  } else {
593  $this->currSchema['TABLE'][$Table][$Column]['ALTER'] .= " $Alter DROP NOT NULL";
594  }
595  if ($R['default'] != '') {
596  $R['default'] = preg_replace("/::bpchar/", "::char", $R['default']);
597  $R['default'] = str_replace("public.", "", $R['default']);
598  $this->currSchema['TABLE'][$Table][$Column]['ALTER'] .= ", $Alter SET DEFAULT " . $R['default'];
599  $this->currSchema['TABLE'][$Table][$Column]['ADD'] .= " DEFAULT " . $R['default'];
600 
601  $rgx = "/nextval\('([a-z_]*)'.*\)/";
602  $matches = array();
603  if (preg_match($rgx, $R['default'], $matches)) {
604  $sequence = $matches[1];
605  $referencedSequencesInTableColumns[$sequence] = array("table" => $Table, "column" => $Column);
606  }
607  }
608  }
609  $this->dbman->freeResult($result);
610 
611  return $referencedSequencesInTableColumns;
612  }
613 
618  function addViews($viewowner)
619  {
620  $sql = "SELECT viewname,definition FROM pg_views WHERE viewowner = $1";
621  $stmt = __METHOD__;
622  $this->dbman->prepare($stmt, $sql);
623  $result = $this->dbman->execute($stmt, array($viewowner));
624  while ($row = $this->dbman->fetchArray($result)) {
625  $sql = "CREATE VIEW \"" . $row['viewname'] . "\" AS " . $row['definition'];
626  $this->currSchema['VIEW'][$row['viewname']] = $sql;
627  }
628  $this->dbman->freeResult($result);
629  }
630 
635  function addSequences($referencedSequencesInTableColumns)
636  {
637  $sql = "SELECT relname
638  FROM pg_class
639  WHERE relkind = 'S'
640  AND relnamespace IN (
641  SELECT oid FROM pg_namespace WHERE nspname NOT LIKE 'pg_%' AND nspname != 'information_schema'
642  )";
643 
644  $stmt = __METHOD__;
645  $this->dbman->prepare($stmt, $sql);
646  $result = $this->dbman->execute($stmt);
647 
648  while ($row = $this->dbman->fetchArray($result)) {
649  $sequence = $row['relname'];
650  if (empty($sequence)) {
651  continue;
652  }
653 
654  $sqlCreate = "CREATE SEQUENCE \"" . $sequence . "\"";
655  $this->currSchema['SEQUENCE'][$sequence]['CREATE'] = $sqlCreate;
656 
657  if (array_key_exists($sequence, $referencedSequencesInTableColumns)) {
658  $table = $referencedSequencesInTableColumns[$sequence]['table'];
659  $column = $referencedSequencesInTableColumns[$sequence]['column'];
660 
661  $sqlUpdate = "SELECT setval('$sequence',(SELECT greatest(1,max($column)) val FROM $table))";
662  $this->currSchema['SEQUENCE'][$sequence]['UPDATE'] = $sqlUpdate;
663  }
664  }
665 
666  $this->dbman->freeResult($result);
667  }
668 
672  function addConstraints()
673  {
674  $sql = "SELECT c.conname AS constraint_name,
675  CASE c.contype
676  WHEN 'c' THEN 'CHECK'
677  WHEN 'f' THEN 'FOREIGN KEY'
678  WHEN 'p' THEN 'PRIMARY KEY'
679  WHEN 'u' THEN 'UNIQUE'
680  END AS type,
681  CASE WHEN c.condeferrable = 'f' THEN 0 ELSE 1 END AS is_deferrable,
682  CASE WHEN c.condeferred = 'f' THEN 0 ELSE 1 END AS is_deferred,
683  t.relname AS table_name, array_to_string(c.conkey, ' ') AS constraint_key,
684  CASE confupdtype
685  WHEN 'a' THEN 'NO ACTION'
686  WHEN 'r' THEN 'RESTRICT'
687  WHEN 'c' THEN 'CASCADE'
688  WHEN 'n' THEN 'SET NULL'
689  WHEN 'd' THEN 'SET DEFAULT'
690  END AS on_update,
691  CASE confdeltype
692  WHEN 'a' THEN 'NO ACTION'
693  WHEN 'r' THEN 'RESTRICT'
694  WHEN 'c' THEN 'CASCADE'
695  WHEN 'n' THEN 'SET NULL'
696  WHEN 'd' THEN 'SET DEFAULT' END AS on_delete,
697  CASE confmatchtype
698  WHEN 'u' THEN 'UNSPECIFIED'
699  WHEN 'f' THEN 'FULL'
700  WHEN 'p' THEN 'PARTIAL'
701  END AS match_type,
702  t2.relname AS references_table,
703  array_to_string(c.confkey, ' ') AS fk_constraint_key
704  FROM pg_constraint AS c
705  LEFT JOIN pg_class AS t ON c.conrelid = t.oid
706  INNER JOIN information_schema.tables AS tab ON t.relname = tab.table_name
707  LEFT JOIN pg_class AS t2 ON c.confrelid = t2.oid
708  ORDER BY constraint_name,table_name
709  ";
710  $stmt = __METHOD__;
711  $this->dbman->prepare($stmt, $sql);
712  $result = $this->dbman->execute($stmt);
713  $Results = $this->dbman->fetchAll($result);
714  $this->dbman->freeResult($result);
715  /* Constraints use indexes into columns. Covert those to column names. */
716  for ($i = 0; !empty($Results[$i]['constraint_name']); $i++) {
717  $Key = "";
718  $Keys = explode(" ", $Results[$i]['constraint_key']);
719  foreach ($Keys as $K) {
720  if (empty($K)) {
721  continue;
722  }
723  if (!empty($Key)) {
724  $Key .= ",";
725  }
726  $Key .= '"' . $this->currSchema['TABLEID'][$Results[$i]['table_name']][$K] . '"';
727  }
728  $Results[$i]['constraint_key'] = $Key;
729  $Key = "";
730  $Keys = explode(" ", $Results[$i]['fk_constraint_key']);
731  foreach ($Keys as $K) {
732  if (empty($K)) {
733  continue;
734  }
735  if (!empty($Key)) {
736  $Key .= ",";
737  }
738  $Key .= '"' . $this->currSchema['TABLEID'][$Results[$i]['references_table']][$K] . '"';
739  }
740  $Results[$i]['fk_constraint_key'] = $Key;
741  }
742  /* Save the constraint */
743  /* There are different types of constraints that must be stored in order */
744  /* CONSTRAINT: PRIMARY KEY */
745  for ($i = 0; !empty($Results[$i]['constraint_name']); $i++) {
746  if ($Results[$i]['type'] != 'PRIMARY KEY') {
747  continue;
748  }
749  $sql = "ALTER TABLE \"" . $Results[$i]['table_name'] . "\"";
750  $sql .= " ADD CONSTRAINT \"" . $Results[$i]['constraint_name'] . '"';
751  $sql .= " " . $Results[$i]['type'];
752  $sql .= " (" . $Results[$i]['constraint_key'] . ")";
753  if (!empty($Results[$i]['references_table'])) {
754  $sql .= " REFERENCES \"" . $Results[$i]['references_table'] . "\"";
755  $sql .= " (" . $Results[$i]['fk_constraint_key'] . ")";
756  }
757  $sql .= ";";
758  $this->currSchema['CONSTRAINT'][$Results[$i]['constraint_name']] = $sql;
759  $Results[$i]['processed'] = 1;
760  }
761  /* CONSTRAINT: UNIQUE */
762  for ($i = 0; !empty($Results[$i]['constraint_name']); $i++) {
763  if ($Results[$i]['type'] != 'UNIQUE') {
764  continue;
765  }
766  $sql = "ALTER TABLE \"" . $Results[$i]['table_name'] . "\"";
767  $sql .= " ADD CONSTRAINT \"" . $Results[$i]['constraint_name'] . '"';
768  $sql .= " " . $Results[$i]['type'];
769  $sql .= " (" . $Results[$i]['constraint_key'] . ")";
770  if (!empty($Results[$i]['references_table'])) {
771  $sql .= " REFERENCES \"" . $Results[$i]['references_table'] . "\"";
772  $sql .= " (" . $Results[$i]['fk_constraint_key'] . ")";
773  }
774  $sql .= ";";
775  $this->currSchema['CONSTRAINT'][$Results[$i]['constraint_name']] = $sql;
776  $Results[$i]['processed'] = 1;
777  }
778 
779  /* CONSTRAINT: FOREIGN KEY */
780  for ($i = 0; !empty($Results[$i]['constraint_name']); $i++) {
781  if ($Results[$i]['type'] != 'FOREIGN KEY') {
782  continue;
783  }
784  $sql = "ALTER TABLE \"" . $Results[$i]['table_name'] . "\"";
785  $sql .= " ADD CONSTRAINT \"" . $Results[$i]['constraint_name'] . '"';
786  $sql .= " " . $Results[$i]['type'];
787  $sql .= " (" . $Results[$i]['constraint_key'] . ")";
788  if (!empty($Results[$i]['references_table'])) {
789  $sql .= " REFERENCES \"" . $Results[$i]['references_table'] . "\"";
790  $sql .= " (" . $Results[$i]['fk_constraint_key'] . ")";
791  }
792 
793  if (!empty($Results[$i]['on_update'])) {
794  $sql .= " ON UPDATE " . $Results[$i]['on_update'];
795  }
796  if (!empty($Results[$i]['on_delete'])) {
797  $sql .= " ON DELETE " . $Results[$i]['on_delete'];
798  }
799 
800  $sql .= ";";
801  $this->currSchema['CONSTRAINT'][$Results[$i]['constraint_name']] = $sql;
802  $Results[$i]['processed'] = 1;
803  }
804 
805  /* CONSTRAINT: ALL OTHERS */
806  for ($i = 0; !empty($Results[$i]['constraint_name']); $i++) {
807  if (!empty($Results[$i]['processed']) && $Results[$i]['processed'] == 1) {
808  continue;
809  }
810 
811  $sql = "ALTER TABLE \"" . $Results[$i]['table_name'] . "\"";
812  $sql .= " ADD CONSTRAINT \"" . $Results[$i]['constraint_name'] . '"';
813  $sql .= " " . $Results[$i]['type'];
814  $sql .= " (" . $Results[$i]['constraint_key'] . ")";
815  if (!empty($Results[$i]['references_table'])) {
816  $sql .= " REFERENCES \"" . $Results[$i]['references_table'] . "\"";
817  $sql .= " (" . $Results[$i]['fk_constraint_key'] . ")";
818  }
819  $sql .= ";";
820  $this->currSchema['CONSTRAINT'][$Results[$i]['constraint_name']] = $sql;
821  $Results[$i]['processed'] = 1;
822  }
823  }
824 
828  function addIndexes()
829  {
830  $sql = "SELECT tablename AS table, indexname AS index, indexdef AS define
831  FROM pg_indexes
832  INNER JOIN information_schema.tables ON table_name = tablename
833  AND table_type = 'BASE TABLE'
834  AND table_schema = 'public'
835  AND schemaname = 'public'
836  ORDER BY tablename,indexname;
837  ";
838  $stmt = __METHOD__;
839  $this->dbman->prepare($stmt, $sql);
840  $result = $this->dbman->execute($stmt);
841  while ($row = $this->dbman->fetchArray($result)) {
842  /* UNIQUE constraints also include indexes. */
843  if (empty($this->currSchema['CONSTRAINT'][$row['index']])) {
844  $this->currSchema['INDEX'][$row['table']][$row['index']] = str_replace("public.", "", $row['define']) . ";";
845  }
846  }
847  $this->dbman->freeResult($result);
848  }
849 
856  {
857  // prosrc
858  // proretset == setof
859  $sql = "SELECT proname AS name,
860  pronargs AS input_num,
861  proargnames AS input_names,
862  proargtypes AS input_type,
863  proargmodes AS input_modes,
864  proretset AS setof,
865  prorettype AS output_type
866  FROM pg_proc AS proc
867  INNER JOIN pg_language AS lang ON proc.prolang = lang.oid
868  WHERE lang.lanname = 'plpgsql'
869  ORDER BY proname;";
870  $stmt = __METHOD__;
871  $this->dbman->prepare($stmt, $sql);
872  $result = $this->dbman->execute($stmt);
873  while ($row = $this->dbman->fetchArray($result)) {
874  $sql = "CREATE or REPLACE function " . $row['proname'] . "()";
875  $sql .= ' RETURNS ' . "TBD" . ' AS $$';
876  $sql .= " " . $row['prosrc'];
877  $schema['FUNCTION'][$row['proname']] = $sql;
878  }
879  $this->dbman->freeResult($result);
880  return $schema;
881  }
882 
890  function writeArrayEntries($fout, $key, $value, $varname)
891  {
892  $varname .= '["' . str_replace('"', '\"', $key) . '"]';
893  if (!is_array($value)) {
894  $value = str_replace('"', '\"', $value);
895  fwrite($fout, "$varname = \"$value\";\n");
896  return;
897  }
898  foreach ($value as $k => $v) {
899  $this->writeArrayEntries($fout, $k, $v, $varname);
900  }
901  fwrite($fout, "\n");
902  }
903 
912  function exportSchema($filename = NULL)
913  {
914  global $PG_CONN;
915 
916  /* set driver */
917  $dbDriver = $this->dbman->getDriver();
918  if (empty($dbDriver)) {
919  $this->dbman->setDriver(new Postgres($PG_CONN));
920  }
921 
922  if (empty($filename)) {
923  $filename = stdout;
924  }
925  $Schema = $this->getCurrSchema();
926  $fout = fopen($filename, "w");
927  if (!$fout) {
928  return ("Failed to write to $filename\n");
929  }
930  global $Name;
931  fwrite($fout, "<?php\n");
932  fwrite($fout, "/* This file is generated by " . $Name . " */\n");
933  fwrite($fout, "/* Do not manually edit this file */\n\n");
934  fwrite($fout, ' $Schema=array();' . "\n\n");
935  foreach ($Schema as $K1 => $V1) {
936  $this->writeArrayEntries($fout, $K1, $V1, ' $Schema');
937  }
938  fclose($fout);
939  return false;
940  }
941 
942 
946  function makeFunctions()
947  {
948  print " Applying database functions\n";
949  flush();
950  /* *******************************************
951  * uploadtree2path is a DB function that returns the non-artifact parents of an uploadtree_pk.
952  * drop and recreate to change the return type.
953  */
954  $sql = 'drop function if exists uploadtree2path(integer);';
955  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . '.uploadtree2path.drop');
956 
957  $sql = '
958  CREATE function uploadtree2path(uploadtree_pk_in int) returns setof uploadtree as $$
959  DECLARE
960  UTrec uploadtree;
961  UTpk integer;
962  sql varchar;
963  BEGIN
964  UTpk := uploadtree_pk_in;
965  WHILE UTpk > 0 LOOP
966  sql := ' . "'" . 'select * from uploadtree where uploadtree_pk=' . "'" . ' || UTpk;
967  execute sql into UTrec;
968  IF ((UTrec.ufile_mode & (1<<28)) = 0) THEN RETURN NEXT UTrec; END IF;
969  UTpk := UTrec.parent;
970  END LOOP;
971  RETURN;
972  END;
973  $$
974  LANGUAGE plpgsql;
975  ';
976  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . '.uploadtree2path.create');
977 
978  /*
979  * getItemParent is a DB function that returns the non-artifact parent of an uploadtree_pk.
980  * drop and recreate to change the return type.
981  */
982  $sql = 'drop function if exists getItemParent(integer);';
983  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . '.getItemParent.drop');
984 
985  $sql = '
986  CREATE OR REPLACE FUNCTION getItemParent(itemId Integer) RETURNS Integer AS $$
987  WITH RECURSIVE file_tree(uploadtree_pk, parent, jump, path, cycle) AS (
988  SELECT ut.uploadtree_pk, ut.parent,
989  true,
990  ARRAY[ut.uploadtree_pk],
991  false
992  FROM uploadtree ut
993  WHERE ut.uploadtree_pk = $1
994  UNION ALL
995  SELECT ut.uploadtree_pk, ut.parent,
996  ut.ufile_mode & (1<<28) != 0,
997  path || ut.uploadtree_pk,
998  ut.uploadtree_pk = ANY(path)
999  FROM uploadtree ut, file_tree ft
1000  WHERE ut.uploadtree_pk = ft.parent AND jump AND NOT cycle
1001  )
1002  SELECT uploadtree_pk from file_tree ft WHERE NOT jump
1003  $$
1004  LANGUAGE SQL
1005  STABLE
1006  RETURNS NULL ON NULL INPUT
1007  ';
1008  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . '.getItemParent.create');
1009  return;
1010  }
1011 
1016  {
1017  if (empty($this->schema['INHERITS'])) {
1018  return;
1019  }
1020  foreach ($this->schema['INHERITS'] as $table => $fromTable) {
1021  if (empty($table)) {
1022  continue;
1023  }
1024  if (!$this->dbman->existsTable($table) && $this->dbman->existsTable($fromTable)) {
1025  $sql = "CREATE TABLE \"$table\" () INHERITS (\"$fromTable\")";
1026  $this->applyOrEchoOnce($sql, $stmt = __METHOD__ . $table);
1027  }
1028  }
1029  }
1030 
1031  // MakeFunctions()
1032 }
1033 
1034 if (empty($dbManager) || !($dbManager instanceof DbManager)) {
1035  $logLevel = Logger::INFO;
1036  $logger = new Logger(__FILE__);
1037  $logger->pushHandler(new ErrorLogHandler(ErrorLogHandler::OPERATING_SYSTEM, $logLevel));
1038  $dbManager = new ModernDbManager($logger);
1039  $pgDriver = new Postgres($PG_CONN);
1040  $dbManager->setDriver($pgDriver);
1041 }
1042 /* simulate the old functions*/
1043 $libschema = new fo_libschema($dbManager);
1051 function ApplySchema($Filename = NULL, $Debug = false, $Catalog = 'fossology')
1052 {
1053  global $libschema;
1054  return $libschema->applySchema($Filename, $Debug, $Catalog);
1055 }
1056 
1060 function GetSchema()
1061 {
1062  global $libschema;
1063  return $libschema->getCurrSchema();
1064 }
1065 
1072 function ExportSchema($filename = NULL)
1073 {
1074  global $libschema;
1075  return $libschema->exportSchema($filename);
1076 }
1077 
1081 function MakeFunctions($Debug)
1082 {
1083  global $libschema;
1084  return $libschema->makeFunctions($Debug);
1085 }
int debug
Definition: buckets.c:68
Class to handle database schema.
Definition: libschema.php:39
ApplySchema($Filename=NULL, $Debug=false, $Catalog= 'fossology')
Make schema match $Filename. This is a single transaction.
Definition: libschema.php:1051
dropConstraints()
Delete constraints.
Definition: libschema.php:348
applyClusters()
Add clusters.
Definition: libschema.php:224
dropColumnsFromTable($columns, $table)
Definition: libschema.php:494
addFunctions($schema)
Definition: libschema.php:855
makeFunctions()
Create any required DB functions.
Definition: libschema.php:946
exportSchema($filename=NULL)
Export the schema of the connected ($PG_CONN) database to a file in the format readable by GetSchema(...
Definition: libschema.php:912
applyInheritedRelations()
Definition: libschema.php:1015
ExportSchema($filename=NULL)
Export the schema of the connected ($PG_CONN) database to a file in the format readable by GetSchema(...
Definition: libschema.php:1072
updateSequences()
Add sequences.
Definition: libschema.php:248
applyTables($inherits=false)
Add tables/columns (dependent on sequences for default values)
Definition: libschema.php:270
addViews($viewowner)
Definition: libschema.php:618
DB_TableExists($tableName)
Check if table exists.
Definition: common-db.php:225
MakeFunctions($Debug)
Create any required DB functions.
Definition: libschema.php:1081
applySequences()
Add sequences to the database.
Definition: libschema.php:201
GetSchema()
Load the schema from the db into an array.
Definition: libschema.php:1060
DB_ConstraintExists($ConstraintName, $DBName='fossology')
Check if a constraint exists.
Definition: common-db.php:275
DB_ColExists($tableName, $colName, $DBName='fossology')
Check if a column exists.
Definition: common-db.php:251
ReportCachePurgeAll()
Purge all records from the report cache.
__construct(DbManager &$dbManager)
Definition: libschema.php:69
addSequences($referencedSequencesInTableColumns)
Definition: libschema.php:635
setDriver(Driver &$dbDriver)
Definition: libschema.php:78
writeArrayEntries($fout, $key, $value, $varname)
Definition: libschema.php:890
foreach($Options as $Option=> $OptVal) if(0==$reference_flag &&0==$nomos_flag) $PG_CONN
getCurrSchema()
Load the schema from the db into an array.
Definition: libschema.php:514
addInheritedRelations()
Definition: libschema.php:531
dropViews($catalog)
Delete views.
Definition: libschema.php:465
applyOrEchoOnce($sql, $stmt= '')
Definition: libschema.php:90
applySchema($filename=NULL, $debug=false, $catalog= 'fossology', $migrateColumns=array())
Make schema match $Filename. This is a single transaction.
Definition: libschema.php:108