RSS

trunk : 5532

mkanat%bugzilla.org
2008-03-27 05:08:07
Revision ID: cvs-1:mkanatbugzilla.org-20080327100807-963wkc3xk0qcjiqb
Bug 372795: Implement Bugzilla::Product::preload() to speed up query.cgi when there are many products Patch By Max Kanat-Alexander <mkanat@bugzilla.org> r=LpSolit, a=mkanat

collapse all collapse all

added added

removed removed

129
    my $invocant = shift;
129
    my $invocant = shift;
130
    my $class = ref($invocant) || $invocant;
130
    my $class = ref($invocant) || $invocant;
131
    my ($id_list) = @_;
131
    my ($id_list) = @_;
132
    my $dbh = Bugzilla->dbh;
 
 
133
    my $columns = join(',', $class->DB_COLUMNS);
 
 
134
    my $table   = $class->DB_TABLE;
 
 
135
    my $order   = $class->LIST_ORDER;
 
 
136
    my $id_field = $class->ID_FIELD;
132
    my $id_field = $class->ID_FIELD;
137
 
133
 
138
    my $objects;
134
    my @detainted_ids;
139
    if (@$id_list) {
135
    foreach my $id (@$id_list) {
140
        my @detainted_ids;
136
        detaint_natural($id) ||
141
        foreach my $id (@$id_list) {
137
            ThrowCodeError('param_must_be_numeric',
142
            detaint_natural($id) ||
138
                          {function => $class . '::new_from_list'});
143
                ThrowCodeError('param_must_be_numeric',
139
        push(@detainted_ids, $id);
144
                              {function => $class . '::new_from_list'});
140
    }
145
            push(@detainted_ids, $id);
141
    # We don't do $invocant->match because some classes have
146
        }
142
    # their own implementation of match which is not compatible
147
        $objects = $dbh->selectall_arrayref(
143
    # with this one. However, match() still needs to have the right $invocant
148
            "SELECT $columns FROM $table WHERE " 
144
    # in order to do $class->DB_TABLE and so on.
149
            . $dbh->sql_in($id_field, \@detainted_ids) 
145
    return match($invocant, { $id_field => \@detainted_ids });
150
            . "ORDER BY $order", {Slice=>{}});
 
 
151
    } else {
 
 
152
        return [];
 
 
153
    }
 
 
154
 
 
 
155
    foreach my $object (@$objects) {
 
 
156
        bless($object, $class);
 
 
157
    }
 
 
158
    return $objects;
 
 
159
}
146
}
160
 
147
 
161
# Note: Future extensions to this could be:
148
# Note: Future extensions to this could be:
162
#  * Accept arrays for an IN clause
 
 
163
#  * Add a MATCH_JOIN constant so that we can join against
149
#  * Add a MATCH_JOIN constant so that we can join against
164
#    certain other tables for the WHERE criteria.
150
#    certain other tables for the WHERE criteria.
165
sub match {
151
sub match {
166
    my ($invocant, $criteria) = @_;
152
    my ($invocant, $criteria) = @_;
167
    my $class = ref($invocant) || $invocant;
153
    my $class = ref($invocant) || $invocant;
168
    my $dbh   = Bugzilla->dbh;
154
    my $dbh   = Bugzilla->dbh;
169
    my $id    = $class->ID_FIELD;
 
 
170
    my $table = $class->DB_TABLE;
 
 
171
 
155
 
172
    return [$class->get_all] if !$criteria;
156
    return [$class->get_all] if !$criteria;
173
 
157
 
174
    my (@terms, @values);
158
    my (@terms, @values);
175
    foreach my $field (keys %$criteria) {
159
    foreach my $field (keys %$criteria) {
176
        my $value = $criteria->{$field};
160
        my $value = $criteria->{$field};
177
        if ($value eq NOT_NULL) {
161
        if (ref $value eq 'ARRAY') {
 
 
162
            # IN () is invalid SQL, and if we have an empty list
 
 
163
            # to match against, we're just returning an empty
 
 
164
            # array anyhow.
 
 
165
            return [] if !scalar @$value;
 
 
166
 
 
 
167
            my @qmarks = ("?") x @$value;
 
 
168
            push(@terms, $dbh->sql_in($field, \@qmarks));
 
 
169
            push(@values, @$value);
 
 
170
        }
 
 
171
        elsif ($value eq NOT_NULL) {
178
            push(@terms, "$field IS NOT NULL");
172
            push(@terms, "$field IS NOT NULL");
179
        }
173
        }
180
        elsif ($value eq IS_NULL) {
174
        elsif ($value eq IS_NULL) {
187
    }
181
    }
188
 
182
 
189
    my $where = join(' AND ', @terms);
183
    my $where = join(' AND ', @terms);
190
    my $ids   = $dbh->selectcol_arrayref(
184
    return $class->_do_list_select($where, \@values);
191
        "SELECT $id FROM $table WHERE $where", undef, @values)
185
}
192
        || [];
186
 
193
 
187
sub _do_list_select {
194
    return $class->new_from_list($ids);
188
    my ($class, $where, $values) = @_;
 
 
189
    my $table = $class->DB_TABLE;
 
 
190
    my $cols  = join(',', $class->DB_COLUMNS);
 
 
191
    my $order = $class->LIST_ORDER;
 
 
192
 
 
 
193
    my $sql = "SELECT $cols FROM $table";
 
 
194
    if (defined $where) {
 
 
195
        $sql .= " WHERE $where ";
 
 
196
    }
 
 
197
    $sql .= " ORDER BY $order";
 
 
198
 
 
 
199
    my $dbh = Bugzilla->dbh;
 
 
200
    my $objects = $dbh->selectall_arrayref($sql, {Slice=>{}}, @$values);
 
 
201
    bless ($_, $class) foreach @$objects;
 
 
202
    return $objects
195
}
203
}
196
 
204
 
197
###############################
205
###############################
349
 
357
 
350
sub get_all {
358
sub get_all {
351
    my $class = shift;
359
    my $class = shift;
352
    my $dbh = Bugzilla->dbh;
360
    return @{$class->_do_list_select()};
353
    my $table = $class->DB_TABLE;
 
 
354
    my $order = $class->LIST_ORDER;
 
 
355
    my $id_field = $class->ID_FIELD;
 
 
356
 
 
 
357
    my $ids = $dbh->selectcol_arrayref(qq{
 
 
358
        SELECT $id_field FROM $table ORDER BY $order});
 
 
359
 
 
 
360
    my $objects = $class->new_from_list($ids);
 
 
361
    return @$objects;
 
 
362
}
361
}
363
 
362
 
364
###############################
363
###############################
51
   products.defaultmilestone
51
   products.defaultmilestone
52
);
52
);
53
 
53
 
 
 
54
###############################
 
 
55
####     Constructors     #####
 
 
56
###############################
 
 
57
 
 
 
58
# This is considerably faster than calling new_from_list three times
 
 
59
# for each product in the list, particularly with hundreds or thousands
 
 
60
# of products.
 
 
61
sub preload {
 
 
62
    my ($products) = @_;
 
 
63
    my %prods = map { $_->id => $_ } @$products;
 
 
64
    my @prod_ids = keys %prods;
 
 
65
    return unless @prod_ids;
 
 
66
 
 
 
67
    my $dbh = Bugzilla->dbh;
 
 
68
    foreach my $field (qw(component version milestone)) {
 
 
69
        my $classname = "Bugzilla::" . ucfirst($field);
 
 
70
        my $objects = $classname->match({ product_id => \@prod_ids });
 
 
71
 
 
 
72
        # Now populate the products with this set of objects.
 
 
73
        foreach my $obj (@$objects) {
 
 
74
            my $product_id = $obj->product_id;
 
 
75
            $prods{$product_id}->{"${field}s"} ||= [];
 
 
76
            push(@{$prods{$product_id}->{"${field}s"}}, $obj);
 
 
77
        }
 
 
78
    }
 
 
79
}
54
 
80
 
55
###############################
81
###############################
56
####       Methods         ####
82
####       Methods         ####
300
 
326
 
301
=over
327
=over
302
 
328
 
303
=item C<components()>
329
=item C<components>
304
 
330
 
305
 Description: Returns an array of component objects belonging to
331
 Description: Returns an array of component objects belonging to
306
              the product.
332
              the product.
319
 Returns:     A hash with group id as key and hash containing 
345
 Returns:     A hash with group id as key and hash containing 
320
              a Bugzilla::Group object and the properties of group
346
              a Bugzilla::Group object and the properties of group
321
              relative to the product.
347
              relative to the product.
322
              
348
 
323
=item C<groups_mandatory_for>
349
=item C<groups_mandatory_for>
324
 
350
 
325
=over
351
=over
356
 
382
 
357
=back
383
=back
358
 
384
 
359
=item C<versions()>
385
=item C<versions>
360
 
386
 
361
 Description: Returns all valid versions for that product.
387
 Description: Returns all valid versions for that product.
362
 
388
 
364
 
390
 
365
 Returns:     An array of Bugzilla::Version objects.
391
 Returns:     An array of Bugzilla::Version objects.
366
 
392
 
367
=item C<milestones()>
393
=item C<milestones>
368
 
394
 
369
 Description: Returns all valid milestones for that product.
395
 Description: Returns all valid milestones for that product.
370
 
396
 
415
 
441
 
416
=over
442
=over
417
 
443
 
 
 
444
=item C<preload>
 
 
445
 
 
 
446
When passed an arrayref of C<Bugzilla::Product> objects, preloads their
 
 
447
L</milestones>, L</components>, and L</versions>, which is much faster
 
 
448
than calling those accessors on every item in the array individually.
 
 
449
 
 
 
450
This function is not exported, so must be called like 
 
 
451
C<Bugzilla::Product::preload($products)>.
 
 
452
 
418
=item C<check_product($product_name)>
453
=item C<check_product($product_name)>
419
 
454
 
420
 Description: Checks if the product name was passed in and if is a valid
455
 Description: Checks if the product name was passed in and if is a valid
189
# don't have access to. Remove them from the list.
189
# don't have access to. Remove them from the list.
190
my @selectable_products = sort {lc($a->name) cmp lc($b->name)} 
190
my @selectable_products = sort {lc($a->name) cmp lc($b->name)} 
191
                               @{$user->get_selectable_products};
191
                               @{$user->get_selectable_products};
 
 
192
Bugzilla::Product::preload(\@selectable_products);
192
 
193
 
193
# Create the component, version and milestone lists.
194
# Create the component, version and milestone lists.
194
my %components;
195
my %components;

Loggerhead runs on Bazaar branches