Esempio n. 1
0
 public static function build_upgrade_slonik_replica_set($old_db_doc, $new_db_doc, $old_replica_set, $new_replica_set, $slonik_file_prefix, $origin_header = '')
 {
     dbsteward::notice("Building slonik upgrade replica set ID " . $new_replica_set['id']);
     $timestamp = date('r');
     // output preamble file standalone for tool chains that use the preamble to do additional slonik commands
     $slony_preamble_file = $slonik_file_prefix . '_preamble.slonik';
     pgsql8::build_slonik_preamble($new_db_doc, $new_replica_set, $slony_preamble_file);
     $slony_stage1_file = $slonik_file_prefix . '_stage1.slonik';
     pgsql8::build_slonik_preamble($new_db_doc, $new_replica_set, $slony_stage1_file);
     $slony_stage1_fp = fopen($slony_stage1_file, 'a');
     if ($slony_stage1_fp === FALSE) {
         throw new exception("failed to open upgrade slony stage 1 output file " . $slony_stage1_file);
     }
     $slony_stage1_ofs = new output_file_segmenter($slony_stage1_file, 1, $slony_stage1_fp, $slony_stage1_file);
     $slony_stage1_ofs->set_comment_line_prefix("#");
     // keep slonik file comment lines consistent
     $slony_stage1_ofs->write("# DBSteward slony stage 1 upgrade generated " . $timestamp . "\n");
     $slony_stage1_ofs->write($origin_header . "\n");
     $slony_stage1_ofs->write("ECHO 'DBSteward slony upgrade replica set " . $new_replica_set['id'] . " stage 1 generated " . date('r') . "';\n\n");
     $slony_stage3_file = $slonik_file_prefix . '_stage3.slonik';
     pgsql8::build_slonik_preamble($new_db_doc, $new_replica_set, $slony_stage3_file);
     $slony_stage3_fp = fopen($slony_stage3_file, 'a');
     if ($slony_stage3_fp === FALSE) {
         throw new exception("failed to open upgrade slony stage 3 output file " . $slony_stage3_file . ' for output');
     }
     $slony_stage3_ofs = new output_file_segmenter($slony_stage3_file, 1, $slony_stage3_fp, $slony_stage3_file);
     $slony_stage3_ofs->set_comment_line_prefix("#");
     // keep slonik file comment lines consistent
     $slony_stage3_ofs->write("# DBSteward slony stage 3 upgrade generated " . $timestamp . "\n");
     $slony_stage3_ofs->write($origin_header . "\n");
     $slony_stage3_ofs->write("ECHO 'DBSteward slony upgrade replica set " . $new_replica_set['id'] . " stage 3 generated " . date('r') . "';\n\n");
     // slony replication configuration changes
     // SLONY STAGE 1
     // unsubscribe to abandoned tables/sequences
     foreach ($old_db_doc->schema as $old_schema) {
         // look for the schema in the new definition
         $new_schema = dbx::get_schema($new_db_doc, $old_schema['name']);
         // slony replicated tables that are no longer present
         foreach ($old_schema->table as $old_table) {
             if (!pgsql8::slony_replica_set_contains_table($old_db_doc, $old_replica_set, $old_schema, $old_table)) {
                 // this old table is not contained in the old replica set being processed
                 continue;
             }
             $new_table = NULL;
             if ($new_schema) {
                 $new_table = dbx::get_table($new_schema, $old_table['name']);
             }
             if ($new_schema === NULL || $new_table === NULL) {
                 // schema or table no longer exists
                 // drop sequence subscriptions owned by the table
                 foreach ($old_table->column as $old_column) {
                     // is a replicated type?
                     if (preg_match(pgsql8::PATTERN_REPLICATED_COLUMN, $old_column['type']) > 0 && isset($old_column['slonyId']) && strcasecmp('IGNORE_REQUIRED', $old_column['slonyId']) != 0) {
                         $slony_stage1_ofs->write("# replicated table column " . $old_schema['name'] . '.' . $old_table['name'] . '.' . $old_column['name'] . " slonyId " . $old_table['slonyId'] . " no longer defined, dropping\n");
                         $slony_stage1_ofs->write(sprintf(slony1_slonik::script_drop_sequence, dbsteward::string_cast($old_replica_set['originNodeId']), dbsteward::string_cast($old_column['slonyId'])) . "\n\n");
                     }
                 }
                 if (isset($old_table['slonyId']) && strcasecmp('IGNORE_REQUIRED', $old_table['slonyId']) != 0) {
                     // drop table subscription to the table
                     $slony_stage1_ofs->write("# replicated table " . $old_schema['name'] . '.' . $old_table['name'] . " slonyId " . $old_table['slonyId'] . " no longer defined, dropping\n");
                     $slony_stage1_ofs->write(sprintf(slony1_slonik::script_drop_table, dbsteward::string_cast($old_replica_set['originNodeId']), dbsteward::string_cast($old_table['slonyId'])) . "\n\n");
                 }
             }
             if ($new_table !== NULL) {
                 // table exists, look for replicated columns that have been abandoned or are no longer replicated types
                 foreach ($old_table->column as $old_column) {
                     // it was previously a replicated column type?
                     if (preg_match(pgsql8::PATTERN_REPLICATED_COLUMN, $old_column['type']) > 0) {
                         $nodes = $new_table->xpath("column[@name='" . dbsteward::string_cast($old_column['name']) . "']");
                         $new_column = NULL;
                         if (count($nodes) == 1) {
                             $new_column = $nodes[0];
                             if (preg_match(pgsql8::PATTERN_REPLICATED_COLUMN, $new_column['type']) == 0) {
                                 // not replicated type anymore
                                 $new_column = NULL;
                             }
                         }
                         if ($new_column === NULL && strcasecmp('IGNORE_REQUIRED', $old_column['slonyId']) != 0) {
                             $slony_stage1_ofs->write(sprintf(slony1_slonik::script_drop_sequence, dbsteward::string_cast($old_replica_set['originNodeId']), dbsteward::string_cast($old_column['slonyId'])) . "\n\n");
                         }
                     }
                 }
             }
         }
         // slony replicated stand-alone sequences that are no longer present
         foreach ($old_schema->sequence as $old_sequence) {
             if (!static::slony_replica_set_contains_sequence($old_db_doc, $old_replica_set, $old_schema, $old_sequence)) {
                 // this old sequence is not contained in the old replica set being processed
                 continue;
             }
             $new_sequence = NULL;
             if ($new_schema !== NULL) {
                 $new_sequence = dbx::get_sequence($new_schema, $old_sequence['name']);
             }
             if (($new_schema === NULL || $new_sequence === NULL) && strcasecmp('IGNORE_REQUIRED', $old_sequence['slonyId']) != 0) {
                 // schema or sequence no longer exists, drop the sequence subscription
                 $slony_stage1_ofs->write(sprintf(slony1_slonik::script_drop_sequence, dbsteward::string_cast($old_replica_set['originNodeId']), dbsteward::string_cast($old_sequence['slonyId'])) . "\n\n");
             }
         }
     }
     $upgrade_set_created = FALSE;
     // SLONY STAGE 3
     // new table replication
     foreach ($new_db_doc->schema as $new_schema) {
         // look for the schema in the old definition
         $old_schema = dbx::get_schema($old_db_doc, $new_schema['name']);
         // new tables that were not previously present
         // new replicated columns that were not previously present
         foreach ($new_schema->table as $new_table) {
             // is this table replicated in this replica set?
             if (pgsql8::slony_replica_set_contains_table($new_db_doc, $new_replica_set, $new_schema, $new_table)) {
                 if (!is_numeric(dbsteward::string_cast($new_table['slonyId']))) {
                     throw new exception('table ' . $new_table['name'] . " slonyId " . $new_table['slonyId'] . " is not numeric");
                 }
                 if (in_array(dbsteward::string_cast($new_table['slonyId']), self::$table_slony_ids)) {
                     throw new exception("table " . $new_table['name'] . " slonyId " . $new_table['slonyId'] . " already in table_slony_ids -- duplicates not allowed");
                 }
                 self::$table_slony_ids[] = dbsteward::string_cast($new_table['slonyId']);
                 $old_table = NULL;
                 if ($old_schema) {
                     $old_table = dbx::get_table($old_schema, $new_table['name']);
                     if ($old_table && isset($old_table['slonyId']) && strcasecmp('IGNORE_REQUIRED', $old_table['slonyId']) !== 0 && strcasecmp('IGNORE_REQUIRED', $new_table['slonyId']) !== 0 && (string) $new_table['slonyId'] != (string) $old_table['slonyId']) {
                         throw new Exception("table slonyId {$new_table['slonyId']} in new does not match slonyId {$old_table['slonyId']} in old");
                     }
                 }
                 if (($old_schema === NULL || $old_table === NULL) && strcasecmp('IGNORE_REQUIRED', $new_table['slonyId']) != 0) {
                     // if it has not been declared, create the upgrade set to be merged
                     if (!$upgrade_set_created) {
                         self::create_slonik_upgrade_set($slony_stage3_ofs, $new_db_doc, $new_replica_set);
                         $upgrade_set_created = TRUE;
                     }
                     // schema or table did not exist before, add it
                     $slony_stage3_ofs->write(sprintf(slony1_slonik::script_add_table, dbsteward::string_cast($new_replica_set['upgradeSetId']), dbsteward::string_cast($new_replica_set['originNodeId']), dbsteward::string_cast($new_table['slonyId']), $new_schema['name'] . '.' . $new_table['name'], $new_schema['name'] . '.' . $new_table['name'] . ' table replication') . "\n\n");
                 }
             }
             // add table owned sequence subscriptions for any not already present
             foreach ($new_table->column as $new_column) {
                 // is this column sequence replicated in this replica set?
                 if (pgsql8::slony_replica_set_contains_table_column_serial_sequence($new_db_doc, $new_replica_set, $new_schema, $new_table, $new_column)) {
                     self::check_duplicate_sequence_slony_id((string) $new_column['name'], 'column', (string) $new_column['slonyId']);
                     self::set_sequence_slony_ids($new_column, $new_db_doc);
                     // resolve $old_table on our own -- the table itself may not be replicated
                     $old_table = NULL;
                     if ($old_schema) {
                         $old_table = dbx::get_table($old_schema, $new_table['name']);
                         if ($old_table && isset($old_table['slonyId']) && strcasecmp('IGNORE_REQUIRED', $old_table['slonyId']) !== 0 && strcasecmp('IGNORE_REQUIRED', $new_table['slonyId']) !== 0 && (string) $new_table['slonyId'] != (string) $old_table['slonyId']) {
                             throw new Exception("table slonyId {$new_table['slonyId']} in new does not match slonyId {$old_table['slonyId']} in old");
                         }
                     }
                     // schema/table/column not present before
                     $old_column = NULL;
                     if ($old_table !== NULL) {
                         $nodes = $old_table->xpath("column[@name='" . dbsteward::string_cast($new_column['name']) . "']");
                         if (count($nodes) == 1) {
                             // column is in new schema
                             $old_column = $nodes[0];
                         }
                     }
                     if ($old_column && isset($old_column['slonyId']) && strcasecmp('IGNORE_REQUIRED', $old_column['slonyId']) !== 0 && strcasecmp('IGNORE_REQUIRED', $new_column['slonyId']) !== 0 && (string) $new_column['slonyId'] != (string) $old_column['slonyId']) {
                         throw new Exception("column sequence slonyId {$new_column['slonyId']} in new does not match slonyId {$old_column['slonyId']} in old");
                     }
                     if (($old_schema === NULL || $old_table === NULL || $old_column === NULL) && strcasecmp('IGNORE_REQUIRED', $new_column['slonyId']) != 0) {
                         // if it has not been declared, create the upgrade set to be merged
                         if (!$upgrade_set_created) {
                             self::create_slonik_upgrade_set($slony_stage3_ofs, $new_db_doc, $new_replica_set);
                             $upgrade_set_created = TRUE;
                         }
                         $col_sequence = pgsql8::identifier_name($new_schema['name'], $new_table['name'], $new_column['name'], '_seq');
                         $slony_stage3_ofs->write(sprintf(slony1_slonik::script_add_sequence, dbsteward::string_cast($new_replica_set['upgradeSetId']), dbsteward::string_cast($new_replica_set['originNodeId']), dbsteward::string_cast($new_column['slonyId']), $new_schema['name'] . '.' . $col_sequence, $new_schema['name'] . '.' . $col_sequence . ' serial sequence column replication') . "\n\n");
                     }
                     // also check to make sure that additions of slonyIds in new XML
                     // where the column did exist before also generates slonik changes
                     if ($old_schema !== NULL && $old_table !== NULL && $old_column !== NULL && strcasecmp('IGNORE_REQUIRED', $new_column['slonyId']) !== 0 && !isset($old_column['slonyId'])) {
                         if (!$upgrade_set_created) {
                             self::create_slonik_upgrade_set($slony_stage3_ofs, $new_db_doc, $new_replica_set);
                             $upgrade_set_created = TRUE;
                         }
                         $col_sequence = pgsql8::identifier_name($new_schema['name'], $new_table['name'], $new_column['name'], '_seq');
                         $slony_stage3_ofs->write(sprintf(slony1_slonik::script_add_sequence, dbsteward::string_cast($new_replica_set['upgradeSetId']), dbsteward::string_cast($new_replica_set['originNodeId']), dbsteward::string_cast($new_column['slonyId']), $new_schema['name'] . '.' . $col_sequence, $new_schema['name'] . '.' . $col_sequence . ' serial sequence column replication') . "\n\n");
                     }
                 }
             }
         }
         // new stand alone sequences not owned by tables that were not previously present
         foreach ($new_schema->sequence as $new_sequence) {
             // is this sequence replicated in this replica set?
             if (pgsql8::slony_replica_set_contains_sequence($new_db_doc, $new_replica_set, $new_schema, $new_sequence)) {
                 self::check_duplicate_sequence_slony_id((string) $new_sequence['name'], 'sequence', (string) $new_sequence['slonyId']);
                 self::set_sequence_slony_ids($new_sequence, $new_db_doc);
             }
             $old_sequence = NULL;
             if ($old_schema) {
                 $old_sequence = dbx::get_sequence($old_schema, $new_sequence['name']);
             }
             if ($old_sequence && isset($old_sequence['slonyId']) && strcasecmp('IGNORE_REQUIRED', $old_sequence['slonyId']) !== 0 && strcasecmp('IGNORE_REQUIRED', $new_sequence['slonyId']) !== 0 && (string) $new_sequence['slonyId'] != (string) $old_sequence['slonyId']) {
                 throw new Exception("sequence slonyId {$new_sequence['slonyId']} in new does not match slonyId {$old_sequence['slonyId']} in old");
             }
             if (($old_schema === NULL || $old_sequence === NULL) && strcasecmp('IGNORE_REQUIRED', $new_sequence['slonyId']) != 0) {
                 // if it has not been declared, create the upgrade set to be merged
                 if (!$upgrade_set_created) {
                     self::create_slonik_upgrade_set($slony_stage3_ofs, $new_db_doc, $new_replica_set);
                     $upgrade_set_created = TRUE;
                 }
                 // sequence did not previously exist, add it
                 $slony_stage3_ofs->write(sprintf(slony1_slonik::script_add_sequence, dbsteward::string_cast($new_replica_set['upgradeSetId']), dbsteward::string_cast($new_replica_set['originNodeId']), dbsteward::string_cast($new_sequence['slonyId']), $new_schema['name'] . '.' . $new_sequence['name'], $new_schema['name'] . '.' . $new_sequence['name'] . ' sequence replication') . "\n\n");
             }
         }
     }
     // if we created an upgrade set, subscribe and merge it
     if ($upgrade_set_created) {
         $slony_stage3_ofs->write("ECHO 'Waiting for merge set creation';\n");
         $slony_stage3_ofs->write(sprintf(slony1_slonik::script_node_sync_wait, $new_replica_set['originNodeId'], $new_replica_set['originNodeId'], $new_replica_set['originNodeId']) . "\n\n");
         //
         foreach ($new_replica_set->slonyReplicaSetNode as $replica_node) {
             // subscribe replicaNode to its provider node providerId
             $slony_stage3_ofs->write("ECHO 'Subscribing replicaNode " . $replica_node['id'] . " to providerNodeId " . $replica_node['providerNodeId'] . " set ID " . $new_replica_set['upgradeSetId'] . "';\n");
             $slony_stage3_ofs->write(sprintf(slony1_slonik::script_subscribe_set, $new_replica_set['upgradeSetId'], $replica_node['providerNodeId'], $replica_node['id']) . "\n\n");
             // do a sync and wait for it on the subscribing node
             $slony_stage3_ofs->write("ECHO 'Waiting for replicaNode " . $replica_node['id'] . " subscription to providerNodeId " . $replica_node['providerNodeId'] . " set ID " . $new_replica_set['upgradeSetId'] . "';\n");
             $slony_stage3_ofs->write(sprintf(slony1_slonik::script_node_sync_wait, $new_replica_set['originNodeId'], $new_replica_set['originNodeId'], $replica_node['id']) . "\n\n");
         }
         // now we can merge the upgrade set to the main
         $slony_stage3_ofs->write("ECHO 'Merging replicationUpgradeSet " . $new_replica_set['upgradeSetId'] . " to set " . $new_replica_set['id'] . "';\n");
         $slony_stage3_ofs->write(sprintf(slony1_slonik::script_merge_set, $new_replica_set['id'], $new_replica_set['upgradeSetId'], $new_replica_set['originNodeId']) . "\n\n");
     }
     // execute post-slony shaping SQL DDL / DCL commands at the end of stage 1 and 3 .slonik files
     $sql_stage1_file = $slonik_file_prefix . '_stage1_schema1.sql';
     // TODO: need to collect sql_stage1_file names from diff_doc() if there is
     // more than one as a result of many changes between definition files
     $slony_stage1_ofs->write("ECHO 'DBSteward upgrade replica set " . $new_replica_set['id'] . " stage 1 SQL EXECUTE SCRIPT';\n");
     $slony_stage1_ofs->write("EXECUTE SCRIPT (\n  FILENAME = '" . basename($sql_stage1_file) . "',\n  EVENT NODE = " . $new_replica_set['originNodeId'] . "\n);\n\n");
     $sql_stage3_file = $slonik_file_prefix . '_stage3_schema1.sql';
     $slony_stage3_ofs->write("ECHO 'DBSteward upgrade replica set " . $new_replica_set['id'] . " stage 3 SQL EXECUTE SCRIPT';\n");
     $slony_stage3_ofs->write("EXECUTE SCRIPT (\n  FILENAME = '" . basename($sql_stage3_file) . "',\n  EVENT NODE = " . $new_replica_set['originNodeId'] . "\n);\n\n");
 }