Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
convert_database.php
1 <?php
24 error_reporting(E_ALL);
25 ini_set('memory_limit', '256M');
26 
88 $source_dsn = Array(
89  'DSN' => '',
90  'user' => '',
91  'password' => '',
92  'type' => '',
93  );
94 
95 $destination_dsn = Array(
96  'DSN' => '',
97  'user' => '',
98  'password' => '',
99  'type' => '',
100  );
101 
106 if (php_sapi_name() != 'cli') trigger_error("You can only run this script from the command line\n", E_USER_ERROR);
107 
108 $SYSTEM_ROOT = (isset($_SERVER['argv'][1])) ? $_SERVER['argv'][1] : '';
109 if (empty($SYSTEM_ROOT) || !is_dir($SYSTEM_ROOT)) {
110  echo "ERROR: You need to supply the path to the source System Root as the first argument\n";
111  exit();
112 }
113 
117 if (empty($source_dsn) || empty($destination_dsn)) {
118  echo "ERROR: Please fill in the source & destination dsn's\n";
119  exit;
120 }
121 
122 if (empty($source_dsn['DSN']) || empty($destination_dsn['DSN'])) {
123  echo "ERROR: Please fill in the source & destination dsn's\n";
124  exit;
125 }
126 
127 require_once $SYSTEM_ROOT.'/fudge/dev/dev.inc';
128 require_once $SYSTEM_ROOT.'/fudge/db_extras/db_extras.inc';
129 require_once $SYSTEM_ROOT.'/core/include/general.inc';
130 require_once $SYSTEM_ROOT.'/core/lib/DAL/DAL.inc';
131 require_once $SYSTEM_ROOT.'/core/lib/MatrixDAL/MatrixDAL.inc';
132 
140 $dest_db = serialize($destination_dsn);
141 $source_db = serialize($source_dsn);
142 
143 $db_error = false;
144 try {
145  $source_db_connection = MatrixDAL::dbConnect($source_dsn, $source_db);
146 } catch (Exception $e) {
147  echo "Unable to connect to the source db: " . $e->getMessage() . "\n";
148  $db_error = true;
149 }
150 
151 try {
152  $dest_db_connection = MatrixDAL::dbConnect($destination_dsn, $dest_db);
153 } catch (Exception $e) {
154  echo "Unable to connect to the destination db: " . $e->getMessage() . "\n";
155  $db_error = true;
156 }
157 
162 if ($db_error) {
163  exit;
164 }
165 
166 MatrixDAL::changeDb($dest_db);
167 
168 $dest_exists = checkDestinationDb();
169 if (!$dest_exists) {
170  echo "Before running the conversion script, you need to run step_02.php from the installer to create the destination tables.\n";
171  exit;
172 }
173 
174 $xml_files = array (
175  $SYSTEM_ROOT . '/core/assets/tables.xml',
176 );
177 
181 $packages = scandir($SYSTEM_ROOT . '/packages');
182 foreach ($packages as $package) {
183  $xml_path = $SYSTEM_ROOT . '/packages/' . $package . '/tables.xml';
184  if (is_file($xml_path)) {
185  $xml_files[] = $xml_path;
186  }
187 }
188 
195 $nulls_convert_list = array(
196  'ast_lnk',
197 );
198 
199 
207 $first_run = true;
208 
209 foreach ($xml_files as $xml_filename) {
210  $info = parse_tables_xml($xml_filename, $dest_db);
211 
212  if (empty($info)) {
213  continue;
214  }
215 
219  MatrixDAL::changeDb($source_db);
220  foreach ($info['sequences'] as $sequence) {
221  $sequence_values[$sequence] = getSequenceValue($sequence);
222  }
223 
231 
232  if ($first_run) {
233  pre_echo('Dropping destination sequences');
234 
235  $del_seqs = getSequences();
236 
237  foreach ($del_seqs as $sequence) {
238  printName('Dropping: '.strtolower($sequence));
239  $sql = 'DROP SEQUENCE ' . $sequence;
240  $ok = MatrixDAL::executeSql($sql);
241  if ($ok === false) {
242  printUpdateStatus('Failure, unable to run query: ' . $sql);
243  exit;
244  }
245  printUpdateStatus('OK');
246  }
247 
248  pre_echo('Dropping destination indexes');
249 
250  $del_indexes = getIndexes();
251  foreach($del_indexes as $index) {
252  if (substr($index, 0, 3) == 'sq_') {
253  printName('Dropping: '.$index);
254  $sql = 'DROP INDEX ' . $index;
255  $ok = MatrixDAL::executeSql($sql);
256  if ($ok === false) {
257  printUpdateStatus('Failure, unable to run query: ' . $sql);
258  exit;
259  }
260  printUpdateStatus('OK');
261  }//end if
262  }//end foreach
263  }
264 
265  // Empty out the destination tables
266  pre_echo('Truncating destination tables');
267 
268  foreach ($info['tables'] as $tablename => $table_info) {
269  printName('Truncating: sq_'.$tablename);
270  $sql = 'TRUNCATE TABLE sq_'.$tablename;
271  $ok = MatrixDAL::executeSql($sql);
272  if ($ok === false) {
273  printUpdateStatus('Failure, unable to run query: ' . $sql);
274  exit;
275  }
276  printUpdateStatus('OK');
277  }//end foreach
278 
284  foreach ($info['tables'] as $tablename => $table_info) {
285  if ($tablename === 'sch_idx') {
286  pre_echo('Skipping search table - you will need to re-index');
287  continue;
288  }
289 
290  pre_echo('Starting table: sq_'.$tablename);
291 
292  $columns = array_keys($table_info['columns']);
293  $sql = generateSQL($tablename, $columns);
294 
298  MatrixDAL::changeDb($source_db);
299 
300  if (!isset($table_info['primary_key'])) {
301  $msg = "This table (sq_${tablename}) cannot be converted since it doesn't have a primary key.\n";
302  $msg .= "A primary key is required to guarantee the chunking of data doesn't\n";
303  $msg .= "miss anything and all data is fetched correctly from the table.\n";
304  $msg .= "Skipping table.\n";
305  pre_echo($msg);
306 
313  continue;
314  }
315 
316  $start = 0;
317  $num_to_fetch = 10000;
318 
319  $primary_key = implode(',', $table_info['primary_key']);
320 
321  $source_sql = 'SELECT * FROM sq_' . $tablename . ' ORDER BY ' . $primary_key;
322 
323  $fetch_source_sql = db_extras_modify_limit_clause($source_sql, $source_dsn['type'], $num_to_fetch, $start);
324 
325  $source_data = MatrixDAL::executeSqlAll($fetch_source_sql);
326 
327  if (empty($source_data)) {
328  printName('Table is empty');
329  printUpdateStatus('OK');
330 
337  continue;
338  }
339 
340  $count = count($source_data);
341 
342  while (!empty($source_data)) {
344 
346 
347  $trans_count = 0;
348 
349  $prepared_sql = MatrixDAL::preparePdoQuery($sql);
350 
351  printName('Inserting Data (' . number_format($start) . ' - ' . number_format($start + $count) . ' rows)');
352 
353  printUpdateStatus("..", "\r");
354 
355  foreach($source_data as $key => $data) {
356  foreach ($data as $data_key => $data_value) {
357  // ignore 0 based indexes..
358  if (is_numeric($data_key)) {
359  continue;
360  }
361 
368  if (!in_array($data_key, $columns)) {
369  continue;
370  }
371 
376  if (in_array($tablename, $nulls_convert_list)) {
377  if ($source_dsn['type'] === 'oci' && $data_value === NULL) {
378  $data_value = '';
379  }
380  }
381 
388  if (is_resource($data_value)) {
389  $stream = $data_value;
390  $data_value = stream_get_contents($stream);
391  fclose($stream);
392  }
393 
394  MatrixDAL::bindValueToPdo($prepared_sql, $data_key, $data_value);
395  }
396  MatrixDAL::execPdoQuery($prepared_sql);
397 
398  $trans_count++;
399 
400  if ($trans_count % 10000 == 0) {
403  $trans_count = 0;
404  }
405  }
406 
407  printName('Inserting Data (' . number_format($start) . ' - ' . number_format($start + $count) . ' rows)');
408  printUpdateStatus('OK', "\r");
409 
411 
415  MatrixDAL::changeDb($source_db);
416 
421  if (count($source_data) < $num_to_fetch) {
422  break;
423  }
424 
425  $start += $num_to_fetch;
426 
427  $fetch_source_sql = db_extras_modify_limit_clause($source_sql, $source_dsn['type'], $num_to_fetch, $start);
428 
429  $source_data = MatrixDAL::executeSqlAll($fetch_source_sql);
430 
431  $count = count($source_data);
432  }
433 
434  printUpdateStatus(null, "\n");
435 
440 
441  }//end foreach
442 
443  pre_echo('Rebuilding Indexes');
444 
445  foreach($info['tables'] as $tablename => $table_info) {
446  if (!empty($table_info['indexes'])) {
447  foreach ($table_info['indexes'] as $index_col => $index_info) {
448  printName('Creating index sq_'.$tablename.'_'.$index_info['name']);
449  $sql = create_index_sql($tablename, $index_info['columns'], $index_info['name'], $index_info['type']);
450  $ok = MatrixDAL::executeSql($sql);
451  if ($ok === false) {
452  printUpdateStatus('Failure, unable to run query: ' . $sql);
453  exit;
454  }
455  printUpdateStatus('OK');
456 
457  if ($table_info['rollback']) {
458  printName('Creating index sq_rb_'.$tablename.'_'.$index_info['name']);
459  $sql = create_index_sql('rb_'.$tablename, $index_info['columns'], $index_info['name'], $index_info['type']);
460  $ok = MatrixDAL::executeSql($sql);
461  if ($ok === false) {
462  printUpdateStatus('Failure, unable to run query: ' . $sql);
463  exit;
464  }
465  printUpdateStatus('OK');
466  }
467  }// end foreach
468  }//end if
469  }
470 
471  pre_echo('Rebuilding Sequences');
472 
473  foreach ($info['sequences'] as $sequence) {
474  $new_seq_start = $sequence_values[$sequence];
475 
476  printName('Creating sq_'.$sequence.'_seq ('.$new_seq_start.')');
477  $sql = 'CREATE SEQUENCE sq_'.$sequence.'_seq START WITH '.$new_seq_start;
478  $ok = MatrixDAL::executeSql($sql);
479  if ($ok === false) {
480  printUpdateStatus('Failure, unable to run query: ' . $sql);
481  exit;
482  }
483  printUpdateStatus('OK');
484  }
485 
486  $first_run = false;
487 }
488 
493 MatrixDAL::dbClose($dest_db);
494 MatrixDAL::dbClose($source_db);
495 
496 pre_echo('Conversion is complete');
497 
498 echo "\n";
499 $rebakeCmd = "/usr/bin/php ${SYSTEM_ROOT}/scripts/dev/rebake.php ${SYSTEM_ROOT}";
500 echo "rebake.php now needs to run. Do this now [Y/n] ? ";
501 $response = strtolower(trim(fgets(STDIN)));
502 if (empty($response) === TRUE) {
503  $response = 'y';
504 }
505 
506 if ($response != 'y') {
507  pre_echo("You will need to run ${rebakeCmd} manually.\n");
508  exit;
509 }
510 
511 echo "Running rebake.php now .. \n";
512 $output = array();
513 $rc = -1;
514 exec($rebakeCmd, $output, $rc);
515 pre_echo("Rebake returned the following:\n".implode("\n", $output)."\n");
516 pre_echo('This script is now finished.');
517 
527 function parse_tables_xml($xml_file, $dest_db)
528 {
529  try {
530  $root = new SimpleXMLElement($xml_file, LIBXML_NOCDATA, TRUE);
531  } catch (Exception $e) {
532  throw new Exception('Could not parse tables XML file: '.$e->getMessage());
533  }
534 
535  if (($root->getName() != 'schema') || !isset($root->tables) || !isset($root->sequences)) {
536  throw new Exception('Tables XML file is not valid.');
537  trigger_localised_error('SYS0012', E_USER_WARNING);
538  return FALSE;
539  }
540 
541  $info = Array();
542  $info['tables'] = Array();
543  $info['sequences'] = Array();
544 
545  //-- TABLES --//
546 
547  foreach ($root->tables->table as $table) {
548  $table_name = (string)$table->attributes()->name;
549 
550  $info['tables'][$table_name] = Array();
551  $info['tables'][$table_name]['rollback'] = (($table->attributes()->{'require_rollback'} == 1) ? TRUE : FALSE);
552 
553  //-- TABLE COLUMNS --//
554  $info['tables'][$table_name]['columns'] = Array();
555 
556  foreach ($table->columns->column as $table_column) {
557  $column_name = (string)$table_column->attributes()->name;
558 
559  $info['tables'][$table_name]['columns'][$column_name] = Array();
560  $info['tables'][$table_name]['columns'][$column_name]['allow_null'] = (($table_column->attributes()->{'allow_null'} == 1) ? TRUE : FALSE);
561 
562  //-- TABLE COLUMN VARS --//
563 
564  $type = NULL;
565  $default = NULL;
566 
567  foreach ($table_column->children() as $column_var) {
568  switch (strtolower($column_var->getName())) {
569  case 'type' :
570  // set the type of the column if it hasnt already been
571  // set in a variation (this is the default column type)
572  if (is_null($type)) $type = (string)$column_var;
573  break;
574  case 'type_variations' :
575  // check for varitions of the column type for his database
576  foreach ($column_var->children() as $variation) {
577  if ($variation->getName() == _getDbType(false, $dest_db)) {
578  $type = (string)$variation;
579  break;
580  }
581  }
582  break;
583  case 'default' :
584  if (trim((string)$column_var) != '') {
585  $default = (string)$column_var;
586  }
587  break;
588  default :
589  continue;
590  break;
591  }
592  }
593  $info['tables'][$table_name]['columns'][$column_name]['type'] = $type;
594  $info['tables'][$table_name]['columns'][$column_name]['default'] = $default;
595 
596  //-- KEYS --//
597 
598  $info['tables'][$table_name]['primary_key'] = Array();
599  $info['tables'][$table_name]['unique_key'] = Array();
600 
601  if (isset($table->keys) && (count($table->keys->children()) > 0)) {
602  foreach ($table->keys->children() as $table_key) {
603  $index_db_type = $table_key->attributes()->db;
604  if (!is_null($index_db_type) && ((string)$index_db_type != _getDbType(false, $dest_db))) {
605  continue;
606  }
607 
608  // work out the columns in this key
609  $key_columns = Array();
610  foreach ($table_key->column as $table_key_column) {
611  $col_name = (string)$table_key_column->attributes()->name;
612  $key_columns[] = $col_name;
613 
614  // cache the primary key columns for this table
615  if ($table_key->getName() == 'primary_key') {
616  $info['tables'][$table_name]['primary_key'][] = $col_name;
617  }
618  if ($table_key->getName() == 'unique_key') {
619  $info['tables'][$table_name]['unique_key'][] = $col_name;
620  }
621  }//end foreach
622  }//end foreach
623  }//end if
624 
625  //-- INDEXES --//
626 
627  // check for any indexes that need creating
628  if (!empty($table->indexes->index)) {
629  foreach ($table->indexes->index as $table_index) {
630 
631  // work out the columns in this index
632  $index_cols = Array();
633  foreach ($table_index->column as $table_index_column) {
634  $index_cols[] = (string)$table_index_column->attributes()->name;
635  }
636 
637  // work out the name of the index
638  $index_name = isset($table_index->attributes()->name) ? (string)$table_index->attributes()->name : reset($index_cols);
639  $index_type = isset($table_index->attributes()->type) ? (string)$table_index->attributes()->type : NULL;
640  $index_db_type = isset($table_index->attributes()->db) ? (string)$table_index->attributes()->db : NULL;
641 
642  $index_info = Array(
643  'name' => $index_name,
644  'columns' => $index_cols,
645  'type' => $index_type,
646  'db_type' => $index_db_type,
647  );
648  $info['tables'][$table_name]['indexes'][$index_name] = $index_info;
649  }//end for
650  }//end if
651  }//end for
652  }//end for
653 
654  foreach ($root->sequences->sequence as $sequence) {
655  $sequence_name = (string)$sequence->attributes()->name;
656  $info['sequences'][] = $sequence_name;
657  }
658 
659  return $info;
660 
661 }//end parse_tables_xml()
662 
672 function generateSQL($tablename, $columns)
673 {
674  asort($columns);
675 
676  $sql = 'INSERT INTO sq_'.$tablename . ' (' . implode(', ', $columns) . ') values (:' . implode(', :', $columns) . ')';
677 
678  return $sql;
679 
680 }//end function
681 
693 function create_index_sql($tablename, $column, $index_name=null, $index_type=null)
694 {
695  if (is_array($column)) {
696  $column = implode(',', $column);
697  }
698 
699  if (is_null($index_name)) {
700  $index_name = str_replace(',', '_', $column);
701  }
702 
703  $dbtype = _getDbType();
704 
705  $sql = 'CREATE INDEX sq_'.$tablename.'_'.$index_name.' ON sq_'.$tablename;
706 
707  if (!empty($index_type)) {
708  $sql .= '('.$column.')';
709  if ($dbtype == 'oci') {
710  $sql .= ' indextype is '.$index_type;
711  }
712  } else {
713  $sql .= ' ('.$column.')';
714  }
715 
716  return $sql;
717 
718 }//end create_index_sql()
719 
720 
722  // HELPER FUNCTIONS //
724 function printName($name)
725 {
726  printf ('%s%'.(60 - strlen($name)).'s', $name, '');
727 
728 }//end printName()
729 
730 
731 function printUpdateStatus($status, $newline="\n")
732 {
733  if ($status !== null) {
734  echo "[ $status ]";
735  }
736  echo $newline;
737 
738 }//end printUpdateStatus()
739 
740 
748 function checkDestinationDb()
749 {
750  $dbtype = _getDbType();
751 
755  switch ($dbtype) {
756  case 'oci':
757  $sql = 'select table_name from user_tables';
758  break;
759  case 'pgsql':
760  $sql = 'select table_name from information_schema.tables WHERE table_schema NOT IN (\'pg_catalog\', \'information_schema\')';
761  break;
762  }
763  $dest_tables = MatrixDAL::executeSqlAll($sql);
764  if (empty($dest_tables)) {
765  return false;
766  }
767  return true;
768 }
769 
776 function getSequences()
777 {
778  $dbtype = _getDbType();
779 
780  switch ($dbtype) {
781  case 'oci':
782  $sql = 'SELECT sequence_name FROM user_sequences';
783  break;
784  case 'pgsql':
785  $sql = 'SELECT relname FROM pg_catalog.pg_statio_user_sequences WHERE schemaname = \'public\'';
786  break;
787  }
788 
789  $sequence_names = array();
790  $sequences = MatrixDAL::executeSqlAll($sql);
791  foreach($sequences as $key => $value) {
792  $sequence_names[] = $value[0];
793  }
794  return $sequence_names;
795 }
796 
806 function getSequenceValue($sequence='')
807 {
808  $dbtype = _getDbType();
809 
810  switch ($dbtype) {
811  case 'oci':
812  $sql = 'SELECT sq_'.$sequence.'_seq.nextval FROM DUAL';
813  break;
814  case 'pgsql':
815  $sql = 'SELECT nextval(\'sq_' . $sequence . '_seq\') AS nextval';
816  break;
817  }
818  $value = MatrixDAL::executeSqlAll($sql);
819  return $value[0]['nextval'];
820 }
821 
828 function getIndexes()
829 {
830  $dbtype = _getDbType();
831 
832  switch ($dbtype) {
833  case 'oci':
834  $sql = 'SELECT index_name FROM user_indexes';
835  break;
836  case 'pgsql':
837  $sql = 'SELECT indexname from pg_indexes where tablename like \'sq_%\' and indexdef not ilike \'create unique index %\'';
838  break;
839  }
840 
841  $del_idx = array();
842  if ($sql !== false) {
843  $indexes = MatrixDAL::executeSqlAll($sql);
844  foreach($indexes as $key => $value) {
845  $del_idx[] = strtolower($value[0]);
846  }
847  }
848  return $del_idx;
849 }
850 
860 function _getDbType()
861 {
862  $dbtype = MatrixDAL::GetDbType();
863 
864  if ($dbtype instanceof PDO) {
865  $dbtype = $dbtype->getAttribute(PDO::ATTR_DRIVER_NAME);
866  }
867  return strtolower($dbtype);
868 }
869