Bug 576670: Optimize Search.pm's "init" method for being called many times
in a loop r=glob, a=mkanat git-svn-id: svn://10.0.0.236/trunk@260692 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
parent
363d9127c6
commit
0b5e02b469
@ -1 +1 @@
|
||||
7313
|
||||
7314
|
||||
@ -523,6 +523,45 @@ sub switch_to_main_db {
|
||||
return $class->dbh_main;
|
||||
}
|
||||
|
||||
sub fields {
|
||||
my ($class, $criteria) = @_;
|
||||
$criteria ||= {};
|
||||
my $cache = $class->request_cache;
|
||||
|
||||
# We create an advanced cache for fields by type, so that we
|
||||
# can avoid going back to the database for every fields() call.
|
||||
# (And most of our fields() calls are for getting fields by type.)
|
||||
#
|
||||
# We also cache fields by name, because calling $field->name a few
|
||||
# million times can be slow in calling code, but if we just do it
|
||||
# once here, that makes things a lot faster for callers.
|
||||
if (!defined $cache->{fields}) {
|
||||
my @all_fields = Bugzilla::Field->get_all;
|
||||
my (%by_name, %by_type);
|
||||
foreach my $field (@all_fields) {
|
||||
my $name = $field->name;
|
||||
$by_type{$field->type}->{$name} = $field;
|
||||
$by_name{$name} = $field;
|
||||
}
|
||||
$cache->{fields} = { by_type => \%by_type, by_name => \%by_name };
|
||||
}
|
||||
|
||||
my $fields = $cache->{fields};
|
||||
my %requested;
|
||||
if (my $types = $criteria->{type}) {
|
||||
$types = ref($types) ? $types : [$types];
|
||||
%requested = map { %{ $fields->{by_type}->{$_} || {} } } @$types;
|
||||
}
|
||||
else {
|
||||
%requested = %{ $fields->{by_name} };
|
||||
}
|
||||
|
||||
my $do_by_name = $criteria->{by_name};
|
||||
|
||||
return $do_by_name ? \%requested : [values %requested];
|
||||
}
|
||||
|
||||
# DEPRECATED. Use fields() instead.
|
||||
sub get_fields {
|
||||
my $class = shift;
|
||||
my $criteria = shift;
|
||||
@ -768,6 +807,30 @@ Essentially, causes calls to C<Bugzilla-E<gt>user> to return C<undef>. This has
|
||||
effect of logging out a user for the current request only; cookies and
|
||||
database sessions are left intact.
|
||||
|
||||
=item C<fields>
|
||||
|
||||
This is the standard way to get arrays or hashes of L<Bugzilla::Field>
|
||||
objects when you need them. It takes the following named arguments
|
||||
in a hashref:
|
||||
|
||||
=over
|
||||
|
||||
=item C<by_name>
|
||||
|
||||
If false (or not specified), this method will return an arrayref of
|
||||
the requested fields. The order of the returned fields is random.
|
||||
|
||||
If true, this method will return a hashref of fields, where the keys
|
||||
are field names and the valules are L<Bugzilla::Field> objects.
|
||||
|
||||
=item C<type>
|
||||
|
||||
Either a single C<FIELD_TYPE_*> constant or an arrayref of them. If specified,
|
||||
the returned fields will be limited to the types in the list. If you don't
|
||||
specify this argument, all fields will be returned.
|
||||
|
||||
=back
|
||||
|
||||
=item C<error_mode>
|
||||
|
||||
Call either C<Bugzilla->error_mode(Bugzilla::Constants::ERROR_MODE_DIE)>
|
||||
|
||||
@ -35,6 +35,7 @@ use base qw(Exporter);
|
||||
|
||||
# For bz_locations
|
||||
use File::Basename;
|
||||
use Memoize;
|
||||
|
||||
@Bugzilla::Constants::EXPORT = qw(
|
||||
BUGZILLA_VERSION
|
||||
@ -404,11 +405,11 @@ use constant EMPTY_DATETIME_REGEX => qr/^[0\-:\sA-Za-z]+$/;
|
||||
|
||||
# See the POD for Bugzilla::Field/is_abnormal to see why these are listed
|
||||
# here.
|
||||
use constant ABNORMAL_SELECTS => qw(
|
||||
classification
|
||||
product
|
||||
component
|
||||
);
|
||||
use constant ABNORMAL_SELECTS => {
|
||||
classification => 1,
|
||||
component => 1,
|
||||
product => 1,
|
||||
};
|
||||
|
||||
# The fields from fielddefs that are blocked from non-timetracking users.
|
||||
# work_time is sometimes called actual_time.
|
||||
@ -619,4 +620,8 @@ sub bz_locations {
|
||||
};
|
||||
}
|
||||
|
||||
# This makes us not re-compute all the bz_locations data every time it's
|
||||
# called.
|
||||
BEGIN { memoize('bz_locations') };
|
||||
|
||||
1;
|
||||
|
||||
@ -53,12 +53,6 @@ sub _throw_error {
|
||||
$vars ||= {};
|
||||
|
||||
$vars->{error} = $error;
|
||||
# Don't show function arguments, in case they contain confidential data.
|
||||
local $Carp::MaxArgNums = -1;
|
||||
# Don't show the error as coming from Bugzilla::Error, show it as coming
|
||||
# from the caller.
|
||||
local $Carp::CarpInternal{'Bugzilla::Error'} = 1;
|
||||
$vars->{traceback} = Carp::longmess();
|
||||
|
||||
# Make sure any transaction is rolled back (if supported).
|
||||
# If we are within an eval(), do not roll back transactions as we are
|
||||
@ -159,6 +153,16 @@ sub ThrowUserError {
|
||||
}
|
||||
|
||||
sub ThrowCodeError {
|
||||
my (undef, $vars) = @_;
|
||||
|
||||
# Don't show function arguments, in case they contain
|
||||
# confidential data.
|
||||
local $Carp::MaxArgNums = -1;
|
||||
# Don't show the error as coming from Bugzilla::Error, show it
|
||||
# as coming from the caller.
|
||||
local $Carp::CarpInternal{'Bugzilla::Error'} = 1;
|
||||
$vars->{traceback} = Carp::longmess();
|
||||
|
||||
_throw_error("global/code-error.html.tmpl", @_);
|
||||
}
|
||||
|
||||
|
||||
@ -544,7 +544,7 @@ This method returns C<1> if the field is "abnormal", C<0> otherwise.
|
||||
|
||||
sub is_abnormal {
|
||||
my $self = shift;
|
||||
return grep($_ eq $self->name, ABNORMAL_SELECTS) ? 1 : 0;
|
||||
return ABNORMAL_SELECTS->{$self->name} ? 1 : 0;
|
||||
}
|
||||
|
||||
sub legal_values {
|
||||
|
||||
@ -461,13 +461,11 @@ sub init {
|
||||
my %special_order = %{SPECIAL_ORDER()};
|
||||
my %special_order_join = %{SPECIAL_ORDER_JOIN()};
|
||||
|
||||
my @select_fields =
|
||||
Bugzilla->get_fields({ type => FIELD_TYPE_SINGLE_SELECT });
|
||||
my $select_fields = Bugzilla->fields({ type => FIELD_TYPE_SINGLE_SELECT });
|
||||
|
||||
my @multi_select_fields = Bugzilla->get_fields({
|
||||
type => [FIELD_TYPE_MULTI_SELECT, FIELD_TYPE_BUG_URLS],
|
||||
obsolete => 0 });
|
||||
foreach my $field (@select_fields) {
|
||||
my $multi_select_fields = Bugzilla->fields({
|
||||
type => [FIELD_TYPE_MULTI_SELECT, FIELD_TYPE_BUG_URLS]});
|
||||
foreach my $field (@$select_fields) {
|
||||
next if $field->is_abnormal;
|
||||
my $name = $field->name;
|
||||
$special_order{$name} = [ "$name.sortkey", "$name.value" ],
|
||||
@ -522,7 +520,7 @@ sub init {
|
||||
push(@supptables, "LEFT JOIN longdescs AS ldtime " .
|
||||
"ON ldtime.bug_id = bugs.bug_id");
|
||||
}
|
||||
foreach my $field (@multi_select_fields) {
|
||||
foreach my $field (@$multi_select_fields) {
|
||||
my $field_name = $field->name;
|
||||
next if !grep($_ eq $field_name, @fields);
|
||||
push(@supptables, "LEFT JOIN bug_$field_name AS map_bug_$field_name"
|
||||
@ -594,15 +592,18 @@ sub init {
|
||||
|
||||
# All fields that don't have a . in their name should be specifyable
|
||||
# in the URL directly.
|
||||
my @legal_fields = grep { $_->name !~ /\./ } Bugzilla->get_fields;
|
||||
my $legal_fields = Bugzilla->fields({ by_name => 1 });
|
||||
if (!$user->is_timetracker) {
|
||||
foreach my $field (TIMETRACKING_FIELDS) {
|
||||
@legal_fields = grep { $_->name ne $field } @legal_fields;
|
||||
foreach my $name (TIMETRACKING_FIELDS) {
|
||||
delete $legal_fields->{$name};
|
||||
}
|
||||
}
|
||||
foreach my $name (keys %$legal_fields) {
|
||||
delete $legal_fields->{$name} if $name =~ /\./;
|
||||
}
|
||||
|
||||
foreach my $field ($params->param()) {
|
||||
if (grep { $_->name eq $field } @legal_fields) {
|
||||
if ($legal_fields->{$field}) {
|
||||
my $type = $params->param("${field}_type");
|
||||
if (!$type) {
|
||||
if ($field eq 'keywords') {
|
||||
@ -940,12 +941,11 @@ sub init {
|
||||
# $suppstring = String which is pasted into query containing all table names
|
||||
|
||||
# get a list of field names to verify the user-submitted chart fields against
|
||||
my %chartfields = @{$dbh->selectcol_arrayref(
|
||||
q{SELECT name, id FROM fielddefs}, { Columns=>[1,2] })};
|
||||
my $chart_fields = Bugzilla->fields({ by_name => 1 });
|
||||
|
||||
if (!$user->is_timetracker) {
|
||||
foreach my $tt_field (TIMETRACKING_FIELDS) {
|
||||
delete $chartfields{$tt_field};
|
||||
delete $chart_fields->{$tt_field};
|
||||
}
|
||||
}
|
||||
|
||||
@ -976,12 +976,12 @@ sub init {
|
||||
|
||||
# chart -1 is generated by other code above, not from the user-
|
||||
# submitted form, so we'll blindly accept any values in chart -1
|
||||
if (!$chartfields{$field} and $chart != -1) {
|
||||
if (!$chart_fields->{$field} and $chart != -1) {
|
||||
ThrowCodeError("invalid_field_name", { field => $field });
|
||||
}
|
||||
|
||||
# This is either from the internal chart (in which case we
|
||||
# already know about it), or it was in %chartfields, so it is
|
||||
# already know about it), or it was in $chart_fields, so it is
|
||||
# a valid field name, which means that it's ok.
|
||||
trick_taint($field);
|
||||
my $quoted = $dbh->quote($value);
|
||||
@ -996,13 +996,13 @@ sub init {
|
||||
operator => $operator,
|
||||
value => $value,
|
||||
quoted => $quoted,
|
||||
multi_fields => \@multi_select_fields,
|
||||
multi_fields => $multi_select_fields,
|
||||
joins => \@supptables,
|
||||
where => \@wherepart,
|
||||
having => \@having,
|
||||
group_by => \@groupby,
|
||||
fields => \@fields,
|
||||
chart_fields => \%chartfields,
|
||||
chart_fields => $chart_fields,
|
||||
);
|
||||
# This should add a "term" selement to %search_args.
|
||||
$self->do_search_function(\%search_args);
|
||||
@ -2358,13 +2358,16 @@ sub _nowords {
|
||||
|
||||
sub _changedbefore_changedafter {
|
||||
my ($self, $args) = @_;
|
||||
my ($chart_id, $joins, $field, $operator, $value) =
|
||||
@$args{qw(chart_id joins field operator value)};
|
||||
my ($chart_id, $joins, $field, $operator, $value, $chart_fields) =
|
||||
@$args{qw(chart_id joins field operator value chart_fields)};
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $sql_operator = ($operator =~ /before/) ? '<' : '>';
|
||||
my $table = "act_$chart_id";
|
||||
my $field_id = get_field_id($field);
|
||||
my $field_object = $chart_fields->{$field}
|
||||
|| ThrowCodeError("invalid_field_name", { field => $field });
|
||||
my $field_id = $field_object->id;
|
||||
|
||||
my $sql_date = $dbh->quote(SqlifyDate($value));
|
||||
push(@$joins,
|
||||
"LEFT JOIN bugs_activity AS $table"
|
||||
@ -2376,12 +2379,14 @@ sub _changedbefore_changedafter {
|
||||
|
||||
sub _changedfrom_changedto {
|
||||
my ($self, $args) = @_;
|
||||
my ($chart_id, $joins, $field, $operator, $quoted) =
|
||||
@$args{qw(chart_id joins field operator quoted)};
|
||||
my ($chart_id, $joins, $field, $operator, $quoted, $chart_fields) =
|
||||
@$args{qw(chart_id joins field operator quoted chart_fields)};
|
||||
|
||||
my $column = ($operator =~ /from/) ? 'removed' : 'added';
|
||||
my $table = "act_$chart_id";
|
||||
my $field_id = get_field_id($field);
|
||||
my $field_object = $chart_fields->{$field}
|
||||
|| ThrowCodeError("invalid_field_name", { field => $field });
|
||||
my $field_id = $field_object->id;
|
||||
push(@$joins,
|
||||
"LEFT JOIN bugs_activity AS $table"
|
||||
. " ON $table.bug_id = bugs.bug_id"
|
||||
@ -2392,11 +2397,13 @@ sub _changedfrom_changedto {
|
||||
|
||||
sub _changedby {
|
||||
my ($self, $args) = @_;
|
||||
my ($chart_id, $joins, $field, $operator, $value) =
|
||||
@$args{qw(chart_id joins field operator value)};
|
||||
my ($chart_id, $joins, $field, $operator, $value, $chart_fields) =
|
||||
@$args{qw(chart_id joins field operator value chart_fields)};
|
||||
|
||||
my $table = "act_$chart_id";
|
||||
my $field_id = get_field_id($field);
|
||||
my $field_object = $chart_fields->{$field}
|
||||
|| ThrowCodeError("invalid_field_name", { field => $field });
|
||||
my $field_id = $field_object->id;
|
||||
my $user_id = login_to_id($value, THROW_ERROR);
|
||||
push(@$joins,
|
||||
"LEFT JOIN bugs_activity AS $table"
|
||||
|
||||
@ -765,8 +765,8 @@ sub create {
|
||||
# A way for all templates to get at Field data, cached.
|
||||
'bug_fields' => sub {
|
||||
my $cache = Bugzilla->request_cache;
|
||||
$cache->{template_bug_fields} ||=
|
||||
{ map { $_->name => $_ } Bugzilla->get_fields() };
|
||||
$cache->{template_bug_fields} ||=
|
||||
Bugzilla->fields({ by_name => 1 });
|
||||
return $cache->{template_bug_fields};
|
||||
},
|
||||
|
||||
|
||||
@ -603,18 +603,28 @@ sub groups {
|
||||
return $self->{groups};
|
||||
}
|
||||
|
||||
# It turns out that calling ->id on objects a few hundred thousand
|
||||
# times is pretty slow. (It showed up as a significant time contributor
|
||||
# when profiling xt/search.t.) So we cache the group ids separately from
|
||||
# groups for functions that need the group ids.
|
||||
sub _group_ids {
|
||||
my ($self) = @_;
|
||||
$self->{group_ids} ||= [map { $_->id } @{ $self->groups }];
|
||||
return $self->{group_ids};
|
||||
}
|
||||
|
||||
sub groups_as_string {
|
||||
my $self = shift;
|
||||
my @ids = map { $_->id } @{ $self->groups };
|
||||
return scalar(@ids) ? join(',', @ids) : '-1';
|
||||
my $ids = $self->_group_ids;
|
||||
return scalar(@$ids) ? join(',', @$ids) : '-1';
|
||||
}
|
||||
|
||||
sub groups_in_sql {
|
||||
my ($self, $field) = @_;
|
||||
$field ||= 'group_id';
|
||||
my @ids = map { $_->id } @{ $self->groups };
|
||||
@ids = (-1) if !scalar @ids;
|
||||
return Bugzilla->dbh->sql_in($field, \@ids);
|
||||
my $ids = $self->_group_ids;
|
||||
$ids = [-1] if !scalar @$ids;
|
||||
return Bugzilla->dbh->sql_in($field, $ids);
|
||||
}
|
||||
|
||||
sub bless_groups {
|
||||
@ -1096,7 +1106,7 @@ sub queryshare_groups {
|
||||
}
|
||||
}
|
||||
else {
|
||||
@queryshare_groups = map { $_->id } @{ $self->groups };
|
||||
@queryshare_groups = @{ $self->_group_ids };
|
||||
}
|
||||
}
|
||||
|
||||
@ -1848,15 +1858,31 @@ sub is_available_username {
|
||||
return 1;
|
||||
}
|
||||
|
||||
# This is used in a few performance-critical areas where we don't want to
|
||||
# do check() and pull all the user data from the database.
|
||||
sub login_to_id {
|
||||
my ($login, $throw_error) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
# No need to validate $login -- it will be used by the following SELECT
|
||||
# statement only, so it's safe to simply trick_taint.
|
||||
trick_taint($login);
|
||||
my $user_id = $dbh->selectrow_array("SELECT userid FROM profiles WHERE " .
|
||||
$dbh->sql_istrcmp('login_name', '?'),
|
||||
undef, $login);
|
||||
my $cache = Bugzilla->request_cache->{user_login_to_id} ||= {};
|
||||
|
||||
# We cache lookups because this function showed up as taking up a
|
||||
# significant amount of time in profiles of xt/search.t. However,
|
||||
# for users that don't exist, we re-do the check every time, because
|
||||
# otherwise we break is_available_username.
|
||||
my $user_id;
|
||||
if (defined $cache->{$login}) {
|
||||
$user_id = $cache->{$login};
|
||||
}
|
||||
else {
|
||||
# No need to validate $login -- it will be used by the following SELECT
|
||||
# statement only, so it's safe to simply trick_taint.
|
||||
trick_taint($login);
|
||||
$user_id = $dbh->selectrow_array(
|
||||
"SELECT userid FROM profiles
|
||||
WHERE " . $dbh->sql_istrcmp('login_name', '?'), undef, $login);
|
||||
$cache->{$login} = $user_id;
|
||||
}
|
||||
|
||||
if ($user_id) {
|
||||
return $user_id;
|
||||
} elsif ($throw_error) {
|
||||
|
||||
@ -147,7 +147,7 @@ sub all_fields {
|
||||
my $self = shift;
|
||||
if (not $self->{all_fields}) {
|
||||
$self->_create_custom_fields();
|
||||
my @fields = Bugzilla->get_fields;
|
||||
my @fields = @{ Bugzilla->fields };
|
||||
@fields = sort { $a->name cmp $b->name } @fields;
|
||||
$self->{all_fields} = \@fields;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user