/** * @todo should *all* sequences be dropped in stage3, rather than just the shim? * the only reason we don't is because pgsql8 drops in stage 1... */ public static function diff_sequences($ofs1, $ofs3, $old_schema, $new_schema) { $new_sequences = static::get_sequences($new_schema); if ($old_schema != null) { $old_sequences = static::get_sequences($old_schema); } else { $old_sequences = array(); } if (empty($new_sequences)) { // there are no sequences in the new schema, so if there used to be sequences, // we can just drop the whole shim table if (!empty($old_sequences)) { $ofs3->write(mysql5_sequence::get_shim_drop_sql()); } } else { // there *are* sequences in the new schema, so if there didn't used to be, // we need to add the shim in before adding any sequences if (empty($old_sequences)) { $ofs1->write(mysql5_sequence::get_shim_creation_sql() . "\n\n"); $ofs1->write(mysql5_sequence::get_creation_sql($new_schema, $new_sequences) . "\n"); } else { // there were schemas in the old schema $common_sequences = array(); // only drop sequences not in the new schema $to_drop = array(); foreach ($old_sequences as $old_seq) { if (static::schema_contains_sequence($new_schema, $old_seq['name'], true)) { // if the sequence *is* in the new schema, then it might have changed $common_sequences[(string) $old_seq['name']] = $old_seq; } else { $to_drop[] = $old_seq; } } if (!empty($to_drop)) { $ofs1->write(mysql5_sequence::get_drop_sql($old_schema, $to_drop) . "\n\n"); } // only add sequences not in the old schema $to_insert = array(); foreach ($new_sequences as $new_seq) { if (static::schema_contains_sequence($old_schema, $new_seq['name'])) { // there used to be a sequence named $new_seq['name'] self::diff_single($ofs1, $common_sequences[(string) $new_seq['name']], $new_seq); } elseif (!dbsteward::$ignore_oldnames && !empty($new_seq['oldSequenceName']) && static::schema_contains_sequence($old_schema, $new_seq['oldSequenceName'])) { // there used to be a sequence named $new_seq['oldSequenceName'] self::diff_single($ofs1, $common_sequences[(string) $new_seq['oldSequenceName']], $new_seq); } else { $to_insert[] = $new_seq; } } if (!empty($to_insert)) { $ofs1->write(mysql5_sequence::get_creation_sql($new_schema, $to_insert) . "\n"); } } } }
private function setup_shim() { $this->pdo->exec($sql = mysql5_sequence::get_shim_drop_sql()); // echo $sql . "\n\n"; $this->pdo->exec($sql = mysql5_sequence::get_shim_creation_sql()); // echo $sql . "\n\n"; }
public function testSerials() { $old = <<<XML <schema name="test0" owner="NOBODY"> </schema> XML; $new = <<<XML <schema name="test0" owner="NOBODY"> <table name="table" owner="NOBODY"> <column name="id" type="serial"/> </table> </schema> XML; // shouldn't create any serial sequences $this->common($new, $new, ''); // should create a serial sequence $expected = mysql5_sequence::get_shim_creation_sql(); $expected .= <<<SQL INSERT INTO `__sequences` (`name`, `increment`, `min_value`, `max_value`, `cur_value`, `start_value`, `cycle`) VALUES ('__test0_table_id_serial_seq', DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT); SQL; $this->common($old, $new, $expected); // should drop a serial sequence $expected = <<<SQL DROP TABLE IF EXISTS `__sequences`; DROP FUNCTION IF EXISTS `nextval`; DROP FUNCTION IF EXISTS `setval`; DROP FUNCTION IF EXISTS `currval`; DROP FUNCTION IF EXISTS `lastval`; SQL; $this->common($new, $old, $expected); $renamed = <<<XML <schema name="test0" owner="NOBODY"> <table name="table" owner="NOBODY"> <column name="newid" type="serial" oldColumnName="id"/> </table> </schema> XML; // should UPDATE for name $this->common($new, $renamed, "UPDATE `__sequences`\nSET `name` = '__test0_table_newid_serial_seq'\nWHERE `name` = '__test0_table_id_serial_seq';"); }
public static function build_schema($db_doc, $ofs, $table_depends) { // schema creation if (static::$use_schema_name_prefix) { dbsteward::info("MySQL schema name prefixing mode turned on"); } else { if (count($db_doc->schema) > 1) { throw new Exception("You cannot use more than one schema in mysql5 without schema name prefixing\nPass the --useschemaprefix flag to turn this on"); } } foreach ($db_doc->schema as $schema) { // database grants foreach ($schema->grant as $grant) { $ofs->write(mysql5_permission::get_permission_sql($db_doc, $schema, $schema, $grant) . "\n"); } // enums foreach ($schema->type as $type) { $ofs->write(mysql5_type::get_creation_sql($schema, $type) . "\n"); } // function definitions foreach ($schema->function as $function) { if (mysql5_function::has_definition($function)) { $ofs->write(mysql5_function::get_creation_sql($schema, $function) . "\n\n"); } // function grants foreach ($function->grant as $grant) { $ofs->write(mysql5_permission::get_permission_sql($db_doc, $schema, $function, $grant) . "\n"); } } $sequences = array(); $triggers = array(); // create defined tables foreach ($schema->table as $table) { // get sequences and triggers needed to make this table work $sequences = array_merge($sequences, mysql5_table::get_sequences_needed($schema, $table)); $triggers = array_merge($triggers, mysql5_table::get_triggers_needed($schema, $table)); // table definition $ofs->write(mysql5_table::get_creation_sql($schema, $table) . "\n\n"); // table indexes // mysql5_diff_indexes::diff_indexes_table($ofs, NULL, NULL, $schema, $table); // table grants if (isset($table->grant)) { foreach ($table->grant as $grant) { $ofs->write(mysql5_permission::get_permission_sql($db_doc, $schema, $table, $grant) . "\n"); } } $ofs->write("\n"); } // sequences contained in the schema + sequences used by serials $sequences = array_merge($sequences, dbx::to_array($schema->sequence)); if (count($sequences) > 0) { $ofs->write(mysql5_sequence::get_shim_creation_sql() . "\n\n"); $ofs->write(mysql5_sequence::get_creation_sql($schema, $sequences) . "\n\n"); // sequence grants foreach ($sequences as $sequence) { foreach ($sequence->grant as $grant) { $ofs->write("-- grant for the {$sequence['name']} sequence applies to ALL sequences\n"); $ofs->write(mysql5_permission::get_permission_sql($db_doc, $schema, $sequence, $grant) . "\n"); } } } // trigger definitions + triggers used by serials $triggers = array_merge($triggers, dbx::to_array($schema->trigger)); $unique_triggers = array(); foreach ($triggers as $trigger) { // only do triggers set to the current sql format if (strcasecmp($trigger['sqlFormat'], dbsteward::get_sql_format()) == 0) { // check that this table/timing/event combo hasn't been defined, because MySQL only // allows one trigger per table per BEFORE/AFTER per action $unique_name = "{$trigger['table']}-{$trigger['when']}-{$trigger['event']}"; if (array_key_exists($unique_name, $unique_triggers)) { throw new Exception("MySQL will not allow trigger {$trigger['name']} to be created because it happens on the same table/timing/event as trigger {$unique_triggers[$unique_name]}"); } $unique_triggers[$unique_name] = $trigger['name']; $ofs->write(mysql5_trigger::get_creation_sql($schema, $trigger) . "\n"); } } } foreach ($db_doc->schema as $schema) { // define table primary keys before foreign keys so unique requirements are always met for FOREIGN KEY constraints foreach ($schema->table as $table) { mysql5_diff_constraints::diff_constraints_table($ofs, NULL, NULL, $schema, $table, 'primaryKey', FALSE); } $ofs->write("\n"); } // foreign key references // use the dependency order to specify foreign keys in an order that will satisfy nested foreign keys and etc for ($i = 0; $i < count($table_depends); $i++) { $dep_schema = $table_depends[$i]['schema']; $table = $table_depends[$i]['table']; if ($table['name'] === dbsteward::TABLE_DEPENDENCY_IGNORABLE_NAME) { // don't do anything with this table, it is a magic internal DBSteward value continue; } mysql5_diff_constraints::diff_constraints_table($ofs, NULL, NULL, $dep_schema, $table, 'constraint', FALSE); } $ofs->write("\n"); mysql5_diff_views::create_views_ordered($ofs, null, $db_doc); // view permission grants foreach ($db_doc->schema as $schema) { foreach ($schema->view as $view) { if (isset($view->grant)) { foreach ($view->grant as $grant) { $ofs->write(mysql5_permission::get_permission_sql($db_doc, $schema, $view, $grant) . "\n"); } } } } // @TODO: database configurationParameter support }
public function testDelimiters() { mysql5::$swap_function_delimiters = TRUE; $actual = mysql5_sequence::get_shim_creation_sql(); $actual = trim(preg_replace('/--.*(\\n\\s*)?/', '', $actual)); $expected = <<<SQL CREATE TABLE IF NOT EXISTS `__sequences` ( `name` VARCHAR(100) NOT NULL, `increment` INT(11) unsigned NOT NULL DEFAULT 1, `min_value` INT(11) unsigned NOT NULL DEFAULT 1, `max_value` BIGINT(20) unsigned NOT NULL DEFAULT 18446744073709551615, `cur_value` BIGINT(20) unsigned DEFAULT 1, `start_value` BIGINT(20) unsigned DEFAULT 1, `cycle` BOOLEAN NOT NULL DEFAULT FALSE, `should_advance` BOOLEAN NOT NULL DEFAULT TRUE, PRIMARY KEY (`name`) ) ENGINE = MyISAM; DELIMITER \$_\$ DROP FUNCTION IF EXISTS `currval`\$_\$ CREATE FUNCTION `currval` (`seq_name` varchar(100)) RETURNS BIGINT(20) NOT DETERMINISTIC BEGIN DECLARE val BIGINT(20); IF @__sequences_lastval IS NULL THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'nextval() has not been called yet this session'; ELSE SELECT `currval` INTO val FROM `__sequences_currvals` WHERE `name` = seq_name; RETURN val; END IF; END\$_\$ DROP FUNCTION IF EXISTS `lastval`\$_\$ CREATE FUNCTION `lastval` () RETURNS BIGINT(20) NOT DETERMINISTIC BEGIN IF @__sequences_lastval IS NULL THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'nextval() has not been called yet this session'; ELSE RETURN @__sequences_lastval; END IF; END\$_\$ DROP FUNCTION IF EXISTS `nextval`\$_\$ CREATE FUNCTION `nextval` (`seq_name` varchar(100)) RETURNS BIGINT(20) NOT DETERMINISTIC BEGIN DECLARE advance BOOLEAN; CREATE TEMPORARY TABLE IF NOT EXISTS `__sequences_currvals` ( `name` VARCHAR(100) NOT NULL, `currval` BIGINT(20), PRIMARY KEY (`name`) ); SELECT `cur_value` INTO @__sequences_lastval FROM `__sequences` WHERE `name` = seq_name; SELECT `should_advance` INTO advance FROM `__sequences` WHERE `name` = seq_name; IF @__sequences_lastval IS NOT NULL THEN IF advance = TRUE THEN UPDATE `__sequences` SET `cur_value` = IF ( (`cur_value` + `increment`) > `max_value`, IF (`cycle` = TRUE, `min_value`, NULL), `cur_value` + `increment` ) WHERE `name` = seq_name; SELECT `cur_value` INTO @__sequences_lastval FROM `__sequences` WHERE `name` = seq_name; ELSE UPDATE `__sequences` SET `should_advance` = TRUE WHERE `name` = seq_name; END IF; REPLACE INTO `__sequences_currvals` (`name`, `currval`) VALUE (seq_name, @__sequences_lastval); END IF; RETURN @__sequences_lastval; END\$_\$ DROP FUNCTION IF EXISTS `setval`\$_\$ CREATE FUNCTION `setval` (`seq_name` varchar(100), `value` bigint(20), `advance` BOOLEAN) RETURNS bigint(20) NOT DETERMINISTIC BEGIN UPDATE `__sequences` SET `cur_value` = value, `should_advance` = advance WHERE `name` = seq_name; IF advance = FALSE THEN CREATE TEMPORARY TABLE IF NOT EXISTS `__sequences_currvals` ( `name` VARCHAR(100) NOT NULL, `currval` BIGINT(20), PRIMARY KEY (`name`) ); REPLACE INTO `__sequences_currvals` (`name`, `currval`) VALUE (seq_name, value); SET @__sequences_lastval = value; END IF; RETURN value; END\$_\$ DELIMITER ; SQL; $this->assertEquals($expected, $actual); }