Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
db_install.inc
1 <?php
40 function parse_tables_xml($xml_file)
41 {
42  $db = MatrixDAL::getDb();
43 
44  try {
45  $root = new SimpleXMLElement($xml_file, LIBXML_NOCDATA, TRUE);
46  } catch (Exception $e) {
47  throw new Exception('Could not parse tables XML file: '.$e->getMessage());
48  }
49 
50  if (($root->getName() != 'schema') || !isset($root->tables) || !isset($root->sequences)) {
51  throw new Exception('Tables XML file is not valid.');
52  trigger_localised_error('SYS0012', E_USER_WARNING);
53  return FALSE;
54  }
55 
56  $info = Array();
57  $info['tables'] = Array();
58  $info['sequences'] = Array();
59 
60  //-- TABLES --//
61 
62  foreach ($root->tables->table as $table) {
63  $table_name = (string)$table->attributes()->name;
64 
65  $info['tables'][$table_name] = Array();
66  $info['tables'][$table_name]['rollback'] = (($table->attributes()->{'require_rollback'} == 1) ? TRUE : FALSE);
67 
68  //-- TABLE COLUMNS --//
69  $info['tables'][$table_name]['columns'] = Array();
70 
71  foreach ($table->columns->column as $table_column) {
72  $column_name = (string)$table_column->attributes()->name;
73 
74  $info['tables'][$table_name]['columns'][$column_name] = Array();
75  $info['tables'][$table_name]['columns'][$column_name]['allow_null'] = (($table_column->attributes()->{'allow_null'} == 1) ? TRUE : FALSE);
76 
77  //-- TABLE COLUMN VARS --//
78 
79  $type = NULL;
80  $default = NULL;
81 
82  foreach ($table_column->children() as $column_var) {
83  switch (strtolower($column_var->getName())) {
84  case 'type' :
85  // set the type of the column if it hasnt already been
86  // set in a variation (this is the default column type)
87  if (is_null($type)) $type = (string)$column_var;
88  break;
89  case 'type_variations' :
90  // check for varitions of the column type for his database
91  foreach ($column_var->children() as $variation) {
92  if ($variation->getName() == MatrixDAL::getDbType()) {
93  $type = (string)$variation;
94  break;
95  }
96  }
97  break;
98  case 'default' :
99  if (trim((string)$column_var) != '') {
100  $default = (string)$column_var;
101  }
102  break;
103  default :
104  continue;
105  break;
106  }
107  }
108  $info['tables'][$table_name]['columns'][$column_name]['type'] = $type;
109  $info['tables'][$table_name]['columns'][$column_name]['default'] = $default;
110 
111  //-- KEYS --//
112 
113  $info['tables'][$table_name]['primary_key'] = Array();
114  $info['tables'][$table_name]['unique_key'] = Array();
115 
116  if (isset($table->keys) && (count($table->keys->children()) > 0)) {
117  foreach ($table->keys->children() as $table_key) {
118  $index_db_type = $table_key->attributes()->db;
119  if (!is_null($index_db_type) && ((string)$index_db_type != MatrixDAL::getDbType())) {
120  continue;
121  }
122 
123  // work out the columns in this key
124  $key_columns = Array();
125  foreach ($table_key->column as $table_key_column) {
126  $col_name = (string)$table_key_column->attributes()->name;
127  $key_columns[] = $col_name;
128 
129  // cache the primary key columns for this table
130  if ($table_key->getName() == 'primary_key') {
131  $info['tables'][$table_name]['primary_key'][] = $col_name;
132  }
133  if ($table_key->getName() == 'unique_key') {
134  $info['tables'][$table_name]['unique_key'][] = $col_name;
135  }
136  }//end foreach
137  }//end foreach
138  }//end if
139 
140  //-- INDEXES --//
141 
142  // check for any indexes that need creating
143  if (!empty($table->indexes->index)) {
144  foreach ($table->indexes->index as $table_index) {
145 
146  // work out the columns in this index
147  $index_cols = Array();
148  foreach ($table_index->column as $table_index_column) {
149  $index_cols[] = (string)$table_index_column->attributes()->name;
150  }
151 
152  // work out the name of the index
153  $index_name = isset($table_index->attributes()->name) ? (string)$table_index->attributes()->name : reset($index_cols);
154  $index_type = isset($table_index->attributes()->type) ? (string)$table_index->attributes()->type : NULL;
155  $index_db_type = isset($table_index->attributes()->db) ? (string)$table_index->attributes()->db : NULL;
156 
157  $index_info = Array(
158  'columns' => $index_cols,
159  'type' => $index_type,
160  'db_type' => $index_db_type,
161  );
162  $info['tables'][$table_name]['indexes'][$index_name] = $index_info;
163  }//end for
164  }//end if
165  }//end for
166  }//end for
167 
168  foreach ($root->sequences->sequence as $sequence) {
169  $sequence_name = (string)$sequence->attributes()->name;
170  $info['sequences'][] = $sequence_name;
171  }
172 
173  return $info;
174 
175 }//end parse_tables_xml()
176 
177 
189 function create_index($tablename, $columns, $index_name=NULL, $index_type=NULL)
190 {
191  $db = MatrixDAL::getDb();
192  if (is_null($index_name)) $index_name = implode('_', $columns);
193 
194  // Truncate any index names to 30 characters to satisfy Oracle restrictions
195  // The '27' value takes out the 'sq_'
196  $sql = 'CREATE INDEX sq_'.substr($tablename.'_'.$index_name, 0, 27).' ON sq_'.$tablename;
197  if (!empty($index_type)) {
198  if (DAL::getDbType() == 'oci') {
199  $sql .= '('.implode(', ', $columns).') indextype is '.$index_type;
200  } else if (DAL::getDbType() == 'pgsql') {
201  $sql .= ' USING '.$index_type.'('.implode(', ', $columns).')';
202  }
203  } else {
204  $sql .= ' ('.implode(', ', $columns).')';
205  }
206 
207  try {
208  DAL::executeSql($sql);
209  } catch (DALException $e) {
210  throw $e;
211  }
212 
213 }//end create_index()
214 
215 
227 function create_table($tablename, $table_info)
228 {
229  $db = MatrixDAL::getDb();
230  $sql = 'CREATE TABLE sq_'.$tablename.'(';
231 
232  $i = 0;
233  // build the columns string
234  foreach ($table_info['columns'] as $col_name => $col_info) {
235  if ($i++ != 0) $sql .= ',';
236  $sql .= $col_name.' '.$col_info['type'];
237  if (!is_null($col_info['default'])) {
238  $sql .= ' DEFAULT '.$col_info['default'];
239  }
240  if (!$col_info['allow_null']) $sql .= ' NOT NULL';
241  }
242 
243  // build the keys string
244  if (!empty($table_info['primary_key'])) {
245  $sql .= ', CONSTRAINT '.$tablename.'_pk PRIMARY KEY ('.implode(',', $table_info['primary_key']).')';
246  }
247  if (!empty($table_info['unique_key'])) {
248  $sql .= ', UNIQUE ('.implode(',', $table_info['unique_key']).')';
249  }
250  $sql .= ')';
251 
252  // oracle replication requires row dependencies so that the replicating
253  // server knows what order to create the new entries
254  if (DAL::getDbType() == 'oci') {
255  $sql .= ' ROWDEPENDENCIES';
256  }
257 
258  try {
259  DAL::executeSql($sql);
260  } catch (DALException $e) {
261  throw $e;
262  }
263 
264 }//end create_table()
265 
266 
277 function get_database_indexes($tablename, $include_rollback=TRUE)
278 {
279  $db = MatrixDAL::getDb();
280  $db_type = MatrixDAL::getDbType();
281 
282  if ($db_type == 'pgsql') {
283  $sql = 'SELECT
284  indexname
285  FROM
286  pg_indexes
287  WHERE
288  tablename = :std_table';
289  if ($include_rollback) {
290  $sql .= ' OR tablename = :rollback_table';
291  }
292  } else if ($db_type == 'oci') {
293  $sql = 'SELECT
294  index_name
295  FROM
296  user_indexes
297  WHERE
298  LOWER(table_name) = :std_table';
299  if ($include_rollback) {
300  $sql .= ' OR LOWER(table_name) = :rollback_table';
301  }
302  }
303 
304  try {
305  $query = MatrixDAL::preparePdoQuery($sql);
306  MatrixDAL::bindValueToPdo($query, 'std_table', 'sq_'.$tablename);
307  MatrixDAL::bindValueToPdo($query, 'rollback_table', 'sq_rb_'.$tablename);
308  $indexes_in_db = MatrixDAL::executePdoAssoc($query, 0);
309  } catch (DALException $e) {
310  throw $e;
311  }
312 
313  for (reset($indexes_in_db); NULL !== ($key = key($indexes_in_db)); next($indexes_in_db)) {
314  $indexes_in_db[$key] = strtolower($indexes_in_db[$key]);
315  }
316 
317  return $indexes_in_db;
318 
319 }//end get_database_indexes()
320 
321 
328 function get_database_sequences()
329 {
330  $db = MatrixDAL::getDb();
331  $db_type = MatrixDAL::getDbType();
332 
333  if ($db_type == 'pgsql') {
334  $sql = 'SELECT c.relname
335  FROM pg_class c, pg_user u
336  WHERE c.relowner = u.usesysid
337  AND c.relkind = \'S\'
338  AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname)
339  AND c.relname !~ :pattern';
340 
341  try {
342  $query = MatrixDAL::preparePdoQuery($sql);
343  MatrixDAL::bindValueToPdo($query, 'pattern', '^(pg_|sql_)', PDO::PARAM_STR);
344  } catch (DALException $e) {
345  throw $e;
346  }
347 
348  } else if ($db_type == 'oci') {
349  $sql = 'SELECT sequence_name FROM user_sequences';
350 
351  try {
352  $query = MatrixDAL::preparePdoQuery($sql);
353  } catch (DALException $e) {
354  throw $e;
355  }
356  }
357 
358  try {
359  $sequences_in_db = MatrixDAL::executePdoAssoc($query, 0);
360  } catch (DALException $e) {
361  throw $e;
362  }
363 
364  for (reset($sequences_in_db); NULL !== ($key = key($sequences_in_db)); next($sequences_in_db)) {
365  $sequences_in_db[$key] = strtolower($sequences_in_db[$key]);
366  }
367 
368  return $sequences_in_db;
369 
370 }//end get_database_sequences()
371 
372 
379 function get_database_tables()
380 {
381  $db = MatrixDAL::getDb();
382  $db_type = MatrixDAL::getDbType();
383 
384  if ($db_type == 'pgsql') {
385  // formerly DB::getSpecialQuery('tables')
386  $sql = 'SELECT c.relname
387  FROM pg_class c, pg_user u
388  WHERE c.relowner = u.usesysid
389  AND c.relkind = \'r\'
390  AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname)
391  AND c.relname !~ :pattern
392  UNION
393  SELECT c.relname
394  FROM pg_class c
395  WHERE c.relkind = \'r\'
396  AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname)
397  AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner)
398  AND c.relname !~ \'^pg_\'';
399 
400  try {
401  $query = MatrixDAL::preparePdoQuery($sql);
402  MatrixDAL::bindValueToPdo($query, 'pattern', '^(pg_|sql_)', PDO::PARAM_STR);
403  } catch (DALException $e) {
404  throw $e;
405  }
406 
407  } else if ($db_type == 'oci') {
408  $sql = 'SELECT table_name FROM user_tables';
409 
410  try {
411  $query = MatrixDAL::preparePdoQuery($sql);
412  } catch (DALException $e) {
413  throw $e;
414  }
415  }
416 
417  try {
418  $tables_in_db = MatrixDAL::executePdoAssoc($query, 0);
419  } catch (DALException $e) {
420  throw $e;
421  }
422 
423  for (reset($tables_in_db); NULL !== ($key = key($tables_in_db)); next($tables_in_db)) {
424  $tables_in_db[$key] = strtolower($tables_in_db[$key]);
425  }
426  return $tables_in_db;
427 
428 }//end get_database_tables()
429 
430 
441 function db_install($xml_file, $verbose=TRUE)
442 {
443  $db = MatrixDAL::getDb();
444  $info = parse_tables_xml($xml_file);
445  $tables_in_db = get_database_tables();
446 
447  // we need to get the cached tables file so that when this function gets called
448  // for each of the packages, we keep appending the tables installed for the current package.
449  // It won't exist for the first call to this function as step_02 unlinks it
450  $current_cached_tables = Array();
451  if (file_exists(SQ_DATA_PATH.'/private/db/table_columns.inc')) {
452  require SQ_DATA_PATH.'/private/db/table_columns.inc';
453  $current_cached_tables = $tables;
454  }
455 
456  $created_tables = Array();
457  $created_indexes = Array();
458 
459  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
460 
461  foreach ($info['tables'] as $tablename => $table_info) {
462 
463  //-- TABLES --//
464 
465  if ($tablename == 'cache') {
466  // change the db connection
467  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
468  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('dbcache');
469  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
470  // check existing tables in the new connection
471  $old_tables_in_db = $tables_in_db;
472  $tables_in_db = get_database_tables();
473  }
474 
475  // create this table if it doesn't exist in the database
476  if (!in_array('sq_'.$tablename, $tables_in_db)) {
477  create_table($tablename, $table_info);
478  $created_tables[] = 'sq_'.$tablename;
479  }
480 
481  if ($table_info['rollback']) {
482 
483  // use the date type for oracle so we can manipulate its format
484  // with the NLS_DATE_FORMAT session variable
485  $date_type = (DAL::getDbType() == 'oci') ? 'DATE' : 'TIMESTAMP';
486 
487  // create this rollback table if it does not exist in the database
488  if (!in_array('sq_rb_'.$tablename, $tables_in_db)) {
489  $rb_table_info = $table_info;
490  $rb_col['sq_eff_from'] = Array(
491  'type' => $date_type,
492  'default' => NULL,
493  'allow_null' => FALSE,
494  );
495  $rb_col['sq_eff_to'] = Array(
496  'type' => $date_type,
497  'default' => NULL,
498  'allow_null' => TRUE,
499  );
500 
501  // append these elements to the front of the column array so they appear
502  // as the top of the table
503  $rb_table_info['columns'] = array_merge($rb_col, $rb_table_info['columns']);
504 
505  if (!empty($rb_table_info['primary_key'])) {
506  foreach ($rb_table_info['primary_key'] as $field) {
507  // Create individual index on each field of the primary
508  // key (needed for some triggers)
509  $table_info['rb_indexes'][$field] = Array(
510  'columns' => Array($field),
511  'type' => NULL,
512  'db_type' => NULL,
513  );
514  }
515  array_unshift($rb_table_info['primary_key'], 'sq_eff_from');
516  }
517  if (!empty($rb_table_info['unique_key'])) {
518  foreach ($rb_table_info['unique_key'] as $field) {
519  // Create individual index on each field of the unique key
520  $table_info['rb_indexes'][$field] = Array(
521  'columns' => Array($field),
522  'type' => NULL,
523  'db_type' => NULL,
524  );
525  }
526  array_unshift($rb_table_info['unique_key'], 'sq_eff_from');
527  }
528 
529  // prepare indexes for the "sq_eff_from" and "sq_eff_to" columns
530  $table_info['rb_indexes']['effrm'] = Array(
531  'columns' => Array('sq_eff_from'),
532  'type' => NULL,
533  'db_type' => NULL,
534  );
535  $table_info['rb_indexes']['efto'] = Array(
536  'columns' => Array('sq_eff_to'),
537  'type' => NULL,
538  'db_type' => NULL,
539  );
540 
541  create_table('rb_'.$tablename, $rb_table_info);
542  $created_tables[] = 'sq_rb_'.$tablename;
543  }//end if
544  }//end if rollback
545 
546  //-- INDEXES --//
547 
548  // create the database indexes for this table if they don't allready exist
549  $indexes_in_db = get_database_indexes($tablename, TRUE);
550 
551  if (!empty($table_info['indexes'])) {
552  foreach ($table_info['indexes'] as $index_name => $index_info) {
553  if (is_null($index_info['db_type']) || $index_info['db_type'] == MatrixDAL::getDbType()) {
554  if (!in_array('sq_'.$tablename.'_'.$index_name, $indexes_in_db)) {
555  create_index($tablename, $index_info['columns'], $index_name, $index_info['type']);
556  $created_indexes[] = 'sq_'.$tablename.'_'.$index_name;
557  }
558  if ($table_info['rollback']) {
559  if (!in_array('sq_rb_'.$tablename.'_'.$index_name, $indexes_in_db)) {
560  create_index('rb_'.$tablename, $index_info['columns'], $index_name, $index_info['type']);
561  $created_indexes[] = 'sq_rb_'.$tablename.'_'.$index_name;
562  }
563  }
564  }
565  }// end foreach
566 
567  }//end if
568 
569  // reload the list of indexes, to prevent cases where a rollback
570  // index overlaps one created above (which would cause a duplicate
571  // index error)
572  $indexes_in_db = get_database_indexes($tablename, TRUE);
573 
574  // create indexes (prepared earlier) which are specific to rollback and not required to be replicated for "non-rollback" tables
575  if (($table_info['rollback']) && (!empty($table_info['rb_indexes']))) {
576  foreach ($table_info['rb_indexes'] as $index_name => $index_info) {
577  if (!in_array('sq_rb_'.$tablename.'_'.$index_name, $indexes_in_db)) {
578  create_index('rb_'.$tablename, $index_info['columns'], $index_name, $index_info['type']);
579  $created_indexes[] = 'sq_rb_'.$tablename.'_'.$index_name;
580  }
581  }// end foreach
582  }// end if
583 
584  if ($tablename == 'cache') {
585  // restore the db connection
586  $tables_in_db = $old_tables_in_db;
587  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
588  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
589  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
590  }
591 
592  }//end foreach tables
593 
594  if ($verbose) {
595  $table_names = empty($created_tables) ? 'No new tables added' : 'New Tables:'."\n".implode($created_tables, "\n");
596  pre_echo("TABLE CREATION COMPLETE\n".$table_names);
597 
598  $index_names = empty($created_indexes) ? 'No Indexes added' : 'New Indexes:'."\n".implode($created_indexes, "\n");
599  pre_echo("INDEX CREATION COMPLETE\n".$index_names);
600  }
601 
602  //-- SEQUENCES --//
603 
604  $sequences_in_db = get_database_sequences();
605  foreach ($info['sequences'] as $sequence_name) {
606  if (in_array('sq_'.$sequence_name.'_seq', $sequences_in_db)) {
607  continue;
608  }
609 
610  // TODO: replacement for CREATE SEQUENCE?
611  try {
612  $sql = 'CREATE SEQUENCE sq_'.$sequence_name.'_seq';
613  DAL::executeSql($sql);
614  } catch (DALException $e) {
615  throw $e;
616  }
617  }
618 
619  if ($verbose) pre_echo('SEQUENCE CREATION COMPLETE');
620 
621  //-- CACHED FILES --//
622 
623  $cached_sequences_string = '<'.'?php $sequences = '.var_export($info['sequences'], TRUE).'; ?'.'>';
624  if (!string_to_file($cached_sequences_string, SQ_DATA_PATH.'/private/db/sequences.inc')) {
625  trigger_localised_error('SYS0010', E_USER_WARNING);
626  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
627  return FALSE;
628  }
629 
630  // we don't need the detailed column information, so just
631  // create an array of the actual column names and cache that
632  $cached_table_info = $info['tables'];
633  foreach ($cached_table_info as $table => $info) {
634  $cached_table_info[$table]['columns'] = array_keys($info['columns']);
635  }
636  $cached_table_info = array_merge($current_cached_tables, $cached_table_info);
637 
638  $cached_tables_string = '<'.'?php $tables = '.var_export($cached_table_info, TRUE).'; ?'.'>';
639  if (!string_to_file($cached_tables_string, SQ_DATA_PATH.'/private/db/table_columns.inc')) {
640  trigger_localised_error('SYS0011', E_USER_WARNING);
641  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
642  return FALSE;
643  }
644 
645  // if rollback is enabled, install the rollback triggers, otherwise remove them
646  if (SQ_CONF_ROLLBACK_ENABLED) {
647  install_rollback_triggers($cached_table_info, $verbose);
648  } else {
649  uninstall_rollback_triggers();
650  }
651 
652  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
653 
654  return TRUE;
655 
656 }//end db_install()
657 
658 
669 function grant_secondary_user_perms($verbose=TRUE)
670 {
671  if (DAL::getDbType() == 'pgsql') {
672  $db_conf = require(SQ_SYSTEM_ROOT.'/data/private/conf/db.inc');
673 
674  // 'db' connection can be configured with multiple DSNs. But we only get the first DSN in the list for now.
675  $primary_dsn = !isset($db_conf['db']['DSN']) && isset($db_conf['db'][0]) ? $db_conf['db'][0] : $db_conf['db'];
676  $secondary_dsn = $db_conf['db2'];
677  $tertiary_dsn = $db_conf['db3'];
678  $cache_dsn = $db_conf['dbcache'];
679  $search_dsn = $db_conf['dbsearch'];
680  $grant_sql = 'SELECT sq_grant_access(:user, :access_type)';
681 
682  // grant to the primary user
683  try {
684  $query = MatrixDAL::preparePdoQuery($grant_sql);
685  MatrixDAL::bindValueToPdo($query, 'user', $primary_dsn['user']);
686  MatrixDAL::bindValueToPdo($query, 'access_type', 'ALL');
687  MatrixDAL::execPdoQuery($query);
688  } catch (DALException $e) {
689  throw $e;
690  }
691 
692  // grant to the secondary user, if different
693  if ($primary_dsn['user'] != $secondary_dsn['user']) {
694  try {
695  $query = MatrixDAL::preparePdoQuery($grant_sql);
696  MatrixDAL::bindValueToPdo($query, 'user', $secondary_dsn['user']);
697  MatrixDAL::bindValueToPdo($query, 'access_type', 'ALL');
698  MatrixDAL::execPdoQuery($query);
699  } catch (DALException $e) {
700  throw $e;
701  }
702  }
703 
704  // grant to the tertiary user, if different to the first two
705  if (($primary_dsn['user'] != $tertiary_dsn['user']) && ($secondary_dsn['user'] != $tertiary_dsn['user'])) {
706  try {
707  $query = MatrixDAL::preparePdoQuery($grant_sql);
708  MatrixDAL::bindValueToPdo($query, 'user', $tertiary_dsn['user']);
709  MatrixDAL::bindValueToPdo($query, 'access_type', 'ALL');
710  MatrixDAL::execPdoQuery($query);
711  } catch (DALException $e) {
712  throw $e;
713  }
714  }
715 
716  if ($verbose) {
717  pre_echo('PGSQL SECONDARY AND TERTIARY USER PERMISSIONS FIXED');
718  }
719 
720  $postgres_db = DAL::getDbType() == 'pgsql';
721  $diff_dbcache_user = $primary_dsn['user'] != $cache_dsn['user'] && $secondary_dsn['user'] != $cache_dsn['user'] && $tertiary_dsn['user'] != $cache_dsn['user'];;
722  $diff_dbcache_dsn = $primary_dsn['DSN'] != $cache_dsn['DSN'];
723 
724  // grant to the cache user, if dsn is defined and is differnet from primary dns or if the cache user is different
725  if ($postgres_db && !is_null($cache_dsn) && ($diff_dbcache_user || $diff_dbcache_dsn)) {
726  // Change db connection only if the cache dns is differnet from the primary dsn
727  if ($diff_dbcache_dsn) {
728  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('dbcache');
729  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
730  }
731 
732  try {
733  $query = MatrixDAL::preparePdoQuery($grant_sql);
734  MatrixDAL::bindValueToPdo($query, 'user', $cache_dsn['user']);
735  MatrixDAL::bindValueToPdo($query, 'access_type', 'ALL');
736  MatrixDAL::execPdoQuery($query);
737  } catch (DALException $e) {
738  throw $e;
739  }
740 
741  if ($verbose) {
742  pre_echo('PGSQL CACHE USER PERMISSIONS FIXED');
743  }
744 
745  if ($diff_dbcache_dsn) {
746  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
747  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
748  }
749  }
750 
751  $diff_dbsearch_user = $primary_dsn['user'] != $search_dsn['user'] && $secondary_dsn['user'] != $search_dsn['user'] && $tertiary_dsn['user'] != $search_dsn['user'] && $cache_dsn['user'] != $search_dsn['user'];
752  $diff_dbsearch_dsn = $primary_dsn['DSN'] != $search_dsn['DSN'];
753 
754  // grant to the search user, if dsn is defined and is differnet from primary dns or if the search user is different
755  if ($postgres_db && !is_null($search_dsn) && ($diff_dbsearch_user || $diff_dbsearch_dsn)) {
756 
757  // Change db connection only if the search dns is differnet from the primary dsn
758  if ($diff_dbsearch_dsn) {
759  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('dbsearch');
760  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
761  }
762 
763  try {
764  $query = MatrixDAL::preparePdoQuery($grant_sql);
765  MatrixDAL::bindValueToPdo($query, 'user', $search_dsn['user']);
766  MatrixDAL::bindValueToPdo($query, 'access_type', 'SELECT');
767  MatrixDAL::execPdoQuery($query);
768  } catch (DALException $e) {
769  throw $e;
770  }
771 
772  if ($verbose) {
773  pre_echo('PGSQL SEARCH USER PERMISSIONS FIXED');
774  }
775 
776  if ($diff_dbsearch_dsn) {
777  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
778  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
779  }
780  }
781 
782  }//end if
783 
784 }//end grant_secondary_user_perms()
785 
786 
797 function install_stored_relations($relations, $package=NULL, $verbose=TRUE)
798 {
799  $db = MatrixDAL::getDb();
800  $db_type = MatrixDAL::getDbType();
801 
802  if (is_null($package)) {
803  $subdir = 'install';
804  } else {
805  $subdir = 'packages/'.$package;
806  }
807  $fns_file = SQ_SYSTEM_ROOT.'/'.$subdir.'/'.$db_type.'_'.$relations.'.xml';
808 
809  // if the file does not exist, check to see if there is a common file
810  // for all database types
811  if (!file_exists($fns_file)) {
812  $fns_file = SQ_SYSTEM_ROOT.'/'.$subdir.'/common_'.$relations.'.xml';
813  }
814 
815  if (file_exists($fns_file)) {
816 
817  try {
818  // Load stored relations file as SimpleXML
819  $root = new SimpleXMLElement($fns_file, LIBXML_NOCDATA, TRUE);
820  } catch (Exception $e) {
821  throw new Exception('Could not parse stored relations file: '.$e->getMessage());
822  }
823 
824  if ($root->getName() != 'sql') {
825  throw new Exception('Cannot install stored relations file: expected root element "sql", found "'.$root->getName().'".');
826  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
827  return FALSE;
828  }
829 
830  $display_names = Array();
831 
832  foreach ($root->children() as $sql_element) {
833  $element_name = $sql_element->getName();
834  if ($sql_element->getName() != 'sql_element') {
835  throw new Exception('Cannot install stored relations file: expected element "sql_element", found "'.$sql_element->getName().'".');
836  }
837 
838  $display_names[] = isset($sql_element->attributes()->{'display_name'}) ? $sql_element->attributes()->{'display_name'} : '<Unknown Element>';
839  $sql = trim(xml_entity_decode((string)$sql_element));
840 
841  if (!empty($sql)) {
842  try {
843  $result = MatrixDAL::executeSql($sql);
844  } catch (DALException $e) {
845  throw $e;
846  }
847  }
848  }
849 
850  if ($verbose) {
851  if (is_null($package)) {
852  pre_echo(strtoupper($db_type).' STORED '.strtoupper($relations).' CREATED'."\n".implode("\n", $display_names));
853  } else {
854  pre_echo(strtoupper($db_type).' STORED '.strtoupper($relations).' FOR PACKAGE '.$package.' CREATED'."\n".implode("\n", $display_names));
855  }
856  }
857  }//end if
858 
859 }//end install_stored_relations()
860 
861 
868 function get_installed_rollback_triggers()
869 {
870  $db = MatrixDAL::getDb();
871 
872  if (DAL::getDbType() == 'oci') {
873  $sql = 'SELECT trigger_name FROM user_triggers WHERE LOWER(trigger_name)';
874  } else if (DAL::getDbType() == 'pgsql') {
875  $sql = 'SELECT tgname FROM pg_trigger WHERE tgname';
876  }
877  $sql .= ' LIKE :trigger_pattern';
878 
879  try {
880  $query = MatrixDAL::preparePdoQuery($sql);
881  MatrixDAL::bindValueToPdo($query, 'trigger_pattern', 'sq_%_rb_trg', PDO::PARAM_STR);
882  $triggers = MatrixDAL::executePdoAssoc($query, 0);
883  } catch (DALException $e) {
884  throw $e;
885  }
886 
887  return $triggers;
888 
889 }//end get_installed_rollback_triggers()
890 
891 
906 function install_rollback_triggers($table_columns, $verbose=TRUE, $override=FALSE)
907 {
908  $db = MatrixDAL::getDb();
909  $curr_triggers = Array();
910  $installed_triggers = Array();
911 
912  // get the current triggers in the system, as we don't want
913  // to try to re-install them as postgres does not support the OR REPLACE
914  // clause when defining triggers
915 
916  $curr_triggers = get_installed_rollback_triggers();
917 
918  for (reset($curr_triggers); NULL !== ($key = key($curr_triggers)); next($curr_triggers)) {
919  $curr_triggers[$key] = strtolower($curr_triggers[$key]);
920  }
921 
922  foreach ($table_columns as $table_name => $table_info) {
923  // we only care about rollback tables
924  if (!isset($table_info['rollback']) || !$table_info['rollback']) {
925  continue;
926  }
927  $rollback_table = 'sq_rb_'.$table_name;
928  $trigger_name = strtolower('sq_'.$table_name.'_rb_trg');
929  $table = 'sq_'.$table_name;
930 
931  // don't install triggers that have allready been installed
932  if (!$override) {
933  if (in_array($trigger_name, $curr_triggers)) continue;
934  }
935 
936  $sql = 'CREATE TRIGGER '.$trigger_name.
937  ' BEFORE INSERT OR UPDATE OR DELETE ON '.$table.
938  ' FOR EACH ROW ';
939 
940  if (DAL::getDbType() == 'oci') {
941  $sql .= ' DECLARE BEGIN ';
942  // if we are oracle make sure that we are not a remote
943  // replication server as the rows will get replicated for us
944 
945  $sql .= 'IF DBMS_REPUTIL.FROM_REMOTE = TRUE THEN
946  RETURN;
947  END IF;';
948 
949  // set the timestamp for this session
950  // note that the timestamp is set only once per session
951  $sql .= 'sq_common_pkg.sq_set_rollback_timestamp;';
952 
953  $if_inserting = 'IF INSERTING THEN ';
954  $if_updating = 'IF UPDATING THEN ';
955  $if_deleting = 'IF DELETING THEN ';
956  // oracle packages are cool, check them out ;)
957  $get_timestamp_fn = 'sq_common_pkg.sq_get_rollback_timestamp';
958 
959  } else if (DAL::getDbType() == 'pgsql') {
960  // if we are postgres then we want to define the trigger to call a
961  // procedure, end the trigger and begin the prodecure declaration
962  $sql .= 'EXECUTE PROCEDURE '.$trigger_name.'_fn();';
963 
964  // because postgres triggers can only execute a trigger function, we need to
965  // store the actual trigger and execute it AFTER the creation of the trigger function
966  $trigger_sql = $sql;
967  $sql = '';
968  $sql .= 'CREATE OR REPLACE FUNCTION '.$trigger_name."_fn() RETURNS trigger AS '";
969  $sql .= ' DECLARE ';
970  $sql .= 'rollback_timestamp timestamp;';
971  $sql .= ' BEGIN ';
972 
973  // We don't need to set the timestamp for the session,
974  // sq_get_rollback_timestamp does that for us already.
975  $sql .= "EXECUTE ''SELECT sq_get_rollback_timestamp();'' INTO rollback_timestamp;";
976 
977  $if_inserting = "IF TG_OP = ''INSERT'' THEN ";
978  $if_updating = "IF TG_OP = ''UPDATE'' THEN ";
979  $if_deleting = "IF TG_OP = ''DELETE'' THEN ";
980 
981  // This is just a function variable, but since oracle is still calling a
982  // function it's easier to just use the variable here so we don't end up
983  // with complicated code.
984  $get_timestamp_fn = 'rollback_timestamp';
985  }
986 
987  $old_primary_key_where = '';
988  $new_primary_key_where = '';
989  $primary_key_where = '';
990 
991  $i = 0;
992 
993  foreach ($table_info['primary_key'] as $key) {
994  $primary_key_where = '';
995  if ($i++ != 0) $primary_key_where = ' AND ';
996  $primary_key_where .= $key.' = ';
997  // old and new vars are treated like bind variables in oracle
998  // and therefore require a colon before them
999  if (DAL::getDbType() == 'oci') $primary_key_where .= ':';
1000 
1001  $old_primary_key_where .= $primary_key_where.'OLD.'.$key;
1002  $new_primary_key_where .= $primary_key_where.'NEW.'.$key;
1003  }
1004  $old_primary_key_where .= ';';
1005  $new_primary_key_where .= ';';
1006 
1007  // construct a column string for the column names
1008  // and two strings for inserting - one for inserting the values
1009  // that were just inserted into the non-rollback table and another
1010  // for inserting the values of any rows that were effected by an update
1011 
1012  $col_string = ' (sq_eff_from, sq_eff_to,';
1013  $new_val_string = ' ('.$get_timestamp_fn.',null,';
1014  $old_val_string = $new_val_string;
1015  $update_string = '';
1016 
1017  $i = 0;
1018 
1019  foreach ($table_info['columns'] as $column) {
1020  // we want to remove the primary and unique keys from the SET
1021  // sql as they will cause constraint violations
1022  $is_unique_key = (isset($table_info['primary_key']) && in_array($column, $table_info['primary_key']));
1023  $is_unique_key |= (isset($table_info['unique_key']) && in_array($column, $table_info['unique_key']));
1024  $col_string .= $column;
1025 
1026  if (!$is_unique_key) $update_string .= $column.' = ';
1027 
1028  // old and new vars are treated like bind variables in oracle
1029  // and therefore require a colon before them
1030  if (DAL::getDbType() == 'oci') {
1031  $new_val_string .= ':';
1032  $old_val_string .= ':';
1033  if (!$is_unique_key) $update_string .= ':';
1034  }
1035  $new_val_string .= 'NEW.'.$column;
1036  $old_val_string .= 'OLD.'.$column;
1037  if (!$is_unique_key) $update_string .= 'NEW.'.$column;
1038 
1039  if (++$i != count($table_info['columns'])) {
1040  $col_string .= ',';
1041  $new_val_string .= ',';
1042  $old_val_string .= ',';
1043  if (!$is_unique_key) $update_string .= ',';
1044  }
1045  }//end foreach
1046 
1047  $col_string .= ')';
1048  $new_val_string .= ');';
1049  $old_val_string .= ');';
1050  // If the last column was unique it leaves a nasty comma on the end, remove it
1051  $update_string = rtrim($update_string, ',');
1052 
1053  if (DAL::getDbType() == 'oci') {
1054  $not_found = 'SQL%NOTFOUND';
1055  } else if (DAL::getDbType() == 'pgsql') {
1056  $not_found = 'NOT FOUND';
1057  }
1058 
1059  // remove any constraint collisions
1060  // we don't do an update because you cant update primary
1061  // key columns in oracle
1062  $remove_rollback_collision = 'DELETE FROM '.
1063  $rollback_table.
1064  ' WHERE sq_eff_from = '.$get_timestamp_fn.
1065  ' AND '.$new_primary_key_where;
1066 
1067  // if we are inserting then we just want to insert the new
1068  // row into the rollback table
1069 
1070  $sql .= $if_inserting.
1071  $remove_rollback_collision.
1072  'INSERT INTO '.$rollback_table.
1073  $col_string.' VALUES '.$new_val_string;
1074 
1075  if (DAL::getDbType() == 'pgsql') $sql .= ' RETURN NEW; ';
1076  $sql .= 'END IF;';
1077 
1078  // if we are updating then we want to align the most
1079  // recent entry in the rollback table and insert any
1080  // affected rows from the non-rollback table into rollback
1081 
1082  $update_sql = 'UPDATE '.$rollback_table.
1083  ' SET sq_eff_to = '.$get_timestamp_fn.
1084  ' WHERE sq_eff_to IS NULL AND '.
1085  $old_primary_key_where;
1086 
1087  // update any entries that were performed in the current session
1088  // where the timestamp is the same as the session timestamp
1089  $replace_sql = 'UPDATE '.$rollback_table.
1090  ' SET '.$update_string.
1091  ' WHERE sq_eff_from = '.$get_timestamp_fn.' AND '.$old_primary_key_where;
1092 
1093  if (trim($update_string) != '') {
1094  $sql .= $if_updating.
1095  $replace_sql.
1096  ' IF '.$not_found.' THEN '.
1097  $update_sql.
1098  'INSERT INTO '.$rollback_table.
1099  $col_string.' VALUES '.$new_val_string.
1100  'END IF;';
1101  if (DAL::getDbType() == 'pgsql') $sql .= 'RETURN NEW;';
1102  $sql .= 'END IF;';
1103  } else {
1104  // if there is no update string, it means that every
1105  // column is either a primary or unique key. Therefore
1106  // you cannot update anything on this table - only insert and delete
1107  if (DAL::getDbType() == 'pgsql') {
1108  $sql .= $if_updating.'RETURN NEW;END IF;';
1109  }
1110  }
1111 
1112  // if we are deleting then we just close off
1113  // the rollback entry
1114  $sql .= $if_deleting.$update_sql;
1115  if (DAL::getDbType() == 'pgsql') $sql .= ' RETURN OLD; ';
1116  $sql .= 'END IF;';
1117 
1118  // end begin
1119  $sql .= 'END;';
1120 
1121  if (DAL::getDbType() == 'pgsql') {
1122  $sql .= "' LANGUAGE plpgsql;";
1123  // before we finish we will re-order the sql
1124  // so that the trigger sql is AFTER the actual trigger function
1125  // sql so that postgres doesn't complain about functions that does exist
1126  if (!$override) $sql = $sql.$trigger_sql;
1127  }
1128 
1129  try {
1130  DAL::executeSql($sql);
1131  } catch (DALException $e) {
1132  throw $e;
1133  }
1134 
1135  $installed_triggers[] = $trigger_name;
1136 
1137  }//end foreach tables
1138 
1139  if ($verbose) {
1140  $trigger_names = (!empty($installed_triggers)) ? 'New Triggers:'."\n".implode("\n", $installed_triggers) : 'No Triggers Installed';
1141  pre_echo('DATABASE TRIGGER CREATION COMPLETE'."\n".$trigger_names);
1142  }
1143 
1144 }//end install_rollback_triggers()
1145 
1146 
1153 function uninstall_rollback_triggers()
1154 {
1155  $triggers = get_installed_rollback_triggers();
1156  if (!empty($triggers)) {
1157  $db = MatrixDAL::getDb();
1158  foreach ($triggers as $trigger_name) {
1159  // we need to cascade the dropping of the trigger
1160  // so that the postgres trigger functions are also dropped
1161  $sql = 'DROP TRIGGER '.$trigger_name;
1162  if (DAL::getDbType() == 'pgsql') {
1163  $matches = Array();
1164  preg_match('/^sq_(.*)_rb_trg$/i', $trigger_name, $matches);
1165  $sql .= ' on sq_'.$matches[1].' CASCADE';
1166  }
1167 
1168  try {
1169  DAL::executeSql($sql);
1170  } catch (DALException $e) {
1171  throw $e;
1172  }
1173  }
1174  }
1175 
1176 }//end uninstall_rollback_triggers()
1177 
1178 
1179 ?>