diff options
-rw-r--r-- | Bugzilla/Install/DB.pm | 2174 | ||||
-rw-r--r-- | Bugzilla/Install/Filesystem.pm | 11 | ||||
-rwxr-xr-x | checksetup.pl | 2111 |
3 files changed, 2187 insertions, 2109 deletions
diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm new file mode 100644 index 000000000..c30dbf2dd --- /dev/null +++ b/Bugzilla/Install/DB.pm @@ -0,0 +1,2174 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org> + +package Bugzilla::Install::DB; + +# NOTE: This package may "use" any modules that it likes, +# localconfig is available, and params are up to date. + +use strict; + +use Bugzilla::Bug qw(is_open_state); +use Bugzilla::Constants; +use Bugzilla::Field; +use Bugzilla::Util; +use Bugzilla::Series; + +use Date::Parse; +use Date::Format; +use IO::File; + +use base qw(Exporter); +our @EXPORT = qw( + indicate_progress +); + +sub indicate_progress { + my ($params) = @_; + my $current = $params->{current}; + my $total = $params->{total}; + my $every = $params->{every} || 1; + + print "." if !($current % $every); + if ($current % ($every * 50) == 0) { + print "$current/$total (" . int($current * 100 / $total) . "%)\n"; + } +} + + +# Small changes can be put directly into this function. +# However, larger changes (more than three or four lines) should +# go into their own private subroutine, and you should call that +# subroutine from this function. That keeps this function readable. +# +# This function runs in historical order--from upgrades that older +# installations need, to upgrades that newer installations need. +# The order of items inside this function should only be changed if +# absolutely necessary. +# +# The subroutines should have long, descriptive names, so that you +# can easily see what is being done, just by reading this function. +# +# This function is mostly self-documenting. If you're curious about +# what each of the added/removed columns does, you should see the schema +# docs at: +# http://www.ravenbrook.com/project/p4dti/tool/cgi/bugzilla-schema/ +# +# When you add a change, you should only add a comment if you want +# to describe why the change was made. You don't need to describe +# the purpose of a column. +# +sub update_table_definitions { + my $dbh = Bugzilla->dbh; + _update_pre_checksetup_bugzillas(); + + $dbh->bz_add_column('attachments', 'submitter_id', + {TYPE => 'INT3', NOTNULL => 1}, 0); + + $dbh->bz_rename_column('bugs_activity', 'when', 'bug_when'); + + _add_bug_vote_cache(); + _update_product_name_definition(); + _add_bug_keyword_cache(); + + $dbh->bz_add_column('profiles', 'disabledtext', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); + + _populate_longdescs(); + _update_bugs_activity_field_to_fieldid(); + + if (!$dbh->bz_column_info('bugs', 'lastdiffed')) { + $dbh->bz_add_column('bugs', 'lastdiffed', {TYPE =>'DATETIME'}); + $dbh->do('UPDATE bugs SET lastdiffed = NOW()'); + } + + _add_unique_login_name_index_to_profiles(); + + $dbh->bz_add_column('profiles', 'mybugslink', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); + + _update_component_user_fields_to_ids(); + + $dbh->bz_add_column('bugs', 'everconfirmed', + {TYPE => 'BOOLEAN', NOTNULL => 1}, 1); + + $dbh->bz_add_column('products', 'maxvotesperbug', + {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '10000'}); + $dbh->bz_add_column('products', 'votestoconfirm', + {TYPE => 'INT2', NOTNULL => 1}, 0); + + _populate_milestones_table(); + + # 2000-03-22 Changed the default value for target_milestone to be "---" + # (which is still not quite correct, but much better than what it was + # doing), and made the size of the value field in the milestones table match + # the size of the target_milestone field in the bugs table. + $dbh->bz_alter_column('bugs', 'target_milestone', + {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"}); + $dbh->bz_alter_column('milestones', 'value', + {TYPE => 'varchar(20)', NOTNULL => 1}); + + _add_products_defaultmilestone(); + + # 2000-03-24 Added unique indexes into the cc and keyword tables. This + # prevents certain database inconsistencies, and, moreover, is required for + # new generalized list code to work. + if (!$dbh->bz_index_info('cc', 'cc_bug_id_idx')->{TYPE}) { + $dbh->bz_drop_index('cc', 'cc_bug_id_idx'); + $dbh->bz_add_index('cc', 'cc_bug_id_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(bug_id who)]}); + } + if (!$dbh->bz_index_info('keywords', 'keywords_bug_id_idx')->{TYPE}) { + $dbh->bz_drop_index('keywords', 'keywords_bug_id_idx'); + $dbh->bz_add_index('keywords', 'keywords_bug_id_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(bug_id keywordid)]}); + } + + _copy_from_comments_to_longdescs(); + _populate_duplicates_table(); + + if (!$dbh->bz_column_info('email_setting', 'user_id')) { + $dbh->bz_add_column('profiles', 'emailflags', {TYPE => 'MEDIUMTEXT'}); + } + + $dbh->bz_add_column('groups', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); + + $dbh->bz_add_column('attachments', 'isobsolete', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + + $dbh->bz_drop_column("profiles", "emailnotification"); + $dbh->bz_drop_column("profiles", "newemailtech"); + + # 2003-11-19; chicks@chicks.net; bug 225973: fix field size to accommodate + # wider algorithms such as Blowfish. Note that this needs to be run + # before recrypting passwords in the following block. + $dbh->bz_alter_column('profiles', 'cryptpassword', + {TYPE => 'varchar(128)'}); + + _recrypt_plaintext_passwords(); + + # 2001-06-06 justdave@syndicomm.com: + # There was no index on the 'who' column in the long descriptions table. + # This caused queries by who posted comments to take a LONG time. + # http://bugzilla.mozilla.org/show_bug.cgi?id=57350 + $dbh->bz_add_index('longdescs', 'longdescs_who_idx', [qw(who)]); + + # 2001-06-15 kiko@async.com.br - Change bug:version size to avoid + # truncates re http://bugzilla.mozilla.org/show_bug.cgi?id=9352 + $dbh->bz_alter_column('bugs', 'version', + {TYPE => 'varchar(64)', NOTNULL => 1}); + + _update_bugs_activity_to_only_record_changes(); + + $dbh->bz_alter_column("profiles", "disabledtext", + {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); + + $dbh->bz_add_column("bugs", "reporter_accessible", + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); + $dbh->bz_add_column("bugs", "cclist_accessible", + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); + + $dbh->bz_add_column("bugs_activity", "attach_id", {TYPE => 'INT3'}); + + _delete_logincookies_cryptpassword_and_handle_invalid_cookies(); + + # qacontact/assignee should always be able to see bugs: bug 97471 + $dbh->bz_drop_column("bugs", "qacontact_accessible"); + $dbh->bz_drop_column("bugs", "assignee_accessible"); + + # 2002-02-20 jeff.hedlund@matrixsi.com - bug 24789 time tracking + $dbh->bz_add_column("longdescs", "work_time", + {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'}); + $dbh->bz_add_column("bugs", "estimated_time", + {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'}); + $dbh->bz_add_column("bugs", "remaining_time", + {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'}); + $dbh->bz_add_column("bugs", "deadline", {TYPE => 'DATETIME'}); + + _use_ip_instead_of_hostname_in_logincookies(); + + $dbh->bz_add_column('longdescs', 'isprivate', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + $dbh->bz_add_column('attachments', 'isprivate', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + + $dbh->bz_add_column("bugs", "alias", {TYPE => "varchar(20)"}); + $dbh->bz_add_index('bugs', 'bugs_alias_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(alias)]}); + + _move_quips_into_db(); + + $dbh->bz_drop_column("namedqueries", "watchfordiffs"); + + _use_ids_for_products_and_components(); + _convert_groups_system_from_groupset(); + _convert_attachment_statuses_to_flags(); + _remove_spaces_and_commas_from_flagtypes(); + _setup_usebuggroups_backward_compatibility(); + _remove_user_series_map(); + _copy_old_charts_into_database(); + + Bugzilla::Field::create_or_update( + {name => "owner_idle_time", desc => "Time Since Assignee Touched"}); + + _add_user_group_map_grant_type(); + _add_group_group_map_grant_type(); + + $dbh->bz_add_column("profiles", "extern_id", {TYPE => 'varchar(64)'}); + + $dbh->bz_add_column('flagtypes', 'grant_group_id', {TYPE => 'INT3'}); + $dbh->bz_add_column('flagtypes', 'request_group_id', {TYPE => 'INT3'}); + + # mailto is no longer just userids + $dbh->bz_rename_column('whine_schedules', 'mailto_userid', 'mailto'); + $dbh->bz_add_column('whine_schedules', 'mailto_type', + {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'}); + + _add_longdescs_already_wrapped(); + + # Moved enum types to separate tables so we need change the old enum + # types to standard varchars in the bugs table. + $dbh->bz_alter_column('bugs', 'bug_status', + {TYPE => 'varchar(64)', NOTNULL => 1}); + # 2005-03-23 Tomas.Kopal@altap.cz - add default value to resolution, + # bug 286695 + $dbh->bz_alter_column('bugs', 'resolution', + {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "''"}); + $dbh->bz_alter_column('bugs', 'priority', + {TYPE => 'varchar(64)', NOTNULL => 1}); + $dbh->bz_alter_column('bugs', 'bug_severity', + {TYPE => 'varchar(64)', NOTNULL => 1}); + $dbh->bz_alter_column('bugs', 'rep_platform', + {TYPE => 'varchar(64)', NOTNULL => 1}, ''); + $dbh->bz_alter_column('bugs', 'op_sys', + {TYPE => 'varchar(64)', NOTNULL => 1}); + + # When migrating quips from the '$datadir/comments' file to the DB, + # the user ID should be NULL instead of 0 (which is an invalid user ID). + if ($dbh->bz_column_info('quips', 'userid')->{NOTNULL}) { + $dbh->bz_alter_column('quips', 'userid', {TYPE => 'INT3'}); + print "Changing owner to NULL for quips where the owner is", + " unknown...\n"; + $dbh->do('UPDATE quips SET userid = NULL WHERE userid = 0'); + } + + $dbh->bz_add_index('bugs', 'bugs_short_desc_idx', + {TYPE => 'FULLTEXT', FIELDS => [qw(short_desc)]}); + + # Right now, we only create the "thetext" index on MySQL. + if ($dbh->isa('Bugzilla::DB::Mysql')) { + $dbh->bz_add_index('longdescs', 'longdescs_thetext_idx', + {TYPE => 'FULLTEXT', FIELDS => [qw(thetext)]}); + } + + _convert_attachments_filename_from_mediumtext(); + + $dbh->bz_add_column('quips', 'approved', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); + + # 2002-12-20 Bug 180870 - remove manual shadowdb replication code + $dbh->bz_drop_table("shadowlog"); + + _rename_votes_count_and_force_group_refresh(); + + # 2004/02/15 - Summaries shouldn't be null - see bug 220232 + if (!exists $dbh->bz_column_info('bugs', 'short_desc')->{NOTNULL}) { + $dbh->bz_alter_column('bugs', 'short_desc', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); + } + + $dbh->bz_add_column('products', 'classification_id', + {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '1'}); + + _fix_group_with_empty_name(); + + $dbh->bz_add_index('bugs_activity', 'bugs_activity_who_idx', [qw(who)]); + + # Add defaults for some fields that should have them but didn't. + $dbh->bz_alter_column('bugs', 'status_whiteboard', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}); + $dbh->bz_alter_column('bugs', 'keywords', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}); + $dbh->bz_alter_column('bugs', 'votes', + {TYPE => 'INT3', NOTNULL => 1, DEFAULT => '0'}); + + $dbh->bz_alter_column('bugs', 'lastdiffed', {TYPE => 'DATETIME'}); + + # 2005-03-09 qa_contact should be NULL instead of 0, bug 285534 + if ($dbh->bz_column_info('bugs', 'qa_contact')->{NOTNULL}) { + $dbh->bz_alter_column('bugs', 'qa_contact', {TYPE => 'INT3'}); + $dbh->do("UPDATE bugs SET qa_contact = NULL WHERE qa_contact = 0"); + } + + # 2005-03-27 initialqacontact should be NULL instead of 0, bug 287483 + if ($dbh->bz_column_info('components', 'initialqacontact')->{NOTNULL}) { + $dbh->bz_alter_column('components', 'initialqacontact', + {TYPE => 'INT3'}); + $dbh->do("UPDATE components SET initialqacontact = NULL " . + "WHERE initialqacontact = 0"); + } + + _migrate_email_prefs_to_new_table(); + _initialize_dependency_tree_changes_email_pref(); + _change_all_mysql_booleans_to_tinyint(); + + +} + +# Subroutines should be ordered in the order that they are called. +# Thus, newer subroutines should be at the bottom. + +sub _update_pre_checksetup_bugzillas { + my $dbh = Bugzilla->dbh; + # really old fields that were added before checksetup.pl existed + # but aren't in very old bugzilla's (like 2.1) + # Steve Stock (sstock@iconnect-inc.com) + + $dbh->bz_add_column('bugs', 'target_milestone', + {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"}); + $dbh->bz_add_column('bugs', 'qa_contact', {TYPE => 'INT3'}); + $dbh->bz_add_column('bugs', 'status_whiteboard', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}); + $dbh->bz_add_column('products', 'disallownew', + {TYPE => 'BOOLEAN', NOTNULL => 1}, 0); + $dbh->bz_add_column('products', 'milestoneurl', + {TYPE => 'TINYTEXT', NOTNULL => 1}, ''); + $dbh->bz_add_column('components', 'initialqacontact', + {TYPE => 'TINYTEXT'}); + $dbh->bz_add_column('components', 'description', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); +} + +sub _add_bug_vote_cache { + my $dbh = Bugzilla->dbh; + # 1999-10-11 Restructured voting database to add a cached value in each + # bug recording how many total votes that bug has. While I'm at it, + # I removed the unused "area" field from the bugs database. It is + # distressing to realize that the bugs table has reached the maximum + # number of indices allowed by MySQL (16), which may make future + # enhancements awkward. + # (P.S. All is not lost; it appears that the latest betas of MySQL + # support a new table format which will allow 32 indices.) + + $dbh->bz_drop_column('bugs', 'area'); + if (!$dbh->bz_column_info('bugs', 'votes')) { + $dbh->bz_add_column('bugs', 'votes', {TYPE => 'INT3', NOTNULL => 1, + DEFAULT => 0}); + $dbh->bz_add_index('bugs', 'bugs_votes_idx', [qw(votes)]); + } + $dbh->bz_add_column('products', 'votesperuser', + {TYPE => 'INT2', NOTNULL => 1}, 0); +} + +sub _update_product_name_definition { + my $dbh = Bugzilla->dbh; + # The product name used to be very different in various tables. + # + # It was varchar(16) in bugs + # tinytext in components + # tinytext in products + # tinytext in versions + # + # tinytext is equivalent to varchar(255), which is quite huge, so I change + # them all to varchar(64). + + # Only do this if these fields still exist - they're removed in + # a later change + if ($dbh->bz_column_info('products', 'product')) { + $dbh->bz_alter_column('bugs', 'product', + {TYPE => 'varchar(64)', NOTNULL => 1}); + $dbh->bz_alter_column('components', 'program', {TYPE => 'varchar(64)'}); + $dbh->bz_alter_column('products', 'product', {TYPE => 'varchar(64)'}); + $dbh->bz_alter_column('versions', 'program', + {TYPE => 'varchar(64)', NOTNULL => 1}); + } +} + +sub _add_bug_keyword_cache { + my $dbh = Bugzilla->dbh; + # 2000-01-16 Added a "keywords" field to the bugs table, which + # contains a string copy of the entries of the keywords table for this + # bug. This is so that I can easily sort and display a keywords + # column in bug lists. + + if (!$dbh->bz_column_info('bugs', 'keywords')) { + $dbh->bz_add_column('bugs', 'keywords', + {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}); + + my @kwords; + print "Making sure 'keywords' field of table 'bugs' is empty...\n"; + $dbh->do("UPDATE bugs SET keywords = '' WHERE keywords != ''"); + print "Repopulating 'keywords' field of table 'bugs'...\n"; + my $sth = $dbh->prepare("SELECT keywords.bug_id, keyworddefs.name " . + "FROM keywords, keyworddefs " . + "WHERE keyworddefs.id = keywords.keywordid " . + "ORDER BY keywords.bug_id, keyworddefs.name"); + $sth->execute; + my @list; + my $bugid = 0; + my @row; + while (1) { + my ($b, $k) = ($sth->fetchrow_array()); + if (!defined $b || $b ne $bugid) { + if (@list) { + $dbh->do("UPDATE bugs SET keywords = " . + $dbh->quote(join(', ', @list)) . + " WHERE bug_id = $bugid"); + } + last if !$b; + $bugid = $b; + @list = (); + } + push(@list, $k); + } + } +} + +# A helper for the function below. +sub _write_one_longdesc { + my ($id, $who, $when, $buffer) = (@_); + my $dbh = Bugzilla->dbh; + $buffer = trim($buffer); + return if !$buffer; + $dbh->do("INSERT INTO longdescs (bug_id, who, bug_when, thetext) + VALUES (?,?,?,?)", undef, $id, $who, + time2str("%Y/%m/%d %H:%M:%S", $when), $buffer); +} + +sub _populate_longdescs { + my $dbh = Bugzilla->dbh; + # 2000-01-20 Added a new "longdescs" table, which is supposed to have + # all the long descriptions in it, replacing the old long_desc field + # in the bugs table. The below hideous code populates this new table + # with things from the old field, with ugly parsing and heuristics. + + if ($dbh->bz_column_info('bugs', 'long_desc')) { + my ($total) = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs"); + + print "Populating new long_desc table. This is slow. There are", + " $total\nbugs to process; a line of dots will be printed", + " for each 50.\n\n"; + local $| = 1; + + $dbh->bz_lock_tables('bugs write', 'longdescs write', 'profiles write', + 'bz_schema WRITE'); + + $dbh->do('DELETE FROM longdescs'); + + my $sth = $dbh->prepare("SELECT bug_id, creation_ts, reporter, + long_desc FROM bugs ORDER BY bug_id"); + $sth->execute(); + my $count = 0; + while (my ($id, $createtime, $reporterid, $desc) = + $sth->fetchrow_array()) + { + $count++; + indicate_progress({ total => $total, current => $count }); + $desc =~ s/\r//g; + my $who = $reporterid; + my $when = str2time($createtime); + my $buffer = ""; + foreach my $line (split(/\n/, $desc)) { + $line =~ s/\s+$//g; # Trim trailing whitespace. + if ($line =~ /^------- Additional Comments From ([^\s]+)\s+(\d.+\d)\s+-------$/) + { + my $name = $1; + my $date = str2time($2); + # Oy, what a hack. The creation time is accurate to the + # second. But the long text only contains things accurate + # to the And so, if someone makes a comment within a + # minute of the original bug creation, then the comment can + # come *before* the bug creation. So, we add 59 seconds to + # the time of all comments, so that they are always + # considered to have happened at the *end* of the given + # minute, not the beginning. + $date += 59; + if ($date >= $when) { + _write_one_longdesc($id, $who, $when, $buffer); + $buffer = ""; + $when = $date; + my $s2 = $dbh->prepare("SELECT userid FROM profiles " . + "WHERE login_name = ?"); + $s2->execute($name); + ($who) = ($s2->fetchrow_array()); + + if (!$who) { + # This username doesn't exist. Maybe someone + # renamed him or something. Invent a new profile + # entry disabled, just to represent him. + $dbh->do("INSERT INTO profiles (login_name, + cryptpassword, disabledtext) + VALUES (?,?,?)", undef, $name, '*', + "Account created only to maintain" + . " database integrity"); + $who = $dbh->bz_last_key('profiles', 'userid'); + } + next; + } + } + $buffer .= $line . "\n"; + } + _write_one_longdesc($id, $who, $when, $buffer); + } # while loop + + print "\n\n"; + $dbh->bz_drop_column('bugs', 'long_desc'); + $dbh->bz_unlock_tables(); + } # main if +} + +sub _update_bugs_activity_field_to_fieldid { + my $dbh = Bugzilla->dbh; + + # 2000-01-18 Added a new table fielddefs that records information about the + # different fields we keep an activity log on. The bugs_activity table + # now has a pointer into that table instead of recording the name directly. + if ($dbh->bz_column_info('bugs_activity', 'field')) { + $dbh->bz_add_column('bugs_activity', 'fieldid', + {TYPE => 'INT3', NOTNULL => 1}, 0); + + $dbh->bz_add_index('bugs_activity', 'bugs_activity_fieldid_idx', + [qw(fieldid)]); + print "Populating new bugs_activity.fieldid field...\n"; + + $dbh->bz_lock_tables('bugs_activity WRITE', 'fielddefs WRITE'); + + + my $ids = $dbh->selectall_arrayref( + 'SELECT DISTINCT fielddefs.id, bugs_activity.field + FROM bugs_activity LEFT JOIN fielddefs + ON bugs_activity.field = fielddefs.name', {Slice=>{}}); + + foreach my $item (@$ids) { + my $id = $item->{id}; + my $field = $item->{field}; + # If the id is NULL + if (!$id) { + $dbh->do("INSERT INTO fielddefs (name, description) VALUES " . + "(?, ?)", undef, $field, $field); + $id = $dbh->bz_last_key('fielddefs', 'id'); + } + $dbh->do("UPDATE bugs_activity SET fieldid = ? WHERE field = ?", + undef, $id, $field); + } + $dbh->bz_unlock_tables(); + + $dbh->bz_drop_column('bugs_activity', 'field'); + } +} + +sub _add_unique_login_name_index_to_profiles { + my $dbh = Bugzilla->dbh; + + # 2000-01-22 The "login_name" field in the "profiles" table was not + # declared to be unique. Sure enough, somehow, I got 22 duplicated entries + # in my database. This code detects that, cleans up the duplicates, and + # then tweaks the table to declare the field to be unique. What a pain. + if (!$dbh->bz_index_info('profiles', 'profiles_login_name_idx') || + !$dbh->bz_index_info('profiles', 'profiles_login_name_idx')->{TYPE}) { + print "Searching for duplicate entries in the profiles table...\n"; + while (1) { + # This code is weird in that it loops around and keeps doing this + # select again. That's because I'm paranoid about deleting entries + # out from under us in the profiles table. Things get weird if + # there are *three* or more entries for the same user... + my $sth = $dbh->prepare("SELECT p1.userid, p2.userid, p1.login_name + FROM profiles AS p1, profiles AS p2 + WHERE p1.userid < p2.userid + AND p1.login_name = p2.login_name + ORDER BY p1.login_name"); + $sth->execute(); + my ($u1, $u2, $n) = ($sth->fetchrow_array); + last if !$u1; + + print "Both $u1 & $u2 are ids for $n! Merging $u2 into $u1...\n"; + foreach my $i (["bugs", "reporter"], + ["bugs", "assigned_to"], + ["bugs", "qa_contact"], + ["attachments", "submitter_id"], + ["bugs_activity", "who"], + ["cc", "who"], + ["votes", "who"], + ["longdescs", "who"]) { + my ($table, $field) = (@$i); + print " Updating $table.$field...\n"; + $dbh->do("UPDATE $table SET $field = $u1 " . + "WHERE $field = $u2"); + } + $dbh->do("DELETE FROM profiles WHERE userid = $u2"); + } + print "OK, changing index type to prevent duplicates in the", + " future...\n"; + + $dbh->bz_drop_index('profiles', 'profiles_login_name_idx'); + $dbh->bz_add_index('profiles', 'profiles_login_name_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(login_name)]}); + } +} + +sub _update_component_user_fields_to_ids { + my $dbh = Bugzilla->dbh; + + # components.initialowner + my $comp_init_owner = $dbh->bz_column_info('components', 'initialowner'); + if ($comp_init_owner && $comp_init_owner->{TYPE} eq 'TINYTEXT') { + my $sth = $dbh->prepare("SELECT program, value, initialowner + FROM components"); + $sth->execute(); + while (my ($program, $value, $initialowner) = $sth->fetchrow_array()) { + my ($id) = $dbh->selectrow_array( + "SELECT userid FROM profiles WHERE login_name = ?", + undef, $initialowner); + + unless (defined $id) { + print "Warning: You have an invalid default assignee", + " '$initialowner'\n in component '$value' of program", + " '$program'!\n"; + $id = 0; + } + + $dbh->do("UPDATE components SET initialowner = ? + WHERE program = ? AND value = ?", undef, + $id, $program, $value); + } + $dbh->bz_alter_column('components','initialowner',{TYPE => 'INT3'}); + } + + # components.initialqacontact + my $comp_init_qa = $dbh->bz_column_info('components', 'initialqacontact'); + if ($comp_init_qa && $comp_init_qa->{TYPE} eq 'TINYTEXT') { + my $sth = $dbh->prepare("SELECT program, value, initialqacontact + FROM components"); + $sth->execute(); + while (my ($program, $value, $initialqacontact) = + $sth->fetchrow_array()) + { + my ($id) = $dbh->selectrow_array( + "SELECT userid FROM profiles WHERE login_name = ?", + undef, $initialqacontact); + + unless (defined $id) { + if ($initialqacontact) { + print "Warning: You have an invalid default QA contact", + " $initialqacontact' in program '$program',", + " component '$value'!\n"; + } + $id = 0; + } + + $dbh->do("UPDATE components SET initialqacontact = ? + WHERE program = ? AND value = ?", undef, + $id, $program, $value); + } + + $dbh->bz_alter_column('components','initialqacontact',{TYPE => 'INT3'}); + } +} + +sub _populate_milestones_table { + my $dbh = Bugzilla->dbh; + # 2000-03-21 Adding a table for target milestones to + # database - matthew@zeroknowledge.com + # If the milestones table is empty, and we're still back in a Bugzilla + # that has a bugs.product field, that means that we just created + # the milestones table and it needs to be populated. + my $milestones_exist = $dbh->selectrow_array( + "SELECT DISTINCT 1 FROM milestones"); + if (!$milestones_exist && $dbh->bz_column_info('bugs', 'product')) { + print "Replacing blank milestones...\n"; + + $dbh->do("UPDATE bugs + SET target_milestone = '---' + WHERE target_milestone = ' '"); + + # If we are upgrading from 2.8 or earlier, we will have *created* + # the milestones table with a product_id field, but Bugzilla expects + # it to have a "product" field. So we change the field backward so + # other code can run. The change will be reversed later in checksetup. + if ($dbh->bz_column_info('milestones', 'product_id')) { + # Dropping the column leaves us with a milestones_product_id_idx + # index that is only on the "value" column. We need to drop the + # whole index so that it can be correctly re-created later. + $dbh->bz_drop_index('milestones', 'milestones_product_id_idx'); + $dbh->bz_drop_column('milestones', 'product_id'); + $dbh->bz_add_column('milestones', 'product', + {TYPE => 'varchar(64)', NOTNULL => 1}, ''); + } + + # Populate the milestone table with all existing values in the database + my $sth = $dbh->prepare("SELECT DISTINCT target_milestone, product + FROM bugs"); + $sth->execute(); + + print "Populating milestones table...\n"; + + while (my ($value, $product) = $sth->fetchrow_array()) { + # check if the value already exists + my $sortkey = substr($value, 1); + if ($sortkey !~ /^\d+$/) { + $sortkey = 0; + } else { + $sortkey *= 10; + } + my $ms_exists = $dbh->selectrow_array( + "SELECT value FROM milestones + WHERE value = ? AND product = ?", undef, $value, $product); + + if (!$ms_exists) { + $dbh->do("INSERT INTO milestones(value, product, sortkey) + VALUES (?,?,?)", undef, $value, $product, $sortkey); + } + } + } +} + +sub _add_products_defaultmilestone { + my $dbh = Bugzilla->dbh; + + # 2000-03-23 Added a defaultmilestone field to the products table, so that + # we know which milestone to initially assign bugs to. + if (!$dbh->bz_column_info('products', 'defaultmilestone')) { + $dbh->bz_add_column('products', 'defaultmilestone', + {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"}); + my $sth = $dbh->prepare( + "SELECT product, defaultmilestone FROM products"); + $sth->execute(); + while (my ($product, $default_ms) = $sth->fetchrow_array()) { + my $exists = $dbh->selectrow_array( + "SELECT value FROM milestones + WHERE value = ? AND product = ?", + undef, $default_ms, $product); + if (!$exists) { + $dbh->do("INSERT INTO milestones(value, product) " . + "VALUES (?, ?)", undef, $default_ms, $product); + } + } + } +} + +sub _copy_from_comments_to_longdescs { + my $dbh = Bugzilla->dbh; + # 2000-11-27 For Bugzilla 2.5 and later. Copy data from 'comments' to + # 'longdescs' - the new name of the comments table. + if ($dbh->bz_table_info('comments')) { + my $quoted_when = $dbh->quote_identifier('when'); + $dbh->do("INSERT INTO longdescs (bug_when, bug_id, who, thetext) + SELECT $quoted_when, bug_id, who, comment + FROM comments"); + $dbh->bz_drop_table("comments"); + } +} + +sub _populate_duplicates_table { + my $dbh = Bugzilla->dbh; + # 2000-07-15 Added duplicates table so Bugzilla tracks duplicates in a + # better way than it used to. This code searches the comments to populate + # the table initially. It's executed if the table is empty; if it's + # empty because there are no dupes (as opposed to having just created + # the table) it won't have any effect anyway, so it doesn't matter. + my ($dups_exist) = $dbh->selectrow_array( + "SELECT DISTINCT 1 FROM duplicates"); + # We also check against a schema change that happened later. + if (!$dups_exist && !$dbh->bz_column_info('groups', 'isactive')) { + # populate table + print "Populating duplicates table from comments...\n"; + + my $sth = $dbh->prepare( + "SELECT longdescs.bug_id, thetext + FROM longdescs LEFT JOIN bugs + ON longdescs.bug_id = bugs.bug_id + WHERE (" . $dbh->sql_regexp("thetext", + "'[.*.]{3} This bug has been marked as a duplicate" + . " of [[:digit:]]+ [.*.]{3}'") + . ") + AND resolution = 'DUPLICATE' + ORDER BY longdescs.bug_when"); + $sth->execute(); + + my (%dupes, $key); + # Because of the way hashes work, this loop removes all but the + # last dupe resolution found for a given bug. + while (my ($dupe, $dupe_of) = $sth->fetchrow_array()) { + $dupes{$dupe} = $dupe_of; + } + + foreach $key (keys(%dupes)){ + $dupes{$key} =~ /^.*\*\*\* This bug has been marked as a duplicate of (\d+) \*\*\*$/ms; + $dupes{$key} = $1; + $dbh->do("INSERT INTO duplicates VALUES(?, ?)", undef, + $dupes{$key}, $key); + # BugItsADupeOf Dupe + } + } +} + +sub _recrypt_plaintext_passwords { + my $dbh = Bugzilla->dbh; + # 2001-06-12; myk@mozilla.org; bugs 74032, 77473: + # Recrypt passwords using Perl &crypt instead of the mysql equivalent + # and delete plaintext passwords from the database. + if ($dbh->bz_column_info('profiles', 'password')) { + + print <<ENDTEXT; +Your current installation of Bugzilla stores passwords in plaintext +in the database and uses mysql's encrypt function instead of Perl's +crypt function to crypt passwords. Passwords are now going to be +re-crypted with the Perl function, and plaintext passwords will be +deleted from the database. This could take a while if your +installation has many users. +ENDTEXT + + # Re-crypt everyone's password. + my $sth = $dbh->prepare("SELECT userid, password FROM profiles"); + $sth->execute(); + + my $total = $sth->rows; + my $i = 1; + + print "Fixing passwords...\n"; + while (my ($userid, $password) = $sth->fetchrow_array()) { + my $cryptpassword = $dbh->quote(bz_crypt($password)); + $dbh->do("UPDATE profiles " . + "SET cryptpassword = $cryptpassword " . + "WHERE userid = $userid"); + indicate_progress({ total => $total, current => $i, every => 10 }); + } + print "\n"; + + # Drop the plaintext password field. + $dbh->bz_drop_column('profiles', 'password'); + } +} + +sub _update_bugs_activity_to_only_record_changes { + my $dbh = Bugzilla->dbh; + # 2001-07-20 jake@bugzilla.org - Change bugs_activity to only record changes + # http://bugzilla.mozilla.org/show_bug.cgi?id=55161 + if ($dbh->bz_column_info('bugs_activity', 'oldvalue')) { + $dbh->bz_add_column("bugs_activity", "removed", {TYPE => "TINYTEXT"}); + $dbh->bz_add_column("bugs_activity", "added", {TYPE => "TINYTEXT"}); + + # Need to get field id's for the fields that have multiple values + my @multi; + foreach my $f ("cc", "dependson", "blocked", "keywords") { + my $sth = $dbh->prepare("SELECT id " . + "FROM fielddefs " . + "WHERE name = '$f'"); + $sth->execute(); + my ($fid) = $sth->fetchrow_array(); + push (@multi, $fid); + } + + # Now we need to process the bugs_activity table and reformat the data + print "Fixing activity log...\n"; + my $sth = $dbh->prepare("SELECT bug_id, who, bug_when, fieldid, + oldvalue, newvalue FROM bugs_activity"); + $sth->execute; + my $i = 0; + my $total = $sth->rows; + while (my ($bug_id, $who, $bug_when, $fieldid, $oldvalue, $newvalue) + = $sth->fetchrow_array()) + { + $i++; + indicate_progress({ total => $total, current => $i, every => 10 }); + # Make sure (old|new)value isn't null (to suppress warnings) + $oldvalue ||= ""; + $newvalue ||= ""; + my ($added, $removed) = ""; + if (grep ($_ eq $fieldid, @multi)) { + $oldvalue =~ s/[\s,]+/ /g; + $newvalue =~ s/[\s,]+/ /g; + my @old = split(" ", $oldvalue); + my @new = split(" ", $newvalue); + my (@add, @remove) = (); + # Find values that were "added" + foreach my $value(@new) { + if (! grep ($_ eq $value, @old)) { + push (@add, $value); + } + } + # Find values that were removed + foreach my $value(@old) { + if (! grep ($_ eq $value, @new)) { + push (@remove, $value); + } + } + $added = join (", ", @add); + $removed = join (", ", @remove); + # If we can't determine what changed, put a ? in both fields + unless ($added || $removed) { + $added = "?"; + $removed = "?"; + } + # If the original field (old|new)value was full, then this + # could be incomplete data. + if (length($oldvalue) == 255 || length($newvalue) == 255) { + $added = "? $added"; + $removed = "? $removed"; + } + } else { + $removed = $oldvalue; + $added = $newvalue; + } + $added = $dbh->quote($added); + $removed = $dbh->quote($removed); + $dbh->do("UPDATE bugs_activity + SET removed = $removed, added = $added + WHERE bug_id = $bug_id AND who = $who + AND bug_when = '$bug_when' + AND fieldid = $fieldid"); + } + print "\n"; + $dbh->bz_drop_column("bugs_activity", "oldvalue"); + $dbh->bz_drop_column("bugs_activity", "newvalue"); + } +} + +sub _delete_logincookies_cryptpassword_and_handle_invalid_cookies { + my $dbh = Bugzilla->dbh; + # 2002-02-04 bbaetz@student.usyd.edu.au bug 95732 + # Remove logincookies.cryptpassword, and delete entries which become + # invalid + if ($dbh->bz_column_info("logincookies", "cryptpassword")) { + # We need to delete any cookies which are invalid before dropping the + # column + print "Removing invalid login cookies...\n"; + + # mysql doesn't support DELETE with multi-table queries, so we have + # to iterate + my $sth = $dbh->prepare("SELECT cookie FROM logincookies, profiles " . + "WHERE logincookies.cryptpassword != " . + "profiles.cryptpassword AND " . + "logincookies.userid = profiles.userid"); + $sth->execute(); + while (my ($cookie) = $sth->fetchrow_array()) { + $dbh->do("DELETE FROM logincookies WHERE cookie = $cookie"); + } + + $dbh->bz_drop_column("logincookies", "cryptpassword"); + } +} + +sub _use_ip_instead_of_hostname_in_logincookies { + my $dbh = Bugzilla->dbh; + + # 2002-03-15 bbaetz@student.usyd.edu.au - bug 129466 + # 2002-05-13 preed@sigkill.com - bug 129446 patch backported to the + # BUGZILLA-2_14_1-BRANCH as a security blocker for the 2.14.2 release + # + # Use the ip, not the hostname, in the logincookies table + if ($dbh->bz_column_info("logincookies", "hostname")) { + # We've changed what we match against, so all entries are now invalid + $dbh->do("DELETE FROM logincookies"); + + # Now update the logincookies schema + $dbh->bz_drop_column("logincookies", "hostname"); + $dbh->bz_add_column("logincookies", "ipaddr", + {TYPE => 'varchar(40)', NOTNULL => 1}, ''); + } +} + +sub _move_quips_into_db { + my $dbh = Bugzilla->dbh; + my $datadir = bz_locations->{'datadir'}; + # 2002-07-15 davef@tetsubo.com - bug 67950 + # Move quips to the db. + if (-e "$datadir/comments") { + print "Populating quips table from $datadir/comments...\n"; + my $comments = new IO::File("$datadir/comments", 'r') + || die "$datadir/comments: $!"; + $comments->untaint; + while (<$comments>) { + chomp; + $dbh->do("INSERT INTO quips (quip) VALUES (?)", undef, $_); + } + + print <<EOT; + +Quips are now stored in the database, rather than in an external file. +The quips previously stored in $datadir/comments have been copied into +the database, and that file has been renamed to $datadir/comments.bak +You may delete the renamed file once you have confirmed that all your +quips were moved successfully. + +EOT + $comments->close; + rename("$datadir/comments", "$datadir/comments.bak") + || warn "Failed to rename: $!"; + } +} + +sub _use_ids_for_products_and_components { + my $dbh = Bugzilla->dbh; + # 2002-08-12 jake@bugzilla.org/bbaetz@student.usyd.edu.au - bug 43600 + # Use integer IDs for products and components. + if ($dbh->bz_column_info("products", "product")) { + print "Updating database to use product IDs.\n"; + + # First, we need to remove possible NULL entries + # NULLs may exist, but won't have been used, since all the uses of them + # are in NOT NULL fields in other tables + $dbh->do("DELETE FROM products WHERE product IS NULL"); + $dbh->do("DELETE FROM components WHERE value IS NULL"); + + $dbh->bz_add_column("products", "id", + {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + $dbh->bz_add_column("components", "product_id", + {TYPE => 'INT2', NOTNULL => 1}, 0); + $dbh->bz_add_column("versions", "product_id", + {TYPE => 'INT2', NOTNULL => 1}, 0); + $dbh->bz_add_column("milestones", "product_id", + {TYPE => 'INT2', NOTNULL => 1}, 0); + $dbh->bz_add_column("bugs", "product_id", + {TYPE => 'INT2', NOTNULL => 1}, 0); + + # The attachstatusdefs table was added in version 2.15, but + # removed again in early 2.17. If it exists now, we still need + # to perform this change with product_id because the code later on + # which converts the attachment statuses to flags depends on it. + # But we need to avoid this if the user is upgrading from 2.14 + # or earlier (because it won't be there to convert). + if ($dbh->bz_table_info("attachstatusdefs")) { + $dbh->bz_add_column("attachstatusdefs", "product_id", + {TYPE => 'INT2', NOTNULL => 1}, 0); + } + + my %products; + my $sth = $dbh->prepare("SELECT id, product FROM products"); + $sth->execute; + while (my ($product_id, $product) = $sth->fetchrow_array()) { + if (exists $products{$product}) { + print "Ignoring duplicate product $product\n"; + $dbh->do("DELETE FROM products WHERE id = $product_id"); + next; + } + $products{$product} = 1; + $dbh->do("UPDATE components SET product_id = $product_id " . + "WHERE program = " . $dbh->quote($product)); + $dbh->do("UPDATE versions SET product_id = $product_id " . + "WHERE program = " . $dbh->quote($product)); + $dbh->do("UPDATE milestones SET product_id = $product_id " . + "WHERE product = " . $dbh->quote($product)); + $dbh->do("UPDATE bugs SET product_id = $product_id " . + "WHERE product = " . $dbh->quote($product)); + $dbh->do("UPDATE attachstatusdefs SET product_id = $product_id " . + "WHERE product = " . $dbh->quote($product)) + if $dbh->bz_table_info("attachstatusdefs"); + } + + print "Updating the database to use component IDs.\n"; + $dbh->bz_add_column("components", "id", + {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + $dbh->bz_add_column("bugs", "component_id", + {TYPE => 'INT2', NOTNULL => 1}, 0); + + my %components; + $sth = $dbh->prepare("SELECT id, value, product_id FROM components"); + $sth->execute; + while (my ($component_id, $component, $product_id) + = $sth->fetchrow_array()) + { + if (exists $components{$component}) { + if (exists $components{$component}{$product_id}) { + print "Ignoring duplicate component $component for", + " product $product_id\n"; + $dbh->do("DELETE FROM components WHERE id = $component_id"); + next; + } + } else { + $components{$component} = {}; + } + $components{$component}{$product_id} = 1; + $dbh->do("UPDATE bugs SET component_id = $component_id " . + "WHERE component = " . $dbh->quote($component) . + " AND product_id = $product_id"); + } + print "Fixing Indexes and Uniqueness.\n"; + $dbh->bz_drop_index('milestones', 'milestones_product_idx'); + + $dbh->bz_add_index('milestones', 'milestones_product_id_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(product_id value)]}); + + $dbh->bz_drop_index('bugs', 'bugs_product_idx'); + $dbh->bz_add_index('bugs', 'bugs_product_id_idx', [qw(product_id)]); + $dbh->bz_drop_index('bugs', 'bugs_component_idx'); + $dbh->bz_add_index('bugs', 'bugs_component_id_idx', [qw(component_id)]); + + print "Removing, renaming, and retyping old product and", + " component fields.\n"; + $dbh->bz_drop_column("components", "program"); + $dbh->bz_drop_column("versions", "program"); + $dbh->bz_drop_column("milestones", "product"); + $dbh->bz_drop_column("bugs", "product"); + $dbh->bz_drop_column("bugs", "component"); + $dbh->bz_drop_column("attachstatusdefs", "product") + if $dbh->bz_table_info("attachstatusdefs"); + $dbh->bz_rename_column("products", "product", "name"); + $dbh->bz_alter_column("products", "name", + {TYPE => 'varchar(64)', NOTNULL => 1}); + $dbh->bz_rename_column("components", "value", "name"); + $dbh->bz_alter_column("components", "name", + {TYPE => 'varchar(64)', NOTNULL => 1}); + + print "Adding indexes for products and components tables.\n"; + $dbh->bz_add_index('products', 'products_name_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(name)]}); + $dbh->bz_add_index('components', 'components_product_id_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(product_id name)]}); + $dbh->bz_add_index('components', 'components_name_idx', [qw(name)]); + } +} + +# Helper for the below function. +# +# _list_bits(arg) returns a list of UNKNOWN<n> if the group +# has been deleted for all bits set in arg. When the activity +# records are converted from groupset numbers to lists of +# group names, _list_bits is used to fill in a list of references +# to groupset bits for groups that no longer exist. +sub _list_bits { + my ($num) = @_; + my $dbh = Bugzilla->dbh; + my @res; + my $curr = 1; + while (1) { + # Convert a big integer to a list of bits + my $sth = $dbh->prepare("SELECT ($num & ~$curr) > 0, + ($num & $curr), + ($num & ~$curr), + $curr << 1"); + $sth->execute; + my ($more, $thisbit, $remain, $nval) = $sth->fetchrow_array; + push @res,"UNKNOWN<$curr>" if ($thisbit); + $curr = $nval; + $num = $remain; + last if !$more; + } + return @res; +} + +sub _convert_groups_system_from_groupset { + my $dbh = Bugzilla->dbh; + # 2002-09-22 - bugreport@peshkin.net - bug 157756 + # + # If the whole groups system is new, but the installation isn't, + # convert all the old groupset groups, etc... + # + # This requires: + # 1) define groups ids in group table + # 2) populate user_group_map with grants from old groupsets + # and blessgroupsets + # 3) populate bug_group_map with data converted from old bug groupsets + # 4) convert activity logs to use group names instead of numbers + # 5) identify the admin from the old all-ones groupset + + # The groups system needs to be converted if groupset exists + if ($dbh->bz_column_info("profiles", "groupset")) { + $dbh->bz_add_column('groups', 'last_changed', + {TYPE => 'DATETIME', NOTNULL => 1}, '0000-00-00 00:00:00'); + + # Some mysql versions will promote any unique key to primary key + # so all unique keys are removed first and then added back in + $dbh->bz_drop_index('groups', 'groups_bit_idx'); + $dbh->bz_drop_index('groups', 'groups_name_idx'); + if ($dbh->primary_key(undef, undef, 'groups')) { + $dbh->do("ALTER TABLE groups DROP PRIMARY KEY"); + } + + $dbh->bz_add_column('groups', 'id', + {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + + $dbh->bz_add_index('groups', 'groups_name_idx', + {TYPE => 'UNIQUE', FIELDS => [qw(name)]}); + $dbh->bz_add_column('profiles', 'refreshed_when', + {TYPE => 'DATETIME', NOTNULL => 1}, '0000-00-00 00:00:00'); + + # Convert all existing groupset records to map entries before removing + # groupset fields or removing "bit" from groups. + my $sth = $dbh->prepare("SELECT bit, id FROM groups WHERE bit > 0"); + $sth->execute(); + while (my ($bit, $gid) = $sth->fetchrow_array) { + # Create user_group_map membership grants for old groupsets. + # Get each user with the old groupset bit set + my $sth2 = $dbh->prepare("SELECT userid FROM profiles + WHERE (groupset & $bit) != 0"); + $sth2->execute(); + while (my ($uid) = $sth2->fetchrow_array) { + # Check to see if the user is already a member of the group + # and, if not, insert a new record. + my $query = "SELECT user_id FROM user_group_map + WHERE group_id = $gid AND user_id = $uid + AND isbless = 0"; + my $sth3 = $dbh->prepare($query); + $sth3->execute(); + if ( !$sth3->fetchrow_array() ) { + $dbh->do("INSERT INTO user_group_map + (user_id, group_id, isbless, grant_type) + VALUES ($uid, $gid, 0, " . GRANT_DIRECT . ")"); + } + } + # Create user can bless group grants for old groupsets, but only + # if we're upgrading from a Bugzilla that had blessing. + if($dbh->bz_column_info('profiles', 'blessgroupset')) { + # Get each user with the old blessgroupset bit set + $sth2 = $dbh->prepare("SELECT userid FROM profiles + WHERE (blessgroupset & $bit) != 0"); + $sth2->execute(); + while (my ($uid) = $sth2->fetchrow_array) { + $dbh->do("INSERT INTO user_group_map + (user_id, group_id, isbless, grant_type) + VALUES ($uid, $gid, 1, " . GRANT_DIRECT . ")"); + } + } + # Create bug_group_map records for old groupsets. + # Get each bug with the old group bit set. + $sth2 = $dbh->prepare("SELECT bug_id FROM bugs + WHERE (groupset & $bit) != 0"); + $sth2->execute(); + while (my ($bug_id) = $sth2->fetchrow_array) { + # Insert the bug, group pair into the bug_group_map. + $dbh->do("INSERT INTO bug_group_map (bug_id, group_id) + VALUES ($bug_id, $gid)"); + } + } + # Replace old activity log groupset records with lists of names + # of groups. Start by defining the bug_group field and getting its id. + Bugzilla::Field::create_or_update( + {name => "bug_group", desc => "Group"}); + $sth = $dbh->prepare("SELECT id FROM fielddefs + WHERE name = " . $dbh->quote('bug_group')); + $sth->execute(); + my ($bgfid) = $sth->fetchrow_array; + # Get the field id for the old groupset field + $sth = $dbh->prepare("SELECT id FROM fielddefs + WHERE name = " . $dbh->quote('groupset')); + $sth->execute(); + my ($gsid) = $sth->fetchrow_array; + # Get all bugs_activity records from groupset changes + if ($gsid) { + $sth = $dbh->prepare("SELECT bug_id, bug_when, who, added, removed + FROM bugs_activity WHERE fieldid = $gsid"); + $sth->execute(); + while (my ($bug_id, $bug_when, $who, $added, $removed) = + $sth->fetchrow_array) + { + $added ||= 0; + $removed ||= 0; + # Get names of groups added. + my $sth2 = $dbh->prepare("SELECT name FROM groups + WHERE (bit & $added) != 0 + AND (bit & $removed) = 0"); + $sth2->execute(); + my @logadd; + while (my ($n) = $sth2->fetchrow_array) { + push @logadd, $n; + } + # Get names of groups removed. + $sth2 = $dbh->prepare("SELECT name FROM groups + WHERE (bit & $removed) != 0 + AND (bit & $added) = 0"); + $sth2->execute(); + my @logrem; + while (my ($n) = $sth2->fetchrow_array) { + push @logrem, $n; + } + # Get list of group bits added that correspond to + # missing groups. + $sth2 = $dbh->prepare("SELECT ($added & ~BIT_OR(bit)) + FROM groups"); + $sth2->execute(); + my ($miss) = $sth2->fetchrow_array; + if ($miss) { + push @logadd, _list_bits($miss); + print "\nWARNING - GROUPSET ACTIVITY ON BUG $bug_id", + " CONTAINS DELETED GROUPS\n"; + } + # Get list of group bits deleted that correspond to + # missing groups. + $sth2 = $dbh->prepare("SELECT ($removed & ~BIT_OR(bit)) + FROM groups"); + $sth2->execute(); + ($miss) = $sth2->fetchrow_array; + if ($miss) { + push @logrem, _list_bits($miss); + print "\nWARNING - GROUPSET ACTIVITY ON BUG $bug_id", + " CONTAINS DELETED GROUPS\n"; + } + my $logr = ""; + my $loga = ""; + $logr = join(", ", @logrem) . '?' if @logrem; + $loga = join(", ", @logadd) . '?' if @logadd; + # Replace to old activity record with the converted data. + $dbh->do("UPDATE bugs_activity SET fieldid = $bgfid, added = " . + $dbh->quote($loga) . ", removed = " . + $dbh->quote($logr) . + " WHERE bug_id = $bug_id AND bug_when = " . + $dbh->quote($bug_when) . + " AND who = $who AND fieldid = $gsid"); + } + # Replace groupset changes with group name changes in + # profiles_activity. Get profiles_activity records for groupset. + $sth = $dbh->prepare( + "SELECT userid, profiles_when, who, newvalue, oldvalue " . + "FROM profiles_activity " . + "WHERE fieldid = $gsid"); + $sth->execute(); + while (my ($uid, $uwhen, $uwho, $added, $removed) = + $sth->fetchrow_array) + { + $added ||= 0; + $removed ||= 0; + # Get names of groups added. + my $sth2 = $dbh->prepare("SELECT name FROM groups + WHERE (bit & $added) != 0 + AND (bit & $removed) = 0"); + $sth2->execute(); + my @logadd; + while (my ($n) = $sth2->fetchrow_array) { + push @logadd, $n; + } + # Get names of groups removed. + $sth2 = $dbh->prepare("SELECT name FROM groups + WHERE (bit & $removed) != 0 + AND (bit & $added) = 0"); + $sth2->execute(); + my @logrem; + while (my ($n) = $sth2->fetchrow_array) { + push @logrem, $n; + } + my $ladd = ""; + my $lrem = ""; + $ladd = join(", ", @logadd) . '?' if @logadd; + $lrem = join(", ", @logrem) . '?' if @logrem; + # Replace profiles_activity record for groupset change + # with group list. + $dbh->do("UPDATE profiles_activity " . + "SET fieldid = $bgfid, newvalue = " . + $dbh->quote($ladd) . ", oldvalue = " . + $dbh->quote($lrem) . + " WHERE userid = $uid AND profiles_when = " . + $dbh->quote($uwhen) . + " AND who = $uwho AND fieldid = $gsid"); + } + } + + # Identify admin group. + my ($admin_gid) = $dbh->selectrow_array( + "SELECT id FROM groups WHERE name = 'admin'"); + if (!$admin_gid) { + $dbh->do(q{INSERT INTO groups (name, description) + VALUES ('admin', 'Administrators')}); + $admin_gid = $dbh->bz_last_key('groups', 'id'); + } + # Find current admins + my @admins; + # Don't lose admins from DBs where Bug 157704 applies + $sth = $dbh->prepare( + "SELECT userid, (groupset & 65536), login_name " . + "FROM profiles " . + "WHERE (groupset | 65536) = 9223372036854775807"); + $sth->execute(); + while ( my ($userid, $iscomplete, $login_name) + = $sth->fetchrow_array() ) + { + # existing administrators are made members of group "admin" + print "\nWARNING - $login_name IS AN ADMIN IN SPITE OF BUG", + " 157704\n\n" if (!$iscomplete); + push(@admins, $userid) unless grep($_ eq $userid, @admins); + } + # Now make all those users admins directly. They were already + # added to every other group, above, because of their groupset. + foreach my $admin_id (@admins) { + $dbh->do("INSERT INTO user_group_map + (user_id, group_id, isbless, grant_type) + VALUES (?, ?, ?, ?)", + undef, $admin_id, $admin_gid, $_, GRANT_DIRECT) + foreach (0, 1); + } + + $dbh->bz_drop_column('profiles','groupset'); + $dbh->bz_drop_column('profiles','blessgroupset'); + $dbh->bz_drop_column('bugs','groupset'); + $dbh->bz_drop_column('groups','bit'); + $dbh->do("DELETE FROM fielddefs WHERE name = " + . $dbh->quote('groupset')); + } +} + +sub _convert_attachment_statuses_to_flags { + my $dbh = Bugzilla->dbh; + + # September 2002 myk@mozilla.org bug 98801 + # Convert the attachment statuses tables into flags tables. + if ($dbh->bz_table_info("attachstatuses") + && $dbh->bz_table_info("attachstatusdefs")) + { + print "Converting attachment statuses to flags...\n"; + + # Get IDs for the old attachment status and new flag fields. + my ($old_field_id) = $dbh->selectrow_array( + "SELECT id FROM fielddefs WHERE name='attachstatusdefs.name'") + || 0; + my ($new_field_id) = $dbh->selectrow_array( + "SELECT id FROM fielddefs WHERE name = 'flagtypes.name'"); + + # Convert attachment status definitions to flag types. If more than one + # status has the same name and description, it is merged into a single + # status with multiple inclusion records. + + my $sth = $dbh->prepare( + "SELECT id, name, description, sortkey, product_id + FROM attachstatusdefs"); + + # status definition IDs indexed by name/description + my $def_ids = {}; + + # merged IDs and the IDs they were merged into. The key is the old ID, + # the value is the new one. This allows us to give statuses the right + # ID when we convert them over to flags. This map includes IDs that + # weren't merged (in this case the old and new IDs are the same), since + # it makes the code simpler. + my $def_id_map = {}; + + $sth->execute(); + while (my ($id, $name, $desc, $sortkey, $prod_id) = + $sth->fetchrow_array()) + { + my $key = $name . $desc; + if (!$def_ids->{$key}) { + $def_ids->{$key} = $id; + my $quoted_name = $dbh->quote($name); + my $quoted_desc = $dbh->quote($desc); + $dbh->do("INSERT INTO flagtypes (id, name, description, + sortkey, target_type) + VALUES ($id, $quoted_name, $quoted_desc, + $sortkey,'a')"); + } + $def_id_map->{$id} = $def_ids->{$key}; + $dbh->do("INSERT INTO flaginclusions (type_id, product_id) + VALUES ($def_id_map->{$id}, $prod_id)"); + } + + # Note: even though we've converted status definitions, we still + # can't drop the table because we need it to convert the statuses + # themselves. + + # Convert attachment statuses to flags. To do this we select + # the statuses from the status table and then, for each one, + # figure out who set it and when they set it from the bugs + # activity table. + my $id = 0; + $sth = $dbh->prepare( + "SELECT attachstatuses.attach_id, attachstatusdefs.id, + attachstatusdefs.name, attachments.bug_id + FROM attachstatuses, attachstatusdefs, attachments + WHERE attachstatuses.statusid = attachstatusdefs.id + AND attachstatuses.attach_id = attachments.attach_id"); + + # a query to determine when the attachment status was set and who set it + my $sth2 = $dbh->prepare("SELECT added, who, bug_when + FROM bugs_activity + WHERE bug_id = ? AND attach_id = ? + AND fieldid = $old_field_id + ORDER BY bug_when DESC"); + + $sth->execute(); + while (my ($attach_id, $def_id, $status, $bug_id) = + $sth->fetchrow_array()) + { + ++$id; + + # Determine when the attachment status was set and who set it. + # We should always be able to find out this info from the bug + # activity, but we fall back to default values just in case. + $sth2->execute($bug_id, $attach_id); + my ($added, $who, $when); + while (($added, $who, $when) = $sth2->fetchrow_array()) { + last if $added =~ /(^|[, ]+)\Q$status\E([, ]+|$)/; + } + $who = $dbh->quote($who); # "NULL" by default if $who is undefined + $when = $when ? $dbh->quote($when) : "NOW()"; + + + $dbh->do("INSERT INTO flags (id, type_id, status, bug_id, + attach_id, creation_date, modification_date, + requestee_id, setter_id) + VALUES ($id, $def_id_map->{$def_id}, '+', $bug_id, + $attach_id, $when, $when, NULL, $who)"); + } + + # Now that we've converted both tables we can drop them. + $dbh->bz_drop_table("attachstatuses"); + $dbh->bz_drop_table("attachstatusdefs"); + + # Convert activity records for attachment statuses into records + # for flags. + $sth = $dbh->prepare("SELECT attach_id, who, bug_when, added, + removed + FROM bugs_activity + WHERE fieldid = $old_field_id"); + $sth->execute(); + while (my ($attach_id, $who, $when, $old_added, $old_removed) = + $sth->fetchrow_array()) + { + my @additions = split(/[, ]+/, $old_added); + @additions = map("$_+", @additions); + my $new_added = $dbh->quote(join(", ", @additions)); + + my @removals = split(/[, ]+/, $old_removed); + @removals = map("$_+", @removals); + my $new_removed = $dbh->quote(join(", ", @removals)); + + $old_added = $dbh->quote($old_added); + $old_removed = $dbh->quote($old_removed); + $who = $dbh->quote($who); + $when = $dbh->quote($when); + + $dbh->do("UPDATE bugs_activity SET fieldid = $new_field_id, " . + "added = $new_added, removed = $new_removed " . + "WHERE attach_id = $attach_id AND who = $who " . + "AND bug_when = $when AND fieldid = $old_field_id " . + "AND added = $old_added AND removed = $old_removed"); + } + + # Remove the attachment status field from the field definitions. + $dbh->do("DELETE FROM fielddefs WHERE name='attachstatusdefs.name'"); + + print "done.\n"; + } +} + +sub _remove_spaces_and_commas_from_flagtypes { + my $dbh = Bugzilla->dbh; + # Get all names and IDs, to find broken ones and to + # check for collisions when renaming. + my $sth = $dbh->prepare("SELECT name, id FROM flagtypes"); + $sth->execute(); + + my %flagtypes; + my @badflagnames; + # find broken flagtype names, and populate a hash table + # to check for collisions. + while (my ($name, $id) = $sth->fetchrow_array()) { + $flagtypes{$name} = $id; + if ($name =~ /[ ,]/) { + push(@badflagnames, $name); + } + } + if (@badflagnames) { + print "Removing spaces and commas from flag names...\n"; + my ($flagname, $tryflagname); + my $sth = $dbh->prepare("UPDATE flagtypes SET name = ? WHERE id = ?"); + foreach $flagname (@badflagnames) { + print " Bad flag type name \"$flagname\" ...\n"; + # find a new name for this flagtype. + ($tryflagname = $flagname) =~ tr/ ,/__/; + # avoid collisions with existing flagtype names. + while (defined($flagtypes{$tryflagname})) { + print " ... can't rename as \"$tryflagname\" ...\n"; + $tryflagname .= "'"; + if (length($tryflagname) > 50) { + my $lastchanceflagname = (substr $tryflagname, 0, 47) . '...'; + if (defined($flagtypes{$lastchanceflagname})) { + print " ... last attempt as \"$lastchanceflagname\" still failed.'\n", + "Rename the flag by hand and run checksetup.pl again.\n"; + die("Bad flag type name $flagname"); + } + $tryflagname = $lastchanceflagname; + } + } + $sth->execute($tryflagname, $flagtypes{$flagname}); + print " renamed flag type \"$flagname\" as \"$tryflagname\"\n"; + $flagtypes{$tryflagname} = $flagtypes{$flagname}; + delete $flagtypes{$flagname}; + } + print "... done.\n"; + } +} + +sub _setup_usebuggroups_backward_compatibility { + my $dbh = Bugzilla->dbh; + # 2002-11-24 - bugreport@peshkin.net - bug 147275 + # + # If group_control_map is empty, backward-compatibility + # usebuggroups-equivalent records should be created. + my $entry = Bugzilla->params->{'useentrygroupdefault'}; + my ($maps_exist) = $dbh->selectrow_array( + "SELECT DISTINCT 1 FROM group_control_map"); + if (!$maps_exist) { + # Initially populate group_control_map. + # First, get all the existing products and their groups. + my $sth = $dbh->prepare("SELECT groups.id, products.id, groups.name, + products.name + FROM groups, products + WHERE isbuggroup != 0"); + $sth->execute(); + while (my ($groupid, $productid, $groupname, $productname) + = $sth->fetchrow_array()) + { + if ($groupname eq $productname) { + # Product and group have same name. + $dbh->do("INSERT INTO group_control_map " . + "(group_id, product_id, entry, membercontrol, " . + "othercontrol, canedit) " . + "VALUES ($groupid, $productid, $entry, " . + CONTROLMAPDEFAULT . ", " . + CONTROLMAPNA . ", 0)"); + } else { + # See if this group is a product group at all. + my $sth2 = $dbh->prepare("SELECT id FROM products + WHERE name = " .$dbh->quote($groupname)); + $sth2->execute(); + my ($id) = $sth2->fetchrow_array(); + if (!$id) { + # If there is no product with the same name as this + # group, then it is permitted for all products. + $dbh->do("INSERT INTO group_control_map " . + "(group_id, product_id, entry, membercontrol, " . + "othercontrol, canedit) " . + "VALUES ($groupid, $productid, 0, " . + CONTROLMAPSHOWN . ", " . + CONTROLMAPNA . ", 0)"); + } + } + } + } +} + +sub _remove_user_series_map { + my $dbh = Bugzilla->dbh; + # 2004-07-17 GRM - Remove "subscriptions" concept from charting, and add + # group-based security instead. + if ($dbh->bz_table_info("user_series_map")) { + # Oracle doesn't like "date" as a column name, and apparently some DBs + # don't like 'value' either. We use the changes to subscriptions as + # something to hang these renamings off. + $dbh->bz_rename_column('series_data', 'date', 'series_date'); + $dbh->bz_rename_column('series_data', 'value', 'series_value'); + + # series_categories.category_id produces a too-long column name for the + # auto-incrementing sequence (Oracle again). + $dbh->bz_rename_column('series_categories', 'category_id', 'id'); + + $dbh->bz_add_column("series", "public", + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); + + # Migrate public-ness across from user_series_map to new field + my $sth = $dbh->prepare("SELECT series_id from user_series_map " . + "WHERE user_id = 0"); + $sth->execute(); + while (my ($public_series_id) = $sth->fetchrow_array()) { + $dbh->do("UPDATE series SET public = 1 " . + "WHERE series_id = $public_series_id"); + } + + $dbh->bz_drop_table("user_series_map"); + } +} + +sub _copy_old_charts_into_database { + my $dbh = Bugzilla->dbh; + my $datadir = bz_locations()->{'datadir'}; + # 2003-06-26 Copy the old charting data into the database, and create the + # queries that will keep it all running. When the old charting system goes + # away, if this code ever runs, it'll just find no files and do nothing. + my $series_exists = $dbh->selectrow_array("SELECT 1 FROM series " . + $dbh->sql_limit(1)); + if (!$series_exists && -d "$datadir/mining" && -e "$datadir/mining/-All-") { + print "Migrating old chart data into database...\n"; + + # We prepare the handle to insert the series data + my $seriesdatasth = $dbh->prepare( + "INSERT INTO series_data (series_id, series_date, series_value) + VALUES (?, ?, ?)"); + + my $deletesth = $dbh->prepare( + "DELETE FROM series_data WHERE series_id = ? AND series_date = ?"); + + my $groupmapsth = $dbh->prepare( + "INSERT INTO category_group_map (category_id, group_id) + VALUES (?, ?)"); + + # Fields in the data file (matches the current collectstats.pl) + my @statuses = + qw(NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED VERIFIED CLOSED); + my @resolutions = + qw(FIXED INVALID WONTFIX LATER REMIND DUPLICATE WORKSFORME MOVED); + my @fields = (@statuses, @resolutions); + + # We have a localisation problem here. Where do we get these values? + my $all_name = "-All-"; + my $open_name = "All Open"; + + my $products = $dbh->selectall_arrayref("SELECT name FROM products"); + + foreach my $product ((map { $_->[0] } @$products), "-All-") { + # First, create the series + my %queries; + my %seriesids; + + my $query_prod = ""; + if ($product ne "-All-") { + $query_prod = "product=" . html_quote($product) . "&"; + } + + # The query for statuses is different to that for resolutions. + $queries{$_} = ($query_prod . "bug_status=$_") foreach (@statuses); + $queries{$_} = ($query_prod . "resolution=$_") + foreach (@resolutions); + + foreach my $field (@fields) { + # Create a Series for each field in this product. + # user ID = 0 is used. + my $series = new Bugzilla::Series(undef, $product, $all_name, + $field, 0, 1, + $queries{$field}, 1); + $series->writeToDatabase(); + $seriesids{$field} = $series->{'series_id'}; + } + + # We also add a new query for "Open", so that migrated products get + # the same set as new products (see editproducts.cgi.) + my @openedstatuses = ("UNCONFIRMED", "NEW", "ASSIGNED", "REOPENED"); + my $query = join("&", map { "bug_status=$_" } @openedstatuses); + my $series = new Bugzilla::Series(undef, $product, $all_name, + $open_name, 0, 1, + $query_prod . $query, 1); + $series->writeToDatabase(); + $seriesids{$open_name} = $series->{'series_id'}; + + # Now, we attempt to read in historical data, if any + # Convert the name in the same way that collectstats.pl does + my $product_file = $product; + $product_file =~ s/\//-/gs; + $product_file = "$datadir/mining/$product_file"; + + # There are many reasons that this might fail (e.g. no stats + # for this product), so we don't worry if it does. + my $in = new IO::File($product_file) or next; + + # The data files should be in a standard format, even for old + # Bugzillas, because of the conversion code further up this file. + my %data; + my $last_date = ""; + + while (<$in>) { + if (/^(\d+\|.*)/) { + my @numbers = split(/\||\r/, $1); + + # Only take the first line for each date; it was possible to + # run collectstats.pl more than once in a day. + next if $numbers[0] eq $last_date; + + for my $i (0 .. $#fields) { + # $numbers[0] is the date + $data{$fields[$i]}{$numbers[0]} = $numbers[$i + 1]; + + # Keep a total of the number of open bugs for this day + if (is_open_state($fields[$i])) { + $data{$open_name}{$numbers[0]} += $numbers[$i + 1]; + } + } + + $last_date = $numbers[0]; + } + } + + $in->close; + + foreach my $field (@fields, $open_name) { + # Insert values into series_data: series_id, date, value + my %fielddata = %{$data{$field}}; + foreach my $date (keys %fielddata) { + # We need to delete in case the text file had duplicate + # entries in it. + $deletesth->execute($seriesids{$field}, $date); + + # We prepared this above + $seriesdatasth->execute($seriesids{$field}, + $date, $fielddata{$date} || 0); + } + } + + # Create the groupsets for the category + my $category_id = + $dbh->selectrow_array("SELECT id FROM series_categories " . + "WHERE name = " . $dbh->quote($product)); + my $product_id = + $dbh->selectrow_array("SELECT id FROM products " . + "WHERE name = " . $dbh->quote($product)); + + if (defined($category_id) && defined($product_id)) { + + # Get all the mandatory groups for this product + my $group_ids = + $dbh->selectcol_arrayref("SELECT group_id " . + "FROM group_control_map " . + "WHERE product_id = $product_id " . + "AND (membercontrol = " . CONTROLMAPMANDATORY . + " OR othercontrol = " . CONTROLMAPMANDATORY . ")"); + + foreach my $group_id (@$group_ids) { + $groupmapsth->execute($category_id, $group_id); + } + } + } + } +} + +sub _add_user_group_map_grant_type { + my $dbh = Bugzilla->dbh; + # 2004-04-12 - Keep regexp-based group permissions up-to-date - Bug 240325 + if ($dbh->bz_column_info("user_group_map", "isderived")) { + $dbh->bz_add_column('user_group_map', 'grant_type', + {TYPE => 'INT1', NOTNULL => 1, DEFAULT => '0'}); + $dbh->do("DELETE FROM user_group_map WHERE isderived != 0"); + $dbh->do("UPDATE user_group_map SET grant_type = " . GRANT_DIRECT); + $dbh->bz_drop_column("user_group_map", "isderived"); + + $dbh->bz_drop_index('user_group_map', 'user_group_map_user_id_idx'); + $dbh->bz_add_index('user_group_map', 'user_group_map_user_id_idx', + {TYPE => 'UNIQUE', + FIELDS => [qw(user_id group_id grant_type isbless)]}); + + # Make sure groups get rederived + $dbh->do("UPDATE groups SET last_changed = NOW() WHERE name = 'admin'"); + } +} + +sub _add_group_group_map_grant_type { + my $dbh = Bugzilla->dbh; + # 2004-07-16 - Make it possible to have group-group relationships other than + # membership and bless. + if ($dbh->bz_column_info("group_group_map", "isbless")) { + $dbh->bz_add_column('group_group_map', 'grant_type', + {TYPE => 'INT1', NOTNULL => 1, DEFAULT => '0'}); + $dbh->do("UPDATE group_group_map SET grant_type = " . + "IF(isbless, " . GROUP_BLESS . ", " . + GROUP_MEMBERSHIP . ")"); + $dbh->bz_drop_index('group_group_map', 'group_group_map_member_id_idx'); + $dbh->bz_drop_column("group_group_map", "isbless"); + $dbh->bz_add_index('group_group_map', 'group_group_map_member_id_idx', + {TYPE => 'UNIQUE', + FIELDS => [qw(member_id grantor_id grant_type)]}); + } +} + +sub _add_longdescs_already_wrapped { + my $dbh = Bugzilla->dbh; + # 2005-01-29 - mkanat@bugzilla.org + if (!$dbh->bz_column_info('longdescs', 'already_wrapped')) { + # Old, pre-wrapped comments should not be auto-wrapped + $dbh->bz_add_column('longdescs', 'already_wrapped', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}, 1); + # If an old comment doesn't have a newline in the first 81 characters, + # (or doesn't contain a newline at all) and it contains a space, + # then it's probably a mis-wrapped comment and we should wrap it + # at display-time. + print "Fixing old, mis-wrapped comments...\n"; + $dbh->do(q{UPDATE longdescs SET already_wrapped = 0 + WHERE (} . $dbh->sql_position(q{'\n'}, 'thetext') . q{ > 81 + OR } . $dbh->sql_position(q{'\n'}, 'thetext') . q{ = 0) + AND SUBSTRING(thetext FROM 1 FOR 80) LIKE '% %'}); + } +} + +sub _convert_attachments_filename_from_mediumtext { + my $dbh = Bugzilla->dbh; + # 2002 November, myk@mozilla.org, bug 178841: + # + # Convert the "attachments.filename" column from a ridiculously large + # "mediumtext" to a much more sensible "varchar(100)". Also takes + # the opportunity to remove paths from existing filenames, since they + # shouldn't be there for security. Buggy browsers include them, + # and attachment.cgi now takes them out, but old ones need converting. + my $ref = $dbh->bz_column_info("attachments", "filename"); + if ($ref->{TYPE} ne 'varchar(100)') { + print "Removing paths from filenames in attachments table...\n"; + + my $sth = $dbh->prepare("SELECT attach_id, filename FROM attachments " . + "WHERE " . $dbh->sql_position(q{'/'}, 'filename') . " > 0 OR " . + $dbh->sql_position(q{'\\\\'}, 'filename') . " > 0"); + $sth->execute; + + while (my ($attach_id, $filename) = $sth->fetchrow_array) { + $filename =~ s/^.*[\/\\]//; + my $quoted_filename = $dbh->quote($filename); + $dbh->do("UPDATE attachments SET filename = $quoted_filename " . + "WHERE attach_id = $attach_id"); + } + + print "Done.\n"; + + print "Resizing attachments.filename from mediumtext to", + " varchar(100).\n"; + $dbh->bz_alter_column("attachments", "filename", + {TYPE => 'varchar(100)', NOTNULL => 1}); + } +} + +sub _rename_votes_count_and_force_group_refresh { + my $dbh = Bugzilla->dbh; + # 2003-04-27 - bugzilla@chimpychompy.org (GavinS) + # + # Bug 180086 (http://bugzilla.mozilla.org/show_bug.cgi?id=180086) + # + # Renaming the 'count' column in the votes table because Sybase doesn't + # like it + if ($dbh->bz_column_info('votes', 'count')) { + # 2003-04-24 - myk@mozilla.org/bbaetz@acm.org, bug 201018 + # Force all cached groups to be updated at login, due to security bug + # Do this here, inside the next schema change block, so that it doesn't + # get invalidated on every checksetup run. + $dbh->do("UPDATE profiles SET refreshed_when='1900-01-01 00:00:00'"); + + $dbh->bz_rename_column('votes', 'count', 'vote_count'); + } +} + +sub _fix_group_with_empty_name { + my $dbh = Bugzilla->dbh; + # 2005-01-12 Nick Barnes <nb@ravenbrook.com> bug 278010 + # Rename any group which has an empty name. + # Note that there can be at most one such group (because of + # the SQL index on the name column). + my ($emptygroupid) = $dbh->selectrow_array( + "SELECT id FROM groups where name = ''"); + if ($emptygroupid) { + # There is a group with an empty name; find a name to rename it + # as. Must avoid collisions with existing names. Start with + # group_$gid and add _<n> if necessary. + my $trycount = 0; + my $trygroupname; + my $trygroupsth = $dbh->prepare("SELECT id FROM groups where name = ?"); + do { + $trygroupname = "group_$emptygroupid"; + if ($trycount > 0) { + $trygroupname .= "_$trycount"; + } + $trygroupsth->execute($trygroupname); + if ($trygroupsth->rows > 0) { + $trycount ++; + } + } while ($trygroupsth->rows > 0); + my $sth = $dbh->prepare("UPDATE groups SET name = ? " . + "WHERE id = $emptygroupid"); + $sth->execute($trygroupname); + print "Group $emptygroupid had an empty name; renamed as", + " '$trygroupname'.\n"; + } +} + +# A helper for the emailprefs subs below +sub _clone_email_event { + my ($source, $target) = @_; + my $dbh = Bugzilla->dbh; + + my $sth1 = $dbh->prepare("SELECT user_id, relationship FROM email_setting + WHERE event = $source"); + my $sth2 = $dbh->prepare("INSERT into email_setting " . + "(user_id, relationship, event) VALUES (" . + "?, ?, $target)"); + + $sth1->execute(); + + while (my ($userid, $relationship) = $sth1->fetchrow_array()) { + $sth2->execute($userid, $relationship); + } +} + +sub _migrate_email_prefs_to_new_table { + my $dbh = Bugzilla->dbh; + # 2005-03-29 - gerv@gerv.net - bug 73665. + # Migrate email preferences to new email prefs table. + if ($dbh->bz_column_info("profiles", "emailflags")) { + print "Migrating email preferences to new table...\n"; + + # These are the "roles" and "reasons" from the original code, mapped to + # the new terminology of relationships and events. + my %relationships = ("Owner" => REL_ASSIGNEE, + "Reporter" => REL_REPORTER, + "QAcontact" => REL_QA, + "CClist" => REL_CC, + "Voter" => REL_VOTER); + + my %events = ("Removeme" => EVT_ADDED_REMOVED, + "Comments" => EVT_COMMENT, + "Attachments" => EVT_ATTACHMENT, + "Status" => EVT_PROJ_MANAGEMENT, + "Resolved" => EVT_OPENED_CLOSED, + "Keywords" => EVT_KEYWORD, + "CC" => EVT_CC, + "Other" => EVT_OTHER, + "Unconfirmed" => EVT_UNCONFIRMED); + + # Request preferences + my %requestprefs = ("FlagRequestee" => EVT_FLAG_REQUESTED, + "FlagRequester" => EVT_REQUESTED_FLAG); + + # Select all emailflags flag strings + my $sth = $dbh->prepare("SELECT userid, emailflags FROM profiles"); + $sth->execute(); + my $i = 0; + my $total = $sth->rows; + + while (my ($userid, $flagstring) = $sth->fetchrow_array()) { + $i++; + indicate_progress({ total => $total, current => $i, every => 10 }); + # If the user has never logged in since emailprefs arrived, and the + # temporary code to give them a default string never ran, then + # $flagstring will be null. In this case, they just get all mail. + $flagstring ||= ""; + + # The 255 param is here, because without a third param, split will + # trim any trailing null fields, which causes Perl to eject lots of + # warnings. Any suitably large number would do. + my %emailflags = split(/~/, $flagstring, 255); + + my $sth2 = $dbh->prepare("INSERT into email_setting " . + "(user_id, relationship, event) VALUES (" . + "$userid, ?, ?)"); + foreach my $relationship (keys %relationships) { + foreach my $event (keys %events) { + my $key = "email$relationship$event"; + if (!exists($emailflags{$key}) + || $emailflags{$key} eq 'on') + { + $sth2->execute($relationships{$relationship}, + $events{$event}); + } + } + } + # Note that in the old system, the value of "excludeself" is + # assumed to be off if the preference does not exist in the + # user's list, unlike other preferences whose value is + # assumed to be on if they do not exist. + # + # This preference has changed from global to per-relationship. + if (!exists($emailflags{'ExcludeSelf'}) + || $emailflags{'ExcludeSelf'} ne 'on') + { + foreach my $relationship (keys %relationships) { + $dbh->do("INSERT into email_setting " . + "(user_id, relationship, event) VALUES (" . + $userid . ", " . + $relationships{$relationship}. ", " . + EVT_CHANGED_BY_ME . ")"); + } + } + + foreach my $key (keys %requestprefs) { + if (!exists($emailflags{$key}) || $emailflags{$key} eq 'on') { + $dbh->do("INSERT into email_setting " . + "(user_id, relationship, event) VALUES (" . + $userid . ", " . REL_ANY . ", " . + $requestprefs{$key} . ")"); + } + } + } + print "\n"; + + # EVT_ATTACHMENT_DATA should initially have identical settings to + # EVT_ATTACHMENT. + _clone_email_event(EVT_ATTACHMENT, EVT_ATTACHMENT_DATA); + + $dbh->bz_drop_column("profiles", "emailflags"); + } +} + +sub _initialize_dependency_tree_changes_email_pref { + my $dbh = Bugzilla->dbh; + # Check for any "new" email settings that wouldn't have been ported over + # during the block above. Since these settings would have otherwise + # fallen under EVT_OTHER, we'll just clone those settings. That way if + # folks have already disabled all of that mail, there won't be any change. + my %events = ("Dependency Tree Changes" => EVT_DEPEND_BLOCK); + + foreach my $desc (keys %events) { + my $event = $events{$desc}; + my $sth = $dbh->prepare("SELECT COUNT(*) FROM email_setting + WHERE event = $event"); + $sth->execute(); + if (!($sth->fetchrow_arrayref()->[0])) { + # No settings in the table yet, so we assume that this is the + # first time it's being set. + print "Initializing \"$desc\" email_setting ...\n"; + _clone_email_event(EVT_OTHER, $event); + } + } +} + +sub _change_all_mysql_booleans_to_tinyint { + my $dbh = Bugzilla->dbh; + # 2005-03-27: Standardize all boolean fields to plain "tinyint" + if ( $dbh->isa('Bugzilla::DB::Mysql') ) { + # This is a change to make things consistent with Schema, so we use + # direct-database access methods. + my $quip_info_sth = $dbh->column_info(undef, undef, 'quips', '%'); + my $quips_cols = $quip_info_sth->fetchall_hashref("COLUMN_NAME"); + my $approved_col = $quips_cols->{'approved'}; + if ( $approved_col->{TYPE_NAME} eq 'TINYINT' + and $approved_col->{COLUMN_SIZE} == 1 ) + { + # series.public could have been renamed to series.is_public, + # and so wouldn't need to be fixed manually. + if ($dbh->bz_column_info('series', 'public')) { + $dbh->bz_alter_column_raw('series', 'public', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '0'}); + } + $dbh->bz_alter_column_raw('bug_status', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); + $dbh->bz_alter_column_raw('rep_platform', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); + $dbh->bz_alter_column_raw('resolution', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); + $dbh->bz_alter_column_raw('op_sys', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); + $dbh->bz_alter_column_raw('bug_severity', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); + $dbh->bz_alter_column_raw('priority', 'isactive', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); + $dbh->bz_alter_column_raw('quips', 'approved', + {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); + } + } +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::Install::DB - Fix up the database during installation. + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +This module is used primarily by L<checksetup.pl> to modify the +database during upgrades. + +=over + +=back + +=head1 SUBROUTINES + +=over + +=back diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm index 399a61ee7..d408a70e7 100644 --- a/Bugzilla/Install/Filesystem.pm +++ b/Bugzilla/Install/Filesystem.pm @@ -30,6 +30,7 @@ use Bugzilla::Constants; use Bugzilla::Install::Localconfig; use File::Find; +use File::Path; use IO::File; use POSIX (); @@ -337,6 +338,16 @@ sub update_filesystem { EOT } + + # Delete old files that no longer need to exist + + # 2001-04-29 jake@bugzilla.org - Remove oldemailtech + # http://bugzilla.mozilla.org/show_bugs.cgi?id=71552 + if (-d 'shadow') { + print "Removing shadow directory...\n"; + rmtree("shadow"); + } + } sub create_htaccess { diff --git a/checksetup.pl b/checksetup.pl index 7fa0e65fa..ae3cda5e5 100755 --- a/checksetup.pl +++ b/checksetup.pl @@ -326,6 +326,7 @@ import Bugzilla::Install::Localconfig qw(read_localconfig update_localconfig); require Bugzilla::Install::Filesystem; import Bugzilla::Install::Filesystem qw(update_filesystem create_htaccess fix_all_file_permissions); +require Bugzilla::Install::DB; require Bugzilla::DB; require Bugzilla::Template; @@ -691,2071 +692,7 @@ if (!$class_count) { # Update the tables to the current definition --TABLE-- ########################################################################### -# Both legacy code and modern code need this variable. -my @admins = (); - -# really old fields that were added before checksetup.pl existed -# but aren't in very old bugzilla's (like 2.1) -# Steve Stock (sstock@iconnect-inc.com) - -$dbh->bz_add_column('bugs', 'target_milestone', - {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"}); -$dbh->bz_add_column('bugs', 'qa_contact', {TYPE => 'INT3'}); -$dbh->bz_add_column('bugs', 'status_whiteboard', - {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}); -$dbh->bz_add_column('products', 'disallownew', - {TYPE => 'BOOLEAN', NOTNULL => 1}, 0); -$dbh->bz_add_column('products', 'milestoneurl', - {TYPE => 'TINYTEXT', NOTNULL => 1}, ''); -$dbh->bz_add_column('components', 'initialqacontact', - {TYPE => 'TINYTEXT'}); -$dbh->bz_add_column('components', 'description', - {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); - -# 1999-06-22 Added an entry to the attachments table to record who the -# submitter was. Nothing uses this yet, but it still should be recorded. -$dbh->bz_add_column('attachments', 'submitter_id', - {TYPE => 'INT3', NOTNULL => 1}, 0); - -# -# One could even populate this field automatically, e.g. with -# -# unless (GetField('attachments', 'submitter_id') { -# $dbh->bz_add_column ... -# populate -# } -# -# For now I was too lazy, so you should read the documentation :-) - - - -# 1999-9-15 Apparently, newer alphas of MySQL won't allow you to have "when" -# as a column name. So, I have had to rename a column in the bugs_activity -# table. - -$dbh->bz_rename_column('bugs_activity', 'when', 'bug_when'); - - - -# 1999-10-11 Restructured voting database to add a cached value in each bug -# recording how many total votes that bug has. While I'm at it, I removed -# the unused "area" field from the bugs database. It is distressing to -# realize that the bugs table has reached the maximum number of indices -# allowed by MySQL (16), which may make future enhancements awkward. -# (P.S. All is not lost; it appears that the latest betas of MySQL support -# a new table format which will allow 32 indices.) - -$dbh->bz_drop_column('bugs', 'area'); -if (!$dbh->bz_column_info('bugs', 'votes')) { - $dbh->bz_add_column('bugs', 'votes', {TYPE => 'INT3', NOTNULL => 1, - DEFAULT => 0}); - $dbh->bz_add_index('bugs', 'bugs_votes_idx', [qw(votes)]); -} -$dbh->bz_add_column('products', 'votesperuser', - {TYPE => 'INT2', NOTNULL => 1}, 0); - - -# The product name used to be very different in various tables. -# -# It was varchar(16) in bugs -# tinytext in components -# tinytext in products -# tinytext in versions -# -# tinytext is equivalent to varchar(255), which is quite huge, so I change -# them all to varchar(64). - -# Only do this if these fields still exist - they're removed below, in -# a later change -if ($dbh->bz_column_info('products', 'product')) { - $dbh->bz_alter_column('bugs', 'product', - {TYPE => 'varchar(64)', NOTNULL => 1}); - $dbh->bz_alter_column('components', 'program', {TYPE => 'varchar(64)'}); - $dbh->bz_alter_column('products', 'product', {TYPE => 'varchar(64)'}); - $dbh->bz_alter_column('versions', 'program', - {TYPE => 'varchar(64)', NOTNULL => 1}); -} - -# 2000-01-16 Added a "keywords" field to the bugs table, which -# contains a string copy of the entries of the keywords table for this -# bug. This is so that I can easily sort and display a keywords -# column in bug lists. - -if (!$dbh->bz_column_info('bugs', 'keywords')) { - $dbh->bz_add_column('bugs', 'keywords', - {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}); - - my @kwords; - print "Making sure 'keywords' field of table 'bugs' is empty ...\n"; - $dbh->do("UPDATE bugs SET keywords = '' " . - "WHERE keywords != ''"); - print "Repopulating 'keywords' field of table 'bugs' ...\n"; - my $sth = $dbh->prepare("SELECT keywords.bug_id, keyworddefs.name " . - "FROM keywords, keyworddefs " . - "WHERE keyworddefs.id = keywords.keywordid " . - "ORDER BY keywords.bug_id, keyworddefs.name"); - $sth->execute; - my @list; - my $bugid = 0; - my @row; - while (1) { - my ($b, $k) = ($sth->fetchrow_array()); - if (!defined $b || $b ne $bugid) { - if (@list) { - $dbh->do("UPDATE bugs SET keywords = " . - $dbh->quote(join(', ', @list)) . - " WHERE bug_id = $bugid"); - } - if (!$b) { - last; - } - $bugid = $b; - @list = (); - } - push(@list, $k); - } -} - - -# 2000-01-18 Added a "disabledtext" field to the profiles table. If not -# empty, then this account has been disabled, and this field is to contain -# text describing why. -$dbh->bz_add_column('profiles', 'disabledtext', - {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); - - -# 2000-01-20 Added a new "longdescs" table, which is supposed to have all the -# long descriptions in it, replacing the old long_desc field in the bugs -# table. The below hideous code populates this new table with things from -# the old field, with ugly parsing and heuristics. - -sub WriteOneDesc { - my ($id, $who, $when, $buffer) = (@_); - $buffer = trim($buffer); - if ($buffer eq '') { - return; - } - $dbh->do("INSERT INTO longdescs (bug_id, who, bug_when, thetext) VALUES " . - "($id, $who, " . time2str("'%Y/%m/%d %H:%M:%S'", $when) . - ", " . $dbh->quote($buffer) . ")"); -} - - -if ($dbh->bz_column_info('bugs', 'long_desc')) { - eval("use Date::Parse"); - eval("use Date::Format"); - my $sth = $dbh->prepare("SELECT count(*) FROM bugs"); - $sth->execute(); - my ($total) = ($sth->fetchrow_array); - - print "Populating new long_desc table. This is slow. There are $total\n"; - print "bugs to process; a line of dots will be printed for each 50.\n\n"; - $| = 1; - - $dbh->bz_lock_tables('bugs write', 'longdescs write', 'profiles write', - 'bz_schema WRITE'); - - $dbh->do('DELETE FROM longdescs'); - - $sth = $dbh->prepare("SELECT bug_id, creation_ts, reporter, long_desc " . - "FROM bugs ORDER BY bug_id"); - $sth->execute(); - my $count = 0; - while (1) { - my ($id, $createtime, $reporterid, $desc) = ($sth->fetchrow_array()); - if (!$id) { - last; - } - print "."; - $count++; - if ($count % 10 == 0) { - print " "; - if ($count % 50 == 0) { - print "$count/$total (" . int($count * 100 / $total) . "%)\n"; - } - } - $desc =~ s/\r//g; - my $who = $reporterid; - my $when = str2time($createtime); - my $buffer = ""; - foreach my $line (split(/\n/, $desc)) { - $line =~ s/\s+$//g; # Trim trailing whitespace. - if ($line =~ /^------- Additional Comments From ([^\s]+)\s+(\d.+\d)\s+-------$/) { - my $name = $1; - my $date = str2time($2); - $date += 59; # Oy, what a hack. The creation time is - # accurate to the second. But we the long - # text only contains things accurate to the - # minute. And so, if someone makes a comment - # within a minute of the original bug creation, - # then the comment can come *before* the - # bug creation. So, we add 59 seconds to - # the time of all comments, so that they - # are always considered to have happened at - # the *end* of the given minute, not the - # beginning. - if ($date >= $when) { - WriteOneDesc($id, $who, $when, $buffer); - $buffer = ""; - $when = $date; - my $s2 = $dbh->prepare("SELECT userid FROM profiles " . - "WHERE login_name = " . - $dbh->quote($name)); - $s2->execute(); - ($who) = ($s2->fetchrow_array()); - if (!$who) { - # This username doesn't exist. Try a special - # netscape-only hack (sorry about that, but I don't - # think it will hurt any other installations). We - # have many entries in the bugsystem from an ancient - # world where the "@netscape.com" part of the loginname - # was omitted. So, look up the user again with that - # appended, and use it if it's there. - if ($name !~ /\@/) { - my $nsname = $name . "\@netscape.com"; - $s2 = - $dbh->prepare("SELECT userid FROM profiles " . - "WHERE login_name = " . - $dbh->quote($nsname)); - $s2->execute(); - ($who) = ($s2->fetchrow_array()); - } - } - - if (!$who) { - # This username doesn't exist. Maybe someone renamed - # him or something. Invent a new profile entry, - # disabled, just to represent him. - $dbh->do("INSERT INTO profiles " . - "(login_name, cryptpassword," . - " disabledtext) VALUES (" . - $dbh->quote($name) . - ", " . $dbh->quote(bz_crypt('okthen')) . - ", " . - "'Account created only to maintain database integrity')"); - $who = $dbh->bz_last_key('profiles', 'userid'); - } - next; - } else { -# print "\nDecided this line of bug $id has a date of " . -# time2str("'%Y/%m/%d %H:%M:%S'", $date) . -# "\nwhich is less than previous line:\n$line\n\n"; - } - - } - $buffer .= $line . "\n"; - } - WriteOneDesc($id, $who, $when, $buffer); - } - - - print "\n\n"; - - $dbh->bz_drop_column('bugs', 'long_desc'); - - $dbh->bz_unlock_tables(); -} - - -# 2000-01-18 Added a new table fielddefs that records information about the -# different fields we keep an activity log on. The bugs_activity table -# now has a pointer into that table instead of recording the name directly. - -if ($dbh->bz_column_info('bugs_activity', 'field')) { - $dbh->bz_add_column('bugs_activity', 'fieldid', - {TYPE => 'INT3', NOTNULL => 1}, 0); - - $dbh->bz_add_index('bugs_activity', 'bugs_activity_fieldid_idx', - [qw(fieldid)]); - print "Populating new fieldid field ...\n"; - - $dbh->bz_lock_tables('bugs_activity WRITE', 'fielddefs WRITE'); - - my $sth = $dbh->prepare('SELECT DISTINCT field FROM bugs_activity'); - $sth->execute(); - my %ids; - while (my ($f) = ($sth->fetchrow_array())) { - my $q = $dbh->quote($f); - my $s2 = - $dbh->prepare("SELECT id FROM fielddefs WHERE name = $q"); - $s2->execute(); - my ($id) = ($s2->fetchrow_array()); - if (!$id) { - $dbh->do("INSERT INTO fielddefs (name, description) VALUES " . - "($q, $q)"); - $id = $dbh->bz_last_key('fielddefs', 'id'); - } - $dbh->do("UPDATE bugs_activity SET fieldid = $id WHERE field = $q"); - } - $dbh->bz_unlock_tables(); - - $dbh->bz_drop_column('bugs_activity', 'field'); -} - - - -# 2000-01-18 New email-notification scheme uses a new field in the bug to -# record when email notifications were last sent about this bug. Also, -# added 'newemailtech' field to record if user wants to use the experimental -# stuff. -# 2001-04-29 jake@bugzilla.org - The newemailtech field is no longer needed -# http://bugzilla.mozilla.org/show_bugs.cgi?id=71552 - -if (!$dbh->bz_column_info('bugs', 'lastdiffed')) { - $dbh->bz_add_column('bugs', 'lastdiffed', {TYPE =>'DATETIME'}); - $dbh->do('UPDATE bugs SET lastdiffed = now()'); -} - - -# 2000-01-22 The "login_name" field in the "profiles" table was not -# declared to be unique. Sure enough, somehow, I got 22 duplicated entries -# in my database. This code detects that, cleans up the duplicates, and -# then tweaks the table to declare the field to be unique. What a pain. -if (!$dbh->bz_index_info('profiles', 'profiles_login_name_idx') || - !$dbh->bz_index_info('profiles', 'profiles_login_name_idx')->{TYPE}) { - print "Searching for duplicate entries in the profiles table ...\n"; - while (1) { - # This code is weird in that it loops around and keeps doing this - # select again. That's because I'm paranoid about deleting entries - # out from under us in the profiles table. Things get weird if - # there are *three* or more entries for the same user... - $sth = $dbh->prepare("SELECT p1.userid, p2.userid, p1.login_name " . - "FROM profiles AS p1, profiles AS p2 " . - "WHERE p1.userid < p2.userid " . - "AND p1.login_name = p2.login_name " . - "ORDER BY p1.login_name"); - $sth->execute(); - my ($u1, $u2, $n) = ($sth->fetchrow_array); - if (!$u1) { - last; - } - print "Both $u1 & $u2 are ids for $n! Merging $u2 into $u1 ...\n"; - foreach my $i (["bugs", "reporter"], - ["bugs", "assigned_to"], - ["bugs", "qa_contact"], - ["attachments", "submitter_id"], - ["bugs_activity", "who"], - ["cc", "who"], - ["votes", "who"], - ["longdescs", "who"]) { - my ($table, $field) = (@$i); - print " Updating $table.$field ...\n"; - $dbh->do("UPDATE $table SET $field = $u1 " . - "WHERE $field = $u2"); - } - $dbh->do("DELETE FROM profiles WHERE userid = $u2"); - } - print "OK, changing index type to prevent duplicates in the future ...\n"; - - $dbh->bz_drop_index('profiles', 'profiles_login_name_idx'); - $dbh->bz_add_index('profiles', 'profiles_login_name_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(login_name)]}); -} - - -# 2000-01-24 Added a new field to let people control whether the "My -# bugs" link appears at the bottom of each page. Also can control -# whether each named query should show up there. - -$dbh->bz_add_column('profiles', 'mybugslink', {TYPE => 'BOOLEAN', NOTNULL => 1, - DEFAULT => 'TRUE'}); - -my $comp_init_owner = $dbh->bz_column_info('components', 'initialowner'); -if ($comp_init_owner && $comp_init_owner->{TYPE} eq 'TINYTEXT') { - $sth = $dbh->prepare( - "SELECT program, value, initialowner, initialqacontact " . - "FROM components"); - $sth->execute(); - while (my ($program, $value, $initialowner) = $sth->fetchrow_array()) { - $initialowner =~ s/([\\\'])/\\$1/g; $initialowner =~ s/\0/\\0/g; - $program =~ s/([\\\'])/\\$1/g; $program =~ s/\0/\\0/g; - $value =~ s/([\\\'])/\\$1/g; $value =~ s/\0/\\0/g; - - my $s2 = $dbh->prepare("SELECT userid " . - "FROM profiles " . - "WHERE login_name = '$initialowner'"); - $s2->execute(); - - my $initialownerid = $s2->fetchrow_array(); - - unless (defined $initialownerid) { - print "Warning: You have an invalid default assignee '$initialowner'\n" . - "in component '$value' of program '$program'. !\n"; - $initialownerid = 0; - } - - my $update = - "UPDATE components " . - "SET initialowner = $initialownerid " . - "WHERE program = '$program' " . - "AND value = '$value'"; - my $s3 = $dbh->prepare("UPDATE components " . - "SET initialowner = $initialownerid " . - "WHERE program = '$program' " . - "AND value = '$value';"); - $s3->execute(); - } - - $dbh->bz_alter_column('components','initialowner',{TYPE => 'INT3'}); -} - -my $comp_init_qa = $dbh->bz_column_info('components', 'initialqacontact'); -if ($comp_init_qa && $comp_init_qa->{TYPE} eq 'TINYTEXT') { - $sth = $dbh->prepare( - "SELECT program, value, initialqacontact, initialqacontact " . - "FROM components"); - $sth->execute(); - while (my ($program, $value, $initialqacontact) = $sth->fetchrow_array()) { - $initialqacontact =~ s/([\\\'])/\\$1/g; $initialqacontact =~ s/\0/\\0/g; - $program =~ s/([\\\'])/\\$1/g; $program =~ s/\0/\\0/g; - $value =~ s/([\\\'])/\\$1/g; $value =~ s/\0/\\0/g; - - my $s2 = $dbh->prepare("SELECT userid " . - "FROM profiles " . - "WHERE login_name = '$initialqacontact'"); - $s2->execute(); - - my $initialqacontactid = $s2->fetchrow_array(); - - unless (defined $initialqacontactid) { - if ($initialqacontact ne '') { - print "Warning: You have an invalid default QA contact $initialqacontact' in program '$program', component '$value'!\n"; - } - $initialqacontactid = 0; - } - - my $update = "UPDATE components " . - "SET initialqacontact = $initialqacontactid " . - "WHERE program = '$program' AND value = '$value'"; - my $s3 = $dbh->prepare("UPDATE components " . - "SET initialqacontact = $initialqacontactid " . - "WHERE program = '$program' " . - "AND value = '$value';"); - $s3->execute(); - } - - $dbh->bz_alter_column('components','initialqacontact',{TYPE => 'INT3'}); -} - - -if (!$dbh->bz_column_info('bugs', 'everconfirmed')) { - $dbh->bz_add_column('bugs', 'everconfirmed', - {TYPE => 'BOOLEAN', NOTNULL => 1}, 1); -} -$dbh->bz_add_column('products', 'maxvotesperbug', - {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '10000'}); -$dbh->bz_add_column('products', 'votestoconfirm', - {TYPE => 'INT2', NOTNULL => 1}, 0); - -# 2000-03-21 Adding a table for target milestones to -# database - matthew@zeroknowledge.com -# If the milestones table is empty, and we're still back in a Bugzilla -# that has a bugs.product field, that means that we just created -# the milestones table and it needs to be populated. -my $milestones_exist = $dbh->selectrow_array("SELECT 1 FROM milestones"); -if (!$milestones_exist && $dbh->bz_column_info('bugs', 'product')) { - print "Replacing blank milestones...\n"; - - $dbh->do("UPDATE bugs " . - "SET target_milestone = '---' " . - "WHERE target_milestone = ' '"); - - # If we are upgrading from 2.8 or earlier, we will have *created* - # the milestones table with a product_id field, but Bugzilla expects - # it to have a "product" field. So we change the field backward so - # other code can run. The change will be reversed later in checksetup. - if ($dbh->bz_column_info('milestones', 'product_id')) { - # Dropping the column leaves us with a milestones_product_id_idx - # index that is only on the "value" column. We need to drop the - # whole index so that it can be correctly re-created later. - $dbh->bz_drop_index('milestones', 'milestones_product_id_idx'); - $dbh->bz_drop_column('milestones', 'product_id'); - $dbh->bz_add_column('milestones', 'product', - {TYPE => 'varchar(64)', NOTNULL => 1}, ''); - } - - # Populate the milestone table with all existing values in the database - $sth = $dbh->prepare("SELECT DISTINCT target_milestone, product FROM bugs"); - $sth->execute(); - - print "Populating milestones table...\n"; - - my $value; - my $product; - while(($value, $product) = $sth->fetchrow_array()) - { - # check if the value already exists - my $sortkey = substr($value, 1); - if ($sortkey !~ /^\d+$/) { - $sortkey = 0; - } else { - $sortkey *= 10; - } - $value = $dbh->quote($value); - $product = $dbh->quote($product); - my $s2 = $dbh->prepare("SELECT value " . - "FROM milestones " . - "WHERE value = $value " . - "AND product = $product"); - $s2->execute(); - - if(!$s2->fetchrow_array()) - { - $dbh->do("INSERT INTO milestones(value, product, sortkey) VALUES($value, $product, $sortkey)"); - } - } -} - -# 2000-03-22 Changed the default value for target_milestone to be "---" -# (which is still not quite correct, but much better than what it was -# doing), and made the size of the value field in the milestones table match -# the size of the target_milestone field in the bugs table. - -$dbh->bz_alter_column('bugs', 'target_milestone', - {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"}); -$dbh->bz_alter_column('milestones', 'value', - {TYPE => 'varchar(20)', NOTNULL => 1}); - - -# 2000-03-23 Added a defaultmilestone field to the products table, so that -# we know which milestone to initially assign bugs to. - -if (!$dbh->bz_column_info('products', 'defaultmilestone')) { - $dbh->bz_add_column('products', 'defaultmilestone', - {TYPE => 'varchar(20)', NOTNULL => 1, DEFAULT => "'---'"}); - $sth = $dbh->prepare("SELECT product, defaultmilestone FROM products"); - $sth->execute(); - while (my ($product, $defaultmilestone) = $sth->fetchrow_array()) { - $product = $dbh->quote($product); - $defaultmilestone = $dbh->quote($defaultmilestone); - my $s2 = $dbh->prepare("SELECT value FROM milestones " . - "WHERE value = $defaultmilestone " . - "AND product = $product"); - $s2->execute(); - if (!$s2->fetchrow_array()) { - $dbh->do("INSERT INTO milestones(value, product) " . - "VALUES ($defaultmilestone, $product)"); - } - } -} - -# 2000-03-24 Added unique indexes into the cc and keyword tables. This -# prevents certain database inconsistencies, and, moreover, is required for -# new generalized list code to work. - -if (!$dbh->bz_index_info('cc', 'cc_bug_id_idx')->{TYPE}) { - - # XXX should eliminate duplicate entries before altering - # - $dbh->bz_drop_index('cc', 'cc_bug_id_idx'); - $dbh->bz_add_index('cc', 'cc_bug_id_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(bug_id who)]}); -} - -if (!$dbh->bz_index_info('keywords', 'keywords_bug_id_idx')->{TYPE}) { - - # XXX should eliminate duplicate entries before altering - # - $dbh->bz_drop_index('keywords', 'keywords_bug_id_idx'); - $dbh->bz_add_index('keywords', 'keywords_bug_id_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(bug_id keywordid)]}); -} - -# 2000-07-15 Added duplicates table so Bugzilla tracks duplicates in a better -# way than it used to. This code searches the comments to populate the table -# initially. It's executed if the table is empty; if it's empty because there -# are no dupes (as opposed to having just created the table) it won't have -# any effect anyway, so it doesn't matter. -$sth = $dbh->prepare("SELECT count(*) from duplicates"); -$sth->execute(); -if (!($sth->fetchrow_arrayref()->[0])) { - # populate table - print("Populating duplicates table...\n") unless $silent; - - $sth = $dbh->prepare( - "SELECT longdescs.bug_id, thetext " . - "FROM longdescs " . - "LEFT JOIN bugs ON longdescs.bug_id = bugs.bug_id " . - "WHERE (" . $dbh->sql_regexp("thetext", - "'[.*.]{3} This bug has been marked as a duplicate of [[:digit:]]+ [.*.]{3}'") . ") " . - "AND (resolution = 'DUPLICATE') " . - "ORDER BY longdescs.bug_when"); - $sth->execute(); - - my %dupes; - my $key; - - # Because of the way hashes work, this loop removes all but the last dupe - # resolution found for a given bug. - while (my ($dupe, $dupe_of) = $sth->fetchrow_array()) { - $dupes{$dupe} = $dupe_of; - } - - foreach $key (keys(%dupes)){ - $dupes{$key} =~ /^.*\*\*\* This bug has been marked as a duplicate of (\d+) \*\*\*$/ms; - $dupes{$key} = $1; - $dbh->do("INSERT INTO duplicates VALUES('$dupes{$key}', '$key')"); - # BugItsADupeOf Dupe - } -} - -# 2000-12-18. Added an 'emailflags' field for storing preferences about -# when email gets sent on a per-user basis. -if (!$dbh->bz_column_info('profiles', 'emailflags') && - !$dbh->bz_column_info('email_setting', 'user_id')) { - $dbh->bz_add_column('profiles', 'emailflags', {TYPE => 'MEDIUMTEXT'}); -} - -# 2000-11-27 For Bugzilla 2.5 and later. Copy data from 'comments' to -# 'longdescs' - the new name of the comments table. -if ($dbh->bz_table_info('comments')) { - my $quoted_when = $dbh->quote_identifier('when'); - # This is MySQL-specific syntax, but that's OK because it will only - # ever run on MySQL. - $dbh->do("INSERT INTO longdescs (bug_when, bug_id, who, thetext) - SELECT $quoted_when, bug_id, who, comment - FROM comments"); - $dbh->bz_drop_table("comments"); -} - -# -# 2001-04-10 myk@mozilla.org: -# isactive determines whether or not a group is active. An inactive group -# cannot have bugs added to it. Deactivation is a much milder form of -# deleting a group that allows users to continue to work on bugs in the group -# without enabling them to extend the life of the group by adding bugs to it. -# http://bugzilla.mozilla.org/show_bug.cgi?id=75482 -# -$dbh->bz_add_column('groups', 'isactive', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - -# -# 2001-06-15 myk@mozilla.org: -# isobsolete determines whether or not an attachment is pertinent/relevant/valid. -# -$dbh->bz_add_column('attachments', 'isobsolete', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - -# 2001-04-29 jake@bugzilla.org - Remove oldemailtech -# http://bugzilla.mozilla.org/show_bugs.cgi?id=71552 -if (-d 'shadow') { - print "Removing shadow directory...\n"; - unlink glob("shadow/*"); - unlink glob("shadow/.*"); - rmdir "shadow"; -} -$dbh->bz_drop_column("profiles", "emailnotification"); -$dbh->bz_drop_column("profiles", "newemailtech"); - - -# 2003-11-19; chicks@chicks.net; bug 225973: fix field size to accommodate -# wider algorithms such as Blowfish. Note that this needs to be run -# before recrypting passwords in the following block. -$dbh->bz_alter_column('profiles', 'cryptpassword', {TYPE => 'varchar(128)'}); - -# 2001-06-12; myk@mozilla.org; bugs 74032, 77473: -# Recrypt passwords using Perl &crypt instead of the mysql equivalent -# and delete plaintext passwords from the database. -if ($dbh->bz_column_info('profiles', 'password')) { - - print <<ENDTEXT; -Your current installation of Bugzilla stores passwords in plaintext -in the database and uses mysql's encrypt function instead of Perl's -crypt function to crypt passwords. Passwords are now going to be -re-crypted with the Perl function, and plaintext passwords will be -deleted from the database. This could take a while if your -installation has many users. -ENDTEXT - - # Re-crypt everyone's password. - my $sth = $dbh->prepare("SELECT userid, password FROM profiles"); - $sth->execute(); - - my $i = 1; - - print "Fixing password #1... "; - while (my ($userid, $password) = $sth->fetchrow_array()) { - my $cryptpassword = $dbh->quote(bz_crypt($password)); - $dbh->do("UPDATE profiles " . - "SET cryptpassword = $cryptpassword " . - "WHERE userid = $userid"); - ++$i; - # Let the user know where we are at every 500 records. - print "$i... " if !($i%500); - } - print "$i... Done.\n"; - - # Drop the plaintext password field. - $dbh->bz_drop_column('profiles', 'password'); -} - -# -# 2001-06-06 justdave@syndicomm.com: -# There was no index on the 'who' column in the long descriptions table. -# This caused queries by who posted comments to take a LONG time. -# http://bugzilla.mozilla.org/show_bug.cgi?id=57350 -$dbh->bz_add_index('longdescs', 'longdescs_who_idx', [qw(who)]); - -# 2001-06-15 kiko@async.com.br - Change bug:version size to avoid -# truncates re http://bugzilla.mozilla.org/show_bug.cgi?id=9352 -$dbh->bz_alter_column('bugs', 'version', - {TYPE => 'varchar(64)', NOTNULL => 1}); - -# 2001-07-20 jake@bugzilla.org - Change bugs_activity to only record changes -# http://bugzilla.mozilla.org/show_bug.cgi?id=55161 -if ($dbh->bz_column_info('bugs_activity', 'oldvalue')) { - $dbh->bz_add_column("bugs_activity", "removed", {TYPE => "TINYTEXT"}); - $dbh->bz_add_column("bugs_activity", "added", {TYPE => "TINYTEXT"}); - - # Need to get field id's for the fields that have multiple values - my @multi = (); - foreach my $f ("cc", "dependson", "blocked", "keywords") { - my $sth = $dbh->prepare("SELECT id " . - "FROM fielddefs " . - "WHERE name = '$f'"); - $sth->execute(); - my ($fid) = $sth->fetchrow_array(); - push (@multi, $fid); - } - - # Now we need to process the bugs_activity table and reformat the data - my $i = 0; - print "Fixing activity log "; - my $sth = $dbh->prepare("SELECT bug_id, who, bug_when, fieldid, - oldvalue, newvalue FROM bugs_activity"); - $sth->execute; - while (my ($bug_id, $who, $bug_when, $fieldid, $oldvalue, $newvalue) = $sth->fetchrow_array()) { - # print the iteration count every 500 records - # so the user knows we didn't die - print "$i..." if !($i++ % 500); - # Make sure (old|new)value isn't null (to suppress warnings) - $oldvalue ||= ""; - $newvalue ||= ""; - my ($added, $removed) = ""; - if (grep ($_ eq $fieldid, @multi)) { - $oldvalue =~ s/[\s,]+/ /g; - $newvalue =~ s/[\s,]+/ /g; - my @old = split(" ", $oldvalue); - my @new = split(" ", $newvalue); - my (@add, @remove) = (); - # Find values that were "added" - foreach my $value(@new) { - if (! grep ($_ eq $value, @old)) { - push (@add, $value); - } - } - # Find values that were removed - foreach my $value(@old) { - if (! grep ($_ eq $value, @new)) { - push (@remove, $value); - } - } - $added = join (", ", @add); - $removed = join (", ", @remove); - # If we can't determine what changed, put a ? in both fields - unless ($added || $removed) { - $added = "?"; - $removed = "?"; - } - # If the original field (old|new)value was full, then this - # could be incomplete data. - if (length($oldvalue) == 255 || length($newvalue) == 255) { - $added = "? $added"; - $removed = "? $removed"; - } - } else { - $removed = $oldvalue; - $added = $newvalue; - } - $added = $dbh->quote($added); - $removed = $dbh->quote($removed); - $dbh->do("UPDATE bugs_activity SET removed = $removed, added = $added - WHERE bug_id = $bug_id AND who = $who - AND bug_when = '$bug_when' AND fieldid = $fieldid"); - } - print ". Done.\n"; - $dbh->bz_drop_column("bugs_activity", "oldvalue"); - $dbh->bz_drop_column("bugs_activity", "newvalue"); -} - -$dbh->bz_alter_column("profiles", "disabledtext", - {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); - -# 2001-07-26 myk@mozilla.org bug 39816 (original) -# 2002-02-06 bbaetz@student.usyd.edu.au bug 97471 (revision) -# Add fields to the bugs table that record whether or not the reporter -# and users on the cc: list can see bugs even when -# they are not members of groups to which the bugs are restricted. -$dbh->bz_add_column("bugs", "reporter_accessible", - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); -$dbh->bz_add_column("bugs", "cclist_accessible", - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - -# 2001-08-21 myk@mozilla.org bug84338: -# Add a field to the bugs_activity table for the attachment ID, so installations -# using the attachment manager can record changes to attachments. -$dbh->bz_add_column("bugs_activity", "attach_id", {TYPE => 'INT3'}); - -# 2002-02-04 bbaetz@student.usyd.edu.au bug 95732 -# Remove logincookies.cryptpassword, and delete entries which become -# invalid -if ($dbh->bz_column_info("logincookies", "cryptpassword")) { - # We need to delete any cookies which are invalid before dropping the - # column - - print "Removing invalid login cookies...\n"; - - # mysql doesn't support DELETE with multi-table queries, so we have - # to iterate - my $sth = $dbh->prepare("SELECT cookie FROM logincookies, profiles " . - "WHERE logincookies.cryptpassword != " . - "profiles.cryptpassword AND " . - "logincookies.userid = profiles.userid"); - $sth->execute(); - while (my ($cookie) = $sth->fetchrow_array()) { - $dbh->do("DELETE FROM logincookies WHERE cookie = $cookie"); - } - - $dbh->bz_drop_column("logincookies", "cryptpassword"); -} - -# 2002-02-13 bbaetz@student.usyd.edu.au - bug 97471 -# qacontact/assignee should always be able to see bugs, -# so remove their restriction column -if ($dbh->bz_column_info("bugs", "qacontact_accessible")) { - print "Removing restrictions on bugs for assignee and qacontact...\n"; - - $dbh->bz_drop_column("bugs", "qacontact_accessible"); - $dbh->bz_drop_column("bugs", "assignee_accessible"); -} - -# 2002-02-20 jeff.hedlund@matrixsi.com - bug 24789 time tracking -$dbh->bz_add_column("longdescs", "work_time", - {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'}); -$dbh->bz_add_column("bugs", "estimated_time", - {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'}); -$dbh->bz_add_column("bugs", "remaining_time", - {TYPE => 'decimal(5,2)', NOTNULL => 1, DEFAULT => '0'}); -$dbh->bz_add_column("bugs", "deadline", {TYPE => 'DATETIME'}); - -# 2002-03-15 bbaetz@student.usyd.edu.au - bug 129466 -# 2002-05-13 preed@sigkill.com - bug 129446 patch backported to the -# BUGZILLA-2_14_1-BRANCH as a security blocker for the 2.14.2 release -# -# Use the ip, not the hostname, in the logincookies table -if ($dbh->bz_column_info("logincookies", "hostname")) { - # We've changed what we match against, so all entries are now invalid - $dbh->do("DELETE FROM logincookies"); - - # Now update the logincookies schema - $dbh->bz_drop_column("logincookies", "hostname"); - $dbh->bz_add_column("logincookies", "ipaddr", - {TYPE => 'varchar(40)', NOTNULL => 1}, ''); -} - -# 2002-08-19 - bugreport@peshkin.net bug 143826 -# Add private comments and private attachments on less-private bugs -$dbh->bz_add_column('longdescs', 'isprivate', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); -$dbh->bz_add_column('attachments', 'isprivate', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - - -# 2002-07-03 myk@mozilla.org bug99203: -# Add a bug alias field to the bugs table so bugs can be referenced by alias -# in addition to ID. -if (!$dbh->bz_column_info("bugs", "alias")) { - $dbh->bz_add_column("bugs", "alias", {TYPE => "varchar(20)"}); - $dbh->bz_add_index('bugs', 'bugs_alias_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(alias)]}); -} - -# 2002-07-15 davef@tetsubo.com - bug 67950 -# Move quips to the db. -if (-r "$datadir/comments" && -s "$datadir/comments" - && open (COMMENTS, "<$datadir/comments")) { - print "Populating quips table from $datadir/comments...\n\n"; - while (<COMMENTS>) { - chomp; - $dbh->do("INSERT INTO quips (quip) VALUES (" - . $dbh->quote($_) . ")"); - } - print "Quips are now stored in the database, rather than in an external file.\n" . - "The quips previously stored in $datadir/comments have been copied into\n" . - "the database, and that file has been renamed to $datadir/comments.bak\n" . - "You may delete the renamed file once you have confirmed that all your \n" . - "quips were moved successfully.\n\n"; - close COMMENTS; - rename("$datadir/comments", "$datadir/comments.bak"); -} - -# 2002-07-31 bbaetz@student.usyd.edu.au bug 158236 -# Remove unused column -if ($dbh->bz_column_info("namedqueries", "watchfordiffs")) { - $dbh->bz_drop_column("namedqueries", "watchfordiffs"); -} - -# 2002-08-12 jake@bugzilla.org/bbaetz@student.usyd.edu.au - bug 43600 -# Use integer IDs for products and components. -if ($dbh->bz_column_info("products", "product")) { - print "Updating database to use product IDs.\n"; - - # First, we need to remove possible NULL entries - # NULLs may exist, but won't have been used, since all the uses of them - # are in NOT NULL fields in other tables - $dbh->do("DELETE FROM products WHERE product IS NULL"); - $dbh->do("DELETE FROM components WHERE value IS NULL"); - - $dbh->bz_add_column("products", "id", - {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); - $dbh->bz_add_column("components", "product_id", - {TYPE => 'INT2', NOTNULL => 1}, 0); - $dbh->bz_add_column("versions", "product_id", - {TYPE => 'INT2', NOTNULL => 1}, 0); - $dbh->bz_add_column("milestones", "product_id", - {TYPE => 'INT2', NOTNULL => 1}, 0); - $dbh->bz_add_column("bugs", "product_id", - {TYPE => 'INT2', NOTNULL => 1}, 0); - - # The attachstatusdefs table was added in version 2.15, but removed again - # in early 2.17. If it exists now, we still need to perform this change - # with product_id because the code further down which converts the - # attachment statuses to flags depends on it. But we need to avoid this - # if the user is upgrading from 2.14 or earlier (because it won't be - # there to convert). - if ($dbh->bz_table_info("attachstatusdefs")) { - $dbh->bz_add_column("attachstatusdefs", "product_id", - {TYPE => 'INT2', NOTNULL => 1}, 0); - } - - my %products; - my $sth = $dbh->prepare("SELECT id, product FROM products"); - $sth->execute; - while (my ($product_id, $product) = $sth->fetchrow_array()) { - if (exists $products{$product}) { - print "Ignoring duplicate product $product\n"; - $dbh->do("DELETE FROM products WHERE id = $product_id"); - next; - } - $products{$product} = 1; - $dbh->do("UPDATE components SET product_id = $product_id " . - "WHERE program = " . $dbh->quote($product)); - $dbh->do("UPDATE versions SET product_id = $product_id " . - "WHERE program = " . $dbh->quote($product)); - $dbh->do("UPDATE milestones SET product_id = $product_id " . - "WHERE product = " . $dbh->quote($product)); - $dbh->do("UPDATE bugs SET product_id = $product_id " . - "WHERE product = " . $dbh->quote($product)); - $dbh->do("UPDATE attachstatusdefs SET product_id = $product_id " . - "WHERE product = " . $dbh->quote($product)) - if $dbh->bz_table_info("attachstatusdefs"); - } - - print "Updating the database to use component IDs.\n"; - $dbh->bz_add_column("components", "id", - {TYPE => 'SMALLSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); - $dbh->bz_add_column("bugs", "component_id", - {TYPE => 'INT2', NOTNULL => 1}, 0); - - my %components; - $sth = $dbh->prepare("SELECT id, value, product_id FROM components"); - $sth->execute; - while (my ($component_id, $component, $product_id) = $sth->fetchrow_array()) { - if (exists $components{$component}) { - if (exists $components{$component}{$product_id}) { - print "Ignoring duplicate component $component for product $product_id\n"; - $dbh->do("DELETE FROM components WHERE id = $component_id"); - next; - } - } else { - $components{$component} = {}; - } - $components{$component}{$product_id} = 1; - $dbh->do("UPDATE bugs SET component_id = $component_id " . - "WHERE component = " . $dbh->quote($component) . - " AND product_id = $product_id"); - } - print "Fixing Indexes and Uniqueness.\n"; - $dbh->bz_drop_index('milestones', 'milestones_product_idx'); - - $dbh->bz_add_index('milestones', 'milestones_product_id_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(product_id value)]}); - - $dbh->bz_drop_index('bugs', 'bugs_product_idx'); - $dbh->bz_add_index('bugs', 'bugs_product_id_idx', [qw(product_id)]); - $dbh->bz_drop_index('bugs', 'bugs_component_idx'); - $dbh->bz_add_index('bugs', 'bugs_component_id_idx', [qw(component_id)]); - - print "Removing, renaming, and retyping old product and component fields.\n"; - $dbh->bz_drop_column("components", "program"); - $dbh->bz_drop_column("versions", "program"); - $dbh->bz_drop_column("milestones", "product"); - $dbh->bz_drop_column("bugs", "product"); - $dbh->bz_drop_column("bugs", "component"); - $dbh->bz_drop_column("attachstatusdefs", "product") - if $dbh->bz_table_info("attachstatusdefs"); - $dbh->bz_rename_column("products", "product", "name"); - $dbh->bz_alter_column("products", "name", - {TYPE => 'varchar(64)', NOTNULL => 1}); - $dbh->bz_rename_column("components", "value", "name"); - $dbh->bz_alter_column("components", "name", - {TYPE => 'varchar(64)', NOTNULL => 1}); - - print "Adding indexes for products and components tables.\n"; - $dbh->bz_add_index('products', 'products_name_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(name)]}); - $dbh->bz_add_index('components', 'components_product_id_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(product_id name)]}); - $dbh->bz_add_index('components', 'components_name_idx', [qw(name)]); -} - -# 2002-09-22 - bugreport@peshkin.net - bug 157756 -# -# If the whole groups system is new, but the installation isn't, -# convert all the old groupset groups, etc... -# -# This requires: -# 1) define groups ids in group table -# 2) populate user_group_map with grants from old groupsets and blessgroupsets -# 3) populate bug_group_map with data converted from old bug groupsets -# 4) convert activity logs to use group names instead of numbers -# 5) identify the admin from the old all-ones groupset -# -# ListBits(arg) returns a list of UNKNOWN<n> if the group -# has been deleted for all bits set in arg. When the activity -# records are converted from groupset numbers to lists of -# group names, ListBits is used to fill in a list of references -# to groupset bits for groups that no longer exist. -# -sub ListBits { - my ($num) = @_; - my @res = (); - my $curr = 1; - while (1) { - # Convert a big integer to a list of bits - my $sth = $dbh->prepare("SELECT ($num & ~$curr) > 0, - ($num & $curr), - ($num & ~$curr), - $curr << 1"); - $sth->execute; - my ($more, $thisbit, $remain, $nval) = $sth->fetchrow_array; - push @res,"UNKNOWN<$curr>" if ($thisbit); - $curr = $nval; - $num = $remain; - last if (!$more); - } - return @res; -} - -# The groups system needs to be converted if groupset exists -if ($dbh->bz_column_info("profiles", "groupset")) { - $dbh->bz_add_column('groups', 'last_changed', - {TYPE => 'DATETIME', NOTNULL => 1}, '0000-00-00 00:00:00'); - - # Some mysql versions will promote any unique key to primary key - # so all unique keys are removed first and then added back in - $dbh->bz_drop_index('groups', 'groups_bit_idx'); - $dbh->bz_drop_index('groups', 'groups_name_idx'); - if ($dbh->primary_key(undef, undef, 'groups')) { - $dbh->do("ALTER TABLE groups DROP PRIMARY KEY"); - } - - $dbh->bz_add_column('groups', 'id', - {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); - - $dbh->bz_add_index('groups', 'groups_name_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(name)]}); - $dbh->bz_add_column('profiles', 'refreshed_when', - {TYPE => 'DATETIME', NOTNULL => 1}, '0000-00-00 00:00:00'); - - # Convert all existing groupset records to map entries before removing - # groupset fields or removing "bit" from groups. - $sth = $dbh->prepare("SELECT bit, id FROM groups - WHERE bit > 0"); - $sth->execute(); - while (my ($bit, $gid) = $sth->fetchrow_array) { - # Create user_group_map membership grants for old groupsets. - # Get each user with the old groupset bit set - my $sth2 = $dbh->prepare("SELECT userid FROM profiles - WHERE (groupset & $bit) != 0"); - $sth2->execute(); - while (my ($uid) = $sth2->fetchrow_array) { - # Check to see if the user is already a member of the group - # and, if not, insert a new record. - my $query = "SELECT user_id FROM user_group_map - WHERE group_id = $gid AND user_id = $uid - AND isbless = 0"; - my $sth3 = $dbh->prepare($query); - $sth3->execute(); - if ( !$sth3->fetchrow_array() ) { - $dbh->do("INSERT INTO user_group_map - (user_id, group_id, isbless, grant_type) - VALUES($uid, $gid, 0, " . GRANT_DIRECT . ")"); - } - } - # Create user can bless group grants for old groupsets, but only - # if we're upgrading from a Bugzilla that had blessing. - if($dbh->bz_column_info('profiles', 'blessgroupset')) { - # Get each user with the old blessgroupset bit set - $sth2 = $dbh->prepare("SELECT userid FROM profiles - WHERE (blessgroupset & $bit) != 0"); - $sth2->execute(); - while (my ($uid) = $sth2->fetchrow_array) { - $dbh->do("INSERT INTO user_group_map - (user_id, group_id, isbless, grant_type) - VALUES($uid, $gid, 1, " . GRANT_DIRECT . ")"); - } - } - # Create bug_group_map records for old groupsets. - # Get each bug with the old group bit set. - $sth2 = $dbh->prepare("SELECT bug_id FROM bugs - WHERE (groupset & $bit) != 0"); - $sth2->execute(); - while (my ($bug_id) = $sth2->fetchrow_array) { - # Insert the bug, group pair into the bug_group_map. - $dbh->do("INSERT INTO bug_group_map - (bug_id, group_id) - VALUES($bug_id, $gid)"); - } - } - # Replace old activity log groupset records with lists of names of groups. - $sth = $dbh->prepare("SELECT id " . - "FROM fielddefs " . - "WHERE name = " . $dbh->quote('bug_group')); - $sth->execute(); - my ($bgfid) = $sth->fetchrow_array; - # Get the field id for the old groupset field - $sth = $dbh->prepare("SELECT id " . - "FROM fielddefs " . - "WHERE name = " . $dbh->quote('groupset')); - $sth->execute(); - my ($gsid) = $sth->fetchrow_array; - # Get all bugs_activity records from groupset changes - if ($gsid) { - $sth = $dbh->prepare("SELECT bug_id, bug_when, who, added, removed - FROM bugs_activity WHERE fieldid = $gsid"); - $sth->execute(); - while (my ($bug_id, $bug_when, $who, $added, $removed) = $sth->fetchrow_array) { - $added ||= 0; - $removed ||= 0; - # Get names of groups added. - my $sth2 = $dbh->prepare("SELECT name " . - "FROM groups " . - "WHERE (bit & $added) != 0 " . - "AND (bit & $removed) = 0"); - $sth2->execute(); - my @logadd = (); - while (my ($n) = $sth2->fetchrow_array) { - push @logadd, $n; - } - # Get names of groups removed. - $sth2 = $dbh->prepare("SELECT name " . - "FROM groups " . - "WHERE (bit & $removed) != 0 " . - "AND (bit & $added) = 0"); - $sth2->execute(); - my @logrem = (); - while (my ($n) = $sth2->fetchrow_array) { - push @logrem, $n; - } - # Get list of group bits added that correspond to missing groups. - $sth2 = $dbh->prepare("SELECT ($added & ~BIT_OR(bit)) FROM groups"); - $sth2->execute(); - my ($miss) = $sth2->fetchrow_array; - if ($miss) { - push @logadd, ListBits($miss); - print "\nWARNING - GROUPSET ACTIVITY ON BUG $bug_id CONTAINS DELETED GROUPS\n"; - } - # Get list of group bits deleted that correspond to missing groups. - $sth2 = $dbh->prepare("SELECT ($removed & ~BIT_OR(bit)) FROM groups"); - $sth2->execute(); - ($miss) = $sth2->fetchrow_array; - if ($miss) { - push @logrem, ListBits($miss); - print "\nWARNING - GROUPSET ACTIVITY ON BUG $bug_id CONTAINS DELETED GROUPS\n"; - } - my $logr = ""; - my $loga = ""; - $logr = join(", ", @logrem) . '?' if @logrem; - $loga = join(", ", @logadd) . '?' if @logadd; - # Replace to old activity record with the converted data. - $dbh->do("UPDATE bugs_activity SET fieldid = $bgfid, added = " . - $dbh->quote($loga) . ", removed = " . - $dbh->quote($logr) . - " WHERE bug_id = $bug_id AND bug_when = " . - $dbh->quote($bug_when) . - " AND who = $who AND fieldid = $gsid"); - - } - # Replace groupset changes with group name changes in profiles_activity. - # Get profiles_activity records for groupset. - $sth = $dbh->prepare( - "SELECT userid, profiles_when, who, newvalue, oldvalue " . - "FROM profiles_activity " . - "WHERE fieldid = $gsid"); - $sth->execute(); - while (my ($uid, $uwhen, $uwho, $added, $removed) = $sth->fetchrow_array) { - $added ||= 0; - $removed ||= 0; - # Get names of groups added. - my $sth2 = $dbh->prepare("SELECT name " . - "FROM groups " . - "WHERE (bit & $added) != 0 " . - "AND (bit & $removed) = 0"); - $sth2->execute(); - my @logadd = (); - while (my ($n) = $sth2->fetchrow_array) { - push @logadd, $n; - } - # Get names of groups removed. - $sth2 = $dbh->prepare("SELECT name " . - "FROM groups " . - "WHERE (bit & $removed) != 0 " . - "AND (bit & $added) = 0"); - $sth2->execute(); - my @logrem = (); - while (my ($n) = $sth2->fetchrow_array) { - push @logrem, $n; - } - my $ladd = ""; - my $lrem = ""; - $ladd = join(", ", @logadd) . '?' if @logadd; - $lrem = join(", ", @logrem) . '?' if @logrem; - # Replace profiles_activity record for groupset change - # with group list. - $dbh->do("UPDATE profiles_activity " . - "SET fieldid = $bgfid, newvalue = " . - $dbh->quote($ladd) . ", oldvalue = " . - $dbh->quote($lrem) . - " WHERE userid = $uid AND profiles_when = " . - $dbh->quote($uwhen) . - " AND who = $uwho AND fieldid = $gsid"); - - } - } - # Identify admin group. - my $sth = $dbh->prepare("SELECT id FROM groups - WHERE name = 'admin'"); - $sth->execute(); - my ($adminid) = $sth->fetchrow_array(); - # find existing admins - # Don't lose admins from DBs where Bug 157704 applies - $sth = $dbh->prepare( - "SELECT userid, (groupset & 65536), login_name " . - "FROM profiles " . - "WHERE (groupset | 65536) = 9223372036854775807"); - $sth->execute(); - while ( my ($userid, $iscomplete, $login_name) = $sth->fetchrow_array() ) { - # existing administrators are made members of group "admin" - print "\nWARNING - $login_name IS AN ADMIN IN SPITE OF BUG 157704\n\n" - if (!$iscomplete); - push @admins, $userid; - } - $dbh->bz_drop_column('profiles','groupset'); - $dbh->bz_drop_column('profiles','blessgroupset'); - $dbh->bz_drop_column('bugs','groupset'); - $dbh->bz_drop_column('groups','bit'); - $dbh->do("DELETE FROM fielddefs WHERE name = " . $dbh->quote('groupset')); -} - -# September 2002 myk@mozilla.org bug 98801 -# Convert the attachment statuses tables into flags tables. -if ($dbh->bz_table_info("attachstatuses") - && $dbh->bz_table_info("attachstatusdefs")) -{ - print "Converting attachment statuses to flags...\n"; - - # Get IDs for the old attachment status and new flag fields. - my ($old_field_id) = $dbh->selectrow_array( - "SELECT id FROM fielddefs WHERE name='attachstatusdefs.name'") - || 0; - - $sth = $dbh->prepare("SELECT id FROM fielddefs " . - "WHERE name='flagtypes.name'"); - $sth->execute(); - my $new_field_id = $sth->fetchrow_arrayref()->[0]; - - # Convert attachment status definitions to flag types. If more than one - # status has the same name and description, it is merged into a single - # status with multiple inclusion records. - $sth = $dbh->prepare("SELECT id, name, description, sortkey, product_id " . - "FROM attachstatusdefs"); - - # status definition IDs indexed by name/description - my $def_ids = {}; - - # merged IDs and the IDs they were merged into. The key is the old ID, - # the value is the new one. This allows us to give statuses the right - # ID when we convert them over to flags. This map includes IDs that - # weren't merged (in this case the old and new IDs are the same), since - # it makes the code simpler. - my $def_id_map = {}; - - $sth->execute(); - while (my ($id, $name, $desc, $sortkey, $prod_id) = $sth->fetchrow_array()) { - my $key = $name . $desc; - if (!$def_ids->{$key}) { - $def_ids->{$key} = $id; - my $quoted_name = $dbh->quote($name); - my $quoted_desc = $dbh->quote($desc); - $dbh->do("INSERT INTO flagtypes (id, name, description, sortkey, " . - "target_type) VALUES ($id, $quoted_name, $quoted_desc, " . - "$sortkey, 'a')"); - } - $def_id_map->{$id} = $def_ids->{$key}; - $dbh->do("INSERT INTO flaginclusions (type_id, product_id) " . - "VALUES ($def_id_map->{$id}, $prod_id)"); - } - - # Note: even though we've converted status definitions, we still can't drop - # the table because we need it to convert the statuses themselves. - - # Convert attachment statuses to flags. To do this we select the statuses - # from the status table and then, for each one, figure out who set it - # and when they set it from the bugs activity table. - my $id = 0; - $sth = $dbh->prepare( - "SELECT attachstatuses.attach_id, attachstatusdefs.id, " . - "attachstatusdefs.name, attachments.bug_id " . - "FROM attachstatuses, attachstatusdefs, attachments " . - "WHERE attachstatuses.statusid = attachstatusdefs.id " . - "AND attachstatuses.attach_id = attachments.attach_id"); - - # a query to determine when the attachment status was set and who set it - my $sth2 = $dbh->prepare("SELECT added, who, bug_when " . - "FROM bugs_activity " . - "WHERE bug_id = ? AND attach_id = ? " . - "AND fieldid = $old_field_id " . - "ORDER BY bug_when DESC"); - - $sth->execute(); - while (my ($attach_id, $def_id, $status, $bug_id) = $sth->fetchrow_array()) { - ++$id; - - # Determine when the attachment status was set and who set it. - # We should always be able to find out this info from the bug activity, - # but we fall back to default values just in case. - $sth2->execute($bug_id, $attach_id); - my ($added, $who, $when); - while (($added, $who, $when) = $sth2->fetchrow_array()) { - last if $added =~ /(^|[, ]+)\Q$status\E([, ]+|$)/; - } - $who = $dbh->quote($who); # "NULL" by default if $who is undefined - $when = $when ? $dbh->quote($when) : "NOW()"; - - - $dbh->do("INSERT INTO flags (id, type_id, status, bug_id, attach_id, " . - "creation_date, modification_date, requestee_id, setter_id) " . - "VALUES ($id, $def_id_map->{$def_id}, '+', $bug_id, " . - "$attach_id, $when, $when, NULL, $who)"); - } - - # Now that we've converted both tables we can drop them. - $dbh->bz_drop_table("attachstatuses"); - $dbh->bz_drop_table("attachstatusdefs"); - - # Convert activity records for attachment statuses into records for flags. - my $sth = $dbh->prepare("SELECT attach_id, who, bug_when, added, removed " . - "FROM bugs_activity WHERE fieldid = $old_field_id"); - $sth->execute(); - while (my ($attach_id, $who, $when, $old_added, $old_removed) = - $sth->fetchrow_array()) - { - my @additions = split(/[, ]+/, $old_added); - @additions = map("$_+", @additions); - my $new_added = $dbh->quote(join(", ", @additions)); - - my @removals = split(/[, ]+/, $old_removed); - @removals = map("$_+", @removals); - my $new_removed = $dbh->quote(join(", ", @removals)); - - $old_added = $dbh->quote($old_added); - $old_removed = $dbh->quote($old_removed); - $who = $dbh->quote($who); - $when = $dbh->quote($when); - - $dbh->do("UPDATE bugs_activity SET fieldid = $new_field_id, " . - "added = $new_added, removed = $new_removed " . - "WHERE attach_id = $attach_id AND who = $who " . - "AND bug_when = $when AND fieldid = $old_field_id " . - "AND added = $old_added AND removed = $old_removed"); - } - - # Remove the attachment status field from the field definitions. - $dbh->do("DELETE FROM fielddefs WHERE name='attachstatusdefs.name'"); - - print "done.\n"; -} - -# 2004-12-13 Nick.Barnes@pobox.com bug 262268 -# Check for spaces and commas in flag type names; if found, rename them. -if ($dbh->bz_table_info("flagtypes")) { - # Get all names and IDs, to find broken ones and to - # check for collisions when renaming. - $sth = $dbh->prepare("SELECT name, id FROM flagtypes"); - $sth->execute(); - - my %flagtypes; - my @badflagnames; - - # find broken flagtype names, and populate a hash table - # to check for collisions. - while (my ($name, $id) = $sth->fetchrow_array()) { - $flagtypes{$name} = $id; - if ($name =~ /[ ,]/) { - push(@badflagnames, $name); - } - } - if (@badflagnames) { - print "Removing spaces and commas from flag names...\n"; - my ($flagname, $tryflagname); - my $sth = $dbh->prepare("UPDATE flagtypes SET name = ? WHERE id = ?"); - foreach $flagname (@badflagnames) { - print " Bad flag type name \"$flagname\" ...\n"; - # find a new name for this flagtype. - ($tryflagname = $flagname) =~ tr/ ,/__/; - # avoid collisions with existing flagtype names. - while (defined($flagtypes{$tryflagname})) { - print " ... can't rename as \"$tryflagname\" ...\n"; - $tryflagname .= "'"; - if (length($tryflagname) > 50) { - my $lastchanceflagname = (substr $tryflagname, 0, 47) . '...'; - if (defined($flagtypes{$lastchanceflagname})) { - print " ... last attempt as \"$lastchanceflagname\" still failed.'\n", - "Rename the flag by hand and run checksetup.pl again.\n"; - die("Bad flag type name $flagname"); - } - $tryflagname = $lastchanceflagname; - } - } - $sth->execute($tryflagname, $flagtypes{$flagname}); - print " renamed flag type \"$flagname\" as \"$tryflagname\"\n"; - $flagtypes{$tryflagname} = $flagtypes{$flagname}; - delete $flagtypes{$flagname}; - } - print "... done.\n"; - } -} - -# 2002-11-24 - bugreport@peshkin.net - bug 147275 -# -# If group_control_map is empty, backward-compatibility -# usebuggroups-equivalent records should be created. -my $entry = Bugzilla->params->{'useentrygroupdefault'}; -$sth = $dbh->prepare("SELECT COUNT(*) FROM group_control_map"); -$sth->execute(); -my ($mapcnt) = $sth->fetchrow_array(); -if ($mapcnt == 0) { - # Initially populate group_control_map. - # First, get all the existing products and their groups. - $sth = $dbh->prepare("SELECT groups.id, products.id, groups.name, " . - "products.name FROM groups, products " . - "WHERE isbuggroup != 0"); - $sth->execute(); - while (my ($groupid, $productid, $groupname, $productname) - = $sth->fetchrow_array()) { - if ($groupname eq $productname) { - # Product and group have same name. - $dbh->do("INSERT INTO group_control_map " . - "(group_id, product_id, entry, membercontrol, " . - "othercontrol, canedit) " . - "VALUES ($groupid, $productid, $entry, " . - CONTROLMAPDEFAULT . ", " . - CONTROLMAPNA . ", 0)"); - } else { - # See if this group is a product group at all. - my $sth2 = $dbh->prepare("SELECT id FROM products WHERE name = " . - $dbh->quote($groupname)); - $sth2->execute(); - my ($id) = $sth2->fetchrow_array(); - if (!$id) { - # If there is no product with the same name as this - # group, then it is permitted for all products. - $dbh->do("INSERT INTO group_control_map " . - "(group_id, product_id, entry, membercontrol, " . - "othercontrol, canedit) " . - "VALUES ($groupid, $productid, 0, " . - CONTROLMAPSHOWN . ", " . - CONTROLMAPNA . ", 0)"); - } - } - } -} - -# 2004-07-17 GRM - Remove "subscriptions" concept from charting, and add -# group-based security instead. -if ($dbh->bz_table_info("user_series_map")) { - # Oracle doesn't like "date" as a column name, and apparently some DBs - # don't like 'value' either. We use the changes to subscriptions as - # something to hang these renamings off. - $dbh->bz_rename_column('series_data', 'date', 'series_date'); - $dbh->bz_rename_column('series_data', 'value', 'series_value'); - - # series_categories.category_id produces a too-long column name for the - # auto-incrementing sequence (Oracle again). - $dbh->bz_rename_column('series_categories', 'category_id', 'id'); - - $dbh->bz_add_column("series", "public", - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}); - - # Migrate public-ness across from user_series_map to new field - $sth = $dbh->prepare("SELECT series_id from user_series_map " . - "WHERE user_id = 0"); - $sth->execute(); - while (my ($public_series_id) = $sth->fetchrow_array()) { - $dbh->do("UPDATE series SET public = 1 " . - "WHERE series_id = $public_series_id"); - } - - $dbh->bz_drop_table("user_series_map"); -} - -# 2003-06-26 Copy the old charting data into the database, and create the -# queries that will keep it all running. When the old charting system goes -# away, if this code ever runs, it'll just find no files and do nothing. -my $series_exists = $dbh->selectrow_array("SELECT 1 FROM series " . - $dbh->sql_limit(1)); -if (!$series_exists) { - print "Migrating old chart data into database ...\n" unless $silent; - - require Bugzilla::Series; - - # We prepare the handle to insert the series data - my $seriesdatasth = $dbh->prepare("INSERT INTO series_data " . - "(series_id, series_date, series_value) " . - "VALUES (?, ?, ?)"); - - my $deletesth = $dbh->prepare("DELETE FROM series_data - WHERE series_id = ? AND series_date = ?"); - - my $groupmapsth = $dbh->prepare("INSERT INTO category_group_map " . - "(category_id, group_id) " . - "VALUES (?, ?)"); - - # Fields in the data file (matches the current collectstats.pl) - my @statuses = - qw(NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED VERIFIED CLOSED); - my @resolutions = - qw(FIXED INVALID WONTFIX LATER REMIND DUPLICATE WORKSFORME MOVED); - my @fields = (@statuses, @resolutions); - - # We have a localisation problem here. Where do we get these values? - my $all_name = "-All-"; - my $open_name = "All Open"; - - my $products = $dbh->selectall_arrayref("SELECT name FROM products"); - - foreach my $product ((map { $_->[0] } @$products), "-All-") { - # First, create the series - my %queries; - my %seriesids; - - my $query_prod = ""; - if ($product ne "-All-") { - $query_prod = "product=" . html_quote($product) . "&"; - } - - # The query for statuses is different to that for resolutions. - $queries{$_} = ($query_prod . "bug_status=$_") foreach (@statuses); - $queries{$_} = ($query_prod . "resolution=$_") foreach (@resolutions); - - foreach my $field (@fields) { - # Create a Series for each field in this product. - # user ID = 0 is used. - my $series = new Bugzilla::Series(undef, $product, $all_name, - $field, 0, 1, - $queries{$field}, 1); - $series->writeToDatabase(); - $seriesids{$field} = $series->{'series_id'}; - } - - # We also add a new query for "Open", so that migrated products get - # the same set as new products (see editproducts.cgi.) - my @openedstatuses = ("UNCONFIRMED", "NEW", "ASSIGNED", "REOPENED"); - my $query = join("&", map { "bug_status=$_" } @openedstatuses); - my $series = new Bugzilla::Series(undef, $product, $all_name, - $open_name, 0, 1, - $query_prod . $query, 1); - $series->writeToDatabase(); - $seriesids{$open_name} = $series->{'series_id'}; - - # Now, we attempt to read in historical data, if any - # Convert the name in the same way that collectstats.pl does - my $product_file = $product; - $product_file =~ s/\//-/gs; - $product_file = "$datadir/mining/$product_file"; - - # There are many reasons that this might fail (e.g. no stats for this - # product), so we don't worry if it does. - open(IN, $product_file) or next; - - # The data files should be in a standard format, even for old - # Bugzillas, because of the conversion code further up this file. - my %data; - my $last_date = ""; - - while (<IN>) { - if (/^(\d+\|.*)/) { - my @numbers = split(/\||\r/, $1); - - # Only take the first line for each date; it was possible to - # run collectstats.pl more than once in a day. - next if $numbers[0] eq $last_date; - - for my $i (0 .. $#fields) { - # $numbers[0] is the date - $data{$fields[$i]}{$numbers[0]} = $numbers[$i + 1]; - - # Keep a total of the number of open bugs for this day - if (is_open_state($fields[$i])) { - $data{$open_name}{$numbers[0]} += $numbers[$i + 1]; - } - } - - $last_date = $numbers[0]; - } - } - - close(IN); - - foreach my $field (@fields, $open_name) { - # Insert values into series_data: series_id, date, value - my %fielddata = %{$data{$field}}; - foreach my $date (keys %fielddata) { - # We need to delete in case the text file had duplicate entries - # in it. - $deletesth->execute($seriesids{$field}, - $date); - - # We prepared this above - $seriesdatasth->execute($seriesids{$field}, - $date, - $fielddata{$date} || 0); - } - } - - # Create the groupsets for the category - my $category_id = - $dbh->selectrow_array("SELECT id " . - "FROM series_categories " . - "WHERE name = " . $dbh->quote($product)); - my $product_id = - $dbh->selectrow_array("SELECT id " . - "FROM products " . - "WHERE name = " . $dbh->quote($product)); - - if (defined($category_id) && defined($product_id)) { - - # Get all the mandatory groups for this product - my $group_ids = - $dbh->selectcol_arrayref("SELECT group_id " . - "FROM group_control_map " . - "WHERE product_id = $product_id " . - "AND (membercontrol = " . CONTROLMAPMANDATORY . - " OR othercontrol = " . CONTROLMAPMANDATORY . ")"); - - foreach my $group_id (@$group_ids) { - $groupmapsth->execute($category_id, $group_id); - } - } - } -} - -Bugzilla::Field::create_or_update( - {name => "owner_idle_time", desc => "Time Since Assignee Touched"}); - -# 2004-04-12 - Keep regexp-based group permissions up-to-date - Bug 240325 -if ($dbh->bz_column_info("user_group_map", "isderived")) { - $dbh->bz_add_column('user_group_map', 'grant_type', - {TYPE => 'INT1', NOTNULL => 1, DEFAULT => '0'}); - $dbh->do("DELETE FROM user_group_map WHERE isderived != 0"); - $dbh->do("UPDATE user_group_map SET grant_type = " . GRANT_DIRECT); - $dbh->bz_drop_column("user_group_map", "isderived"); - - $dbh->bz_drop_index('user_group_map', 'user_group_map_user_id_idx'); - $dbh->bz_add_index('user_group_map', 'user_group_map_user_id_idx', - {TYPE => 'UNIQUE', - FIELDS => [qw(user_id group_id grant_type isbless)]}); - - # Make sure groups get rederived - $dbh->do("UPDATE groups SET last_changed = NOW() WHERE name = 'admin'"); -} - -# 2004-07-16 - Make it possible to have group-group relationships other than -# membership and bless. -if ($dbh->bz_column_info("group_group_map", "isbless")) { - $dbh->bz_add_column('group_group_map', 'grant_type', - {TYPE => 'INT1', NOTNULL => 1, DEFAULT => '0'}); - $dbh->do("UPDATE group_group_map SET grant_type = " . - "IF(isbless, " . GROUP_BLESS . ", " . - GROUP_MEMBERSHIP . ")"); - $dbh->bz_drop_index('group_group_map', 'group_group_map_member_id_idx'); - $dbh->bz_drop_column("group_group_map", "isbless"); - $dbh->bz_add_index('group_group_map', 'group_group_map_member_id_idx', - {TYPE => 'UNIQUE', FIELDS => [qw(member_id grantor_id grant_type)]}); -} - -# Allow profiles to optionally be linked to a unique identifier in an outside -# login data source -$dbh->bz_add_column("profiles", "extern_id", {TYPE => 'varchar(64)'}); - -# 2004-11-20 - LpSolit@netscape.net - Bug 180879 -# Add grant and request groups for flags -$dbh->bz_add_column('flagtypes', 'grant_group_id', {TYPE => 'INT3'}); -$dbh->bz_add_column('flagtypes', 'request_group_id', {TYPE => 'INT3'}); - -# 2004-01-03 - bug 253721 erik@dasbistro.com -# mailto is no longer just userids -$dbh->bz_rename_column('whine_schedules', 'mailto_userid', 'mailto'); -$dbh->bz_add_column('whine_schedules', 'mailto_type', - {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '0'}); - -# 2005-01-29 - mkanat@bugzilla.org -if (!$dbh->bz_column_info('longdescs', 'already_wrapped')) { - # Old, pre-wrapped comments should not be auto-wrapped - $dbh->bz_add_column('longdescs', 'already_wrapped', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}, 1); - # If an old comment doesn't have a newline in the first 80 characters, - # (or doesn't contain a newline at all) and it contains a space, - # then it's probably a mis-wrapped comment and we should wrap it - # at display-time. - print "Fixing old, mis-wrapped comments...\n"; - $dbh->do(q{UPDATE longdescs SET already_wrapped = 0 - WHERE (} . $dbh->sql_position(q{'\n'}, 'thetext') . q{ > 80 - OR } . $dbh->sql_position(q{'\n'}, 'thetext') . q{ = 0) - AND SUBSTRING(thetext FROM 1 FOR 80) LIKE '% %'}); -} - -# 2001-09-03 (landed 2005-02-24) dkl@redhat.com bug 17453 -# Moved enum types to separate tables so we need change the old enum types to -# standard varchars in the bugs table. -$dbh->bz_alter_column('bugs', 'bug_status', - {TYPE => 'varchar(64)', NOTNULL => 1}); -# 2005-03-23 Tomas.Kopal@altap.cz - add default value to resolution, bug 286695 -$dbh->bz_alter_column('bugs', 'resolution', - {TYPE => 'varchar(64)', NOTNULL => 1, DEFAULT => "''"}); -$dbh->bz_alter_column('bugs', 'priority', - {TYPE => 'varchar(64)', NOTNULL => 1}); -$dbh->bz_alter_column('bugs', 'bug_severity', - {TYPE => 'varchar(64)', NOTNULL => 1}); -$dbh->bz_alter_column('bugs', 'rep_platform', - {TYPE => 'varchar(64)', NOTNULL => 1}, ''); -$dbh->bz_alter_column('bugs', 'op_sys', - {TYPE => 'varchar(64)', NOTNULL => 1}); - - -# 2005-02-20 - LpSolit@gmail.com - Bug 277504 -# When migrating quips from the '$datadir/comments' file to the DB, -# the user ID should be NULL instead of 0 (which is an invalid user ID). -if ($dbh->bz_column_info('quips', 'userid')->{NOTNULL}) { - $dbh->bz_alter_column('quips', 'userid', {TYPE => 'INT3'}); - print "Changing owner to NULL for quips where the owner is unknown...\n"; - $dbh->do('UPDATE quips SET userid = NULL WHERE userid = 0'); -} - -# Add fulltext indexes for bug summaries and descriptions/comments. -if (!$dbh->bz_index_info('bugs', 'bugs_short_desc_idx')) { - print "Adding full-text index for short_desc column in bugs table...\n"; - $dbh->bz_add_index('bugs', 'bugs_short_desc_idx', - {TYPE => 'FULLTEXT', FIELDS => [qw(short_desc)]}); -} -# Right now, we only create the "thetext" index on MySQL. -if ($dbh->isa('Bugzilla::DB::Mysql') - && !$dbh->bz_index_info('longdescs', 'longdescs_thetext_idx')) -{ - print "Adding full-text index for thetext column in longdescs table...\n"; - $dbh->bz_add_index('longdescs', 'longdescs_thetext_idx', - {TYPE => 'FULLTEXT', FIELDS => [qw(thetext)]}); -} - -# 2002 November, myk@mozilla.org, bug 178841: -# -# Convert the "attachments.filename" column from a ridiculously large -# "mediumtext" to a much more sensible "varchar(100)". Also takes -# the opportunity to remove paths from existing filenames, since they -# shouldn't be there for security. Buggy browsers include them, -# and attachment.cgi now takes them out, but old ones need converting. -# -{ - my $ref = $dbh->bz_column_info("attachments", "filename"); - if ($ref->{TYPE} ne 'varchar(100)') { - print "Removing paths from filenames in attachments table...\n"; - - $sth = $dbh->prepare("SELECT attach_id, filename FROM attachments " . - "WHERE " . $dbh->sql_position(q{'/'}, 'filename') . " > 0 OR " . - $dbh->sql_position(q{'\\\\'}, 'filename') . " > 0"); - $sth->execute; - - while (my ($attach_id, $filename) = $sth->fetchrow_array) { - $filename =~ s/^.*[\/\\]//; - my $quoted_filename = $dbh->quote($filename); - $dbh->do("UPDATE attachments SET filename = $quoted_filename " . - "WHERE attach_id = $attach_id"); - } - - print "Done.\n"; - - print "Resizing attachments.filename from mediumtext to varchar(100).\n"; - $dbh->bz_alter_column("attachments", "filename", - {TYPE => 'varchar(100)', NOTNULL => 1}); - } -} - -# 2003-01-11, burnus@net-b.de, bug 184309 -# Support for quips approval -$dbh->bz_add_column('quips', 'approved', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'TRUE'}); - -# 2002-12-20 Bug 180870 - remove manual shadowdb replication code -$dbh->bz_drop_table("shadowlog"); - -# 2003-04-27 - bugzilla@chimpychompy.org (GavinS) -# -# Bug 180086 (http://bugzilla.mozilla.org/show_bug.cgi?id=180086) -# -# Renaming the 'count' column in the votes table because Sybase doesn't -# like it -if ($dbh->bz_column_info('votes', 'count')) { - # 2003-04-24 - myk@mozilla.org/bbaetz@acm.org, bug 201018 - # Force all cached groups to be updated at login, due to security bug - # Do this here, inside the next schema change block, so that it doesn't - # get invalidated on every checksetup run. - $dbh->do("UPDATE profiles SET refreshed_when='1900-01-01 00:00:00'"); - - $dbh->bz_rename_column('votes', 'count', 'vote_count'); -} - -# 2004/02/15 - Summaries shouldn't be null - see bug 220232 -if (!exists $dbh->bz_column_info('bugs', 'short_desc')->{NOTNULL}) { - $dbh->bz_alter_column('bugs', 'short_desc', - {TYPE => 'MEDIUMTEXT', NOTNULL => 1}, ''); -} - -# 2003-10-24 - alt@sonic.net, bug 224208 -# Support classification level -$dbh->bz_add_column('products', 'classification_id', - {TYPE => 'INT2', NOTNULL => 1, DEFAULT => '1'}); - -# 2005-01-12 Nick Barnes <nb@ravenbrook.com> bug 278010 -# Rename any group which has an empty name. -# Note that there can be at most one such group (because of -# the SQL index on the name column). -$sth = $dbh->prepare("SELECT id FROM groups where name = ''"); -$sth->execute(); -my ($emptygroupid) = $sth->fetchrow_array(); -if ($emptygroupid) { - # There is a group with an empty name; find a name to rename it - # as. Must avoid collisions with existing names. Start with - # group_$gid and add _<n> if necessary. - my $trycount = 0; - my $trygroupname; - my $trygroupsth = $dbh->prepare("SELECT id FROM groups where name = ?"); - do { - $trygroupname = "group_$emptygroupid"; - if ($trycount > 0) { - $trygroupname .= "_$trycount"; - } - $trygroupsth->execute($trygroupname); - if ($trygroupsth->rows > 0) { - $trycount ++; - } - } while ($trygroupsth->rows > 0); - $sth = $dbh->prepare("UPDATE groups SET name = ? " . - "WHERE id = $emptygroupid"); - $sth->execute($trygroupname); - print "Group $emptygroupid had an empty name; renamed as '$trygroupname'.\n"; -} - -# 2005-02-12 bugreport@peshkin.net, bug 281787 -$dbh->bz_add_index('bugs_activity', 'bugs_activity_who_idx', [qw(who)]); - -# This lastdiffed change and these default changes are unrelated, -# but in order for MySQL to successfully run these default changes only once, -# they have to be inside this block. -# If bugs.lastdiffed is NOT NULL... -if($dbh->bz_column_info('bugs', 'lastdiffed')->{NOTNULL}) { - # Add defaults for some fields that should have them but didn't. - $dbh->bz_alter_column('bugs', 'status_whiteboard', - {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}); - $dbh->bz_alter_column('bugs', 'keywords', - {TYPE => 'MEDIUMTEXT', NOTNULL => 1, DEFAULT => "''"}); - $dbh->bz_alter_column('bugs', 'votes', - {TYPE => 'INT3', NOTNULL => 1, DEFAULT => '0'}); - # And change lastdiffed to NULL - $dbh->bz_alter_column('bugs', 'lastdiffed', {TYPE => 'DATETIME'}); -} - -# 2005-03-09 qa_contact should be NULL instead of 0, bug 285534 -if ($dbh->bz_column_info('bugs', 'qa_contact')->{NOTNULL}) { - $dbh->bz_alter_column('bugs', 'qa_contact', {TYPE => 'INT3'}); - $dbh->do("UPDATE bugs SET qa_contact = NULL WHERE qa_contact = 0"); -} - -# 2005-03-27 initialqacontact should be NULL instead of 0, bug 287483 -if ($dbh->bz_column_info('components', 'initialqacontact')->{NOTNULL}) { - $dbh->bz_alter_column('components', 'initialqacontact', {TYPE => 'INT3'}); - $dbh->do("UPDATE components SET initialqacontact = NULL " . - "WHERE initialqacontact = 0"); -} - -# 2005-03-29 - gerv@gerv.net - bug 73665. -# Migrate email preferences to new email prefs table. -if ($dbh->bz_column_info("profiles", "emailflags")) { - print "Migrating email preferences to new table ...\n" unless $silent; - - # These are the "roles" and "reasons" from the original code, mapped to - # the new terminology of relationships and events. - my %relationships = ("Owner" => REL_ASSIGNEE, - "Reporter" => REL_REPORTER, - "QAcontact" => REL_QA, - "CClist" => REL_CC, - "Voter" => REL_VOTER); - - my %events = ("Removeme" => EVT_ADDED_REMOVED, - "Comments" => EVT_COMMENT, - "Attachments" => EVT_ATTACHMENT, - "Status" => EVT_PROJ_MANAGEMENT, - "Resolved" => EVT_OPENED_CLOSED, - "Keywords" => EVT_KEYWORD, - "CC" => EVT_CC, - "Other" => EVT_OTHER, - "Unconfirmed" => EVT_UNCONFIRMED); - - # Request preferences - my %requestprefs = ("FlagRequestee" => EVT_FLAG_REQUESTED, - "FlagRequester" => EVT_REQUESTED_FLAG); - - # Select all emailflags flag strings - my $sth = $dbh->prepare("SELECT userid, emailflags FROM profiles"); - $sth->execute(); - - while (my ($userid, $flagstring) = $sth->fetchrow_array()) { - # If the user has never logged in since emailprefs arrived, and the - # temporary code to give them a default string never ran, then - # $flagstring will be null. In this case, they just get all mail. - $flagstring ||= ""; - - # The 255 param is here, because without a third param, split will - # trim any trailing null fields, which causes Perl to eject lots of - # warnings. Any suitably large number would do. - my %emailflags = split(/~/, $flagstring, 255); # Appease my editor: / - - my $sth2 = $dbh->prepare("INSERT into email_setting " . - "(user_id, relationship, event) VALUES (" . - "$userid, ?, ?)"); - foreach my $relationship (keys %relationships) { - foreach my $event (keys %events) { - my $key = "email$relationship$event"; - if (!exists($emailflags{$key}) || $emailflags{$key} eq 'on') { - $sth2->execute($relationships{$relationship}, - $events{$event}); - } - } - } - - # Note that in the old system, the value of "excludeself" is assumed to - # be off if the preference does not exist in the user's list, unlike - # other preferences whose value is assumed to be on if they do not - # exist. - # - # This preference has changed from global to per-relationship. - if (!exists($emailflags{'ExcludeSelf'}) - || $emailflags{'ExcludeSelf'} ne 'on') - { - foreach my $relationship (keys %relationships) { - $dbh->do("INSERT into email_setting " . - "(user_id, relationship, event) VALUES (" . - $userid . ", " . - $relationships{$relationship}. ", " . - EVT_CHANGED_BY_ME . ")"); - } - } - - foreach my $key (keys %requestprefs) { - if (!exists($emailflags{$key}) || $emailflags{$key} eq 'on') { - $dbh->do("INSERT into email_setting " . - "(user_id, relationship, event) VALUES (" . - $userid . ", " . REL_ANY . ", " . - $requestprefs{$key} . ")"); - } - } - } - - # EVT_ATTACHMENT_DATA should initially have identical settings to - # EVT_ATTACHMENT. - CloneEmailEvent(EVT_ATTACHMENT, EVT_ATTACHMENT_DATA); - - $dbh->bz_drop_column("profiles", "emailflags"); -} - -# Check for any "new" email settings that wouldn't have been ported over -# during the block above. Since these settings would have otherwise -# fallen under EVT_OTHER, we'll just clone those settings. That way if -# folks have already disabled all of that mail, there won't be any change. -{ - my %events = ("Dependency Tree Changes" => EVT_DEPEND_BLOCK); - - foreach my $desc (keys %events) { - my $event = $events{$desc}; - $sth = $dbh->prepare("SELECT count(*) FROM email_setting WHERE event = $event"); - $sth->execute(); - if (!($sth->fetchrow_arrayref()->[0])) { - # No settings in the table yet, so we assume that this is the - # first time it's being set. - print "Initializing \"$desc\" email_setting ...\n" unless $silent; - CloneEmailEvent(EVT_OTHER, $event); - } - } -} - -sub CloneEmailEvent { - my ($source, $target) = @_; - - my $sth1 = $dbh->prepare("SELECT user_id, relationship FROM email_setting - WHERE event = $source"); - my $sth2 = $dbh->prepare("INSERT into email_setting " . - "(user_id, relationship, event) VALUES (" . - "?, ?, $target)"); - - $sth1->execute(); - - while (my ($userid, $relationship) = $sth1->fetchrow_array()) { - $sth2->execute($userid, $relationship); - } -} - -# 2005-03-27: Standardize all boolean fields to plain "tinyint" -if ( $dbh->isa('Bugzilla::DB::Mysql') ) { - # This is a change to make things consistent with Schema, so we use - # direct-database access methods. - my $quip_info_sth = $dbh->column_info(undef, undef, 'quips', '%'); - my $quips_cols = $quip_info_sth->fetchall_hashref("COLUMN_NAME"); - my $approved_col = $quips_cols->{'approved'}; - if ( $approved_col->{TYPE_NAME} eq 'TINYINT' - and $approved_col->{COLUMN_SIZE} == 1 ) - { - # series.public could have been renamed to series.is_public, - # and so wouldn't need to be fixed manually. - if ($dbh->bz_column_info('series', 'public')) { - $dbh->bz_alter_column_raw('series', 'public', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '0'}); - } - $dbh->bz_alter_column_raw('bug_status', 'isactive', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); - $dbh->bz_alter_column_raw('rep_platform', 'isactive', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); - $dbh->bz_alter_column_raw('resolution', 'isactive', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); - $dbh->bz_alter_column_raw('op_sys', 'isactive', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); - $dbh->bz_alter_column_raw('bug_severity', 'isactive', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); - $dbh->bz_alter_column_raw('priority', 'isactive', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); - $dbh->bz_alter_column_raw('quips', 'approved', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => '1'}); - } -} +Bugzilla::Install::DB::update_table_definitions(); # 2005-04-07 - alt@sonic.net, bug 289455 # make classification_id field type be consistent with DB:Schema @@ -3293,50 +1230,6 @@ sub bailout { # this is just in case we get interrupted while getting passwd exit 1; } -if (@admins) { - # Identify admin group. - my $sth = $dbh->prepare("SELECT id FROM groups - WHERE name = 'admin'"); - $sth->execute(); - my ($adminid) = $sth->fetchrow_array(); - foreach my $userid (@admins) { - $dbh->do("INSERT INTO user_group_map - (user_id, group_id, isbless, grant_type) - VALUES ($userid, $adminid, 0, " . GRANT_DIRECT . ")"); - # Existing administrators are made blessers of group "admin" - # but only explicitly defined blessers can bless group admin. - # Other groups can be blessed by any admin (by default) or additional - # defined blessers. - $dbh->do("INSERT INTO user_group_map - (user_id, group_id, isbless, grant_type) - VALUES ($userid, $adminid, 1, " . GRANT_DIRECT . ")"); - } - - $dbh->bz_lock_tables('groups READ', - 'group_group_map WRITE'); - $dbh->do('DELETE FROM group_group_map WHERE member_id = ?', - undef, $adminid); - $sth = $dbh->prepare("SELECT id FROM groups"); - $sth->execute(); - while ( my ($id) = $sth->fetchrow_array() ) { - # Admins can bless every group. - $dbh->do("INSERT INTO group_group_map - (member_id, grantor_id, grant_type) - VALUES ($adminid, $id," . GROUP_BLESS . ")"); - # Admins can see every group. - $dbh->do("INSERT INTO group_group_map - (member_id, grantor_id, grant_type) - VALUES ($adminid, $id," . GROUP_VISIBLE . ")"); - # Admins are initially members of every group. - next if ($id == $adminid); - $dbh->do("INSERT INTO group_group_map - (member_id, grantor_id, grant_type) - VALUES ($adminid, $id," . GROUP_MEMBERSHIP . ")"); - } - $dbh->bz_unlock_tables(); -} - - my @groups = (); $sth = $dbh->prepare("SELECT id FROM groups"); $sth->execute(); |