[Rt-commit] rt branch 5.0/cf-numeric-comparison created. rt-5.0.2-66-gadb5ec517e

BPS Git Server git at git.bestpractical.com
Tue Feb 15 20:51:14 UTC 2022


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "rt".

The branch, 5.0/cf-numeric-comparison has been created
        at  adb5ec517ed648275f733d0e4cb74866d5149046 (commit)

- Log -----------------------------------------------------------------
commit adb5ec517ed648275f733d0e4cb74866d5149046
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Feb 15 03:57:43 2022 +0800

    Test searching/sorting cf values numerically

diff --git a/t/ticket/search_by_cf_numeric.t b/t/ticket/search_by_cf_numeric.t
new file mode 100644
index 0000000000..edc73767a7
--- /dev/null
+++ b/t/ticket/search_by_cf_numeric.t
@@ -0,0 +1,55 @@
+
+use strict;
+use warnings;
+
+use RT::Test nodata => 1, tests => undef;
+
+{
+    no warnings 'redefine';
+    use RT::CustomField;
+    *RT::CustomField::IsNumeric = sub { 1 }
+}
+
+my $queue = RT::Test->load_or_create_queue( Name => 'General' );
+my $cf    = RT::Test->load_or_create_custom_field( Name => 'test_cf', Queue => $queue->id, Type => 'FreeformSingle' );
+my $cfid = $cf->id;
+
+my $cf2    = RT::Test->load_or_create_custom_field( Name => 'test_cf2', Queue => $queue->id, Type => 'FreeformSingle' );
+my $cf2id = $cf2->id;
+
+my @tickets = RT::Test->create_tickets(
+    { Queue   => $queue->Name },
+    { Subject => 'Big',   "CustomField-$cfid" => 12, "CustomField-$cf2id" => 5 },
+    { Subject => 'Small', "CustomField-$cfid" => 3, "CustomField-$cf2id" => 10 },
+);
+
+my $tickets = RT::Tickets->new( RT->SystemUser );
+$tickets->FromSQL(q{Queue = 'General' AND CF.test_cf > 5 });
+is( $tickets->Count,     1,               'Found 1 ticket' );
+is( $tickets->First->id, $tickets[0]->id, 'Found the big ticket' );
+
+$tickets->FromSQL(q{Queue = 'General' AND CF.test_cf = 12 });
+is( $tickets->Count,     1,               'Found 1 ticket' );
+is( $tickets->First->id, $tickets[0]->id, 'Found the big ticket' );
+
+$tickets->FromSQL(q{Queue = 'General' AND CF.test_cf < 5});
+is( $tickets->Count,     1,               'Found 1 ticket' );
+is( $tickets->First->id, $tickets[1]->id, 'Found the small ticket' );
+
+$tickets->FromSQL(q{Queue = 'General' AND CF.test_cf = 3});
+is( $tickets->Count,     1,               'Found 1 ticket' );
+is( $tickets->First->id, $tickets[1]->id, 'Found the small ticket' );
+
+$tickets->FromSQL(q{Queue = 'General' AND CF.test_cf < CF.test_cf2 });
+is( $tickets->Count,     1,               'Found 1 ticket' );
+is( $tickets->First->id, $tickets[1]->id, 'Found the small ticket' );
+
+$tickets->FromSQL(q{Queue = 'General'});
+is( $tickets->Count, 2, 'Found 2 tickets' );
+$tickets->OrderByCols( { FIELD => 'CustomField.test_cf' } );
+is( $tickets->First->id, $tickets[1]->id, 'Small ticket first' );
+
+$tickets->OrderByCols( { FIELD => 'CustomField.test_cf', ORDER => 'DESC' } );
+is( $tickets->First->id, $tickets[0]->id, 'Big ticket first' );
+
+done_testing;

commit f85a093e63512cdd3a8f19cf3eaf9cfdb036b881
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Sat Feb 12 00:32:46 2022 +0800

    Support to search/sort cf values numerically

diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
index a89ad6a4b9..e98198cf02 100644
--- a/lib/RT/CustomField.pm
+++ b/lib/RT/CustomField.pm
@@ -2385,6 +2385,10 @@ sub CleanupDefaultValues {
     }
 }
 
+sub IsNumeric {
+    my $self = shift;
+}
+
 =head2 id
 
 Returns the current value of id. 
diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index caf1d035b1..2f16439194 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -169,8 +169,15 @@ sub _OrderByCF {
         ENTRYAGGREGATOR => 'AND'
     );
 
-    return { %$row, ALIAS => $CFvs,  FIELD => 'SortOrder' },
-           { %$row, ALIAS => $ocfvs, FIELD => 'Content' };
+    return { %$row, ALIAS => $CFvs, FIELD => 'SortOrder' },
+        {
+            %$row,
+            ALIAS => $ocfvs,
+            FIELD => 'Content',
+            blessed $cf && $cf->IsNumeric
+            ? ( FUNCTION => $self->_CastToDecimal('Content') )
+            : ()
+        };
 }
 
 sub OrderByCols {
@@ -529,9 +536,20 @@ sub _LimitCustomField {
 
 
     my $fix_op = sub {
+        my %args = @_;
+
+        if (   $args{'FIELD'} eq 'Content'
+            && blessed $cf
+            && $cf->IsNumeric
+            && ( !$args{QUOTEVALUE} || Scalar::Util::looks_like_number($args{'VALUE'}) ) )
+        {
+            $args{QUOTEVALUE} = 0;
+            $args{FUNCTION} = $self->_CastToDecimal( "$args{ALIAS}.$args{FIELD}" );
+            return %args;
+        }
+
         return @_ unless RT->Config->Get('DatabaseType') eq 'Oracle';
 
-        my %args = @_;
         return %args unless $args{'FIELD'} eq 'LargeContent';
 
         my $op = $args{'OPERATOR'};
@@ -827,17 +845,18 @@ sub _LimitCustomField {
         );
     } else {
         # Otherwise, go looking at the Content
-        $self->Limit(
+        $self->Limit( $fix_op->(
             %args,
             ALIAS    => $ocfvalias,
             FIELD    => 'Content',
             OPERATOR => $op,
             VALUE    => $value,
             CASESENSITIVE => 0,
-        );
+        ) );
     }
 
-    if (!$value_is_long and $op eq "=") {
+    if ( ( blessed($cf) and $cf->IsNumeric ) or ( !$value_is_long and $op eq "=" ) ) {
+        # Skip LargeContent comparison for numeric values.
         # Doesn't matter what LargeContent contains, as it cannot match
         # the short value.
     } elsif (!$value_is_long and $op =~ /^(!=|<>)$/) {
@@ -1140,6 +1159,23 @@ sub DistinctFieldValues {
     return @values;
 }
 
+sub _CastToDecimal {
+    my $self = shift;
+    my $field = shift or return;
+
+    my $db_type = RT->Config->Get('DatabaseType');
+    if ( $db_type eq 'Oracle' ) {
+        return "TO_NUMBER($field)";
+    }
+    elsif ( $db_type eq 'mysql' ) {
+        # mysql's CAST decimal requires precision specification, which we don't know.
+        return "($field+0)";
+    }
+    else {
+        return "CAST($field AS DECIMAL)";
+    }
+}
+
 RT::Base->_ImportOverlays();
 
 1;
diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 4ffbd6ab61..b957188969 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -1568,7 +1568,14 @@ sub OrderByCols {
                         ENTRYAGGREGATOR => 'AND'
                     );
                     push @res, { %$row, ALIAS => $CFvs, FIELD => 'SortOrder' },
-                        { %$row, ALIAS => $ocfvs, FIELD => 'Content' };
+                        {
+                            %$row,
+                            ALIAS => $ocfvs,
+                            FIELD => 'Content',
+                            blessed $cf && $cf->IsNumeric
+                            ? ( FUNCTION => $self->_CastToDecimal('Content') )
+                            : ()
+                        };
                 }
                 else {
                     RT->Logger->warning("Couldn't load user custom field $cf_name");
@@ -3462,28 +3469,39 @@ sub _parser {
                 $value = "main.$value" if $class eq 'RT::Tickets' && $value =~ /^\w+$/;
 
                 if ( $class eq 'RT::ObjectCustomFieldValues' ) {
+                    my $cast_to;
+                    if ( $meta->[0] eq 'CUSTOMFIELD' ) {
+                        my ($object, $field, $cf, $column) = $self->_CustomFieldDecipher( $subkey );
+                        if ( $cf && $cf->IsNumeric ) {
+                            $cast_to = 'DECIMAL';
+                        }
+                    }
+
                     if ( RT->Config->Get('DatabaseType') eq 'Pg' ) {
-                        my $cast_to;
-                        if ($subkey) {
+                        if ( !$cast_to ) {
+                            if ($subkey) {
 
-                            # like Requestor.id
-                            if ( $subkey eq 'id' ) {
-                                $cast_to = 'INTEGER';
-                            }
-                        }
-                        elsif ( my $meta = $self->RecordClass->_ClassAccessible->{$key} ) {
-                            if ( $meta->{is_numeric} ) {
-                                $cast_to = 'INTEGER';
+                                # like Requestor.id
+                                if ( $subkey eq 'id' ) {
+                                    $cast_to = 'INTEGER';
+                                }
                             }
-                            elsif ( $meta->{type} eq 'datetime' ) {
-                                $cast_to = 'TIMESTAMP';
+                            elsif ( my $meta = $self->RecordClass->_ClassAccessible->{$key} ) {
+                                if ( $meta->{is_numeric} ) {
+                                    $cast_to = 'INTEGER';
+                                }
+                                elsif ( $meta->{type} eq 'datetime' ) {
+                                    $cast_to = 'TIMESTAMP';
+                                }
                             }
                         }
-
                         $value = "CAST($value AS $cast_to)" if $cast_to;
                     }
                     elsif ( RT->Config->Get('DatabaseType') eq 'Oracle' ) {
-                        if ($subkey) {
+                        if ( $cast_to && $cast_to eq 'DECIMAL' ) {
+                            $value = "TO_NUMBER($value)";
+                        }
+                        elsif ($subkey) {
 
                             # like Requestor.id
                             if ( $subkey eq 'id' ) {

-----------------------------------------------------------------------


hooks/post-receive
-- 
rt


More information about the rt-commit mailing list