############################################################################### # Copyright (c) 2003 Jason Remillard. All rights reserved. # # This program is free software; you can redistribute it and modify it under # the terms of the GPL. # Model object for handling metric data. package Codestriker::Model::Metrics; use strict; use Encode qw(decode_utf8); use Codestriker::DB::DBI; sub new { my ($class, $topicid) = @_; my $self = {}; $self->{topicmetrics} = undef; $self->{topicid} = $topicid; $self->{usermetrics} = {}; $self->{topichistoryrows} = undef; bless $self, $class; return $self; } # Sets the topic metrics values. The values are passed in as an # array. The array must be in the same order returned by # get_topic_metric(). Metrics that are bad are silently not stored. sub set_topic_metrics { my ($self,@metric_values) = @_; my @metrics = $self->get_topic_metrics(); for (my $index = 0; $index < scalar(@metrics); ++$index) { next if ($metrics[$index]->{enabled} == 0); die "error: not enough metrics" if (scalar(@metric_values) == 0); my $value = shift @metric_values; if ($self->_verify_metric($metrics[$index], $value) eq '') { $metrics[$index]->{value} = $value; } } } # Verifies that all of the topic metrics are well formed and valid. It will # return a non-empty string if a problem is found. sub verify_topic_metrics { my ($self,@metric_values) = @_; my $msg = ''; my @metrics = $self->get_topic_metrics(); for (my $index = 0; $index < scalar(@metrics); ++$index) { next if ($metrics[$index]->{enabled} == 0); # Disabled values may be in the database (somebody turned off # the metrics). However, they are not paramters so the index # between the paramters and the metrics objects will not # match. my $value = shift @metric_values; $msg .= $self->_verify_metric($metrics[$index], $value); } return $msg; } # Returns the topic metrics as a collection of references to # hashs. The hash that is returned has the same keys as the # metrics_schema hash, plus a value key. If the user has not entered a # value, it will be set to an empty string. sub get_topic_metrics { my $self = shift; my @topic_metrics; if (defined($self->{topicmetrics})) { # The topic metrics have already been loaded from the # database, just return the cached data. @topic_metrics = @{$self->{topicmetrics}}; } else { my @stored_metrics = (); if (defined($self->{topicid})) { # Obtain a database connection. my $dbh = Codestriker::DB::DBI->get_connection(); my $select_topic_metrics = $dbh->prepare_cached('SELECT topicmetric.metric_name, topicmetric.value ' . 'FROM topicmetric ' . 'WHERE topicmetric.topicid = ?'); $select_topic_metrics->execute($self->{topicid}); @stored_metrics = @{$select_topic_metrics->fetchall_arrayref()}; # Close the connection, and check for any database errors. Codestriker::DB::DBI->release_connection($dbh, 1); } # Match the configured metrics to the metrics in the database. If # the configured metric is found in the database, it is removed # from the stored_metric list to find any data that is in the # database, but is not configured. foreach my $metric_schema (Codestriker::get_metric_schema()) { if ($metric_schema->{scope} eq 'topic') { my $metric = { # This is the topic metric. name => $metric_schema->{name}, description => $metric_schema->{description}, value => '', filter => $metric_schema->{filter}, enabled => $metric_schema->{enabled}, in_database => 0 }; for (my $index = 0; $index < scalar(@stored_metrics); ++$index) { my $stored_metric = $stored_metrics[$index]; if ($stored_metric->[0] eq $metric_schema->{name}) { $metric->{value} = $stored_metric->[1]; $metric->{in_database} = 1; splice @stored_metrics, $index, 1; last; } } if ($metric_schema->{enabled} || $metric->{in_database}) { push @topic_metrics, $metric; } } } # Add in any metrics that are in the database but not # currently configured. The system should display the # metrics, but not let the user modify them. for (my $index = 0; $index < scalar(@stored_metrics); ++$index) { my $stored_metric = $stored_metrics[$index]; # This is the topic metric. my $metric = { name => $stored_metric->[0], description => '', value => $stored_metric->[1], # User can not change the metric, not configured. enabled => 0, in_database => 1 }; push @topic_metrics, $metric; } push @topic_metrics, $self->_get_built_in_topic_metrics(); $self->{topicmetrics} = \@topic_metrics; } return @topic_metrics; } # Get just the list of users that have actually looked at the review. This is # used on the main page to out users that are not doing the reviews when invited. sub get_list_of_actual_topic_participants { my ($self) = @_; my $dbh = Codestriker::DB::DBI->get_connection(); my $actual_user_list_ref = $dbh->selectall_arrayref( 'SELECT DISTINCT LOWER(email) FROM topicviewhistory ' . 'WHERE topicid = ?',{}, $self->{topicid}); my @actual_user_list = (); foreach my $user ( @$actual_user_list_ref ) { push @actual_user_list,$user->[0] if defined $user->[0] && $user->[0] ne ""; } # Close the connection, and check for any database errors. Codestriker::DB::DBI->release_connection($dbh, 1); return @actual_user_list; } # Get a list of users that have metric data for this topic. People can # look at the topic even if they were not invited, so if somebody touches the # topic, they will appear in this list. Using this function rather than the # invite list from the topic will insure that people don't get missed from # the metric data. sub get_complete_list_of_topic_participants { my ($self) = @_; my $dbh = Codestriker::DB::DBI->get_connection(); my @metric_user_list = @{ $dbh->selectall_arrayref(' SELECT distinct LOWER(email) from participant WHERE topicid = ?',{}, $self->{topicid})}; push @metric_user_list, @{ $dbh->selectall_arrayref(' SELECT LOWER(author) FROM topic WHERE id = ?',{}, $self->{topicid})}; push @metric_user_list, @{ $dbh->selectall_arrayref(' SELECT DISTINCT LOWER(email) FROM topicusermetric WHERE topicid = ?',{}, $self->{topicid})}; push @metric_user_list, @{ $dbh->selectall_arrayref( 'SELECT DISTINCT LOWER(author) FROM commentdata, commentstate ' . 'WHERE commentstate.topicid = ? AND commentstate.id = commentdata.commentstateid ', {}, $self->{topicid})}; push @metric_user_list, @{ $dbh->selectall_arrayref( 'SELECT DISTINCT LOWER(email) FROM topicviewhistory ' . 'WHERE topicid = ? AND email IS NOT NULL',{}, $self->{topicid})}; # remove the duplicates. my %metric_user_hash; foreach my $user (@metric_user_list) { $metric_user_hash{$user->[0]} = 1; } # Need to sort the empty user name last so that the template parameters # that are done by index don't start at 1, and therefor not allow users # to save the metrics. @metric_user_list = sort { return 1 if ( $a eq ""); return -1 if ( $b eq ""); return $a cmp $b; } keys %metric_user_hash; # Close the connection, and check for any database errors. Codestriker::DB::DBI->release_connection($dbh, 1); return @metric_user_list; } # Get a list of users that are invited for this review. sub get_list_of_topic_participants { my ($self) = @_; my $dbh = Codestriker::DB::DBI->get_connection(); my @metric_user_list = @{ $dbh->selectall_arrayref(' SELECT distinct LOWER(email) from participant WHERE topicid = ?',{}, $self->{topicid})}; push @metric_user_list, @{ $dbh->selectall_arrayref(' SELECT LOWER(author) FROM topic WHERE id = ?',{}, $self->{topicid})}; # remove the duplicates. my %metric_user_hash; foreach my $user (@metric_user_list) { $metric_user_hash{$user->[0]} = 1; } # Need to sort the empty user name last so that the template parameters # that are done by index don't start at 1, and therefor not allow users # to save the metrics. @metric_user_list = sort { return 1 if ( $a eq ""); return -1 if ( $b eq ""); return $a cmp $b; } keys %metric_user_hash; # Close the connection, and check for any database errors. Codestriker::DB::DBI->release_connection($dbh, 1); return @metric_user_list; } # Sets the metrics for a specific user, both authors and reviewers. cc's don't # get metrics. The metrics are sent in as an array, that must in the same # order as the get_user_metric() call returns them. Metrics that are bad are # silently not stored. sub set_user_metric { my ($self, $user, @metric_values) = @_; my @metrics = $self->get_user_metrics($user); for (my $index = 0; $index < scalar(@metrics); ++$index) { next if ($metrics[$index]->{enabled} == 0); die "error: not enough metrics" if (scalar(@metric_values) == 0); # Disabled values may be in the database (somebody turned off # the metrics). However, they are not paramters so the index # between the paramters and the metrics objects will not # match. my $value = shift @metric_values; if ($self->_verify_metric($metrics[$index], $value) eq '') { $metrics[$index]->{value} = $value } } } # Verifies that all of the user metrics are well formed and valid inputs. If a # problem is found the function will return a non-empty string. sub verify_user_metrics { my ($self, $user, @metric_values) = @_; my $msg = ''; my @metrics = $self->get_user_metrics($user); for (my $index = 0; $index < scalar(@metrics); ++$index) { next if ($metrics[$index]->{enabled} == 0); # Disabled values may be in the database (somebody turned off # the metrics). However, they are not paramters so the index # between the paramters and the metrics objects will not # match. my $value = shift @metric_values; $msg .= $self->_verify_metric($metrics[$index], $value); } return $msg; } # Returns the user metrics as a collection of references to hashs. The # hash that is returned has the same keys as the metrics_schema hash, # plus a value key. If the user has not entered a value, it will be # set to an empty string. sub get_user_metrics { my ($self, $username) = @_; my @user_metrics; if (exists($self->{usermetrics}->{$username})) { # If the metrics for this user has already been loaded from # the database, return the cached result of that load. @user_metrics = @{$self->{usermetrics}->{$username}}; } else { my @stored_metrics = (); if (defined($self->{topicid})) { # Obtain a database connection. my $dbh = Codestriker::DB::DBI->get_connection(); # Get all of the user outputs for this topic regardless of # the user. my $selected_all_user_metrics = $dbh->prepare_cached('SELECT DISTINCT metric_name ' . 'FROM topicusermetric ' . 'WHERE topicid = ? ' . 'ORDER BY metric_name'); $selected_all_user_metrics->execute($self->{topicid}); @stored_metrics = @{$selected_all_user_metrics->fetchall_arrayref()}; # Get the outputs for this user. my $select_user_metrics = $dbh->prepare_cached('SELECT metric_name, value ' . 'FROM topicusermetric ' . 'WHERE topicid = ? AND LOWER(email) = LOWER(?) ' . 'ORDER BY metric_name'); $select_user_metrics->execute($self->{topicid}, $username); my @user_stored_metrics = @{$select_user_metrics->fetchall_arrayref()}; # Stuff the complete list with the values from the current # user list. Handle displaying metrics that are in the # db, but not enabled for new topics. foreach my $metric (@stored_metrics) { my $foundit = 0; foreach my $user_metric (@user_stored_metrics) { if ($user_metric->[0] eq $metric->[0]) { $foundit = 1; push @$metric, $user_metric->[1]; last; } } if ($foundit == 0) { push @$metric,''; } } # Close the connection, and check for any database errors. Codestriker::DB::DBI->release_connection($dbh, 1); } foreach my $metric_schema (Codestriker::get_metric_schema()) { if ($metric_schema->{scope} ne 'topic') { my $metric = { name => $metric_schema->{name}, description => $metric_schema->{description}, value => '', enabled => $metric_schema->{enabled}, scope => $metric_schema->{scope}, filter => $metric_schema->{filter}, }; for (my $index = 0; $index < scalar(@stored_metrics); ++$index) { my $stored_metric = $stored_metrics[$index]; if ($stored_metric->[0] eq $metric_schema->{name}) { $metric->{value} = $stored_metric->[1]; $metric->{in_database} = 1; splice @stored_metrics, $index,1; last; } } if ($metric_schema->{enabled} || $metric->{in_database}) { if ($username eq "") { # don't let any metrics be set into the db for unknown users. $metric->{enabled} = 0; } push @user_metrics, $metric; } } } # Clean up any metrics that are in the database but not in the # schema, we will not let them change them, and we don't have # the description anymore. for (my $index = 0; $index < scalar(@stored_metrics); ++$index) { my $stored_metric = $stored_metrics[$index]; my $metric = { # this is the topic metric name=>$stored_metric->[0], description=>'', value=>$stored_metric->[1], scope=>'participant', enabled=>0, # user can not change the metric, no schema. in_database=>1 }; push @user_metrics, $metric; } $self->{usermetrics}->{$username} = \@user_metrics; } push @user_metrics, $self->_get_built_in_user_metrics($username); return @user_metrics; } # Returns the user metrics as a collection of references to hashs. sub get_user_metrics_totals { my ($self,@users) = @_; my @user_metrics; if (exists($self->{usermetrics_totals})) { @user_metrics = @$self->{usermetrics_totals}; } my @total_metrics; foreach my $user (@users) { my @metrics = $self->get_user_metrics($user); if (scalar(@total_metrics) == 0) { # Copy the metrics in. foreach my $metric (@metrics) { my %temp = %$metric; push @total_metrics, \%temp; } } else { # Add them up! for (my $index = 0; $index < scalar(@total_metrics) ; ++$index) { if ($metrics[$index]->{value} ne '') { if ($total_metrics[$index]->{value} eq '') { $total_metrics[$index]->{value} = 0; } $total_metrics[$index]->{value} += $metrics[$index]->{value}; } } } } $self->{usermetrics_totals}= \@total_metrics; return @total_metrics; } # Returns a list of hashes. Each hash is an event. In the hash is stored who # caused the event, when it happened, and what happened. The hashes are defined # as: # email -> the email address of the user who caused the event. # date -> when the event happened. # description -> the event description. # # The topic must be loaded from the db before this function can be called. sub get_topic_history { my ($self) = @_; my @topic_history = $self->_get_topic_history_rows(); my @event_list; my $last_history_row; foreach my $current_history_row (@topic_history) { if ( !defined($last_history_row) ) { # The first event is always the topic creation, so lets make # that now. my $filteredemail = Codestriker->filter_email($current_history_row->{author}); my $formatted_time = Codestriker->format_short_timestamp($current_history_row->{modified_ts}); push @event_list, { email=>$filteredemail, date =>$formatted_time, description=>'The topic is created.' }; } else { my %event = ( email=> Codestriker->filter_email( $current_history_row->{modified_by}), date => Codestriker->format_short_timestamp( $current_history_row->{modified_ts}), description=>'' ); # Look for changes in all of the fields. Several fields could have # changed at once. if ($current_history_row->{author} ne $last_history_row->{author}) { my %new_event = %event; $new_event{description} = "Author changed: $last_history_row->{author} to " . "$current_history_row->{author}."; push @event_list, \%new_event; } if ($current_history_row->{title} ne $last_history_row->{title}) { my %new_event = %event; $new_event{description} = "Title changed to: \"$current_history_row->{title}\"."; push @event_list, \%new_event; } if ($current_history_row->{description} ne $last_history_row->{description}) { my %new_event = %event; $new_event{description} = "Description changed to: " . "$current_history_row->{description}."; push @event_list, \%new_event; } if ($current_history_row->{state} ne $last_history_row->{state}) { my %new_event = %event; $new_event{description} = "Topic state changed to: " . $Codestriker::topic_states[$current_history_row->{state}]; push @event_list, \%new_event; } if ($current_history_row->{repository} ne $last_history_row->{repository}) { my %new_event = %event; $new_event{description} = "Repository changed to: $current_history_row->{repository}."; push @event_list, \%new_event; } if ($current_history_row->{project} ne $last_history_row->{project}) { my %new_event = %event; $new_event{description} = "Project changed to: $current_history_row->{project}."; push @event_list, \%new_event; } if ($current_history_row->{reviewers} ne $last_history_row->{reviewers}) { my %new_event = %event; # Figure out who was removed, and who was added to the list. my @reviewers = split /,/,$current_history_row->{reviewers}; my @l_reviewers = split /,/,$last_history_row->{reviewers}; my @new; my @removed; Codestriker::set_differences(\@reviewers, \@l_reviewers, \@new, \@removed); if (@new == 0) { $new_event{description} = "Reviewers removed: " . join(',',@removed);; } elsif (@removed == 0) { $new_event{description} = "Reviewers added: " . join(',',@new); } else { $new_event{description} = "Reviewers added: " . join(',',@new) . " and reviewers removed: " . join(',',@removed); } push @event_list, \%new_event; } if ($current_history_row->{cc} ne $last_history_row->{cc}) { my %new_event = %event; $new_event{description} = "CC changed to $current_history_row->{cc}."; push @event_list, \%new_event; } } $last_history_row = $current_history_row } return @event_list; } # Returns the topic metrics as a collection of references to # hashes. The hash that is returned has the same keys as the # metrics_schema hash, plus a value key. This private function # returns "built in" metrics derived from the topic history # table. sub _get_built_in_topic_metrics { my $self = shift; my @topic_metrics; my @topic_history = $self->_get_topic_history_rows(); my %state_times; my $last_history_row; # Figure out how long the topic has spent in each state. for ( my $topic_history_index = 0; $topic_history_index <= scalar(@topic_history); ++$topic_history_index) { my $current_history_row; if ($topic_history_index < scalar(@topic_history)) { $current_history_row = $topic_history[$topic_history_index]; } if (defined($last_history_row)) { my $start = Codestriker->convert_date_timestamp_time( $last_history_row->{modified_ts}); my $end = 0; if (defined($current_history_row)) { $end = Codestriker->convert_date_timestamp_time( $current_history_row->{modified_ts}); } else { $end = time(); } if (exists($state_times{$last_history_row->{state}})) { $state_times{$last_history_row->{state}} += $end - $start; } else { $state_times{$last_history_row->{state}} = $end - $start; } } $last_history_row = $current_history_row } foreach my $state ( sort keys %state_times) { my $statename = $Codestriker::topic_states[$state]; my $time_days = sprintf("%1.1f",$state_times{$state} / (60*60*24)); # This is the topic metric. my $metric = { name => 'Time In ' . $statename, description => 'Time in days the topic spent in the ' . $statename . ' state.', value => $time_days, # User can not change the metric, not configured. enabled => 0, in_database => 0, filter =>"count", builtin => 1, }; push @topic_metrics, $metric; } return @topic_metrics; } # Returns the user topic metrics as a collection of references to # hashs. The hash that is returned has the same keys as the # metrics_schema hash, plus a value key. This private function # returns "built in" metrics derived from the topic history # table and the topic view history table. sub _get_built_in_user_metrics { my ($self,$username) = @_; my @user_metrics; my $dbh = Codestriker::DB::DBI->get_connection(); # Setup the prepared statements. my $select_topic = $dbh->prepare_cached('SELECT creation_ts ' . 'FROM topicviewhistory ' . 'WHERE topicid = ? AND ' . 'LOWER(email) = LOWER(?) ' . 'ORDER BY creation_ts'); $select_topic->execute($self->{topicid}, $username); # my $total_time = $self->calculate_topic_view_time($select_topic); my $total_time = 0; Codestriker::DB::DBI->release_connection($dbh, 1); if ($total_time == 0) { $total_time = ""; } else { $total_time = sprintf("%1.0f",$total_time / (60)); } # This is the topic metric. my $metric = { name => 'Codestriker Time', description => 'Time in minutes spent in Codestriker looking at this topic.', value => $total_time, enabled => 0, in_database => 0, filter =>"minutes", builtin => 1, scope =>'participant', }; push @user_metrics, $metric; return @user_metrics; } # Given a DBI statement that returns a sorted collection of timestamps from # the topicviewhistory table, return the total time. sub calculate_topic_view_time { my ($self,$select_topic) = @_; # The amount of time you give to people after a click assuming no other # clicks are after it. my $time_increment = 4*60; my $total_time = 0; my $last_time = 0; while ( my @row_array = $select_topic->fetchrow_array) { my ($creation_ts) = @row_array; my $time = Codestriker->convert_date_timestamp_time($creation_ts); if ($last_time) { if ($time - $last_time > $time_increment) { $total_time += $time_increment } else { $total_time += $time - $last_time; } } $last_time = $time; } if ($last_time) { $total_time += $time_increment; } return $total_time; } # Returns the topichistory rows as an array of hashes. Each element in the # array is a row, each field in the table is a key. It will only fetch if # from the db once. sub _get_topic_history_rows { my ($self) = @_; if (defined( $self->{topichistoryrows})) { return @{$self->{topichistoryrows}}; } else { my $dbh = Codestriker::DB::DBI->get_connection(); my @history_list; # Setup the prepared statements. my $select_topic = $dbh->prepare_cached('SELECT topichistory.author, ' . 'topichistory.title, ' . 'topichistory.description, ' . 'topichistory.state, ' . 'topichistory.modified_ts, ' . 'topichistory.version, ' . 'topichistory.repository, ' . 'project.name, ' . 'topichistory.reviewers, ' . 'topichistory.cc, ' . 'topichistory.modified_by_user ' . 'FROM topichistory, project ' . 'WHERE topichistory.topicid = ? AND ' . 'topichistory.projectid = project.id ' . 'ORDER BY topichistory.version'); $select_topic->execute($self->{topicid}); while ( my @row_array = $select_topic->fetchrow_array) { my ($author,$title,$description,$state,$modified_ts, $version, $repository,$project,$reviewers,$cc, $modified_by) = @row_array; my %entry = ( author=>$author, title=>decode_utf8($title), description=>decode_utf8($description), state=>$state, modified_ts=>$modified_ts, version=>$version, repository=>$repository, project=>decode_utf8($project), reviewers=>lc($reviewers), cc=>lc($cc), modified_by=>lc($modified_by) ); push @history_list, \%entry; } Codestriker::DB::DBI->release_connection($dbh, 1); $self->{topichistoryrows} = \@history_list; return @history_list; } } # Returns an error message if a number is not a valid value for a given metric. sub _verify_metric { my ($self, $metric, $value) = @_; my $msg = ''; if ($metric->{enabled}) { my $input_ok = 0; if ($metric->{filter} eq "hours") { $input_ok = ($value =~ /(^[\d]+([\.:][\d]*)?$)|(^$)/); $msg = $metric->{name} . " must be a valid time in hours. " . HTML::Entities::encode($value) . " was " . "not saved.
" unless $input_ok; } elsif ($metric->{filter} eq "minutes") { $input_ok = ($value =~ /(^[\d]+)|(^$)/); $msg = $metric->{name} . " must be a valid time in minutes. " . HTML::Entities::encode($value) . " was " . "not saved.
" unless $input_ok; } elsif ($metric->{filter} eq "count") { $input_ok = ($value =~ /(^[\d]+$)|(^$)/); $msg = $metric->{name} . " must be a valid count. " . HTML::Entities::encode($value) . " was not " . "saved.
" unless $input_ok; } elsif ($metric->{filter} eq "percent") { $input_ok = ($value =~ /(^[\d]+(\.[\d]*)?$)|(^$)/); if ($input_ok && $value ne '') { $input_ok = 0 unless ($value >= 0.0 && $value <= 100.0); } $msg = $metric->{name} . " must be a valid percent, between 0 and 100. " . HTML::Entities::encode($value) . " was not saved.
" unless $input_ok; } else { # invalid config. $input_ok = 0; $msg = HTML::Entities::encode($metric->{name}) . " invalid filter type in configuration. Must " . "be hours, count, or percent.
"; } } return $msg; } # Stores all of the metrics to the database. sub store { my ($self) = @_; $self->_store_topic_metrics(); $self->_store_user_metrics(); } # Stores the topic metrics to the database. sub _store_user_metrics { my ($self) = @_; foreach my $user (keys %{$self->{usermetrics}}) { $self->get_user_metrics($user); } # Obtain a database connection. my $dbh = Codestriker::DB::DBI->get_connection(); # flush out the user metrics from the topic, my $delete_alluser_metric = $dbh->prepare_cached('DELETE FROM topicusermetric ' . 'WHERE topicid = ?'); $delete_alluser_metric->execute($self->{topicid}); my $insert_user_metric = $dbh->prepare_cached('INSERT INTO topicusermetric (topicid, email, metric_name, value) ' . 'VALUES (?, LOWER(?), ?, ? )'); foreach my $user (keys %{$self->{usermetrics}}) { my @metrics = $self->get_user_metrics($user); foreach my $metric (@metrics) { next if ($metric->{builtin}); if ($metric->{value} ne '') { $insert_user_metric->execute($self->{topicid}, $user, $metric->{name}, $metric->{value}); } } } # Close the connection, and check for any database errors. Codestriker::DB::DBI->release_connection($dbh, 1); } # Stores the topic metrics to the database. sub _store_topic_metrics { my ($self) = @_; # Obtain a database connection. my $dbh = Codestriker::DB::DBI->get_connection(); # Store the topic metrics first. my @topic_metrics = $self->get_topic_metrics(); my $insert_topic_metric = $dbh->prepare_cached('INSERT INTO topicmetric (topicid, metric_name, value) ' . 'VALUES (?, ?, ? )'); my $update_topic_metric = $dbh->prepare_cached('UPDATE topicmetric SET value = ? ' . 'WHERE topicid = ? and metric_name = ?'); my $delete_topic_metric = $dbh->prepare_cached('DELETE FROM topicmetric ' . 'WHERE topicid = ? and metric_name = ?'); foreach my $metric (@topic_metrics) { # don't save built in metrics next if ($metric->{builtin}); if ($metric->{in_database}) { if ($metric->{value} ne '') { $update_topic_metric->execute($metric->{value}, $self->{topicid}, $metric->{name}); } else { # Delete the row. $delete_topic_metric->execute($self->{topicid}, $metric->{name}); $metric->{in_database} = 0; } } else { # New metric that is not in the datbase. if ($metric->{value} ne '') { $insert_topic_metric->execute($self->{topicid}, $metric->{name}, $metric->{value}); $metric->{in_database} = 1; } } $metric->{in_database} = 1; } # Close the connection, and check for any database errors. Codestriker::DB::DBI->release_connection($dbh, 1); } 1;