]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 602456: Make Search.pm not quote numeric input for numeric fields
authorMax Kanat-Alexander <mkanat@bugzilla.org>
Thu, 28 Oct 2010 22:38:45 +0000 (15:38 -0700)
committerMax Kanat-Alexander <mkanat@bugzilla.org>
Thu, 28 Oct 2010 22:38:45 +0000 (15:38 -0700)
when generating SQL.
r=glob, a=mkanat

Bugzilla/DB/Schema.pm
Bugzilla/Field.pm
Bugzilla/Install/DB.pm
Bugzilla/Search.pm

index cfa1608f94844826f527ef100e319d6f73577bd3..56e763d2004ad3f837ad06b5189e52e4a884a697 100644 (file)
@@ -683,6 +683,8 @@ use constant ABSTRACT_SCHEMA => {
             reverse_desc => {TYPE => 'TINYTEXT'},
             is_mandatory => {TYPE => 'BOOLEAN', NOTNULL => 1,
                              DEFAULT => 'FALSE'},
+            is_numeric    => {TYPE => 'BOOLEAN', NOTNULL => 1,
+                             DEFAULT => 'FALSE'},
         ],
         INDEXES => [
             fielddefs_name_idx    => {FIELDS => ['name'],
index 1229d31cb47ce021f5ffec8a824aeee852bde509..052717f004ebbe9229a5798b37585d8a4052f337 100644 (file)
@@ -103,6 +103,7 @@ use constant DB_COLUMNS => qw(
     value_field_id
     reverse_desc
     is_mandatory
+    is_numeric
 );
 
 use constant VALIDATORS => {
@@ -120,9 +121,11 @@ use constant VALIDATORS => {
     visibility_field_id => \&_check_visibility_field_id,
     visibility_values => \&_check_visibility_values,
     is_mandatory => \&Bugzilla::Object::check_boolean,
+    is_numeric   => \&_check_is_numeric,
 };
 
 use constant VALIDATOR_DEPENDENCIES => {
+    is_numeric => ['type'],
     name => ['custom'],
     type => ['custom'],
     reverse_desc => ['type'],
@@ -141,6 +144,7 @@ use constant UPDATE_COLUMNS => qw(
     value_field_id
     reverse_desc
     is_mandatory
+    is_numeric
     type
 );
 
@@ -161,7 +165,7 @@ use constant SQL_DEFINITIONS => {
 # the fielddefs table.
 use constant DEFAULT_FIELDS => (
     {name => 'bug_id',       desc => 'Bug #',      in_new_bugmail => 1,
-     buglist => 1},
+     buglist => 1, is_numeric => 1},
     {name => 'short_desc',   desc => 'Summary',    in_new_bugmail => 1,
      is_mandatory => 1, buglist => 1},
     {name => 'classification', desc => 'Classification', in_new_bugmail => 1,
@@ -199,15 +203,20 @@ use constant DEFAULT_FIELDS => (
     {name => 'qa_contact',   desc => 'QAContact',  in_new_bugmail => 1,
      buglist => 1},
     {name => 'cc',           desc => 'CC',         in_new_bugmail => 1},
-    {name => 'dependson',    desc => 'Depends on', in_new_bugmail => 1},
-    {name => 'blocked',      desc => 'Blocks',     in_new_bugmail => 1},
+    {name => 'dependson',    desc => 'Depends on', in_new_bugmail => 1,
+     is_numeric => 1},
+    {name => 'blocked',      desc => 'Blocks',     in_new_bugmail => 1,
+     is_numeric => 1},
 
     {name => 'attachments.description', desc => 'Attachment description'},
     {name => 'attachments.filename',    desc => 'Attachment filename'},
     {name => 'attachments.mimetype',    desc => 'Attachment mime type'},
-    {name => 'attachments.ispatch',     desc => 'Attachment is patch'},
-    {name => 'attachments.isobsolete',  desc => 'Attachment is obsolete'},
-    {name => 'attachments.isprivate',   desc => 'Attachment is private'},
+    {name => 'attachments.ispatch',     desc => 'Attachment is patch',
+     is_numeric => 1},
+    {name => 'attachments.isobsolete',  desc => 'Attachment is obsolete',
+     is_numeric => 1},
+    {name => 'attachments.isprivate',   desc => 'Attachment is private',
+     is_numeric => 1},
     {name => 'attachments.submitter',   desc => 'Attachment creator'},
 
     {name => 'target_milestone',      desc => 'Target Milestone',
@@ -217,26 +226,32 @@ use constant DEFAULT_FIELDS => (
     {name => 'delta_ts',              desc => 'Last changed date',
      buglist => 1},
     {name => 'longdesc',              desc => 'Comment'},
-    {name => 'longdescs.isprivate',   desc => 'Comment is private'},
+    {name => 'longdescs.isprivate',   desc => 'Comment is private',
+     is_numeric => 1},
     {name => 'longdescs.count',       desc => 'Number of Comments',
-     buglist => 1},
+     buglist => 1, is_numeric => 1},
     {name => 'alias',                 desc => 'Alias', buglist => 1},
-    {name => 'everconfirmed',         desc => 'Ever Confirmed'},
-    {name => 'reporter_accessible',   desc => 'Reporter Accessible'},
-    {name => 'cclist_accessible',     desc => 'CC Accessible'},
+    {name => 'everconfirmed',         desc => 'Ever Confirmed',
+     is_numeric => 1},
+    {name => 'reporter_accessible',   desc => 'Reporter Accessible',
+     is_numeric => 1},
+    {name => 'cclist_accessible',     desc => 'CC Accessible',
+     is_numeric => 1},
     {name => 'bug_group',             desc => 'Group', in_new_bugmail => 1},
     {name => 'estimated_time',        desc => 'Estimated Hours',
-     in_new_bugmail => 1, buglist => 1},
-    {name => 'remaining_time',        desc => 'Remaining Hours', buglist => 1},
+     in_new_bugmail => 1, buglist => 1, is_numeric => 1},
+    {name => 'remaining_time',        desc => 'Remaining Hours', buglist => 1,
+     is_numeric => 1},
     {name => 'deadline',              desc => 'Deadline',
      type => FIELD_TYPE_DATETIME, in_new_bugmail => 1, buglist => 1},
     {name => 'commenter',             desc => 'Commenter'},
     {name => 'flagtypes.name',        desc => 'Flags', buglist => 1},
     {name => 'requestees.login_name', desc => 'Flag Requestee'},
     {name => 'setters.login_name',    desc => 'Flag Setter'},
-    {name => 'work_time',             desc => 'Hours Worked', buglist => 1},
+    {name => 'work_time',             desc => 'Hours Worked', buglist => 1,
+     is_numeric => 1},
     {name => 'percentage_complete',   desc => 'Percentage Complete',
-     buglist => 1},
+     buglist => 1, is_numeric => 1},
     {name => 'content',               desc => 'Content'},
     {name => 'attach_data.thedata',   desc => 'Attachment data'},
     {name => "owner_idle_time",       desc => "Time Since Assignee Touched"},
@@ -273,6 +288,13 @@ sub _check_description {
 
 sub _check_enter_bug { return $_[1] ? 1 : 0; }
 
+sub _check_is_numeric {
+    my ($invocant, $value, undef, $params) = @_;
+    my $type = blessed($invocant) ? $invocant->type : $params->{type};
+    return 1 if $type == FIELD_TYPE_BUG_ID;
+    return $value ? 1 : 0;
+}
+
 sub _check_mailhead { return $_[1] ? 1 : 0; }
 
 sub _check_name {
@@ -779,6 +801,21 @@ a boolean specifying whether or not the field is mandatory;
 
 sub is_mandatory { return $_[0]->{is_mandatory} }
 
+=over
+
+=item C<is_numeric>
+
+A boolean specifying whether or not this field logically contains
+numeric (integer, decimal, or boolean) values. By "logically contains" we
+mean that the user inputs numbers into the value of the field in the UI.
+This is mostly used by L<Bugzilla::Search>.
+
+=back
+
+=cut
+
+sub is_numeric { return $_[0]->{is_numeric} }
+
 
 =pod
 
@@ -821,6 +858,7 @@ They will throw an error if you try to set the values to something invalid.
 
 sub set_description    { $_[0]->set('description', $_[1]); }
 sub set_enter_bug      { $_[0]->set('enter_bug',   $_[1]); }
+sub set_is_numeric     { $_[0]->set('is_numeric',  $_[1]); }
 sub set_obsolete       { $_[0]->set('obsolete',    $_[1]); }
 sub set_sortkey        { $_[0]->set('sortkey',     $_[1]); }
 sub set_in_new_bugmail { $_[0]->set('mailhead',    $_[1]); }
@@ -1095,6 +1133,7 @@ sub populate_field_definitions {
             $field->set_buglist($def->{buglist});
             $field->_set_type($def->{type}) if $def->{type};
             $field->set_is_mandatory($def->{is_mandatory});
+            $field->set_is_numeric($def->{is_numeric});
             $field->update();
         }
         else {
index 4dcf0d63788fd5eafde4d5b94495c754c01ac1ad..47c8873fa388da47df2c548462aa995182c139eb 100644 (file)
@@ -115,6 +115,11 @@ sub update_fielddefs_definition {
     # 2010-04-05 dkl@redhat.com - Bug 479400
     _migrate_field_visibility_value();
 
+    $dbh->bz_add_column('fielddefs', 'is_numeric',
+        {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'});
+    $dbh->do('UPDATE fielddefs SET is_numeric = 1 WHERE type = '
+             . FIELD_TYPE_BUG_ID);
+
     # Remember, this is not the function for adding general table changes.
     # That is below. Add new changes to the fielddefs table above this
     # comment.
index d81e26c16b7e7b74ff62f6d8c6356890c5395114..7dc0f2c0079e4fd9d4a6ae7fa88e07fb05377f7f 100644 (file)
@@ -129,6 +129,26 @@ use Storable qw(dclone);
 # Constants #
 #############
 
+# This is the regex for real numbers from Regexp::Common, modified to be
+# more readable.
+use constant NUMBER_REGEX => qr/
+    ^[+-]?      # A sign, optionally.
+
+    (?=\d|\.)   # Then either a digit or "."
+    \d*         # Followed by many other digits
+    (?:
+        \.      # Followed possibly by some decimal places
+        (?:\d*)
+    )?
+    (?:         # Followed possibly by an exponent.
+        [Ee]
+        [+-]?
+        \d+
+    )?
+    $
+/x;
+
 # If you specify a search type in the boolean charts, this describes
 # which operator maps to which internal function here.
 use constant OPERATORS => {
@@ -188,6 +208,17 @@ use constant OPERATOR_REVERSE => {
     # casesubstring, anyexact, allwords, allwordssubstr
 };
 
+# For these operators, even if a field is numeric (is_numeric returns true),
+# we won't treat the input like a number.
+use constant NON_NUMERIC_OPERATORS => qw(
+    changedafter
+    changedbefore
+    changedfrom
+    changedto
+    regexp
+    notregexp
+);
+
 use constant OPERATOR_FIELD_OVERRIDE => {
     # User fields
     'attachments.submitter' => {
@@ -1568,9 +1599,9 @@ sub _handle_chart {
     # parameters. If the user does pass multiple parameters, they will
     # become a space-separated string for those search functions.
     #
-    # all_values and all_quoted are for search functions that do operate
+    # all_values is for search functions that do operate
     # on multiple values, like anyexact.
-
+    
     my %search_args = (
         chart_id   => $sql_chart_id,
         sequence   => $or_id,
@@ -1578,11 +1609,11 @@ sub _handle_chart {
         full_field => $full_field,
         operator   => $operator,
         value      => $string_value,
-        quoted     => $dbh->quote($string_value),
         all_values => $value,
         joins      => [],
         having     => [],
     );
+    $search_args{quoted} = $self->_quote_unless_numeric(\%search_args);
     # This should add a "term" selement to %search_args.
     $self->do_search_function(\%search_args);
     
@@ -1596,7 +1627,7 @@ sub _handle_chart {
     
     return \%search_args;
 }
-   
+
 ##################################
 # do_search_function And Helpers #
 ##################################
@@ -1713,6 +1744,29 @@ sub _get_operator_field_override {
 # Search Function Helpers #
 ###########################
 
+# When we're doing a numeric search against a numeric column, we want to
+# just put a number into the SQL instead of a string. On most DBs, this
+# is just a performance optimization, but on SQLite it actually changes
+# the behavior of some searches.
+sub _quote_unless_numeric {
+    my ($self, $args, $value) = @_;
+    if (!defined $value) {
+        $value = $args->{value};
+    }
+    my ($field, $operator) = @$args{qw(field operator)};
+    
+    my $numeric_operator = !grep { $_ eq $operator } NON_NUMERIC_OPERATORS;
+    my $numeric_field = $self->_chart_fields->{$field}->is_numeric;
+    my $numeric_value = ($value =~ NUMBER_REGEX) ? 1 : 0;
+    my $is_numeric = $numeric_operator && $numeric_field && $numeric_value;
+    if ($is_numeric) {
+        my $quoted = $value;
+        trick_taint($quoted);
+        return $quoted;
+    }
+    return Bugzilla->dbh->quote($value);
+}
+
 sub build_subselect {
     my ($outer, $inner, $table, $cond) = @_;
     return "$outer IN (SELECT $inner FROM $table WHERE $cond)";
@@ -2726,7 +2780,7 @@ sub _anyexact {
     my $dbh = Bugzilla->dbh;
     
     my @list = $self->_all_values($args, ',');
-    @list = map { $dbh->quote($_) } @list;
+    @list = map { $self->_quote_unless_numeric($args, $_) } @list;
     
     if (@list) {
         $args->{term} = $dbh->sql_in($full_field, \@list);