From 8fa9965e5476717e574f2674c6df8c4487874634 Mon Sep 17 00:00:00 2001 From: Frédéric Buclin Date: Sun, 30 Jan 2011 13:07:59 +0100 Subject: Bug 616185: Move tags (aka lists of bugs) to their own DB tables r/a=mkanat --- Bugzilla/Bug.pm | 83 ++++++++++++++++++++++++++++++++++++++++++++++++ Bugzilla/Constants.pm | 7 ---- Bugzilla/DB/Schema.pm | 31 +++++++++++++++++- Bugzilla/Install/DB.pm | 48 +++++++++++++++++++++++----- Bugzilla/Search/Saved.pm | 12 +------ Bugzilla/User.pm | 23 ++++++++++++++ 6 files changed, 177 insertions(+), 27 deletions(-) (limited to 'Bugzilla') diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm index 398843009..e516e4bf8 100644 --- a/Bugzilla/Bug.pm +++ b/Bugzilla/Bug.pm @@ -1895,6 +1895,17 @@ sub _check_strict_isolation_for_user { } } +sub _check_tag_name { + my ($invocant, $tag) = @_; + + $tag = clean_text($tag); + $tag || ThrowUserError('no_tag_to_edit'); + ThrowUserError('tag_name_too_long') if length($tag) > MAX_LEN_QUERY_NAME; + trick_taint($tag); + # Tags are all lowercase. + return lc($tag); +} + sub _check_target_milestone { my ($invocant, $target, undef, $params) = @_; my $product = blessed($invocant) ? $invocant->product_obj @@ -2866,6 +2877,78 @@ sub remove_see_also { $self->{see_also} = \@new_see_also; } +sub add_tag { + my ($self, $tag) = @_; + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; + $tag = $self->_check_tag_name($tag); + + my $tag_id = $user->tags->{$tag}->{id}; + # If this tag doesn't exist for this user yet, create it. + if (!$tag_id) { + $dbh->do('INSERT INTO tags (user_id, name) VALUES (?, ?)', + undef, ($user->id, $tag)); + + $tag_id = $dbh->selectrow_array('SELECT id FROM tags + WHERE name = ? AND user_id = ?', + undef, ($tag, $user->id)); + # The list has changed. + delete $user->{tags}; + } + # Do nothing if this tag is already set for this bug. + return if grep { $_ eq $tag } @{$self->tags}; + + # Increment the counter. Do it before the SQL call below, + # to not count the tag twice. + $user->tags->{$tag}->{bug_count}++; + + $dbh->do('INSERT INTO bug_tag (bug_id, tag_id) VALUES (?, ?)', + undef, ($self->id, $tag_id)); + + push(@{$self->{tags}}, $tag); +} + +sub remove_tag { + my ($self, $tag) = @_; + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; + $tag = $self->_check_tag_name($tag); + + my $tag_id = exists $user->tags->{$tag} ? $user->tags->{$tag}->{id} : undef; + # Do nothing if the user doesn't use this tag, or didn't set it for this bug. + return unless ($tag_id && grep { $_ eq $tag } @{$self->tags}); + + $dbh->do('DELETE FROM bug_tag WHERE bug_id = ? AND tag_id = ?', + undef, ($self->id, $tag_id)); + + $self->{tags} = [grep { $_ ne $tag } @{$self->tags}]; + + # Decrement the counter, and delete the tag if no bugs are using it anymore. + if (!--$user->tags->{$tag}->{bug_count}) { + $dbh->do('DELETE FROM tags WHERE name = ? AND user_id = ?', + undef, ($tag, $user->id)); + + # The list has changed. + delete $user->{tags}; + } +} + +sub tags { + my $self = shift; + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; + + # This method doesn't support several users using the same bug object. + if (!exists $self->{tags}) { + $self->{tags} = $dbh->selectcol_arrayref( + 'SELECT name FROM bug_tag + INNER JOIN tags ON tags.id = bug_tag.tag_id + WHERE bug_id = ? AND user_id = ?', + undef, ($self->id, $user->id)); + } + return $self->{tags}; +} + ##################################################################### # Simple Accessors ##################################################################### diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm index f25c4f156..5b58fd494 100644 --- a/Bugzilla/Constants.pm +++ b/Bugzilla/Constants.pm @@ -82,9 +82,6 @@ use Memoize; DEFAULT_QUERY_NAME DEFAULT_MILESTONE - QUERY_LIST - LIST_OF_BUGS - SAVE_NUM_SEARCHES COMMENT_COLS @@ -288,10 +285,6 @@ use constant DEFAULT_QUERY_NAME => '(Default query)'; # The default "defaultmilestone" created for products. use constant DEFAULT_MILESTONE => '---'; -# The possible types for saved searches. -use constant QUERY_LIST => 0; -use constant LIST_OF_BUGS => 1; - # How many of the user's most recent searches to save. use constant SAVE_NUM_SEARCHES => 10; diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm index e2c1d22e4..2e1b3f78a 100644 --- a/Bugzilla/DB/Schema.pm +++ b/Bugzilla/DB/Schema.pm @@ -953,7 +953,6 @@ use constant ABSTRACT_SCHEMA => { DELETE => 'CASCADE'}}, name => {TYPE => 'varchar(64)', NOTNULL => 1}, query => {TYPE => 'LONGTEXT', NOTNULL => 1}, - query_type => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0}, ], INDEXES => [ namedqueries_userid_idx => {FIELDS => [qw(userid name)], @@ -979,6 +978,36 @@ use constant ABSTRACT_SCHEMA => { ], }, + tags => { + FIELDS => [ + id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}, + name => {TYPE => 'varchar(64)', NOTNULL => 1}, + user_id => {TYPE => 'INT3', NOTNULL => 1, + REFERENCES => {TABLE => 'profiles', + COLUMN => 'userid', + DELETE => 'CASCADE'}}, + ], + INDEXES => [ + tags_user_id_idx => {FIELDS => [qw(user_id name)], TYPE => 'UNIQUE'}, + ], + }, + + bug_tag => { + FIELDS => [ + bug_id => {TYPE => 'INT3', NOTNULL => 1, + REFERENCES => {TABLE => 'bugs', + COLUMN => 'bug_id', + DELETE => 'CASCADE'}}, + tag_id => {TYPE => 'INT3', NOTNULL => 1, + REFERENCES => {TABLE => 'tags', + COLUMN => 'id', + DELETE => 'CASCADE'}}, + ], + INDEXES => [ + bug_tag_bug_id_idx => {FIELDS => [qw(bug_id tag_id)], TYPE => 'UNIQUE'}, + ], + }, + component_cc => { FIELDS => [ diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm index fee87fa92..7233c9dc0 100644 --- a/Bugzilla/Install/DB.pm +++ b/Bugzilla/Install/DB.pm @@ -438,10 +438,6 @@ sub update_table_definitions { # PUBLIC is a reserved word in Oracle. $dbh->bz_rename_column('series', 'public', 'is_public'); - # 2005-10-21 LpSolit@gmail.com - Bug 313020 - $dbh->bz_add_column('namedqueries', 'query_type', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0}); - # 2005-11-04 LpSolit@gmail.com - Bug 305927 $dbh->bz_alter_column('groups', 'userregexp', {TYPE => 'TINYTEXT', NOTNULL => 1, DEFAULT => "''"}); @@ -549,10 +545,6 @@ sub update_table_definitions { # 2007-09-09 LpSolit@gmail.com - Bug 99215 _fix_attachment_modification_date(); - # This had the wrong definition in DB::Schema. - $dbh->bz_alter_column('namedqueries', 'query_type', - {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 0}); - $dbh->bz_drop_index('longdescs', 'longdescs_thetext_idx'); _populate_bugs_fulltext(); @@ -650,6 +642,9 @@ sub update_table_definitions { $dbh->bz_add_column('bug_see_also', 'id', {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}); + # 2011-01-29 LpSolit@gmail.com - Bug 616185 + _migrate_user_tags(); + ################################################################ # New --TABLE-- changes should go *** A B O V E *** this point # ################################################################ @@ -3472,6 +3467,43 @@ sub _fix_series_indexes { {FIELDS => [qw(category subcategory name)], TYPE => 'UNIQUE'}); } +sub _migrate_user_tags { + my $dbh = Bugzilla->dbh; + return unless $dbh->bz_column_info('namedqueries', 'query_type'); + + my $tags = $dbh->selectall_arrayref('SELECT userid, name, query + FROM namedqueries + WHERE query_type != 0'); + + my $sth_tags = $dbh->prepare('INSERT INTO tags (user_id, name) VALUES (?, ?)'); + my $sth_bug_tag = $dbh->prepare('INSERT INTO bug_tag (bug_id, tag_id) + VALUES (?, ?)'); + my $sth_nq = $dbh->prepare('UPDATE namedqueries SET query = ? + WHERE userid = ? AND name = ?'); + + foreach my $tag (@$tags) { + my ($user_id, $name, $query) = @$tag; + # Tags are all lowercase. + my $tag_name = lc($name); + + $sth_tags->execute($user_id, $tag_name); + my $tag_id = $dbh->selectrow_array( + 'SELECT id FROM tags WHERE user_id = ? AND name = ?', + undef, ($user_id, $tag_name)); + + $query =~ s/^bug_id=//; + my @bug_ids = split(/[\s,]+/, $query); + $sth_bug_tag->execute($_, $tag_id) foreach @bug_ids; + + # Existing tags may be used in whines, or shared with + # other users. So we convert them rather than delete them. + my $encoded_name = url_quote($tag_name); + $sth_nq->execute("tag=$encoded_name", $user_id, $name); + } + + $dbh->bz_drop_column('namedqueries', 'query_type'); +} + 1; __END__ diff --git a/Bugzilla/Search/Saved.pm b/Bugzilla/Search/Saved.pm index 4b46fc75c..9828d6e02 100644 --- a/Bugzilla/Search/Saved.pm +++ b/Bugzilla/Search/Saved.pm @@ -45,17 +45,15 @@ use constant DB_COLUMNS => qw( userid name query - query_type ); use constant VALIDATORS => { name => \&_check_name, query => \&_check_query, - query_type => \&_check_query_type, link_in_footer => \&_check_link_in_footer, }; -use constant UPDATE_COLUMNS => qw(name query query_type); +use constant UPDATE_COLUMNS => qw(name query); ############### # Constructor # @@ -141,12 +139,6 @@ sub _check_query { return $cgi->query_string; } -sub _check_query_type { - my ($invocant, $type) = @_; - # Right now the only query type is LIST_OF_BUGS. - return $type ? LIST_OF_BUGS : QUERY_LIST; -} - ######################### # Database Manipulation # ######################### @@ -301,7 +293,6 @@ sub shared_with_users { # Simple Accessors # #################### -sub type { return $_[0]->{'query_type'}; } sub url { return $_[0]->{'query'}; } sub user { @@ -317,7 +308,6 @@ sub user { sub set_name { $_[0]->set('name', $_[1]); } sub set_url { $_[0]->set('query', $_[1]); } -sub set_query_type { $_[0]->set('query_type', $_[1]); } 1; diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm index eafda6563..0b639ee0d 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -363,6 +363,24 @@ sub queries_available { return $self->{queries_available}; } +sub tags { + my $self = shift; + my $dbh = Bugzilla->dbh; + + if (!defined $self->{tags}) { + # We must use LEFT JOIN instead of INNER JOIN as we may be + # in the process of inserting a new tag to some bugs, + # in which case there are no bugs with this tag yet. + $self->{tags} = $dbh->selectall_hashref( + 'SELECT name, id, COUNT(bug_id) AS bug_count + FROM tags + LEFT JOIN bug_tag ON bug_tag.tag_id = tags.id + WHERE user_id = ? ' . $dbh->sql_group_by('id', 'name'), + 'name', undef, $self->id); + } + return $self->{tags}; +} + ########################## # Saved Recent Bug Lists # ########################## @@ -2074,6 +2092,11 @@ internally, such code must call this method to flush the cached result. An arrayref of group ids. The user can share their own queries with these groups. +=item C + +Returns a hashref with tag IDs as key, and a hashref with tag 'id', +'name' and 'bug_count' as value. + =back =head2 Account Lockout -- cgit v1.2.3-24-g4f1b